Skip to content

Commit 722fb81

Browse files
committed
Added support for implicit boolean condition in where statement.
1 parent 79ed2ac commit 722fb81

16 files changed

+132
-27
lines changed

Lib/CSQLQueryExpress/CSQLQueryExpress.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<Title>CSQLQueryExpress</Title>
6-
<Version>1.3.1</Version>
6+
<Version>1.3.3</Version>
77
<Description>A simple c# library to compile TSQL queries</Description>
88
<PackageProjectUrl></PackageProjectUrl>
99
<PackageReadmeFile>README.md</PackageReadmeFile>

Lib/CSQLQueryExpress/Compiler/ISQLQueryTranslator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace CSQLQueryExpress
55
{
66
public interface ISQLQueryTranslator
77
{
8-
string Translate(Expression expression);
8+
string Translate(Expression expression, SQLQueryFragmentType fragmentType);
99

1010
string MakeParameter(object value);
1111

Lib/CSQLQueryExpress/Compiler/SQLQueryTranslator.cs

+81-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal class SQLQueryTranslator : ExpressionVisitor, ISQLQueryTranslator
1616
private readonly ISQLQueryParametersBuilder _parametersBuilder;
1717
private readonly ISQLQueryTableNameResolver _aliasBuilder;
1818
private readonly StringBuilder _queryBuilder = new StringBuilder();
19+
private SQLQueryFragmentType _fragmentType;
1920

2021
public SQLQueryTranslator(
2122
ISQLQueryParametersBuilder parametersBuilder,
@@ -25,8 +26,10 @@ public SQLQueryTranslator(
2526
_aliasBuilder = aliasBuilder;
2627
}
2728

28-
public string Translate(Expression expression)
29+
public string Translate(Expression expression, SQLQueryFragmentType fragmentType)
2930
{
31+
_fragmentType = fragmentType;
32+
3033
_queryBuilder.Clear();
3134

3235
base.Visit(expression);
@@ -63,11 +66,20 @@ public string GetColumnsWithoutTableAlias(string column)
6366
return column.Replace(tableAlias, string.Empty);
6467
}
6568

69+
private bool _memberExpressionFromBinaryExpression;
70+
6671
protected override Expression VisitBinary(BinaryExpression node)
6772
{
6873
_queryBuilder.Append("(");
74+
75+
_memberExpressionFromBinaryExpression = node.Left.NodeType == ExpressionType.MemberAccess &&
76+
node.NodeType != ExpressionType.And && node.NodeType != ExpressionType.AndAlso &&
77+
node.NodeType != ExpressionType.Or && node.NodeType != ExpressionType.OrElse;
78+
6979
this.Visit(node.Left);
7080

81+
_memberExpressionFromBinaryExpression = false;
82+
7183
switch (node.NodeType)
7284
{
7385
case ExpressionType.Add:
@@ -1229,6 +1241,10 @@ private Expression VisitQueryAssignmentMethodCall(MethodCallExpression node)
12291241
throw new NotSupportedException(string.Format("The method '{0}' is not supported.", node.Method.Name));
12301242
}
12311243

1244+
private readonly Type BoolType = typeof(bool);
1245+
private readonly Type NullBoolType = typeof(bool?);
1246+
private readonly IList<Type> BooleanTypes = new List<Type> { typeof(bool), typeof(bool?) };
1247+
12321248
protected override Expression VisitMember(MemberExpression node)
12331249
{
12341250
if (node.Expression != null &&
@@ -1247,11 +1263,43 @@ protected override Expression VisitMember(MemberExpression node)
12471263
var member = node.Expression.NodeType == ExpressionType.Parameter
12481264
? node.Member
12491265
: ((MemberExpression)node.Expression).Member;
1250-
1266+
12511267
var tableName = _aliasBuilder.ResolveTableName(type);
12521268
var columnName = _aliasBuilder.ResolveColumnName(type, member);
12531269

1254-
_queryBuilder.Append($"{tableName.TableAlias}.{columnName}");
1270+
if (_fragmentType == SQLQueryFragmentType.Where &&
1271+
!_memberExpressionFromBinaryExpression &&
1272+
BooleanTypes.Contains(node.Expression.NodeType == ExpressionType.Parameter ? node.Type : node.Expression.Type))
1273+
{
1274+
ExpressionType typeExpression;
1275+
Expression valueExpression;
1276+
if (node.Member.Name == nameof(Nullable<bool>.HasValue))
1277+
{
1278+
typeExpression = ExpressionType.NotEqual;
1279+
valueExpression = Expression.Constant(null);
1280+
}
1281+
else
1282+
{
1283+
typeExpression = ExpressionType.Equal;
1284+
valueExpression = (node.Expression.NodeType == ExpressionType.Parameter
1285+
? node.Type
1286+
: node.Expression.Type) == NullBoolType
1287+
? (Expression)Expression.Convert(Expression.Constant(true), NullBoolType)
1288+
: Expression.Constant(true);
1289+
}
1290+
1291+
Visit(Expression.MakeBinary(
1292+
typeExpression,
1293+
node.Expression.NodeType == ExpressionType.Parameter
1294+
? node
1295+
: node.Expression,
1296+
valueExpression));
1297+
}
1298+
else
1299+
{
1300+
_queryBuilder.Append($"{tableName.TableAlias}.{columnName}");
1301+
}
1302+
12551303
return node;
12561304
}
12571305
else if (node.Expression != null && node.Expression.NodeType == ExpressionType.Constant)
@@ -1549,6 +1597,36 @@ protected override Expression VisitUnary(UnaryExpression node)
15491597

15501598
return node;
15511599
}
1600+
else if (_fragmentType == SQLQueryFragmentType.Where &&
1601+
node.NodeType == ExpressionType.Not &&
1602+
node.Operand.NodeType == ExpressionType.MemberAccess &&
1603+
BooleanTypes.Contains(((MemberExpression)node.Operand).Expression.NodeType == ExpressionType.Parameter
1604+
? node.Operand.Type :
1605+
((MemberExpression)node.Operand).Expression.Type))
1606+
{
1607+
Expression valueExpression;
1608+
if (((MemberExpression)node.Operand).Member.Name == nameof(Nullable<bool>.HasValue))
1609+
{
1610+
valueExpression = Expression.Constant(null);
1611+
}
1612+
else
1613+
{
1614+
valueExpression = (((MemberExpression)node.Operand).Expression.NodeType == ExpressionType.Parameter
1615+
? node.Operand.Type
1616+
: ((MemberExpression)node.Operand).Expression.Type) == NullBoolType
1617+
? (Expression)Expression.Convert(Expression.Constant(false), NullBoolType)
1618+
: Expression.Constant(false);
1619+
}
1620+
1621+
Visit(Expression.MakeBinary(
1622+
ExpressionType.Equal,
1623+
((MemberExpression)node.Operand).Expression.NodeType == ExpressionType.Parameter
1624+
? node.Operand
1625+
: ((MemberExpression)node.Operand).Expression,
1626+
valueExpression));
1627+
1628+
return node;
1629+
}
15521630

15531631
return base.VisitUnary(node);
15541632
}

Lib/CSQLQueryExpress/Fragments/SQLQueryForXml.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
4646
{
4747
if (_forXml != null)
4848
{
49-
return $"FOR XML {expressionTranslator.Translate(_forXml)}";
49+
return $"FOR XML {expressionTranslator.Translate(_forXml, FragmentType)}";
5050
}
5151

5252
return "FOR XML";

Lib/CSQLQueryExpress/Fragments/SQLQueryFrom.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
189189
{
190190
if (_fragments.Any(f => f.FragmentType == SQLQueryFragmentType.Select || f.FragmentType == SQLQueryFragmentType.SelectCte))
191191
{
192-
fromBuilder.Append($"FROM {expressionTranslator.Translate(Expression.Constant(typeof(T)))}");
192+
fromBuilder.Append($"FROM {expressionTranslator.Translate(Expression.Constant(typeof(T)), FragmentType)}");
193193
}
194194
else
195195
{

Lib/CSQLQueryExpress/Fragments/SQLQueryGroup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public SQLQuerySelect SelectAll()
2828

2929
public string Translate(ISQLQueryTranslator expressionTranslator)
3030
{
31-
return $"GROUP BY {string.Join(", ", _group.Select(g => expressionTranslator.Translate(g)))}";
31+
return $"GROUP BY {string.Join(", ", _group.Select(g => expressionTranslator.Translate(g, FragmentType)))}";
3232
}
3333
}
3434

Lib/CSQLQueryExpress/Fragments/SQLQueryGroupHaving.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public SQLQuerySelect SelectAll()
2828

2929
public string Translate(ISQLQueryTranslator expressionTranslator)
3030
{
31-
return $"HAVING {string.Join(", ", _having.Select(h => expressionTranslator.Translate(h)))}";
31+
return $"HAVING {string.Join(", ", _having.Select(h => expressionTranslator.Translate(h, FragmentType)))}";
3232
}
3333
}
3434

Lib/CSQLQueryExpress/Fragments/SQLQueryInsert.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
121121
}
122122
else
123123
{
124-
insertBuilder.Append($"{Environment.NewLine}({string.Join(", ", _select.Select.Select(u => expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(u))))})");
124+
insertBuilder.Append($"{Environment.NewLine}({string.Join(", ", _select.Select.Select(u => expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(u, FragmentType))))})");
125125
}
126126

127127
return insertBuilder.ToString();
@@ -211,7 +211,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
211211
{
212212
selectBuilder.Append($"SELECT ");
213213

214-
selectBuilder.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select.Select(s => expressionTranslator.Translate(s)))} {Environment.NewLine}");
214+
selectBuilder.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select.Select(s => expressionTranslator.Translate(s, FragmentType)))} {Environment.NewLine}");
215215

216216
selectBuilder.Append($"FROM {expressionTranslator.GetTableAlias(typeof(T))}");
217217
}
@@ -243,7 +243,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
243243

244244
valuesBuilder.Append($"VALUES ");
245245

246-
valuesBuilder.Append($"{Environment.NewLine}({string.Join(", ", _tableColumns.Where(c => _insertParameters.ContainsKey(c)).Select(c => expressionTranslator.Translate(_insertParameters[c])))})");
246+
valuesBuilder.Append($"{Environment.NewLine}({string.Join(", ", _tableColumns.Where(c => _insertParameters.ContainsKey(c)).Select(c => expressionTranslator.Translate(_insertParameters[c], FragmentType)))})");
247247

248248
return valuesBuilder.ToString();
249249
}

Lib/CSQLQueryExpress/Fragments/SQLQueryJoin.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
9595
{
9696
if (_select.Any(f => f.FragmentType == SQLQueryFragmentType.Where))
9797
{
98-
joinBuilder.Append($" {Environment.NewLine}({Environment.NewLine}{string.Join($"{Environment.NewLine} ", _select.Select(s => s.FragmentType == SQLQueryFragmentType.Where ? $"{s.Translate(expressionTranslator)} AND {expressionTranslator.Translate(_join)}" : s.Translate(expressionTranslator)))}{Environment.NewLine}) AS {expressionTranslator.GetTableAlias(_type)}");
98+
joinBuilder.Append($" {Environment.NewLine}({Environment.NewLine}{string.Join($"{Environment.NewLine} ", _select.Select(s => s.FragmentType == SQLQueryFragmentType.Where ? $"{s.Translate(expressionTranslator)} AND {expressionTranslator.Translate(_join, FragmentType)}" : s.Translate(expressionTranslator)))}{Environment.NewLine}) AS {expressionTranslator.GetTableAlias(_type)}");
9999
}
100100
else
101101
{
@@ -125,15 +125,15 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
125125
}
126126
else
127127
{
128-
joinBuilder.Append($" {expressionTranslator.Translate(Expression.Constant(_type))}");
128+
joinBuilder.Append($" {expressionTranslator.Translate(Expression.Constant(_type), FragmentType)}");
129129
}
130130

131131
if (TableHints.HasValue)
132132
{
133133
joinBuilder.Append($" WITH ({TableHints.Value})");
134134
}
135135

136-
joinBuilder.Append($" ON {expressionTranslator.Translate(_join)}");
136+
joinBuilder.Append($" ON {expressionTranslator.Translate(_join, FragmentType)}");
137137
}
138138

139139
return joinBuilder.ToString();
@@ -168,7 +168,7 @@ public SQLQueryJoinWhere(Expression where)
168168

169169
public string Translate(ISQLQueryTranslator expressionTranslator)
170170
{
171-
return $"WHERE {expressionTranslator.Translate(_where)}";
171+
return $"WHERE {expressionTranslator.Translate(_where, FragmentType)}";
172172
}
173173
}
174174

Lib/CSQLQueryExpress/Fragments/SQLQueryOrder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ protected void Paging(Expression paging)
3333

3434
public string Translate(ISQLQueryTranslator expressionTranslator)
3535
{
36-
return $"ORDER BY {string.Join(", ", _orderBy.Select(o => expressionTranslator.Translate(o)))}";
36+
return $"ORDER BY {string.Join(", ", _orderBy.Select(o => expressionTranslator.Translate(o, FragmentType)))}";
3737
}
3838
}
3939

Lib/CSQLQueryExpress/Fragments/SQLQueryOutput.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal SQLQueryOutput(SQLQueryFragmentType owner, IList<ISQLQueryFragment> fra
3636

3737
public override string Translate(ISQLQueryTranslator expressionTranslator)
3838
{
39-
return $"OUTPUT {Environment.NewLine}{string.Join($"{Environment.NewLine}, ", _output.Select(u => expressionTranslator.Translate(u)))}";
39+
return $"OUTPUT {Environment.NewLine}{string.Join($"{Environment.NewLine}, ", _output.Select(u => expressionTranslator.Translate(u, _owner)))}";
4040
}
4141

4242
public override IEnumerator<ISQLQueryFragment> GetEnumerator()

Lib/CSQLQueryExpress/Fragments/SQLQueryPage.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal SQLQueryPage(Expression paging)
1515

1616
public string Translate(ISQLQueryTranslator expressionTranslator)
1717
{
18-
return expressionTranslator.Translate(_paging);
18+
return expressionTranslator.Translate(_paging, FragmentType);
1919
}
2020
}
2121
}

Lib/CSQLQueryExpress/Fragments/SQLQuerySelect.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public virtual string Translate(ISQLQueryTranslator expressionTranslator)
9999
{
100100
if (_select != null && _select.Length > 0)
101101
{
102-
selectBase.Append($"{Environment.NewLine}COUNT(DISTINCT {string.Join($", ", _select.Select(s => expressionTranslator.Translate(s)))})");
102+
selectBase.Append($"{Environment.NewLine}COUNT(DISTINCT {string.Join($", ", _select.Select(s => expressionTranslator.Translate(s, FragmentType)))})");
103103
}
104104
else
105105
{
@@ -110,7 +110,7 @@ public virtual string Translate(ISQLQueryTranslator expressionTranslator)
110110
{
111111
if (_select != null && _select.Length > 0)
112112
{
113-
selectBase.Append($"{Environment.NewLine}COUNT({string.Join($", ", _select.Select(s => expressionTranslator.Translate(s)))})");
113+
selectBase.Append($"{Environment.NewLine}COUNT({string.Join($", ", _select.Select(s => expressionTranslator.Translate(s, FragmentType)))})");
114114
}
115115
else
116116
{
@@ -130,7 +130,7 @@ public virtual string Translate(ISQLQueryTranslator expressionTranslator)
130130

131131
if (_select != null && _select.Length > 0)
132132
{
133-
selectBase.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select(s => expressionTranslator.Translate(s)))}");
133+
selectBase.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select(s => expressionTranslator.Translate(s, FragmentType)))}");
134134
}
135135
else
136136
{
@@ -146,7 +146,7 @@ public virtual string Translate(ISQLQueryTranslator expressionTranslator)
146146

147147
if (_select != null && _select.Length > 0)
148148
{
149-
selectBase.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select(s => expressionTranslator.Translate(s)))}");
149+
selectBase.Append($"{Environment.NewLine}{string.Join($", {Environment.NewLine}", _select.Select(s => expressionTranslator.Translate(s, FragmentType)))}");
150150
}
151151
else
152152
{

Lib/CSQLQueryExpress/Fragments/SQLQueryUpdate.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
8181

8282
if (_updateProperties == null)
8383
{
84-
updateBuilder.Append($"{Environment.NewLine}SET {string.Join(", ", _update.Select(u => expressionTranslator.Translate(u)))}");
84+
updateBuilder.Append($"{Environment.NewLine}SET {string.Join(", ", _update.Select(u => expressionTranslator.Translate(u, FragmentType)))}");
8585
}
8686
else
8787
{
@@ -99,7 +99,7 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
9999

100100
if (_updateProperties == null)
101101
{
102-
updateBuilder.Append($"{Environment.NewLine}SET {string.Join(", ", _update.Select(u => expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(u))))}");
102+
updateBuilder.Append($"{Environment.NewLine}SET {string.Join(", ", _update.Select(u => expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(u, FragmentType))))}");
103103
}
104104
else
105105
{

Lib/CSQLQueryExpress/Fragments/SQLQueryWhere.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public string Translate(ISQLQueryTranslator expressionTranslator)
3030
{
3131
if (Fragments.Any(f => f.FragmentType == SQLQueryFragmentType.FromBySelect || f.FragmentType == SQLQueryFragmentType.Select || f.FragmentType == SQLQueryFragmentType.SelectCte))
3232
{
33-
return $"WHERE {expressionTranslator.Translate(GetWhereCondition())}";
33+
return $"WHERE {expressionTranslator.Translate(GetWhereCondition(), FragmentType)}";
3434
}
3535

36-
return $"WHERE {expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(GetWhereCondition()))}";
36+
return $"WHERE {expressionTranslator.GetColumnsWithoutTableAlias(expressionTranslator.Translate(GetWhereCondition(), FragmentType))}";
3737
}
3838
}
3939

Tests/CSQLQueryExpress.Tests/UnitTests/QueryConditionUnitTest.cs

+27
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,33 @@ public void TestIsNotNullExpression()
3838
Is.EqualTo(@"SELECT _t0.* FROM [dbo].[Products] AS _t0 WHERE (_t0.[ProductID] IS NOT NULL)"));
3939
}
4040

41+
[Test]
42+
public void TestImplicitBooleanExpression()
43+
{
44+
var query = new SQLQuery()
45+
.From<ShippedOrders>()
46+
.Where(o => (!o.Shipped.HasValue || (o.Shipped.HasValue && (o.Shipped.Value || !o.Shipped.Value))) && (o.Prepared || !o.Prepared))
47+
.Select(p => p.All());
48+
49+
var compiledQuery = query.Compile();
50+
51+
Assert.That(compiledQuery.Parameters.Count, Is.EqualTo(4));
52+
Assert.That(compiledQuery.Parameters[0].Value, Is.EqualTo(true));
53+
Assert.That(compiledQuery.Parameters[1].Value, Is.EqualTo(false));
54+
Assert.That(compiledQuery.Parameters[2].Value, Is.EqualTo(true));
55+
Assert.That(compiledQuery.Parameters[3].Value, Is.EqualTo(false));
56+
57+
Assert.That(compiledQuery.Statement.Replace(Environment.NewLine, string.Empty),
58+
Is.EqualTo(@"SELECT _t0.* FROM [dbo].[Orders] AS _t0 WHERE (((_t0.[Shipped] IS NULL) OR ((_t0.[Shipped] IS NOT NULL) AND ((_t0.[Shipped] = @p0) OR (_t0.[Shipped] = @p1)))) AND ((_t0.[Prepared] = @p2) OR (_t0.[Prepared] = @p3)))"));
59+
}
60+
61+
class ShippedOrders : dbo.Orders
62+
{
63+
public bool Prepared { get; set; }
64+
65+
public bool? Shipped { get; set; }
66+
}
67+
4168
[Test]
4269
public void TestInExpression()
4370
{

0 commit comments

Comments
 (0)