From f537b84112fdc46c639c51d3ba5168c2bc7d03d3 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 28 Feb 2022 20:46:19 +0500 Subject: [PATCH 1/4] Fix case-sensitive schema extraction --- Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs index 29d89582ef..e456e72f30 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Extractor.cs @@ -142,9 +142,9 @@ protected virtual string ToUpperInvariantIfNeeded(string schemaName) private ExtractionContext CreateContext(string catalogName, string[] schemaNames) { - var catalog = new Catalog(catalogName); + var catalog = new Catalog(catalogName, true); for(var i = 0; i < schemaNames.Length; i++) { - schemaNames[i] = schemaNames[i].ToUpperInvariant(); + schemaNames[i] = ToUpperInvariantIfNeeded(schemaNames[i]); } var replacements = new Dictionary(); From 7083ad711a354d09d19916ba314a93cf473cc920 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 28 Feb 2022 21:16:02 +0500 Subject: [PATCH 2/4] Add support for custom comparer for NodeCollection and set altertative comparer for oracle Oracle RDBMS supports case-sensitive schemas so schemas collection has to support case-sensitive name index. --- Orm/Xtensive.Orm/Sql/Model/Catalog.cs | 12 +++++++++++- Orm/Xtensive.Orm/Sql/Model/NodeCollection.cs | 11 +++++++++++ .../Sql/Model/PairedNodeCollection.cs | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm/Sql/Model/Catalog.cs b/Orm/Xtensive.Orm/Sql/Model/Catalog.cs index d7259e16c0..08c0f05335 100644 --- a/Orm/Xtensive.Orm/Sql/Model/Catalog.cs +++ b/Orm/Xtensive.Orm/Sql/Model/Catalog.cs @@ -198,10 +198,20 @@ internal string GetActualDbName(IDictionary catalogNameMap) // Constructors - public Catalog(string name) : base(name) + public Catalog(string name) + : base(name) { schemas = new PairedNodeCollection(this, "Schemas", 1); } + + public Catalog(string name, bool caseSensitiveNames = false) + : base(name) + { + schemas = caseSensitiveNames + ? new PairedNodeCollection(this, "Schemas", 1, StringComparer.Ordinal) + : new PairedNodeCollection(this, "Schemas", 1, StringComparer.OrdinalIgnoreCase); + } + } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Model/NodeCollection.cs b/Orm/Xtensive.Orm/Sql/Model/NodeCollection.cs index 093774af4e..5b71980c3b 100644 --- a/Orm/Xtensive.Orm/Sql/Model/NodeCollection.cs +++ b/Orm/Xtensive.Orm/Sql/Model/NodeCollection.cs @@ -84,5 +84,16 @@ public NodeCollection(int capacity) { nameIndex = new Dictionary(capacity, Comparer); } + + /// + /// Initializes new instance of this type. + /// + /// The initial collection capacity. + /// Comparer for inner name index. + public NodeCollection(int capacity, IEqualityComparer comparer) + : base(capacity) + { + nameIndex = new Dictionary(capacity, comparer); + } } } \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Sql/Model/PairedNodeCollection.cs b/Orm/Xtensive.Orm/Sql/Model/PairedNodeCollection.cs index 94e7e4b9d2..a99748d05d 100644 --- a/Orm/Xtensive.Orm/Sql/Model/PairedNodeCollection.cs +++ b/Orm/Xtensive.Orm/Sql/Model/PairedNodeCollection.cs @@ -4,6 +4,7 @@ using System; using System.Collections; +using System.Collections.Generic; using Xtensive.Core; namespace Xtensive.Sql.Model @@ -73,6 +74,24 @@ public PairedNodeCollection(TOwner owner, string property, int capacity) this.property = property; } + /// + /// Initializes a new instance of the class. + /// + /// The collection owner. + /// Owner collection property. + /// The initial collection capacity. + /// Comparer for inner name index. + public PairedNodeCollection(TOwner owner, string property, int capacity, IEqualityComparer equalityComparer) + : base(capacity, equalityComparer) + { + ArgumentValidator.EnsureArgumentNotNull(owner, nameof(owner)); + ArgumentValidator.EnsureArgumentNotNullOrEmpty(property, nameof(property)); + ArgumentValidator.EnsureArgumentNotNull(equalityComparer, nameof(equalityComparer)); + + this.owner = owner; + this.property = property; + } + #endregion } } \ No newline at end of file From bd7b9f4df323b9c4268b73795ea8db5920a81b91 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 14 Mar 2022 13:30:00 +0500 Subject: [PATCH 3/4] Improve changelog --- ChangeLog/7.0.3_dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog/7.0.3_dev.txt b/ChangeLog/7.0.3_dev.txt index e69de29bb2..144d5bbf3d 100644 --- a/ChangeLog/7.0.3_dev.txt +++ b/ChangeLog/7.0.3_dev.txt @@ -0,0 +1,2 @@ +[main] NodeCollection supports different equality comparers for internal name index +[oracle] Fixed scheme extraction for case-sensitive schema names \ No newline at end of file From 32721b39c6a7315663e5443a43e690a343965893 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 15 Mar 2022 13:44:26 +0500 Subject: [PATCH 4/4] Test for case-sensitive schema names --- .../StorageTestHelper.cs | 15 ++- .../Multimapping/CaseSensitiveSchemasTest.cs | 106 ++++++++++++++++++ 2 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 Orm/Xtensive.Orm.Tests/Storage/Multimapping/CaseSensitiveSchemasTest.cs diff --git a/Orm/Xtensive.Orm.Tests.Framework/StorageTestHelper.cs b/Orm/Xtensive.Orm.Tests.Framework/StorageTestHelper.cs index 24e0aae801..3b9a86d479 100644 --- a/Orm/Xtensive.Orm.Tests.Framework/StorageTestHelper.cs +++ b/Orm/Xtensive.Orm.Tests.Framework/StorageTestHelper.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2009-2022 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2009.12.17 @@ -51,13 +51,16 @@ public static void DemandSchemas(ConnectionInfo connectionInfo, params string[] var extractionResult = driver.Extract(connection, new[] {extractionTask}); var catalog = extractionResult.Catalogs.Single(); var existingSchemas = catalog.Schemas.Select(s => s.Name); - var schemasToCreate = schemas.Except(existingSchemas, StringComparer.OrdinalIgnoreCase); // Oracle does not support creating schemas, user should be created instead. - if (connectionInfo.Provider==WellKnown.Provider.Oracle) + if (connectionInfo.Provider == WellKnown.Provider.Oracle) { + var schemasToCreate = schemas.Except(existingSchemas, StringComparer.Ordinal); CreateUsers(connection, schemasToCreate); - else + } + else { + var schemasToCreate = schemas.Except(existingSchemas, StringComparer.OrdinalIgnoreCase); CreateSchemas(connection, catalog, schemasToCreate); + } connection.Close(); } diff --git a/Orm/Xtensive.Orm.Tests/Storage/Multimapping/CaseSensitiveSchemasTest.cs b/Orm/Xtensive.Orm.Tests/Storage/Multimapping/CaseSensitiveSchemasTest.cs new file mode 100644 index 0000000000..8234eb3e7d --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Storage/Multimapping/CaseSensitiveSchemasTest.cs @@ -0,0 +1,106 @@ +// Copyright (C) 2022 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System.Linq; +using NUnit.Framework; +using Xtensive.Orm.Configuration; +using M = Xtensive.Orm.Tests.Storage.Multimapping.CaseSensitiveSchemasTestModel; + +namespace Xtensive.Orm.Tests.Storage.Multimapping.CaseSensitiveSchemasTestModel +{ + namespace Schema1 + { + [HierarchyRoot] + public class Entity1 : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public string OriginalSchemaName { get; set; } + } + } + + namespace Schema2 + { + [HierarchyRoot] + public class Entity2 : Entity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public string OriginalSchemaName { get; set; } + } + } +} + +namespace Xtensive.Orm.Tests.Storage.Multimapping +{ + public sealed class CaseSensitiveSchemasTest : MultimappingTest + { + private const string Schema1Name = WellKnownSchemas.Schema1; + + private readonly string schema1UpperCaseName = Schema1Name.ToUpperInvariant(); + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.Oracle); + + protected override DomainConfiguration BuildConfiguration() + { + var configuration = base.BuildConfiguration(); + configuration.DefaultSchema = Schema1Name; + configuration.Types.Register(typeof(M.Schema1.Entity1)); + configuration.Types.Register(typeof(M.Schema2.Entity2)); + var rules = configuration.MappingRules; + rules.Map(typeof(M.Schema1.Entity1).Namespace).ToSchema(Schema1Name); + rules.Map(typeof(M.Schema2.Entity2).Namespace).ToSchema(schema1UpperCaseName); + return configuration; + } + + [Test] + public void MainTest() + { + BuildInitialDomain(); + BuildUpgradedDomain(); + } + + private void BuildInitialDomain() + { + var config = BuildConfiguration(); + PrepareSchema(config.ConnectionInfo); + var domain = Domain.Build(config); + using (domain) { + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + _ = new M.Schema1.Entity1 { OriginalSchemaName = Schema1Name }; + _ = new M.Schema2.Entity2 { OriginalSchemaName = schema1UpperCaseName }; + tx.Complete(); + } + } + } + + private void BuildUpgradedDomain() + { + var config = BuildConfiguration(); + config.UpgradeMode = DomainUpgradeMode.PerformSafely; + var domain = Domain.Build(config); + using (domain) { + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var e1 = session.Query.All().Single(); + var e2 = session.Query.All().Single(); + Assert.That(e1.OriginalSchemaName, Is.EqualTo(Schema1Name)); + Assert.That(e2.OriginalSchemaName, Is.EqualTo(schema1UpperCaseName)); + tx.Complete(); + } + } + } + + private void PrepareSchema(ConnectionInfo connectionInfo) + { + StorageTestHelper.DemandSchemas( + connectionInfo, Schema1Name, schema1UpperCaseName); + } + } +}