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
Explicit RuntimeContext threading: replaces Simulation.ActiveBatch (AsyncLocal<BatchContext?>) with a readonly struct RuntimeContext { ResolveColumn, Batch } passed as the single Expression.Run / BooleanExpression.Run parameter (named runtime at every callsite, symmetric with ParserContext context). Late-bound expressions (CurrentTimeFunction, LastIdentityExpression, RowCountExpression, IdentCurrent) read runtime.Batch.CurrentStatement.UtcNow / runtime.Batch.Connection.LastIdentity / runtime.Batch.CurrentDatabase.HeapTables directly at evaluation time instead of capturing Simulation at parse time — closes the parse-once-run-many side channel that the prior AsyncLocal patched. Selection.Execute(BatchContext, Func<MPN, V>? outerResolver = null) and every internal helper (BuildSqlProjection / BuildAggregateProjectionRows / ProjectSqlRows / ProjectStreaming / ProjectBuffered / ProjectWindowedRows / EnumerateJoinedRows / JoinDriver / CoerceBranchRows + set-op variants / RunRecursiveCte / EnumerateOpenJsonRows / ResolveAcrossTuple + DecodeOrCompute / ComputeOrderKeys) thread batch alongside outerResolver and build new RuntimeContext(perRowResolver, batch) at each Run call. MutationOutputProjection / OutputProjection capture BatchContext at construction so per-row ProjectRow has access; EvaluateComputedColumns / EnforceCheckConstraints take batch. CLAUDE.md's Context layering and Expression evaluation sections drop ActiveBatch references and document the explicit shape.
Copy file name to clipboardExpand all lines: CLAUDE.md
+6-4Lines changed: 6 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,15 +55,15 @@ Readonly struct, up to 4 inline slots (SQL Server's grammar limit). API: `Leaf`,
55
55
`SimulatedSqlException` constructor is private; each error case is an `internal static` factory in a topical partial (`TypeErrors`, `SchemaErrors`, `ConstraintErrors`, `ResolutionErrors`, `QueryErrors`, `SyntaxErrors`). The number lands in `Data["HelpLink.EvtID"]`. **Grep for an existing factory before adding a new one.**
56
56
57
57
### Expression evaluation
58
-
`Expression.Run(columnResolver)` (runtime) and `Expression.GetSqlType(...)` (static, for projection schema) must agree on result type — drift breaks union/CASE/coalesce schema. `BooleanExpression.Run` returns `bool?` (three-valued); WHERE/MERGE-ON exclude UNKNOWN, CHECK passes UNKNOWN. Aggregates: subclass `Aggregator` (`Add(SqlValue)` / `Result()`), register in `AggregateExpression`'s dispatch.
58
+
`Expression.Run(RuntimeContext runtime)` (runtime) and `Expression.GetSqlType(...)` (static, for projection schema) must agree on result type — drift breaks union/CASE/coalesce schema. `RuntimeContext` bundles `ResolveColumn` (per-row column lookup) and `Batch` (the executing `BatchContext`); expressions that need batch / session / database state read `runtime.Batch.*` directly. `BooleanExpression.Run` returns `bool?` (three-valued); WHERE/MERGE-ON exclude UNKNOWN, CHECK passes UNKNOWN. Aggregates: subclass `Aggregator` (`Add(SqlValue)` / `Result()`), register in `AggregateExpression`'s dispatch.
59
59
60
60
### Context layering
61
61
Five scopes, one home each. **Add new state to whichever class matches its true scope** — when in doubt, the rule of thumb is: who outlives whom?
62
62
63
-
-**`Simulation`** = server / instance. Process-shared system tables (`SystemHeapTables`), `NEWSEQUENTIALID` anchor + counter, the `Databases` dictionary, and the `ActiveBatch``AsyncLocal` pointer published by the dispatch loop. Public surface (`Simulation` ctor + `CreateDbConnection()`) stays on this class.
63
+
-**`Simulation`** = server / instance. Process-shared system tables (`SystemHeapTables`), `NEWSEQUENTIALID` anchor + counter, the `Databases` dictionary. Public surface (`Simulation` ctor + `CreateDbConnection()`) stays on this class.
64
64
-**`Database`** (internal) = one database hosted by the server instance. `HeapTables`, `CompatibilityLevel`, `VerboseTruncationWarnings`, `rowVersionCounter` (per-DB `@@DBTS`). Every `Simulation` ships with one entry named `Simulation.DefaultDatabaseName` (`"simulated"`); a future `USE <db>` adds entries to the dictionary.
65
65
-**`SimulatedDbConnection`** = session. `CurrentDatabase` pointer, `CurrentTransaction`, `LastIdentity` (`SCOPE_IDENTITY()` / `@@IDENTITY`), `LastStatementRowCount` (`@@ROWCOUNT`), `IdentityInsertTable`, `TraceFlags`, `IsVerboseTruncationActive()` (reads its own `TraceFlags` + the current database's compat / scoped-config).
66
-
-**`BatchContext`** (internal, in `Parser/`) = one command execution. Owns the `ParserContext` (parse-time-only scratch — `Token`, `AggregateCollector`, `WindowCollector`, `OuterTypeResolver`, `CteBindings`, `InDefaultClause`, `AllowsWindowExpressions`) and holds batch-lifetime runtime state: `Variables`, `CurrentUndoLog`, plus the per-statement frame `CurrentStatement`. Late-bound expressions (`CurrentTimeFunction`, `LastIdentityExpression`, `RowCountExpression`, `IdentCurrent`) capture `Simulation` at parse time and read through `simulation.ActiveBatch` at runtime, so a column default's `getutcdate()` parsed during one batch correctly resolves against the *executing* batch on later INSERTs.
66
+
-**`BatchContext`** (internal, in `Parser/`) = one command execution. Owns the `ParserContext` (parse-time-only scratch — `Token`, `AggregateCollector`, `WindowCollector`, `OuterTypeResolver`, `CteBindings`, `InDefaultClause`, `AllowsWindowExpressions`) and holds batch-lifetime runtime state: `Variables`, `CurrentUndoLog`, plus the per-statement frame `CurrentStatement`. Threaded explicitly into every `Expression.Run(RuntimeContext runtime)` call via `runtime.Batch` (see "Expression runtime context" below).
67
67
-**`StatementContext`** (internal, in `Parser/`) = the dispatch loop's per-statement frame. Allocated once per batch and overwritten in place at the top of each iteration; holds `UtcNow` (the per-statement-freeze the time scalars read). Stored-proc EXEC / TRY-CATCH frames slot in here when added.
68
68
69
69
**Don't stack misfit state into these buckets unthinkingly**: when adding fields, ask which scope it actually belongs to. If none fits, that's the signal to introduce the missing scope rather than squat on a neighbor. Multi-database is structurally supported but exercised only by the default entry; `USE <db>` is the trigger to populate the dictionary properly.
@@ -287,7 +287,9 @@ Variable references resolve at runtime via a captured `VariableSlot` — require
287
287
288
288
**Per-session state lives on `SimulatedDbConnection`** (see the Context layering section for the full five-scope map): alongside `@@ROWCOUNT`, the connection carries `LastIdentity` (`SCOPE_IDENTITY()` / `@@IDENTITY`), `IdentityInsertTable` (active `SET IDENTITY_INSERT` table), `TraceFlags` (`DBCC TRACEON(N)`), `IsVerboseTruncationActive()` (reads its own `TraceFlags` plus the current database's compat / scoped-config), `CurrentDatabase` (per-session pointer into `Simulation.Databases`), and `CurrentTransaction`. Two connections fanned from one `Simulation` are isolated — matches SQL Server's session = ADO.NET connection scoping.
289
289
290
-
**Late-binding via `Simulation.ActiveBatch`**: `CurrentTimeFunction`, `LastIdentityExpression`, `RowCountExpression`, and `IdentCurrent` capture the **`Simulation`** at parse time and look up the runtime-active batch via `Simulation.ActiveBatch` (an `AsyncLocal<BatchContext?>` published by the dispatch loop with try/finally save-restore) when `Run` fires. Parse-time-capture-the-connection-or-batch is wrong for any expression embedded in a column default: `HasDefaultValueSql("getutcdate()")` parses once during CREATE TABLE, then every later INSERT runs in a different batch, and the default has to read *that* batch's per-statement freeze (`ActiveBatch.CurrentStatement.UtcNow`), not the long-gone CREATE-time batch's.
290
+
**Expression runtime context** (`RuntimeContext`): `Expression.Run` takes a single `RuntimeContext runtime` bundling `ResolveColumn` (per-row column lookup) and `Batch` (the executing `BatchContext`). Most expressions only touch `runtime.ResolveColumn`; the few that need batch-, session-, or database-scoped state (`CurrentTimeFunction`, `LastIdentityExpression`, `RowCountExpression`, `IdentCurrent`) read `runtime.Batch.CurrentStatement.UtcNow`, `runtime.Batch.Connection.LastIdentity`, etc. directly. Explicit threading replaced an earlier `Simulation.ActiveBatch``AsyncLocal` — necessary because column defaults parsed once at CREATE TABLE run on every later INSERT, possibly from a different batch / connection, and parse-time capture froze them on the wrong batch.
291
+
292
+
`BooleanExpression.Run(RuntimeContext)` mirrors the same shape (returning `bool?` for three-valued logic). `Selection.Execute(BatchContext, Func<MPN, V>? outerResolver)` threads `batch` through the row pipeline so internal helpers can build `new RuntimeContext(perRowResolver, batch)` at each Run call site. The only callsites that don't have a `BatchContext` in scope are EF-relevant SCALAR expressions evaluated at parse time (TOP / OFFSET / FETCH literals, column-default eval for `BuildSynthesizedSqlRow`) — those construct a `new RuntimeContext(throwingResolver, context.Batch)` from the parser context's batch.
291
293
292
294
**Compound assignment** (`SET @v += expr` etc.) and **table variables** (`DECLARE @t TABLE (...)`) aren't modeled — rewrite as `SET @v = @v + expr` for the former; the latter is a separate bundle.
0 commit comments