From e1a134f5870c53a570debe7fbc1083c2afca60e8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Mar 2014 17:11:21 +1100 Subject: [PATCH 1/2] Fixes: U4-4430 MySql primary key issues --- .../Models/Rdbms/PreviewXmlDto.cs | 2 +- .../Initial/DatabaseSchemaResult.cs | 2 +- .../Persistence/Migrations/MigrationRunner.cs | 22 +- .../AssignMissingPrimaryForMySqlKeys.cs | 97 ++++++ .../SqlSyntax/MySqlSyntaxProvider.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../MySqlSyntaxProviderTests.cs | 49 ++++ ...erTests.cs => SqlCeSyntaxProviderTests.cs} | 277 +++++++++--------- src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- 9 files changed, 307 insertions(+), 148 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs create mode 100644 src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs rename src/Umbraco.Tests/Persistence/SyntaxProvider/{SqlSyntaxProviderTests.cs => SqlCeSyntaxProviderTests.cs} (97%) diff --git a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs b/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs index f0f3ebffd0..d981855f24 100644 --- a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models.Rdbms internal class PreviewXmlDto { [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "", OnColumns = "nodeId, versionId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsContentPreviewXml", OnColumns = "nodeId, versionId")] [ForeignKey(typeof(ContentDto), Column = "nodeId")] public int NodeId { get; set; } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 4db0f7222a..3fa8cd4ab0 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(6, 0, 0); } - //if the error is for an index + //if the error is for this IX_umbracoNodeTrashed which is added in 6.2, then it must mean we are on 6.1 if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) { return new Version(6, 1, 0); diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index b4117003fb..bb14bb67f3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Migrations { ExecuteMigrations(schemaMigrationContext, database); } - catch (Exception) + catch (Exception ex) { //if this fails then the transaction will be rolled back, BUT if we are using MySql this is not the case, //since it does not support schema changes in a transaction, see: http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html @@ -77,11 +77,21 @@ namespace Umbraco.Core.Persistence.Migrations if (databaseProvider == DatabaseProviders.MySql) { - var downgrades = OrderedDowngradeMigrations(foundMigrations.Where(x => (x is SchemaMigration))).ToList(); - var downgradeMigrationContext = InitializeMigrations(downgrades, database, databaseProvider, false); - //lets hope that works! - if something cannot be rolled back then a CatastrophicDataLossException should - // be thrown. - ExecuteMigrations(downgradeMigrationContext, database); + try + { + var downgrades = OrderedDowngradeMigrations(foundMigrations.Where(x => (x is SchemaMigration))).ToList(); + var downgradeMigrationContext = InitializeMigrations(downgrades, database, databaseProvider, false); + //lets hope that works! - if something cannot be rolled back then a CatastrophicDataLossException should + // be thrown. + ExecuteMigrations(downgradeMigrationContext, database); + } + catch (Exception e) + { + throw new CatastrophicDataLossException( + "An error occurred running a schema migration but the changes could not be rolled back. Error: " + ex.Message + ". In some cases, it may be required that the database be restored to it's original state before running this upgrade process again.", + ex); + + } } //continue throwing the exception diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs new file mode 100644 index 0000000000..27c0dc91ba --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs @@ -0,0 +1,97 @@ +using System.Linq; +using System.Web.UI; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero +{ + //see: http://issues.umbraco.org/issue/U4-4430 + [Migration("6.2.0", 3, GlobalSettings.UmbracoMigrationName)] + public class AssignMissingPrimaryForMySqlKeys : SchemaMigration + { + public override void Up() + { + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) + { + var constraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentType2ContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsContentType2ContentType") + .OnTable("cmsContentType2ContentType") + .Columns(new[] {"parentContentTypeId", "childContentTypeId"}); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentTypeAllowedContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsContentTypeAllowedContentType") + .OnTable("cmsContentTypeAllowedContentType") + .Columns(new[] { "Id", "AllowedId" }); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsDocumentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsDocumentType") + .OnTable("cmsDocumentType") + .Columns(new[] { "contentTypeNodeId", "templateNodeId" }); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsMember2MemberGroup") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsMember2MemberGroup") + .OnTable("cmsMember2MemberGroup") + .Columns(new[] { "Member", "MemberGroup" }); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsContentPreviewXml") + .OnTable("cmsPreviewXml") + .Columns(new[] { "nodeId", "versionId" }); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsTagRelationship") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsTagRelationship") + .OnTable("cmsTagRelationship") + .Columns(new[] { "nodeId", "tagId" }); + } + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_user2app") + .OnTable("umbracoUser2app") + .Columns(new[] { "user", "app" }); + } + + //This should be 2 because this table has 3 keys + if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2NodeNotify") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_umbracoUser2NodeNotify") + .OnTable("umbracoUser2NodeNotify") + .Columns(new[] { "userId", "nodeId", "action" }); + } + + //This should be 2 because this table has 3 keys + if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2NodePermission") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_umbracoUser2NodePermission") + .OnTable("umbracoUser2NodePermission") + .Columns(new[] { "userId", "nodeId", "permission" }); + } + } + } + + public override void Down() + { + //don't do anything, these keys should have always existed! + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 7e8d7b7566..ba6f4dcc7e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -218,7 +218,7 @@ ORDER BY TABLE_NAME, INDEX_NAME", { string primaryKey = string.Empty; var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition != null && columnDefinition.PrimaryKeyColumns.Contains(",") == false) + if (columnDefinition != null) { string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) ? GetQuotedColumnName(columnDefinition.Name) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 549c1378bc..2158fb1409 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -216,6 +216,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs new file mode 100644 index 0000000000..00319c2ccc --- /dev/null +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/MySqlSyntaxProviderTests.cs @@ -0,0 +1,49 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Tests.Persistence.SyntaxProvider +{ + [NUnit.Framework.Ignore("This doesn't actually test anything")] + [TestFixture] + public class MySqlSyntaxProviderTests + { + [SetUp] + public void SetUp() + { + SqlSyntaxContext.SqlSyntaxProvider = MySqlSyntax.Provider; + } + + [Test] + public void Can_Generate_Create_Table_Statement() + { + var type = typeof(TagRelationshipDto); + var definition = DefinitionFactory.GetTableDefinition(type); + + string create = SqlSyntaxContext.SqlSyntaxProvider.Format(definition); + string primaryKey = SqlSyntaxContext.SqlSyntaxProvider.FormatPrimaryKey(definition); + var indexes = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.Indexes); + var keys = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.ForeignKeys); + + Console.WriteLine(create); + Console.WriteLine(primaryKey); + foreach (var sql in keys) + { + Console.WriteLine(sql); + } + + foreach (var sql in indexes) + { + Console.WriteLine(sql); + } + } + + [TearDown] + public void TearDown() + { + SqlSyntaxContext.SqlSyntaxProvider = null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs similarity index 97% rename from src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs rename to src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 7802ea6d58..4862333b07 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -1,139 +1,140 @@ -using System; -using System.Collections.Generic; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Tests.Persistence.SyntaxProvider -{ - [TestFixture] - public class SqlSyntaxProviderTests - { - [SetUp] - public void SetUp() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider; - } - - [Test] - public void Can_Generate_Delete_SubQuery_Statement() - { - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType); - - var sql = SqlSyntaxContext.SqlSyntaxProvider.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); - - Assert.AreEqual(@"DELETE FROM [cmsContentXml] WHERE [nodeId] IN (SELECT [nodeId] FROM (SELECT DISTINCT cmsContentXml.nodeId -FROM [cmsContentXml] -INNER JOIN [umbracoNode] -ON [cmsContentXml].[nodeId] = [umbracoNode].[id] -WHERE ([umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c')) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sql.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); - } - - [Test] - public void Can_Generate_Create_Table_Statement() - { - var type = typeof (NodeDto); - var definition = DefinitionFactory.GetTableDefinition(type); - - string create = SqlSyntaxContext.SqlSyntaxProvider.Format(definition); - string primaryKey = SqlSyntaxContext.SqlSyntaxProvider.FormatPrimaryKey(definition); - var indexes = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.Indexes); - var keys = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.ForeignKeys); - - Console.WriteLine(create); - Console.WriteLine(primaryKey); - foreach (var sql in keys) - { - Console.WriteLine(sql); - } - - foreach (var sql in indexes) - { - Console.WriteLine(sql); - } - } - - [Test] - public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - - var indexDefinition = CreateIndexDefinition(); - indexDefinition.IndexType = IndexTypes.NonClustered; - - var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); - Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); - } - - [Test] - public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - - var indexDefinition = CreateIndexDefinition(); - indexDefinition.IsClustered = false; - - var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); - Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); - } - - [Test] - public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; - var builder = new CreateIndexBuilder(createExpression); - builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().NonClustered(); - Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); - } - - [Test] - public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; - var builder = new CreateIndexBuilder(createExpression); - builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Unique(); - Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); - } - - [Test] - public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() - { - SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; - var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; - var builder = new CreateIndexBuilder(createExpression); - builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Clustered(); - Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); - } - - private static IndexDefinition CreateIndexDefinition() - { - return new IndexDefinition - { - ColumnName = "A", - Name = "IX_A", - TableName = "TheTable", - SchemaName = "dbo" - }; - } - - [TearDown] - public void TearDown() - { - SqlSyntaxContext.SqlSyntaxProvider = null; - } - } +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; +using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Tests.Persistence.SyntaxProvider +{ + [TestFixture] + public class SqlCeSyntaxProviderTests + { + [SetUp] + public void SetUp() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider; + } + + [Test] + public void Can_Generate_Delete_SubQuery_Statement() + { + var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); + var subQuery = new Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); + + var sql = SqlSyntaxContext.SqlSyntaxProvider.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + + Assert.AreEqual(@"DELETE FROM [cmsContentXml] WHERE [nodeId] IN (SELECT [nodeId] FROM (SELECT DISTINCT cmsContentXml.nodeId +FROM [cmsContentXml] +INNER JOIN [umbracoNode] +ON [cmsContentXml].[nodeId] = [umbracoNode].[id] +WHERE ([umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c')) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sql.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); + } + + [NUnit.Framework.Ignore("This doesn't actually test anything")] + [Test] + public void Can_Generate_Create_Table_Statement() + { + var type = typeof (NodeDto); + var definition = DefinitionFactory.GetTableDefinition(type); + + string create = SqlSyntaxContext.SqlSyntaxProvider.Format(definition); + string primaryKey = SqlSyntaxContext.SqlSyntaxProvider.FormatPrimaryKey(definition); + var indexes = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.Indexes); + var keys = SqlSyntaxContext.SqlSyntaxProvider.Format(definition.ForeignKeys); + + Console.WriteLine(create); + Console.WriteLine(primaryKey); + foreach (var sql in keys) + { + Console.WriteLine(sql); + } + + foreach (var sql in indexes) + { + Console.WriteLine(sql); + } + } + + [Test] + public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + + var indexDefinition = CreateIndexDefinition(); + indexDefinition.IndexType = IndexTypes.NonClustered; + + var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); + Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); + } + + [Test] + public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + + var indexDefinition = CreateIndexDefinition(); + indexDefinition.IsClustered = false; + + var actual = SqlSyntaxContext.SqlSyntaxProvider.Format(indexDefinition); + Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", actual); + } + + [Test] + public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().NonClustered(); + Assert.AreEqual("CREATE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + [Test] + public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Unique(); + Assert.AreEqual("CREATE UNIQUE NONCLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + [Test] + public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() + { + SqlSyntaxContext.SqlSyntaxProvider = SqlServerSyntax.Provider; + var createExpression = new CreateIndexExpression { Index = { Name = "IX_A" } }; + var builder = new CreateIndexBuilder(createExpression); + builder.OnTable("TheTable").OnColumn("A").Ascending().WithOptions().Clustered(); + Assert.AreEqual("CREATE CLUSTERED INDEX [IX_A] ON [TheTable] ([A])", createExpression.ToString()); + } + + private static IndexDefinition CreateIndexDefinition() + { + return new IndexDefinition + { + ColumnName = "A", + Name = "IX_A", + TableName = "TheTable", + SchemaName = "dbo" + }; + } + + [TearDown] + public void TearDown() + { + SqlSyntaxContext.SqlSyntaxProvider = null; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4f632ed9d9..b19066f0d6 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -220,6 +220,7 @@ + @@ -276,7 +277,7 @@ - + From b5329ace3bb22223249bf58bea2de716dd253e78 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Mar 2014 18:38:57 +1100 Subject: [PATCH 2/2] Backports fixes for legacy encoding checks and to not do any db lookups for the new membership providers and also ensures the legacy membership provider doesn't do any db lookups on init. --- .../Configuration/GlobalSettings.cs | 29 +++--- .../Providers/MembersMembershipProvider.cs | 29 ++++-- .../Providers/UsersMembershipProvider.cs | 43 ++++++--- .../members/UmbracoMembershipProvider.cs | 36 ++++++-- .../members/UmbracoRoleProvider.cs | 91 ++++++++++++------- 5 files changed, 156 insertions(+), 72 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 028d745a0e..8911f6c130 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -5,11 +5,13 @@ using System.Linq; using System.Web; using System.Web.Configuration; using System.Web.Routing; +using System.Web.Security; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Security; namespace Umbraco.Core.Configuration { @@ -265,7 +267,7 @@ namespace Umbraco.Core.Configuration { get { - return ConfiguredMembershipProvidersLegacyEncoding(Constants.Conventions.Member.UmbracoMemberProviderName); + return IsConfiguredMembershipProviderUsingLegacyEncoding(Constants.Conventions.Member.UmbracoMemberProviderName); } set { @@ -281,7 +283,7 @@ namespace Umbraco.Core.Configuration { get { - return ConfiguredMembershipProvidersLegacyEncoding(Constants.Conventions.User.UmbracoUsersProviderName); + return IsConfiguredMembershipProviderUsingLegacyEncoding(Constants.Conventions.Member.UmbracoMemberProviderName); } set { @@ -366,22 +368,17 @@ namespace Umbraco.Core.Configuration webConfigXml.Save(webConfigFilename, SaveOptions.DisableFormatting); } - - private static bool ConfiguredMembershipProvidersLegacyEncoding(string providerName) + + private static bool IsConfiguredMembershipProviderUsingLegacyEncoding(string providerName) { - var webConfigFilename = GetFullWebConfigFileName(); - var webConfigXml = XDocument.Load(webConfigFilename, LoadOptions.PreserveWhitespace); + //check if this can even be configured. + var membershipProvider = Membership.Providers[providerName] as MembershipProviderBase; + if (membershipProvider == null) + { + return false; + } - var membershipConfigs = webConfigXml.XPathSelectElements("configuration/system.web/membership/providers/add").ToList(); - - var provider = membershipConfigs.SingleOrDefault(c => c.Attribute("name") != null && c.Attribute("name").Value == providerName); - - var useLegacyEncodingAttribute = provider.Attribute("useLegacyEncoding"); - - bool useLegacyEncoding; - bool.TryParse(useLegacyEncodingAttribute.Value, out useLegacyEncoding); - - return useLegacyEncoding; + return membershipProvider.UseLegacyEncoding; } /// diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 1dc88b4b51..4ad0836a12 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -40,6 +40,8 @@ namespace Umbraco.Web.Security.Providers } private string _defaultMemberTypeAlias = "Member"; + private volatile bool _hasDefaultMember = false; + private static readonly object Locker = new object(); public override string ProviderName { @@ -77,20 +79,35 @@ namespace Umbraco.Web.Security.Providers if (config["defaultMemberTypeAlias"] != null) { _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - } - else - { - _defaultMemberTypeAlias = MemberService.GetDefaultMemberType(); if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { - throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); } + _hasDefaultMember = true; } } public override string DefaultMemberTypeAlias { - get { return _defaultMemberTypeAlias; } + get + { + if (_hasDefaultMember == false) + { + lock (Locker) + { + if (_hasDefaultMember == false) + { + _defaultMemberTypeAlias = MemberService.GetDefaultMemberType(); + if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) + { + throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + } + _hasDefaultMember = true; + } + } + } + return _defaultMemberTypeAlias; + } } } } diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index ba2c127877..d1ec03c97d 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -15,20 +15,22 @@ namespace Umbraco.Web.Security.Providers /// public class UsersMembershipProvider : UmbracoMembershipProvider, IUsersMembershipProvider { - + public UsersMembershipProvider() : this(ApplicationContext.Current.Services.UserService) - { + { } public UsersMembershipProvider(IMembershipMemberService memberService) : base(memberService) - { + { } private string _defaultMemberTypeAlias = "writer"; + private volatile bool _hasDefaultMember = false; + private static readonly object Locker = new object(); - public override string ProviderName + public override string ProviderName { get { return Constants.Conventions.User.UmbracoUsersProviderName; } } @@ -44,7 +46,7 @@ namespace Umbraco.Web.Security.Providers protected override MembershipUser ConvertToMembershipUser(IUser entity) { //the provider user key is always the int id - return entity.AsConcreteMembershipUser(Name); + return entity.AsConcreteMembershipUser(Name); } public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) @@ -55,20 +57,35 @@ namespace Umbraco.Web.Security.Providers if (config["defaultUserTypeAlias"] != null) { _defaultMemberTypeAlias = config["defaultUserTypeAlias"]; - } - else - { - var defaultFromService = MemberService.GetDefaultMemberType(); - if (defaultFromService.IsNullOrWhiteSpace()) + if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); } - } - } + _hasDefaultMember = true; + } + } public override string DefaultMemberTypeAlias { - get { return _defaultMemberTypeAlias; } + get + { + if (_hasDefaultMember == false) + { + lock (Locker) + { + if (_hasDefaultMember == false) + { + _defaultMemberTypeAlias = MemberService.GetDefaultMemberType(); + if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) + { + throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + } + _hasDefaultMember = true; + } + } + } + return _defaultMemberTypeAlias; + } } } } \ No newline at end of file diff --git a/src/umbraco.providers/members/UmbracoMembershipProvider.cs b/src/umbraco.providers/members/UmbracoMembershipProvider.cs index dc094fd15b..cd6a80d340 100644 --- a/src/umbraco.providers/members/UmbracoMembershipProvider.cs +++ b/src/umbraco.providers/members/UmbracoMembershipProvider.cs @@ -48,7 +48,9 @@ namespace umbraco.providers.members #region Fields private string _defaultMemberTypeAlias = "Member"; - private string _providerName = Member.UmbracoMemberProviderName; + private string _providerName = Member.UmbracoMemberProviderName; + private volatile bool _hasDefaultMember = false; + private static readonly object Locker = new object(); #endregion @@ -118,11 +120,14 @@ namespace umbraco.providers.members // test for membertype (if not specified, choose the first member type available) if (config["defaultMemberTypeAlias"] != null) + { _defaultMemberTypeAlias = config["defaultMemberTypeAlias"]; - else if (MemberType.GetAll.Length == 1) - _defaultMemberTypeAlias = MemberType.GetAll[0].Alias; - else - throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) + { + throw new ProviderException("No default user type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); + } + _hasDefaultMember = true; + } // test for approve status if (config["umbracoApprovePropertyTypeAlias"] != null) @@ -233,7 +238,26 @@ namespace umbraco.providers.members public override string DefaultMemberTypeAlias { - get { return _defaultMemberTypeAlias; } + get + { + if (_hasDefaultMember == false) + { + lock (Locker) + { + if (_hasDefaultMember == false) + { + var types = MemberType.GetAll; + if (types.Length == 1) + _defaultMemberTypeAlias = types[0].Alias; + else + throw new ProviderException("No default MemberType alias is specified in the web.config string. Please add a 'defaultMemberTypeAlias' to the add element in the provider declaration in web.config"); + + _hasDefaultMember = true; + } + } + } + return _defaultMemberTypeAlias; + } } /// diff --git a/src/umbraco.providers/members/UmbracoRoleProvider.cs b/src/umbraco.providers/members/UmbracoRoleProvider.cs index bfb8863ed0..f87f960a79 100644 --- a/src/umbraco.providers/members/UmbracoRoleProvider.cs +++ b/src/umbraco.providers/members/UmbracoRoleProvider.cs @@ -14,13 +14,13 @@ using umbraco.cms.businesslogic.member; using System.Collections; #endregion -namespace umbraco.providers.members +namespace umbraco.providers.members { /// /// A role provider for members /// [Obsolete("This has been superceded by Umbraco.Web.Security.Providers.MembersRoleProvider")] - public class UmbracoRoleProvider : RoleProvider + public class UmbracoRoleProvider : RoleProvider { #region @@ -33,11 +33,14 @@ namespace umbraco.providers.members /// /// /// The name of the application to store and retrieve role information for. - public override string ApplicationName { - get { + public override string ApplicationName + { + get + { return _applicationName; } - set { + set + { if (string.IsNullOrEmpty(value)) throw new ProviderException("ApplicationName cannot be empty."); @@ -60,13 +63,15 @@ namespace umbraco.providers.members /// on a provider /// after the provider has already been initialized. /// The name of the provider has a length of zero. - public override void Initialize(string name, NameValueCollection config) { + public override void Initialize(string name, NameValueCollection config) + { // Initialize values from web.config if (config == null) throw new ArgumentNullException("config"); if (name == null || name.Length == 0) name = "UmbracoMemberRoleProvider"; - if (String.IsNullOrEmpty(config["description"])) { + if (String.IsNullOrEmpty(config["description"])) + { config.Remove("description"); config.Add("description", "Umbraco Member Role provider"); } @@ -86,15 +91,20 @@ namespace umbraco.providers.members /// /// A string array of user names to be added to the specified roles. /// A string array of the role names to add the specified user names to. - public override void AddUsersToRoles(string[] usernames, string[] roleNames) { + public override void AddUsersToRoles(string[] usernames, string[] roleNames) + { ArrayList roles = new ArrayList(); foreach (string role in roleNames) - try { + try + { roles.Add(MemberGroup.GetByName(role).Id); - } catch { + } + catch + { throw new ProviderException(String.Format("No role with name '{0}' exists", role)); } - foreach (string username in usernames) { + foreach (string username in usernames) + { Member m = Member.GetMemberFromLoginName(username); foreach (int roleId in roles) m.AddGroup(roleId); @@ -105,7 +115,8 @@ namespace umbraco.providers.members /// Adds a new role to the data source for the configured applicationName. /// /// The name of the role to create. - public override void CreateRole(string roleName) { + public override void CreateRole(string roleName) + { MemberGroup.MakeNew(roleName, User.GetUser(0)); } @@ -117,13 +128,15 @@ namespace umbraco.providers.members /// /// true if the role was successfully deleted; otherwise, false. /// - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { + public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) + { MemberGroup group = MemberGroup.GetByName(roleName); if (group == null) throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); else if (throwOnPopulatedRole && group.GetMembersAsIds().Length > 0) throw new ProviderException(String.Format("Can't delete role '{0}', there are members assigned to the role", roleName)); - else { + else + { foreach (Member m in group.GetMembers()) m.RemoveGroup(group.Id); group.delete(); @@ -139,12 +152,14 @@ namespace umbraco.providers.members /// /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role. /// - public override string[] FindUsersInRole(string roleName, string usernameToMatch) { + public override string[] FindUsersInRole(string roleName, string usernameToMatch) + { ArrayList members = new ArrayList(); MemberGroup group = MemberGroup.GetByName(roleName); if (group == null) throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { + else + { foreach (Member m in group.GetMembers(usernameToMatch)) members.Add(m.LoginName); return (string[])members.ToArray(typeof(string)); @@ -157,7 +172,8 @@ namespace umbraco.providers.members /// /// A string array containing the names of all the roles stored in the data source for the configured applicationName. /// - public override string[] GetAllRoles() { + public override string[] GetAllRoles() + { ArrayList roles = new ArrayList(); foreach (MemberGroup mg in MemberGroup.GetAll) roles.Add(mg.Text); @@ -171,15 +187,18 @@ namespace umbraco.providers.members /// /// A string array containing the names of all the roles that the specified user is in for the configured applicationName. /// - public override string[] GetRolesForUser(string username) { + public override string[] GetRolesForUser(string username) + { ArrayList roles = new ArrayList(); Member m = Member.GetMemberFromLoginName(username); - if (m != null) { + if (m != null) + { IDictionaryEnumerator ide = m.Groups.GetEnumerator(); while (ide.MoveNext()) roles.Add(((MemberGroup)ide.Value).Text); return (string[])roles.ToArray(typeof(string)); - } else + } + else throw new ProviderException(String.Format("No member with username '{0}' exists", username)); } @@ -190,12 +209,14 @@ namespace umbraco.providers.members /// /// A string array containing the names of all the users who are members of the specified role for the configured applicationName. /// - public override string[] GetUsersInRole(string roleName) { + public override string[] GetUsersInRole(string roleName) + { ArrayList members = new ArrayList(); MemberGroup group = MemberGroup.GetByName(roleName); if (group == null) throw new ProviderException(String.Format("No role with name '{0}' exists", roleName)); - else { + else + { foreach (Member m in group.GetMembers()) members.Add(m.LoginName); return (string[])members.ToArray(typeof(string)); @@ -210,11 +231,13 @@ namespace umbraco.providers.members /// /// true if the specified user is in the specified role for the configured applicationName; otherwise, false. /// - public override bool IsUserInRole(string username, string roleName) { + public override bool IsUserInRole(string username, string roleName) + { Member m = Member.GetMemberFromLoginName(username); if (m == null) throw new ProviderException(String.Format("No user with name '{0}' exists", username)); - else { + else + { MemberGroup mg = MemberGroup.GetByName(roleName); if (mg == null) throw new ProviderException(String.Format("No Membergroup with name '{0}' exists", roleName)); @@ -228,15 +251,20 @@ namespace umbraco.providers.members /// /// A string array of user names to be removed from the specified roles. /// A string array of role names to remove the specified user names from. - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { + public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) + { ArrayList roles = new ArrayList(); foreach (string role in roleNames) - try { + try + { roles.Add(MemberGroup.GetByName(role).Id); - } catch { + } + catch + { throw new ProviderException(String.Format("No role with name '{0}' exists", role)); } - foreach (string username in usernames) { + foreach (string username in usernames) + { Member m = Member.GetMemberFromLoginName(username); foreach (int roleId in roles) m.RemoveGroup(roleId); @@ -250,9 +278,10 @@ namespace umbraco.providers.members /// /// true if the role name already exists in the data source for the configured applicationName; otherwise, false. /// - public override bool RoleExists(string roleName) { - MemberGroup mg = MemberGroup.GetByName(roleName); - return mg != null; + public override bool RoleExists(string roleName) + { + MemberGroup mg = MemberGroup.GetByName(roleName); + return mg != null; } #endregion