From 0e6d7a95f7a11bfdac92e4be843e4690506b25e6 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Mon, 15 Jan 2018 15:52:34 +0100 Subject: [PATCH 01/30] Add query support for the static methods of System.Decimal #831 --- .../NHSpecificTest/GH0831/Entity.cs | 22 +++ .../NHSpecificTest/GH0831/FixtureByCode.cs | 153 ++++++++++++++++++ .../Linq/Functions/DecimalGenerator.cs | 43 +++++ .../DefaultLinqToHqlGeneratorsRegistry.cs | 4 +- 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs create mode 100644 src/NHibernate/Linq/Functions/DecimalGenerator.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs new file mode 100644 index 00000000000..bab0fa8e86c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs @@ -0,0 +1,22 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH0831 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual decimal Value { get; set; } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public override bool Equals(object obj) + { + var that = obj as Entity; + + return (that != null) && Id.Equals(that.Id); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs new file mode 100644 index 00000000000..80f2e3dbe01 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; + +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH0831 +{ + public class ByCodeFixture : TestCaseMappingByCode + { + private readonly IList entities = new List + { + new Entity { Value = 0.5m }, + new Entity { Value = 1.0m }, + new Entity { Value = 1.5m }, + new Entity { Value = 2.0m }, + new Entity { Value = 2.5m }, + new Entity { Value = 3.0m } + }; + + 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.Value); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (ISession session = OpenSession()) + using (ITransaction transaction = session.BeginTransaction()) + { + foreach (Entity entity in entities) + { + session.Save(entity); + } + + 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 CanHandleAdd() + { + CanHandle(e => decimal.Add(e.Value, 2) > 3.0m); + CanHandle(e => decimal.Add(2, e.Value) > 3.0m); + } + + [Test] + public void CanHandleCeiling() + { + CanHandle(e => decimal.Ceiling(e.Value) > 1.0m); + } + + [Test] + public void CanHandleCompare() + { + CanHandle(e => decimal.Compare(e.Value, 1.0m) < 1); + CanHandle(e => decimal.Compare(1.0m, e.Value) < 1); + } + + [Test] + public void CanHandleDivide() + { + CanHandle(e => decimal.Divide(e.Value, 1.25m) < 1); + CanHandle(e => decimal.Divide(1.25m, e.Value) < 1); + } + + [Test] + public void CanHandleEquals() + { + CanHandle(e => decimal.Equals(e.Value, 1.0m)); + CanHandle(e => decimal.Equals(1.0m, e.Value)); + } + + [Test] + public void CanHandleFloor() + { + CanHandle(e => decimal.Floor(e.Value) > 1.0m); + } + + [Test] + public void CanHandleMultiply() + { + CanHandle(e => decimal.Multiply(e.Value, 10m) > 10m); + CanHandle(e => decimal.Multiply(10m, e.Value) > 10m); + } + + [Test] + public void CanHandleNegate() + { + CanHandle(e => decimal.Negate(e.Value) > -1.0m); + } + + [Test] + public void CanHandleRemainder() + { + CanHandle(e => decimal.Remainder(e.Value, 2m) >= 0.5m); + CanHandle(e => decimal.Remainder(2m, e.Value) >= 0.5m); + } + + [Test] + public void CanHandleRound() + { + CanHandle(e => decimal.Round(e.Value) >= 2.0m); + CanHandle(e => decimal.Round(e.Value, 1) >= 1.5m); + CanHandle(e => decimal.Round(e.Value, MidpointRounding.AwayFromZero) >= 2.0m); + CanHandle(e => decimal.Round(e.Value, 1, MidpointRounding.AwayFromZero) >= 2.0m); + } + + [Test] + public void CanHandleSubtract() + { + CanHandle(e => decimal.Subtract(e.Value, 1m) > 1m); + CanHandle(e => decimal.Subtract(2m, e.Value) > 1m); + } + + private void CanHandle(Func predicate) + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + IEnumerable inMemory = entities.Where(predicate).ToList(); + IEnumerable inSession = session.Query().Where(predicate).ToList(); + + Assume.That(inMemory.Any()); + + CollectionAssert.AreEquivalent(inMemory, inSession); + } + } + } +} diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs new file mode 100644 index 00000000000..137f8afa976 --- /dev/null +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; + +namespace NHibernate.Linq.Functions +{ + public class DecimalGenerator : BaseHqlGeneratorForMethod + { + public DecimalGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Add(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Divide(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Multiply(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Negate(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Remainder(default(decimal), default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(MidpointRounding))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int), default(MidpointRounding))), + ReflectHelper.GetMethodDefinition(() => decimal.Subtract(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + string function = method.Name.ToLowerInvariant(); + + return treeBuilder.MethodCall(function, arguments.Select(x => visitor.Visit(x).AsExpression())); + } + } +} diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 75e20952052..5a7732ab79c 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -51,6 +51,8 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new CollectionContainsGenerator()); this.Merge(new DateTimePropertiesHqlGenerator()); + + this.Merge(new DecimalGenerator()); } protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) @@ -100,4 +102,4 @@ public void RegisterGenerator(IRuntimeMethodHqlGenerator generator) runtimeMethodHqlGenerators.Add(generator); } } -} \ No newline at end of file +} From ef0490e38c39735f95e6d51e1e69f1e5fc53edf5 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Tue, 16 Jan 2018 09:55:35 +1300 Subject: [PATCH 02/30] Fix type of predicate to be expression --- src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 80f2e3dbe01..d978cdb526c 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -136,12 +136,12 @@ public void CanHandleSubtract() CanHandle(e => decimal.Subtract(2m, e.Value) > 1m); } - private void CanHandle(Func predicate) + private void CanHandle(Expression> predicate) { using (ISession session = OpenSession()) using (session.BeginTransaction()) { - IEnumerable inMemory = entities.Where(predicate).ToList(); + IEnumerable inMemory = entities.Where(predicate.Compile()).ToList(); IEnumerable inSession = session.Query().Where(predicate).ToList(); Assume.That(inMemory.Any()); From 10d0567f126ee7f3e85123623fc65e3bae7dff54 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Tue, 16 Jan 2018 10:46:03 +1300 Subject: [PATCH 03/30] Add missing namespace import --- src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index d978cdb526c..a43db328f7a 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; From 6b86b78574ba2859e23edc12f763115dd8f031f7 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Tue, 16 Jan 2018 14:44:00 +0100 Subject: [PATCH 04/30] Apply mapping between Decimal.* and SQL functions --- .../NHSpecificTest/GH0831/Entity.cs | 7 ++- .../NHSpecificTest/GH0831/FixtureByCode.cs | 54 +++++++++---------- .../Linq/Functions/DecimalGenerator.cs | 40 ++++++++++++-- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs index bab0fa8e86c..9ffba39ddd0 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/Entity.cs @@ -5,7 +5,7 @@ namespace NHibernate.Test.NHSpecificTest.GH0831 class Entity { public virtual Guid Id { get; set; } - public virtual decimal Value { get; set; } + public virtual decimal EntityValue { get; set; } public override int GetHashCode() { @@ -18,5 +18,10 @@ public override bool Equals(object obj) return (that != null) && Id.Equals(that.Id); } + + public override string ToString() + { + return EntityValue.ToString(); + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index a43db328f7a..2ec7f65fd03 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -14,12 +14,12 @@ public class ByCodeFixture : TestCaseMappingByCode { private readonly IList entities = new List { - new Entity { Value = 0.5m }, - new Entity { Value = 1.0m }, - new Entity { Value = 1.5m }, - new Entity { Value = 2.0m }, - new Entity { Value = 2.5m }, - new Entity { Value = 3.0m } + new Entity { EntityValue = 0.5m }, + new Entity { EntityValue = 1.0m }, + new Entity { EntityValue = 1.5m }, + new Entity { EntityValue = 2.0m }, + new Entity { EntityValue = 2.5m }, + new Entity { EntityValue = 3.0m } }; protected override HbmMapping GetMappings() @@ -28,7 +28,7 @@ protected override HbmMapping GetMappings() mapper.Class(rc => { rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); - rc.Property(x => x.Value); + rc.Property(x => x.EntityValue); }); return mapper.CompileMappingForAllExplicitlyAddedEntities(); @@ -64,77 +64,75 @@ protected override void OnTearDown() [Test] public void CanHandleAdd() { - CanHandle(e => decimal.Add(e.Value, 2) > 3.0m); - CanHandle(e => decimal.Add(2, e.Value) > 3.0m); + CanHandle(e => decimal.Add(e.EntityValue, 2) > 3.0m); + CanHandle(e => decimal.Add(2, e.EntityValue) > 3.0m); } [Test] public void CanHandleCeiling() { - CanHandle(e => decimal.Ceiling(e.Value) > 1.0m); + CanHandle(e => decimal.Ceiling(e.EntityValue) > 1.0m); } [Test] public void CanHandleCompare() { - CanHandle(e => decimal.Compare(e.Value, 1.0m) < 1); - CanHandle(e => decimal.Compare(1.0m, e.Value) < 1); + CanHandle(e => decimal.Compare(e.EntityValue, 1.5m) < 1); + CanHandle(e => decimal.Compare(1.0m, e.EntityValue) < 1); } [Test] public void CanHandleDivide() { - CanHandle(e => decimal.Divide(e.Value, 1.25m) < 1); - CanHandle(e => decimal.Divide(1.25m, e.Value) < 1); + CanHandle(e => decimal.Divide(e.EntityValue, 1.25m) < 1); + CanHandle(e => decimal.Divide(1.25m, e.EntityValue) < 1); } [Test] public void CanHandleEquals() { - CanHandle(e => decimal.Equals(e.Value, 1.0m)); - CanHandle(e => decimal.Equals(1.0m, e.Value)); + CanHandle(e => decimal.Equals(e.EntityValue, 1.0m)); + CanHandle(e => decimal.Equals(1.0m, e.EntityValue)); } [Test] public void CanHandleFloor() { - CanHandle(e => decimal.Floor(e.Value) > 1.0m); + CanHandle(e => decimal.Floor(e.EntityValue) > 1.0m); } [Test] public void CanHandleMultiply() { - CanHandle(e => decimal.Multiply(e.Value, 10m) > 10m); - CanHandle(e => decimal.Multiply(10m, e.Value) > 10m); + CanHandle(e => decimal.Multiply(e.EntityValue, 10m) > 10m); + CanHandle(e => decimal.Multiply(10m, e.EntityValue) > 10m); } [Test] public void CanHandleNegate() { - CanHandle(e => decimal.Negate(e.Value) > -1.0m); + CanHandle(e => decimal.Negate(e.EntityValue) > -1.0m); } [Test] public void CanHandleRemainder() { - CanHandle(e => decimal.Remainder(e.Value, 2m) >= 0.5m); - CanHandle(e => decimal.Remainder(2m, e.Value) >= 0.5m); + CanHandle(e => decimal.Remainder(e.EntityValue, 2) == 0); + CanHandle(e => decimal.Remainder(2, e.EntityValue) < 1); } [Test] public void CanHandleRound() { - CanHandle(e => decimal.Round(e.Value) >= 2.0m); - CanHandle(e => decimal.Round(e.Value, 1) >= 1.5m); - CanHandle(e => decimal.Round(e.Value, MidpointRounding.AwayFromZero) >= 2.0m); - CanHandle(e => decimal.Round(e.Value, 1, MidpointRounding.AwayFromZero) >= 2.0m); + CanHandle(e => decimal.Round(e.EntityValue) >= 2.0m); + CanHandle(e => decimal.Round(e.EntityValue, 1) >= 1.5m); } [Test] public void CanHandleSubtract() { - CanHandle(e => decimal.Subtract(e.Value, 1m) > 1m); - CanHandle(e => decimal.Subtract(2m, e.Value) > 1m); + CanHandle(e => decimal.Subtract(e.EntityValue, 1m) > 1m); + CanHandle(e => decimal.Subtract(2m, e.EntityValue) > 1m); } private void CanHandle(Expression> predicate) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 137f8afa976..f488c135912 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -27,8 +26,6 @@ public DecimalGenerator() ReflectHelper.GetMethodDefinition(() => decimal.Remainder(default(decimal), default(decimal))), ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(MidpointRounding))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int), default(MidpointRounding))), ReflectHelper.GetMethodDefinition(() => decimal.Subtract(default(decimal), default(decimal))) }; } @@ -37,7 +34,40 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, { string function = method.Name.ToLowerInvariant(); - return treeBuilder.MethodCall(function, arguments.Select(x => visitor.Visit(x).AsExpression())); + HqlExpression[] expressions = arguments.Select(x => visitor.Visit(x).AsExpression()).ToArray(); + + if (function == "remainder") + { + function = "mod"; + } + + switch (function) + { + case "add": + return treeBuilder.Add(expressions[0], expressions[1]); + case "subtract": + return treeBuilder.Subtract(expressions[0], expressions[1]); + case "divide": + return treeBuilder.Divide(expressions[0], expressions[1]); + case "equals": + return treeBuilder.Equality(expressions[0], expressions[1]); + case "negate": + return treeBuilder.Multiply(expressions[0], treeBuilder.Constant(-1)); + case "compare": + return treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); + case "multiply": + return treeBuilder.Multiply(expressions[0], expressions[1]); + case "round": + HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); + return treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); + } + + if (arguments.Count == 2) + { + return treeBuilder.MethodCall(function, expressions[0], expressions[1]); + } + + return treeBuilder.MethodCall(function, expressions[0]); } } } From d8190c51cc1cd5cff7dfe8d04af06d1f497d6a63 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Thu, 18 Jan 2018 14:39:32 +0100 Subject: [PATCH 05/30] Ignore tests if the dialect does not support the function --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 18 ++++++++++++++++-- .../Linq/Functions/DecimalGenerator.cs | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 2ec7f65fd03..25f6eb1060c 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -61,6 +61,14 @@ protected override void OnTearDown() } } + private void IgnoreIfNotSupported(string function) + { + if (!Dialect.Functions.ContainsKey(function)) + { + Assert.Ignore("Dialect {0} does not support '{1}' function", Dialect.GetType(), function); + } + } + [Test] public void CanHandleAdd() { @@ -71,12 +79,16 @@ public void CanHandleAdd() [Test] public void CanHandleCeiling() { + IgnoreIfNotSupported("ceiling"); + CanHandle(e => decimal.Ceiling(e.EntityValue) > 1.0m); } [Test] public void CanHandleCompare() { + IgnoreIfNotSupported("sign"); + CanHandle(e => decimal.Compare(e.EntityValue, 1.5m) < 1); CanHandle(e => decimal.Compare(1.0m, e.EntityValue) < 1); } @@ -98,6 +110,8 @@ public void CanHandleEquals() [Test] public void CanHandleFloor() { + IgnoreIfNotSupported("floor"); + CanHandle(e => decimal.Floor(e.EntityValue) > 1.0m); } @@ -124,6 +138,8 @@ public void CanHandleRemainder() [Test] public void CanHandleRound() { + IgnoreIfNotSupported("round"); + CanHandle(e => decimal.Round(e.EntityValue) >= 2.0m); CanHandle(e => decimal.Round(e.EntityValue, 1) >= 1.5m); } @@ -143,8 +159,6 @@ private void CanHandle(Expression> predicate) IEnumerable inMemory = entities.Where(predicate.Compile()).ToList(); IEnumerable inSession = session.Query().Where(predicate).ToList(); - Assume.That(inMemory.Any()); - CollectionAssert.AreEquivalent(inMemory, inSession); } } diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index f488c135912..fe14012dd8b 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -52,7 +52,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, case "equals": return treeBuilder.Equality(expressions[0], expressions[1]); case "negate": - return treeBuilder.Multiply(expressions[0], treeBuilder.Constant(-1)); + return treeBuilder.Negate(expressions[0]); case "compare": return treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); case "multiply": From 19265970e58aa223cb747d373d0161121fe213a0 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Sat, 20 Jan 2018 15:17:59 +0100 Subject: [PATCH 06/30] Replace IgnoreIfNotSupported with TestCase.AssumeFunctionSupported --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 25f6eb1060c..c4df5960956 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -61,14 +61,6 @@ protected override void OnTearDown() } } - private void IgnoreIfNotSupported(string function) - { - if (!Dialect.Functions.ContainsKey(function)) - { - Assert.Ignore("Dialect {0} does not support '{1}' function", Dialect.GetType(), function); - } - } - [Test] public void CanHandleAdd() { @@ -79,7 +71,7 @@ public void CanHandleAdd() [Test] public void CanHandleCeiling() { - IgnoreIfNotSupported("ceiling"); + AssumeFunctionSupported("ceiling"); CanHandle(e => decimal.Ceiling(e.EntityValue) > 1.0m); } @@ -87,7 +79,7 @@ public void CanHandleCeiling() [Test] public void CanHandleCompare() { - IgnoreIfNotSupported("sign"); + AssumeFunctionSupported("sign"); CanHandle(e => decimal.Compare(e.EntityValue, 1.5m) < 1); CanHandle(e => decimal.Compare(1.0m, e.EntityValue) < 1); @@ -110,7 +102,7 @@ public void CanHandleEquals() [Test] public void CanHandleFloor() { - IgnoreIfNotSupported("floor"); + AssumeFunctionSupported("floor"); CanHandle(e => decimal.Floor(e.EntityValue) > 1.0m); } @@ -138,7 +130,7 @@ public void CanHandleRemainder() [Test] public void CanHandleRound() { - IgnoreIfNotSupported("round"); + AssumeFunctionSupported("round"); CanHandle(e => decimal.Round(e.EntityValue) >= 2.0m); CanHandle(e => decimal.Round(e.EntityValue, 1) >= 1.5m); From 8c986943c030a9239f62f6634e2476136a887b48 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Sat, 20 Jan 2018 23:28:09 +0100 Subject: [PATCH 07/30] Add mod-function that expects a decimal --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 83 ++++++++++++++----- src/NHibernate/Dialect/Dialect.cs | 1 + .../Linq/Functions/DecimalGenerator.cs | 2 +- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index c4df5960956..c439c3c96b2 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -64,8 +64,11 @@ protected override void OnTearDown() [Test] public void CanHandleAdd() { - CanHandle(e => decimal.Add(e.EntityValue, 2) > 3.0m); - CanHandle(e => decimal.Add(2, e.EntityValue) > 3.0m); + CanFilter(e => decimal.Add(e.EntityValue, 2) > 3.0m); + CanFilter(e => decimal.Add(2, e.EntityValue) > 3.0m); + + CanSelect(e => decimal.Add(e.EntityValue, 2)); + CanSelect(e => decimal.Add(2, e.EntityValue)); } [Test] @@ -73,7 +76,8 @@ public void CanHandleCeiling() { AssumeFunctionSupported("ceiling"); - CanHandle(e => decimal.Ceiling(e.EntityValue) > 1.0m); + CanFilter(e => decimal.Ceiling(e.EntityValue) > 1.0m); + CanSelect(e => decimal.Ceiling(e.EntityValue)); } [Test] @@ -81,22 +85,28 @@ public void CanHandleCompare() { AssumeFunctionSupported("sign"); - CanHandle(e => decimal.Compare(e.EntityValue, 1.5m) < 1); - CanHandle(e => decimal.Compare(1.0m, e.EntityValue) < 1); + CanFilter(e => decimal.Compare(e.EntityValue, 1.5m) < 1); + CanFilter(e => decimal.Compare(1.0m, e.EntityValue) < 1); + + CanSelect(e => decimal.Compare(e.EntityValue, 1.5m)); + CanSelect(e => decimal.Compare(1.0m, e.EntityValue)); } [Test] public void CanHandleDivide() { - CanHandle(e => decimal.Divide(e.EntityValue, 1.25m) < 1); - CanHandle(e => decimal.Divide(1.25m, e.EntityValue) < 1); + CanFilter(e => decimal.Divide(e.EntityValue, 1.25m) < 1); + CanFilter(e => decimal.Divide(1.25m, e.EntityValue) < 1); + + CanSelect(e => decimal.Divide(e.EntityValue, 1.25m)); + CanSelect(e => decimal.Divide(1.25m, e.EntityValue)); } [Test] public void CanHandleEquals() { - CanHandle(e => decimal.Equals(e.EntityValue, 1.0m)); - CanHandle(e => decimal.Equals(1.0m, e.EntityValue)); + CanFilter(e => decimal.Equals(e.EntityValue, 1.0m)); + CanFilter(e => decimal.Equals(1.0m, e.EntityValue)); } [Test] @@ -104,27 +114,35 @@ public void CanHandleFloor() { AssumeFunctionSupported("floor"); - CanHandle(e => decimal.Floor(e.EntityValue) > 1.0m); + CanFilter(e => decimal.Floor(e.EntityValue) > 1.0m); + CanSelect(e => decimal.Floor(e.EntityValue)); } [Test] public void CanHandleMultiply() { - CanHandle(e => decimal.Multiply(e.EntityValue, 10m) > 10m); - CanHandle(e => decimal.Multiply(10m, e.EntityValue) > 10m); + CanFilter(e => decimal.Multiply(e.EntityValue, 10m) > 10m); + CanFilter(e => decimal.Multiply(10m, e.EntityValue) > 10m); + + CanSelect(e => decimal.Multiply(e.EntityValue, 10m)); + CanSelect(e => decimal.Multiply(10m, e.EntityValue)); } [Test] public void CanHandleNegate() { - CanHandle(e => decimal.Negate(e.EntityValue) > -1.0m); + CanFilter(e => decimal.Negate(e.EntityValue) > -1.0m); + CanSelect(e => decimal.Negate(e.EntityValue)); } [Test] public void CanHandleRemainder() { - CanHandle(e => decimal.Remainder(e.EntityValue, 2) == 0); - CanHandle(e => decimal.Remainder(2, e.EntityValue) < 1); + CanFilter(e => decimal.Remainder(e.EntityValue, 2) == 0); + CanFilter(e => decimal.Remainder(2, e.EntityValue) < 1); + + CanSelect(e => decimal.Remainder(e.EntityValue, 2)); + CanSelect(e => decimal.Remainder(2, e.EntityValue)); } [Test] @@ -132,18 +150,25 @@ public void CanHandleRound() { AssumeFunctionSupported("round"); - CanHandle(e => decimal.Round(e.EntityValue) >= 2.0m); - CanHandle(e => decimal.Round(e.EntityValue, 1) >= 1.5m); + CanFilter(e => decimal.Round(e.EntityValue) >= 2.0m); + CanFilter(e => decimal.Round(e.EntityValue, 1) >= 1.5m); + + // SQL round() always rounds up. + CanSelect(e => decimal.Round(e.EntityValue), entities.Select(e => decimal.Round(e.EntityValue, MidpointRounding.AwayFromZero))); + CanSelect(e => decimal.Round(e.EntityValue, 1), entities.Select(e => decimal.Round(e.EntityValue, 1, MidpointRounding.AwayFromZero))); } [Test] public void CanHandleSubtract() { - CanHandle(e => decimal.Subtract(e.EntityValue, 1m) > 1m); - CanHandle(e => decimal.Subtract(2m, e.EntityValue) > 1m); + CanFilter(e => decimal.Subtract(e.EntityValue, 1m) > 1m); + CanFilter(e => decimal.Subtract(2m, e.EntityValue) > 1m); + + CanSelect(e => decimal.Subtract(e.EntityValue, 1m)); + CanSelect(e => decimal.Subtract(2m, e.EntityValue)); } - private void CanHandle(Expression> predicate) + private void CanFilter(Expression> predicate) { using (ISession session = OpenSession()) using (session.BeginTransaction()) @@ -154,5 +179,23 @@ private void CanHandle(Expression> predicate) CollectionAssert.AreEquivalent(inMemory, inSession); } } + + private void CanSelect(Expression> predicate) + { + IEnumerable inMemory = entities.Select(predicate.Compile()).ToList(); + + CanSelect(predicate, inMemory); + } + + private void CanSelect(Expression> predicate, IEnumerable expected) + { + using (ISession session = OpenSession()) + using (session.BeginTransaction()) + { + IEnumerable inSession = session.Query().Select(predicate).ToList(); + + Assert.That(inSession.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x)).Within(0.001M)); + } + } } } diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 53a31c43e15..7d2bc52d575 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -94,6 +94,7 @@ protected Dialect() RegisterFunction("nullif", new StandardSQLFunction("nullif")); RegisterFunction("abs", new StandardSQLFunction("abs")); RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); + RegisterFunction("mod_decimal", new SQLFunctionTemplate(NHibernateUtil.Decimal, "?1 % ?2")); RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double)); RegisterFunction("upper", new StandardSQLFunction("upper")); RegisterFunction("lower", new StandardSQLFunction("lower")); diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index fe14012dd8b..5fbed142b18 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -38,7 +38,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, if (function == "remainder") { - function = "mod"; + function = "mod_decimal"; } switch (function) From f16a8ef9ec12b7cd520cfcf5918db75fae806b8b Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Sun, 21 Jan 2018 13:26:28 +0100 Subject: [PATCH 08/30] Replace 'mod_decimal'-hack with casting the result to decimal --- src/NHibernate/Dialect/Dialect.cs | 1 - src/NHibernate/Linq/Functions/DecimalGenerator.cs | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 7d2bc52d575..53a31c43e15 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -94,7 +94,6 @@ protected Dialect() RegisterFunction("nullif", new StandardSQLFunction("nullif")); RegisterFunction("abs", new StandardSQLFunction("abs")); RegisterFunction("mod", new StandardSQLFunction("mod", NHibernateUtil.Int32)); - RegisterFunction("mod_decimal", new SQLFunctionTemplate(NHibernateUtil.Decimal, "?1 % ?2")); RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double)); RegisterFunction("upper", new StandardSQLFunction("upper")); RegisterFunction("lower", new StandardSQLFunction("lower")); diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 5fbed142b18..a5de64f6795 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -36,11 +36,6 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, HqlExpression[] expressions = arguments.Select(x => visitor.Visit(x).AsExpression()).ToArray(); - if (function == "remainder") - { - function = "mod_decimal"; - } - switch (function) { case "add": @@ -57,6 +52,9 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, return treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); case "multiply": return treeBuilder.Multiply(expressions[0], expressions[1]); + case "remainder": + HqlMethodCall mod = treeBuilder.MethodCall("mod", expressions[0], expressions[1]); + return treeBuilder.Cast(mod, typeof(decimal)); case "round": HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); return treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); From 6359afb9897831a4eca108fe88a1ee0add4df299 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Mon, 22 Jan 2018 17:32:16 +0100 Subject: [PATCH 09/30] Replace Cast with TransparentCast to avoid casting in the database --- src/NHibernate.Test/App.config | 2 +- src/NHibernate/Linq/Functions/DecimalGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate.Test/App.config b/src/NHibernate.Test/App.config index 0db5a1028c3..942c1e3d4e1 100644 --- a/src/NHibernate.Test/App.config +++ b/src/NHibernate.Test/App.config @@ -47,7 +47,7 @@ none true - false + true 444 diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index a5de64f6795..6da21218444 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -54,7 +54,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, return treeBuilder.Multiply(expressions[0], expressions[1]); case "remainder": HqlMethodCall mod = treeBuilder.MethodCall("mod", expressions[0], expressions[1]); - return treeBuilder.Cast(mod, typeof(decimal)); + return treeBuilder.TransparentCast(mod, typeof(decimal)); case "round": HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); return treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); From aba2f2615362d4c6d322295582cf47b6ba4b6cdf Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Tue, 23 Jan 2018 11:35:08 +0100 Subject: [PATCH 10/30] Introduce a property to indicate if mod on decimal is supported SQL Server CE doesn't support this Firebird rounds the values before calculating --- src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs | 2 ++ src/NHibernate.Test/TestDialect.cs | 5 +++++ src/NHibernate.Test/TestDialects/FirebirdTestDialect.cs | 4 ++++ src/NHibernate.Test/TestDialects/MsSqlCe40TestDialect.cs | 5 +++++ 4 files changed, 16 insertions(+) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index c439c3c96b2..a3a65358b5e 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -138,6 +138,8 @@ public void CanHandleNegate() [Test] public void CanHandleRemainder() { + Assume.That(TestDialect.SupportsModuloOnDecimal, Is.True); + CanFilter(e => decimal.Remainder(e.EntityValue, 2) == 0); CanFilter(e => decimal.Remainder(2, e.EntityValue) < 1); diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs index 9d6c321ad15..68efde5d7f3 100644 --- a/src/NHibernate.Test/TestDialect.cs +++ b/src/NHibernate.Test/TestDialect.cs @@ -75,5 +75,10 @@ public bool SupportsSqlType(SqlType sqlType) return false; } } + + /// + /// Supports the modulo operator on decimal types + /// + public virtual bool SupportsModuloOnDecimal => true; } } diff --git a/src/NHibernate.Test/TestDialects/FirebirdTestDialect.cs b/src/NHibernate.Test/TestDialects/FirebirdTestDialect.cs index 96e4181b1cd..09b259a98f5 100644 --- a/src/NHibernate.Test/TestDialects/FirebirdTestDialect.cs +++ b/src/NHibernate.Test/TestDialects/FirebirdTestDialect.cs @@ -8,5 +8,9 @@ public FirebirdTestDialect(Dialect.Dialect dialect) : base(dialect) public override bool SupportsComplexExpressionInGroupBy => false; public override bool SupportsNonDataBoundCondition => false; + /// + /// Non-integer arguments are rounded before the division takes place. So, “7.5 mod 2.5” gives 2 (8 mod 3), not 0. + /// + public override bool SupportsModuloOnDecimal => false; } } diff --git a/src/NHibernate.Test/TestDialects/MsSqlCe40TestDialect.cs b/src/NHibernate.Test/TestDialects/MsSqlCe40TestDialect.cs index 8f384459d76..89156042f21 100644 --- a/src/NHibernate.Test/TestDialects/MsSqlCe40TestDialect.cs +++ b/src/NHibernate.Test/TestDialects/MsSqlCe40TestDialect.cs @@ -25,5 +25,10 @@ public MsSqlCe40TestDialect(Dialect.Dialect dialect) : base(dialect) public override bool SupportsDuplicatedColumnAliases => false; public override bool SupportsEmptyInserts => false; + + /// + /// Modulo is not supported on real, float, money, and numeric data types. [ Data type = numeric ] + /// + public override bool SupportsModuloOnDecimal => false; } } From edc2996d42bcb431f9d429142e5fdc1c79f7dd73 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Tue, 23 Jan 2018 12:47:42 +0100 Subject: [PATCH 11/30] Revert show_sql=true in the App.Config --- src/NHibernate.Test/App.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate.Test/App.config b/src/NHibernate.Test/App.config index 942c1e3d4e1..0db5a1028c3 100644 --- a/src/NHibernate.Test/App.config +++ b/src/NHibernate.Test/App.config @@ -47,7 +47,7 @@ none true - true + false 444 From cc18f884bbd397801b2ff8310cab83eb2098413f Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Sat, 27 Jan 2018 21:17:32 +0100 Subject: [PATCH 12/30] Fix 'round' for PostgreSQL --- src/NHibernate/Linq/Functions/DecimalGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 6da21218444..27e6b0d3e13 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -57,7 +57,8 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, return treeBuilder.TransparentCast(mod, typeof(decimal)); case "round": HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); - return treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); + HqlMethodCall round = treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); + return treeBuilder.TransparentCast(round, typeof(decimal)); } if (arguments.Count == 2) From 4828214bf3056bf0c2ec0b724a708c3a77de1597 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Fri, 9 Feb 2018 16:42:31 +0100 Subject: [PATCH 13/30] Add support for Firebird --- src/NHibernate/Linq/Functions/DecimalGenerator.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 27e6b0d3e13..dc8812a3b4a 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -34,7 +34,10 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, { string function = method.Name.ToLowerInvariant(); - HqlExpression[] expressions = arguments.Select(x => visitor.Visit(x).AsExpression()).ToArray(); + HqlExpression[] expressions = + arguments + .Select(x => treeBuilder.TransparentCast(visitor.Visit(x).AsExpression(), typeof(decimal))) + .ToArray(); switch (function) { @@ -58,14 +61,9 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, case "round": HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); HqlMethodCall round = treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); - return treeBuilder.TransparentCast(round, typeof(decimal)); + return round; } - - if (arguments.Count == 2) - { - return treeBuilder.MethodCall(function, expressions[0], expressions[1]); - } - + return treeBuilder.MethodCall(function, expressions[0]); } } From 2f9d0e7fc46e9ddc6a6c5c613750db8b75effeb9 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Mon, 12 Feb 2018 14:55:15 +0100 Subject: [PATCH 14/30] Transparent cast the results to the correct type It mimicks the regular functions that expect a different return type --- .../Linq/Functions/DecimalGenerator.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index dc8812a3b4a..b11ece5b596 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -32,39 +32,50 @@ public DecimalGenerator() public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { + HqlExpression result; + string function = method.Name.ToLowerInvariant(); HqlExpression[] expressions = arguments - .Select(x => treeBuilder.TransparentCast(visitor.Visit(x).AsExpression(), typeof(decimal))) + .Select(x => visitor.Visit(x).AsExpression()) .ToArray(); switch (function) { case "add": - return treeBuilder.Add(expressions[0], expressions[1]); + result = treeBuilder.Add(expressions[0], expressions[1]); + break; case "subtract": - return treeBuilder.Subtract(expressions[0], expressions[1]); + result = treeBuilder.Subtract(expressions[0], expressions[1]); + break; case "divide": - return treeBuilder.Divide(expressions[0], expressions[1]); + result = treeBuilder.Divide(expressions[0], expressions[1]); + break; case "equals": return treeBuilder.Equality(expressions[0], expressions[1]); case "negate": - return treeBuilder.Negate(expressions[0]); + result = treeBuilder.Negate(expressions[0]); + break; case "compare": - return treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); + result = treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); + break; case "multiply": - return treeBuilder.Multiply(expressions[0], expressions[1]); + result = treeBuilder.Multiply(expressions[0], expressions[1]); + break; case "remainder": - HqlMethodCall mod = treeBuilder.MethodCall("mod", expressions[0], expressions[1]); - return treeBuilder.TransparentCast(mod, typeof(decimal)); + result = treeBuilder.MethodCall("mod", expressions[0], expressions[1]); + break; case "round": HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); - HqlMethodCall round = treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); - return round; + result = treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); + break; + default: + result = treeBuilder.MethodCall(function, expressions[0]); + break; } - - return treeBuilder.MethodCall(function, expressions[0]); + + return treeBuilder.TransparentCast(result, typeof(decimal)); } } } From 7c61ab0f6138d685a24add6a3d62b3e68ba6d529 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Mon, 12 Feb 2018 14:55:53 +0100 Subject: [PATCH 15/30] Skip testing modulo for SQLite It does not support modulo on decimals properly --- src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs index 95c77873d2f..da04e00f2f0 100644 --- a/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs +++ b/src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs @@ -44,5 +44,7 @@ public override bool SupportsHavingWithoutGroupBy { get { return false; } } + + public override bool SupportsModuloOnDecimal => false; } } From a6d2e2536e806176dd4b792b15c76b03f50594c6 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Wed, 21 Feb 2018 17:58:20 +0100 Subject: [PATCH 16/30] Split DecimalGenerator into specific generators --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 107 ++++++--- .../Linq/Functions/DecimalGenerator.cs | 215 +++++++++++++----- .../DefaultLinqToHqlGeneratorsRegistry.cs | 12 +- 3 files changed, 242 insertions(+), 92 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index a3a65358b5e..75cbdab9315 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -64,11 +64,14 @@ protected override void OnTearDown() [Test] public void CanHandleAdd() { - CanFilter(e => decimal.Add(e.EntityValue, 2) > 3.0m); - CanFilter(e => decimal.Add(2, e.EntityValue) > 3.0m); + Assert.Multiple(() => + { + CanFilter(e => decimal.Add(e.EntityValue, 2) > 3.0m); + CanFilter(e => decimal.Add(2, e.EntityValue) > 3.0m); - CanSelect(e => decimal.Add(e.EntityValue, 2)); - CanSelect(e => decimal.Add(2, e.EntityValue)); + CanSelect(e => decimal.Add(e.EntityValue, 2)); + CanSelect(e => decimal.Add(2, e.EntityValue)); + }); } [Test] @@ -76,8 +79,11 @@ public void CanHandleCeiling() { AssumeFunctionSupported("ceiling"); - CanFilter(e => decimal.Ceiling(e.EntityValue) > 1.0m); - CanSelect(e => decimal.Ceiling(e.EntityValue)); + Assert.Multiple(() => + { + CanFilter(e => decimal.Ceiling(e.EntityValue) > 1.0m); + CanSelect(e => decimal.Ceiling(e.EntityValue)); + }); } [Test] @@ -85,28 +91,37 @@ public void CanHandleCompare() { AssumeFunctionSupported("sign"); - CanFilter(e => decimal.Compare(e.EntityValue, 1.5m) < 1); - CanFilter(e => decimal.Compare(1.0m, e.EntityValue) < 1); + Assert.Multiple(() => + { + CanFilter(e => decimal.Compare(e.EntityValue, 1.5m) < 1); + CanFilter(e => decimal.Compare(1.0m, e.EntityValue) < 1); - CanSelect(e => decimal.Compare(e.EntityValue, 1.5m)); - CanSelect(e => decimal.Compare(1.0m, e.EntityValue)); + CanSelect(e => decimal.Compare(e.EntityValue, 1.5m)); + CanSelect(e => decimal.Compare(1.0m, e.EntityValue)); + }); } [Test] public void CanHandleDivide() { - CanFilter(e => decimal.Divide(e.EntityValue, 1.25m) < 1); - CanFilter(e => decimal.Divide(1.25m, e.EntityValue) < 1); + Assert.Multiple(() => + { + CanFilter(e => decimal.Divide(e.EntityValue, 1.25m) < 1); + CanFilter(e => decimal.Divide(1.25m, e.EntityValue) < 1); - CanSelect(e => decimal.Divide(e.EntityValue, 1.25m)); - CanSelect(e => decimal.Divide(1.25m, e.EntityValue)); + CanSelect(e => decimal.Divide(e.EntityValue, 1.25m)); + CanSelect(e => decimal.Divide(1.25m, e.EntityValue)); + }); } [Test] public void CanHandleEquals() { - CanFilter(e => decimal.Equals(e.EntityValue, 1.0m)); - CanFilter(e => decimal.Equals(1.0m, e.EntityValue)); + Assert.Multiple(() => + { + CanFilter(e => decimal.Equals(e.EntityValue, 1.0m)); + CanFilter(e => decimal.Equals(1.0m, e.EntityValue)); + }); } [Test] @@ -114,25 +129,34 @@ public void CanHandleFloor() { AssumeFunctionSupported("floor"); - CanFilter(e => decimal.Floor(e.EntityValue) > 1.0m); - CanSelect(e => decimal.Floor(e.EntityValue)); + Assert.Multiple(() => + { + CanFilter(e => decimal.Floor(e.EntityValue) > 1.0m); + CanSelect(e => decimal.Floor(e.EntityValue)); + }); } [Test] public void CanHandleMultiply() { - CanFilter(e => decimal.Multiply(e.EntityValue, 10m) > 10m); - CanFilter(e => decimal.Multiply(10m, e.EntityValue) > 10m); + Assert.Multiple(() => + { + CanFilter(e => decimal.Multiply(e.EntityValue, 10m) > 10m); + CanFilter(e => decimal.Multiply(10m, e.EntityValue) > 10m); - CanSelect(e => decimal.Multiply(e.EntityValue, 10m)); - CanSelect(e => decimal.Multiply(10m, e.EntityValue)); + CanSelect(e => decimal.Multiply(e.EntityValue, 10m)); + CanSelect(e => decimal.Multiply(10m, e.EntityValue)); + }); } [Test] public void CanHandleNegate() { - CanFilter(e => decimal.Negate(e.EntityValue) > -1.0m); - CanSelect(e => decimal.Negate(e.EntityValue)); + Assert.Multiple(() => + { + CanFilter(e => decimal.Negate(e.EntityValue) > -1.0m); + CanSelect(e => decimal.Negate(e.EntityValue)); + }); } [Test] @@ -140,11 +164,14 @@ public void CanHandleRemainder() { Assume.That(TestDialect.SupportsModuloOnDecimal, Is.True); - CanFilter(e => decimal.Remainder(e.EntityValue, 2) == 0); - CanFilter(e => decimal.Remainder(2, e.EntityValue) < 1); + Assert.Multiple(() => + { + CanFilter(e => decimal.Remainder(e.EntityValue, 2m) == 0); + CanFilter(e => decimal.Remainder(2m, e.EntityValue) < 1); - CanSelect(e => decimal.Remainder(e.EntityValue, 2)); - CanSelect(e => decimal.Remainder(2, e.EntityValue)); + CanSelect(e => decimal.Remainder(e.EntityValue, 2m)); + CanSelect(e => decimal.Remainder(2m, e.EntityValue)); + }); } [Test] @@ -152,22 +179,28 @@ public void CanHandleRound() { AssumeFunctionSupported("round"); - CanFilter(e => decimal.Round(e.EntityValue) >= 2.0m); - CanFilter(e => decimal.Round(e.EntityValue, 1) >= 1.5m); + Assert.Multiple(() => + { + CanFilter(e => decimal.Round(e.EntityValue) >= 2.0m); + CanFilter(e => decimal.Round(e.EntityValue, 1) >= 1.5m); - // SQL round() always rounds up. - CanSelect(e => decimal.Round(e.EntityValue), entities.Select(e => decimal.Round(e.EntityValue, MidpointRounding.AwayFromZero))); - CanSelect(e => decimal.Round(e.EntityValue, 1), entities.Select(e => decimal.Round(e.EntityValue, 1, MidpointRounding.AwayFromZero))); + // SQL round() always rounds up. + CanSelect(e => decimal.Round(e.EntityValue), entities.Select(e => decimal.Round(e.EntityValue, MidpointRounding.AwayFromZero))); + CanSelect(e => decimal.Round(e.EntityValue, 1), entities.Select(e => decimal.Round(e.EntityValue, 1, MidpointRounding.AwayFromZero))); + }); } [Test] public void CanHandleSubtract() { - CanFilter(e => decimal.Subtract(e.EntityValue, 1m) > 1m); - CanFilter(e => decimal.Subtract(2m, e.EntityValue) > 1m); + Assert.Multiple(() => + { + CanFilter(e => decimal.Subtract(e.EntityValue, 1m) > 1m); + CanFilter(e => decimal.Subtract(2m, e.EntityValue) > 1m); - CanSelect(e => decimal.Subtract(e.EntityValue, 1m)); - CanSelect(e => decimal.Subtract(2m, e.EntityValue)); + CanSelect(e => decimal.Subtract(e.EntityValue, 1m)); + CanSelect(e => decimal.Subtract(2m, e.EntityValue)); + }); } private void CanFilter(Expression> predicate) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index b11ece5b596..97bc8541ca4 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -9,73 +8,181 @@ namespace NHibernate.Linq.Functions { - public class DecimalGenerator : BaseHqlGeneratorForMethod + public class DecimalAddGenerator : BaseHqlGeneratorForMethod { - public DecimalGenerator() + public DecimalAddGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Add(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.TransparentCast(treeBuilder.Add(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalCompareGenerator : BaseHqlGeneratorForMethod + { + public DecimalCompareGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall("sign", treeBuilder.Subtract(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression())); + } + } + + public class DecimalDivideGenerator : BaseHqlGeneratorForMethod + { + public DecimalDivideGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Divide(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.TransparentCast(treeBuilder.Divide(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalEqualsGenerator : BaseHqlGeneratorForMethod + { + public DecimalEqualsGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.Equality(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()); + } + } + + public class DecimalMultiplyGenerator : BaseHqlGeneratorForMethod + { + public DecimalMultiplyGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Multiply(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.TransparentCast(treeBuilder.Multiply(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalSubtractGenerator : BaseHqlGeneratorForMethod + { + public DecimalSubtractGenerator() { SupportedMethods = new[] { - ReflectHelper.GetMethodDefinition(() => decimal.Add(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Divide(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Multiply(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Negate(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Remainder(default(decimal), default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), ReflectHelper.GetMethodDefinition(() => decimal.Subtract(default(decimal), default(decimal))) }; } public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { - HqlExpression result; + return treeBuilder.TransparentCast(treeBuilder.Subtract(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalRemainderGenerator : BaseHqlGeneratorForMethod + { + public DecimalRemainderGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Remainder(default(decimal), default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.TransparentCast(treeBuilder.MethodCall("mod", visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalNegateGenerator : BaseHqlGeneratorForMethod + { + public DecimalNegateGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Negate(default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.TransparentCast(treeBuilder.Negate(visitor.Visit(arguments[0]).AsExpression()), typeof(decimal)); + } + } + + public class DecimalFloorGenerator : BaseHqlGeneratorForMethod + { + public DecimalFloorGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall("floor", visitor.Visit(arguments[0]).AsExpression()); + } + } - string function = method.Name.ToLowerInvariant(); + public class DecimalCeilingGenerator : BaseHqlGeneratorForMethod + { + public DecimalCeilingGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))) + }; + } - HqlExpression[] expressions = - arguments - .Select(x => visitor.Visit(x).AsExpression()) - .ToArray(); + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall("ceiling", visitor.Visit(arguments[0]).AsExpression()); + } + } - switch (function) + public class DecimalRoundGenerator : BaseHqlGeneratorForMethod + { + public DecimalRoundGenerator() + { + SupportedMethods = new[] { - case "add": - result = treeBuilder.Add(expressions[0], expressions[1]); - break; - case "subtract": - result = treeBuilder.Subtract(expressions[0], expressions[1]); - break; - case "divide": - result = treeBuilder.Divide(expressions[0], expressions[1]); - break; - case "equals": - return treeBuilder.Equality(expressions[0], expressions[1]); - case "negate": - result = treeBuilder.Negate(expressions[0]); - break; - case "compare": - result = treeBuilder.MethodCall("sign", treeBuilder.Subtract(expressions[0], expressions[1])); - break; - case "multiply": - result = treeBuilder.Multiply(expressions[0], expressions[1]); - break; - case "remainder": - result = treeBuilder.MethodCall("mod", expressions[0], expressions[1]); - break; - case "round": - HqlExpression numberOfDecimals = (arguments.Count == 2) ? expressions[1] : treeBuilder.Constant(0); - result = treeBuilder.MethodCall("round", expressions[0], numberOfDecimals); - break; - default: - result = treeBuilder.MethodCall(function, expressions[0]); - break; - } - - return treeBuilder.TransparentCast(result, typeof(decimal)); + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + HqlExpression numberOfDecimals = (arguments.Count == 2) ? visitor.Visit(arguments[1]).AsExpression() : treeBuilder.Constant(0); + return treeBuilder.TransparentCast(treeBuilder.MethodCall("round", visitor.Visit(arguments[0]).AsExpression(), numberOfDecimals), typeof(decimal)); } } } diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 5a7732ab79c..9c619d0e3bc 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -52,7 +52,17 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DateTimePropertiesHqlGenerator()); - this.Merge(new DecimalGenerator()); + this.Merge(new DecimalAddGenerator()); + this.Merge(new DecimalCompareGenerator()); + this.Merge(new DecimalDivideGenerator()); + this.Merge(new DecimalEqualsGenerator()); + this.Merge(new DecimalMultiplyGenerator()); + this.Merge(new DecimalSubtractGenerator()); + this.Merge(new DecimalRemainderGenerator()); + this.Merge(new DecimalNegateGenerator()); + this.Merge(new DecimalFloorGenerator()); + this.Merge(new DecimalCeilingGenerator()); + this.Merge(new DecimalRoundGenerator()); } protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) From bd577f163b5d1d21246593031f4dca5028bed126 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Wed, 28 Feb 2018 10:51:16 +0100 Subject: [PATCH 17/30] Explicit cast the result of 'Divide' to fix Oracle --- src/NHibernate/Linq/Functions/DecimalGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 97bc8541ca4..0725157af54 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -52,7 +52,7 @@ public DecimalDivideGenerator() public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { - return treeBuilder.TransparentCast(treeBuilder.Divide(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); + return treeBuilder.Cast(treeBuilder.Divide(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()), typeof(decimal)); } } From e1b1e5d58aabb156011136cec0210a8f84b39e2d Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Thu, 1 Mar 2018 08:55:31 +0100 Subject: [PATCH 18/30] Make Assert.Multiple more effective --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 5 +++-- src/NHibernate.sln.DotSettings | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 75cbdab9315..5e062300bdb 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -227,9 +227,10 @@ private void CanSelect(Expression> predicate, IEnumerable< using (ISession session = OpenSession()) using (session.BeginTransaction()) { - IEnumerable inSession = session.Query().Select(predicate).ToList(); + IEnumerable inSession = null; + Assert.That(() => inSession = session.Query().OrderBy(e => e.EntityValue).Select(predicate).ToList(), Throws.Nothing); - Assert.That(inSession.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x)).Within(0.001M)); + Assert.That(inSession, Is.EqualTo(expected.OrderBy(x => x)).Within(0.001M)); } } } diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings index a38b0a203a1..ec2a3ce2054 100644 --- a/src/NHibernate.sln.DotSettings +++ b/src/NHibernate.sln.DotSettings @@ -1,5 +1,5 @@  - USE_SPACES + True True True @@ -7,7 +7,10 @@ True True True + NEVER + NEVER False + NEVER False True True @@ -20,9 +23,14 @@ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True + True + True + True True + True True True + True True True NUnit Assert.NotNull From f9acc6af90099d062ea6a1a5e1b4e15e37401273 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Thu, 1 Mar 2018 09:02:13 +0100 Subject: [PATCH 19/30] Revert accidental changes to NHibernate.sln.DotSettings --- src/NHibernate.sln.DotSettings | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings index ec2a3ce2054..a38b0a203a1 100644 --- a/src/NHibernate.sln.DotSettings +++ b/src/NHibernate.sln.DotSettings @@ -1,5 +1,5 @@  - + USE_SPACES True True True @@ -7,10 +7,7 @@ True True True - NEVER - NEVER False - NEVER False True True @@ -23,14 +20,9 @@ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True - True - True - True True - True True True - True True True NUnit Assert.NotNull From 33c985bf20f8a564bb8783cf99a60d06fc118fb4 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Thu, 1 Mar 2018 17:14:38 +0100 Subject: [PATCH 20/30] Compare decimal values in any order, within a small tolerance --- src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 5e062300bdb..8b290f56ab8 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -228,9 +228,9 @@ private void CanSelect(Expression> predicate, IEnumerable< using (session.BeginTransaction()) { IEnumerable inSession = null; - Assert.That(() => inSession = session.Query().OrderBy(e => e.EntityValue).Select(predicate).ToList(), Throws.Nothing); + Assert.That(() => inSession = session.Query().Select(predicate).ToList(), Throws.Nothing); - Assert.That(inSession, Is.EqualTo(expected.OrderBy(x => x)).Within(0.001M)); + Assert.That(inSession, Is.EquivalentTo(expected).Using((decimal a, decimal b) => Math.Abs(a - b) < 0.0001m)); } } } From e0f50c895b81b096ef8b8b5597df0f32767c0500 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 01:24:30 +1300 Subject: [PATCH 21/30] Merge DecimalEqualsGenerator into EqualsGenerator --- .../Linq/Functions/DecimalGenerator.cs | 16 ---------------- .../DefaultLinqToHqlGeneratorsRegistry.cs | 1 - src/NHibernate/Linq/Functions/EqualsGenerator.cs | 2 ++ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 0725157af54..03334c386da 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -56,22 +56,6 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalEqualsGenerator : BaseHqlGeneratorForMethod - { - public DecimalEqualsGenerator() - { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))) - }; - } - - public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) - { - return treeBuilder.Equality(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression()); - } - } - public class DecimalMultiplyGenerator : BaseHqlGeneratorForMethod { public DecimalMultiplyGenerator() diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 9c619d0e3bc..b2013d9b999 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -55,7 +55,6 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DecimalAddGenerator()); this.Merge(new DecimalCompareGenerator()); this.Merge(new DecimalDivideGenerator()); - this.Merge(new DecimalEqualsGenerator()); this.Merge(new DecimalMultiplyGenerator()); this.Merge(new DecimalSubtractGenerator()); this.Merge(new DecimalRemainderGenerator()); diff --git a/src/NHibernate/Linq/Functions/EqualsGenerator.cs b/src/NHibernate/Linq/Functions/EqualsGenerator.cs index a7c8b52102c..1593b8d3d6f 100644 --- a/src/NHibernate/Linq/Functions/EqualsGenerator.cs +++ b/src/NHibernate/Linq/Functions/EqualsGenerator.cs @@ -32,6 +32,8 @@ public EqualsGenerator() ReflectHelper.GetMethodDefinition(x => x.Equals(x)), ReflectHelper.GetMethodDefinition(x => x.Equals(x)), + + ReflectHelper.GetMethodDefinition(() => decimal.Equals(default(decimal), default(decimal))), ReflectHelper.GetMethodDefinition(x => x.Equals(x)), ReflectHelper.GetMethodDefinition(x => x.Equals(x)), From 16719fde403c59bea87d5ddb57e92b9db90a02f9 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 01:43:38 +1300 Subject: [PATCH 22/30] Merge DecimalCompareGenerator into CompareGenerator --- .../Linq/Functions/CompareGenerator.cs | 2 ++ .../Linq/Functions/DecimalGenerator.cs | 16 ---------------- .../DefaultLinqToHqlGeneratorsRegistry.cs | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/NHibernate/Linq/Functions/CompareGenerator.cs b/src/NHibernate/Linq/Functions/CompareGenerator.cs index 173faf7c0c5..7e88f9c8e18 100644 --- a/src/NHibernate/Linq/Functions/CompareGenerator.cs +++ b/src/NHibernate/Linq/Functions/CompareGenerator.cs @@ -32,6 +32,8 @@ internal class CompareGenerator : BaseHqlGeneratorForMethod, IRuntimeMethodHqlGe ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), + + ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), ReflectHelper.GetMethodDefinition(x => x.CompareTo(x)), diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 03334c386da..b94d16e3a4b 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -24,22 +24,6 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalCompareGenerator : BaseHqlGeneratorForMethod - { - public DecimalCompareGenerator() - { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => decimal.Compare(default(decimal), default(decimal))) - }; - } - - public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) - { - return treeBuilder.MethodCall("sign", treeBuilder.Subtract(visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression())); - } - } - public class DecimalDivideGenerator : BaseHqlGeneratorForMethod { public DecimalDivideGenerator() diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index b2013d9b999..5b9d8e0aebd 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -53,7 +53,6 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DateTimePropertiesHqlGenerator()); this.Merge(new DecimalAddGenerator()); - this.Merge(new DecimalCompareGenerator()); this.Merge(new DecimalDivideGenerator()); this.Merge(new DecimalMultiplyGenerator()); this.Merge(new DecimalSubtractGenerator()); From 28dde4331f7fa31d9c4e43033a61e2ca0dad2081 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 01:50:02 +1300 Subject: [PATCH 23/30] Merge DecimalFloorGenerator & DecimalCeilingGenerator into MathGenerator --- .../Linq/Functions/DecimalGenerator.cs | 32 ------------------- .../DefaultLinqToHqlGeneratorsRegistry.cs | 2 -- .../Linq/Functions/MathGenerator.cs | 2 ++ 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index b94d16e3a4b..42ee3197560 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -104,38 +104,6 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalFloorGenerator : BaseHqlGeneratorForMethod - { - public DecimalFloorGenerator() - { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))) - }; - } - - public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) - { - return treeBuilder.MethodCall("floor", visitor.Visit(arguments[0]).AsExpression()); - } - } - - public class DecimalCeilingGenerator : BaseHqlGeneratorForMethod - { - public DecimalCeilingGenerator() - { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))) - }; - } - - public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) - { - return treeBuilder.MethodCall("ceiling", visitor.Visit(arguments[0]).AsExpression()); - } - } - public class DecimalRoundGenerator : BaseHqlGeneratorForMethod { public DecimalRoundGenerator() diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 5b9d8e0aebd..7beafccade5 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -58,8 +58,6 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DecimalSubtractGenerator()); this.Merge(new DecimalRemainderGenerator()); this.Merge(new DecimalNegateGenerator()); - this.Merge(new DecimalFloorGenerator()); - this.Merge(new DecimalCeilingGenerator()); this.Merge(new DecimalRoundGenerator()); } diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index aff4807f73b..7e15cd4acf6 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -51,8 +51,10 @@ public MathGenerator() ReflectHelper.GetMethodDefinition(() => Math.Round(default(double), default(int))), ReflectHelper.GetMethodDefinition(() => Math.Floor(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Floor(default(double))), + ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(double))), + ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), From 8906825295c0c8eb93450d3dbb47ce0ac703f7bb Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 02:03:15 +1300 Subject: [PATCH 24/30] Adjust RoundGenerator to consider Math.Round methods --- .../Linq/Functions/DecimalGenerator.cs | 18 ---------- .../DefaultLinqToHqlGeneratorsRegistry.cs | 2 +- .../Linq/Functions/MathGenerator.cs | 6 ++-- .../Linq/Functions/RoundGenerator.cs | 36 +++++++++++++++++++ 4 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 src/NHibernate/Linq/Functions/RoundGenerator.cs diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index 42ee3197560..f36d09f07c2 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -103,22 +103,4 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, return treeBuilder.TransparentCast(treeBuilder.Negate(visitor.Visit(arguments[0]).AsExpression()), typeof(decimal)); } } - - public class DecimalRoundGenerator : BaseHqlGeneratorForMethod - { - public DecimalRoundGenerator() - { - SupportedMethods = new[] - { - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), - ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), - }; - } - - public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) - { - HqlExpression numberOfDecimals = (arguments.Count == 2) ? visitor.Visit(arguments[1]).AsExpression() : treeBuilder.Constant(0); - return treeBuilder.TransparentCast(treeBuilder.MethodCall("round", visitor.Visit(arguments[0]).AsExpression(), numberOfDecimals), typeof(decimal)); - } - } } diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 7beafccade5..3bbe4c9eb5b 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -58,7 +58,7 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DecimalSubtractGenerator()); this.Merge(new DecimalRemainderGenerator()); this.Merge(new DecimalNegateGenerator()); - this.Merge(new DecimalRoundGenerator()); + this.Merge(new RoundGenerator()); } protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index 7e15cd4acf6..bfa28e00262 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -45,16 +45,14 @@ public MathGenerator() ReflectHelper.GetMethodDefinition(() => Math.Sign(default(short))), ReflectHelper.GetMethodDefinition(() => Math.Sign(default(sbyte))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal), default(int))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Round(default(double), default(int))), ReflectHelper.GetMethodDefinition(() => Math.Floor(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Floor(default(double))), ReflectHelper.GetMethodDefinition(() => decimal.Floor(default(decimal))), + ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(double))), ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), + ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), diff --git a/src/NHibernate/Linq/Functions/RoundGenerator.cs b/src/NHibernate/Linq/Functions/RoundGenerator.cs new file mode 100644 index 00000000000..550c38e99df --- /dev/null +++ b/src/NHibernate/Linq/Functions/RoundGenerator.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; + +namespace NHibernate.Linq.Functions +{ + internal class RoundGenerator : BaseHqlGeneratorForMethod + { + public RoundGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => Math.Round(default(double))), + ReflectHelper.GetMethodDefinition(() => Math.Round(default(double), default(int))), + ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal))), + ReflectHelper.GetMethodDefinition(() => Math.Round(default(decimal), default(int))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal))), + ReflectHelper.GetMethodDefinition(() => decimal.Round(default(decimal), default(int))), + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + var numberOfDecimals = arguments.Count == 2 + ? visitor.Visit(arguments[1]).AsExpression() + : treeBuilder.Constant(0); + return treeBuilder.TransparentCast( + treeBuilder.MethodCall("round", visitor.Visit(arguments[0]).AsExpression(), numberOfDecimals), + method.ReturnType); + } + } +} \ No newline at end of file From 63a5b2a5cae54bf4c6a0b8bdb8e936d0d22abdfa Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 02:09:46 +1300 Subject: [PATCH 25/30] Implement Decimal.Truncate --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 12 ++++++++++++ src/NHibernate/Linq/Functions/MathGenerator.cs | 1 + 2 files changed, 13 insertions(+) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 8b290f56ab8..27e9d2abf1b 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -135,6 +135,18 @@ public void CanHandleFloor() CanSelect(e => decimal.Floor(e.EntityValue)); }); } + + [Test] + public void CanHandleTruncate() + { + AssumeFunctionSupported("truncate"); + + Assert.Multiple(() => + { + CanFilter(e => decimal.Truncate(e.EntityValue) > 1.0m); + CanSelect(e => decimal.Truncate(e.EntityValue)); + }); + } [Test] public void CanHandleMultiply() diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index bfa28e00262..5a4cc5dc804 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -55,6 +55,7 @@ public MathGenerator() ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), + ReflectHelper.GetMethodDefinition(() => decimal.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Pow(default(double), default(double))), }; From 50f27c4f9b1fbddd4f771b768da90bde768879e7 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 02:19:36 +1300 Subject: [PATCH 26/30] Make generators internal --- src/NHibernate/Linq/Functions/DecimalGenerator.cs | 12 ++++++------ src/NHibernate/Linq/Functions/RoundGenerator.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/NHibernate/Linq/Functions/DecimalGenerator.cs b/src/NHibernate/Linq/Functions/DecimalGenerator.cs index f36d09f07c2..f79a3bdcb12 100644 --- a/src/NHibernate/Linq/Functions/DecimalGenerator.cs +++ b/src/NHibernate/Linq/Functions/DecimalGenerator.cs @@ -8,7 +8,7 @@ namespace NHibernate.Linq.Functions { - public class DecimalAddGenerator : BaseHqlGeneratorForMethod + internal class DecimalAddGenerator : BaseHqlGeneratorForMethod { public DecimalAddGenerator() { @@ -24,7 +24,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalDivideGenerator : BaseHqlGeneratorForMethod + internal class DecimalDivideGenerator : BaseHqlGeneratorForMethod { public DecimalDivideGenerator() { @@ -40,7 +40,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalMultiplyGenerator : BaseHqlGeneratorForMethod + internal class DecimalMultiplyGenerator : BaseHqlGeneratorForMethod { public DecimalMultiplyGenerator() { @@ -56,7 +56,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalSubtractGenerator : BaseHqlGeneratorForMethod + internal class DecimalSubtractGenerator : BaseHqlGeneratorForMethod { public DecimalSubtractGenerator() { @@ -72,7 +72,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalRemainderGenerator : BaseHqlGeneratorForMethod + internal class DecimalRemainderGenerator : BaseHqlGeneratorForMethod { public DecimalRemainderGenerator() { @@ -88,7 +88,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, } } - public class DecimalNegateGenerator : BaseHqlGeneratorForMethod + internal class DecimalNegateGenerator : BaseHqlGeneratorForMethod { public DecimalNegateGenerator() { diff --git a/src/NHibernate/Linq/Functions/RoundGenerator.cs b/src/NHibernate/Linq/Functions/RoundGenerator.cs index 550c38e99df..a297cdd9299 100644 --- a/src/NHibernate/Linq/Functions/RoundGenerator.cs +++ b/src/NHibernate/Linq/Functions/RoundGenerator.cs @@ -33,4 +33,4 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, method.ReturnType); } } -} \ No newline at end of file +} From a53307835ecda648d277e986df74565f9311a7a6 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Sun, 4 Mar 2018 12:17:48 +1300 Subject: [PATCH 27/30] Revert "Implement Decimal.Truncate" This reverts commit 63a5b2a5cae54bf4c6a0b8bdb8e936d0d22abdfa. --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 12 ------------ src/NHibernate/Linq/Functions/MathGenerator.cs | 1 - 2 files changed, 13 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 27e9d2abf1b..8b290f56ab8 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -135,18 +135,6 @@ public void CanHandleFloor() CanSelect(e => decimal.Floor(e.EntityValue)); }); } - - [Test] - public void CanHandleTruncate() - { - AssumeFunctionSupported("truncate"); - - Assert.Multiple(() => - { - CanFilter(e => decimal.Truncate(e.EntityValue) > 1.0m); - CanSelect(e => decimal.Truncate(e.EntityValue)); - }); - } [Test] public void CanHandleMultiply() diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index 5a4cc5dc804..bfa28e00262 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -55,7 +55,6 @@ public MathGenerator() ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), - ReflectHelper.GetMethodDefinition(() => decimal.Truncate(default(decimal))), ReflectHelper.GetMethodDefinition(() => Math.Pow(default(double), default(double))), }; From 7c1594a33ffd6dc1d488af05d243e3a198bc7a6f Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Sun, 4 Mar 2018 13:53:56 +0100 Subject: [PATCH 28/30] Add support for Decimal.Truncate In Firebird the function is called 'trunc' MySQL requires the number of decimals to be specified --- .../NHSpecificTest/GH0831/FixtureByCode.cs | 12 ++++++++ src/NHibernate/Dialect/FirebirdDialect.cs | 2 +- src/NHibernate/Dialect/MySQLDialect.cs | 4 +-- .../DefaultLinqToHqlGeneratorsRegistry.cs | 1 + .../Linq/Functions/MathGenerator.cs | 3 -- .../Linq/Functions/TruncateGenerator.cs | 28 +++++++++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 src/NHibernate/Linq/Functions/TruncateGenerator.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs index 8b290f56ab8..b0dced8ea2f 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH0831/FixtureByCode.cs @@ -203,6 +203,18 @@ public void CanHandleSubtract() }); } + [Test] + public void CanHandleTruncate() + { + AssumeFunctionSupported("truncate"); + + Assert.Multiple(() => + { + CanFilter(e => decimal.Truncate(e.EntityValue) > 1m); + CanSelect(e => decimal.Truncate(e.EntityValue)); + }); + } + private void CanFilter(Expression> predicate) { using (ISession session = OpenSession()) diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index 5fa709e15c9..9950f4fc021 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -464,7 +464,7 @@ private void RegisterMathematicalFunctions() RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); RegisterFunction("sqtr", new StandardSQLFunction("sqtr", NHibernateUtil.Double)); - RegisterFunction("truncate", new StandardSQLFunction("truncate")); + RegisterFunction("truncate", new StandardSQLFunction("trunc")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new StandardSQLFunction("round")); } diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs index f38759e7964..9e0dc83e673 100644 --- a/src/NHibernate/Dialect/MySQLDialect.cs +++ b/src/NHibernate/Dialect/MySQLDialect.cs @@ -261,8 +261,8 @@ protected virtual void RegisterFunctions() RegisterFunction("ceiling", new StandardSQLFunction("ceiling")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new StandardSQLFunction("round")); - RegisterFunction("truncate", new StandardSQLFunction("truncate")); - + RegisterFunction("truncate", new SQLFunctionTemplate(null, "truncate(?1, 0)")); + RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); diff --git a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs index 3bbe4c9eb5b..e2cf2574cac 100644 --- a/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs +++ b/src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs @@ -59,6 +59,7 @@ public DefaultLinqToHqlGeneratorsRegistry() this.Merge(new DecimalRemainderGenerator()); this.Merge(new DecimalNegateGenerator()); this.Merge(new RoundGenerator()); + this.Merge(new TruncateGenerator()); } protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator) diff --git a/src/NHibernate/Linq/Functions/MathGenerator.cs b/src/NHibernate/Linq/Functions/MathGenerator.cs index bfa28e00262..f7f42ef5c13 100644 --- a/src/NHibernate/Linq/Functions/MathGenerator.cs +++ b/src/NHibernate/Linq/Functions/MathGenerator.cs @@ -53,9 +53,6 @@ public MathGenerator() ReflectHelper.GetMethodDefinition(() => Math.Ceiling(default(double))), ReflectHelper.GetMethodDefinition(() => decimal.Ceiling(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), - ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), - ReflectHelper.GetMethodDefinition(() => Math.Pow(default(double), default(double))), }; } diff --git a/src/NHibernate/Linq/Functions/TruncateGenerator.cs b/src/NHibernate/Linq/Functions/TruncateGenerator.cs new file mode 100644 index 00000000000..557ae03e171 --- /dev/null +++ b/src/NHibernate/Linq/Functions/TruncateGenerator.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Hql.Ast; +using NHibernate.Linq.Visitors; +using NHibernate.Util; + +namespace NHibernate.Linq.Functions +{ + internal class TruncateGenerator : BaseHqlGeneratorForMethod + { + public TruncateGenerator() + { + SupportedMethods = new[] + { + ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(decimal))), + ReflectHelper.GetMethodDefinition(() => Math.Truncate(default(double))), + ReflectHelper.GetMethodDefinition(() => decimal.Truncate(default(decimal))) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, Expression expression, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) + { + return treeBuilder.MethodCall("truncate", visitor.Visit(arguments[0]).AsExpression()); + } + } +} From 3abadf69ab040ad5c2dc7551ec6f9c83d85a4939 Mon Sep 17 00:00:00 2001 From: Jeroen Weelink Date: Mon, 5 Mar 2018 10:40:25 +0100 Subject: [PATCH 29/30] Redefine truncate function for MySQL and SQL Server --- src/NHibernate/Dialect/MsSql2000Dialect.cs | 1 + src/NHibernate/Dialect/MySQLDialect.cs | 2 +- src/NHibernate/Linq/Functions/TruncateGenerator.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs index 050d65e142d..2f929e47eb7 100644 --- a/src/NHibernate/Dialect/MsSql2000Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs @@ -287,6 +287,7 @@ protected virtual void RegisterFunctions() RegisterFunction("ceil", new StandardSQLFunction("ceiling")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new RoundEmulatingSingleParameterFunction()); + RegisterFunction("truncate", new VarArgsSQLFunction("round(", ",", ",1)")); RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs index 9e0dc83e673..a99db04efa5 100644 --- a/src/NHibernate/Dialect/MySQLDialect.cs +++ b/src/NHibernate/Dialect/MySQLDialect.cs @@ -261,7 +261,7 @@ protected virtual void RegisterFunctions() RegisterFunction("ceiling", new StandardSQLFunction("ceiling")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new StandardSQLFunction("round")); - RegisterFunction("truncate", new SQLFunctionTemplate(null, "truncate(?1, 0)")); + RegisterFunction("truncate", new StandardSQLFunction("truncate")); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); diff --git a/src/NHibernate/Linq/Functions/TruncateGenerator.cs b/src/NHibernate/Linq/Functions/TruncateGenerator.cs index 557ae03e171..c1f1babae25 100644 --- a/src/NHibernate/Linq/Functions/TruncateGenerator.cs +++ b/src/NHibernate/Linq/Functions/TruncateGenerator.cs @@ -22,7 +22,7 @@ public TruncateGenerator() public override HqlTreeNode BuildHql(MethodInfo method, Expression expression, ReadOnlyCollection arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) { - return treeBuilder.MethodCall("truncate", visitor.Visit(arguments[0]).AsExpression()); + return treeBuilder.MethodCall("truncate", visitor.Visit(arguments[0]).AsExpression(), treeBuilder.Constant(0)); } } } From f6c5038d43f115b0a3607acefe1e82ad2093a452 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Mon, 5 Mar 2018 23:24:39 +1300 Subject: [PATCH 30/30] Fix truncate registration --- src/NHibernate/Dialect/FirebirdDialect.cs | 1 + src/NHibernate/Dialect/MsSql2000Dialect.cs | 2 +- src/NHibernate/Dialect/MsSqlCeDialect.cs | 1 + src/NHibernate/Dialect/MySQLDialect.cs | 2 +- src/NHibernate/Dialect/Oracle8iDialect.cs | 1 + src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs | 2 ++ 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index 9950f4fc021..5883dfe2344 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -464,6 +464,7 @@ private void RegisterMathematicalFunctions() RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32)); RegisterFunction("sqtr", new StandardSQLFunction("sqtr", NHibernateUtil.Double)); + RegisterFunction("trunc", new StandardSQLFunction("trunc")); RegisterFunction("truncate", new StandardSQLFunction("trunc")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new StandardSQLFunction("round")); diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs index 2f929e47eb7..3553d5923a6 100644 --- a/src/NHibernate/Dialect/MsSql2000Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs @@ -287,7 +287,7 @@ protected virtual void RegisterFunctions() RegisterFunction("ceil", new StandardSQLFunction("ceiling")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new RoundEmulatingSingleParameterFunction()); - RegisterFunction("truncate", new VarArgsSQLFunction("round(", ",", ",1)")); + RegisterFunction("truncate", new SQLFunctionTemplate(null, "round(?1, ?2, 1)")); RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double)); diff --git a/src/NHibernate/Dialect/MsSqlCeDialect.cs b/src/NHibernate/Dialect/MsSqlCeDialect.cs index 7d7e81b1a0e..3aebc47e5e3 100644 --- a/src/NHibernate/Dialect/MsSqlCeDialect.cs +++ b/src/NHibernate/Dialect/MsSqlCeDialect.cs @@ -195,6 +195,7 @@ protected virtual void RegisterFunctions() RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))")); RegisterFunction("round", new RoundEmulatingSingleParameterFunction()); + RegisterFunction("truncate", new SQLFunctionTemplate(null, "round(?1, ?2, 1)")); RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8")); RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)")); diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs index a99db04efa5..3292dabaffd 100644 --- a/src/NHibernate/Dialect/MySQLDialect.cs +++ b/src/NHibernate/Dialect/MySQLDialect.cs @@ -261,7 +261,7 @@ protected virtual void RegisterFunctions() RegisterFunction("ceiling", new StandardSQLFunction("ceiling")); RegisterFunction("floor", new StandardSQLFunction("floor")); RegisterFunction("round", new StandardSQLFunction("round")); - RegisterFunction("truncate", new StandardSQLFunction("truncate")); + RegisterFunction("truncate", new StandardSafeSQLFunction("truncate", 2)); RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double)); diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index 321237c3a05..7120cd13624 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -229,6 +229,7 @@ protected virtual void RegisterFunctions() RegisterFunction("round", new StandardSQLFunction("round")); RegisterFunction("trunc", new StandardSQLFunction("trunc")); + RegisterFunction("truncate", new StandardSQLFunction("trunc")); RegisterFunction("ceil", new StandardSQLFunction("ceil")); RegisterFunction("ceiling", new StandardSQLFunction("ceil")); RegisterFunction("floor", new StandardSQLFunction("floor")); diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs index 5758cd5da31..09f8c6491b6 100644 --- a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs +++ b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs @@ -343,6 +343,8 @@ protected virtual void RegisterMiscellaneousFunctions() RegisterFunction("transactsql", new StandardSQLFunction("transactsql", NHibernateUtil.String)); RegisterFunction("varexists", new StandardSQLFunction("varexists", NHibernateUtil.Int32)); RegisterFunction("watcomsql", new StandardSQLFunction("watcomsql", NHibernateUtil.String)); + RegisterFunction("truncnum", new StandardSafeSQLFunction("truncnum", 2)); + RegisterFunction("truncate", new StandardSafeSQLFunction("truncnum", 2)); } #region private static readonly string[] DialectKeywords = { ... }