Skip to content

Commit e73a5e6

Browse files
committed
Select statements can now contain both expressions and tables.
1 parent 989d74e commit e73a5e6

4 files changed

Lines changed: 62 additions & 17 deletions

File tree

SqlServerSimulator.Tests/SelectTests.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public void Expression(string commandText, string name, object value)
6767
[DataRow("select 1 from systypes", 34, 1, 1)]
6868
[DataRow("select 1 from systypes as s", 34, 1, 1)]
6969
[DataRow("select name from systypes", 34, 34, "image")]
70+
[DataRow("select 1 + 1 from systypes", 34, 1, 2)]
7071
public void ExpressionFromTable(string commandText, int minimumRows, int uniqueRows, object value)
7172
{
7273
using var reader = new Simulation().ExecuteReader(commandText);
@@ -87,6 +88,7 @@ public void ExpressionFromTable(string commandText, int minimumRows, int uniqueR
8788
[DataRow("select name as c from systypes as s", 34, 34, "c", "image")]
8889
[DataRow("select systypes.name from systypes", 34, 34, "name", "image")]
8990
[DataRow("select s.name from systypes as s", 34, 34, "name", "image")]
91+
[DataRow("select 1 + 1 as c from systypes", 34, 1, "c", 2)]
9092
public void NamedExpressionFromTable(string commandText, int minimumRows, int uniqueRows, string name, object value)
9193
{
9294
using var reader = new Simulation().ExecuteReader(commandText);
@@ -106,6 +108,29 @@ public void NamedExpressionFromTable(string commandText, int minimumRows, int un
106108
Assert.AreEqual(value, results[0]);
107109
}
108110

111+
[TestMethod]
112+
[DataRow("select 1 + 1 as x, name as c from systypes", 34, "x", 2, "c", "image")]
113+
[DataRow("select 1 + 1, name as c from systypes", 34, "", 2, "c", "image")]
114+
public void NamedExpressionAndColumnFromTable(string commandText, int minimumRows, string name0, object value0, string name1, object value1)
115+
{
116+
using var reader = new Simulation().ExecuteReader(commandText);
117+
118+
var results = reader
119+
.EnumerateRecords()
120+
.Take(minimumRows) // There might be more someday, but there won't be less.
121+
.Select(reader =>
122+
{
123+
Assert.AreEqual(name0, reader.GetName(0));
124+
Assert.AreEqual(name1, reader.GetName(1));
125+
return (C0: reader[0], C1: reader[1]);
126+
})
127+
.ToArray();
128+
129+
Assert.HasCount(minimumRows, results);
130+
Assert.AreEqual(value0, results[0].C0);
131+
Assert.AreEqual(value1, results[0].C1);
132+
}
133+
109134
[TestMethod]
110135
public void Select1Comma2()
111136
{
@@ -147,6 +172,21 @@ public void SelectSyntaxErrorsAreCorrect(string commandText, string nearSyntax)
147172
new Simulation().ValidateSyntaxError(commandText, nearSyntax);
148173

149174
[TestMethod]
150-
public void SelectFromDerivedTable() =>
151-
Assert.AreEqual(1, new Simulation().ExecuteScalar("select x from ( select 1 as x ) as x"));
175+
[DataRow("select x from ( select 1 as x ) as x", "x", 1)]
176+
[DataRow("select x from ( select 1 + 1 as x ) as x", "x", 2)]
177+
public void DerivedTable(string commandText, string name, object value)
178+
{
179+
using var reader = new Simulation().ExecuteReader(commandText);
180+
181+
var result = reader
182+
.EnumerateRecords()
183+
.Select(reader =>
184+
{
185+
Assert.AreEqual(name, reader.GetName(0));
186+
return reader[0];
187+
})
188+
.SingleOrDefault();
189+
190+
Assert.AreEqual(value, result);
191+
}
152192
}

SqlServerSimulator/Parser/Expression.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ private protected Expression()
1313
public static Expression Parse(Simulation simulation, IEnumerator<Token> tokens, ref Token? token, Func<string, object?> getVariableValue)
1414
{
1515
Expression? expression = null;
16+
bool tokenWasRead;
1617

1718
do
1819
{
20+
tokenWasRead = false;
21+
1922
switch (token)
2023
{
2124
case Numeric number:
@@ -59,6 +62,8 @@ public static Expression Parse(Simulation simulation, IEnumerator<Token> tokens,
5962
expression = new Add(expression, parsed);
6063
if (parsed is NamedExpression named)
6164
expression = named.TransferName(expression);
65+
66+
tokenWasRead = true;
6267
break;
6368
case Period:
6469
if (expression is null)
@@ -70,13 +75,14 @@ public static Expression Parse(Simulation simulation, IEnumerator<Token> tokens,
7075
reference.AddMultiPartComponent(tokens.RequireNext<Name>());
7176
break;
7277
case Comma:
78+
case CloseParentheses:
7379
if (expression is null)
7480
throw SimulatedSqlException.SyntaxErrorNear(token);
7581
return expression;
7682
default:
7783
throw new NotSupportedException($"Simulated expression parser doesn't know how to handle '{token}'.");
7884
}
79-
} while (tokens.TryMoveNext(out token));
85+
} while ((tokenWasRead && token is not null) || tokens.TryMoveNext(out token));
8086

8187
return expression;
8288
}

SqlServerSimulator/Parser/Selection.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,11 @@ public static Selection Parse(Simulation simulation, IEnumerator<Token> tokens,
1212
{
1313
token = tokens.RequireNext();
1414

15-
Dictionary<string, int> columnIndexes = [];
1615
List<Expression> expressions = [];
1716

1817
do
1918
{
20-
var expression = Expression.Parse(simulation, tokens, ref token, getVariableValue);
21-
if (expression.Name.Length > 0)
22-
columnIndexes.Add(expression.Name, columnIndexes.Count);
23-
expressions.Add(expression);
19+
expressions.Add(Expression.Parse(simulation, tokens, ref token, getVariableValue));
2420

2521
switch (token)
2622
{
@@ -35,7 +31,7 @@ public static Selection Parse(Simulation simulation, IEnumerator<Token> tokens,
3531

3632
case null: // "Select" with no "From".
3733
return new(new(
38-
columnIndexes,
34+
expressions,
3935
[[.. expressions.Select(x => x.Run(column => throw SimulatedSqlException.InvalidColumnName(column)))]]
4036
));
4137

@@ -63,7 +59,7 @@ public static Selection Parse(Simulation simulation, IEnumerator<Token> tokens,
6359
}
6460

6561
return new(new(
66-
columnIndexes,
62+
expressions,
6763
table.Rows.Select<object?[], object?[]>(row => [..expressions.Select(x => x.Run(columnName =>
6864
{
6965
var columnIndex = table.Columns.FindIndex(column => Collation.Default.Equals(column.Name, columnName.Last()));
@@ -89,7 +85,7 @@ public static Selection Parse(Simulation simulation, IEnumerator<Token> tokens,
8985
}
9086

9187
return new(new(
92-
columnIndexes,
88+
expressions,
9389
derived.Select<object?[], object?[]>(row => [..expressions.Select(x => x.Run(columnName =>
9490
{
9591
var columnIndex = Array.FindIndex(derived.columnNames, name => Collation.Default.Equals(name, columnName.Last()));

SqlServerSimulator/SimulatedResultSet.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
using System.Collections;
1+
using SqlServerSimulator.Parser;
2+
using System.Collections;
3+
using System.Collections.Frozen;
24

35
namespace SqlServerSimulator;
46

5-
sealed class SimulatedResultSet(Dictionary<string, int> columnIndexes, IEnumerable<object?[]> records) : SimulatedStatementOutcome(-1), IEnumerable<object?[]>
7+
sealed class SimulatedResultSet(List<Expression> expressions, IEnumerable<object?[]> records) : SimulatedStatementOutcome(-1), IEnumerable<object?[]>
68
{
79
internal readonly IEnumerable<object?[]> records = records;
8-
internal readonly Dictionary<string, int> columnIndexes = columnIndexes;
9-
internal readonly string[] columnNames = [.. columnIndexes
10-
.OrderBy(kv => kv.Value)
11-
.Select(kv => kv.Key)];
10+
internal readonly FrozenDictionary<string, int> columnIndexes = expressions
11+
.Select((x, i) => (x, i))
12+
.Where(x => x.x.Name.Length > 0)
13+
.ToFrozenDictionary(x => x.x.Name, x => x.i);
14+
internal readonly string[] columnNames = [.. expressions.Select(x => x.Name)];
1215

1316
public IEnumerator<object?[]> GetEnumerator() => this.records.GetEnumerator();
1417

0 commit comments

Comments
 (0)