mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-26 20:44:23 +01:00
This PR introduces support for foreign key constraints, and the `PRAGMA foreign_keys;`, and relevant opcodes: `FkCounter` and `FkIfZero`. Extensive fuzz tests were added both for regular and composite PK/rowid/unique index constraints, as well as some really weird edgecases to make sure we our affinity handling is correct as well when we trigger the constraints. Foreign-key checking is driven by two VDBE ops: `FkCounter` and `FkIfZero`, and `FkCounter` is a running meter on the `Connection` for deferred FK violations. When an `insert/delete/update` operation creates a potential orphan (we insert a child row that doesn’t have a matching parent, or we delete/update a parent that children still point at), this counter is incremented. When a later operation fixes that (e.g. we insert the missing parent or re-target the child), we decrement the counter. If any is remaining at commit time, the commit fails. For immediate constraints, on the violation path we emit Halt right away. `FkIfZero` can either be used to guard a decrement of FkCounter to prevent underflow, or can potentially (in the future) be used to avoid work checking if any constraints need resolving. NOTE: this PR does not implement `pragma defer_foreign_keys` for global `deferred` constraint semantics. only explicit `col INT REFERENCES t(id) DEFERRABLE INITIALLY DEFERRED` is supported in this PR. This PR does not add support for `ON UPDATE|DELETE CASCADE`, only for basic implicit `DO NOTHING` behavior. ~~NOTE: I did notice that, as referenced here: #3463~~ ~~our current handling of unique constraints does not pass fuzz tests, I believe only in the case of composite primary keys,~~ ~~because the fuzz test for FK referencing composite PK is failing but only for UNIQUE constraints, never (or as many times as i tried) for foreign key constraints.~~ EDIT: all fuzzers are passing, because @sivukhin fixed the unique constraint issue. The reason that the `deferred` fuzzer is `#[ignore]`'d is because sqlite uses sub-transactions, and even though the fuzzing only does 1 entry per transaction... the fuzzer can lose track of _when_ it's in a transaction and when it hits a FK constraint, and there is an error in both DB's, it can just continue to do run regular statements, and then the eventual ROLLBACK will revert different things in sqlite vs turso.. so for now, we leave it `ignore`d Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #3510