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 new file mode 100644 index 00000000000..fb94109c222 --- /dev/null +++ b/src/NHibernate.Test/DialectTest/SchemaTests/DialectNotSupportingNullInUnique.cs @@ -0,0 +1,48 @@ +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; + } + + [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)); + rc.Property( + x => x.Name1, + m => + { + m.NotNullable(true); + m.UniqueKey("Test"); + }); + rc.Property(x => x.Name2, m => m.UniqueKey("Test")); + }); + + 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/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(); + } + } + } +} 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) {