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
INSERT … SELECT source: Selection.Parse + Execute, full-buffer materialization via RowDecoder, parse-time projection-count check via selection.Schema.Length (Msg 120 / Msg 121). Source-kind dispatch (Values vs Select) lives at the post-OUTPUT-clause cursor; both branches funnel into one shared per-row encode loop that operates on SqlValue[] directly. Self-insert is safe — source materializes before any destination write. WHERE / JOIN / GROUP BY / aggregates / ORDER BY / TOP / OFFSET-FETCH / UNION all work on the source side. Statement-level atomicity carries over: a CHECK / NOT NULL / PK / UNIQUE violation mid-source rolls back every row from the SELECT.
Copy file name to clipboardExpand all lines: CLAUDE.md
+11Lines changed: 11 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -339,6 +339,17 @@ Three entry points share one per-connection undo log: implicit (statement-level
339
339
### `rowversion` (legacy synonym `timestamp`)
340
340
8-byte big-endian database-scoped monotonic counter; `Simulation.AllocateRowVersion` advances on every INSERT into a rowversion-bearing table and every UPDATE that affects a row in one. Storage type name surfaces as `timestamp` in `information_schema` regardless of declaration keyword. Explicit insert → Msg 273; explicit update → Msg 272; second column on a table → Msg 2738. Outbound CAST: `varbinary(N)` / `binary(N)` copy the 8 bytes; `bigint` reads big-endian. `Promote(RowVersion, Varbinary)` → `Varbinary` so EF Core's `WHERE [rv] = @originalRv` optimistic-concurrency parameter works directly. `EF Core [Timestamp]` SaveChanges round-trips end-to-end via `UPDATE ... OUTPUT INSERTED.[RowVersion] WHERE [Id] = @p AND [RowVersion] = @originalRv`.
341
341
342
+
### INSERT … SELECT
343
+
`INSERT [INTO] target [(cols)] SELECT …` accepts the same Selection grammar as a top-level SELECT — WHERE / JOIN / GROUP BY / aggregates / ORDER BY / TOP / OFFSET-FETCH / UNION / INTERSECT / EXCEPT all work on the source side. Probe-confirmed against SQL Server 2025 on 2026-05-10.
344
+
345
+
Source-kind dispatch happens after the OUTPUT-clause parse: a `Values`-keyword token routes to the existing tuple-parsing path; a `Select`-keyword token routes to `Selection.Parse(…).Execute()`. Both paths funnel into one shared per-row encode loop that handles defaults / identity / rowversion / computed / constraints / OUTPUT — VALUES eagerly evaluates each cell expression to a `SqlValue` upstream, SELECT-source rows arrive pre-decoded via `RowDecoder` from the executed `SimulatedSqlResultSet`'s row bytes.
346
+
347
+
Buffering is full: `ExecuteSelectSource` materializes the entire source result-set into `List<SqlValue[]>` before any destination write. This makes self-insert (`INSERT t SELECT … FROM t`) safe — without it, scanning the source's heap while inserting into it would yield undefined behavior.
348
+
349
+
Projection-count vs insert-list mismatch fires at parse time via `selection.Schema.Length`: too few SELECT columns → **Msg 120 St 1 Cls 15** (`"The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns."`); too many → **Msg 121 St 1 Cls 15** with `"more items"` wording. Empty source → silent success with rows-affected 0. CHECK / NOT NULL / PK / UNIQUE violations mid-source still trigger statement-level rollback (every row from the SELECT is unwound, matching the VALUES path's atomicity).
350
+
351
+
EF Core 10 doesn't emit `INSERT … SELECT` from SaveChanges (which uses INSERT…OUTPUT VALUES for single-row and MERGE for batched-multi-row); this is reachable from raw SQL (`FromSqlInterpolated` / direct command text) and from application-side bulk-copy patterns. CTE-prefix INSERTs (`WITH … INSERT t SELECT …`) aren't modeled — orthogonal to this bundle since the simulator has no CTE support.
simulation.AssertSqlError("insert dst (a, b) select a, b, c from src",121,
514
+
"The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.");
simulation.AssertSqlError("insert dst (a, b) select a from src",120,
527
+
"The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.");
new("The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.",120,15,1);
new("The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.",121,15,1);
193
+
178
194
/// <summary>
179
195
/// Mimics SQL Server error 108: a positional <c>ORDER BY</c> ordinal
180
196
/// (e.g. <c>order by 0</c>, <c>order by 5</c> with only 3 columns) is
0 commit comments