-
Notifications
You must be signed in to change notification settings - Fork 933
Add query support for the static methods of System.Decimal #1533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
0e6d7a9
ef0490e
10d0567
ec6ae5d
6b86b78
d8190c5
bda62c9
1926597
8c98694
11bfbf2
f16a8ef
4724f23
56a9aae
6359afb
aba2f26
f28a34e
edc2996
cc18f88
cee8732
7247f80
e217489
31887cc
4828214
2f9d0e7
7c61ab0
d009181
a6d2e25
bd577f1
c6806cf
e7cbfa0
e1b1e5d
f9acc6a
33c985b
4b51cfb
e0f50c8
16719fd
28dde43
8906825
63a5b2a
50f27c4
a533078
7c1594a
3abadf6
f6c5038
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
|
||
namespace NHibernate.Test.NHSpecificTest.GH0831 | ||
{ | ||
class Entity | ||
{ | ||
public virtual Guid Id { get; set; } | ||
public virtual decimal EntityValue { 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); | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return EntityValue.ToString(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
|
||
using NHibernate.Cfg.MappingSchema; | ||
using NHibernate.Mapping.ByCode; | ||
|
||
using NUnit.Framework; | ||
|
||
namespace NHibernate.Test.NHSpecificTest.GH0831 | ||
{ | ||
public class ByCodeFixture : TestCaseMappingByCode | ||
{ | ||
private readonly IList<Entity> entities = new List<Entity> | ||
{ | ||
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() | ||
{ | ||
var mapper = new ModelMapper(); | ||
mapper.Class<Entity>(rc => | ||
{ | ||
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); | ||
rc.Property(x => x.EntityValue); | ||
}); | ||
|
||
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() | ||
{ | ||
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] | ||
public void CanHandleCeiling() | ||
{ | ||
AssumeFunctionSupported("ceiling"); | ||
|
||
CanFilter(e => decimal.Ceiling(e.EntityValue) > 1.0m); | ||
CanSelect(e => decimal.Ceiling(e.EntityValue)); | ||
} | ||
|
||
[Test] | ||
public void CanHandleCompare() | ||
{ | ||
AssumeFunctionSupported("sign"); | ||
|
||
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() | ||
{ | ||
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() | ||
{ | ||
CanFilter(e => decimal.Equals(e.EntityValue, 1.0m)); | ||
CanFilter(e => decimal.Equals(1.0m, e.EntityValue)); | ||
} | ||
|
||
[Test] | ||
public void CanHandleFloor() | ||
{ | ||
AssumeFunctionSupported("floor"); | ||
|
||
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); | ||
|
||
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)); | ||
} | ||
|
||
[Test] | ||
public void CanHandleRemainder() | ||
{ | ||
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] | ||
public void CanHandleRound() | ||
{ | ||
AssumeFunctionSupported("round"); | ||
|
||
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() | ||
{ | ||
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 CanFilter(Expression<Func<Entity, bool>> predicate) | ||
{ | ||
using (ISession session = OpenSession()) | ||
using (session.BeginTransaction()) | ||
{ | ||
IEnumerable<Entity> inMemory = entities.Where(predicate.Compile()).ToList(); | ||
IEnumerable<Entity> inSession = session.Query<Entity>().Where(predicate).ToList(); | ||
|
||
CollectionAssert.AreEquivalent(inMemory, inSession); | ||
} | ||
} | ||
|
||
private void CanSelect(Expression<Func<Entity, decimal>> predicate) | ||
{ | ||
IEnumerable<decimal> inMemory = entities.Select(predicate.Compile()).ToList(); | ||
|
||
CanSelect(predicate, inMemory); | ||
} | ||
|
||
private void CanSelect(Expression<Func<Entity, decimal>> predicate, IEnumerable<decimal> expected) | ||
{ | ||
using (ISession session = OpenSession()) | ||
using (session.BeginTransaction()) | ||
{ | ||
IEnumerable<decimal> inSession = session.Query<Entity>().Select(predicate).ToList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the IEnumerable<decimal> inSession = null;
Assert.That(() => inSession = session.Query<Entity>().Select(predicate).ToList(), Throws.Nothing); (And use IEnumerable<decimal> inSession = null;
Assert.That(() => inSession = session.Query<Entity>().OrderBy(e => e.EntityValue).Select(predicate).ToList(), Throws.Nothing);
Assert.That(inSession, Is.EqualTo(expected).Within(0.001M)); ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll change this soon. It took a month, but I think I'm nearly there. |
||
|
||
Assert.That(inSession.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x)).Within(0.001M)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather avoid the order by on result values, which may hide a trouble (granted, unlikely). Instead, the |
||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
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.Subtract(default(decimal), default(decimal))) | ||
}; | ||
} | ||
|
||
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) | ||
{ | ||
string function = method.Name.ToLowerInvariant(); | ||
|
||
HqlExpression[] expressions = arguments.Select(x => visitor.Visit(x).AsExpression()).ToArray(); | ||
|
||
switch (function) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that it would be better to split the class into different generators. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. After everything works I need to clean it all up. |
||
{ | ||
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.Negate(expressions[0]); | ||
case "compare": | ||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clearly a better solution. Now is it needed to have the cast in the resulting SQL query? If no, you could use a |
||
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]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no functions which accepts 2 arguments anymore. Only functions left after the switch above is "ceiling" and "floor", both accept single argument. |
||
} | ||
|
||
return treeBuilder.MethodCall(function, expressions[0]); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wish to keep on explicitly ordering the in-memory case, add the ordering here:
entities.OrderBy(e => e.EntityValue).Select(predicate.Compile()).ToList()
.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I would like to get rid of it entirely. It is noise. It should be replaced with an assertion that uses a tolerance when comparing values and where the order does not matter. As far as I can find, NUnit doesn't have this. It can either assert with a tolerance, or where the order doesn't matter, not both.
I noticed thatNHibernate.Test
already has aObjectAssertion
,NHAssert
andSubclassAssert
. I can add one for collections.I've changed the assertion to ignore order.