From 46a54d0bc3e7500cd04fae930534fe27b564d002 Mon Sep 17 00:00:00 2001 From: "Morten@Thinkpad-X220.ab-nat1.dk" Date: Sat, 13 Oct 2012 09:57:45 -0200 Subject: [PATCH] Changing abstract migration classes to be a bit more intuitive and actually use the class instead of the decorations. Added unit test for sample add and drop column changes. --- .../AttributeExtensions.cs | 28 +++++++--- .../DatabaseAnnotations/ChangeTypes.cs | 9 ++++ .../Persistence/DatabaseFactory.cs | 51 ++++++++++++++++-- .../Persistence/DatabaseProviders.cs | 12 +++++ .../Persistence/Migrations/AddColumnChange.cs | 41 ++++++++++++++ .../Persistence/Migrations/BaseChange.cs | 11 ++++ .../Migrations/ConstraintChange.cs | 10 ++++ .../Migrations/DatabaseTypeChange.cs | 10 ++++ .../Migrations/DropColumnChange.cs | 29 ++++++++++ .../Migrations/ForeignKeyChange.cs | 10 ++++ .../Persistence/Migrations/IDatabaseChange.cs | 8 +++ .../Persistence/Migrations/IndexChange.cs | 10 ++++ .../Persistence/Migrations/TableChange.cs | 10 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 11 ++++ src/Umbraco.Tests/App.config | 2 +- .../Persistence/DatabaseExtensionsTest.cs | 17 +++++- .../Persistence/DatabaseFactoryTests.cs | 8 +++ .../Persistence/MigrationTests.cs | 28 ++++++++++ .../MockedMigrations/AddAllowAtRootColumn.cs | 49 +++++++++++++++++ .../DropMasterContentTypeColumn.cs | 26 +++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 54 ++++++++++++++++++- 21 files changed, 419 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/DatabaseAnnotations/ChangeTypes.cs create mode 100644 src/Umbraco.Core/Persistence/DatabaseProviders.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/AddColumnChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/BaseChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/ConstraintChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/DatabaseTypeChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/DropColumnChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/ForeignKeyChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/IDatabaseChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/IndexChange.cs create mode 100644 src/Umbraco.Core/Persistence/Migrations/TableChange.cs create mode 100644 src/Umbraco.Tests/Persistence/MigrationTests.cs create mode 100644 src/Umbraco.Tests/TestHelpers/MockedMigrations/AddAllowAtRootColumn.cs create mode 100644 src/Umbraco.Tests/TestHelpers/MockedMigrations/DropMasterContentTypeColumn.cs diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/AttributeExtensions.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/AttributeExtensions.cs index 7e662a28f4..878c8a284f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/AttributeExtensions.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/AttributeExtensions.cs @@ -2,15 +2,20 @@ { public static class AttributeExtensions { - public static string ToSqlSyntax(this NullSettingAttribute attribute) + public static string ToSqlSyntax(this NullSettings settings) { - return attribute.NullSetting == NullSettings.Null ? "NULL" : "NOT NULL"; + return settings == NullSettings.Null ? "NULL" : "NOT NULL"; } - public static string ToSqlSyntax(this DatabaseTypeAttribute attribute) + public static string ToSqlSyntax(this NullSettingAttribute attribute) + { + return attribute.NullSetting.ToSqlSyntax(); + } + + public static string ToSqlSyntax(this DatabaseTypes databaseTypes, int length) { string syntax = string.Empty; - switch (attribute.DatabaseType) + switch (databaseTypes) { case DatabaseTypes.Bool: syntax = "[bit]"; @@ -32,13 +37,18 @@ break; case DatabaseTypes.Nvarchar: syntax = "[nvarchar]"; - if (attribute.Length > 0) - syntax += string.Format(" ({0})", attribute.Length); + if (length > 0) + syntax += string.Format(" ({0})", length); break; } return syntax; } + public static string ToSqlSyntax(this DatabaseTypeAttribute attribute) + { + return attribute.DatabaseType.ToSqlSyntax(attribute.Length); + } + public static string ToSqlSyntax(this PrimaryKeyColumnAttribute attribute) { string syntax = string.Empty; @@ -53,6 +63,10 @@ { string constraintName = string.IsNullOrEmpty(attribute.Name) ? string.Format("PK_{0}", tableName) : attribute.Name; string clustered = attribute.Clustered ? "CLUSTERED" : "NONCLUSTERED"; + + if (DatabaseFactory.Current.DatabaseProvider == DatabaseProviders.SqlServerCE) + clustered = string.Empty; + string syntax = string.Format("ALTER TABLE [{0}] ADD CONSTRAINT [{1}] PRIMARY KEY {2} ([{3}])", tableName, constraintName, clustered, propertyName); @@ -62,7 +76,7 @@ public static string ToSqlSyntax(this ConstraintAttribute attribute, string tableName, string propertyName) { if (!string.IsNullOrEmpty(attribute.Name)) - return attribute.Name; + return string.Format("CONSTRAINT [{0}] DEFAULT ({1})", attribute.Name, attribute.Default); return string.Format("CONSTRAINT [DF_{0}_{1}] DEFAULT ({2})", tableName, propertyName, attribute.Default); } diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ChangeTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ChangeTypes.cs new file mode 100644 index 0000000000..0b2f669b17 --- /dev/null +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ChangeTypes.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Persistence.DatabaseAnnotations +{ + public enum ChangeTypes + { + ADD, + DROP, + RENAME + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseFactory.cs b/src/Umbraco.Core/Persistence/DatabaseFactory.cs index aafe03a635..c469995ddc 100644 --- a/src/Umbraco.Core/Persistence/DatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseFactory.cs @@ -1,18 +1,19 @@ using System; -using System.Threading; -using Umbraco.Core.Configuration; +using System.Configuration; +using System.Data.Common; namespace Umbraco.Core.Persistence { /// - /// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifecycle. + /// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifetime. /// This is necessary for transactions to work properly /// public sealed class DatabaseFactory { #region Singleton - private static readonly Database _database = new Database("umbracoDbDSN"); + private const string ConnectionStringName = "umbracoDbDSN"; + private static readonly Database _database = new Database(ConnectionStringName); private static readonly Lazy lazy = new Lazy(() => new DatabaseFactory()); public static DatabaseFactory Current { get { return lazy.Value; } } @@ -30,5 +31,47 @@ namespace Umbraco.Core.Persistence { get { return _database; } } + + /// + /// Returns the name of the dataprovider from the connectionstring setting in config + /// + public string ProviderName + { + get + { + var providerName = "System.Data.SqlClient"; + if (ConfigurationManager.ConnectionStrings[ConnectionStringName] != null) + { + if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[ConnectionStringName].ProviderName)) + providerName = ConfigurationManager.ConnectionStrings[ConnectionStringName].ProviderName; + } + else + { + throw new InvalidOperationException("Can't find a connection string with the name '" + ConnectionStringName + "'"); + } + return providerName; + } + } + + /// + /// Returns the Type of DatabaseProvider used + /// + public DatabaseProviders DatabaseProvider + { + get + { + var factory = DbProviderFactories.GetFactory(ProviderName); + + string dbtype = (factory.GetType()).Name; + + if (dbtype.StartsWith("MySql")) return DatabaseProviders.MySql; + if (dbtype.StartsWith("SqlCe")) return DatabaseProviders.SqlServerCE; + if (dbtype.StartsWith("Npgsql")) return DatabaseProviders.PostgreSQL; + if (dbtype.StartsWith("Oracle")) return DatabaseProviders.Oracle; + if (dbtype.StartsWith("SQLite")) return DatabaseProviders.SQLite; + + return DatabaseProviders.SqlServer; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseProviders.cs b/src/Umbraco.Core/Persistence/DatabaseProviders.cs new file mode 100644 index 0000000000..473528e38d --- /dev/null +++ b/src/Umbraco.Core/Persistence/DatabaseProviders.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Persistence +{ + public enum DatabaseProviders + { + SqlServer, + SqlServerCE, + MySql, + PostgreSQL, + Oracle, + SQLite + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/AddColumnChange.cs b/src/Umbraco.Core/Persistence/Migrations/AddColumnChange.cs new file mode 100644 index 0000000000..e154f61c2a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/AddColumnChange.cs @@ -0,0 +1,41 @@ +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database column + /// Used to Add a column with an optional constraint + /// + public abstract class AddColumnChange : DropColumnChange + { + public abstract string Constraint { get; } + + public abstract string DefaultForConstraint { get; } + + public abstract DatabaseTypes DatabaseType { get; } + + public virtual int DatabaseTypeLength + { + get { return 0; } + } + + public abstract NullSettings NullSetting { get; } + + public override Sql ToSql() + { + var sql = new Sql(); + + string constraint = !string.IsNullOrEmpty(Constraint) + ? string.Format("CONSTRAINT [{0}] DEFAULT ({1})", Constraint, DefaultForConstraint) + : string.Format("CONSTRAINT [DF_{0}_{1}] DEFAULT ({2})", TableName, ColumnName, + DefaultForConstraint); + + sql.Append(string.Format("ALTER TABLE [{0}] ADD [{1}] {2} {3} {4};", + TableName, ColumnName, + DatabaseType.ToSqlSyntax(DatabaseTypeLength), + NullSetting.ToSqlSyntax(), + constraint)); + return sql; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/BaseChange.cs b/src/Umbraco.Core/Persistence/Migrations/BaseChange.cs new file mode 100644 index 0000000000..bf31dcbe88 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/BaseChange.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + public abstract class BaseChange : IDatabaseChange + { + public abstract Sql ToSql(); + + public abstract string TableName { get; } + + public abstract string Version { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/ConstraintChange.cs b/src/Umbraco.Core/Persistence/Migrations/ConstraintChange.cs new file mode 100644 index 0000000000..61d3efe9ec --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/ConstraintChange.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database constraint + /// Used to Add, Remove or Rename a constraint + /// + public abstract class ConstraintChange + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/DatabaseTypeChange.cs b/src/Umbraco.Core/Persistence/Migrations/DatabaseTypeChange.cs new file mode 100644 index 0000000000..5880170024 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/DatabaseTypeChange.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database column's type + /// Used to Add, Remove or Change (increase/decrease length) a column's database + /// + public abstract class DatabaseTypeChange + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/DropColumnChange.cs b/src/Umbraco.Core/Persistence/Migrations/DropColumnChange.cs new file mode 100644 index 0000000000..0657537d27 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/DropColumnChange.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database column + /// Used to Remove a column + /// + public abstract class DropColumnChange : BaseChange + { + public abstract string ColumnName { get; } + + public virtual ChangeTypes ChangeType + { + get + { + return ChangeTypes.DROP; + } + } + + public override Sql ToSql() + { + var sql = new Sql(); + sql.Append(string.Format("ALTER TABLE [{0}] DROP COLUMN [{1}];", + TableName, ColumnName)); + return sql; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/ForeignKeyChange.cs b/src/Umbraco.Core/Persistence/Migrations/ForeignKeyChange.cs new file mode 100644 index 0000000000..a05aa92587 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/ForeignKeyChange.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database column's foreign key + /// Used to Add, Remove or Rename a column's foreign key + /// + public abstract class ForeignKeyChange + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/IDatabaseChange.cs b/src/Umbraco.Core/Persistence/Migrations/IDatabaseChange.cs new file mode 100644 index 0000000000..b19ee80657 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/IDatabaseChange.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + public interface IDatabaseChange + { + string Version { get; } + Sql ToSql(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/IndexChange.cs b/src/Umbraco.Core/Persistence/Migrations/IndexChange.cs new file mode 100644 index 0000000000..fdb935df6f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/IndexChange.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database index + /// Used to Add, Remove or Rename an index + /// + public abstract class IndexChange + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/TableChange.cs b/src/Umbraco.Core/Persistence/Migrations/TableChange.cs new file mode 100644 index 0000000000..838e36c77f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/TableChange.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Migrations +{ + /// + /// Represents an abstract class for descriping changes to a database table. + /// Used to create or drop tables + /// + public abstract class TableChange + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 104d140919..7a6e6042d7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -105,6 +105,7 @@ + @@ -116,6 +117,7 @@ + @@ -130,6 +132,15 @@ + + + + + + + + + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index cbb4866fb0..d2c4862ae2 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -8,7 +8,7 @@ - + diff --git a/src/Umbraco.Tests/Persistence/DatabaseExtensionsTest.cs b/src/Umbraco.Tests/Persistence/DatabaseExtensionsTest.cs index 01432d9085..f493d649a2 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseExtensionsTest.cs @@ -1,5 +1,7 @@ using System; using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; using NUnit.Framework; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -13,14 +15,25 @@ namespace Umbraco.Tests.Persistence [SetUp] public virtual void Initialize() { - AppDomain.CurrentDomain.SetData("DataDirectory", TestHelper.CurrentAssemblyDirectory); + string path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", path); + + string filePath = string.Concat(path, "\\test.sdf"); + if(File.Exists(filePath)) + { + File.Delete(filePath); + } + + string connectionString = "Datasource=|DataDirectory|test.sdf"; + var engine = new SqlCeEngine(connectionString); + engine.CreateDatabase(); } [Test] public void Can_Create_umbracoNode_Table() { var factory = DatabaseFactory.Current; - + //var database = new Database("Datasource=|DataDirectory|test.sdf", "System.Data.SqlServerCe.4.0"); using(Transaction transaction = factory.Database.GetTransaction()) { factory.Database.CreateTable(); diff --git a/src/Umbraco.Tests/Persistence/DatabaseFactoryTests.cs b/src/Umbraco.Tests/Persistence/DatabaseFactoryTests.cs index 4a3337f1b2..7c3f965581 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseFactoryTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseFactoryTests.cs @@ -14,5 +14,13 @@ namespace Umbraco.Tests.Persistence Assert.AreSame(db1, db2); } + + [Test] + public void Can_Assert_DatabaseProvider() + { + var provider = DatabaseFactory.Current.DatabaseProvider; + + Assert.AreEqual(DatabaseProviders.SqlServerCE, provider); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/MigrationTests.cs b/src/Umbraco.Tests/Persistence/MigrationTests.cs new file mode 100644 index 0000000000..2964bb001f --- /dev/null +++ b/src/Umbraco.Tests/Persistence/MigrationTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Umbraco.Core.Persistence; +using Umbraco.Tests.TestHelpers.MockedMigrations; + +namespace Umbraco.Tests.Persistence +{ + [TestFixture] + public class MigrationTests + { + [Test] + public void Can_Verify_Add_ColumnChange() + { + var columnChange = new AddAllowAtRootColumn(); + Sql sql = columnChange.ToSql(); + + Assert.AreEqual(sql.SQL, "ALTER TABLE [cmsContentType] ADD [allowAtRoot] [bit] NOT NULL CONSTRAINT [df_cmsContentType_allowAtRoot] DEFAULT (0);"); + } + + [Test] + public void Can_Verify_Drop_ColumnChange() + { + var columnChange = new DropMasterContentTypeColumn(); + Sql sql = columnChange.ToSql(); + + Assert.AreEqual(sql.SQL, "ALTER TABLE [cmsContentType] DROP COLUMN [masterContentType];"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/MockedMigrations/AddAllowAtRootColumn.cs b/src/Umbraco.Tests/TestHelpers/MockedMigrations/AddAllowAtRootColumn.cs new file mode 100644 index 0000000000..8f2bceb8ed --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/MockedMigrations/AddAllowAtRootColumn.cs @@ -0,0 +1,49 @@ +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.TestHelpers.MockedMigrations +{ + /// + /// Mocked Column Change that'll generate the following sql: + /// ALTER TABLE [cmsContentType] + /// ADD [allowAtRoot] bit NOT NULL + /// CONSTRAINT [df_cmsContentType_allowAtRoot] DEFAULT 0 + /// + public class AddAllowAtRootColumn : AddColumnChange + { + public override string TableName + { + get { return "cmsContentType"; } + } + + public override string Version + { + get { return "4.10.0"; } + } + + public override string ColumnName + { + get { return "allowAtRoot"; } + } + + public override string Constraint + { + get { return "df_cmsContentType_allowAtRoot"; } + } + + public override string DefaultForConstraint + { + get { return "0"; } + } + + public override DatabaseTypes DatabaseType + { + get { return DatabaseTypes.Bool; } + } + + public override NullSettings NullSetting + { + get { return NullSettings.NotNull; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/MockedMigrations/DropMasterContentTypeColumn.cs b/src/Umbraco.Tests/TestHelpers/MockedMigrations/DropMasterContentTypeColumn.cs new file mode 100644 index 0000000000..6ee431b07d --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/MockedMigrations/DropMasterContentTypeColumn.cs @@ -0,0 +1,26 @@ +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.TestHelpers.MockedMigrations +{ + /// + /// Mocked Column Change that'll generate the following sql: + /// ALTER TABLE [cmsContentType] DROP COLUMN [masterContentType]; + /// + public class DropMasterContentTypeColumn : DropColumnChange + { + public override string Version + { + get { return "4.10.0"; } + } + + public override string TableName + { + get { return "cmsContentType"; } + } + + public override string ColumnName + { + get { return "masterContentType"; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e3343c4b29..cef8489396 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -14,6 +14,21 @@ 512 ..\ true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true true @@ -46,6 +61,14 @@ + + False + ..\..\lib\SQLCE4\System.Data.SqlServerCe.dll + + + False + ..\..\lib\SQLCE4\System.Data.SqlServerCe.Entity.dll + @@ -69,6 +92,7 @@ + @@ -105,6 +129,8 @@ + + @@ -186,7 +212,33 @@ Umbraco.Web - + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + SQL Server Compact 4.0 SP1 + true + + + False + Windows Installer 4.5 + true + +