From 76d029361bfe4c548b1e676efdc00db91ef72bc0 Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 4 Jun 2011 23:01:45 -0700 Subject: [PATCH 1/4] Added CreateIndex to Migrator --- .../ITransformationProvider.cs | 78 +- .../NoOpTransformationProvider.cs | 18 +- .../TransformationProvider.cs | 1648 +++++++++-------- 3 files changed, 902 insertions(+), 842 deletions(-) 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(); + } + } + } +} From ecc2a7e2f680d048216dda90300df728b80fcb04 Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 4 Jun 2011 23:02:31 -0700 Subject: [PATCH 2/4] More updates to Datastore --- NzbDrone.Core.Test/Framework/MockLib.cs | 24 +++-- NzbDrone.Core.Test/dbBenchmark.cs | 92 ++++++++++++------- NzbDrone.Core/CentralDispatch.cs | 2 +- NzbDrone.Core/Datastore/Connection.cs | 5 + NzbDrone.Core/Datastore/Migrations.cs | 49 ++++++++-- NzbDrone.Core/Datastore/RepositoryProvider.cs | 9 ++ 6 files changed, 131 insertions(+), 50 deletions(-) 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,40 @@ 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); + + + Thread.Sleep(1000); + + + var random = new Random(); + Console.WriteLine("Starting Test"); + + var sw = Stopwatch.StartNew(); + for (int i = 0; i < 5000; i++) + { + epProvider.GetEpisode(6, random.Next(2, 5), random.Next(2, Episodes_Per_Season - 10)).Should().NotBeNull(); + } + + + sw.Stop(); + + Console.WriteLine("Took " + sw.Elapsed); + } + + [Test] + public void get_episode_by_series_seasons_x1000() { var epProvider = new EpisodeProvider(repo, null); @@ -89,30 +114,6 @@ namespace NzbDrone.Core.Test var sw = Stopwatch.StartNew(); for (int i = 0; i < 1000; i++) - { - epProvider.GetEpisode(6, random.Next(2, 5), random.Next(2, Episodes_Per_Season - 10)).Should().NotBeNull(); - } - - - sw.Stop(); - - Console.WriteLine("Took " + sw.Elapsed); - } - - [Test] - public void get_episode_by_series_seasons_x500() - { - var epProvider = new EpisodeProvider(repo, null); - - - Thread.Sleep(1000); - - - var random = new Random(); - Console.WriteLine("Starting Test"); - - var sw = Stopwatch.StartNew(); - for (int i = 0; i < 500; 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..621256685 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -56,7 +56,7 @@ namespace NzbDrone.Core LogConfiguration.Setup(); - Migrations.Run(Connection.MainConnectionString); + Migrations.Run(Connection.MainConnectionString, true); ForceMigration(_kernel.Get()); SetupDefaultQualityProfiles(_kernel.Get()); //Setup the default QualityProfiles on start-up 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..91330a07c 100644 --- a/NzbDrone.Core/Datastore/Migrations.cs +++ b/NzbDrone.Core/Datastore/Migrations.cs @@ -16,14 +16,23 @@ 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(); @@ -93,11 +102,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 +127,32 @@ namespace NzbDrone.Core.Datastore throw new NotImplementedException(); } } + + [Migration(20110604)] + public class Migration20110604 : Migration + { + public override void Up() + { + 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; } + + } } From 57690f49a0646eea84630ceab83eafe869c7f55c Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 4 Jun 2011 23:24:24 -0700 Subject: [PATCH 3/4] Fixed IsSeasonIgnore logic, added tests --- NzbDrone.Core.Test/EpisodeProviderTest.cs | 88 +++++++++++++++++++++ NzbDrone.Core/Providers/EpisodeProvider.cs | Bin 10740 -> 10742 bytes 2 files changed, 88 insertions(+) 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/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index e6135751c182ac01edc8f51153a99fb52d2042b4..05ce49b69b94be8357d0a2fd701dc511840b1c2d 100644 GIT binary patch delta 26 icmewo{4IDxrZ}VG2>}3zZV0Xb delta 20 ccmews{3UoprugIq%si9J!~-{<5Z4m|0A$SwMgRZ+ From 72af060d68f4cae4827b54e7849cf22c6b1b0650 Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 4 Jun 2011 23:35:03 -0700 Subject: [PATCH 4/4] Fixed migration issue --- NzbDrone.Core/CentralDispatch.cs | 11 ----------- NzbDrone.Core/Datastore/Migrations.cs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 621256685..89fa5766c 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -57,7 +57,6 @@ namespace NzbDrone.Core LogConfiguration.Setup(); Migrations.Run(Connection.MainConnectionString, true); - ForceMigration(_kernel.Get()); 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/Migrations.cs b/NzbDrone.Core/Datastore/Migrations.cs index 91330a07c..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 @@ -36,7 +38,11 @@ namespace NzbDrone.Core.Datastore migrator.MigrateToLastVersion(); + ForceSubSonicMigration(Connection.CreateSimpleRepository(connetionString)); + Logger.Info("Database migration completed"); + + } catch (Exception e) { @@ -44,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) { @@ -133,6 +149,8 @@ namespace NzbDrone.Core.Datastore { 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,