Skip to content

Commit ae112a2

Browse files
committed
WIP
1 parent bddde8d commit ae112a2

19 files changed

Lines changed: 380 additions & 58 deletions
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator.Storage;
4+
5+
/// <summary>
6+
/// Unit tests for <see cref="RowDecoder.DecodeColumn"/>, which the page-backend
7+
/// reader uses to navigate row bytes one column at a time without materializing
8+
/// the whole row.
9+
/// </summary>
10+
[TestClass]
11+
public sealed class DecodeColumnTests
12+
{
13+
[TestMethod]
14+
[DataRow(0)]
15+
[DataRow(1)]
16+
[DataRow(2)]
17+
public void EachFixedColumn_DecodesIndependently(int ordinal)
18+
{
19+
SqlType[] schema = [SqlType.Int32, SqlType.BigInt, SqlType.SmallInt];
20+
SqlValue[] values = [SqlValue.FromInt32(11), SqlValue.FromInt64(22L), SqlValue.FromInt16(33)];
21+
var bytes = RowEncoder.EncodeRow(schema, values);
22+
23+
AreEqual(values[ordinal], RowDecoder.DecodeColumn(schema, bytes, ordinal));
24+
}
25+
26+
[TestMethod]
27+
public void NullColumn_DecodesAsTypedNull()
28+
{
29+
SqlType[] schema = [SqlType.Int32, SqlType.Int32];
30+
SqlValue[] values = [SqlValue.FromInt32(1), SqlValue.Null(SqlType.Int32)];
31+
var bytes = RowEncoder.EncodeRow(schema, values);
32+
33+
var decoded = RowDecoder.DecodeColumn(schema, bytes, 1);
34+
IsTrue(decoded.IsNull);
35+
AreEqual(SqlType.Int32, decoded.Type);
36+
}
37+
38+
[TestMethod]
39+
[DataRow(0)]
40+
[DataRow(1)]
41+
[DataRow(2)]
42+
public void MixedFixedAndVarColumns_DecodeIndependently(int ordinal)
43+
{
44+
SqlType[] schema = [SqlType.Int32, SqlType.Varchar, SqlType.Int32];
45+
SqlValue[] values = [SqlValue.FromInt32(1), SqlValue.FromVarchar("hello"), SqlValue.FromInt32(2)];
46+
var bytes = RowEncoder.EncodeRow(schema, values);
47+
48+
AreEqual(values[ordinal], RowDecoder.DecodeColumn(schema, bytes, ordinal));
49+
}
50+
51+
[TestMethod]
52+
public void TwoVarColumns_SecondVarColumn_DecodesCorrectly()
53+
{
54+
SqlType[] schema = [SqlType.Varchar, SqlType.Varchar];
55+
SqlValue[] values = [SqlValue.FromVarchar("alpha"), SqlValue.FromVarchar("omega")];
56+
var bytes = RowEncoder.EncodeRow(schema, values);
57+
58+
AreEqual(values[0], RowDecoder.DecodeColumn(schema, bytes, 0));
59+
AreEqual(values[1], RowDecoder.DecodeColumn(schema, bytes, 1));
60+
}
61+
62+
[TestMethod]
63+
public void NullVarColumn_BetweenTwoNonNullVarColumns_DecodesCorrectly()
64+
{
65+
SqlType[] schema = [SqlType.Varchar, SqlType.Varchar, SqlType.Varchar];
66+
SqlValue[] values = [SqlValue.FromVarchar("a"), SqlValue.Null(SqlType.Varchar), SqlValue.FromVarchar("c")];
67+
var bytes = RowEncoder.EncodeRow(schema, values);
68+
69+
AreEqual(values[0], RowDecoder.DecodeColumn(schema, bytes, 0));
70+
IsTrue(RowDecoder.DecodeColumn(schema, bytes, 1).IsNull);
71+
AreEqual(values[2], RowDecoder.DecodeColumn(schema, bytes, 2));
72+
}
73+
74+
[TestMethod]
75+
public void OutOfRange_Throws()
76+
{
77+
SqlType[] schema = [SqlType.Int32];
78+
var bytes = RowEncoder.EncodeRow(schema, [SqlValue.FromInt32(1)]);
79+
80+
_ = Throws<ArgumentOutOfRangeException>(() => RowDecoder.DecodeColumn(schema, bytes, 1));
81+
_ = Throws<ArgumentOutOfRangeException>(() => RowDecoder.DecodeColumn(schema, bytes, -1));
82+
}
83+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Curated subset of <see cref="SelectTests"/>-style queries run against
7+
/// <see cref="StorageBackend.Pages"/>. Each query exercises the page-row
8+
/// encoder/decoder end-to-end via the parallel <c>Expression.RunSql</c>
9+
/// evaluator. Tableless only — table-backed queries arrive in a later step.
10+
/// </summary>
11+
[TestClass]
12+
public sealed class PagesBackendIntegrationTests
13+
{
14+
private static object? ExecuteScalar(string commandText) =>
15+
new Simulation(StorageBackend.Pages).ExecuteScalar(commandText);
16+
17+
[TestMethod]
18+
[DataRow("select 1", 1)]
19+
[DataRow("select 0", 0)]
20+
[DataRow("select 42", 42)]
21+
[DataRow("select +1", 1)]
22+
[DataRow("select +0", 0)]
23+
[DataRow("select - 1", -1)]
24+
[DataRow("select -1", -1)]
25+
public void IntegerLiteral(string commandText, int expected)
26+
=> AreEqual(expected, ExecuteScalar(commandText));
27+
28+
[TestMethod]
29+
[DataRow("select 1 + 1", 2)]
30+
[DataRow("select 1 - 1", 0)]
31+
[DataRow("select 2 * 2", 4)]
32+
[DataRow("select 2 / 2", 1)]
33+
[DataRow("select 5 % 2", 1)]
34+
[DataRow("select 5 % 3", 2)]
35+
[DataRow("select 5 % 5", 0)]
36+
[DataRow("select 1 + 2 * 3", 7)]
37+
[DataRow("select 3 * 2 + 1", 7)]
38+
public void Arithmetic(string commandText, int expected)
39+
=> AreEqual(expected, ExecuteScalar(commandText));
40+
41+
[TestMethod]
42+
[DataRow("select 1 & 3", 1)]
43+
[DataRow("select 1 | 3", 3)]
44+
[DataRow("select 1 ^ 3", 2)]
45+
public void Bitwise(string commandText, int expected)
46+
=> AreEqual(expected, ExecuteScalar(commandText));
47+
48+
[TestMethod]
49+
[DataRow("select (1)", 1)]
50+
[DataRow("select (1) + 1", 2)]
51+
[DataRow("select (1) + (1)", 2)]
52+
[DataRow("select (1 + 2) * 3", 9)]
53+
public void Parenthesized(string commandText, int expected)
54+
=> AreEqual(expected, ExecuteScalar(commandText));
55+
56+
[TestMethod]
57+
[DataRow("select abs(1)", 1)]
58+
[DataRow("select abs(0)", 0)]
59+
[DataRow("select abs(-1)", 1)]
60+
[DataRow("select datalength(1)", 4)]
61+
public void BuiltInFunction(string commandText, int expected)
62+
=> AreEqual(expected, ExecuteScalar(commandText));
63+
64+
[TestMethod]
65+
public void AbsOfNull()
66+
=> AreEqual(DBNull.Value, ExecuteScalar("select abs(null)"));
67+
68+
[TestMethod]
69+
public void DataLengthOfNull()
70+
=> AreEqual(DBNull.Value, ExecuteScalar("select datalength(null)"));
71+
72+
[TestMethod]
73+
public void ArithmeticPropagatesNull()
74+
=> AreEqual(DBNull.Value, ExecuteScalar("select null + 1"));
75+
76+
[TestMethod]
77+
[DataRow("select 1 as c", "c", 1)]
78+
[DataRow("select 1 + 1 as c", "c", 2)]
79+
[DataRow("select 1 c", "c", 1)]
80+
public void NamedExpression(string commandText, string name, int value)
81+
{
82+
using var reader = new Simulation(StorageBackend.Pages).ExecuteReader(commandText);
83+
IsTrue(reader.Read());
84+
AreEqual(name, reader.GetName(0));
85+
AreEqual(value, reader[0]);
86+
}
87+
88+
[TestMethod]
89+
public void MultiColumnConstants()
90+
{
91+
using var reader = new Simulation(StorageBackend.Pages).ExecuteReader("select 1, 2");
92+
IsTrue(reader.Read());
93+
AreEqual(2, reader.FieldCount);
94+
AreEqual(1, reader[0]);
95+
AreEqual(2, reader[1]);
96+
IsFalse(reader.Read());
97+
}
98+
99+
[TestMethod]
100+
public void MultiColumnMixedNamed()
101+
{
102+
using var reader = new Simulation(StorageBackend.Pages).ExecuteReader("select 1 + 1 as x, 7 as y");
103+
IsTrue(reader.Read());
104+
AreEqual(2, reader.FieldCount);
105+
AreEqual("x", reader.GetName(0));
106+
AreEqual("y", reader.GetName(1));
107+
AreEqual(2, reader[0]);
108+
AreEqual(7, reader[1]);
109+
}
110+
111+
[TestMethod]
112+
[DataRow("select @p0", "p0", 5)]
113+
[DataRow("select @p0", "@p0", 6)]
114+
[DataRow("select (@p0)", "p0", 7)]
115+
[DataRow("select @p0 + 1", "p0", 41)]
116+
public void ParameterValue(string commandText, string name, int value)
117+
{
118+
var result = new Simulation(StorageBackend.Pages)
119+
.CreateOpenConnection()
120+
.CreateCommand(commandText, (name, value))
121+
.ExecuteScalar();
122+
123+
AreEqual(commandText.EndsWith("+ 1") ? value + 1 : value, result);
124+
}
125+
}

SqlServerSimulator.Tests/PagesBackendTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ public void Reader_RoundsTripsRowAndExposesScalar()
3737
}
3838

3939
[TestMethod]
40-
public void Arithmetic_NotYetSupported_OnPages()
40+
public void StringLiteralExpression_NotYetSupported_OnPages()
4141
{
42-
// Add expression doesn't override RunSql yet, so the page backend rejects it.
42+
// The Value.RunSql translation shim only maps int literals to SqlValue
43+
// today; string literals (here via @@VERSION) hit NotSupportedException.
4344
// Demonstrates that the parallel evaluator surface is honest about its coverage.
4445
var simulation = new Simulation(StorageBackend.Pages);
45-
_ = Throws<NotSupportedException>(() => simulation.ExecuteScalar("select 1 + 1"));
46+
_ = Throws<NotSupportedException>(() => simulation.ExecuteScalar("select @@version"));
4647
}
4748

4849
[TestMethod]

SqlServerSimulator/Parser/Expressions/AbsoluteValue.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace SqlServerSimulator.Parser.Expressions;
1+
using SqlServerSimulator.Storage;
2+
3+
namespace SqlServerSimulator.Parser.Expressions;
24

35
/// <summary>
46
/// Encapsulates the SQL ABS command: https://learn.microsoft.com/en-us/sql/t-sql/functions/abs-transact-sql
@@ -19,6 +21,16 @@ public override DataValue Run(Func<List<string>, DataValue> getColumnValue)
1921
}, value.Type);
2022
}
2123

24+
public override SqlValue RunSql(Func<List<string>, SqlValue> getColumnValue)
25+
{
26+
var value = source.RunSql(getColumnValue);
27+
return value.Type != SqlType.Int32
28+
? throw new NotSupportedException($"ABS on Pages backend currently supports only int operands; got {value.Type}.")
29+
: value.IsNull
30+
? SqlValue.Null(SqlType.Int32)
31+
: SqlValue.FromInt32(Math.Abs(value.AsInt32));
32+
}
33+
2234
#if DEBUG
2335
public override string ToString() => $"ABS({source})";
2436
#endif

SqlServerSimulator/Parser/Expressions/Add.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ internal sealed class Add(Expression left, ParserContext context) : MathExpressi
66

77
protected override DataValue Run(DataType.NumericCompatibleDataType common, DataValue left, DataValue right) => common.Add(left, right);
88

9+
protected override Storage.SqlValue RunSql(Storage.SqlValue left, Storage.SqlValue right) => Int32Operation(left, right, '+', static (a, b) => a + b);
10+
911
protected override char Operator => '+';
1012
}

SqlServerSimulator/Parser/Expressions/BitwiseAnd.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ internal sealed class BitwiseAnd(Expression left, ParserContext context) : Bitwi
66

77
protected override DataValue Run(DataType.BitwiseCompatibleDataType common, DataValue left, DataValue right) => common.BitwiseAnd(left, right);
88

9+
protected override Storage.SqlValue RunSql(Storage.SqlValue left, Storage.SqlValue right) => Int32Operation(left, right, '&', static (a, b) => a & b);
10+
911
protected override char Operator => '&';
1012
}

SqlServerSimulator/Parser/Expressions/BitwiseExclusiveOr.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ internal sealed class BitwiseExclusiveOr(Expression left, ParserContext context)
66

77
protected override DataValue Run(DataType.BitwiseCompatibleDataType common, DataValue left, DataValue right) => common.BitwiseExclusiveOr(left, right);
88

9+
protected override Storage.SqlValue RunSql(Storage.SqlValue left, Storage.SqlValue right) => Int32Operation(left, right, '^', static (a, b) => a ^ b);
10+
911
protected override char Operator => '^';
1012
}

SqlServerSimulator/Parser/Expressions/BitwiseOr.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ internal sealed class BitwiseOr(Expression left, ParserContext context) : Bitwis
66

77
protected override DataValue Run(DataType.BitwiseCompatibleDataType common, DataValue left, DataValue right) => common.BitwiseOr(left, right);
88

9+
protected override Storage.SqlValue RunSql(Storage.SqlValue left, Storage.SqlValue right) => Int32Operation(left, right, '|', static (a, b) => a | b);
10+
911
protected override char Operator => '|';
1012
}

SqlServerSimulator/Parser/Expressions/DataLength.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace SqlServerSimulator.Parser.Expressions;
1+
using SqlServerSimulator.Storage;
2+
3+
namespace SqlServerSimulator.Parser.Expressions;
24

35
/// <summary>
46
/// Encapsulates the SQL DATALENGTH command: https://learn.microsoft.com/en-us/sql/t-sql/functions/datalength-transact-sql
@@ -13,6 +15,16 @@ public override DataValue Run(Func<List<string>, DataValue> getColumnValue)
1315
return value.Value is null ? default : new(value.Type.DataLength(value));
1416
}
1517

18+
public override SqlValue RunSql(Func<List<string>, SqlValue> getColumnValue)
19+
{
20+
var value = source.RunSql(getColumnValue);
21+
return value.IsNull
22+
? SqlValue.Null(SqlType.Int32)
23+
: value.Type.IsFixedLength
24+
? SqlValue.FromInt32(value.Type.FixedLength)
25+
: SqlValue.FromInt32(value.Type.GetVariableByteCount(value));
26+
}
27+
1628
#if DEBUG
1729
public override string ToString() => $"DATALENGTH({source})";
1830
#endif

SqlServerSimulator/Parser/Expressions/Divide.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ internal sealed class Divide(Expression left, ParserContext context) : MathExpre
66

77
protected override DataValue Run(DataType.NumericCompatibleDataType common, DataValue left, DataValue right) => common.Divide(left, right);
88

9+
protected override Storage.SqlValue RunSql(Storage.SqlValue left, Storage.SqlValue right) => Int32Operation(left, right, '/', static (a, b) => a / b);
10+
911
protected override char Operator => '/';
1012
}

0 commit comments

Comments
 (0)