Skip to content

Commit 6987985

Browse files
Fix round registration
* PostgreSQL: * It was yielding double on decimal columns. * It was failing on no-second-argument round. * SQL Server and SQL Server Compact Edition: * They were not supporting single argument round.
1 parent 59ff1df commit 6987985

File tree

8 files changed

+185
-6
lines changed

8 files changed

+185
-6
lines changed

src/NHibernate.Test/Async/Hql/HQLFunctions.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,65 @@ public async Task CeilingAsync()
520520
}
521521
}
522522

523+
[Test]
524+
public async Task RoundAsync()
525+
{
526+
AssumeFunctionSupported("round");
527+
528+
using (var s = OpenSession())
529+
{
530+
var a1 = new Animal("a1", 1.87f);
531+
await (s.SaveAsync(a1));
532+
var m1 = new MaterialResource("m1", "18", MaterialResource.MaterialState.Available) { Cost = 51.76m };
533+
await (s.SaveAsync(m1));
534+
await (s.FlushAsync());
535+
}
536+
using (var s = OpenSession())
537+
{
538+
var roundF = await (s.CreateQuery("select round(a.BodyWeight) from Animal a").UniqueResultAsync<float>());
539+
Assert.That(roundF, Is.EqualTo(2), "Selecting round(double) failed.");
540+
var countF =
541+
await (s
542+
.CreateQuery("select count(*) from Animal a where round(a.BodyWeight) = :c")
543+
.SetInt32("c", 2)
544+
.UniqueResultAsync<long>());
545+
Assert.That(countF, Is.EqualTo(1), "Filtering round(double) failed.");
546+
547+
roundF = await (s.CreateQuery("select round(a.BodyWeight, 1) from Animal a").UniqueResultAsync<float>());
548+
Assert.That(roundF, Is.EqualTo(1.9f).Within(0.01f), "Selecting round(double, 1) failed.");
549+
countF =
550+
await (s
551+
.CreateQuery("select count(*) from Animal a where round(a.BodyWeight, 1) between :c1 and :c2")
552+
.SetDouble("c1", 1.89)
553+
.SetDouble("c2", 1.91)
554+
.UniqueResultAsync<long>());
555+
Assert.That(countF, Is.EqualTo(1), "Filtering round(double, 1) failed.");
556+
557+
var roundD = await (s.CreateQuery("select round(m.Cost) from MaterialResource m").UniqueResultAsync<decimal?>());
558+
Assert.That(roundD, Is.EqualTo(52), "Selecting round(decimal) failed.");
559+
var count =
560+
await (s
561+
.CreateQuery("select count(*) from MaterialResource m where round(m.Cost) = :c")
562+
.SetInt32("c", 52)
563+
.UniqueResultAsync<long>());
564+
Assert.That(count, Is.EqualTo(1), "Filtering round(decimal) failed.");
565+
566+
roundD = await (s.CreateQuery("select round(m.Cost, 1) from MaterialResource m").UniqueResultAsync<decimal?>());
567+
Assert.That(roundD, Is.EqualTo(51.8m), "Selecting round(decimal, 1) failed.");
568+
569+
if (TestDialect.HasBrokenDecimalType)
570+
// SQLite fails the equality test due to using double instead, wich requires a tolerance.
571+
return;
572+
573+
count =
574+
await (s
575+
.CreateQuery("select count(*) from MaterialResource m where round(m.Cost, 1) = :c")
576+
.SetDecimal("c", 51.8m)
577+
.UniqueResultAsync<long>());
578+
Assert.That(count, Is.EqualTo(1), "Filtering round(decimal, 1) failed.");
579+
}
580+
}
581+
523582
[Test]
524583
public async Task ModAsync()
525584
{

src/NHibernate.Test/Hql/HQLFunctions.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,65 @@ public void Ceiling()
509509
}
510510
}
511511

512+
[Test]
513+
public void Round()
514+
{
515+
AssumeFunctionSupported("round");
516+
517+
using (var s = OpenSession())
518+
{
519+
var a1 = new Animal("a1", 1.87f);
520+
s.Save(a1);
521+
var m1 = new MaterialResource("m1", "18", MaterialResource.MaterialState.Available) { Cost = 51.76m };
522+
s.Save(m1);
523+
s.Flush();
524+
}
525+
using (var s = OpenSession())
526+
{
527+
var roundF = s.CreateQuery("select round(a.BodyWeight) from Animal a").UniqueResult<float>();
528+
Assert.That(roundF, Is.EqualTo(2), "Selecting round(double) failed.");
529+
var countF =
530+
s
531+
.CreateQuery("select count(*) from Animal a where round(a.BodyWeight) = :c")
532+
.SetInt32("c", 2)
533+
.UniqueResult<long>();
534+
Assert.That(countF, Is.EqualTo(1), "Filtering round(double) failed.");
535+
536+
roundF = s.CreateQuery("select round(a.BodyWeight, 1) from Animal a").UniqueResult<float>();
537+
Assert.That(roundF, Is.EqualTo(1.9f).Within(0.01f), "Selecting round(double, 1) failed.");
538+
countF =
539+
s
540+
.CreateQuery("select count(*) from Animal a where round(a.BodyWeight, 1) between :c1 and :c2")
541+
.SetDouble("c1", 1.89)
542+
.SetDouble("c2", 1.91)
543+
.UniqueResult<long>();
544+
Assert.That(countF, Is.EqualTo(1), "Filtering round(double, 1) failed.");
545+
546+
var roundD = s.CreateQuery("select round(m.Cost) from MaterialResource m").UniqueResult<decimal?>();
547+
Assert.That(roundD, Is.EqualTo(52), "Selecting round(decimal) failed.");
548+
var count =
549+
s
550+
.CreateQuery("select count(*) from MaterialResource m where round(m.Cost) = :c")
551+
.SetInt32("c", 52)
552+
.UniqueResult<long>();
553+
Assert.That(count, Is.EqualTo(1), "Filtering round(decimal) failed.");
554+
555+
roundD = s.CreateQuery("select round(m.Cost, 1) from MaterialResource m").UniqueResult<decimal?>();
556+
Assert.That(roundD, Is.EqualTo(51.8m), "Selecting round(decimal, 1) failed.");
557+
558+
if (TestDialect.HasBrokenDecimalType)
559+
// SQLite fails the equality test due to using double instead, wich requires a tolerance.
560+
return;
561+
562+
count =
563+
s
564+
.CreateQuery("select count(*) from MaterialResource m where round(m.Cost, 1) = :c")
565+
.SetDecimal("c", 51.8m)
566+
.UniqueResult<long>();
567+
Assert.That(count, Is.EqualTo(1), "Filtering round(decimal, 1) failed.");
568+
}
569+
}
570+
512571
[Test]
513572
public void Mod()
514573
{

src/NHibernate.Test/Hql/MaterialResource.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System;
2-
31
namespace NHibernate.Test.Hql
42
{
53
public class MaterialResource
@@ -51,5 +49,6 @@ public virtual MaterialState State
5149
set { _state = value; }
5250
}
5351

52+
public virtual decimal? Cost { get; set; }
5453
}
5554
}

src/NHibernate.Test/Hql/MaterialResource.hbm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
<property name="Description"/>
1313
<property name="SerialNumber" length="20"/>
1414
<property name="State" type="int"/>
15+
<property name="Cost" access="property"/>
1516
</class>
1617
</hibernate-mapping>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections;
2+
using NHibernate.Engine;
3+
using NHibernate.SqlCommand;
4+
using NHibernate.Type;
5+
using System;
6+
7+
namespace NHibernate.Dialect.Function
8+
{
9+
/// <summary>
10+
/// Provides a round implementation that supports single parameter round by translating to two parameters round.
11+
/// </summary>
12+
[Serializable]
13+
public class RoundEmulatingSingleParameterFunction : ISQLFunction
14+
{
15+
private static readonly ISQLFunction SingleParamRound = new SQLFunctionTemplate(null, "round(?1, 0)");
16+
17+
private static readonly ISQLFunction Round = new StandardSQLFunction("round");
18+
19+
public IType ReturnType(IType columnType, IMapping mapping) => columnType;
20+
21+
public bool HasArguments => true;
22+
23+
public bool HasParenthesesIfNoArguments => true;
24+
25+
public SqlString Render(IList args, ISessionFactoryImplementor factory)
26+
{
27+
return args.Count == 1 ? SingleParamRound.Render(args, factory) : Round.Render(args, factory);
28+
}
29+
30+
public override string ToString() => "round";
31+
}
32+
}

src/NHibernate/Dialect/MsSql2000Dialect.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ protected virtual void RegisterFunctions()
286286
RegisterFunction("ceiling", new StandardSQLFunction("ceiling"));
287287
RegisterFunction("ceil", new StandardSQLFunction("ceiling"));
288288
RegisterFunction("floor", new StandardSQLFunction("floor"));
289-
RegisterFunction("round", new StandardSQLFunction("round"));
289+
RegisterFunction("round", new RoundEmulatingSingleParameterFunction());
290290

291291
RegisterFunction("power", new StandardSQLFunction("power", NHibernateUtil.Double));
292292

src/NHibernate/Dialect/MsSqlCeDialect.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ protected virtual void RegisterFunctions()
194194
RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", "+", ")"));
195195
RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))"));
196196

197-
RegisterFunction("round", new StandardSQLFunction("round"));
197+
RegisterFunction("round", new RoundEmulatingSingleParameterFunction());
198198

199199
RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8"));
200200
RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)"));

src/NHibernate/Dialect/PostgreSQLDialect.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
using System;
2+
using System.Collections;
13
using System.Data;
24
using System.Data.Common;
3-
using NHibernate.Cfg;
45
using NHibernate.Dialect.Function;
56
using NHibernate.Dialect.Schema;
7+
using NHibernate.Engine;
68
using NHibernate.SqlCommand;
79
using NHibernate.SqlTypes;
10+
using NHibernate.Type;
11+
using Environment = NHibernate.Cfg.Environment;
812

913
namespace NHibernate.Dialect
1014
{
@@ -65,7 +69,7 @@ public PostgreSQLDialect()
6569
RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))"));
6670

6771
RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32));
68-
RegisterFunction("round", new SQLFunctionTemplate(NHibernateUtil.Double, "round(cast(?1 as numeric), ?2)"));
72+
RegisterFunction("round", new RoundFunction());
6973

7074
// Trigonometric functions.
7175
RegisterFunction("acos", new StandardSQLFunction("acos", NHibernateUtil.Double));
@@ -313,5 +317,30 @@ public override string CurrentTimestampSelectString
313317
public override bool SupportsUnboundedLobLocatorMaterialization => false;
314318

315319
#endregion
320+
321+
[Serializable]
322+
private class RoundFunction : ISQLFunction
323+
{
324+
private static readonly ISQLFunction Round = new StandardSQLFunction("round");
325+
326+
// PostgreSQL round with two arguments only accepts decimal as input, thus the cast.
327+
// It also yields only decimal, but for emulating similar behavior to other databases, we need
328+
// to have it converted to the original input type, which will be done by NHibernate thanks to
329+
// not specifying the function type.
330+
private static readonly ISQLFunction RoundWith2Params = new SQLFunctionTemplate(null, "round(cast(?1 as numeric), ?2)");
331+
332+
public IType ReturnType(IType columnType, IMapping mapping) => columnType;
333+
334+
public bool HasArguments => true;
335+
336+
public bool HasParenthesesIfNoArguments => true;
337+
338+
public SqlString Render(IList args, ISessionFactoryImplementor factory)
339+
{
340+
return args.Count == 2 ? RoundWith2Params.Render(args, factory) : Round.Render(args, factory);
341+
}
342+
343+
public override string ToString() => "round";
344+
}
316345
}
317346
}

0 commit comments

Comments
 (0)