Skip to content

Commit a911809

Browse files
Support evaluation of Random.Next and NextDouble on db side
Fixes #959, along with two previous commits
1 parent 6a06bed commit a911809

18 files changed

+557
-7
lines changed

src/NHibernate.Test/Async/Linq/PreEvaluationTests.cs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,197 @@ public async Task CanSelectNewGuidAsync()
217217
}
218218
}
219219

220+
[Test]
221+
public async Task CanQueryByRandomDoubleAsync()
222+
{
223+
using (var spy = new SqlLogSpy())
224+
{
225+
var random = new Random();
226+
var x = await (db.Orders.CountAsync(o => o.OrderId > random.NextDouble()));
227+
228+
Assert.That(x, Is.GreaterThan(0));
229+
AssertFunctionInSql("random", spy);
230+
}
231+
}
232+
233+
[Test]
234+
public async Task CanSelectRandomDoubleAsync()
235+
{
236+
using (var spy = new SqlLogSpy())
237+
{
238+
var random = new Random();
239+
var x =
240+
await (db
241+
.Orders.Select(o => new { id = o.OrderId, r = random.NextDouble() })
242+
.OrderBy(o => o.id).ToListAsync());
243+
244+
Assert.That(x, Has.Count.GreaterThan(0));
245+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
246+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(1));
247+
248+
if (!LegacyPreEvaluation && IsFunctionSupported("random"))
249+
{
250+
// Naïve randomness check
251+
Assert.That(
252+
randomValues,
253+
Has.Length.GreaterThan(x.Count / 2),
254+
"Generated values do not seem very random");
255+
}
256+
257+
AssertFunctionInSql("random", spy);
258+
}
259+
}
260+
261+
[Test]
262+
public async Task CanQueryByRandomIntAsync()
263+
{
264+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
265+
using (var spy = new SqlLogSpy())
266+
{
267+
var random = new Random();
268+
// Dodge a Firebird driver limitation by putting the constants before the order id.
269+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
270+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
271+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
272+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
273+
// order for these constants to be casted by the driver.
274+
var x = await (db.Orders.CountAsync(o => -idMin - 1 + o.OrderId < random.Next()));
275+
276+
Assert.That(x, Is.GreaterThan(0));
277+
// Next requires support of both floor and rand
278+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
279+
}
280+
}
281+
282+
[Test]
283+
public async Task CanSelectRandomIntAsync()
284+
{
285+
using (var spy = new SqlLogSpy())
286+
{
287+
var random = new Random();
288+
var x =
289+
await (db
290+
.Orders.Select(o => new { id = o.OrderId, r = random.Next() })
291+
.OrderBy(o => o.id).ToListAsync());
292+
293+
Assert.That(x, Has.Count.GreaterThan(0));
294+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
295+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(int.MaxValue).And.TypeOf<int>());
296+
297+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
298+
{
299+
// Naïve randomness check
300+
Assert.That(
301+
randomValues,
302+
Has.Length.GreaterThan(x.Count / 2),
303+
"Generated values do not seem very random");
304+
}
305+
306+
// Next requires support of both floor and rand
307+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
308+
}
309+
}
310+
311+
[Test]
312+
public async Task CanQueryByRandomIntWithMaxAsync()
313+
{
314+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
315+
using (var spy = new SqlLogSpy())
316+
{
317+
var random = new Random();
318+
// Dodge a Firebird driver limitation by putting the constants before the order id.
319+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
320+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
321+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
322+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
323+
// order for these constants to be casted by the driver.
324+
var x = await (db.Orders.CountAsync(o => -idMin + o.OrderId <= random.Next(10)));
325+
326+
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
327+
// Next requires support of both floor and rand
328+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
329+
}
330+
}
331+
332+
[Test]
333+
public async Task CanSelectRandomIntWithMaxAsync()
334+
{
335+
using (var spy = new SqlLogSpy())
336+
{
337+
var random = new Random();
338+
var x =
339+
await (db
340+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(10) })
341+
.OrderBy(o => o.id).ToListAsync());
342+
343+
Assert.That(x, Has.Count.GreaterThan(0));
344+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
345+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(10).And.TypeOf<int>());
346+
347+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
348+
{
349+
// Naïve randomness check
350+
Assert.That(
351+
randomValues,
352+
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
353+
"Generated values do not seem very random");
354+
}
355+
356+
// Next requires support of both floor and rand
357+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
358+
}
359+
}
360+
361+
[Test]
362+
public async Task CanQueryByRandomIntWithMinMaxAsync()
363+
{
364+
var idMin = await (db.Orders.MinAsync(o => o.OrderId));
365+
using (var spy = new SqlLogSpy())
366+
{
367+
var random = new Random();
368+
// Dodge a Firebird driver limitation by putting the constants before the order id.
369+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
370+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
371+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
372+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
373+
// order for these constants to be casted by the driver.
374+
var x = await (db.Orders.CountAsync(o => -idMin + o.OrderId < random.Next(1, 10)));
375+
376+
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
377+
// Next requires support of both floor and rand
378+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
379+
}
380+
}
381+
382+
[Test]
383+
public async Task CanSelectRandomIntWithMinMaxAsync()
384+
{
385+
using (var spy = new SqlLogSpy())
386+
{
387+
var random = new Random();
388+
var x =
389+
await (db
390+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(1, 11) })
391+
.OrderBy(o => o.id).ToListAsync());
392+
393+
Assert.That(x, Has.Count.GreaterThan(0));
394+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
395+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(1).And.LessThan(11).And.TypeOf<int>());
396+
397+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
398+
{
399+
// Naïve randomness check
400+
Assert.That(
401+
randomValues,
402+
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
403+
"Generated values do not seem very random");
404+
}
405+
406+
// Next requires support of both floor and rand
407+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
408+
}
409+
}
410+
220411
private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
221412
{
222413
if (!IsFunctionSupported(functionName))

src/NHibernate.Test/Linq/PreEvaluationTests.cs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,197 @@ public void CanSelectNewGuid()
205205
}
206206
}
207207

208+
[Test]
209+
public void CanQueryByRandomDouble()
210+
{
211+
using (var spy = new SqlLogSpy())
212+
{
213+
var random = new Random();
214+
var x = db.Orders.Count(o => o.OrderId > random.NextDouble());
215+
216+
Assert.That(x, Is.GreaterThan(0));
217+
AssertFunctionInSql("random", spy);
218+
}
219+
}
220+
221+
[Test]
222+
public void CanSelectRandomDouble()
223+
{
224+
using (var spy = new SqlLogSpy())
225+
{
226+
var random = new Random();
227+
var x =
228+
db
229+
.Orders.Select(o => new { id = o.OrderId, r = random.NextDouble() })
230+
.OrderBy(o => o.id).ToList();
231+
232+
Assert.That(x, Has.Count.GreaterThan(0));
233+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
234+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(1));
235+
236+
if (!LegacyPreEvaluation && IsFunctionSupported("random"))
237+
{
238+
// Naïve randomness check
239+
Assert.That(
240+
randomValues,
241+
Has.Length.GreaterThan(x.Count / 2),
242+
"Generated values do not seem very random");
243+
}
244+
245+
AssertFunctionInSql("random", spy);
246+
}
247+
}
248+
249+
[Test]
250+
public void CanQueryByRandomInt()
251+
{
252+
var idMin = db.Orders.Min(o => o.OrderId);
253+
using (var spy = new SqlLogSpy())
254+
{
255+
var random = new Random();
256+
// Dodge a Firebird driver limitation by putting the constants before the order id.
257+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
258+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
259+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
260+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
261+
// order for these constants to be casted by the driver.
262+
var x = db.Orders.Count(o => -idMin - 1 + o.OrderId < random.Next());
263+
264+
Assert.That(x, Is.GreaterThan(0));
265+
// Next requires support of both floor and rand
266+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
267+
}
268+
}
269+
270+
[Test]
271+
public void CanSelectRandomInt()
272+
{
273+
using (var spy = new SqlLogSpy())
274+
{
275+
var random = new Random();
276+
var x =
277+
db
278+
.Orders.Select(o => new { id = o.OrderId, r = random.Next() })
279+
.OrderBy(o => o.id).ToList();
280+
281+
Assert.That(x, Has.Count.GreaterThan(0));
282+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
283+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(int.MaxValue).And.TypeOf<int>());
284+
285+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
286+
{
287+
// Naïve randomness check
288+
Assert.That(
289+
randomValues,
290+
Has.Length.GreaterThan(x.Count / 2),
291+
"Generated values do not seem very random");
292+
}
293+
294+
// Next requires support of both floor and rand
295+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
296+
}
297+
}
298+
299+
[Test]
300+
public void CanQueryByRandomIntWithMax()
301+
{
302+
var idMin = db.Orders.Min(o => o.OrderId);
303+
using (var spy = new SqlLogSpy())
304+
{
305+
var random = new Random();
306+
// Dodge a Firebird driver limitation by putting the constants before the order id.
307+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
308+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
309+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
310+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
311+
// order for these constants to be casted by the driver.
312+
var x = db.Orders.Count(o => -idMin + o.OrderId <= random.Next(10));
313+
314+
Assert.That(x, Is.GreaterThan(0).And.LessThan(11));
315+
// Next requires support of both floor and rand
316+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
317+
}
318+
}
319+
320+
[Test]
321+
public void CanSelectRandomIntWithMax()
322+
{
323+
using (var spy = new SqlLogSpy())
324+
{
325+
var random = new Random();
326+
var x =
327+
db
328+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(10) })
329+
.OrderBy(o => o.id).ToList();
330+
331+
Assert.That(x, Has.Count.GreaterThan(0));
332+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
333+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(0).And.LessThan(10).And.TypeOf<int>());
334+
335+
if (!LegacyPreEvaluation && IsFunctionSupported("random") && IsFunctionSupported("floor"))
336+
{
337+
// Naïve randomness check
338+
Assert.That(
339+
randomValues,
340+
Has.Length.GreaterThan(Math.Min(10, x.Count) / 2),
341+
"Generated values do not seem very random");
342+
}
343+
344+
// Next requires support of both floor and rand
345+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
346+
}
347+
}
348+
349+
[Test]
350+
public void CanQueryByRandomIntWithMinMax()
351+
{
352+
var idMin = db.Orders.Min(o => o.OrderId);
353+
using (var spy = new SqlLogSpy())
354+
{
355+
var random = new Random();
356+
// Dodge a Firebird driver limitation by putting the constants before the order id.
357+
// This driver cast parameters to their types in some cases for avoiding Firebird complaining of not
358+
// knowing the type of the condition. For some reasons the driver considers the casting should not be
359+
// done next to the conditional operator. Having the cast only on one side is enough for avoiding
360+
// Firebird complain, so moving the constants on the left side have been put before the order id, in
361+
// order for these constants to be casted by the driver.
362+
var x = db.Orders.Count(o => -idMin + o.OrderId < random.Next(1, 10));
363+
364+
Assert.That(x, Is.GreaterThan(0).And.LessThan(10));
365+
// Next requires support of both floor and rand
366+
AssertFunctionInSql(IsFunctionSupported("floor") ? "random" : "floor", spy);
367+
}
368+
}
369+
370+
[Test]
371+
public void CanSelectRandomIntWithMinMax()
372+
{
373+
using (var spy = new SqlLogSpy())
374+
{
375+
var random = new Random();
376+
var x =
377+
db
378+
.Orders.Select(o => new { id = o.OrderId, r = random.Next(1, 11) })
379+
.OrderBy(o => o.id).ToList();
380+
381+
Assert.That(x, Has.Count.GreaterThan(0));
382+
var randomValues = x.Select(o => o.r).Distinct().ToArray();
383+
Assert.That(randomValues, Has.All.GreaterThanOrEqualTo(1).And.LessThan(11).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(Math.Min(10, 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+
208399
private void AssertFunctionInSql(string functionName, SqlLogSpy spy)
209400
{
210401
if (!IsFunctionSupported(functionName))

0 commit comments

Comments
 (0)