Skip to content

Commit 4c3fe84

Browse files
Support evaluation of Random.Next and NextDouble on db side
Fixes #959, along with two previous commits
1 parent 913e275 commit 4c3fe84

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
@@ -271,6 +271,223 @@ public void CanSelectNewGuid()
271271
});
272272
}
273273

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