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