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
Implements ADR-0064 (merge — widening ADR-0057's scope), the ADR-0058 gate in T-SQL, and ADR-0059's staged-terminal shape in SQL Server idiom. Part of the SQL Server destination cycle (map #106, sliced from #113).
Merge is the production pattern this cycle serves — including merge keys on IDENTITY columns. It reuses the staged terminal transaction of ADR-0059 with a created-shape stage, deliberately not a clone of the target: the bulk path always writes ADR-0062 types, and any accepted lossy conversion happens server-side inside MERGE, governed by ADR-0065's opt-in.
write_chunk: bulk_insert into the stage, inside the transaction.
commit, all inside the same transaction:
ADR-0058 duplicate-key gate on the stage — a duplicate key tuple rolls back and fails duplicate_merge_keys with the destination untouched.
Count updated as staged rows with a key match in the target; inserted = staged − updated — preserving the documented updated + inserted == written invariant.
MERGE INTO [schema].[target] WITH (HOLDLOCK) USING stage ON <key equality> WHEN MATCHED THEN UPDATE SET <non-key columns> WHEN NOT MATCHED THEN INSERT <dataset columns> — the UPDATE clause omitted when every column is a key; the INSERT branch omits extra target columns (server defaults apply).
When the merge key maps an IDENTITY column, wrap the MERGE in SET IDENTITY_INSERT [target] ON/OFF (requires ALTER on the target; session-scoped, cannot leak — failure paths end the session).
Drop the stage, terminal COMMIT.
Failure: explicit ROLLBACK; the in-transaction stage vanishes on rollback or connection death — no orphan cleanup exists by construction.
Facts: atomicity: "atomic", strategy: "transactional_merge" (existing string), merge counts as on DuckDB. Zero survivors: empty stage, no-op merge, COMMIT — the mirror posture.
Modes: supported_load_modes += merge — all three live; other destinations' decline messages unchanged. Classification stays Terminal this slice.
Acceptance criteria
Gated live tests:
Fresh merge into an empty existing table inserts all rows (updated: 0); a second merge with changed + new records updates matched rows whole and inserts the rest — counts assert the invariant.
Merge key on an IDENTITY column: end-to-end merge lands inserted keys faithfully (read-back proves the source key values, not server-assigned ones).
Implements ADR-0064 (merge — widening ADR-0057's scope), the ADR-0058 gate in T-SQL, and ADR-0059's staged-terminal shape in SQL Server idiom. Part of the SQL Server destination cycle (map #106, sliced from #113).
Blocked by #136
Context
Merge is the production pattern this cycle serves — including merge keys on
IDENTITYcolumns. It reuses the staged terminal transaction of ADR-0059 with a created-shape stage, deliberately not a clone of the target: the bulk path always writes ADR-0062 types, and any accepted lossy conversion happens server-side insideMERGE, governed by ADR-0065's opt-in.The contract
IDENTITYis acceptable iff it is a merge key;DEFAULT-carrying mapped columns are acceptable). ThenBEGIN TRANand create a real staging tabledata_spark_merge_stage_<uuid>in the target's schema, in the dataset's created shape (Generate SQL Server DDL and bulk rows from the dataset schema #134).bulk_insertinto the stage, inside the transaction.duplicate_merge_keyswith the destination untouched.updatedas staged rows with a key match in the target;inserted = staged − updated— preserving the documentedupdated + inserted == writteninvariant.MERGE INTO [schema].[target] WITH (HOLDLOCK) USING stage ON <key equality> WHEN MATCHED THEN UPDATE SET <non-key columns> WHEN NOT MATCHED THEN INSERT <dataset columns>— the UPDATE clause omitted when every column is a key; the INSERT branch omits extra target columns (server defaults apply).IDENTITYcolumn, wrap theMERGEinSET IDENTITY_INSERT [target] ON/OFF(requires ALTER on the target; session-scoped, cannot leak — failure paths end the session).COMMIT.ROLLBACK; the in-transaction stage vanishes on rollback or connection death — no orphan cleanup exists by construction.atomicity: "atomic",strategy: "transactional_merge"(existing string), merge counts as on DuckDB. Zero survivors: empty stage, no-op merge,COMMIT— the mirror posture.supported_load_modes+= merge — all three live; other destinations' decline messages unchanged. Classification stays Terminal this slice.Acceptance criteria
updated: 0); a second merge with changed + new records updates matched rows whole and inserts the rest — counts assert the invariant.IDENTITYcolumn: end-to-end merge lands inserted keys faithfully (read-back proves the source key values, not server-assigned ones).duplicate_merge_keys, target rows byte-equal before/after (count + content probe).atomic/transactional_mergefacts.IDENTITYstill rejects viaincompatible_destination_table(gated).cargo test --lockedgreen with new testsignored; CI runs them.CHANGELOG.mdUnreleased entry: merge lands on sqlserver (ADR-0057 scope widened).