From 64f004d964814a7ca0821fcd5d3baa8e6d41cb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Wed, 15 Mar 2017 18:40:51 +0100 Subject: [PATCH 1/4] NH-3961: test cases. --- .../NH3961/DateParametersComparedTo.cs | 282 ++++++++++++++++++ .../NHSpecificTest/NH3961/Entity.cs | 12 + .../NHSpecificTest/NH3961/Mappings.hbm.xml | 11 + src/NHibernate.Test/NHibernate.Test.csproj | 3 + 4 files changed, 308 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs b/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs new file mode 100644 index 00000000000..678e90c8502 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3961/DateParametersComparedTo.cs @@ -0,0 +1,282 @@ +using System; +using System.Globalization; +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3961 +{ + [TestFixture] + public class DateParametersComparedTo : BugTestCase + { + private DateTime _testDate; + private CultureInfo _backupCulture; + private CultureInfo _backupUICulture; + private readonly CultureInfo _testCulture = CultureInfo.GetCultureInfo("fr-FR"); + + protected override void OnSetUp() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + // day > 12 for ensuring a mdy/dmy mix-up would cause a failure. + _testDate = new DateTime(2017, 03, 15); + var e1 = new Entity { Name = "Bob", NullableDateTime = _testDate, NonNullableDateTime = _testDate }; + session.Save(e1); + + var e2 = new Entity { Name = "Sally", NullableDateTime = _testDate.AddDays(1), NonNullableDateTime = _testDate.AddDays(1) }; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + } + + _backupCulture = CultureInfo.CurrentCulture; + _backupUICulture = CultureInfo.CurrentUICulture; + // "CultureInfo.CurrentCulture =": Fx 4.6 only; affect trough Thread.CurrentThread instead if in need of supporting a previous Fx. + // This test needs a culture using a dmy date format. If the test system does not support fr-FR, try find another one... + // This test assumes the SQL user language is set as English, otherwise it may not showcase the failure. + CultureInfo.CurrentCulture = _testCulture; + CultureInfo.CurrentUICulture = _testCulture; + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + CultureInfo.CurrentCulture = _backupCulture; + CultureInfo.CurrentUICulture = _backupUICulture; + } + + // Non-reg test case + [Test] + public void NonNullableMappedAsDateShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.Date)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Non-reg test case + [Test] + public void NonNullableMappedAsDateShouldIgnoreTime() + { + using (ISession session = OpenSession()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate.AddMinutes(10).MappedAs(NHibernateUtil.Date)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Non-reg test case + [Test] + public void NonNullableMappedAsDateTimeShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Non-reg test case + [Test] + public void NonNullableMappedAsTimestampShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.Timestamp)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Non-reg test case + [Test] + public void NonNullableParameterValueShouldNotBeCachedWithMappedAsAnd() + { + // Dodges the query parameter formatting bug for showcasing the parameter value bug + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US"); + CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; + try + { + using (ISession session = OpenSession()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + + var testDate = _testDate.AddMinutes(10); + result = session.Query() + .Where(e => e.NonNullableDateTime == testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + CollectionAssert.IsEmpty(result); + } + } + finally + { + CultureInfo.CurrentCulture = _testCulture; + CultureInfo.CurrentUICulture = _testCulture; + } + } + + // Non-reg test case + [Test] + public void NonNullableShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NonNullableDateTime == _testDate) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Failing test case till NH-3961 is fixed + [Test] + public void NullableMappedAsDateShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.Date)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Failing test case till NH-3961 is fixed + [Test] + public void NullableMappedAsDateShouldIgnoreTime() + { + var testDate = _testDate.AddMinutes(10); + using (ISession session = OpenSession()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == testDate.MappedAs(NHibernateUtil.Date)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Failing test case till NH-3961 is fixed + [Test] + public void NullableMappedAsDateTimeShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Failing test case till NH-3961 is fixed + [Test] + public void NullableMappedAsTimestampShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.Timestamp)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + + // Failing test case till NH-3961 is fixed + [Test] + public void NullableParameterValueShouldNotBeCachedWithMappedAs() + { + // Dodges the query parameter formatting bug for showcasing the parameter value bug + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US"); + CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; + try + { + using (ISession session = OpenSession()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == _testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + + var testDate = _testDate.AddMinutes(10); + result = session.Query() + .Where(e => e.NullableDateTime == testDate.MappedAs(NHibernateUtil.DateTime)) + .ToList(); + + CollectionAssert.IsEmpty(result); + } + } + finally + { + CultureInfo.CurrentCulture = _testCulture; + CultureInfo.CurrentUICulture = _testCulture; + } + } + + // Non-reg test case + [Test] + public void NullableShouldBeCultureAgnostic() + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + var result = session.Query() + .Where(e => e.NullableDateTime == _testDate) + .ToList(); + + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Bob", result[0].Name); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs new file mode 100644 index 00000000000..c5fbfca1b15 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3961/Entity.cs @@ -0,0 +1,12 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3961 +{ + public class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual DateTime NonNullableDateTime { get; set; } + public virtual DateTime? NullableDateTime { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml new file mode 100644 index 00000000000..3795a14547c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3961/Mappings.hbm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 8fae8736b23..d8b3df6eeec 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -737,6 +737,8 @@ + + @@ -3208,6 +3210,7 @@ + From 28fbd7f025f7d0861c9ee550c0b99bc36e3a3b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Fri, 17 Mar 2017 16:24:19 +0100 Subject: [PATCH 2/4] NH-3961: fix by avoiding redundant partial expression tree evaluation. --- src/NHibernate/Linq/NhRelinqQueryParser.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Linq/NhRelinqQueryParser.cs b/src/NHibernate/Linq/NhRelinqQueryParser.cs index 86c1410099e..4e3b04f04e1 100644 --- a/src/NHibernate/Linq/NhRelinqQueryParser.cs +++ b/src/NHibernate/Linq/NhRelinqQueryParser.cs @@ -12,6 +12,7 @@ using Remotion.Linq.Parsing.Structure; using Remotion.Linq.Parsing.Structure.IntermediateModel; using Remotion.Linq.Parsing.Structure.NodeTypeProviders; +using Remotion.Linq.Parsing.Structure.ExpressionTreeProcessors; namespace NHibernate.Linq { @@ -26,9 +27,11 @@ static NhRelinqQueryParser() transformerRegistry.Register(new RemoveRedundantCast()); transformerRegistry.Register(new SimplifyCompareTransformer()); - var processor = ExpressionTreeParser.CreateDefaultProcessor(transformerRegistry); - // Add custom processors here: - // processor.InnerProcessors.Add (new MyExpressionTreeProcessor()); + // If needing a compound processor for adding other processing, do not use + // ExpressionTreeParser.CreateDefaultProcessor(transformerRegistry), it would + // cause NH-3961 again by including a PartialEvaluatingExpressionTreeProcessor. + // Directly instanciate a CompoundExpressionTreeProcessor instead. + var processor = new TransformingExpressionTreeProcessor(transformerRegistry); var nodeTypeProvider = new NHibernateNodeTypeProvider(); From e45cc98d8428fbb6d5acbe6744a64db9eff41123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 18 Mar 2017 09:49:42 +0100 Subject: [PATCH 3/4] NH-3386 - Test cases as "fixed" by NH-3961. --- .../NHSpecificTest/NH3386/Entity.cs | 10 +++ .../NHSpecificTest/NH3386/Fixture.cs | 80 +++++++++++++++++++ .../NHSpecificTest/NH3386/Mappings.hbm.xml | 9 +++ src/NHibernate.Test/NHibernate.Test.csproj | 3 + 4 files changed, 102 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3386/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3386/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml diff --git a/src/NHibernate.Test/NHSpecificTest/NH3386/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3386/Entity.cs new file mode 100644 index 00000000000..f0030a5a65e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3386/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3386 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3386/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3386/Fixture.cs new file mode 100644 index 00000000000..0078739a8db --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3386/Fixture.cs @@ -0,0 +1,80 @@ +using System.Linq; +using NHibernate.Linq; +using NUnit.Framework; +using System; +using NHibernate.SqlCommand; + +namespace NHibernate.Test.NHSpecificTest.NH3386 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect is Dialect.MsSql2000Dialect; + } + + protected override void OnSetUp() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + session.Flush(); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + + [Test] + public void ShouldSupportNonRuntimeExtensionWithoutEntityReference() + { + var sqlInterceptor = new SqlInterceptor(); + using (ISession session = OpenSession(sqlInterceptor)) + using (session.BeginTransaction()) + { + var result = session.Query() + .OrderBy(e => SqlServerFunction.NewID()); + + Assert.DoesNotThrow(() => { result.ToList(); }); + Assert.That(sqlInterceptor.Sql.ToString(), Does.Contain(nameof(SqlServerFunction.NewID)).IgnoreCase); + } + } + } + + public static class SqlServerFunction + { + [LinqExtensionMethod] + public static Guid NewID() + { + throw new InvalidOperationException("To be translated to SQL only"); + } + } + + public class SqlInterceptor: EmptyInterceptor + { + public SqlString Sql { get; private set; } + + public override SqlString OnPrepareStatement(SqlString sql) + { + Sql = sql; + return base.OnPrepareStatement(sql); + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml new file mode 100644 index 00000000000..0a13a0df6ff --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3386/Mappings.hbm.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index d8b3df6eeec..e6b4cd82bf4 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -737,6 +737,8 @@ + + @@ -3210,6 +3212,7 @@ + From aba2943b7c84fb989e8d8ce69261056fd118e9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 18 Mar 2017 22:12:00 +0100 Subject: [PATCH 4/4] NH-3386 - update documentation accordingly (and fix some typo, bad wording, ...) --- doc/reference/modules/query_linq.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/reference/modules/query_linq.xml b/doc/reference/modules/query_linq.xml index 32e0e041aa1..fceba0e0f39 100644 --- a/doc/reference/modules/query_linq.xml +++ b/doc/reference/modules/query_linq.xml @@ -8,13 +8,13 @@ The Linq provider works as an extension of the ISession. It is defined in the NHibernate.Linq namespace, so this namespace has to be imported for using the - Linq provider. Of course, the LINQ namespace is still needed too. + Linq provider. Of course, the Linq namespace is still needed too. Note: NHibernate has another querying API which uses lambda, QueryOver. - It should not be confused with a LINQ provider. + It should not be confused with a Linq provider. @@ -545,9 +545,10 @@ IList cats = } }]]> - It is required that at least one of the parameters of the method call has its value originating - from an entity. Otherwise, the Linq provider will try to evaluate the method call with .Net - runtime. + The method call will always be translated to SQL if at least one of the parameters of the + method call has its value originating from an entity. Otherwise, the Linq provider will try to + evaluate the method call with .Net runtime instead. Since NHibernate 5.0, if this runtime + evaluation fails (throws an exception), then the method call will be translated to SQL too. @@ -565,7 +566,7 @@ IList cats =   As an example, here is how to add support for an AsNullable method which - would allow to call aggregates which lay yield null without to explicitly + would allow to call aggregates which may yield null without to explicitly cast to the nullable type of the aggregate. cats = } }]]> - Adding support in Linq to NHibernate for this custom method requires a generator. For this + Adding support in Linq to NHibernate for a custom method requires a generator. For this AsNullable method, we need a method generator, declaring statically its supported method. @@ -612,8 +613,8 @@ IList cats = For adding AsNullableGenerator in Linq to NHibernate provider, a new - generators registry should be used. Derive from the default one and merge it. (Static - declaration of method support case.) + generators registry should be used. Derive from the default one and merge it. (Here we + have a static declaration of method support case.)