diff --git a/Migrator.net/Migrator.Framework/ITransformationProvider.cs b/Migrator.net/Migrator.Framework/ITransformationProvider.cs
index 46d77199f..30a9bf4fd 100644
--- a/Migrator.net/Migrator.Framework/ITransformationProvider.cs
+++ b/Migrator.net/Migrator.Framework/ITransformationProvider.cs
@@ -3,23 +3,23 @@ using System.Data;
using System.Collections.Generic;
namespace Migrator.Framework
-{
+{
///
/// The main interface to use in Migrations to make changes on a database schema.
///
public interface ITransformationProvider : IDisposable
{
-
+
///
/// Get this provider or a NoOp provider if you are not running in the context of 'provider'.
///
- ITransformationProvider this[string provider] { get;}
-
+ ITransformationProvider this[string provider] { get; }
+
///
/// The list of Migrations currently applied to the database.
///
List AppliedMigrations { get; }
-
+
ILogger Logger { get; set; }
///
@@ -32,7 +32,7 @@ namespace Migrator.Framework
/// Properties that can be ORed together
/// The default value of the column if no value is given in a query
void AddColumn(string table, string column, DbType type, int size, ColumnProperty property, object defaultValue);
-
+
///
/// Add a column to an existing table
///
@@ -40,7 +40,7 @@ namespace Migrator.Framework
/// The name of the new column
/// The data type for the new columnd
void AddColumn(string table, string column, DbType type);
-
+
///
/// Add a column to an existing table
///
@@ -49,7 +49,7 @@ namespace Migrator.Framework
/// The data type for the new columnd
/// The precision or size of the column
void AddColumn(string table, string column, DbType type, int size);
-
+
///
/// Add a column to an existing table
///
@@ -59,7 +59,7 @@ namespace Migrator.Framework
/// The precision or size of the column
/// Properties that can be ORed together
void AddColumn(string table, string column, DbType type, int size, ColumnProperty property);
-
+
///
/// Add a column to an existing table
///
@@ -194,6 +194,15 @@ namespace Migrator.Framework
///
void GenerateForeignKey(string foreignTable, string primaryTable, ForeignKeyConstraint constraint);
+ ///
+ /// Add an Index to a table
+ ///
+ /// The name of the index to add.
+ /// The name of the table that will get the index.
+ /// If the index will be unique
+ /// The name of the column or columns that are in the index.
+ void AddIndex(string name, string table, bool unique, params string[] columns);
+
///
/// Add a primary key to a table
///
@@ -209,7 +218,7 @@ namespace Migrator.Framework
/// The name of the table that will get the constraint
/// The name of the column or columns that will get the constraint.
void AddUniqueConstraint(string name, string table, params string[] columns);
-
+
///
/// Add a constraint to a table
///
@@ -217,7 +226,7 @@ namespace Migrator.Framework
/// The name of the table that will get the constraint
/// The check constraint definition.
void AddCheckConstraint(string name, string table, string checkSql);
-
+
///
/// Add a table
///
@@ -244,7 +253,7 @@ namespace Migrator.Framework
/// The name of the table that will get the new column
/// An instance of a Column with the specified properties and the name of an existing column
void ChangeColumn(string table, Column column);
-
+
///
/// Check to see if a column exists
///
@@ -257,7 +266,7 @@ namespace Migrator.Framework
/// Commit the running transction
///
void Commit();
-
+
///
/// Check to see if a constraint exists
///
@@ -265,7 +274,7 @@ namespace Migrator.Framework
/// The table that the constraint lives on.
///
bool ConstraintExists(string table, string name);
-
+
///
/// Check to see if a primary key constraint exists on the table
///
@@ -273,7 +282,7 @@ namespace Migrator.Framework
/// The table that the constraint lives on.
///
bool PrimaryKeyExists(string table, string name);
-
+
///
/// Execute an arbitrary SQL query
///
@@ -294,14 +303,14 @@ namespace Migrator.Framework
/// The SQL to execute.
/// A single value that is returned.
object ExecuteScalar(string sql);
-
+
///
/// Get the information about the columns in a table
///
/// The table name that you want the columns for.
///
Column[] GetColumns(string table);
-
+
///
/// Get information about a single column in a table
///
@@ -309,13 +318,13 @@ namespace Migrator.Framework
/// The column name for which you want information.
///
Column GetColumnByName(string table, string column);
-
+
///
/// Get the names of all of the tables
///
/// The names of all the tables.
string[] GetTables();
-
+
///
/// Insert data into a table
///
@@ -348,13 +357,13 @@ namespace Migrator.Framework
///
/// The version number of the migration that was applied
void MigrationApplied(long version);
-
+
///
/// Marks a Migration version number as having been rolled back from the database
///
/// The version number of the migration that was removed
void MigrationUnApplied(long version);
-
+
///
/// Remove an existing column from a table
///
@@ -375,20 +384,27 @@ namespace Migrator.Framework
/// The table that contains the foreign key.
/// The name of the constraint to remove
void RemoveConstraint(string table, string name);
-
+
+ ///
+ /// Remove an existing index
+ ///
+ /// The table that contains the index.
+ /// The name of the index to remove
+ void RemoveIndex(string table, string name);
+
///
/// Remove an existing table
///
/// The name of the table
void RemoveTable(string tableName);
-
+
///
/// Rename an existing table
///
/// The old name of the table
/// The new name of the table
void RenameTable(string oldName, string newName);
-
+
///
/// Rename an existing table
///
@@ -396,12 +412,12 @@ namespace Migrator.Framework
/// The old name of the column
/// The new name of the column
void RenameColumn(string tableName, string oldColumnName, string newColumnName);
-
+
///
/// Rollback the currently running transaction.
///
void Rollback();
-
+
///
/// Get values from a table
///
@@ -418,7 +434,7 @@ namespace Migrator.Framework
/// The table to select from
///
IDataReader Select(string what, string from);
-
+
///
/// Get a single value from a table
///
@@ -435,14 +451,14 @@ namespace Migrator.Framework
/// The table to select from
///
object SelectScalar(string what, string from);
-
+
///
/// Check if a table already exists
///
/// The name of the table that you want to check on.
///
bool TableExists(string tableName);
-
+
///
/// Update the values in a table
///
@@ -451,7 +467,7 @@ namespace Migrator.Framework
/// The values for the columns in the same order as the names.
///
int Update(string table, string[] columns, string[] columnValues);
-
+
///
/// Update the values in a table
///
@@ -461,7 +477,7 @@ namespace Migrator.Framework
/// A where clause to limit the update
///
int Update(string table, string[] columns, string[] values, string where);
-
+
IDbCommand GetCommand();
void ExecuteSchemaBuilder(SchemaBuilder.SchemaBuilder schemaBuilder);
diff --git a/Migrator.net/Migrator.Providers/NoOpTransformationProvider.cs b/Migrator.net/Migrator.Providers/NoOpTransformationProvider.cs
index 48f666658..4b7903605 100644
--- a/Migrator.net/Migrator.Providers/NoOpTransformationProvider.cs
+++ b/Migrator.net/Migrator.Providers/NoOpTransformationProvider.cs
@@ -1,3 +1,4 @@
+using System;
using System.Data;
using Migrator.Framework;
using ForeignKeyConstraint=Migrator.Framework.ForeignKeyConstraint;
@@ -53,7 +54,12 @@ namespace Migrator.Providers
{
// No Op
}
-
+
+ public void RemoveIndex(string table, string name)
+ {
+ // No Op
+ }
+
public void AddTable(string name, params Column[] columns)
{
// No Op
@@ -129,6 +135,16 @@ namespace Migrator.Providers
// No Op
}
+ public void AddIndex(string name, string table, params string[] columns)
+ {
+ //No Op
+ }
+
+ public void AddIndex(string name, string table, bool unique, params string[] columns)
+ {
+ //No Op
+ }
+
public void AddPrimaryKey(string name, string table, params string[] columns)
{
// No Op
diff --git a/Migrator.net/Migrator.Providers/TransformationProvider.cs b/Migrator.net/Migrator.Providers/TransformationProvider.cs
index 964780746..8ee5c718f 100644
--- a/Migrator.net/Migrator.Providers/TransformationProvider.cs
+++ b/Migrator.net/Migrator.Providers/TransformationProvider.cs
@@ -1,555 +1,577 @@
-#region License
-
-//The contents of this file are subject to the Mozilla Public License
-//Version 1.1 (the "License"); you may not use this file except in
-//compliance with the License. You may obtain a copy of the License at
-//http://www.mozilla.org/MPL/
-//Software distributed under the License is distributed on an "AS IS"
-//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
-//License for the specific language governing rights and limitations
-//under the License.
-
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Data;
-using Migrator.Framework;
-using Migrator.Framework.SchemaBuilder;
-using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint;
-using Migrator.Framework.Loggers;
-
-namespace Migrator.Providers
-{
- ///
- /// Base class for every transformation providers.
- /// A 'tranformation' is an operation that modifies the database.
- ///
- public abstract class TransformationProvider : ITransformationProvider
- {
- private ILogger _logger;
- protected IDbConnection _connection;
- private IDbTransaction _transaction;
- private List _appliedMigrations;
-
- protected readonly string _connectionString;
- protected Dialect _dialect;
-
- private readonly ForeignKeyConstraintMapper constraintMapper = new ForeignKeyConstraintMapper();
-
- protected TransformationProvider(Dialect dialect, string connectionString)
- {
- _dialect = dialect;
- _connectionString = connectionString;
- _logger = new Logger(false);
- }
-
- ///
- /// Returns the event logger
- ///
- public virtual ILogger Logger
- {
- get { return _logger; }
- set { _logger = value; }
- }
-
- public Dialect Dialect
- {
- get { return _dialect; }
- }
-
- public ITransformationProvider this[string provider]
- {
- get
- {
- if (null != provider && IsThisProvider(provider))
- return this;
-
- return NoOpTransformationProvider.Instance;
- }
- }
-
- public bool IsThisProvider(string provider)
- {
- // XXX: This might need to be more sophisticated. Currently just a convention
- return GetType().Name.ToLower().StartsWith(provider.ToLower());
- }
-
- public virtual Column[] GetColumns(string table)
- {
- List columns = new List();
- using (
- IDataReader reader =
- ExecuteQuery(
- String.Format("select COLUMN_NAME, IS_NULLABLE from information_schema.columns where table_name = '{0}'", table)))
- {
- while (reader.Read())
- {
- Column column = new Column(reader.GetString(0), DbType.String);
- string nullableStr = reader.GetString(1);
- bool isNullable = nullableStr == "YES";
- column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull;
-
- columns.Add(column);
- }
- }
-
- return columns.ToArray();
- }
-
- public virtual Column GetColumnByName(string table, string columnName)
- {
- return Array.Find(GetColumns(table),
- delegate(Column column)
- {
- return column.Name == columnName;
- });
- }
-
- public virtual string[] GetTables()
- {
- List tables = new List();
- using (IDataReader reader = ExecuteQuery("SELECT table_name FROM information_schema.tables"))
- {
- while (reader.Read())
- {
- tables.Add((string)reader[0]);
- }
- }
- return tables.ToArray();
- }
-
- public virtual void RemoveForeignKey(string table, string name)
- {
- RemoveConstraint(table, name);
- }
-
- public virtual void RemoveConstraint(string table, string name)
- {
- if (TableExists(table) && ConstraintExists(table, name))
- {
- table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table;
- name = _dialect.ConstraintNameNeedsQuote ? _dialect.Quote(name) : name;
- ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP CONSTRAINT {1}", table, name));
- }
- }
-
- public virtual void AddTable(string table, string engine, string columns)
- {
- table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table;
- string sqlCreate = String.Format("CREATE TABLE {0} ({1})", table, columns);
- ExecuteNonQuery(sqlCreate);
- }
-
- ///
- /// Add a new table
- ///
- /// Table name
- /// Columns
- ///
- /// Adds the Test table with two columns:
- ///
- /// Database.AddTable("Test",
- /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey),
- /// new Column("Title", typeof(string), 100)
- /// );
- ///
- ///
- public virtual void AddTable(string name, params Column[] columns)
- {
- // Most databases don't have the concept of a storage engine, so default is to not use it.
- AddTable(name, null, columns);
- }
-
- ///
- /// Add a new table
- ///
- /// Table name
- /// Columns
- /// the database storage engine to use
- ///
- /// Adds the Test table with two columns:
- ///
- /// Database.AddTable("Test", "INNODB",
- /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey),
- /// new Column("Title", typeof(string), 100)
- /// );
- ///
- ///
- public virtual void AddTable(string name, string engine, params Column[] columns)
- {
-
- if (TableExists(name))
- {
- Logger.Warn("Table {0} already exists", name);
- return;
- }
-
- List pks = GetPrimaryKeys(columns);
- bool compoundPrimaryKey = pks.Count > 1;
-
- List columnProviders = new List(columns.Length);
- foreach (Column column in columns)
- {
- // Remove the primary key notation if compound primary key because we'll add it back later
- if (compoundPrimaryKey && column.IsPrimaryKey)
- column.ColumnProperty = ColumnProperty.Unsigned | ColumnProperty.NotNull;
-
- ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column);
- columnProviders.Add(mapper);
- }
-
- string columnsAndIndexes = JoinColumnsAndIndexes(columnProviders);
- AddTable(name, engine, columnsAndIndexes);
-
- if (compoundPrimaryKey)
- {
- AddPrimaryKey(String.Format("PK_{0}", name), name, pks.ToArray());
- }
- }
-
- public List GetPrimaryKeys(IEnumerable columns)
- {
- List pks = new List();
- foreach (Column col in columns)
- {
- if (col.IsPrimaryKey)
- pks.Add(col.Name);
- }
- return pks;
- }
-
- public virtual void RemoveTable(string name)
- {
- if (TableExists(name))
- ExecuteNonQuery(String.Format("DROP TABLE {0}", name));
- }
-
- public virtual void RenameTable(string oldName, string newName)
- {
- if (TableExists(newName))
- throw new MigrationException(String.Format("Table with name '{0}' already exists", newName));
-
- if (TableExists(oldName))
- ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName));
- }
-
- public virtual void RenameColumn(string tableName, string oldColumnName, string newColumnName)
- {
- if (ColumnExists(tableName, newColumnName))
- throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName));
-
- if (ColumnExists(tableName, oldColumnName))
- ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, oldColumnName, newColumnName));
- }
-
- public virtual void AddColumn(string table, string sqlColumn)
- {
- ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD COLUMN {1}", table, sqlColumn));
- }
-
- public virtual void RemoveColumn(string table, string column)
- {
- if (ColumnExists(table, column))
- {
- ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP COLUMN {1} ", table, column));
- }
- }
-
- public virtual bool ColumnExists(string table, string column)
- {
- try
- {
- ExecuteNonQuery(String.Format("SELECT {0} FROM {1}", column, table));
- return true;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- public virtual void ChangeColumn(string table, Column column)
- {
- if (!ColumnExists(table, column.Name))
- {
- Logger.Warn("Column {0}.{1} does not exist", table, column.Name);
- return;
- }
-
- ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column);
- ChangeColumn(table, mapper.ColumnSql);
- }
-
- public virtual void ChangeColumn(string table, string sqlColumn)
- {
- ExecuteNonQuery(String.Format("ALTER TABLE {0} ALTER COLUMN {1}", table, sqlColumn));
- }
-
- public virtual bool TableExists(string table)
- {
- try
- {
- ExecuteNonQuery("SELECT COUNT(*) FROM " + table);
- return true;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- protected virtual string JoinColumnsAndIndexes(IEnumerable columns)
- {
- string indexes = JoinIndexes(columns);
- string columnsAndIndexes = JoinColumns(columns) + (indexes != null ? "," + indexes : String.Empty);
- return columnsAndIndexes;
- }
-
- protected virtual string JoinIndexes(IEnumerable columns)
- {
- List indexes = new List();
- foreach (ColumnPropertiesMapper column in columns)
- {
- string indexSql = column.IndexSql;
- if (indexSql != null)
- indexes.Add(indexSql);
- }
-
- if (indexes.Count == 0)
- return null;
-
- return String.Join(", ", indexes.ToArray());
- }
-
- protected virtual string JoinColumns(IEnumerable columns)
- {
- List columnStrings = new List();
- foreach (ColumnPropertiesMapper column in columns)
- columnStrings.Add(column.ColumnSql);
- return String.Join(", ", columnStrings.ToArray());
- }
-
- ///
- /// Add a new column to an existing table.
- ///
- /// Table to which to add the column
- /// Column name
- /// Date type of the column
- /// Max length of the column
- /// Properties of the column, see ColumnProperty,
- /// Default value
- public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property,
- object defaultValue)
- {
- if (ColumnExists(table, column))
- {
- Logger.Warn("Column {0}.{1} already exists", table, column);
- return;
- }
-
- ColumnPropertiesMapper mapper =
- _dialect.GetAndMapColumnProperties(new Column(column, type, size, property, defaultValue));
-
- AddColumn(table, mapper.ColumnSql);
- }
-
- ///
- ///
- /// AddColumn(string, string, Type, int, ColumnProperty, object)
- ///
- ///
- public virtual void AddColumn(string table, string column, DbType type)
- {
- AddColumn(table, column, type, 0, ColumnProperty.Null, null);
- }
-
- ///
- ///
- /// AddColumn(string, string, Type, int, ColumnProperty, object)
- ///
- ///
- public virtual void AddColumn(string table, string column, DbType type, int size)
- {
- AddColumn(table, column, type, size, ColumnProperty.Null, null);
- }
-
- public void AddColumn(string table, string column, DbType type, object defaultValue)
- {
- if (ColumnExists(table, column))
- {
- Logger.Warn("Column {0}.{1} already exists", table, column);
- return;
- }
-
- ColumnPropertiesMapper mapper =
- _dialect.GetAndMapColumnProperties(new Column(column, type, defaultValue));
-
- AddColumn(table, mapper.ColumnSql);
-
- }
-
- ///
- ///
- /// AddColumn(string, string, Type, int, ColumnProperty, object)
- ///
- ///
- public virtual void AddColumn(string table, string column, DbType type, ColumnProperty property)
- {
- AddColumn(table, column, type, 0, property, null);
- }
-
- ///
- ///
- /// AddColumn(string, string, Type, int, ColumnProperty, object)
- ///
- ///
- public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property)
- {
- AddColumn(table, column, type, size, property, null);
- }
-
- ///
- /// Append a primary key to a table.
- ///
- /// Constraint name
- /// Table name
- /// Primary column names
- public virtual void AddPrimaryKey(string name, string table, params string[] columns)
- {
- if (ConstraintExists(table, name))
- {
- Logger.Warn("Primary key {0} already exists", name);
- return;
- }
- ExecuteNonQuery(
- String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} PRIMARY KEY ({2}) ", table, name,
- String.Join(",", columns)));
- }
-
- public virtual void AddUniqueConstraint(string name, string table, params string[] columns)
- {
- if (ConstraintExists(table, name))
- {
- Logger.Warn("Constraint {0} already exists", name);
- return;
- }
- ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE({2}) ", table, name, string.Join(", ", columns)));
- }
-
- public virtual void AddCheckConstraint(string name, string table, string checkSql)
- {
- if (ConstraintExists(table, name))
- {
- Logger.Warn("Constraint {0} already exists", name);
- return;
- }
- ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} CHECK ({2}) ", table, name, checkSql));
- }
-
- ///
- /// Guesses the name of the foreign key and add it
- ///
- public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn)
- {
- AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn);
- }
-
- ///
- /// Guesses the name of the foreign key and add it
- ///
- ///
- public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable,
- string[] refColumns)
- {
- AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns);
- }
-
- ///
- /// Guesses the name of the foreign key and add it
- ///
- public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable,
- string refColumn, ForeignKeyConstraint constraint)
- {
- AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn,
- constraint);
- }
-
- ///
- /// Guesses the name of the foreign key and add it
- ///
- ///
- public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable,
- string[] refColumns, ForeignKeyConstraint constraint)
- {
- AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns,
- constraint);
- }
-
- ///
- /// Append a foreign key (relation) between two tables.
- /// tables.
- ///
- /// Constraint name
- /// Table name containing the primary key
- /// Primary key column name
- /// Foreign table name
- /// Foreign column name
- public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable,
- string refColumn)
- {
- AddForeignKey(name, primaryTable, new string[] { primaryColumn }, refTable, new string[] { refColumn });
- }
-
- ///
- ///
- /// AddForeignKey(string, string, string, string, string)
- ///
- ///
- public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns)
- {
- AddForeignKey(name, primaryTable, primaryColumns, refTable, refColumns, ForeignKeyConstraint.NoAction);
- }
-
- public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraint constraint)
- {
- AddForeignKey(name, primaryTable, new string[] { primaryColumn }, refTable, new string[] { refColumn },
- constraint);
- }
-
- public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable,
- string[] refColumns, ForeignKeyConstraint constraint)
- {
- if (ConstraintExists(primaryTable, name))
- {
- Logger.Warn("Constraint {0} already exists", name);
- return;
- }
-
- string constraintResolved = constraintMapper.SqlForConstraint(constraint);
- ExecuteNonQuery(
- String.Format(
- "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}) ON UPDATE {5} ON DELETE {6}",
- primaryTable, name, String.Join(",", primaryColumns),
- refTable, String.Join(",", refColumns), constraintResolved, constraintResolved));
- }
-
- ///
- /// Determines if a constraint exists.
- ///
- /// Constraint name
- /// Table owning the constraint
- /// true if the constraint exists.
- public abstract bool ConstraintExists(string table, string name);
-
- public virtual bool PrimaryKeyExists(string table, string name)
- {
- return ConstraintExists(table, name);
- }
-
- public int ExecuteNonQuery(string sql)
- {
- Logger.Trace(sql);
+#region License
+
+//The contents of this file are subject to the Mozilla Public License
+//Version 1.1 (the "License"); you may not use this file except in
+//compliance with the License. You may obtain a copy of the License at
+//http://www.mozilla.org/MPL/
+//Software distributed under the License is distributed on an "AS IS"
+//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+//License for the specific language governing rights and limitations
+//under the License.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using Migrator.Framework;
+using Migrator.Framework.SchemaBuilder;
+using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint;
+using Migrator.Framework.Loggers;
+
+namespace Migrator.Providers
+{
+ ///
+ /// Base class for every transformation providers.
+ /// A 'tranformation' is an operation that modifies the database.
+ ///
+ public abstract class TransformationProvider : ITransformationProvider
+ {
+ private ILogger _logger;
+ protected IDbConnection _connection;
+ private IDbTransaction _transaction;
+ private List _appliedMigrations;
+
+ protected readonly string _connectionString;
+ protected Dialect _dialect;
+
+ private readonly ForeignKeyConstraintMapper constraintMapper = new ForeignKeyConstraintMapper();
+
+ protected TransformationProvider(Dialect dialect, string connectionString)
+ {
+ _dialect = dialect;
+ _connectionString = connectionString;
+ _logger = new Logger(false);
+ }
+
+ ///
+ /// Returns the event logger
+ ///
+ public virtual ILogger Logger
+ {
+ get { return _logger; }
+ set { _logger = value; }
+ }
+
+ public Dialect Dialect
+ {
+ get { return _dialect; }
+ }
+
+ public ITransformationProvider this[string provider]
+ {
+ get
+ {
+ if (null != provider && IsThisProvider(provider))
+ return this;
+
+ return NoOpTransformationProvider.Instance;
+ }
+ }
+
+ public bool IsThisProvider(string provider)
+ {
+ // XXX: This might need to be more sophisticated. Currently just a convention
+ return GetType().Name.ToLower().StartsWith(provider.ToLower());
+ }
+
+ public virtual Column[] GetColumns(string table)
+ {
+ List columns = new List();
+ using (
+ IDataReader reader =
+ ExecuteQuery(
+ String.Format("select COLUMN_NAME, IS_NULLABLE from information_schema.columns where table_name = '{0}'", table)))
+ {
+ while (reader.Read())
+ {
+ Column column = new Column(reader.GetString(0), DbType.String);
+ string nullableStr = reader.GetString(1);
+ bool isNullable = nullableStr == "YES";
+ column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull;
+
+ columns.Add(column);
+ }
+ }
+
+ return columns.ToArray();
+ }
+
+ public virtual Column GetColumnByName(string table, string columnName)
+ {
+ return Array.Find(GetColumns(table),
+ delegate(Column column)
+ {
+ return column.Name == columnName;
+ });
+ }
+
+ public virtual string[] GetTables()
+ {
+ List tables = new List();
+ using (IDataReader reader = ExecuteQuery("SELECT table_name FROM information_schema.tables"))
+ {
+ while (reader.Read())
+ {
+ tables.Add((string)reader[0]);
+ }
+ }
+ return tables.ToArray();
+ }
+
+ public virtual void RemoveForeignKey(string table, string name)
+ {
+ RemoveConstraint(table, name);
+ }
+
+ public virtual void RemoveConstraint(string table, string name)
+ {
+ if (TableExists(table) && ConstraintExists(table, name))
+ {
+ table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table;
+ name = _dialect.ConstraintNameNeedsQuote ? _dialect.Quote(name) : name;
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP CONSTRAINT {1}", table, name));
+ }
+ }
+
+ public void RemoveIndex(string table, string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual void AddTable(string table, string engine, string columns)
+ {
+ table = _dialect.TableNameNeedsQuote ? _dialect.Quote(table) : table;
+ string sqlCreate = String.Format("CREATE TABLE {0} ({1})", table, columns);
+ ExecuteNonQuery(sqlCreate);
+ }
+
+ ///
+ /// Add a new table
+ ///
+ /// Table name
+ /// Columns
+ ///
+ /// Adds the Test table with two columns:
+ ///
+ /// Database.AddTable("Test",
+ /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey),
+ /// new Column("Title", typeof(string), 100)
+ /// );
+ ///
+ ///
+ public virtual void AddTable(string name, params Column[] columns)
+ {
+ // Most databases don't have the concept of a storage engine, so default is to not use it.
+ AddTable(name, null, columns);
+ }
+
+ ///
+ /// Add a new table
+ ///
+ /// Table name
+ /// Columns
+ /// the database storage engine to use
+ ///
+ /// Adds the Test table with two columns:
+ ///
+ /// Database.AddTable("Test", "INNODB",
+ /// new Column("Id", typeof(int), ColumnProperty.PrimaryKey),
+ /// new Column("Title", typeof(string), 100)
+ /// );
+ ///
+ ///
+ public virtual void AddTable(string name, string engine, params Column[] columns)
+ {
+
+ if (TableExists(name))
+ {
+ Logger.Warn("Table {0} already exists", name);
+ return;
+ }
+
+ List pks = GetPrimaryKeys(columns);
+ bool compoundPrimaryKey = pks.Count > 1;
+
+ List columnProviders = new List(columns.Length);
+ foreach (Column column in columns)
+ {
+ // Remove the primary key notation if compound primary key because we'll add it back later
+ if (compoundPrimaryKey && column.IsPrimaryKey)
+ column.ColumnProperty = ColumnProperty.Unsigned | ColumnProperty.NotNull;
+
+ ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column);
+ columnProviders.Add(mapper);
+ }
+
+ string columnsAndIndexes = JoinColumnsAndIndexes(columnProviders);
+ AddTable(name, engine, columnsAndIndexes);
+
+ if (compoundPrimaryKey)
+ {
+ AddPrimaryKey(String.Format("PK_{0}", name), name, pks.ToArray());
+ }
+ }
+
+ public List GetPrimaryKeys(IEnumerable columns)
+ {
+ List pks = new List();
+ foreach (Column col in columns)
+ {
+ if (col.IsPrimaryKey)
+ pks.Add(col.Name);
+ }
+ return pks;
+ }
+
+ public virtual void RemoveTable(string name)
+ {
+ if (TableExists(name))
+ ExecuteNonQuery(String.Format("DROP TABLE {0}", name));
+ }
+
+ public virtual void RenameTable(string oldName, string newName)
+ {
+ if (TableExists(newName))
+ throw new MigrationException(String.Format("Table with name '{0}' already exists", newName));
+
+ if (TableExists(oldName))
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME TO {1}", oldName, newName));
+ }
+
+ public virtual void RenameColumn(string tableName, string oldColumnName, string newColumnName)
+ {
+ if (ColumnExists(tableName, newColumnName))
+ throw new MigrationException(String.Format("Table '{0}' has column named '{1}' already", tableName, newColumnName));
+
+ if (ColumnExists(tableName, oldColumnName))
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} RENAME COLUMN {1} TO {2}", tableName, oldColumnName, newColumnName));
+ }
+
+ public virtual void AddColumn(string table, string sqlColumn)
+ {
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD COLUMN {1}", table, sqlColumn));
+ }
+
+ public virtual void RemoveColumn(string table, string column)
+ {
+ if (ColumnExists(table, column))
+ {
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} DROP COLUMN {1} ", table, column));
+ }
+ }
+
+ public virtual bool ColumnExists(string table, string column)
+ {
+ try
+ {
+ ExecuteNonQuery(String.Format("SELECT {0} FROM {1}", column, table));
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public virtual void ChangeColumn(string table, Column column)
+ {
+ if (!ColumnExists(table, column.Name))
+ {
+ Logger.Warn("Column {0}.{1} does not exist", table, column.Name);
+ return;
+ }
+
+ ColumnPropertiesMapper mapper = _dialect.GetAndMapColumnProperties(column);
+ ChangeColumn(table, mapper.ColumnSql);
+ }
+
+ public virtual void ChangeColumn(string table, string sqlColumn)
+ {
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} ALTER COLUMN {1}", table, sqlColumn));
+ }
+
+ public virtual bool TableExists(string table)
+ {
+ try
+ {
+ ExecuteNonQuery("SELECT COUNT(*) FROM " + table);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ protected virtual string JoinColumnsAndIndexes(IEnumerable columns)
+ {
+ string indexes = JoinIndexes(columns);
+ string columnsAndIndexes = JoinColumns(columns) + (indexes != null ? "," + indexes : String.Empty);
+ return columnsAndIndexes;
+ }
+
+ protected virtual string JoinIndexes(IEnumerable columns)
+ {
+ List indexes = new List();
+ foreach (ColumnPropertiesMapper column in columns)
+ {
+ string indexSql = column.IndexSql;
+ if (indexSql != null)
+ indexes.Add(indexSql);
+ }
+
+ if (indexes.Count == 0)
+ return null;
+
+ return String.Join(", ", indexes.ToArray());
+ }
+
+ protected virtual string JoinColumns(IEnumerable columns)
+ {
+ List columnStrings = new List();
+ foreach (ColumnPropertiesMapper column in columns)
+ columnStrings.Add(column.ColumnSql);
+ return String.Join(", ", columnStrings.ToArray());
+ }
+
+ ///
+ /// Add a new column to an existing table.
+ ///
+ /// Table to which to add the column
+ /// Column name
+ /// Date type of the column
+ /// Max length of the column
+ /// Properties of the column, see ColumnProperty,
+ /// Default value
+ public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property,
+ object defaultValue)
+ {
+ if (ColumnExists(table, column))
+ {
+ Logger.Warn("Column {0}.{1} already exists", table, column);
+ return;
+ }
+
+ ColumnPropertiesMapper mapper =
+ _dialect.GetAndMapColumnProperties(new Column(column, type, size, property, defaultValue));
+
+ AddColumn(table, mapper.ColumnSql);
+ }
+
+ ///
+ ///
+ /// AddColumn(string, string, Type, int, ColumnProperty, object)
+ ///
+ ///
+ public virtual void AddColumn(string table, string column, DbType type)
+ {
+ AddColumn(table, column, type, 0, ColumnProperty.Null, null);
+ }
+
+ ///
+ ///
+ /// AddColumn(string, string, Type, int, ColumnProperty, object)
+ ///
+ ///
+ public virtual void AddColumn(string table, string column, DbType type, int size)
+ {
+ AddColumn(table, column, type, size, ColumnProperty.Null, null);
+ }
+
+ public void AddColumn(string table, string column, DbType type, object defaultValue)
+ {
+ if (ColumnExists(table, column))
+ {
+ Logger.Warn("Column {0}.{1} already exists", table, column);
+ return;
+ }
+
+ ColumnPropertiesMapper mapper =
+ _dialect.GetAndMapColumnProperties(new Column(column, type, defaultValue));
+
+ AddColumn(table, mapper.ColumnSql);
+
+ }
+
+ ///
+ ///
+ /// AddColumn(string, string, Type, int, ColumnProperty, object)
+ ///
+ ///
+ public virtual void AddColumn(string table, string column, DbType type, ColumnProperty property)
+ {
+ AddColumn(table, column, type, 0, property, null);
+ }
+
+ ///
+ ///
+ /// AddColumn(string, string, Type, int, ColumnProperty, object)
+ ///
+ ///
+ public virtual void AddColumn(string table, string column, DbType type, int size, ColumnProperty property)
+ {
+ AddColumn(table, column, type, size, property, null);
+ }
+
+ public void AddIndex(string name, string table, bool unique, params string[] columns)
+ {
+ try
+ {
+ var uniqueText = "";
+ if (unique) uniqueText = "UNIQUE";
+
+ var command = String.Format("CREATE {0} INDEX {1} ON {2} ({3})", uniqueText, name, table, String.Join(",", columns));
+
+ ExecuteNonQuery(command);
+ }
+ catch (Exception e)
+ {
+ Logger.Exception("Unable to add index", e);
+ }
+ }
+
+ ///
+ /// Append a primary key to a table.
+ ///
+ /// Constraint name
+ /// Table name
+ /// Primary column names
+ public virtual void AddPrimaryKey(string name, string table, params string[] columns)
+ {
+ if (ConstraintExists(table, name))
+ {
+ Logger.Warn("Primary key {0} already exists", name);
+ return;
+ }
+ ExecuteNonQuery(
+ String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} PRIMARY KEY ({2}) ", table, name,
+ String.Join(",", columns)));
+ }
+
+ public virtual void AddUniqueConstraint(string name, string table, params string[] columns)
+ {
+ if (ConstraintExists(table, name))
+ {
+ Logger.Warn("Constraint {0} already exists", name);
+ return;
+ }
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE({2}) ", table, name, string.Join(", ", columns)));
+ }
+
+ public virtual void AddCheckConstraint(string name, string table, string checkSql)
+ {
+ if (ConstraintExists(table, name))
+ {
+ Logger.Warn("Constraint {0} already exists", name);
+ return;
+ }
+ ExecuteNonQuery(String.Format("ALTER TABLE {0} ADD CONSTRAINT {1} CHECK ({2}) ", table, name, checkSql));
+ }
+
+ ///
+ /// Guesses the name of the foreign key and add it
+ ///
+ public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable, string refColumn)
+ {
+ AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn);
+ }
+
+ ///
+ /// Guesses the name of the foreign key and add it
+ ///
+ ///
+ public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable,
+ string[] refColumns)
+ {
+ AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns);
+ }
+
+ ///
+ /// Guesses the name of the foreign key and add it
+ ///
+ public virtual void GenerateForeignKey(string primaryTable, string primaryColumn, string refTable,
+ string refColumn, ForeignKeyConstraint constraint)
+ {
+ AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumn, refTable, refColumn,
+ constraint);
+ }
+
+ ///
+ /// Guesses the name of the foreign key and add it
+ ///
+ ///
+ public virtual void GenerateForeignKey(string primaryTable, string[] primaryColumns, string refTable,
+ string[] refColumns, ForeignKeyConstraint constraint)
+ {
+ AddForeignKey("FK_" + primaryTable + "_" + refTable, primaryTable, primaryColumns, refTable, refColumns,
+ constraint);
+ }
+
+ ///
+ /// Append a foreign key (relation) between two tables.
+ /// tables.
+ ///
+ /// Constraint name
+ /// Table name containing the primary key
+ /// Primary key column name
+ /// Foreign table name
+ /// Foreign column name
+ public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable,
+ string refColumn)
+ {
+ AddForeignKey(name, primaryTable, new string[] { primaryColumn }, refTable, new string[] { refColumn });
+ }
+
+ ///
+ ///
+ /// AddForeignKey(string, string, string, string, string)
+ ///
+ ///
+ public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable, string[] refColumns)
+ {
+ AddForeignKey(name, primaryTable, primaryColumns, refTable, refColumns, ForeignKeyConstraint.NoAction);
+ }
+
+ public virtual void AddForeignKey(string name, string primaryTable, string primaryColumn, string refTable, string refColumn, ForeignKeyConstraint constraint)
+ {
+ AddForeignKey(name, primaryTable, new string[] { primaryColumn }, refTable, new string[] { refColumn },
+ constraint);
+ }
+
+ public virtual void AddForeignKey(string name, string primaryTable, string[] primaryColumns, string refTable,
+ string[] refColumns, ForeignKeyConstraint constraint)
+ {
+ if (ConstraintExists(primaryTable, name))
+ {
+ Logger.Warn("Constraint {0} already exists", name);
+ return;
+ }
+
+ string constraintResolved = constraintMapper.SqlForConstraint(constraint);
+ ExecuteNonQuery(
+ String.Format(
+ "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}) ON UPDATE {5} ON DELETE {6}",
+ primaryTable, name, String.Join(",", primaryColumns),
+ refTable, String.Join(",", refColumns), constraintResolved, constraintResolved));
+ }
+
+ ///
+ /// Determines if a constraint exists.
+ ///
+ /// Constraint name
+ /// Table owning the constraint
+ /// true if the constraint exists.
+ public abstract bool ConstraintExists(string table, string name);
+
+ public virtual bool PrimaryKeyExists(string table, string name)
+ {
+ return ConstraintExists(table, name);
+ }
+
+ public int ExecuteNonQuery(string sql)
+ {
+ Logger.Trace(sql);
Logger.ApplyingDBChange(sql);
using (IDbCommand cmd = BuildCommand(sql))
{
@@ -562,11 +584,12 @@ namespace Migrator.Providers
Logger.Warn(ex.Message);
throw;
}
- }
}
-
- private IDbCommand BuildCommand(string sql)
- {
- IDbCommand cmd = _connection.CreateCommand();
+ }
+ }
+
+ private IDbCommand BuildCommand(string sql)
+ {
+ IDbCommand cmd = _connection.CreateCommand();
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
if (_transaction != null)
@@ -574,15 +597,15 @@ namespace Migrator.Providers
cmd.Transaction = _transaction;
}
return cmd;
- }
-
- ///
- /// Execute an SQL query returning results.
- ///
- /// The SQL command.
- /// A data iterator, IDataReader.
- public IDataReader ExecuteQuery(string sql)
- {
+ }
+
+ ///
+ /// Execute an SQL query returning results.
+ ///
+ /// The SQL command.
+ /// A data iterator, IDataReader.
+ public IDataReader ExecuteQuery(string sql)
+ {
Logger.Trace(sql);
using (IDbCommand cmd = BuildCommand(sql))
{
@@ -595,10 +618,11 @@ namespace Migrator.Providers
Logger.Warn("query failed: {0}", cmd.CommandText);
throw;
}
- }
}
-
- public object ExecuteScalar(string sql)
- {
+ }
+ }
+
+ public object ExecuteScalar(string sql)
+ {
Logger.Trace(sql);
using (IDbCommand cmd = BuildCommand(sql))
{
@@ -611,243 +635,247 @@ namespace Migrator.Providers
Logger.Warn("Query failed: {0}", cmd.CommandText);
throw;
}
- }
}
-
- public IDataReader Select(string what, string from)
- {
- return Select(what, from, "1=1");
- }
-
- public virtual IDataReader Select(string what, string from, string where)
- {
- return ExecuteQuery(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where));
- }
-
- public object SelectScalar(string what, string from)
- {
- return SelectScalar(what, from, "1=1");
- }
-
- public virtual object SelectScalar(string what, string from, string where)
- {
- return ExecuteScalar(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where));
- }
-
- public virtual int Update(string table, string[] columns, string[] values)
- {
- return Update(table, columns, values, null);
- }
-
- public virtual int Update(string table, string[] columns, string[] values, string where)
- {
- string namesAndValues = JoinColumnsAndValues(columns, values);
-
- string query = "UPDATE {0} SET {1}";
- if (!String.IsNullOrEmpty(where))
- {
- query += " WHERE " + where;
- }
-
- return ExecuteNonQuery(String.Format(query, table, namesAndValues));
- }
-
- public virtual int Insert(string table, string[] columns, string[] values)
- {
- return ExecuteNonQuery(String.Format("INSERT INTO {0} ({1}) VALUES ({2})", table, String.Join(", ", columns), String.Join(", ", QuoteValues(values))));
- }
-
- public virtual int Delete(string table)
- {
- return Delete(table, (string[])null, (string[]) null);
- }
-
- public virtual int Delete(string table, string[] columns, string[] values)
- {
- if (null == columns || null == values)
- {
- return ExecuteNonQuery(String.Format("DELETE FROM {0}", table));
- }
- else
- {
- return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE ({1})", table, JoinColumnsAndValues(columns, values)));
- }
- }
-
- public virtual int Delete(string table, string wherecolumn, string wherevalue)
- {
- return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE {1} = {2}", table, wherecolumn, QuoteValues(wherevalue)));
- }
-
- ///
- /// Starts a transaction. Called by the migration mediator.
- ///
- public void BeginTransaction()
- {
- if (_transaction == null && _connection != null)
- {
- EnsureHasConnection();
- _transaction = _connection.BeginTransaction(IsolationLevel.ReadCommitted);
- }
- }
-
- protected void EnsureHasConnection()
- {
- if (_connection.State != ConnectionState.Open)
- {
- _connection.Open();
- }
- }
-
- ///
- /// Rollback the current migration. Called by the migration mediator.
- ///
- public virtual void Rollback()
- {
- if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open)
- {
- try
- {
- _transaction.Rollback();
- }
- finally
- {
- _connection.Close();
- }
- }
- _transaction = null;
- }
-
- ///
- /// Commit the current transaction. Called by the migrations mediator.
- ///
- public void Commit()
- {
- if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open)
- {
- try
- {
- _transaction.Commit();
- }
- finally
- {
- _connection.Close();
- }
- }
- _transaction = null;
- }
-
- ///
- /// The list of Migrations currently applied to the database.
- ///
- public List AppliedMigrations
- {
- get
- {
- if(_appliedMigrations == null)
- {
- _appliedMigrations = new List();
- CreateSchemaInfoTable();
- using(IDataReader reader = Select("version","SchemaInfo")){
- while(reader.Read()){
- _appliedMigrations.Add(Convert.ToInt64(reader.GetValue(0)));
- }
- }
- }
- return _appliedMigrations;
- }
- }
-
- ///
- /// Marks a Migration version number as having been applied
- ///
- /// The version number of the migration that was applied
- public void MigrationApplied(long version)
- {
- CreateSchemaInfoTable();
- Insert("SchemaInfo",new string[]{"version"},new string[]{version.ToString()});
- _appliedMigrations.Add(version);
- }
-
- ///
- /// Marks a Migration version number as having been rolled back from the database
- ///
- /// The version number of the migration that was removed
- public void MigrationUnApplied(long version)
- {
- CreateSchemaInfoTable();
- Delete("SchemaInfo", "version", version.ToString());
- _appliedMigrations.Remove(version);
- }
-
- protected void CreateSchemaInfoTable()
- {
- EnsureHasConnection();
- if (!TableExists("SchemaInfo"))
- {
- AddTable("SchemaInfo", new Column("Version", DbType.Int64, ColumnProperty.PrimaryKey));
- }
- }
-
- public void AddColumn(string table, Column column)
- {
- AddColumn(table, column.Name, column.Type, column.Size, column.ColumnProperty, column.DefaultValue);
- }
-
- public void GenerateForeignKey(string primaryTable, string refTable)
- {
- GenerateForeignKey(primaryTable, refTable, ForeignKeyConstraint.NoAction);
- }
-
- public void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraint constraint)
- {
- GenerateForeignKey(primaryTable, refTable + "Id", refTable, "Id", constraint);
- }
-
- public IDbCommand GetCommand()
- {
- return BuildCommand(null);
- }
-
- public void ExecuteSchemaBuilder(SchemaBuilder builder)
- {
- foreach (ISchemaBuilderExpression expr in builder.Expressions)
- expr.Create(this);
- }
-
- public virtual string QuoteValues(string values)
- {
- return QuoteValues(new string[] {values})[0];
- }
-
- public virtual string[] QuoteValues(string[] values)
- {
- return Array.ConvertAll(values,
- delegate(string val) {
- if (null == val)
- return "null";
- else
- return String.Format("'{0}'", val.Replace("'", "''"));
- });
- }
-
- public string JoinColumnsAndValues(string[] columns, string[] values)
- {
- string[] quotedValues = QuoteValues(values);
- string[] namesAndValues = new string[columns.Length];
- for (int i = 0; i < columns.Length; i++)
- {
- namesAndValues[i] = String.Format("{0}={1}", columns[i], quotedValues[i]);
- }
-
- return String.Join(", ", namesAndValues);
- }
-
- public void Dispose()
- {
- if (_connection != null && _connection.State == ConnectionState.Open)
- {
- _connection.Close();
- }
- }
- }
-}
+ }
+ }
+
+ public IDataReader Select(string what, string from)
+ {
+ return Select(what, from, "1=1");
+ }
+
+ public virtual IDataReader Select(string what, string from, string where)
+ {
+ return ExecuteQuery(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where));
+ }
+
+ public object SelectScalar(string what, string from)
+ {
+ return SelectScalar(what, from, "1=1");
+ }
+
+ public virtual object SelectScalar(string what, string from, string where)
+ {
+ return ExecuteScalar(String.Format("SELECT {0} FROM {1} WHERE {2}", what, from, where));
+ }
+
+ public virtual int Update(string table, string[] columns, string[] values)
+ {
+ return Update(table, columns, values, null);
+ }
+
+ public virtual int Update(string table, string[] columns, string[] values, string where)
+ {
+ string namesAndValues = JoinColumnsAndValues(columns, values);
+
+ string query = "UPDATE {0} SET {1}";
+ if (!String.IsNullOrEmpty(where))
+ {
+ query += " WHERE " + where;
+ }
+
+ return ExecuteNonQuery(String.Format(query, table, namesAndValues));
+ }
+
+ public virtual int Insert(string table, string[] columns, string[] values)
+ {
+ return ExecuteNonQuery(String.Format("INSERT INTO {0} ({1}) VALUES ({2})", table, String.Join(", ", columns), String.Join(", ", QuoteValues(values))));
+ }
+
+ public virtual int Delete(string table)
+ {
+ return Delete(table, (string[])null, (string[])null);
+ }
+
+ public virtual int Delete(string table, string[] columns, string[] values)
+ {
+ if (null == columns || null == values)
+ {
+ return ExecuteNonQuery(String.Format("DELETE FROM {0}", table));
+ }
+ else
+ {
+ return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE ({1})", table, JoinColumnsAndValues(columns, values)));
+ }
+ }
+
+ public virtual int Delete(string table, string wherecolumn, string wherevalue)
+ {
+ return ExecuteNonQuery(String.Format("DELETE FROM {0} WHERE {1} = {2}", table, wherecolumn, QuoteValues(wherevalue)));
+ }
+
+ ///
+ /// Starts a transaction. Called by the migration mediator.
+ ///
+ public void BeginTransaction()
+ {
+ if (_transaction == null && _connection != null)
+ {
+ EnsureHasConnection();
+ _transaction = _connection.BeginTransaction(IsolationLevel.ReadCommitted);
+ }
+ }
+
+ protected void EnsureHasConnection()
+ {
+ if (_connection.State != ConnectionState.Open)
+ {
+ _connection.Open();
+ }
+ }
+
+ ///
+ /// Rollback the current migration. Called by the migration mediator.
+ ///
+ public virtual void Rollback()
+ {
+ if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open)
+ {
+ try
+ {
+ _transaction.Rollback();
+ }
+ finally
+ {
+ _connection.Close();
+ }
+ }
+ _transaction = null;
+ }
+
+ ///
+ /// Commit the current transaction. Called by the migrations mediator.
+ ///
+ public void Commit()
+ {
+ if (_transaction != null && _connection != null && _connection.State == ConnectionState.Open)
+ {
+ try
+ {
+ _transaction.Commit();
+ }
+ finally
+ {
+ _connection.Close();
+ }
+ }
+ _transaction = null;
+ }
+
+ ///
+ /// The list of Migrations currently applied to the database.
+ ///
+ public List AppliedMigrations
+ {
+ get
+ {
+ if (_appliedMigrations == null)
+ {
+ _appliedMigrations = new List();
+ CreateSchemaInfoTable();
+ using (IDataReader reader = Select("version", "SchemaInfo"))
+ {
+ while (reader.Read())
+ {
+ _appliedMigrations.Add(Convert.ToInt64(reader.GetValue(0)));
+ }
+ }
+ }
+ return _appliedMigrations;
+ }
+ }
+
+ ///
+ /// Marks a Migration version number as having been applied
+ ///
+ /// The version number of the migration that was applied
+ public void MigrationApplied(long version)
+ {
+ CreateSchemaInfoTable();
+ Insert("SchemaInfo", new string[] { "version" }, new string[] { version.ToString() });
+ _appliedMigrations.Add(version);
+ }
+
+ ///
+ /// Marks a Migration version number as having been rolled back from the database
+ ///
+ /// The version number of the migration that was removed
+ public void MigrationUnApplied(long version)
+ {
+ CreateSchemaInfoTable();
+ Delete("SchemaInfo", "version", version.ToString());
+ _appliedMigrations.Remove(version);
+ }
+
+ protected void CreateSchemaInfoTable()
+ {
+ EnsureHasConnection();
+ if (!TableExists("SchemaInfo"))
+ {
+ AddTable("SchemaInfo", new Column("Version", DbType.Int64, ColumnProperty.PrimaryKey));
+ }
+ }
+
+ public void AddColumn(string table, Column column)
+ {
+ AddColumn(table, column.Name, column.Type, column.Size, column.ColumnProperty, column.DefaultValue);
+ }
+
+ public void GenerateForeignKey(string primaryTable, string refTable)
+ {
+ GenerateForeignKey(primaryTable, refTable, ForeignKeyConstraint.NoAction);
+ }
+
+ public void GenerateForeignKey(string primaryTable, string refTable, ForeignKeyConstraint constraint)
+ {
+ GenerateForeignKey(primaryTable, refTable + "Id", refTable, "Id", constraint);
+ }
+
+ public IDbCommand GetCommand()
+ {
+ return BuildCommand(null);
+ }
+
+ public void ExecuteSchemaBuilder(SchemaBuilder builder)
+ {
+ foreach (ISchemaBuilderExpression expr in builder.Expressions)
+ expr.Create(this);
+ }
+
+ public virtual string QuoteValues(string values)
+ {
+ return QuoteValues(new string[] { values })[0];
+ }
+
+ public virtual string[] QuoteValues(string[] values)
+ {
+ return Array.ConvertAll(values,
+ delegate(string val)
+ {
+ if (null == val)
+ return "null";
+ else
+ return String.Format("'{0}'", val.Replace("'", "''"));
+ });
+ }
+
+ public string JoinColumnsAndValues(string[] columns, string[] values)
+ {
+ string[] quotedValues = QuoteValues(values);
+ string[] namesAndValues = new string[columns.Length];
+ for (int i = 0; i < columns.Length; i++)
+ {
+ namesAndValues[i] = String.Format("{0}={1}", columns[i], quotedValues[i]);
+ }
+
+ return String.Join(", ", namesAndValues);
+ }
+
+ public void Dispose()
+ {
+ if (_connection != null && _connection.State == ConnectionState.Open)
+ {
+ _connection.Close();
+ }
+ }
+ }
+}
diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs
index 35dfd4f0e..a1a09a6b5 100644
--- a/NzbDrone.Core.Test/EpisodeProviderTest.cs
+++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs
@@ -256,6 +256,92 @@ namespace NzbDrone.Core.Test
}
+ [Test]
+ public void IsSeasonIgnored_should_return_true_if_all_episodes_ignored()
+ {
+ var repo = MockLib.GetEmptyRepository();
+ var mocker = new AutoMoqer(MockBehavior.Strict);
+ mocker.SetConstant(repo);
+
+ var episodes = Builder.CreateListOfSize(4)
+ .WhereAll()
+ .Have(c => c.Ignored = true)
+ .Have(c => c.SeriesId = 10)
+ .Have(c => c.SeasonNumber = 2)
+ .Build();
+
+ repo.AddMany(episodes);
+
+ //Act
+ var result = mocker.Resolve().IsIgnored(10, 2);
+
+ //Assert
+ result.Should().BeTrue();
+ }
+
+ [Test]
+ public void IsSeasonIgnored_should_return_false_if_none_of_episodes_are_ignored()
+ {
+ var repo = MockLib.GetEmptyRepository();
+ var mocker = new AutoMoqer(MockBehavior.Strict);
+ mocker.SetConstant(repo);
+
+ var episodes = Builder.CreateListOfSize(4)
+ .WhereAll()
+ .Have(c => c.Ignored = false)
+ .Have(c => c.SeriesId = 10)
+ .Have(c => c.SeasonNumber = 2)
+ .Build();
+
+ repo.AddMany(episodes);
+
+ //Act
+ var result = mocker.Resolve().IsIgnored(10, 2);
+
+ //Assert
+ result.Should().BeFalse();
+ }
+
+ [Test]
+ public void IsSeasonIgnored_should_return_false_if_some_of_episodes_are_ignored()
+ {
+ var repo = MockLib.GetEmptyRepository();
+ var mocker = new AutoMoqer(MockBehavior.Strict);
+ mocker.SetConstant(repo);
+
+ var episodes = Builder.CreateListOfSize(4)
+ .WhereAll()
+ .Have(c => c.SeriesId = 10)
+ .Have(c => c.SeasonNumber = 2)
+ .Have(c => c.Ignored = true)
+ .Build();
+
+ episodes[2].Ignored = false;
+
+
+ repo.AddMany(episodes);
+
+ //Act
+ var result = mocker.Resolve().IsIgnored(10, 2);
+
+ //Assert
+ result.Should().BeFalse();
+ }
+
+ [Test]
+ public void IsSeasonIgnored_should_return_true_if_invalid_series()
+ {
+ var repo = MockLib.GetEmptyRepository();
+ var mocker = new AutoMoqer(MockBehavior.Strict);
+ mocker.SetConstant(repo);
+
+ //Act
+ var result = mocker.Resolve().IsIgnored(10, 2);
+
+ //Assert
+ result.Should().BeTrue();
+ }
+
[Test]
[Explicit]
public void Add_daily_show_episodes()
@@ -276,5 +362,7 @@ namespace NzbDrone.Core.Test
var episodes = episodeProvider.GetEpisodeBySeries(tvDbSeriesId);
episodes.Should().NotBeEmpty();
}
+
+
}
}
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/Framework/MockLib.cs b/NzbDrone.Core.Test/Framework/MockLib.cs
index 08e9aba04..0030f9682 100644
--- a/NzbDrone.Core.Test/Framework/MockLib.cs
+++ b/NzbDrone.Core.Test/Framework/MockLib.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
+using NzbDrone.Core.Datastore;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Repository;
@@ -34,25 +35,30 @@ namespace NzbDrone.Core.Test.Framework
}
}
- public static IRepository GetEmptyRepository()
- {
- return GetEmptyRepository(false);
- }
- public static IRepository GetEmptyRepository(bool enableLogging)
+ public static IRepository GetEmptyRepository(bool enableLogging = false, string fileName = "")
{
Console.WriteLine("Creating an empty SQLite database");
- var provider = ProviderFactory.GetProvider("Data Source=" + Guid.NewGuid() + ".db;Version=3;New=True",
- "System.Data.SQLite");
- var repo = new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations);
+ if (String.IsNullOrWhiteSpace(fileName))
+ {
+ fileName = Guid.NewGuid() + ".db";
+ }
+
+
+ var provider = Connection.GetDataProvider(Connection.GetConnectionString(fileName));
+ var repo = Connection.CreateSimpleRepository(provider);
ForceMigration(repo);
+ Migrations.Run(Connection.GetConnectionString(fileName), false);
+
if (enableLogging)
{
provider.Log = new NlogWriter();
}
-
+ Console.WriteLine("**********************************************************************************");
+ Console.WriteLine("*****************************REPO IS READY****************************************");
+ Console.WriteLine("**********************************************************************************");
return repo;
}
diff --git a/NzbDrone.Core.Test/dbBenchmark.cs b/NzbDrone.Core.Test/dbBenchmark.cs
index 0adf347bb..0691068b8 100644
--- a/NzbDrone.Core.Test/dbBenchmark.cs
+++ b/NzbDrone.Core.Test/dbBenchmark.cs
@@ -17,8 +17,8 @@ namespace NzbDrone.Core.Test
public class DbBenchmark : TestBase
{
const int Episodes_Per_Season = 20;
- private readonly List seasonsNumbers = new List { 1, 2, 3, 4, 5, 6, 7, 8 };
- private readonly List seriesIds = new List { 1, 2, 3, 4, 5, 6, 7, 8 };
+ private readonly List seasonsNumbers = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ private readonly List seriesIds = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
private readonly List episodes = new List();
private readonly List files = new List();
private readonly IRepository repo = MockLib.GetEmptyRepository();
@@ -68,15 +68,16 @@ namespace NzbDrone.Core.Test
}
}
- repo.AddMany(episodes);
- repo.AddMany(files);
+ }
+
+ repo.AddMany(episodes);
+ repo.AddMany(files);
}
- }
[Test]
- public void get_episode_by_series_seasons_episode_x1000()
+ public void get_episode_by_series_seasons_episode_x5000()
{
var epProvider = new EpisodeProvider(repo, null);
@@ -88,7 +89,7 @@ namespace NzbDrone.Core.Test
Console.WriteLine("Starting Test");
var sw = Stopwatch.StartNew();
- for (int i = 0; i < 1000; i++)
+ for (int i = 0; i < 5000; i++)
{
epProvider.GetEpisode(6, random.Next(2, 5), random.Next(2, Episodes_Per_Season - 10)).Should().NotBeNull();
}
@@ -100,7 +101,7 @@ namespace NzbDrone.Core.Test
}
[Test]
- public void get_episode_by_series_seasons_x500()
+ public void get_episode_by_series_seasons_x1000()
{
var epProvider = new EpisodeProvider(repo, null);
@@ -112,7 +113,7 @@ namespace NzbDrone.Core.Test
Console.WriteLine("Starting Test");
var sw = Stopwatch.StartNew();
- for (int i = 0; i < 500; i++)
+ for (int i = 0; i < 1000; i++)
{
epProvider.GetEpisodesBySeason(6, random.Next(2, 5)).Should().NotBeNull();
}
@@ -124,7 +125,7 @@ namespace NzbDrone.Core.Test
}
[Test]
- public void get_episode_file_count_x50()
+ public void get_episode_file_count_x100()
{
var mocker = new AutoMoq.AutoMoqer();
mocker.SetConstant(repo);
@@ -139,12 +140,39 @@ namespace NzbDrone.Core.Test
Console.WriteLine("Starting Test");
var sw = Stopwatch.StartNew();
- for (int i = 0; i < 50; i++)
+ for (int i = 0; i < 100; i++)
{
mediaProvider.GetEpisodeFilesCount(random.Next(1, 5)).Should().NotBeNull();
}
+ sw.Stop();
+
+ Console.WriteLine("Took " + sw.Elapsed);
+ }
+
+
+ [Test]
+ public void get_season_count_x5000()
+ {
+ var mocker = new AutoMoq.AutoMoqer();
+ mocker.SetConstant(repo);
+ var provider = mocker.Resolve();
+
+
+ Thread.Sleep(1000);
+
+
+ var random = new Random();
+ Console.WriteLine("Starting Test");
+
+ var sw = Stopwatch.StartNew();
+ for (int i = 0; i < 5000; i++)
+ {
+ provider.GetSeasons(random.Next(1, 10)).Should().HaveSameCount(seasonsNumbers);
+ }
+
+
sw.Stop();
Console.WriteLine("Took " + sw.Elapsed);
diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs
index c5a4f887a..89fa5766c 100644
--- a/NzbDrone.Core/CentralDispatch.cs
+++ b/NzbDrone.Core/CentralDispatch.cs
@@ -56,8 +56,7 @@ namespace NzbDrone.Core
LogConfiguration.Setup();
- Migrations.Run(Connection.MainConnectionString);
- ForceMigration(_kernel.Get());
+ Migrations.Run(Connection.MainConnectionString, true);
SetupDefaultQualityProfiles(_kernel.Get()); //Setup the default QualityProfiles on start-up
@@ -134,16 +133,6 @@ namespace NzbDrone.Core
_kernel.Get().InitializeNotifiers(notifiers.ToList());
}
- private static void ForceMigration(IRepository repository)
- {
- repository.All().Count();
- repository.All().Count();
- repository.All().Count();
- repository.All().Count();
- repository.All().Count();
- repository.All().Count();
- }
-
///
/// Forces IISExpress process to exit with the host application
///
diff --git a/NzbDrone.Core/Datastore/Connection.cs b/NzbDrone.Core/Datastore/Connection.cs
index 40a5f56cc..b717abd0f 100644
--- a/NzbDrone.Core/Datastore/Connection.cs
+++ b/NzbDrone.Core/Datastore/Connection.cs
@@ -44,6 +44,11 @@ namespace NzbDrone.Core.Datastore
return ProviderFactory.GetProvider(connectionString, "System.Data.SQLite");
}
+ public static IRepository CreateSimpleRepository(IDataProvider dataProvider)
+ {
+ return new SimpleRepository(dataProvider, SimpleRepositoryOptions.RunMigrations);
+ }
+
public static IRepository CreateSimpleRepository(string connectionString)
{
return new SimpleRepository(GetDataProvider(connectionString), SimpleRepositoryOptions.RunMigrations);
diff --git a/NzbDrone.Core/Datastore/Migrations.cs b/NzbDrone.Core/Datastore/Migrations.cs
index ae677b957..b87f8589f 100644
--- a/NzbDrone.Core/Datastore/Migrations.cs
+++ b/NzbDrone.Core/Datastore/Migrations.cs
@@ -7,7 +7,9 @@ using System.Text;
using Migrator.Framework;
using NLog;
using NzbDrone.Core.Repository;
+using NzbDrone.Core.Repository.Quality;
using SubSonic.Extensions;
+using SubSonic.Repository;
using SubSonic.Schema;
namespace NzbDrone.Core.Datastore
@@ -16,18 +18,31 @@ namespace NzbDrone.Core.Datastore
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- public static void Run(string connetionString)
+ public static void Run(string connetionString, bool trace)
{
Logger.Info("Preparing run database migration");
try
{
- var migrator = new Migrator.Migrator("Sqlite", connetionString,
- Assembly.GetAssembly(typeof(Migrations)), true, new MigrationLogger());
+ Migrator.Migrator migrator;
+ if (trace)
+ {
+ migrator = new Migrator.Migrator("Sqlite", connetionString, Assembly.GetAssembly(typeof(Migrations)), true, new MigrationLogger());
+ }
+ else
+ {
+ migrator = new Migrator.Migrator("Sqlite", connetionString, Assembly.GetAssembly(typeof(Migrations)));
+ }
+
+
migrator.MigrateToLastVersion();
+ ForceSubSonicMigration(Connection.CreateSimpleRepository(connetionString));
+
Logger.Info("Database migration completed");
+
+
}
catch (Exception e)
{
@@ -35,6 +50,16 @@ namespace NzbDrone.Core.Datastore
}
}
+ public static void ForceSubSonicMigration(IRepository repository)
+ {
+ repository.Single(1);
+ repository.Single(1);
+ repository.Single(1);
+ repository.Single(1);
+ repository.Single(1);
+ repository.Single(1);
+ }
+
public static void RemoveDeletedColumns(ITransformationProvider transformationProvider)
{
@@ -93,11 +118,7 @@ namespace NzbDrone.Core.Datastore
{
public override void Up()
{
- //Remove jobs table forcing it to repopulate
- var repoProvider = new RepositoryProvider();
- var jobTable = repoProvider.GetSchemaFromType(typeof(JobSetting));
-
- Database.RemoveTable(jobTable.Name);
+ Database.RemoveTable(RepositoryProvider.JobsSchema.Name);
}
public override void Down()
@@ -122,4 +143,34 @@ namespace NzbDrone.Core.Datastore
throw new NotImplementedException();
}
}
+
+ [Migration(20110604)]
+ public class Migration20110604 : Migration
+ {
+ public override void Up()
+ {
+ Migrations.ForceSubSonicMigration(Connection.CreateSimpleRepository(Connection.MainConnectionString));
+
+ var episodesTable = RepositoryProvider.EpisodesSchema;
+ //Database.AddIndex("idx_episodes_series_season_episode", episodesTable.Name, true,
+ // episodesTable.GetColumnByPropertyName("SeriesId").Name,
+ // episodesTable.GetColumnByPropertyName("SeasonNumber").Name,
+ // episodesTable.GetColumnByPropertyName("EpisodeNumber").Name);
+
+ Database.AddIndex("idx_episodes_series_season", episodesTable.Name, false,
+ episodesTable.GetColumnByPropertyName("SeriesId").Name,
+ episodesTable.GetColumnByPropertyName("SeasonNumber").Name);
+
+ Database.AddIndex("idx_episodes_series", episodesTable.Name, false,
+ episodesTable.GetColumnByPropertyName("SeriesId").Name);
+
+ Migrations.RemoveDeletedColumns(Database);
+ Migrations.AddNewColumns(Database);
+ }
+
+ public override void Down()
+ {
+ throw new NotImplementedException();
+ }
+ }
}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/RepositoryProvider.cs b/NzbDrone.Core/Datastore/RepositoryProvider.cs
index 11006811d..040fa9ac0 100644
--- a/NzbDrone.Core/Datastore/RepositoryProvider.cs
+++ b/NzbDrone.Core/Datastore/RepositoryProvider.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using Migrator.Providers;
using Migrator.Providers.SQLite;
+using NzbDrone.Core.Repository;
using SubSonic.DataProviders;
using SubSonic.Extensions;
using SubSonic.Schema;
@@ -15,6 +16,12 @@ namespace NzbDrone.Core.Datastore
{
public class RepositoryProvider
{
+ public static readonly ITable EpisodesSchema = new RepositoryProvider().GetSchemaFromType(typeof(Episode));
+ public static readonly ITable SeriesSchema = new RepositoryProvider().GetSchemaFromType(typeof(Series));
+ public static readonly ITable EpisodeFilesSchema = new RepositoryProvider().GetSchemaFromType(typeof(EpisodeFile));
+ public static readonly ITable JobsSchema = new RepositoryProvider().GetSchemaFromType(typeof(JobSetting));
+
+
public virtual IList GetRepositoryTypes()
{
var coreAssembly = Assembly.GetExecutingAssembly();
@@ -85,5 +92,7 @@ namespace NzbDrone.Core.Datastore
return migColumn;
}
+
+
}
}
diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs
index e6135751c..05ce49b69 100644
Binary files a/NzbDrone.Core/Providers/EpisodeProvider.cs and b/NzbDrone.Core/Providers/EpisodeProvider.cs differ