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)
{