fix: guard FK counter reconciliation with REPLACE conflict check#6346
fix: guard FK counter reconciliation with REPLACE conflict check#6346reverb256 wants to merge 2 commits intotursodatabase:mainfrom
Conversation
emit_fk_parent_new_key_reconcile was called unconditionally for all UPDATE operations on parent tables with deferred FK children. This produced a double-decrement loop in the bytecode: once from emit_fk_update_parent_actions (correct) and once from emit_fk_parent_new_key_reconcile (duplicate). The counter drifted to zero, allowing RELEASE SAVEPOINT and COMMIT to succeed when they should fail with FOREIGN KEY constraint violation. Guard the call with matches!(effective_rowid_alias_conflict, ResolveType::Replace) so it only runs during REPLACE conflict resolution, matching the function's own documentation. Adds two sqltest regression tests: - deferred-fk-update-parent-pk-in-savepoint-rejects-commit - deferred-fk-update-parent-pk-rollback-restores-state Fixes tursodatabase#6218
Fossier: PR Auto-ClosedThis PR was automatically closed because Score BreakdownTotal Score: 36.2/100 | Confidence: 100% | Outcome: DENY
AppealIf you believe this is a mistake, please open an issue to request manual review. A maintainer can vouch for you by adding your username to the You can also reach the maintainers at: https://discord.gg/mNUkCHs2R to appeal this decision |
|
/fossier approve |
The previous guard only checked effective_rowid_alias_conflict which is set for INTEGER PRIMARY KEY tables. Tables with UNIQUE ON CONFLICT REPLACE (not PRIMARY KEY) also trigger REPLACE deletes in Phase 1 but were excluded from reconciliation, causing deferred FK counter drift. Now checks both rowid alias conflict AND any unique index with REPLACE.
Fix for CI failuresThe two failing tests ( and ) use My previous guard Root cause: When Phase 1 REPLACE deletes a parent row, Fix: Check both the rowid alias conflict AND whether any unique index in the update has let has_replace_conflict = matches!(effective_rowid_alias_conflict, ResolveType::Replace)
|| indexes_to_update.iter().any(|idx| idx.on_conflict == Some(ResolveType::Replace));This correctly covers both REPLACE sources. |
Summary
Fixes #6218 — deferred FK violation counter double-decremented during UPDATE on parent table.
The issue:
emit_fk_parent_new_key_reconcilewas called unconditionally for all UPDATE operations on parent tables with deferred FK children.emit_fk_update_parent_actionsalready handles the correct increment (OLD key) and decrement (NEW key) cycle. The unconditional second call produces a duplicate decrement loop in the bytecode, causing the FK counter to drift to zero and allowingCOMMITto succeed when it should fail.The fix: guard the call with
matches!(effective_rowid_alias_conflict, ResolveType::Replace)so it only runs during REPLACE conflict resolution, which is what the function's own documentation says it should do.Before fix
After fix
Tests
Two new sqltest regression tests in
testing/sqltests/tests/savepoint.sqltest:deferred-fk-update-parent-pk-in-savepoint-rejects-commit— verifies COMMIT failsdeferred-fk-update-parent-pk-rollback-restores-state— verifies ROLLBACK TO restores stateAll 23 savepoint tests pass.
Description of AI Usage
Used AI assistance for code exploration and understanding the bytecode generation pipeline. The fix itself is a one-line guard, as identified in the issue by @LeMikaelF.