Skip to content

Commit 83b2e21

Browse files
committed
Initial support for WHERE.
1 parent 2667d56 commit 83b2e21

5 files changed

Lines changed: 92 additions & 7 deletions

File tree

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ dotnet_diagnostic.IDE0010.severity = none # Add missing cases
1414
dotnet_diagnostic.IDE0021.severity = none # Use block body for constructor
1515
dotnet_diagnostic.IDE0022.severity = none # Use block body for method
1616
dotnet_diagnostic.IDE0040.severity = none # Add accessibility modifiers
17+
dotnet_diagnostic.IDE0061.severity = none # Use block body for local function
1718
dotnet_diagnostic.IDE0072.severity = none # Add missing cases
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
[TestClass]
6+
public class WhereTests
7+
{
8+
[TestMethod]
9+
[DataRow("1 = 0", 0)]
10+
[DataRow("1 = 1", 1)]
11+
public void PureExpressionFilter(string whereExpression, int expectedCount)
12+
{
13+
AreEqual(expectedCount, new Simulation().ExecuteReader($"select 1 where {whereExpression}").EnumerateRecords().Count());
14+
}
15+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using SqlServerSimulator.Parser.Tokens;
2+
3+
namespace SqlServerSimulator.Parser;
4+
5+
/// <summary>
6+
/// A specific type of expression used in WHERE clauses and similar branching scenarios.
7+
/// </summary>
8+
internal abstract class BooleanExpression
9+
{
10+
private protected BooleanExpression()
11+
{
12+
}
13+
14+
public static BooleanExpression Parse(ParserContext context)
15+
{
16+
var left = Expression.Parse(context);
17+
18+
switch (context.Token)
19+
{
20+
case null:
21+
break;
22+
case Operator { Character: '=' }:
23+
context.MoveNextRequired();
24+
return new EqualityExpression(left, Expression.Parse(context));
25+
}
26+
27+
throw SimulatedSqlException.SyntaxErrorNear(context);
28+
}
29+
30+
/// <summary>
31+
/// Runs the expression, returning its result.
32+
/// </summary>
33+
/// <param name="getColumnValue">Provides the value for a column.</param>
34+
/// <returns>The result of the expression.</returns>
35+
public abstract bool Run(Func<List<string>, object?> getColumnValue);
36+
37+
#if DEBUG
38+
public abstract override string ToString();
39+
#endif
40+
41+
private sealed class EqualityExpression(Expression left, Expression right) : BooleanExpression
42+
{
43+
public override bool Run(Func<List<string>, object?> getColumnValue) =>
44+
(left.Run(getColumnValue)?.Equals(right.Run(getColumnValue))).GetValueOrDefault();
45+
46+
#if DEBUG
47+
public override string ToString() => $"{left} = {right}";
48+
#endif
49+
}
50+
}

SqlServerSimulator/Parser/Selection.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,23 @@ public static Selection Parse(ParserContext context, uint depth)
3434
}
3535

3636
List<Expression> expressions = [];
37+
List<BooleanExpression> excluders = [];
3738

38-
IEnumerable<object?[]> ApplyClauses(IEnumerable<object?[]> records)
39+
IEnumerable<object?[]> ApplyClauses(IEnumerable<object?[]> records, Func<object?[], List<string>, object?> getColumnValueFromRow)
3940
{
41+
records = records.Where(row =>
42+
{
43+
foreach (var excluder in excluders)
44+
{
45+
if (!excluder.Run(columnName => getColumnValueFromRow(row, columnName)))
46+
return false;
47+
}
48+
49+
return true;
50+
}).Select<object?[], object?[]>(row =>
51+
[.. expressions.Select(x => x.Run(columnName => getColumnValueFromRow(row, columnName)))]
52+
);
53+
4054
if (topCount is not null)
4155
records = records.Take(topCount.GetValueOrDefault());
4256

@@ -105,11 +119,11 @@ public static Selection Parse(ParserContext context, uint depth)
105119

106120
return new(new(
107121
expressions,
108-
ApplyClauses(table.Rows.Select<object?[], object?[]>(row => [..expressions.Select(x => x.Run(columnName =>
122+
ApplyClauses(table.Rows, (row, columnName) =>
109123
{
110124
var columnIndex = table.Columns.FindIndex(column => Collation.Default.Equals(column.Name, columnName.Last()));
111125
return columnIndex == -1 ? throw SimulatedSqlException.InvalidColumnName(columnName) : row[columnIndex];
112-
}))]))
126+
})
113127
));
114128

115129
case Operator { Character: '(' }:
@@ -131,16 +145,21 @@ public static Selection Parse(ParserContext context, uint depth)
131145

132146
return new(new(
133147
expressions,
134-
ApplyClauses(derived.Select<object?[], object?[]>(row => [..expressions.Select(x => x.Run(columnName =>
148+
ApplyClauses(derived.records, (row, columnName) =>
135149
{
136150
var columnIndex = Array.FindIndex(derived.columnNames, name => Collation.Default.Equals(name, columnName.Last()));
137151
return columnIndex == -1 ? throw SimulatedSqlException.InvalidColumnName(columnName) : row[columnIndex];
138-
}))]))
152+
})
139153
));
140154
}
141155
}
142156

143157
throw SimulatedSqlException.SyntaxErrorNear(context);
158+
159+
case ReservedKeyword { Keyword: Keyword.Where }:
160+
context.MoveNextRequired();
161+
excluders.Add(BooleanExpression.Parse(context));
162+
continue;
144163
}
145164

146165
throw SimulatedSqlException.SyntaxErrorNear(context);
@@ -149,7 +168,7 @@ public static Selection Parse(ParserContext context, uint depth)
149168

150169
return new(new(
151170
expressions,
152-
ApplyClauses([[.. expressions.Select(x => x.Run(column => throw SimulatedSqlException.InvalidColumnName(column)))]])
171+
ApplyClauses([[.. expressions.Select(x => x.Run(column => throw SimulatedSqlException.InvalidColumnName(column)))]], (row, columnName) => throw new NotSupportedException())
153172
));
154173
}
155174
}

SqlServerSimulator/Parser/Tokenizer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ static class Tokenizer
2424
'@' => ParseAtOrDoubleAtPrefixedString(command, ref index),
2525
'-' => ParseMinusOrComment(command, ref index),
2626
'[' => ParseBracketDelimitedString(command, ref index),
27-
'+' or '*' or '(' or ')' or ',' or '.' or ';' => new Operator(command, index),
27+
'+' or '*' or '(' or ')' or ',' or '.' or ';' or '=' => new Operator(command, index),
2828
var c => throw SimulatedSqlException.SyntaxErrorNear(c) // Might throw on valid-but-unsupported syntax.
2929
};
3030

0 commit comments

Comments
 (0)