Skip to content

Add support for single-argument truncate to dialects that do not support it natively #1597

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

Merged
merged 3 commits into from
Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/NHibernate.Test/Hql/HQLFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,65 @@ public void Round()
}
}

[Test]
public void Truncate()
{
AssumeFunctionSupported("truncate");

using (var s = OpenSession())
{
var a1 = new Animal("a1", 1.87f);
s.Save(a1);
var m1 = new MaterialResource("m1", "18", MaterialResource.MaterialState.Available) { Cost = 51.76m };
s.Save(m1);
s.Flush();
}
using (var s = OpenSession())
{
var roundF = s.CreateQuery("select truncate(a.BodyWeight) from Animal a").UniqueResult<float>();
Assert.That(roundF, Is.EqualTo(1), "Selecting truncate(double) failed.");
var countF =
s
.CreateQuery("select count(*) from Animal a where truncate(a.BodyWeight) = :c")
.SetInt32("c", 1)
.UniqueResult<long>();
Assert.That(countF, Is.EqualTo(1), "Filtering truncate(double) failed.");

roundF = s.CreateQuery("select truncate(a.BodyWeight, 1) from Animal a").UniqueResult<float>();
Assert.That(roundF, Is.EqualTo(1.8f).Within(0.01f), "Selecting truncate(double, 1) failed.");
countF =
s
.CreateQuery("select count(*) from Animal a where truncate(a.BodyWeight, 1) between :c1 and :c2")
.SetDouble("c1", 1.79)
.SetDouble("c2", 1.81)
.UniqueResult<long>();
Assert.That(countF, Is.EqualTo(1), "Filtering truncate(double, 1) failed.");

var roundD = s.CreateQuery("select truncate(m.Cost) from MaterialResource m").UniqueResult<decimal?>();
Assert.That(roundD, Is.EqualTo(51), "Selecting truncate(decimal) failed.");
var count =
s
.CreateQuery("select count(*) from MaterialResource m where truncate(m.Cost) = :c")
.SetInt32("c", 51)
.UniqueResult<long>();
Assert.That(count, Is.EqualTo(1), "Filtering truncate(decimal) failed.");

roundD = s.CreateQuery("select truncate(m.Cost, 1) from MaterialResource m").UniqueResult<decimal?>();
Assert.That(roundD, Is.EqualTo(51.7m), "Selecting truncate(decimal, 1) failed.");

if (TestDialect.HasBrokenDecimalType)
// SQLite fails the equality test due to using double instead, wich requires a tolerance.
return;

count =
s
.CreateQuery("select count(*) from MaterialResource m where truncate(m.Cost, 1) = :c")
.SetDecimal("c", 51.7m)
.UniqueResult<long>();
Assert.That(count, Is.EqualTo(1), "Filtering truncate(decimal, 1) failed.");
}
}

[Test]
public void Mod()
{
Expand Down
32 changes: 0 additions & 32 deletions src/NHibernate/Dialect/Function/RoundFunction.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections;
using System.Linq;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.Type;

namespace NHibernate.Dialect.Function
{
/// <summary>
/// A SQL function which substitutes required missing parameters with defaults.
/// </summary>
[Serializable]
internal class StandardSQLFunctionWithRequiredParameters : StandardSQLFunction
{
private readonly object[] _requiredArgs;

/// <inheritdoc />
public StandardSQLFunctionWithRequiredParameters(string name, object[] requiredArgs)
: base(name)
{
_requiredArgs = requiredArgs;
}

/// <inheritdoc />
public StandardSQLFunctionWithRequiredParameters(string name, IType typeValue, object[] requiredArgs)
: base(name, typeValue)
{
_requiredArgs = requiredArgs;
}

/// <inheritdoc />
public override SqlString Render(IList args, ISessionFactoryImplementor factory)
{
var combinedArgs =
args.Cast<object>()
.Concat(_requiredArgs.Skip(args.Count))
.ToArray();
return base.Render(combinedArgs, factory);
}
}
}
4 changes: 2 additions & 2 deletions src/NHibernate/Dialect/MsSql2000Dialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ protected virtual void RegisterFunctions()
RegisterFunction("ceiling", new StandardSQLFunction("ceiling"));
RegisterFunction("ceil", new StandardSQLFunction("ceiling"));
RegisterFunction("floor", new StandardSQLFunction("floor"));
RegisterFunction("round", new RoundEmulatingSingleParameterFunction());
RegisterFunction("truncate", new SQLFunctionTemplate(null, "round(?1, ?2, 1)"));
RegisterFunction("round", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0"}));
RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0", "1"}));

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

Expand Down
4 changes: 2 additions & 2 deletions src/NHibernate/Dialect/MsSqlCeDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ protected virtual void RegisterFunctions()
RegisterFunction("concat", new VarArgsSQLFunction(NHibernateUtil.String, "(", "+", ")"));
RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))"));

RegisterFunction("round", new RoundEmulatingSingleParameterFunction());
RegisterFunction("truncate", new SQLFunctionTemplate(null, "round(?1, ?2, 1)"));
RegisterFunction("round", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0"}));
RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0", "1"}));

RegisterFunction("bit_length", new SQLFunctionTemplate(NHibernateUtil.Int32, "datalength(?1) * 8"));
RegisterFunction("extract", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(?1, ?3)"));
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Dialect/MySQLDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 StandardSafeSQLFunction("truncate", 2));
RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("truncate", new object[] {null, "0"}));

RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double));

Expand Down
32 changes: 28 additions & 4 deletions src/NHibernate/Dialect/PostgreSQLDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ public PostgreSQLDialect()
RegisterFunction("mod", new SQLFunctionTemplate(NHibernateUtil.Int32, "((?1) % (?2))"));

RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32));
RegisterFunction("round", new RoundFunction());
RegisterFunction("round", new RoundFunction(false));
RegisterFunction("truncate", new RoundFunction(true));
RegisterFunction("trunc", new RoundFunction(true));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written here, I consider supporting dialect specific names (trunc here) as a bad practice. But since there is some disagreement about this, I will not fight against it and so I am still adding it here.


// Trigonometric functions.
RegisterFunction("acos", new StandardSQLFunction("acos", NHibernateUtil.Double));
Expand Down Expand Up @@ -322,12 +324,34 @@ public override string CurrentTimestampSelectString
private class RoundFunction : ISQLFunction
{
private static readonly ISQLFunction Round = new StandardSQLFunction("round");
private static readonly ISQLFunction Truncate = new StandardSQLFunction("trunc");

// PostgreSQL round with two arguments only accepts decimal as input, thus the cast.
// PostgreSQL round/trunc with two arguments only accepts decimal as input, thus the cast.
// It also yields only decimal, but for emulating similar behavior to other databases, we need
// to have it converted to the original input type, which will be done by NHibernate thanks to
// not specifying the function type.
private static readonly ISQLFunction RoundWith2Params = new SQLFunctionTemplate(null, "round(cast(?1 as numeric), ?2)");
private static readonly ISQLFunction TruncateWith2Params = new SQLFunctionTemplate(null, "trunc(cast(?1 as numeric), ?2)");

private readonly ISQLFunction _singleParamFunction;
private readonly ISQLFunction _twoParamFunction;
private readonly string _name;

public RoundFunction(bool truncate)
{
if (truncate)
{
_singleParamFunction = Truncate;
_twoParamFunction = TruncateWith2Params;
_name = "truncate";
}
else
{
_singleParamFunction = Round;
_twoParamFunction = RoundWith2Params;
_name = "round";
}
}

public IType ReturnType(IType columnType, IMapping mapping) => columnType;

Expand All @@ -337,10 +361,10 @@ private class RoundFunction : ISQLFunction

public SqlString Render(IList args, ISessionFactoryImplementor factory)
{
return args.Count == 2 ? RoundWith2Params.Render(args, factory) : Round.Render(args, factory);
return args.Count == 2 ? _twoParamFunction.Render(args, factory) : _singleParamFunction.Render(args, factory);
}

public override string ToString() => "round";
public override string ToString() => _name;
}
}
}
7 changes: 3 additions & 4 deletions src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,13 @@ protected virtual void RegisterMathFunctions()
RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double));
RegisterFunction("rand", new StandardSQLFunction("rand", NHibernateUtil.Double));
RegisterFunction("remainder", new StandardSQLFunction("remainder"));
RegisterFunction("round", new StandardSQLFunction("round"));
RegisterFunction("round", new StandardSQLFunctionWithRequiredParameters("round", new object[] {null, "0"}));
RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32));
RegisterFunction("sin", new StandardSQLFunction("sin", NHibernateUtil.Double));
RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double));
RegisterFunction("tan", new StandardSQLFunction("tan", NHibernateUtil.Double));
RegisterFunction("truncate", new StandardSQLFunction("truncate"));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, it was an invalid registration.

RegisterFunction("truncnum", new StandardSQLFunctionWithRequiredParameters("truncnum", new object[] {null, "0"}));
RegisterFunction("truncate", new StandardSQLFunctionWithRequiredParameters("truncnum", new object[] {null, "0"}));
}

protected virtual void RegisterXmlFunctions()
Expand Down Expand Up @@ -343,8 +344,6 @@ 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));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to RegisterMathFunctions (and adjusted).

}

#region private static readonly string[] DialectKeywords = { ... }
Expand Down