You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
sys.* baseline: ship sys.schemas / sys.tables / sys.objects as virtual catalog views projecting from live metadata. Schemas pre-populate at conventional ids (dbo=1, INFORMATION_SCHEMA=3, sys=4); user schemas start at 5. HeapTable gains SchemaId / CreateDate / ModifyDate; KeyConstraint / CheckConstraint gain ObjectId (allocated at CREATE TABLE alongside the table — so PK/UQ/CHECK rows appear in sys.objects with parent_object_id linking to the table). Catalog views live in Simulation.CatalogViews (process-static); FROM-source parser routes sys. via BatchContext.TryResolveCatalogView and wraps the result as a Selection.LateralPlan that re-runs on each Execute. SCHEMA_ID() scalar ships alongside. CREATE TABLE in sys/INFORMATION_SCHEMA raises NotSupportedException.
Copy file name to clipboardExpand all lines: CLAUDE.md
+15-3Lines changed: 15 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -395,17 +395,17 @@ Probe-confirmed semantics (2026-05-11) the simulator handles correctly because e
395
395
**Transactional rollback is supported** via a single `HeapTruncation` undo entry that snapshots the pre-truncate `Pages` / `LobPages` lists AND each identity column's pre-truncate high-water mark. `BEGIN TRAN; TRUNCATE; ROLLBACK` restores both — including the identity counter. **This diverges from the simulator's general "identity counters bypass the undo log" rule, which is INSERT-only**: INSERT-advanced counters stay advanced through rollback (probe-confirmed for real SQL Server too); TRUNCATE's explicit reset action gets undone on rollback. Outside an explicit transaction, TRUNCATE commits immediately (no log entry — same pattern as DROP TABLE on regular tables).
`CREATE SCHEMA <name>` adds an entry to `Database.Schemas`; subsequent two-part references (`SELECT * FROM audit.t`, `INSERT audit.t VALUES (…)`, every DML / DDL targeting a table) route through it. Unqualified references fall back to `Database.DefaultSchemaName` (`"dbo"`), which every `Database` ships pre-populated with. The 9 table-lookup sites (Selection FROM, Insert/Update/Delete/Merge targets, CREATE / DROP / TRUNCATE, SET IDENTITY_INSERT, IDENT_CURRENT, SELECT INTO) all share one parser (`BatchContext.ParseObjectName`) and one resolver pair (`BatchContext.TryResolveTable` for lookup, `BatchContext.TryResolveSchema` for CREATE-shape callsites that need the dict). Probed against SQL Server 2025 (2026-05-11).
398
+
`CREATE SCHEMA <name>` adds an entry to `Database.Schemas`; subsequent two-part references (`SELECT * FROM audit.t`, `INSERT audit.t VALUES (…)`, every DML / DDL targeting a table) route through it. Unqualified references fall back to `Database.DefaultSchemaName` (`"dbo"`), which every `Database` ships pre-populated with. The 9 table-lookup sites (Selection FROM, Insert/Update/Delete/Merge targets, CREATE / DROP / TRUNCATE, SET IDENTITY_INSERT, IDENT_CURRENT, SELECT INTO) all share one parser (`BatchContext.ParseObjectName`) and one resolver pair (`BatchContext.TryResolveTable` for lookup, `BatchContext.TryResolveSchema` for CREATE-shape callsites that need the dict). Every `Database` ships with three pre-populated schemas at conventional ids: `dbo=1`, `INFORMATION_SCHEMA=3`, `sys=4`. User schemas allocate ids starting at 5 from `Database.AllocateSchemaId()` (a counter seeded so the next-allocated value is 5). Probed against SQL Server 2025 (2026-05-11).
399
399
400
400
-**Duplicate `CREATE SCHEMA`** (case-insensitive) → **Msg 2714** (`"There is already an object named '<n>' in the database."` — same factory as duplicate CREATE TABLE; SQL Server shares the namespace).
401
401
-**Reserved schema names** (`dbo`, `sys`, `INFORMATION_SCHEMA`) → **Msg 2760** (`"The specified schema name \"<n>\" either does not exist or you do not have permission to use it."`). Wording is quirky for a CREATE (says "does not exist"), but probe-confirmed verbatim — real SQL Server resolves the principal first and these schemas tie to system principals.
402
402
-**Three-part `db.schema.t`** validates the db segment against `CurrentDatabase.Name` (case-insensitive); mismatch → Msg 208. **Four-part `server.db.schema.t`** always returns false from `TryResolveSchema` (linked-server names aren't modeled — surfaces as Msg 208 / Msg 3701 / NULL from OBJECT_ID per callsite). Empty middle segment (`tempdb..#foo`) is silently compressed by `ParseObjectName`, so a 2-part name pops out — preserves the existing DROP TABLE behavior for temp-table qualifiers.
403
403
-**`CREATE TABLE schema.t`** where `schema` doesn't exist → **Msg 2760** (target schema for the create must already exist). Distinct from FROM / INSERT / UPDATE / DELETE / MERGE / DROP / TRUNCATE access which use 208 / 3701 / 4701 respectively.
404
404
-**`AUTHORIZATION owner`** and the embedded `<schema_element>` list (CREATE TABLE / VIEW / GRANT nested inside CREATE SCHEMA) aren't modeled — `AUTHORIZATION` raises `NotSupportedException`; trailing statement-starting tokens (CREATE / SELECT / INSERT / etc.) parse as their own statements in the same batch (deviates from real SQL Server's strict greedy-consume but reaches the same end state for the common idiom).
405
405
-**No "first in batch" enforcement** — real SQL Server raises Msg 111 if CREATE SCHEMA isn't the first statement, tied to its greedy schema_element grammar. The simulator's dispatch already treats each statement as independent, so CREATE SCHEMA in any position works.
406
-
-**`sys` schema isn't modeled**: `sys.schemas` / `sys.tables` etc. all raise Msg 208. System tables (`systypes` etc.) remain reachable only as bare 1-part names through `SystemHeapTables`.
406
+
-**`sys` schema hosts catalog views** (sys.schemas / sys.tables / sys.objects — see the dedicated section below). Adding a user table via `CREATE TABLE sys.foo (…)` raises `NotSupportedException` ("Cannot CREATE TABLE in the built-in 'sys' schema"); same rejection for `INFORMATION_SCHEMA`. The `sys``Schema` entry exists in `Database.Schemas` to carry its conventional id and to be reachable from `sys.schemas`, but its `HeapTables` dict stays empty — catalog views live in a separate `Simulation.CatalogViews` registry.
407
407
-**Error wording**: Msg 208 wraps the qualified name in single quotes (`Invalid object name 'badschema.t'.`); Msg 3701 (DROP) does the same; Msg 4701 (TRUNCATE) carries only the leaf (probe-confirmed asymmetric — distinct error path).
408
-
-**DROP SCHEMA, ALTER SCHEMA TRANSFER, sys.schemas / INFORMATION_SCHEMA.SCHEMATA**: not modeled.
408
+
-**DROP SCHEMA, ALTER SCHEMA TRANSFER, INFORMATION_SCHEMA.SCHEMATA**: not modeled.
409
409
410
410
### Object identifiers + `OBJECT_ID()`
411
411
Every `HeapTable` carries a stable per-database `int ObjectId` assigned at CREATE time from `Database.AllocateObjectId()` (a `Database`-scoped `Interlocked.Increment` counter seeded at 100). DROP-then-recreate yields a fresh id, matching real SQL Server (probe-confirmed 2026-05-11 — counter never reuses values). The counter bypasses transaction rollback: a rolled-back CREATE TABLE still consumed an id, matching the identity-counter rule. System tables (`SystemHeapTables`) carry a sentinel `ObjectId = -1` — they're process-shared, sit outside per-DB id space, and aren't reachable through `OBJECT_ID()` anyway. Backs `OBJECT_ID()` today; pre-positioned for the upcoming `sys.objects.object_id` catalog column.
@@ -417,6 +417,18 @@ Every `HeapTable` carries a stable per-database `int ObjectId` assigned at CREAT
417
417
-**Bracket-handling fidelity gap**: the runtime-string name parser strips bracket pairs at segment level (`'[dbo].[foo]'` → `dbo`+`foo`) and decodes `]]` → `]` inside brackets — but bracketed segments containing a literal `.` (`'[a.b].[c]'`, the literal-dot case) don't parse correctly (split on `.` happens before bracket-aware tokenization). Rare in practice; revisit if a real app hits it.
418
418
-**Arity**: too-few-args (`OBJECT_ID()`) currently surfaces as Msg 102 (the inner Parse failure path) rather than Msg 174 — same pattern as other built-ins; the simulator doesn't enforce min-args. Too-many-args raises Msg 174 verbatim.
419
419
420
+
### `sys.*` catalog views
421
+
`Simulation.CatalogViews` is a process-static dict of virtual `sys.<view>` projections — currently `sys.schemas`, `sys.tables`, `sys.objects`. Each `CatalogView` carries a fixed `HeapColumn[]` schema and a `Func<BatchContext, IEnumerable<SqlValue[]>>` row generator that runs against live `Database` / `Schema` / `HeapTable` metadata; rows aren't cached, so CREATE / DROP / TRUNCATE changes made earlier in the same batch are visible on the next read. The FROM-source parser detects `sys.<view>` references via `BatchContext.TryResolveCatalogView` (case-insensitive on `sys`, also accepts the 3-part `<currentDb>.sys.<view>` form), wraps the view in `Selection.ForCatalogView`, and threads it as the `FromSource.LateralPlan` — so each Execute re-runs the generator. The `RowEncoder.EncodeRow(HeapColumn[], SqlValue[])` overload bridges the SqlValue-array generator output into the byte stream the FromSource consumes.
422
+
423
+
-**`sys.schemas`** projects `name sysname`, `schema_id int`, `principal_id int NULL` (always NULL — no principal model). Always lists dbo / INFORMATION_SCHEMA / sys plus every user CREATE SCHEMA addition.
-**`sys.objects`** is the superset: one row per `HeapTable` plus one row per `KeyConstraint` (type `PK` / `UQ`) and `CheckConstraint` (type `C `) with `parent_object_id` linking to the owning table. Constraint object_ids allocate from the same `Database.AllocateObjectId` counter as tables, so PK / UQ / CHECK constraints get globally-unique ids that `sys.objects.object_id` surfaces.
426
+
-**`SCHEMA_ID([name])`** scalar: no-arg returns `Database.DboSchemaId` (=1) — the simulator's "caller default schema" (no user model means dbo is universal). With an arg, returns the schema's id or NULL.
427
+
-**Column subset**: real SQL Server's `sys.tables` / `sys.objects` have 30+ columns each; the simulator ships the load-bearing subset that EF / migration tooling and the probe queried. `SELECT *` returns fewer columns than real SQL Server — apps that depend on a specific full-column shape will surface gaps, address those as needed.
428
+
-**Temp tables not in `sys.tables`**: the per-connection `TempTables` dict isn't walked by the row generators (real SQL Server lists temp tables in `tempdb.sys.tables`, which the simulator's single-database model doesn't separate). Catalog views show user tables in `dbo` + any user schema only.
429
+
-**No write paths**: `INSERT sys.tables …` / `UPDATE sys.tables …` / `DROP TABLE sys.tables` etc. all raise Msg 208 — catalog views aren't in `Schema.HeapTables`, so the regular table-lookup miss path fires.
430
+
-**Constraint object_ids**: `KeyConstraint.ObjectId` and `CheckConstraint.ObjectId` are now allocated at CREATE TABLE alongside the table's own id (via `Database.AllocateObjectId()`). The order is: schema resolution → allocate constraint ids (inside `ResolveKeyConstraints` / `ResolveCheckConstraints`) → allocate table id → construct `HeapTable`. The constraint resolvers take a `Database` parameter to thread the allocation.
431
+
420
432
### Local temp tables (`#foo`)
421
433
Per-connection `Dictionary<string, HeapTable> TempTables` on `SimulatedDbConnection`; routed by `BatchContext.TryResolveTable` (`#`-prefix leaf → connection dict, ignoring any qualifier; else the named schema's heap-table dict + flat `SystemHeapTables`). Auto-cleared on `Dispose`, matching real SQL Server's session-close drop. Lifecycle, cross-conn isolation, and Msg 208 from other sessions all probe-confirmed against SQL Server 2025.
0 commit comments