Skip to content

Commit d7442e4

Browse files
committed
BACPAC loader — long tail: ISJSON + DECOMPRESS + scalar-UDF WITH SCHEMABINDING / EXECUTE AS + Filegroup skip + index-after-computed reorder. Five small fixes whose combined effect closed most of the remaining WWI gap inventory and cascade-cleared AW's deferred-computed-column gap that I'd thought required a larger parser refactor. WWI gap census drops from 8 categories to 3; AW SqlComputedColumn cascade-clears, lifting AW indexes 89 → 93 and AW scalar UDF count 10 → 11.
**`IsJson` expression** (`SqlServerSimulator/Parser/Expressions/IsJson.cs`). Wraps `System.Text.Json.JsonDocument.Parse` in try/catch; returns int 1 on parse success, 0 on JsonException, NULL on NULL input, 0 on non-string input. Dispatched from `Expression.cs`'s length-6 switch (`"ISJSON" => new IsJson(context)`). 2-arg shape (with `VALUE | ARRAY | OBJECT | SCALAR` modifier) intentionally not modeled — DACFx-emitted CHECK constraints use the 1-arg form. Unblocks WWI's `CK_Sales_Invoices_ReturnedDeliveryData_Must_Be_Valid_JSON` CHECK constraint. **`Decompress` expression** (`SqlServerSimulator/Parser/Expressions/Decompress.cs`). `System.IO.Compression.GZipStream.CopyTo` on a varbinary argument; returns the inflated bytes as varbinary. NULL input → NULL. Invalid gzip stream returns NULL pending a proper Msg 9803 factory (DACFx-emitted views invoke DECOMPRESS only on known-compressed columns, so the wording doesn't matter for the loader baseline). Dispatched from the length-10 switch. Unblocks WWI's `Website.VehicleTemperatures` view (which casts DECOMPRESS output to nvarchar(1000)). **Scalar-UDF `ParseScalarTail` extended WITH options** (`SqlServerSimulator/Simulation/Simulation.CreateFunction.cs`). The old code accepted only `WITH RETURNS NULL ON NULL INPUT` and hard-rejected everything else. Rewrote into a comma-separated multi-option loop accepting `RETURNS NULL ON NULL INPUT` / `SCHEMABINDING` / `ENCRYPTION` / `EXECUTE AS CALLER|SELF|OWNER|'name'` — only the first affects runtime semantics (NULL-propagation skips the body); the others parse-and-discard since the simulator has no principal model and no schema-binding-dependency tracking. Unblocks WWI's `Website.CalculateCustomerPrice` (`WITH EXECUTE AS OWNER`) — but the surprise win was AW's `dbo.ufnLeadingZeros` (`WITH SCHEMABINDING`), which had been silently failing CREATE and cascade-blocking every AW computed column that referenced it. Net effect on AW: SqlComputedColumn category clears from Skipped entirely; index count 89 → 93 (the previously-blocked filtered indexes now resolve); funcs 10 → 11. **`SqlFilegroup` skip dispatch** (`ModelXmlReader.cs`). New phase-1 no-op handler — `("SqlFilegroup", 1) => Run(static () => { })` — and `IsHandledByAnotherPhase` updated. Filegroups are storage-layout metadata; the simulator has a single in-process heap so they're parse-and-discard. **`SqlIndex` dispatch moves phase 5 → phase 8** (`ModelXmlReader.cs`). Phase 8 also hosts deferred computed columns; element-document-order in WWI / AW puts SqlTable's deferred-computed-column ALTER passes ahead of SqlIndex emissions, so filtered indexes whose predicates reference computed columns now resolve correctly. The phase-comment block is rewritten: phase 5 is now unused (deliberately — keeping the existing 9-phase numbering); phase 8 explicitly carries both SqlTable computed-column ALTERs and SqlIndex emissions. AW reports the gain as +4 indexes; WWI reports the gain as +3 (the `IX_*_IsFinalized` / `IX_*_ConfirmedDeliveryTime` filtered indexes). **Test updates**: - `Load_AW_Indexes_Land` — expected count 89 → 93; Skipped expected count 6 → 2; the two remaining are view-targeted (indexed views, SCHEMABINDING-gated). - `Load_AW_Programmable_Counts` — funcs 10 → 11. - `Load_AW_Unhandled_Elements_Recorded_In_Skipped` — `IsNotEmpty(SqlComputedColumn)` assertion removed (category cleared); comment rewritten to call out the SCHEMABINDING cascade and the remaining permission-statement gap. - `Load_WWI_Known_Gaps_Recorded_In_Skipped` — rewritten to assert *absence* of SqlIndex / SqlView / SqlScalarFunction / SqlFilegroup categories (and keep the existing SqlTableType / SqlProcedure / SqlComputedColumn absence assertions). The 1 remaining SqlCheckConstraint and 2 SqlPermissionStatement entries are documented inline. **Doc updates**: `CLAUDE.md`'s BACPAC pointer refreshes the WWI census to add 94/94 indexes + 3/3 views + 1/1 scalar function. `docs/claude/bacpac-prerequisites.md` gets a new step 7 covering this bundle; step 10's gap inventory drops the resolved categories and documents the 3 remaining (SqlExtendedProperty cascade, SqlPermissionStatement encryption keys, SqlCheckConstraint parser gap on paren-wrapped value subexpressions). Status section's WWI summary picks up the new element counts.
1 parent 8454339 commit d7442e4

15 files changed

Lines changed: 525 additions & 75 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Full `DbDataReader` contract. Typed accessors read `SqlValue` directly via the c
133133

134134
Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger: read the linked file on demand when working in the matching area.
135135

136-
- **Adding or modifying a built-in scalar** (math, date, current-time, `*FROMPARTS`/EOMONTH, AT TIME ZONE, CONCAT/CONCAT_WS, string `+`, ASCII/UNICODE/CHAR/NCHAR char-code, PATINDEX/STUFF/QUOTENAME/REPLICATE/SPACE/FORMAT, ISNUMERIC/ISDATE/RAND, STRING_SPLIT) → [`docs/claude/scalars.md`](docs/claude/scalars.md).
136+
- **Adding or modifying a built-in scalar** (math, date, current-time, `*FROMPARTS`/EOMONTH, AT TIME ZONE, CONCAT/CONCAT_WS, string `+`, ASCII/UNICODE/CHAR/NCHAR char-code, PATINDEX/STUFF/QUOTENAME/REPLICATE/SPACE/FORMAT, ISNUMERIC/ISDATE/RAND, STRING_SPLIT, COMPRESS/DECOMPRESS) → [`docs/claude/scalars.md`](docs/claude/scalars.md).
137137
- **Touching `SqlType.Promote` / `PromoteForArithmetic` / decimal precision-scale formulas / int↔string promotion**[`docs/claude/arithmetic.md`](docs/claude/arithmetic.md).
138138
- **Touching `Cast` or coercion error paths** (CAST/CONVERT narrow targets, TRY_CAST/TRY_CONVERT swallow set) → [`docs/claude/casting.md`](docs/claude/casting.md).
139139
- **Changing `Selection`, aggregates, window functions, set ops, CASE, OFFSET/FETCH**[`docs/claude/query.md`](docs/claude/query.md).
@@ -164,7 +164,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
164164
- **Touching `XmlSqlType` (singleton in `Storage/XmlType.cs`, SystemTypeId=241, IsLob=true), `XmlSchemaCollection` storage class + `Schema.XmlSchemaCollections` dict + `Database.AllocateXmlCollectionId` (seeds at 65536 per probe), `HeapColumn.XmlSchemaCollection` per-column binding, the `xml`-recognizing arms of `SqlType.ResolveSimpleKeyword` / `Precedence` / `SystemTypeId`, the `xml(name)` / `xml(CONTENT name)` / `xml(DOCUMENT name)` column-type parsing in `ParseOneColumnIntoLists` (via `PeekIsXmlSchemaArgument` / `ParseXmlSchemaCollectionArgument`), `Simulation.Xml.cs` partial (CREATE XML SCHEMA COLLECTION / DROP XML SCHEMA COLLECTION / CREATE [PRIMARY] XML INDEX / FOR PATH|VALUE|PROPERTY secondary form), the `Xml` contextual keyword + `Keyword.Primary` dispatch in `TryParseCreate`, `XmlIndex` / `XmlSecondaryIndexType` storage on `HeapTable.XmlIndexes`, the `XmlMethodCall` expression in `Parser/Expressions/XmlMethodCall.cs` (closed accept-list of `value` / `nodes` / `query` / `exist` / `modify`, parses cleanly, throws `NotSupportedException` at `Run`), or the `sys.xml_schema_collections` / `sys.xml_indexes` catalog views** → [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md) (the "`xml` data type + XML schema collections + XML methods + XML indexes" section carries the implementation detail).
165165
- **Touching `GeographySqlType` / `GeometrySqlType` (singletons in `Storage/SpatialType.cs`, both inherit `SpatialSqlType : SqlType(SqlTypeCategory.String)`, SystemTypeId=240, UserTypeId=130/129, IsLob=true), `SqlValue.FromGeography` / `FromGeometry` factories + the spatial branches in `FromString`, the `geography` / `geometry`-recognizing arms of `SqlType.ResolveSimpleKeyword` / `Precedence` / `SystemTypeId` / `UserTypeId`, the `SpatialMethodCall` expression in `Parser/Expressions/SpatialMethodCall.cs` (broad ~70-name accept-list of OGC + Microsoft-extension methods; parses cleanly + throws `NotSupportedException` at `Run` except `.ToString()` returns the stored WKT), the `SpatialStaticCall` expression in `Parser/Expressions/SpatialStaticCall.cs` (`geography::Parse(wkt)` / `geography::STGeomFromText(wkt, srid)` / `geometry::Point(x, y, srid)` work; other static methods throw), the `::` dispatch for `geography`/`geometry` in `Expression.cs` alongside `hierarchyid::`, the `.ToString()` polymorphic delegation in `HierarchyIdMethodCall.Run` (spatial receivers return WKT through the spatial path), the `SpatialIndex` storage class + `SpatialIndexKind` enum on `HeapTable.SpatialIndexes`, the `Simulation.Spatial.cs` partial (CREATE SPATIAL INDEX with USING / BOUNDING_BOX / GRIDS / CELLS_PER_OBJECT parsing + default tessellation_scheme), the `Spatial` contextual keyword routing in `TryParseCreate`, or the `sys.spatial_indexes` (23-col probe-confirmed) / `sys.spatial_index_tessellations` (16-col probe-confirmed) / `sys.spatial_reference_systems` (6-col, empty seed) catalog views** → [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md) (the "`geography` / `geometry` data types" section carries the implementation detail).
166166
- **Adding a new top-level statement parser or changing the dispatch loop's statement-separator rules**[`docs/claude/grammar.md`](docs/claude/grammar.md) + [`docs/claude/control-flow.md`](docs/claude/control-flow.md).
167-
- **Working on BACPAC import (`Simulation.FromBacpac(string, out BacpacLoadResult)` + Stream overload, internal until externally consumed) — the model.xml dispatcher in `SqlServerSimulator/Storage/Bacpac/ModelXmlReader.cs` (9 phases: DB options + schemas + UDDTs + **sequences + roles + table types** → tables → constraints → FKs → indexes → views → programmable objects → **deferred computed columns via ALTER TABLE ADD AS** → extended properties → BCP data), the `BacpacReader` + `BcpRowReader` + `HierarchyIdWireDecoder` data path, the `BacpacLoadResult.Skipped` + `ElementCounts` + `Warnings` + `TableColumnIsAlias` diagnostics carriers, the resilient-loader contract (per-element exceptions land on Skipped with `"Load failed: …"` prefix and don't abort the load; computed-column failures use `"Deferred: …"` prefix so the AW guard stays meaningful), the **BCP-filters-computed-columns contract** (`BacpacReader.LoadRowsFromBcp` strips columns with `HeapColumn.Computed != null` before passing to `BcpRowReader`/`RowEncoder` — DACFx-emitted BCP files exclude computed columns from the wire layout regardless of PERSISTED; the loader emits computed columns *without* the PERSISTED qualifier so the simulator recomputes on every read), the wire-format matrix (fixed-raw / 1-byte-prefix / 2-byte-prefix / 8-byte-prefix tiers — see [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md) "BCP wire format" section)** → [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md). Loader baseline ships (2026-05-15) with AdventureWorks2025 at **100% row coverage** (760,167 / 760,167; zero BCP-file failures); WideWorldImporters-Standard loads end-to-end with **48/48 tables + 26/26 sequences + 9/9 roles + 4/4 table types + 8/8 computed columns + 42/42 procedures + 4.7M rows**, remaining Skipped categories cataloged in `Load_WWI_Known_Gaps_Recorded_In_Skipped` (81 SqlExtendedProperty, 3 SqlIndex, 2 SqlCheckConstraint, 2 SqlPermissionStatement, 1 each SqlView / SqlScalarFunction / SqlFilegroup / SqlDatabaseOptions-Collation). MAX types (varchar(MAX) / nvarchar(MAX) / varbinary(MAX)) + xml + CLR-UDT family all use the simple inline 8-byte-prefix wire shape (NOT TDS-PLP chunked form); plain `bit` uses 1-byte length prefix regardless of nullability (probe-confirmed via Production.Document.FolderFlag, the sole non-UDDT bit column in AW). Hierarchyid wire decoder covers AW's [0..79] positive-ordinal envelope via a 4-prefix order-preserving prefix code (`01` / `100` / `101` / `110`); negative ordinals + ordinals ≥ 80 + dotted sub-ordinals raise `NotSupportedException` for a follow-up bundle to extend. Geography rows load with `SpatialLocation = NULL` (WKB→WKT decoding deferred).
167+
- **Working on BACPAC import (`Simulation.FromBacpac(string, out BacpacLoadResult)` + Stream overload, internal until externally consumed) — the model.xml dispatcher in `SqlServerSimulator/Storage/Bacpac/ModelXmlReader.cs` (9 phases: DB options + schemas + UDDTs + **sequences + roles + table types + filegroups (skip)** → tables → constraints → FKs → (unused) → views → programmable objects → **deferred computed columns + indexes** → extended properties → BCP data), the `BacpacReader` + `BcpRowReader` + `HierarchyIdWireDecoder` data path, the `BacpacLoadResult.Skipped` + `ElementCounts` + `Warnings` + `TableColumnIsAlias` diagnostics carriers, the resilient-loader contract (per-element exceptions land on Skipped with `"Load failed: …"` prefix and don't abort the load; computed-column failures use `"Deferred: …"` prefix so the AW guard stays meaningful), the **BCP-filters-computed-columns contract** (`BacpacReader.LoadRowsFromBcp` strips columns with `HeapColumn.Computed != null` before passing to `BcpRowReader`/`RowEncoder` — DACFx-emitted BCP files exclude computed columns from the wire layout regardless of PERSISTED; the loader emits computed columns *without* the PERSISTED qualifier so the simulator recomputes on every read), the wire-format matrix (fixed-raw / 1-byte-prefix / 2-byte-prefix / 8-byte-prefix tiers — see [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md) "BCP wire format" section)** → [`docs/claude/bacpac-prerequisites.md`](docs/claude/bacpac-prerequisites.md). Loader baseline ships (2026-05-15) with AdventureWorks2025 at **100% row coverage** (760,167 / 760,167; zero BCP-file failures); WideWorldImporters-Standard loads end-to-end with **48/48 tables + 26/26 sequences + 9/9 roles + 4/4 table types + 8/8 computed columns + 42/42 procedures + 94/94 indexes + 3/3 views + 1/1 scalar function + 4.7M rows**, remaining Skipped categories cataloged in `Load_WWI_Known_Gaps_Recorded_In_Skipped` (81 SqlExtendedProperty, 2 SqlPermissionStatement, 1 SqlCheckConstraint (paren-wrapped value subexpression at boolean-parser level), 1 SqlDatabaseOptions-Collation). MAX types (varchar(MAX) / nvarchar(MAX) / varbinary(MAX)) + xml + CLR-UDT family all use the simple inline 8-byte-prefix wire shape (NOT TDS-PLP chunked form); plain `bit` uses 1-byte length prefix regardless of nullability (probe-confirmed via Production.Document.FolderFlag, the sole non-UDDT bit column in AW). Hierarchyid wire decoder covers AW's [0..79] positive-ordinal envelope via a 4-prefix order-preserving prefix code (`01` / `100` / `101` / `110`); negative ordinals + ordinals ≥ 80 + dotted sub-ordinals raise `NotSupportedException` for a follow-up bundle to extend. Geography rows load with `SpatialLocation = NULL` (WKB→WKT decoding deferred).
168168

169169
## Not modeled
170170

SqlServerSimulator.Tests.Internal/Storage/BacpacLoaderTests.cs

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,20 @@ public void Load_AW_Element_Counts_Match_Probe()
9393
public void Load_AW_Unhandled_Elements_Recorded_In_Skipped()
9494
{
9595
_ = LoadAdventureWorks(out var diagnostics);
96-
// Phases A-F handled types are off Skipped; the few remaining bucket
97-
// types (SqlComputedColumn, SqlPermissionStatement, full-text, XML
98-
// schema collections / indexes, filegroup-hosted extended properties)
99-
// appear awaiting their own bundles.
96+
// Phases A-F handled types are off Skipped. Computed columns +
97+
// dependent filtered indexes used to be the dominant remaining
98+
// gap; the 2026-05-15 WITH SCHEMABINDING / EXECUTE AS scalar-UDF
99+
// parser fix unblocked `dbo.ufnLeadingZeros`, which in turn
100+
// unblocked every AW computed column referencing it (and the
101+
// filtered indexes whose predicates depend on those columns).
102+
// SqlPermissionStatement remains — encryption-feature permissions
103+
// not yet wired to the simulator's GRANT surface.
100104
IsNotEmpty(diagnostics.Skipped);
101105
IsEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlTable").ToList());
102106
IsEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlPrimaryKeyConstraint").ToList());
103107
IsEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlForeignKeyConstraint").ToList());
104108
IsEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlCheckConstraint").ToList());
105109
IsEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlDefaultConstraint").ToList());
106-
IsNotEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlComputedColumn").ToList());
107110
IsNotEmpty(diagnostics.Skipped.Where(s => s.ElementType == "SqlPermissionStatement").ToList());
108111
}
109112

@@ -194,16 +197,16 @@ public void Load_AW_Indexes_Land()
194197
command.CommandText = "SELECT COUNT(*) FROM sys.indexes WHERE name LIKE 'AK[_]%' OR name LIKE 'IX[_]%';";
195198
using var reader = command.ExecuteReader();
196199
IsTrue(reader.Read());
197-
// 89 user indexes land = 95 SqlIndex - 2 view-targeted (deferred:
198-
// indexed views need SCHEMABINDING) - 4 that reference computed
199-
// columns the loader currently defers until functions land. The 6
200-
// deferrals all surface as Skipped entries.
201-
AreEqual(89, reader.GetInt32(0));
200+
// 93 user indexes land = 95 SqlIndex - 2 view-targeted (deferred:
201+
// indexed views need SCHEMABINDING). The 2 remaining Skipped
202+
// entries (down from 6 before the 2026-05-15 index-after-computed
203+
// reorder) are the view-targeted indexes.
204+
AreEqual(93, reader.GetInt32(0));
202205
var skippedIndexEntries = diagnostics.Skipped.Where(s => s.ElementType == "SqlIndex").ToList();
203-
HasCount(6, skippedIndexEntries);
204-
// Both deferral reasons appear in Skipped.
205-
IsNotEmpty(skippedIndexEntries.Where(s => s.Reason.Contains("on view '", StringComparison.OrdinalIgnoreCase)).ToList());
206-
IsNotEmpty(skippedIndexEntries.Where(s => s.Reason.Contains("CREATE INDEX", StringComparison.OrdinalIgnoreCase) && s.Reason.Contains("failed", StringComparison.OrdinalIgnoreCase)).ToList());
206+
HasCount(2, skippedIndexEntries);
207+
// Both remaining Skipped entries are view-targeted (indexed views
208+
// need SCHEMABINDING — not modeled).
209+
AreEqual(2, skippedIndexEntries.Count(s => s.Reason.Contains("on view '", StringComparison.OrdinalIgnoreCase)));
207210
}
208211

209212
[TestMethod]
@@ -238,7 +241,7 @@ public void Load_AW_Programmable_Counts()
238241
// separately via SqlDatabaseDdlTrigger.)
239242
AreEqual(11, views);
240243
AreEqual(8, procs);
241-
AreEqual(10, funcs);
244+
AreEqual(11, funcs);
242245
AreEqual(10, triggers);
243246

244247
// Any Skipped programmable entries name their reason (helps the
@@ -598,17 +601,22 @@ public void Load_WWI_Known_Gaps_Recorded_In_Skipped()
598601
_ = LoadWideWorldImporters(out var diagnostics);
599602
var grouped = diagnostics.Skipped.GroupBy(s => s.ElementType)
600603
.ToDictionary(g => g.Key, g => g.Count());
601-
IsFalse(grouped.ContainsKey("SqlTableType"), "SqlTableType is dispatched; should not appear in Skipped.");
602-
IsFalse(grouped.ContainsKey("SqlProcedure"), "All 3 previously-failing sysname-using procs now load (sysname keyword wired into SqlType.ResolveSimpleKeyword).");
603-
IsFalse(grouped.ContainsKey("SqlComputedColumn"), "JSON_QUERY now supported; all 8 WWI computed columns load.");
604-
AreEqual(81, grouped["SqlExtendedProperty"], "Extended properties on table-type columns + filegroup + a handful of computed-col-adjacent columns the loader doesn't yet host-route.");
605-
AreEqual(3, grouped["SqlIndex"], "3 filtered indexes reference computed columns that don't yet exist at phase 5 (computed columns land in phase 8). Reordering indexes to a later phase tripped an unrelated UDF-resolution gap in ALTER TABLE ADD AS for AW; deferred.");
606-
AreEqual(2, grouped["SqlCheckConstraint"], "2 JSON check constraints deferred.");
607-
AreEqual(2, grouped["SqlPermissionStatement"], "GRANT/REVOKE wire-up deferred (same as AW).");
604+
TestContext.WriteLine("=== WWI Skipped (current) ===");
605+
foreach (var kv in grouped.OrderByDescending(k => k.Value))
606+
TestContext.WriteLine($"{kv.Value,5} {kv.Key}");
607+
foreach (var s in diagnostics.Skipped.Where(s => s.ElementType is "SqlCheckConstraint" or "SqlView" or "SqlScalarFunction" or "SqlIndex" or "SqlPermissionStatement"))
608+
TestContext.WriteLine($" [{s.ElementType}] {s.ElementName}: {s.Reason}");
609+
IsFalse(grouped.ContainsKey("SqlTableType"), "SqlTableType dispatched.");
610+
IsFalse(grouped.ContainsKey("SqlProcedure"), "sysname keyword landed; all procs load.");
611+
IsFalse(grouped.ContainsKey("SqlComputedColumn"), "JSON_QUERY landed; all 8 WWI computed columns load.");
612+
IsFalse(grouped.ContainsKey("SqlIndex"), "Indexes moved to phase 8 (after computed columns); filtered indexes on computed columns now resolve.");
613+
IsFalse(grouped.ContainsKey("SqlView"), "DECOMPRESS landed; Website.VehicleTemperatures view loads.");
614+
IsFalse(grouped.ContainsKey("SqlScalarFunction"), "WITH EXECUTE AS OWNER landed on scalar UDFs; Website.CalculateCustomerPrice loads.");
615+
IsFalse(grouped.ContainsKey("SqlFilegroup"), "Filegroup skip-with-diagnostic.");
616+
AreEqual(81, grouped["SqlExtendedProperty"], "Extended properties on table-type columns + a handful of computed-col-adjacent columns the loader doesn't yet host-route.");
617+
AreEqual(1, grouped["SqlCheckConstraint"], "1 CHECK constraint with nested (value_expr)=(1) shape hits the boolean-parser's paren-wrapped-value-subexpression gap.");
618+
AreEqual(2, grouped["SqlPermissionStatement"], "GRANT VIEW ANY COLUMN [ENCRYPTION|MASTER] KEY DEFINITION TO public — encryption-feature permissions deferred (same gap as AW).");
608619
AreEqual(1, grouped["SqlDatabaseOptions"], "Non-default collation deferred.");
609-
AreEqual(1, grouped["SqlView"], "1 WWI view (Website.VehicleTemperatures) uses DECOMPRESS (not modeled).");
610-
AreEqual(1, grouped["SqlScalarFunction"], "1 WWI scalar function uses SCHEMABINDING / WITH EXEC AS clauses beyond RETURNS NULL ON NULL INPUT.");
611-
AreEqual(1, grouped["SqlFilegroup"], "Filegroup not in dispatcher.");
612620
}
613621

614622
[TestMethod]

0 commit comments

Comments
 (0)