Skip to content

Commit eab276c

Browse files
committed
Implemented many of the standard SQL Server functions with a TODO list for the documented set.
1 parent 972577c commit eab276c

47 files changed

Lines changed: 3827 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Auto-loaded orientation. `README.md` is for humans.
1010

1111
## Operating goal
1212

13-
High-fidelity emulation. Authenticity over desirability — when SQL Server's behavior is quirky or lossy (CP1252 `?` replacement, ANSI trailing-space `=` padding, `LEN` excluding trailing spaces), mirror it. Current fidelity bar: EF Core trusts the simulator end-to-end. `*.Tests.EFCore` is the regression oracle. Priority is opportunistic — pick the lowest-effort path that unlocks the most application compatibility next.
13+
High-fidelity emulation. Authenticity over desirability — when SQL Server's behavior is quirky or lossy (CP1252 `?` replacement, ANSI trailing-space `=` padding, `LEN` excluding trailing spaces), mirror it. EF Core trusts the simulator end-to-end (`*.Tests.EFCore` is the regression oracle and must stay green). Beyond that floor, priority is broad SQL Server coverage weighted by popularity (user wins) and ease (thoroughness wins). The function-coverage backlog at [`docs/claude/function-coverage-todo.md`](docs/claude/function-coverage-todo.md) is sorted by that weighting and tracks category-completion milestones — read it before pitching a new built-in function.
1414

1515
## Feature-bundle workflow
1616

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for the expanded <c>@@</c>-keyword surface added alongside the
7+
/// scalar-function coverage push: constant-returning ones land in
8+
/// <c>Value</c>'s switch; session/database-state ones get dedicated
9+
/// expression classes. Probe-confirmed defaults against SQL Server 2025
10+
/// (2026-05-22) for the configurable knobs (@@DATEFIRST=7, @@TEXTSIZE=-1,
11+
/// @@OPTIONS=5432, etc.).
12+
/// </summary>
13+
[TestClass]
14+
public sealed class AtAtKeywordExpansionTests
15+
{
16+
[TestMethod]
17+
public void AtAt_MaxPrecision_ReturnsByte38()
18+
=> AreEqual((byte)38, new Simulation().ExecuteScalar("select @@max_precision"));
19+
20+
[TestMethod]
21+
public void AtAt_MaxConnections_Returns32767()
22+
=> AreEqual(32767, new Simulation().ExecuteScalar("select @@max_connections"));
23+
24+
[TestMethod]
25+
public void AtAt_Langid_ReturnsZero()
26+
=> AreEqual((short)0, new Simulation().ExecuteScalar("select @@langid"));
27+
28+
[TestMethod]
29+
public void AtAt_Language_ReturnsUsEnglish()
30+
=> AreEqual("us_english", new Simulation().ExecuteScalar("select @@language"));
31+
32+
[TestMethod]
33+
public void AtAt_ServiceName_ReturnsMssqlServer()
34+
=> AreEqual("MSSQLSERVER", new Simulation().ExecuteScalar("select @@servicename"));
35+
36+
[TestMethod]
37+
public void AtAt_ServerName_ReturnsSimulated()
38+
=> AreEqual("SIMULATED", new Simulation().ExecuteScalar("select @@servername"));
39+
40+
[TestMethod]
41+
public void AtAt_RemServer_ReturnsNull()
42+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select @@remserver"));
43+
44+
[TestMethod]
45+
public void AtAt_DateFirst_ReturnsSeven()
46+
=> AreEqual((byte)7, new Simulation().ExecuteScalar("select @@datefirst"));
47+
48+
[TestMethod]
49+
public void AtAt_TextSize_ReturnsNegativeOne()
50+
=> AreEqual(-1, new Simulation().ExecuteScalar("select @@textsize"));
51+
52+
[TestMethod]
53+
public void AtAt_Options_ReturnsDefaultMask()
54+
=> AreEqual(5432, new Simulation().ExecuteScalar("select @@options"));
55+
56+
[TestMethod]
57+
public void AtAt_NestLevel_ReturnsZeroOutsideProc()
58+
=> AreEqual(0, new Simulation().ExecuteScalar("select @@nestlevel"));
59+
60+
[TestMethod]
61+
public void AtAt_NestLevel_IncrementsInProc()
62+
{
63+
var sim = new Simulation();
64+
sim.ExecuteBatches(
65+
"create procedure dbo.p as select @@nestlevel");
66+
AreEqual(1, sim.ExecuteScalar("exec dbo.p"));
67+
}
68+
69+
[TestMethod]
70+
public void AtAt_ProcId_ReturnsZeroOutsideProc()
71+
=> AreEqual(0, new Simulation().ExecuteScalar("select @@procid"));
72+
73+
[TestMethod]
74+
public void AtAt_ProcId_ReturnsProcObjectIdInProc()
75+
{
76+
var sim = new Simulation();
77+
sim.ExecuteBatches(
78+
"create procedure dbo.p as select @@procid");
79+
var procId = (int?)sim.ExecuteScalar("select object_id('dbo.p')");
80+
AreEqual(procId, sim.ExecuteScalar("exec dbo.p"));
81+
}
82+
83+
[TestMethod]
84+
public void AtAt_Dbts_ReturnsEightBytes()
85+
{
86+
var result = new Simulation().ExecuteScalar("select @@dbts");
87+
IsTrue(result is byte[] { Length: 8 });
88+
}
89+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for the SQL Server 2022+ bit-manipulation family: <c>BIT_COUNT</c>,
7+
/// <c>GET_BIT</c>, <c>SET_BIT</c>, <c>LEFT_SHIFT</c>, <c>RIGHT_SHIFT</c>.
8+
/// Probe-confirmed against SQL Server 2025 (2026-05-22): RIGHT_SHIFT uses
9+
/// LOGICAL (unsigned) shift semantics — <c>RIGHT_SHIFT(-16, 2)</c> returns
10+
/// <c>1073741820</c>, not <c>-4</c>.
11+
/// </summary>
12+
[TestClass]
13+
public sealed class BitManipulationTests
14+
{
15+
[TestMethod]
16+
public void BitCount_Seven_ReturnsThree()
17+
=> AreEqual(3L, new Simulation().ExecuteScalar("select bit_count(cast(7 as int))"));
18+
19+
[TestMethod]
20+
public void BitCount_NegativeOneInt_Returns32()
21+
=> AreEqual(32L, new Simulation().ExecuteScalar("select bit_count(cast(-1 as int))"));
22+
23+
[TestMethod]
24+
public void BitCount_NegativeOneBigint_Returns64()
25+
=> AreEqual(64L, new Simulation().ExecuteScalar("select bit_count(cast(-1 as bigint))"));
26+
27+
[TestMethod]
28+
public void BitCount_TinyintMax_Returns8()
29+
=> AreEqual(8L, new Simulation().ExecuteScalar("select bit_count(cast(255 as tinyint))"));
30+
31+
[TestMethod]
32+
public void BitCount_Null_RaisesMsg8116()
33+
=> new Simulation().AssertSqlError("select bit_count(null)", 8116);
34+
35+
[TestMethod]
36+
public void GetBit_LowBitSet_ReturnsTrue()
37+
=> IsTrue((bool)new Simulation().ExecuteScalar("select get_bit(cast(7 as int), 0)")!);
38+
39+
[TestMethod]
40+
public void GetBit_HighBitClear_ReturnsFalse()
41+
=> IsFalse((bool)new Simulation().ExecuteScalar("select get_bit(cast(7 as int), 3)")!);
42+
43+
[TestMethod]
44+
public void GetBit_OutOfRange_RaisesMsg8120()
45+
=> new Simulation().AssertSqlError("select get_bit(cast(8 as int), 32)", 8120);
46+
47+
[TestMethod]
48+
public void SetBit_SetsHighBit()
49+
=> AreEqual(8, new Simulation().ExecuteScalar("select set_bit(cast(0 as int), 3)"));
50+
51+
[TestMethod]
52+
public void SetBit_ClearWithValue_Works()
53+
=> AreEqual(7, new Simulation().ExecuteScalar("select set_bit(cast(15 as int), 3, 0)"));
54+
55+
[TestMethod]
56+
public void LeftShift_PreservesType_Tinyint()
57+
=> AreEqual((byte)128, new Simulation().ExecuteScalar("select left_shift(cast(1 as tinyint), 7)"));
58+
59+
[TestMethod]
60+
public void LeftShift_OverflowsTinyint_WrapsToZero()
61+
=> AreEqual((byte)0, new Simulation().ExecuteScalar("select left_shift(cast(1 as tinyint), 8)"));
62+
63+
[TestMethod]
64+
public void RightShift_PositiveInt_Works()
65+
=> AreEqual(4, new Simulation().ExecuteScalar("select right_shift(cast(16 as int), 2)"));
66+
67+
[TestMethod]
68+
public void RightShift_NegativeInt_LogicalShift()
69+
=> AreEqual(1073741820, new Simulation().ExecuteScalar("select right_shift(cast(-16 as int), 2)"));
70+
71+
[TestMethod]
72+
public void LeftShift_Bigint_LargeShift()
73+
=> AreEqual(1073741824L, new Simulation().ExecuteScalar("select left_shift(cast(1 as bigint), 30)"));
74+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for <c>CHECKSUM</c>, <c>BINARY_CHECKSUM</c>, and
7+
/// <c>MIN_ACTIVE_ROWVERSION</c>. The simulator's CHECKSUM uses a
8+
/// 32-bit FNV-1a fold over the value representation rather than
9+
/// SQL Server's unpublished CRC; only the semantic guarantee
10+
/// ("equal inputs → equal outputs") matches, not the exact bit
11+
/// pattern — documented quirk.
12+
/// </summary>
13+
[TestClass]
14+
public sealed class ChecksumAndRowVersionTests
15+
{
16+
[TestMethod]
17+
public void Checksum_SameInput_SameOutput()
18+
{
19+
var a = (int)new Simulation().ExecuteScalar("select checksum('foo')")!;
20+
var b = (int)new Simulation().ExecuteScalar("select checksum('foo')")!;
21+
AreEqual(a, b);
22+
}
23+
24+
[TestMethod]
25+
public void Checksum_DifferentInput_DifferentOutput()
26+
{
27+
var a = (int)new Simulation().ExecuteScalar("select checksum('foo')")!;
28+
var b = (int)new Simulation().ExecuteScalar("select checksum('bar')")!;
29+
AreNotEqual(a, b);
30+
}
31+
32+
[TestMethod]
33+
public void Checksum_MultipleArgs_Works()
34+
{
35+
var result = new Simulation().ExecuteScalar("select checksum(1, 'foo', cast('2024-01-15' as date))");
36+
IsTrue(result is int);
37+
}
38+
39+
[TestMethod]
40+
public void Checksum_CaseInsensitive_SameAsLower()
41+
{
42+
var a = (int)new Simulation().ExecuteScalar("select checksum('FOO')")!;
43+
var b = (int)new Simulation().ExecuteScalar("select checksum('foo')")!;
44+
AreEqual(a, b);
45+
}
46+
47+
[TestMethod]
48+
public void BinaryChecksum_CaseSensitive_DiffersFromCaseChange()
49+
{
50+
var a = (int)new Simulation().ExecuteScalar("select binary_checksum('FOO')")!;
51+
var b = (int)new Simulation().ExecuteScalar("select binary_checksum('foo')")!;
52+
AreNotEqual(a, b);
53+
}
54+
55+
[TestMethod]
56+
public void MinActiveRowVersion_Returns8Bytes()
57+
{
58+
var result = new Simulation().ExecuteScalar("select min_active_rowversion()");
59+
IsTrue(result is byte[] { Length: 8 });
60+
}
61+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for the <c>CHOOSE(index, val1, val2, ...)</c> scalar function — the
7+
/// 1-based variadic picker. Result type is the joint promotion of the value
8+
/// arms (CASE-style branch-type unification); out-of-range and NULL index
9+
/// both return typed NULL.
10+
/// </summary>
11+
[TestClass]
12+
public sealed class ChooseTests
13+
{
14+
[TestMethod]
15+
public void Choose_FirstValue_ReturnsFirst()
16+
=> AreEqual("a", new Simulation().ExecuteScalar("select choose(1, 'a', 'b', 'c')"));
17+
18+
[TestMethod]
19+
public void Choose_MiddleValue_ReturnsThat()
20+
=> AreEqual("b", new Simulation().ExecuteScalar("select choose(2, 'a', 'b', 'c')"));
21+
22+
[TestMethod]
23+
public void Choose_LastValue_ReturnsLast()
24+
=> AreEqual("c", new Simulation().ExecuteScalar("select choose(3, 'a', 'b', 'c')"));
25+
26+
[TestMethod]
27+
public void Choose_IndexZero_ReturnsNull()
28+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select choose(0, 'a', 'b', 'c')"));
29+
30+
[TestMethod]
31+
public void Choose_IndexNegative_ReturnsNull()
32+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select choose(-1, 'a', 'b', 'c')"));
33+
34+
[TestMethod]
35+
public void Choose_IndexBeyondList_ReturnsNull()
36+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select choose(4, 'a', 'b', 'c')"));
37+
38+
[TestMethod]
39+
public void Choose_NullIndex_ReturnsNull()
40+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select choose(cast(null as int), 'a', 'b', 'c')"));
41+
42+
[TestMethod]
43+
public void Choose_PickedValueIsNull_ReturnsNull()
44+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select choose(2, 'a', cast(null as varchar(10)), 'c')"));
45+
46+
[TestMethod]
47+
public void Choose_IntegerValues_ReturnsInt()
48+
=> AreEqual(20, new Simulation().ExecuteScalar("select choose(2, 10, 20, 30)"));
49+
50+
[TestMethod]
51+
public void Choose_MixedTypes_PromotesToHigher()
52+
=> AreEqual(20m, new Simulation().ExecuteScalar("select choose(2, cast(10 as decimal(10, 2)), 20, 30)"));
53+
54+
[TestMethod]
55+
public void Choose_OnlyIndexNoValues_RaisesMsg174()
56+
=> new Simulation().AssertSqlError("select choose(1)", 174);
57+
58+
[TestMethod]
59+
public void Choose_FromTableValues_RoundTrips()
60+
=> AreEqual("medium", new Simulation().ExecuteScalar("""
61+
create table sizes (id int);
62+
insert sizes values (2);
63+
select choose(id, 'small', 'medium', 'large') from sizes
64+
"""));
65+
66+
[TestMethod]
67+
public void Choose_StringIndexCoerced_Works()
68+
=> AreEqual("b", new Simulation().ExecuteScalar("select choose('2', 'a', 'b', 'c')"));
69+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Tests for <c>COL_NAME(table_id, col_id)</c> and
7+
/// <c>COL_LENGTH(table_name, col_name)</c>: column-level metadata
8+
/// lookups used by introspection queries and EF Core's schema
9+
/// migration scaffolding.
10+
/// </summary>
11+
[TestClass]
12+
public sealed class ColNameLengthTests
13+
{
14+
[TestMethod]
15+
public void ColName_FirstColumn_ReturnsName()
16+
=> AreEqual("id", new Simulation().ExecuteScalar("create table t (id int, name varchar(50)); select col_name(object_id('t'), 1)"));
17+
18+
[TestMethod]
19+
public void ColName_SecondColumn_ReturnsName()
20+
=> AreEqual("name", new Simulation().ExecuteScalar("create table t (id int, name varchar(50)); select col_name(object_id('t'), 2)"));
21+
22+
[TestMethod]
23+
public void ColName_OutOfRange_ReturnsNull()
24+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("create table t (id int); select col_name(object_id('t'), 99)"));
25+
26+
[TestMethod]
27+
public void ColName_UnknownTable_ReturnsNull()
28+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select col_name(99999, 1)"));
29+
30+
[TestMethod]
31+
public void ColName_NullTableId_ReturnsNull()
32+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select col_name(null, 1)"));
33+
34+
[TestMethod]
35+
public void ColLength_Int_Returns4()
36+
=> AreEqual((short)4, new Simulation().ExecuteScalar("create table t (id int); select col_length('t', 'id')"));
37+
38+
[TestMethod]
39+
public void ColLength_Bigint_Returns8()
40+
=> AreEqual((short)8, new Simulation().ExecuteScalar("create table t (id bigint); select col_length('t', 'id')"));
41+
42+
[TestMethod]
43+
public void ColLength_Varchar50_Returns50()
44+
=> AreEqual((short)50, new Simulation().ExecuteScalar("create table t (name varchar(50)); select col_length('t', 'name')"));
45+
46+
[TestMethod]
47+
public void ColLength_Nvarchar50_Returns100Bytes()
48+
=> AreEqual((short)100, new Simulation().ExecuteScalar("create table t (name nvarchar(50)); select col_length('t', 'name')"));
49+
50+
[TestMethod]
51+
public void ColLength_UnknownColumn_ReturnsNull()
52+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("create table t (id int); select col_length('t', 'missing')"));
53+
54+
[TestMethod]
55+
public void ColLength_NullArg_ReturnsNull()
56+
=> AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select col_length(null, 'col')"));
57+
}

0 commit comments

Comments
 (0)