Skip to content

Commit 3947e84

Browse files
committed
Fixed everything blocking SSMS's Script Table as CREATE: signed SET integer values — SET LOCK_TIMEOUT -1 — and ANSI_NULL_DFLT_ON/OFF in the accept-list; sys.partitions carries live per-index row counts with sys.stats mirroring index identities; sys.dm_exec_sessions projects one session-backed row per connection (quoted_identifier / lock_timeout / isolation / context_info / @@rowcount / @@error / open-transaction count); sys.periods projects real temporal-table metadata; sys.trigger_events populates from trigger actions; twenty-odd feature catalog views SMO probes ship as empty alongside the sys.tables/sys.columns columns they join through. Engine fixes: QUOTENAME and CAST/CONVERT-to-string results now carry the input/database collation instead of baseline, two coercible-default operands never conflict in collation resolution, catalog_default/database_default pseudo-collations resolve in COLLATE, bare NULL is typeless in CASE result typing, and DATABASEPROPERTYEX returns typed values for lastgoodcheckdbtime/Updateability, a bacpac-loader fix for double-parenthesized default-constraint definitions. Known deferred gaps documented in catalog-views.md.
1 parent f0e0d20 commit 3947e84

25 files changed

Lines changed: 1957 additions & 35 deletions

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ dotnet test
3333

3434
Every `.csproj` sets `EnforceCodeStyleInBuild=true`, so `dotnet build` runs the IDE / SSS / MSTEST analyzers and fails on violations. No separate `dotnet format` pass — it catches nothing build doesn't. CI matrix: Debug + Release. `obj/` permission errors mean building outside the dev container; `rm -rf obj/ bin/` clears them.
3535

36-
Full suite runs in ~3s; single-test filter (`--filter "FullyQualifiedName~Foo"`) under 100ms. Treat `dotnet test` as a verifier between micro-edits, not a checkpoint.
36+
A full build + test cycle runs 20–30s; single-test filter (`--filter "FullyQualifiedName~Foo"`) stays fast. Still cheap enough to treat `dotnet test` as a verifier between micro-edits, not a checkpoint.
37+
38+
**No large binary files in the repo** (bacpacs included — the WWI/AW `.bacpac` fixtures live gitignored under `.vs/`, local-only). Tests get their data by scripting the key shapes in-code (`CREATE TABLE` + inserts, `BacpacBuilder` for import tests), never by committing a fixture blob.
3739

3840
## Architecture — load-bearing patterns
3941

SqlServerSimulator.Tests/Bacpac/BacpacLoaderTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,27 @@ public void AllFiveConstraintTypes_LandIn_CatalogViews()
132132
AreEqual(1, sim.ExecuteScalar("SELECT COUNT(*) FROM sys.default_constraints;"));
133133
}
134134

135+
// DACFx emits DefaultExpressionScript already parenthesized (e.g.
136+
// "(NEXT VALUE FOR ...)" / "(getdate())"). The loader must not add a second
137+
// pair — real sys.default_constraints.definition carries exactly one outer
138+
// pair, so an already-parenthesized script passes through unwrapped.
139+
[TestMethod]
140+
public void Default_ParenthesizedExpressionScript_NotDoubleWrapped()
141+
{
142+
using var bacpac = BacpacBuilder.Create()
143+
.Table("dbo", "T", t => t
144+
.Column("Id", "int")
145+
.Column("N", "int")
146+
.PrimaryKey("PK_T", "Id")
147+
.Default("DF_T_N", "N", "(1)"))
148+
.Build();
149+
150+
var sim = new Simulation();
151+
sim.ImportBacpac(bacpac, out var diag);
152+
IsEmpty(diag.Skipped);
153+
AreEqual("(1)", sim.ExecuteScalar("SELECT definition FROM sys.default_constraints WHERE name = 'DF_T_N';"));
154+
}
155+
135156
[TestMethod]
136157
public void DatabaseOption_ReadCommittedSnapshot_TogglesFlag()
137158
{
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
2+
3+
namespace SqlServerSimulator;
4+
5+
/// <summary>
6+
/// Engine-behavior fixes the SSMS "Script Table as → CREATE To" barrage
7+
/// surfaced against a non-baseline-collation database (WWI's
8+
/// <c>Latin1_General_100_CI_AS</c>): CAST/CONVERT-to-string result collation,
9+
/// two-coercible-default operand resolution, the <c>catalog_default</c> /
10+
/// <c>database_default</c> pseudo-collations, and untyped-NULL CASE typing.
11+
/// Behaviors probed against SQL Server 2025.
12+
/// </summary>
13+
[TestClass]
14+
public sealed class ScriptTableEngineFixTests
15+
{
16+
// === CASE result-type resolution treats a bare NULL literal as typeless ===
17+
18+
[TestMethod]
19+
public void Case_TypedThen_NullElse_YieldsTypedResult()
20+
=> AreEqual("hello", new Simulation().ExecuteScalar("select case when 1 = 1 then N'hello' else null end"));
21+
22+
[TestMethod]
23+
public void Case_NullThen_TypedElse_YieldsTypedResult()
24+
=> AreEqual("x", new Simulation().ExecuteScalar("select case when 1 = 0 then null else 'x' end"));
25+
26+
[TestMethod]
27+
public void Case_NullElse_DoesNotForceIntConversion()
28+
=> AreEqual("hello", new Simulation().ExecuteScalar(
29+
"create table t (id int not null primary key); insert t values (1); select case when 1 = 1 then N'hello' else null end from t"));
30+
31+
[TestMethod]
32+
public void Case_AllNullResults_StillRaisesMsg8133()
33+
=> new Simulation().AssertSqlError("select case when 1 = 1 then null else null end", 8133);
34+
35+
// === CAST / CONVERT to a character type carries the database collation ===
36+
// (non-baseline database so a bug's baseline result would raise Msg 457/468).
37+
38+
[TestMethod]
39+
public void CastIntToVarchar_ConcatWithLiteral_NoCollationConflict()
40+
=> AreEqual("a1", new Simulation { ServerCollationName = "Latin1_General_100_CI_AS" }
41+
.ExecuteScalar("select 'a' + cast(1 as varchar(10))"));
42+
43+
[TestMethod]
44+
public void ConvertIntToVarchar_ComparedWithLiteral_NoCollationConflict()
45+
=> AreEqual(1, new Simulation { ServerCollationName = "Latin1_General_100_CI_AS" }
46+
.ExecuteScalar<int>("select case when convert(varchar(10), 1) = '1' then 1 else 0 end"));
47+
48+
// === Two coercible-default operands with different collations resolve to
49+
// the database default rather than raising Msg 468 — a baseline-collated
50+
// system-function result meeting a database-collation literal. ===
51+
52+
[TestMethod]
53+
public void CoercibleDefaultOperands_DifferentCollations_NoConflict()
54+
=> AreEqual(1, new Simulation { ServerCollationName = "Latin1_General_100_CI_AS" }
55+
.ExecuteScalar<int>("select case when DATABASEPROPERTYEX(db_name(), 'Updateability') = 'READ_WRITE' then 1 else 0 end"));
56+
57+
// === catalog_default / database_default pseudo-collations ===
58+
59+
[TestMethod]
60+
public void CatalogDefault_PseudoCollation_Resolves()
61+
=> AreEqual(1, new Simulation().ExecuteScalar<int>("select case when 'a' collate catalog_default = 'A' then 1 else 0 end"));
62+
63+
[TestMethod]
64+
public void DatabaseDefault_PseudoCollation_Resolves()
65+
=> AreEqual(1, new Simulation().ExecuteScalar<int>("select case when 'a' collate database_default = 'A' then 1 else 0 end"));
66+
67+
[TestMethod]
68+
public void UnknownCollation_StillRaisesMsg448()
69+
=> new Simulation().AssertSqlError("select 'a' collate not_a_real_collation", 448);
70+
}

SqlServerSimulator.Tests/SetOptionTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,27 @@ public void Composed_WithSubsequentStatement_AcceptsBoth()
102102
// hand off cleanly.
103103
AreEqual(1, new Simulation().ExecuteScalar("SET ANSI_NULLS ON; SELECT 1"));
104104
}
105+
106+
[TestMethod]
107+
public void LockTimeout_NegativeOne_ParsesAndReadsBack()
108+
=> AreEqual(-1, new Simulation().ExecuteScalar("SET LOCK_TIMEOUT 5000 SET LOCK_TIMEOUT -1 SELECT @@LOCK_TIMEOUT"));
109+
110+
[TestMethod]
111+
public void Textsize_NegativeOne_Parses()
112+
=> AreEqual(1, new Simulation().ExecuteScalar("SET TEXTSIZE -1 SELECT 1"));
113+
114+
[TestMethod]
115+
public void SmoScriptingPreamble_Parses()
116+
{
117+
// The exact semicolon-less SET barrage SMO's scripting connection
118+
// sends before Script-As (harvested from the SSMS shakedown,
119+
// 2026-07-16): signed LOCK_TIMEOUT and ANSI_NULL_DFLT_ON were the
120+
// two gaps it surfaced.
121+
AreEqual(1, new Simulation().ExecuteScalar(
122+
"SET ROWCOUNT 0 SET TEXTSIZE 2147483647 SET NOCOUNT OFF SET CONCAT_NULL_YIELDS_NULL ON SET ARITHABORT ON"
123+
+ " SET LOCK_TIMEOUT -1 SET QUERY_GOVERNOR_COST_LIMIT 0 SET DEADLOCK_PRIORITY NORMAL"
124+
+ " SET TRANSACTION ISOLATION LEVEL READ COMMITTED SET ANSI_NULLS ON SET ANSI_NULL_DFLT_ON ON"
125+
+ " SET ANSI_PADDING ON SET ANSI_WARNINGS ON SET CURSOR_CLOSE_ON_COMMIT OFF SET IMPLICIT_TRANSACTIONS OFF"
126+
+ " SET QUOTED_IDENTIFIER ON SELECT 1"));
127+
}
105128
}

0 commit comments

Comments
 (0)