From 69364664b79110df0bebec902e5046cfc05840cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Sat, 22 Sep 2018 18:40:06 +0200 Subject: [PATCH 1/3] Fix NotNullUnique not taken into account for single column And rename it to something more explicit --- .../DialectNotSupportingNullInUnique.cs | 47 +++++++++++++++++++ .../NHSpecificTest/NH3749/TestDialect.cs | 4 +- src/NHibernate/Dialect/Dialect.cs | 14 ++++++ src/NHibernate/Mapping/Table.cs | 4 +- src/NHibernate/Mapping/UniqueKey.cs | 6 +-- 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs new file mode 100644 index 00000000000..b1f1606333e --- /dev/null +++ b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs @@ -0,0 +1,47 @@ +using System; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.DialectTest.SchemaTests +{ + public class DialectNotSupportingNullInUnique : GenericDialect + { + public override bool SupportsNullInUnique => false; + } + + public class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } + + [TestFixture] + public class DialectNotSupportingNullInUniqueFixture + { + protected HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Unique(true)); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + [Test] + public void ScriptGenerationForDialectNotSupportingNullInUnique() + { + var configuration = TestConfigurationHelper.GetDefaultConfiguration(); + configuration.AddMapping(GetMappings()); + + var script = configuration.GenerateSchemaCreationScript(new DialectNotSupportingNullInUnique()); + + Assert.That(script, Has.None.Contains("unique")); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs b/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs index a5beb5d72b8..e8ec6ebe300 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3749/TestDialect.cs @@ -3,9 +3,9 @@ namespace NHibernate.Test.NHSpecificTest.NH3749 { public class TestDialect : Dialect.Dialect { - public override bool SupportsNotNullUnique + public override bool SupportsNullInUnique { get { return false; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 1dcdac383f0..b0689932306 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -468,11 +468,25 @@ public virtual bool SupportsCascadeDelete get { return true; } } + // Since v5.2 + [Obsolete("Use or override SupportsNullInUnique instead")] public virtual bool SupportsNotNullUnique { get { return true; } } + /// + /// Does this dialect supports null values in columns belonging to an unique constraint/index? + /// + /// Some databases do not accept null in unique constraints at all. In such case, + /// this property should be overriden for yielding false. This property is not meant for distinguishing + /// databases ignoring null when checking uniqueness (ANSI behavior) from those considering null + /// as a value and checking for its uniqueness. + public virtual bool SupportsNullInUnique +#pragma warning disable 618 + => SupportsNotNullUnique; +#pragma warning restore 618 + public virtual IDataBaseSchema GetDataBaseSchema(DbConnection connection) { throw new NotSupportedException(); diff --git a/src/NHibernate/Mapping/Table.cs b/src/NHibernate/Mapping/Table.cs index 8fdd0f0528d..41643237fe2 100644 --- a/src/NHibernate/Mapping/Table.cs +++ b/src/NHibernate/Mapping/Table.cs @@ -406,7 +406,7 @@ public string SqlCreateString(Dialect.Dialect dialect, IMapping p, string defaul if (col.IsUnique) { - if (dialect.SupportsUnique) + if (dialect.SupportsUnique && (!col.IsNullable || dialect.SupportsNullInUnique)) { buf.Append(" unique"); } @@ -669,7 +669,7 @@ public string[] SqlAlterStrings(Dialect.Dialect dialect, IMapping p, ITableMetad } bool useUniqueConstraint = column.Unique && dialect.SupportsUnique - && (!column.IsNullable || dialect.SupportsNotNullUnique); + && (!column.IsNullable || dialect.SupportsNullInUnique); if (useUniqueConstraint) { alter.Append(" unique"); diff --git a/src/NHibernate/Mapping/UniqueKey.cs b/src/NHibernate/Mapping/UniqueKey.cs index 51148cbd3ad..12f5f1db0f8 100644 --- a/src/NHibernate/Mapping/UniqueKey.cs +++ b/src/NHibernate/Mapping/UniqueKey.cs @@ -32,7 +32,7 @@ public string SqlConstraintString(Dialect.Dialect dialect) buf.Append(column.GetQuotedName(dialect)); } //do not add unique constraint on DB not supporting unique and nullable columns - return !nullable || dialect.SupportsNotNullUnique ? buf.Append(StringHelper.ClosedParen).ToString() : null; + return !nullable || dialect.SupportsNullInUnique ? buf.Append(StringHelper.ClosedParen).ToString() : null; } /// @@ -63,7 +63,7 @@ public override string SqlConstraintString(Dialect.Dialect dialect, string const } return - !nullable || dialect.SupportsNotNullUnique + !nullable || dialect.SupportsNullInUnique ? StringHelper.Replace(buf.Append(StringHelper.ClosedParen).ToString(), "primary key", "unique") : null; } @@ -103,7 +103,7 @@ public override string SqlDropString(Dialect.Dialect dialect, string defaultCata public override bool IsGenerated(Dialect.Dialect dialect) { - if (dialect.SupportsNotNullUnique) + if (dialect.SupportsNullInUnique) return true; foreach (Column column in ColumnIterator) { From bd1e0fea7402185f3e48ef47e0518360b6bf8af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Mon, 24 Sep 2018 14:22:36 +0200 Subject: [PATCH 2/3] Test also the multi-column case --- .../SchemaTests/DialectNotSupportingNullInUnique.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs index b1f1606333e..cb24a0ed09e 100644 --- a/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs +++ b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs @@ -15,6 +15,8 @@ public class Entity { public virtual Guid Id { get; set; } public virtual string Name { get; set; } + public virtual string Name1 { get; set; } + public virtual string Name2 { get; set; } } [TestFixture] @@ -28,6 +30,14 @@ protected HbmMapping GetMappings() { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); rc.Property(x => x.Name, m => m.Unique(true)); + rc.Property( + x => x.Name1, + m => + { + m.NotNullable(true); + m.UniqueKey("Test"); + }); + rc.Property(x => x.Name2, m => m.UniqueKey("Test")); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); From f9b0d4a1020777acc8bd811bf7d439f06db2445c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Mon, 24 Sep 2018 14:59:12 +0200 Subject: [PATCH 3/3] Add an actual insertion test --- .../DialectTest/SchemaTests/NullInUnique.cs | 67 +++++++++++++++++++ .../DialectNotSupportingNullInUnique.cs | 11 +-- .../DialectTest/SchemaTests/Entity.cs | 12 ++++ .../DialectTest/SchemaTests/NullInUnique.cs | 56 ++++++++++++++++ 4 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 src/NHibernate.Test/Async/DialectTest/SchemaTests/NullInUnique.cs create mode 100644 src/NHibernate.Test/DialectTest/SchemaTests/Entity.cs create mode 100644 src/NHibernate.Test/DialectTest/SchemaTests/NullInUnique.cs diff --git a/src/NHibernate.Test/Async/DialectTest/SchemaTests/NullInUnique.cs b/src/NHibernate.Test/Async/DialectTest/SchemaTests/NullInUnique.cs new file mode 100644 index 00000000000..2c06fe1e453 --- /dev/null +++ b/src/NHibernate.Test/Async/DialectTest/SchemaTests/NullInUnique.cs @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.DialectTest.SchemaTests +{ + using System.Threading.Tasks; + [TestFixture] + public class NullInUniqueFixtureAsync: TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Unique(true)); + rc.Property( + x => x.Name1, + m => + { + m.NotNullable(true); + m.UniqueKey("Test"); + }); + rc.Property(x => x.Name2, m => m.UniqueKey("Test")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = Sfi.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Entity").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public async Task InsertNullInUniqueAsync() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + await (session.SaveAsync(new Entity { Name1 = "1" })); + await (session.SaveAsync(new Entity { Name = "N", Name1 = "1", Name2 = "2"})); + await (session.SaveAsync(new Entity { Name = "Na", Name1 = "2", Name2 = "1"})); + await (session.SaveAsync(new Entity { Name = "Nam", Name1 = "2"})); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs index cb24a0ed09e..fb94109c222 100644 --- a/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs +++ b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs @@ -1,5 +1,4 @@ -using System; -using NHibernate.Cfg.MappingSchema; +using NHibernate.Cfg.MappingSchema; using NHibernate.Dialect; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -11,14 +10,6 @@ public class DialectNotSupportingNullInUnique : GenericDialect public override bool SupportsNullInUnique => false; } - public class Entity - { - public virtual Guid Id { get; set; } - public virtual string Name { get; set; } - public virtual string Name1 { get; set; } - public virtual string Name2 { get; set; } - } - [TestFixture] public class DialectNotSupportingNullInUniqueFixture { diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/Entity.cs b/src/NHibernate.Test/DialectTest/SchemaTests/Entity.cs new file mode 100644 index 00000000000..633dd5264e9 --- /dev/null +++ b/src/NHibernate.Test/DialectTest/SchemaTests/Entity.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.DialectTest.SchemaTests +{ + public class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual string Name1 { get; set; } + public virtual string Name2 { get; set; } + } +} diff --git a/src/NHibernate.Test/DialectTest/SchemaTests/NullInUnique.cs b/src/NHibernate.Test/DialectTest/SchemaTests/NullInUnique.cs new file mode 100644 index 00000000000..19effa5c1d6 --- /dev/null +++ b/src/NHibernate.Test/DialectTest/SchemaTests/NullInUnique.cs @@ -0,0 +1,56 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Dialect; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.DialectTest.SchemaTests +{ + [TestFixture] + public class NullInUniqueFixture: TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Unique(true)); + rc.Property( + x => x.Name1, + m => + { + m.NotNullable(true); + m.UniqueKey("Test"); + }); + rc.Property(x => x.Name2, m => m.UniqueKey("Test")); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnTearDown() + { + using (var session = Sfi.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Entity").ExecuteUpdate(); + transaction.Commit(); + } + } + + [Test] + public void InsertNullInUnique() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Save(new Entity { Name1 = "1" }); + session.Save(new Entity { Name = "N", Name1 = "1", Name2 = "2"}); + session.Save(new Entity { Name = "Na", Name1 = "2", Name2 = "1"}); + session.Save(new Entity { Name = "Nam", Name1 = "2"}); + transaction.Commit(); + } + } + } +}