diff --git a/releasenotes.txt b/releasenotes.txt
index 5fd64fc3975..7d995c663c0 100644
--- a/releasenotes.txt
+++ b/releasenotes.txt
@@ -234,6 +234,25 @@ Release notes - NHibernate - Version 5.4.0
* #2242 Test case for NH-3972 - SQL error when selecting a column of a subclass when sibling classes have a column of the same name
+Build 5.3.17
+=============================
+
+Release notes - NHibernate - Version 5.3.17
+
+5 issues were resolved in this release.
+
+** Bug
+
+ * #3306 Invalid SQL when referencing nullable entity in correlated subquery
+ * #3304 Fix SetSnapShot CopyTo variance failure
+ * #3294 Undefined join type failure with cross joins and Informix
+
+** Task
+
+ * #3315 Release 5.3.17
+ * #3300 Backport handling of null DateTime parameters in Npgsql 6+
+
+
Build 5.3.16
=============================
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
new file mode 100644
index 00000000000..ba446d194f9
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
@@ -0,0 +1,69 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System.Linq;
+using NUnit.Framework;
+using NHibernate.Linq;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ private const string NAME_JOE = "Joe";
+ private const string NAME_ALLEN = "Allen";
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ var joe = new Customer { Name = NAME_JOE };
+ session.Save(joe);
+
+ var allen = new Customer { Name = NAME_ALLEN };
+ session.Save(allen);
+
+ var joeInvoice0 = new Invoice { Customer = joe, Number = 0 };
+ session.Save(joeInvoice0);
+
+ var allenInvoice1 = new Invoice { Customer = allen, Number = 1 };
+ session.Save(allenInvoice1);
+
+ tx.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ session.Delete("from Invoice");
+ session.Delete("from Customer");
+ tx.Commit();
+ }
+ }
+
+ [Test]
+ public async Task NullableEntityInCorrelatedSubqueryAsync()
+ {
+ using (var s = OpenSession())
+ {
+ var customers = s.Query().Where(c => c.Name == NAME_JOE);
+ var results = await (s.Query()
+ .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToListAsync());
+
+ Assert.That(results.Count, Is.EqualTo(1));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
new file mode 100644
index 00000000000..d1d511eca59
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ [TestFixture]
+ public class Fixture : BugTestCase
+ {
+ private const string NAME_JOE = "Joe";
+ private const string NAME_ALLEN = "Allen";
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ var joe = new Customer { Name = NAME_JOE };
+ session.Save(joe);
+
+ var allen = new Customer { Name = NAME_ALLEN };
+ session.Save(allen);
+
+ var joeInvoice0 = new Invoice { Customer = joe, Number = 0 };
+ session.Save(joeInvoice0);
+
+ var allenInvoice1 = new Invoice { Customer = allen, Number = 1 };
+ session.Save(allenInvoice1);
+
+ tx.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ session.Delete("from Invoice");
+ session.Delete("from Customer");
+ tx.Commit();
+ }
+ }
+
+ [Test]
+ public void NullableEntityInCorrelatedSubquery()
+ {
+ using (var s = OpenSession())
+ {
+ var customers = s.Query().Where(c => c.Name == NAME_JOE);
+ var results = s.Query()
+ .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToList();
+
+ Assert.That(results.Count, Is.EqualTo(1));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml
new file mode 100644
index 00000000000..515584fe116
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs
new file mode 100644
index 00000000000..2c472579b00
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ public class Customer
+ {
+ public virtual int ID { get; protected set; }
+ public virtual ISet Invoices { get; set; }
+ public virtual string Name { get; set; }
+ }
+
+ public class Invoice
+ {
+ public virtual int ID { get; protected set; }
+ public virtual Customer Customer { get; set; }
+ public virtual int Number { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
index e00af722111..47b03b50ec9 100644
--- a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
+++ b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections;
+using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NHibernate.Collection.Generic.SetHelpers;
+using NSubstitute.ExceptionExtensions;
using NUnit.Framework;
namespace NHibernate.Test.UtilityTest
@@ -70,6 +73,29 @@ public void TestCopyTo()
Assert.That(list, Is.EquivalentTo(array));
}
+ [Test]
+ public void TestCopyToObjectArray()
+ {
+ var list = new List { "test1", null, "test2" };
+ ICollection sn = new SetSnapShot(list);
+
+ var array = new object[3];
+ sn.CopyTo(array, 0);
+ Assert.That(list, Is.EquivalentTo(array));
+ }
+
+ [Test]
+ public void WhenCopyToIsCalledWithIncompatibleArrayTypeThenThrowArgumentOrInvalidCastException()
+ {
+ var list = new List { "test1", null, "test2" };
+ ICollection sn = new SetSnapShot(list);
+
+ var array = new int[3];
+ Assert.That(
+ () => sn.CopyTo(array, 0),
+ Throws.ArgumentException.Or.TypeOf());
+ }
+
[Test]
public void TestSerialization()
{
diff --git a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
index a8985c46c03..475bebf6c2e 100644
--- a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
+++ b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
@@ -114,12 +114,16 @@ IEnumerator IEnumerable.GetEnumerator()
void ICollection.CopyTo(Array array, int index)
{
- if (!(array is T[] typedArray))
+ if (array is T[] typedArray)
{
- throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array));
+ CopyTo(typedArray, index);
+ return;
}
- CopyTo(typedArray, index);
+ if (_hasNull)
+ array.SetValue(default(T), index);
+ ICollection keysCollection = _values.Keys;
+ keysCollection.CopyTo(array, index + (_hasNull ? 1 : 0));
}
public int Count => _values.Count + (_hasNull ? 1 : 0);
@@ -153,12 +157,26 @@ protected SetSnapShot(SerializationInfo info, StreamingContext context) : base(i
void ICollection.CopyTo(Array array, int index)
{
- if (!(array is T[] typedArray))
+ if (array is T[] typedArray)
{
- throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array));
+ CopyTo(typedArray, index);
+ return;
}
- CopyTo(typedArray, index);
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), index, "Array index cannot be negative");
+
+ if (index > array.Length || Count > array.Length - index)
+ throw new ArgumentException("Provided array is too small", nameof(array));
+
+ foreach (var value in this)
+ {
+ array.SetValue(value, index);
+ index++;
+ }
}
bool ICollection.IsSynchronized => false;
diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs
index 2f942815a57..4be77acd721 100644
--- a/src/NHibernate/Dialect/InformixDialect.cs
+++ b/src/NHibernate/Dialect/InformixDialect.cs
@@ -333,6 +333,9 @@ public override JoinFragment CreateOuterJoinFragment()
return new InformixJoinFragment();
}
+ ///
+ public override bool SupportsCrossJoin => false;
+
/// The SQL literal value to which this database maps boolean values.
/// The boolean value
/// The appropriate SQL literal.
diff --git a/src/NHibernate/Dialect/InformixDialect0940.cs b/src/NHibernate/Dialect/InformixDialect0940.cs
index bdfcae4f27f..13563df43e8 100644
--- a/src/NHibernate/Dialect/InformixDialect0940.cs
+++ b/src/NHibernate/Dialect/InformixDialect0940.cs
@@ -126,10 +126,7 @@ public override JoinFragment CreateOuterJoinFragment()
return new ANSIJoinFragment();
}
- ///
- public override bool SupportsCrossJoin => false;
-
- ///
+ ///
/// Does this Dialect have some kind of LIMIT syntax?
///
/// False, unless overridden.
diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
index f0ac747bf4d..4b0f127a2ca 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
@@ -398,7 +398,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string
bool joinIsNeeded;
//For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations)
- bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause;
+ bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause && !IsCorrelatedSubselect;
if ( IsDotNode( parent ) )
{