Skip to content

Commit 3a8a1ba

Browse files
Support evaluation of Random.Next and NextDouble on db side
Fixes #959, along with two previous commits
1 parent 58272eb commit 3a8a1ba

18 files changed

+572
-6
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using NHibernate.Cfg;
15+
using NHibernate.SqlTypes;
16+
using NUnit.Framework;
17+
using Environment = NHibernate.Cfg.Environment;
18+
using NHibernate.Linq;
19+
20+
namespace NHibernate.Test.Linq
21+
{
22+
using System.Threading.Tasks;
23+
[TestFixture(false, false)]
24+
[TestFixture(true, false)]
25+
[TestFixture(false, true)]
26+
public class PreEvaluationTestsAsync : LinqTestCase
27+
{
28+
private readonly bool LegacyPreEvaluation;
29+
private readonly bool FallbackOnPreEvaluation;
30+
31+
public PreEvaluationTestsAsync(bool legacy, bool fallback)
32+
{
33+
LegacyPreEvaluation = legacy;
34+
FallbackOnPreEvaluation = fallback;
35+
}
36+
37+
protected override void Configure(Configuration configuration)
38+
{
39+
base.Configure(configuration);
40+
41+
configuration.SetProperty(Environment.FormatSql, "false");
42+
configuration.SetProperty(Environment.LinqToHqlLegacyPreEvaluation, LegacyPreEvaluation.ToString());
43+
configuration.SetProperty(Environment.LinqToHqlFallbackOnPreEvaluation, FallbackOnPreEvaluation.ToString());
44+
}
45+
46+
private void RunTest(bool isSupported, Action<SqlLogSpy> test)
47+
{
48+
using (var spy = new SqlLogSpy())
49+
{
50+
try
51+
{
52+
test(spy);
53+
}
54+
catch (QueryException)
55+
{
56+
if (!isSupported && !FallbackOnPreEvaluation)
57+
// Expected failure
58+
return;
59+
throw;
60+
}
61+
}
62+
63+
if (!isSupported && !FallbackOnPreEvaluation)
64+
Assert.Fail("The test should have thrown a QueryException, but has not thrown anything");
65+
}
66+
67+
[Test]
68+
public async Task CanQueryByRandomIntAsync()
69+
{
70+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
71+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
72+
RunTest(
73+
isSupported,
74+
spy =>
75+
{
76+
var random = new Random();
77+
// Dodge a Firebird driver limitation by putting the constants before the order id.
78+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
79+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
80+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
81+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
82+
// order for these constants to be casted by the driver.
83+
var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next());
84+
85+
Assert.That(x, Is.GreaterThan(0));
86+
// Next requires support of both floor and rand
87+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
88+
});
89+
}
90+
91+
[Test]
92+
public async Task CanQueryByRandomIntWithMaxAsync()
93+
{
94+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
95+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
96+
RunTest(
97+
isSupported,
98+
spy =>
99+
{
100+
var random = new Random();
101+
// Dodge a Firebird driver limitation by putting the constants before the order id.
102+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
103+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
104+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
105+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
106+
// order for these constants to be casted by the driver.
107+
var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10));
108+
109+
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
110+
// Next requires support of both floor and rand
111+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
112+
});
113+
}
114+
115+
[Test]
116+
public async Task CanQueryByRandomIntWithMinMaxAsync()
117+
{
118+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
119+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
120+
RunTest(
121+
isSupported,
122+
spy =>
123+
{
124+
var random = new Random();
125+
// Dodge a Firebird driver limitation by putting the constants before the order id.
126+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
127+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
128+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
129+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
130+
// order for these constants to be casted by the driver.
131+
var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10));
132+
133+
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
134+
// Next requires support of both floor and rand
135+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
136+
});
137+
}
138+
139+
private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
140+
{
141+
if (!IsFunctionSupported(functionName))
142+
Assert.Inconclusive($"{functionName} is not supported by the dialect");
143+
144+
var function = Dialect.Functions[functionName].Render(new List<object>(), Sfi).ToString();
145+
146+
if (LegacyPreEvaluation)
147+
Assert.That(spy.GetWholeLog(), Does.Not.Contain(function));
148+
else
149+
Assert.That(spy.GetWholeLog(), Does.Contain(function));
150+
}
151+
}
152+
}

src/NHibernate.Test/Linq/PreEvaluationTests.cs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,223 @@ public void CanSelectNewGuid()
291291
});
292292
}
293293

294+
[Test]
295+
public void CanQueryByRandomDouble()
296+
{
297+
var isSupported = IsFunctionSupported("random");
298+
RunTest(
299+
isSupported,
300+
spy =>
301+
{
302+
var random = new Random();
303+
var x = db.Orders.Count(o => o.OrderId > random.NextDouble());
304+
305+
Assert.That(x, Is.GreaterThan(0));
306+
AssertFunctionInSql("random", spy);
307+
});
308+
}
309+
310+
[Test]
311+
public void CanSelectRandomDouble()
312+
{
313+
var isSupported = IsFunctionSupported("random");
314+
RunTest(
315+
isSupported,
316+
spy =>
317+
{
318+
var random = new Random();
319+
var x =
320+
db
321+
.Orders.Select(o => new { id = o.OrderId, r = random.NextDouble() })
322+
.OrderBy(o => o.id).ToList();
323+
324+
Assert.That(x, Has.Count.GreaterThan(0));
325+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
326+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(1));
327+
328+
if (!LegacyPreEvaluation && IsFunctionSupported("random"))
329+
{
330+
// Naïve randomness check
331+
Assert.That(
332+
randomValues,
333+
Has.Length.GreaterThan(x.Count / 2),
334+
"Generated values do not seem very random");
335+
}
336+
337+
AssertFunctionInSql("random", spy);
338+
});
339+
}
340+
341+
[Test]
342+
public void CanQueryByRandomInt()
343+
{
344+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
345+
var idMin = db.Orders.Min(o => o.OrderId);
346+
RunTest(
347+
isSupported,
348+
spy =>
349+
{
350+
var random = new Random();
351+
// Dodge a Firebird driver limitation by putting the constants before the order id.
352+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
353+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
354+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
355+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
356+
// order for these constants to be casted by the driver.
357+
var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next());
358+
359+
Assert.That(x, Is.GreaterThan(0));
360+
// Next requires support of both floor and rand
361+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
362+
});
363+
}
364+
365+
[Test]
366+
public void CanSelectRandomInt()
367+
{
368+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
369+
RunTest(
370+
isSupported,
371+
spy =>
372+
{
373+
var random = new Random();
374+
var x =
375+
db
376+
.Orders.Select(o => new { id = o.OrderId, r = random.Next() })
377+
.OrderBy(o => o.id).ToList();
378+
379+
Assert.That(x, Has.Count.GreaterThan(0));
380+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
381+
Assert.That(
382+
randomValues,
383+
Has.All.GreaterThanOrEqualTo(0).And.LessThan(int.MaxValue).And.TypeOf<int>());
384+
385+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
386+
{
387+
// Naïve randomness check
388+
Assert.That(
389+
randomValues,
390+
Has.Length.GreaterThan(x.Count / 2),
391+
"Generated values do not seem very random");
392+
}
393+
394+
// Next requires support of both floor and rand
395+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
396+
});
397+
}
398+
399+
[Test]
400+
public void CanQueryByRandomIntWithMax()
401+
{
402+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
403+
var idMin = db.Orders.Min(o => o.OrderId);
404+
RunTest(
405+
isSupported,
406+
spy =>
407+
{
408+
var random = new Random();
409+
// Dodge a Firebird driver limitation by putting the constants before the order id.
410+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
411+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
412+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
413+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
414+
// order for these constants to be casted by the driver.
415+
var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10));
416+
417+
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
418+
// Next requires support of both floor and rand
419+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
420+
});
421+
}
422+
423+
[Test]
424+
public void CanSelectRandomIntWithMax()
425+
{
426+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
427+
RunTest(
428+
isSupported,
429+
spy =>
430+
{
431+
var random = new Random();
432+
var x =
433+
db
434+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(10) })
435+
.OrderBy(o => o.id).ToList();
436+
437+
Assert.That(x, Has.Count.GreaterThan(0));
438+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
439+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(10).And.TypeOf<int>());
440+
441+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
442+
{
443+
// Naïve randomness check
444+
Assert.That(
445+
randomValues,
446+
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
447+
"Generated values do not seem very random");
448+
}
449+
450+
// Next requires support of both floor and rand
451+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
452+
});
453+
}
454+
455+
[Test]
456+
public void CanQueryByRandomIntWithMinMax()
457+
{
458+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
459+
var idMin = db.Orders.Min(o => o.OrderId);
460+
RunTest(
461+
isSupported,
462+
spy =>
463+
{
464+
var random = new Random();
465+
// Dodge a Firebird driver limitation by putting the constants before the order id.
466+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
467+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
468+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
469+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
470+
// order for these constants to be casted by the driver.
471+
var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10));
472+
473+
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
474+
// Next requires support of both floor and rand
475+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
476+
});
477+
}
478+
479+
[Test]
480+
public void CanSelectRandomIntWithMinMax()
481+
{
482+
var isSupported = IsFunctionSupported("random") && IsFunctionSupported("floor");
483+
RunTest(
484+
isSupported,
485+
spy =>
486+
{
487+
var random = new Random();
488+
var x =
489+
db
490+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(1, 11) })
491+
.OrderBy(o => o.id).ToList();
492+
493+
Assert.That(x, Has.Count.GreaterThan(0));
494+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
495+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(1).And.LessThan(11).And.TypeOf<int>());
496+
497+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
498+
{
499+
// Naïve randomness check
500+
Assert.That(
501+
randomValues,
502+
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
503+
"Generated values do not seem very random");
504+
}
505+
506+
// Next requires support of both floor and rand
507+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
508+
});
509+
}
510+
294511
private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
295512
{
296513
if (!IsFunctionSupported(functionName))

src/NHibernate/Dialect/DB2Dialect.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public DB2Dialect()
8080
RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double));
8181
RegisterFunction("radians", new StandardSQLFunction("radians", NHibernateUtil.Double));
8282
RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double));
83+
RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double));
8384
RegisterFunction("sin", new StandardSQLFunction("sin", NHibernateUtil.Double));
8485
RegisterFunction("soundex", new StandardSQLFunction("soundex", NHibernateUtil.String));
8586
RegisterFunction("sqrt", new StandardSQLFunction("sqrt", NHibernateUtil.Double));

src/NHibernate/Dialect/FirebirdDialect.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ private void RegisterMathematicalFunctions()
465465
RegisterFunction("log10", new StandardSQLFunction("log10", NHibernateUtil.Double));
466466
RegisterFunction("pi", new NoArgSQLFunction("pi", NHibernateUtil.Double));
467467
RegisterFunction("rand", new NoArgSQLFunction("rand", NHibernateUtil.Double));
468+
RegisterFunction("random", new NoArgSQLFunction("rand", NHibernateUtil.Double));
468469
RegisterFunction("sign", new StandardSQLFunction("sign", NHibernateUtil.Int32));
469470
RegisterFunction("sqtr", new StandardSQLFunction("sqtr", NHibernateUtil.Double));
470471
RegisterFunction("trunc", new StandardSQLFunction("trunc"));

0 commit comments

Comments
 (0)