-
Notifications
You must be signed in to change notification settings - Fork 934
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 all 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,249 @@ | ||
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() | ||
{ | ||
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)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleCeiling() | ||
{ | ||
AssumeFunctionSupported("ceiling"); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
CanFilter(e => decimal.Ceiling(e.EntityValue) > 1.0m); | ||
CanSelect(e => decimal.Ceiling(e.EntityValue)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleCompare() | ||
{ | ||
AssumeFunctionSupported("sign"); | ||
|
||
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)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleDivide() | ||
{ | ||
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)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleEquals() | ||
{ | ||
Assert.Multiple(() => | ||
{ | ||
CanFilter(e => decimal.Equals(e.EntityValue, 1.0m)); | ||
CanFilter(e => decimal.Equals(1.0m, e.EntityValue)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleFloor() | ||
{ | ||
AssumeFunctionSupported("floor"); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
CanFilter(e => decimal.Floor(e.EntityValue) > 1.0m); | ||
CanSelect(e => decimal.Floor(e.EntityValue)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleMultiply() | ||
{ | ||
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)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleNegate() | ||
{ | ||
Assert.Multiple(() => | ||
{ | ||
CanFilter(e => decimal.Negate(e.EntityValue) > -1.0m); | ||
CanSelect(e => decimal.Negate(e.EntityValue)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleRemainder() | ||
{ | ||
Assume.That(TestDialect.SupportsModuloOnDecimal, Is.True); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
CanFilter(e => decimal.Remainder(e.EntityValue, 2m) == 0); | ||
CanFilter(e => decimal.Remainder(2m, e.EntityValue) < 1); | ||
|
||
CanSelect(e => decimal.Remainder(e.EntityValue, 2m)); | ||
CanSelect(e => decimal.Remainder(2m, e.EntityValue)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleRound() | ||
{ | ||
AssumeFunctionSupported("round"); | ||
|
||
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))); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void CanHandleSubtract() | ||
{ | ||
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)); | ||
}); | ||
} | ||
|
||
[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<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 = null; | ||
Assert.That(() => inSession = session.Query<Entity>().Select(predicate).ToList(), Throws.Nothing); | ||
|
||
Assert.That(inSession, Is.EquivalentTo(expected).Using((decimal a, decimal b) => Math.Abs(a - b) < 0.0001m)); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)); | ||
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 do not think that we should register specific function names for being available in HQL when using their specific dialect, since HQL should be database agnostic. 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 think opposite. Moreover, this is how it always was: dialect specific functions + aliases to the more common name. 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. "Always" widely depends on dialect. Some do register specific names for some of their functions in addition to the generic one, others do not. (By example SQL-Server registers only generic names for This practice of registering specific names does not promote portability of HQL, while an ORM is usually meant to be database agnostic. That is why I think this is a bad thing, causing me to prefer not adding new specific names when we have a generic one. (Removing them after a release would of course be a breaking change for those using them.) |
||
RegisterFunction("truncate", new StandardSafeSQLFunction("truncnum", 2)); | ||
} | ||
|
||
#region private static readonly string[] DialectKeywords = { ... } | ||
|
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.