Skip to content

Commit 71ed92f

Browse files
committed
INSTEAD OF triggers (heap tables + views, all three actions): the existing CREATE TRIGGER … INSTEAD OF parse path (which raised NotSupportedException) wires through to a new dispatch surface that replaces the firing DML's heap-write phase. Trigger model refactored from HeapTable ParentTable to a polymorphic object Parent (HeapTable or View) with a ParentObjectId accessor for sys.triggers / sys.objects shaping; Schema.Triggers, BuiltInResources's two trigger-emitting catalog passes, DISABLE/ENABLE TRIGGER, and DROP TRIGGER routing all adapted to the new shape. Pseudo-table materialization generalized to take HeapColumn[] directly so view parents use View.OutputColumns for INSERTED / DELETED. New InvokeTrigger surface: TryFireInsteadOfTrigger (single-trigger dispatch returning whether one fired), HasInsteadOfTrigger (presence predicate excluding in-flight triggers via Connection.FiringTriggerIds so a body's nested same-target DML reaches the heap rather than no-op'ing — probe-confirmed), shared RunTriggerBodies helper extracted from FireTriggers so AFTER + INSTEAD OF reuse the body-execution / recursion-guard / SCOPE_IDENTITY-restore plumbing. CREATE TRIGGER now accepts heap-table OR view parents for INSTEAD OF; AFTER on view raises Msg 8197 (probe-confirmed wording); a second INSTEAD OF whose Actions overlap raises Msg 2111 with table / view parent-kind wording (probe-confirmed verbatim). DML hooks (Insert / Update / Delete / Merge) detect per-target / per-action INSTEAD OF and bifurcate: INSERT through view routes to a new ProcessInsteadOfInsertOnView (works for non-updatable views — the primary use case); INSERT through table threads insteadOfActive through ProcessHeapInsert that skips identity allocation (typed-default 0 for int family — probe-confirmed), CHECK / NOT NULL / key validation, heap write, and AFTER-trigger fire; UPDATE / DELETE thread per-target detection through CommitUpdate / CommitDelete with new ProjectThroughView helper that maps base-table rows to view-column shape via BaseColumnOrdinals (derived slots → typed NULL); MERGE per-action routing in CommitMerge (independent per pending list, so mixed INSERT-via-trigger + UPDATE-via-heap MERGEs work; ApplyInsert threaded with insteadOfInsert flag to suppress identity allocation in the pending phase). DROP TABLE / DROP VIEW cascade-drop attached triggers via new CascadeDropTriggers helper. Deferred (raises NotSupportedException, documented): INSTEAD OF UPDATE / DELETE on non-updatable views — requires executing the view selection to enumerate would-be-affected rows, which loses heap-row identity. 21 new InsteadOfTriggerTests cover all three action kinds on tables, INSTEAD OF on updatable + non-updatable views, Msg 2111 enforcement, AFTER-on-view rejection, direct-recursion with body-side nested INSERT reaching the heap, DEFAULT-and-computed-column evaluation for INSERTED, mixed-action MERGE routing, DROP cascade, sys.triggers.is_instead_of_trigger surfacing. CLAUDE.md and docs/claude/triggers.md updated; the prior "INSTEAD OF triggers — NotSupportedException" gap entry replaced with the narrower non-updatable-view UPDATE/DELETE follow-up.
1 parent 62f9c08 commit 71ed92f

15 files changed

Lines changed: 1315 additions & 272 deletions

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Per-feature deep-dives live under `docs/claude/`. Each entry below is a trigger:
175175
- **CREATE SCHEMA's `<schema_element>` greedy form** — real SQL Server consumes trailing CREATE TABLE / VIEW / GRANT as part of the same CREATE SCHEMA statement (and requires CREATE SCHEMA to be the first statement in the batch as a result). The simulator instead dispatches the trailing tokens as their own statements — same end state for the common idiom, but mismatched-grammar trailers (e.g. anything that isn't a recognized statement start) raise `NotSupportedException`.
176176
- **`CREATE SCHEMA sys` / `INFORMATION_SCHEMA`** — raises Msg 2760 (matching real SQL Server). The schemas themselves exist as catalog-view hosts (`select * from sys.tables` / `select * from INFORMATION_SCHEMA.COLUMNS` work); legacy bare 1-part system-table access (`select * from systypes`) also still works.
177177
- T-SQL `GOTO` / labels — `IF` / `BEGIN…END` / `WHILE` / `BREAK` / `CONTINUE` / `RETURN` (bare + UDF-body and stored-procedure-body value form) ship; unconditional jumps don't.
178-
- Multi-statement table-valued functions (`RETURNS @t TABLE (...) AS BEGIN ... END`), CLR functions, INSTEAD OF triggers (AFTER triggers ship). DDL / logon triggers also unmodeled. Scalar UDFs, inline TVFs, views, DML-through-views (single-source updatable shape), stored procedures (including CREATE / ALTER / DROP, EXEC with input/output/default params, `@rc = EXEC` return-code capture, `CommandType.StoredProcedure`, `EXEC (@sql)` and `sp_executesql` dynamic SQL), user-defined table types + table-valued parameters (CREATE TYPE … AS TABLE / DROP TYPE / DECLARE @t MyType / READONLY proc params / EXEC with TVP arg / ADO.NET Structured parameter via the `DbParameter.TypeName` C# 14 extension property + DataTable/IDataReader sources), sequence objects (CREATE / ALTER / DROP SEQUENCE / NEXT VALUE FOR / sys.sequences — supports EF Core HiLo identity strategy end-to-end), and DML AFTER triggers (CREATE / ALTER / CREATE OR ALTER / DROP TRIGGER, FOR-as-AFTER synonym, multi-action INSERT,UPDATE,DELETE, INSERTED/DELETED pseudo-tables, multiple triggers per table, DISABLE/ENABLE TRIGGER, TRIGGER_NESTLEVEL(), direct-recursion suppression matching RECURSIVE_TRIGGERS OFF, sys.triggers + sys.objects integration, trigger-error rolls back firing DML) ship (see `docs/claude/programmable.md`, `docs/claude/table-valued-parameters.md`, `docs/claude/sequences.md`, and `docs/claude/triggers.md`). JOIN-view single-base-table UPDATE/DELETE, OUTPUT through views, and multi-source alias-form UPDATE/DELETE through views are deferred (Msg 4405 or `NotSupportedException` at the DML site). `BEGIN ATOMIC` / `BEGIN DISTRIBUTED TRANSACTION` raise `NotSupportedException` at dispatch. Value-form `RETURN N` is legal inside a scalar-UDF body and a stored-procedure body; bare batch / dynamic-SQL scope raises Msg 178. TRY/CATCH + THROW + RAISERROR (printf-style `%s %d/%i %u %o %x %X %% %ld %I64d` with width / precision / left-align / zero-pad; severity routing 0-10 informational vs 11-18 catchable; `WITH SETERROR`/`NOWAIT` ship, `WITH LOG` uniformly raises Msg 2778; numeric `msg_id` raises Msg 2732 for 50000 / `< 13000` and Msg 18054 otherwise — `sys.messages` registry / `sp_addmessage` not modeled) + live `@@ERROR` + `ERROR_*()` functions ship (see `docs/claude/control-flow.md`).
178+
- Multi-statement table-valued functions (`RETURNS @t TABLE (...) AS BEGIN ... END`), CLR functions, INSTEAD OF UPDATE / DELETE on *non-updatable* views (INSTEAD OF INSERT on any view ships; INSTEAD OF UPDATE / DELETE on updatable single-base views ships). DDL / logon triggers also unmodeled. Scalar UDFs, inline TVFs, views, DML-through-views (single-source updatable shape), stored procedures (including CREATE / ALTER / DROP, EXEC with input/output/default params, `@rc = EXEC` return-code capture, `CommandType.StoredProcedure`, `EXEC (@sql)` and `sp_executesql` dynamic SQL), user-defined table types + table-valued parameters (CREATE TYPE … AS TABLE / DROP TYPE / DECLARE @t MyType / READONLY proc params / EXEC with TVP arg / ADO.NET Structured parameter via the `DbParameter.TypeName` C# 14 extension property + DataTable/IDataReader sources), sequence objects (CREATE / ALTER / DROP SEQUENCE / NEXT VALUE FOR / sys.sequences — supports EF Core HiLo identity strategy end-to-end), and DML triggers (CREATE / ALTER / CREATE OR ALTER / DROP TRIGGER, FOR-as-AFTER synonym, AFTER on heap tables + INSTEAD OF on heap tables / views, multi-action INSERT,UPDATE,DELETE, INSERTED/DELETED pseudo-tables, multiple AFTER triggers per parent / one INSTEAD OF per action per target (Msg 2111), DISABLE/ENABLE TRIGGER, TRIGGER_NESTLEVEL(), direct-recursion suppression matching RECURSIVE_TRIGGERS OFF, sys.triggers + sys.objects integration, trigger-error rolls back firing DML, MERGE per-action routing through INSTEAD OF, DROP TABLE / DROP VIEW cascade-drops attached triggers) ship (see `docs/claude/programmable.md`, `docs/claude/table-valued-parameters.md`, `docs/claude/sequences.md`, and `docs/claude/triggers.md`). JOIN-view single-base-table UPDATE/DELETE, OUTPUT through views, and multi-source alias-form UPDATE/DELETE through views are deferred (Msg 4405 or `NotSupportedException` at the DML site). `BEGIN ATOMIC` / `BEGIN DISTRIBUTED TRANSACTION` raise `NotSupportedException` at dispatch. Value-form `RETURN N` is legal inside a scalar-UDF body and a stored-procedure body; bare batch / dynamic-SQL scope raises Msg 178. TRY/CATCH + THROW + RAISERROR (printf-style `%s %d/%i %u %o %x %X %% %ld %I64d` with width / precision / left-align / zero-pad; severity routing 0-10 informational vs 11-18 catchable; `WITH SETERROR`/`NOWAIT` ship, `WITH LOG` uniformly raises Msg 2778; numeric `msg_id` raises Msg 2732 for 50000 / `< 13000` and Msg 18054 otherwise — `sys.messages` registry / `sp_addmessage` not modeled) + live `@@ERROR` + `ERROR_*()` functions ship (see `docs/claude/control-flow.md`).
179179
- **`PRINT` message capture** — the statement parses + evaluates the operand (so operand-side errors like Msg 245 surface), but the message is discarded. `DbConnection` has no `InfoMessage` event (that's a `SqlConnection` extension), so adding a public observability surface would mean a new event on `SimulatedDbConnection`. Defer until an application needs it.
180180
- `hierarchyid`, `geography`, `geometry`.
181181

0 commit comments

Comments
 (0)