Skip to content

Commit f07db92

Browse files
committed
Fix conditional expressions not being translated into hql
1 parent 656a88b commit f07db92

File tree

4 files changed

+178
-6
lines changed

4 files changed

+178
-6
lines changed

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,62 @@ public async Task CanSelectWithAnySubQueryAsync()
270270
Assert.AreEqual(1, list.Count(t => !t.HasEntries));
271271
}
272272

273+
[Test]
274+
public async Task CanSelectConditionalAsync()
275+
{
276+
using (var sqlLog = new SqlLogSpy())
277+
{
278+
var q = await (db.Orders.Where(o => o.Customer.CustomerId == "test")
279+
.Select(o => o.ShippedTo.Contains("test") ? o.ShippedTo : o.Customer.CompanyName)
280+
.OrderBy(o => o)
281+
.Distinct()
282+
.ToListAsync());
283+
284+
Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "case"), Is.EqualTo(2));
285+
}
286+
287+
using (var sqlLog = new SqlLogSpy())
288+
{
289+
var q = await (db.Orders.Where(o => o.Customer.CustomerId == "test")
290+
.Select(o => o.OrderDate.HasValue ? o.OrderDate : o.ShippingDate)
291+
.FirstOrDefaultAsync());
292+
293+
Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "case"), Is.EqualTo(1));
294+
}
295+
296+
using (var sqlLog = new SqlLogSpy())
297+
{
298+
var q = await (db.Orders.Where(o => o.Customer.CustomerId == "test")
299+
.Select(o => new
300+
{
301+
Value = o.OrderDate.HasValue
302+
? o.Customer.CompanyName
303+
: (o.ShippingDate.HasValue
304+
? o.Shipper.CompanyName
305+
: o.ShippedTo)
306+
})
307+
.FirstOrDefaultAsync());
308+
309+
var log = sqlLog.GetWholeLog();
310+
Assert.That(FindAllOccurrences(log, "as col"), Is.EqualTo(1));
311+
}
312+
}
313+
314+
[Test]
315+
public async Task CanSelectConditionalSubQueryAsync()
316+
{
317+
var orders = await (db.Customers
318+
.Select(c => new
319+
{
320+
Date = db.Orders.Where(o => o.Customer.CustomerId == c.CustomerId)
321+
.Select(o => o.OrderDate.HasValue ? o.OrderDate : o.ShippingDate)
322+
.First()
323+
})
324+
.ToListAsync());
325+
326+
Assert.That(orders, Has.Count.GreaterThan(0));
327+
}
328+
273329
[Test, KnownBug("NH-3045")]
274330
public async Task CanSelectFirstElementFromChildCollectionAsync()
275331
{
@@ -458,5 +514,20 @@ public class Wrapper<T>
458514
public T item;
459515
public string message;
460516
}
517+
518+
private int FindAllOccurrences(string source, string substring)
519+
{
520+
if (source == null)
521+
{
522+
return 0;
523+
}
524+
int n = 0, count = 0;
525+
while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
526+
{
527+
n += substring.Length;
528+
++count;
529+
}
530+
return count;
531+
}
461532
}
462533
}

src/NHibernate.Test/Linq/SelectionTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,62 @@ public void CanSelectWithAggregateSubQuery()
299299
Assert.AreEqual(4, timesheets[2].EntryCount);
300300
}
301301

302+
[Test]
303+
public void CanSelectConditional()
304+
{
305+
using (var sqlLog = new SqlLogSpy())
306+
{
307+
var q = db.Orders.Where(o => o.Customer.CustomerId == "test")
308+
.Select(o => o.ShippedTo.Contains("test") ? o.ShippedTo : o.Customer.CompanyName)
309+
.OrderBy(o => o)
310+
.Distinct()
311+
.ToList();
312+
313+
Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "case"), Is.EqualTo(2));
314+
}
315+
316+
using (var sqlLog = new SqlLogSpy())
317+
{
318+
var q = db.Orders.Where(o => o.Customer.CustomerId == "test")
319+
.Select(o => o.OrderDate.HasValue ? o.OrderDate : o.ShippingDate)
320+
.FirstOrDefault();
321+
322+
Assert.That(FindAllOccurrences(sqlLog.GetWholeLog(), "case"), Is.EqualTo(1));
323+
}
324+
325+
using (var sqlLog = new SqlLogSpy())
326+
{
327+
var q = db.Orders.Where(o => o.Customer.CustomerId == "test")
328+
.Select(o => new
329+
{
330+
Value = o.OrderDate.HasValue
331+
? o.Customer.CompanyName
332+
: (o.ShippingDate.HasValue
333+
? o.Shipper.CompanyName
334+
: o.ShippedTo)
335+
})
336+
.FirstOrDefault();
337+
338+
var log = sqlLog.GetWholeLog();
339+
Assert.That(FindAllOccurrences(log, "as col"), Is.EqualTo(1));
340+
}
341+
}
342+
343+
[Test]
344+
public void CanSelectConditionalSubQuery()
345+
{
346+
var orders = db.Customers
347+
.Select(c => new
348+
{
349+
Date = db.Orders.Where(o => o.Customer.CustomerId == c.CustomerId)
350+
.Select(o => o.OrderDate.HasValue ? o.OrderDate : o.ShippingDate)
351+
.First()
352+
})
353+
.ToList();
354+
355+
Assert.That(orders, Has.Count.GreaterThan(0));
356+
}
357+
302358
[Test, KnownBug("NH-3045")]
303359
public void CanSelectFirstElementFromChildCollection()
304360
{
@@ -497,5 +553,20 @@ public class Wrapper<T>
497553
public T item;
498554
public string message;
499555
}
556+
557+
private int FindAllOccurrences(string source, string substring)
558+
{
559+
if (source == null)
560+
{
561+
return 0;
562+
}
563+
int n = 0, count = 0;
564+
while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
565+
{
566+
n += substring.Length;
567+
++count;
568+
}
569+
return count;
570+
}
500571
}
501572
}

src/NHibernate/Hql/Ast/HqlTreeNode.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,30 @@ internal HqlIdent(IASTFactory factory, System.Type type)
257257
throw new NotSupportedException(string.Format("Don't currently support idents of type {0}", type.Name));
258258
}
259259
}
260+
261+
internal static bool SupportsType(System.Type type)
262+
{
263+
type = type.UnwrapIfNullable();
264+
switch (System.Type.GetTypeCode(type))
265+
{
266+
case TypeCode.Boolean:
267+
case TypeCode.Int16:
268+
case TypeCode.Int32:
269+
case TypeCode.Int64:
270+
case TypeCode.Decimal:
271+
case TypeCode.Single:
272+
case TypeCode.DateTime:
273+
case TypeCode.String:
274+
case TypeCode.Double:
275+
return true;
276+
default:
277+
return new[]
278+
{
279+
typeof(Guid),
280+
typeof(DateTimeOffset)
281+
}.Contains(type);
282+
}
283+
}
260284
}
261285

262286
public class HqlRange : HqlStatement

src/NHibernate/Linq/Visitors/SelectClauseNominator.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq.Expressions;
4+
using NHibernate.Hql.Ast;
45
using NHibernate.Linq.Functions;
56
using NHibernate.Linq.Expressions;
67
using NHibernate.Util;
@@ -15,6 +16,7 @@ namespace NHibernate.Linq.Visitors
1516
class SelectClauseHqlNominator : RelinqExpressionVisitor
1617
{
1718
private readonly ILinqToHqlGeneratorsRegistry _functionRegistry;
19+
private readonly VisitorParameters _parameters;
1820

1921
/// <summary>
2022
/// The expression parts that can be converted to pure HQL.
@@ -34,6 +36,7 @@ class SelectClauseHqlNominator : RelinqExpressionVisitor
3436

3537
public SelectClauseHqlNominator(VisitorParameters parameters)
3638
{
39+
_parameters = parameters;
3740
_functionRegistry = parameters.SessionFactory.Settings.LinqToHqlGeneratorsRegistry;
3841
}
3942

@@ -145,7 +148,7 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj
145148
}
146149

147150
// Constants will only be evaluated in HQL if they're inside a method call
148-
if (expression.NodeType == ExpressionType.Constant)
151+
if (expression is ConstantExpression constantExpression && _parameters.ConstantToParameterMap.TryGetValue(constantExpression, out _))
149152
{
150153
return projectConstantsInHql;
151154
}
@@ -156,14 +159,17 @@ private bool CanBeEvaluatedInHqlSelectStatement(Expression expression, bool proj
156159
return IsRegisteredFunction(expression);
157160
}
158161

159-
if (expression.NodeType == ExpressionType.Conditional)
162+
if (expression is ConditionalExpression conditionalExpression)
160163
{
161164
// Theoretically, any conditional that returns a CAST-able primitive should be constructable in HQL.
162165
// The type needs to be CAST-able because HQL wraps the CASE clause in a CAST and only supports
163166
// certain types (as defined by the HqlIdent constructor that takes a System.Type as the second argument).
164-
// However, this may still not cover all cases, so to limit the nomination of conditional expressions,
165-
// we will only consider those which are already getting constants projected into them.
166-
return projectConstantsInHql;
167+
return
168+
HqlIdent.SupportsType(conditionalExpression.IfFalse.Type) &&
169+
HqlCandidates.Contains(conditionalExpression.IfFalse) &&
170+
HqlIdent.SupportsType(conditionalExpression.IfTrue.Type) &&
171+
HqlCandidates.Contains(conditionalExpression.IfTrue) &&
172+
HqlCandidates.Contains(conditionalExpression.Test);
167173
}
168174

169175
// Assume all is good
@@ -175,4 +181,4 @@ private static bool CanBeEvaluatedInHqlStatementShortcut(Expression expression)
175181
return expression is NhCountExpression;
176182
}
177183
}
178-
}
184+
}

0 commit comments

Comments
 (0)