Skip to content

Commit

Permalink
Support evaluation of Random.Next and NextDouble on db side
Browse files Browse the repository at this point in the history
Fixes #959, along with two previous commits
  • Loading branch information
fredericDelaporte committed Mar 22, 2020
1 parent 58272eb commit 3a8a1ba
Show file tree
Hide file tree
Showing 18 changed files with 572 additions and 6 deletions.
152 changes: 152 additions & 0 deletions src/NHibernate.Test/Async/Linq/PreEvaluationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Cfg;
using NHibernate.SqlTypes;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;
using NHibernate.Linq;

namespace NHibernate.Test.Linq
{
using System.Threading.Tasks;
[TestFixture(false, false)]
[TestFixture(true, false)]
[TestFixture(false, true)]
public class PreEvaluationTestsAsync : LinqTestCase
{
private readonly bool LegacyPreEvaluation;
private readonly bool FallbackOnPreEvaluation;

public PreEvaluationTestsAsync(bool legacy, bool fallback)
{
LegacyPreEvaluation = legacy;
FallbackOnPreEvaluation = fallback;
}

protected override void Configure(Configuration configuration)
{
base.Configure(configuration);

configuration.SetProperty(Environment.FormatSql, "false");
configuration.SetProperty(Environment.LinqToHqlLegacyPreEvaluation, LegacyPreEvaluation.ToString());
configuration.SetProperty(Environment.LinqToHqlFallbackOnPreEvaluation, FallbackOnPreEvaluation.ToString());
}

private void RunTest(bool isSupported, Action<SqlLogSpy> test)
{
using (var spy = new SqlLogSpy())
{
try
{
test(spy);
}
catch (QueryException)
{
if (!isSupported && !FallbackOnPreEvaluation)
// Expected failure
return;
throw;
}
}

if (!isSupported && !FallbackOnPreEvaluation)
Assert.Fail("The test should have thrown a QueryException, but has not thrown anything");
}

[Test]
public async Task CanQueryByRandomIntAsync()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next());
Assert.That(x, Is.GreaterThan(0));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public async Task CanQueryByRandomIntWithMaxAsync()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10));
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public async Task CanQueryByRandomIntWithMinMaxAsync()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10));
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
{
if (!IsFunctionSupported(functionName))
Assert.Inconclusive($"{functionName} is not supported by the dialect");

var function = Dialect.Functions[functionName].Render(new List<object>(), Sfi).ToString();

if (LegacyPreEvaluation)
Assert.That(spy.GetWholeLog(), Does.Not.Contain(function));
else
Assert.That(spy.GetWholeLog(), Does.Contain(function));
}
}
}
217 changes: 217 additions & 0 deletions src/NHibernate.Test/Linq/PreEvaluationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,223 @@ public void CanSelectNewGuid()
});
}

[Test]
public void CanQueryByRandomDouble()
{
var isSupported = IsFunctionSupported("random");
RunTest(
isSupported,
spy =>
{
var random = new Random();
var x = db.Orders.Count(o => o.OrderId > random.NextDouble());
Assert.That(x, Is.GreaterThan(0));
AssertFunctionInSql("random", spy);
});
}

[Test]
public void CanSelectRandomDouble()
{
var isSupported = IsFunctionSupported("random");
RunTest(
isSupported,
spy =>
{
var random = new Random();
var x =
db
.Orders.Select(o => new { id = o.OrderId, r = random.NextDouble() })
.OrderBy(o => o.id).ToList();
Assert.That(x, Has.Count.GreaterThan(0));
var randomValues = x.Select(o => o.r).Distinct().ToArray();
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(1));
if (!LegacyPreEvaluation && IsFunctionSupported("random"))
{
// Naïve randomness check
Assert.That(
randomValues,
Has.Length.GreaterThan(x.Count / 2),
"Generated values do not seem very random");
}
AssertFunctionInSql("random", spy);
});
}

[Test]
public void CanQueryByRandomInt()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = db.Orders.Min(o => o.OrderId);
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next());
Assert.That(x, Is.GreaterThan(0));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public void CanSelectRandomInt()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
RunTest(
isSupported,
spy =>
{
var random = new Random();
var x =
db
.Orders.Select(o => new { id = o.OrderId, r = random.Next() })
.OrderBy(o => o.id).ToList();
Assert.That(x, Has.Count.GreaterThan(0));
var randomValues = x.Select(o => o.r).Distinct().ToArray();
Assert.That(
randomValues,
Has.All.GreaterThanOrEqualTo(0).And.LessThan(int.MaxValue).And.TypeOf<int>());
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
{
// Naïve randomness check
Assert.That(
randomValues,
Has.Length.GreaterThan(x.Count / 2),
"Generated values do not seem very random");
}
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public void CanQueryByRandomIntWithMax()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = db.Orders.Min(o => o.OrderId);
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10));
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public void CanSelectRandomIntWithMax()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
RunTest(
isSupported,
spy =>
{
var random = new Random();
var x =
db
.Orders.Select(o => new { id = o.OrderId, r = random.Next(10) })
.OrderBy(o => o.id).ToList();
Assert.That(x, Has.Count.GreaterThan(0));
var randomValues = x.Select(o => o.r).Distinct().ToArray();
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(10).And.TypeOf<int>());
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
{
// Naïve randomness check
Assert.That(
randomValues,
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
"Generated values do not seem very random");
}
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public void CanQueryByRandomIntWithMinMax()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
var idMin = db.Orders.Min(o => o.OrderId);
RunTest(
isSupported,
spy =>
{
var random = new Random();
// Dodge a Firebird driver limitation by putting the constants before the order id.
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
// knowing the type of the condition. For some reasons the driver considers the casting should not be
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
// Firebird complain, so moving the constants on the left side have been put before the order id, in
// order for these constants to be casted by the driver.
var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10));
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

[Test]
public void CanSelectRandomIntWithMinMax()
{
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
RunTest(
isSupported,
spy =>
{
var random = new Random();
var x =
db
.Orders.Select(o => new { id = o.OrderId, r = random.Next(1, 11) })
.OrderBy(o => o.id).ToList();
Assert.That(x, Has.Count.GreaterThan(0));
var randomValues = x.Select(o => o.r).Distinct().ToArray();
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(1).And.LessThan(11).And.TypeOf<int>());
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
{
// Naïve randomness check
Assert.That(
randomValues,
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
"Generated values do not seem very random");
}
// Next requires support of both floor and rand
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
});
}

private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
{
if (!IsFunctionSupported(functionName))
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Dialect/DB2Dialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public DB2Dialect()
RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double));
RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double));
RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double));
RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double));
RegisterFunction("sin", new StandardSQLFunction("sin", NHibernateUtil.Double));
RegisterFunction("soundex", new StandardSQLFunction("soundex", NHibernateUtil.String));
RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double));
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Dialect/FirebirdDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ private void RegisterMathematicalFunctions()
RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double));
RegisterFunction("pi", new NoArgSQLFunction("pi", NHibernateUtil.Double));
RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double));
RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double));
RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32));
RegisterFunction("sqtr", new StandardSQLFunction("sqtr", NHibernateUtil.Double));
RegisterFunction("trunc", new StandardSQLFunction("trunc"));
Expand Down
Loading

0 comments on commit 3a8a1ba

Please sign in to comment.