Commit Graph

10918 Commits

Author SHA1 Message Date
Jussi Saurio
92f47dffb0 Merge 'Trigger support' from Jussi Saurio
## Trigger Support
This PR adds support for triggers:
- `CREATE TRIGGER`
- `DROP TRIGGER`
Supported
- `BEFORE/AFTER INSERT`
- `BEFORE/AFTER DELETE`
- `BEFORE/AFTER UPDATE [OF <col1,col2,col3>]`
Not supported:
- `INSTEAD OF`
- `TEMPORARY`
### Implementation details
- Triggers are executed within a new `Insn::Program` instruction. The
spec of the insn differs a bit from SQlite: we store a `Statement`
inside that instruction that we can `reset()` for every invocation.
- Like Sqlite, trigger programs take `NEW` and `OLD` rows as program
parameters.
Whenever there are triggers that would fire as the result of a DML
statement:
- `DELETE` writes the rows being deleted into a `RowSet` first.
- `UPDATE` and `INSERT` write the rows being updated into an ephemeral
table first.
### Other shit
Also added `EXPLAIN` support - the bytecode plans for trigger
subprograms are appended after the main program.
### AI disclosure
Used Cursor quite a bit for generating boilerplate code for this - you
can blame all the bad code on the AI of course 🤡
### Follow-ups:
1. ALTER TABLE ops need to rewrite the sql in the CREATE TRIGGER
statement e.g. if a column is renamed. Columns cannot be dropped if
referenced in triggers.
2. Fix weird rowid -1 fallback:
https://github.com/tursodatabase/turso/pull/3979#issuecomment-3547999449

Closes #3979
2025-11-19 08:42:41 +02:00
Pere Diaz Bou
72bf195f4b Merge 'core/mvcc/cursor: rowid don't seek first rowid' from Pere Diaz Bou
rowid should only try to use the current's position. So if we are not
pointing to a `Loaded` row, then it should return None
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Change `rowid()` to return `None` unless cursor is on a `Loaded` row,
removing the implicit seek from `BeforeFirst`.
>
> - **Core MVCC Cursor (`core/mvcc/cursor.rs`)**:
>   - Adjust `rowid()` behavior: remove implicit first-row seek when
`BeforeFirst`; return `None` unless position is `Loaded`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8848775a71. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3977
2025-11-18 19:51:19 +01:00
Jussi Saurio
ad753281b6 Remove unneeded too_many_arguments annotation 2025-11-18 18:41:45 +02:00
Jussi Saurio
129ee8c82b Remove more AI-generated unnecessary code 2025-11-18 17:24:10 +02:00
Jussi Saurio
2cbc83a01c triggers: add ParamMap abstraction to reduce code noise a bit 2025-11-18 17:08:22 +02:00
Jussi Saurio
11528cff12 Remove weird AI-innovated negative index hack 2025-11-18 16:56:27 +02:00
Jussi Saurio
2674145937 Avoid allocation when no triggers exist 2025-11-18 15:40:06 +02:00
Jussi Saurio
d33c294380 remove unhelpful comment 2025-11-18 15:39:53 +02:00
Jussi Saurio
5c1ebbd011 Use VecDeque for trigger storage for similar reasons as indexes do 2025-11-18 15:19:01 +02:00
Jussi Saurio
e1dee4a072 triggers: add a lot of different kinds of tests 2025-11-18 15:19:01 +02:00
Jussi Saurio
9aa09d5ccf Add EXPLAIN support for trigger subprograms
They get printed after the parent program.
2025-11-18 15:19:01 +02:00
Jussi Saurio
423a1444d1 Don't crash if table cursor is already opened 2025-11-18 15:19:01 +02:00
Jussi Saurio
7f536506c3 Clear deferred_seeks for cursor when it is closed
Sometimes the deferred seek never happens, so we don't want it to
dangle if the same cursor is reused for another seek
2025-11-18 15:19:01 +02:00
Jussi Saurio
d398f12471 triggers: subprograms shouldnt commit or use the transaction opcode 2025-11-18 15:19:01 +02:00
Jussi Saurio
be6f8ab8b3 state.end_statement() should not be called separately in cases where abort() already does it 2025-11-18 15:19:01 +02:00
Jussi Saurio
7a12e184a8 Only reset FK violation counter if stmt was rolled back
In the case of trigger subprograms the statement didn't roll back,
since the parent program will roll it back.
2025-11-18 15:19:01 +02:00
Jussi Saurio
770c6eef9f triggers: subprograms dont use transactions 2025-11-18 15:19:01 +02:00
Jussi Saurio
70267f8710 triggers: add translation logic for INSERT triggers 2025-11-18 15:19:01 +02:00
Jussi Saurio
e28301dc2e triggers: add translation logic for UPDATE triggers 2025-11-18 15:19:01 +02:00
Jussi Saurio
516dae5b6a triggers: add translation logic for DELETE triggers 2025-11-18 15:19:01 +02:00
Jussi Saurio
5b037b0f75 resolve labels for RowSetRead insn 2025-11-18 15:19:01 +02:00
Jussi Saurio
7d1543fcc5 triggers: take triggers into account in optimizer decision
- optimize the select plan used for the RowSet in DELETE
- require ephemeral table when UPDATE involves triggers
2025-11-18 15:19:01 +02:00
Jussi Saurio
78ce3c8658 triggers: add capability for DeletePlan to write the write set into a RowSet first
This is needed for safe DELETE when there are DELETE triggers on the affected
table.
2025-11-18 15:19:01 +02:00
Jussi Saurio
e60e37da7d triggers: add execution plumbing to translation and vdbe layers 2025-11-18 15:19:01 +02:00
Jussi Saurio
3d00686f48 triggers: translation functions for DDL 2025-11-18 12:18:07 +02:00
Jussi Saurio
d4b487eebc triggers: add in-memory schema entries 2025-11-18 12:14:27 +02:00
Preston Thorpe
e61234d522 Merge 'translate/insert: Implement INSERT OR REPLACE' from Preston Thorpe
This PR implements support for `INSERT OR REPLACE INTO t`.
For `OR IGNORE`, we currently rewrite this internally to an `ON CONFLICT
DO NOTHING`, and I was hopeful we could do this with OR REPLACE, however
it seems SQLite actually deletes the row and then proceeds to insert, so
we could not simply rewrite this to an `ON CONFLICT DO UPDATE SET
col=excluded.col`, as this would result in differing rowid's when
compared to SQLite.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3972
2025-11-17 18:50:15 -05:00
PThorpe92
56f35ad4cd cargo fmt 2025-11-17 12:22:55 -05:00
PThorpe92
c3185d0b8c Properly handle foreign keys for INSERT OR REPLACE 2025-11-17 12:19:33 -05:00
Pere Diaz Bou
8848775a71 core/mvcc/cursor: rowid don't seek first rowid
rowid should only try to use the current's position. So if we are not
pointing to a `Loaded` row, then it should return None
2025-11-17 16:17:52 +01:00
PThorpe92
8cd33f3ec9 Add comment for or replace behavior require seek in translate/insert 2025-11-17 08:41:22 -05:00
PThorpe92
0ce5f81008 Cleanup translate/insert fix clippy warnings 2025-11-17 08:23:16 -05:00
PThorpe92
f8e78b73a8 Fix handling of partial indexes when deleting rows in ON REPLACE for insert 2025-11-17 08:23:16 -05:00
PThorpe92
0bc56d3f28 Sprinkle some OR REPLACE into INSERT statements in fuzzing 2025-11-17 08:23:16 -05:00
PThorpe92
b83921d838 Add TCL tests for insert or replace handling 2025-11-17 08:23:16 -05:00
PThorpe92
634af4d6f6 Handle NOT NULL behavior for INSERT OR REPLACE 2025-11-17 08:23:15 -05:00
PThorpe92
5bff10c56e Implement INSERT OR REPLACE translation/emission 2025-11-17 08:23:10 -05:00
Jussi Saurio
693eaeb851 Merge 'Add ColDef struct to make schema::Column creation more ergonomic' from Preston Thorpe
RE: #3970
That Column::new having 14 boolean arguments was not great.
Also this removes the unneeded `parent_cols: Vec<String>` from
`ResolvedFkRef`

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3973
2025-11-17 09:17:56 +02:00
Jussi Saurio
3b0d4f3214 Merge 'Enable nested self-inserts in simulator' from Mikaël Francoeur
This PR follows https://github.com/tursodatabase/turso/pull/3625, and
enables self-inserts with nested subqueries with arbitrary levels of
nesting, of the form:
```sql
INSERT INTO x SELECT * FROM (SELECT * FROM x WHERE TRUE) WHERE TRUE;
```
This is limited, compared to enabling INSERTs with arbitrary SELECTs
like Jussi [initially suggested](https://github.com/tursodatabase/turso/
pull/3625#issuecomment-3397069821), but there are some preexisting
issues in the simulator that need to be solved before arbitrary SELECTs
can be enabled. Already, this PR includes a fix for a preexisting issue
(JOINs weren't computed correctly), but there are also some other
issues, for which I left FIXME's:
* the shadow model doesn't handle type coercion correctly, so INSERT
statements across affinities will fail the
`AllTablesHaveExpectedContent` property
* `SelectInner::arbitrary_sized` can generate SELECT statements with
fewer columns than requested
* `pick_unique` can hang under certain conditions
In addition, there is likely another preexisting issue with the shadow
model, because during development, when I tried to support arbitrary
SELECT queries, I got multiple simulator failures where the shadow model
ended up with an incorrect number of rows.
-----
### Future Work
In order to implement arbitrary SELECTs in `INSERT INTO ... SELECT`
statements, the issues above will need to be addressed. The biggest
issue is with type affinity, and I see 2 solutions:
1. apply type affinity rules in the simulator
2. build some tolerance into the `AllTablesHaveExpectedContent` property
The first solution seems like the best one, but it's far from trivial.
I've started working on it, but I don't know how much longer it will
take. For this reason, I'm opening this PR with just the limited query
generation.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3933
2025-11-17 08:34:18 +02:00
Preston Thorpe
29f30c5c35 Merge 'allow null for unique columns and don't validate fkeys on parent' from Pavan Nambi
closes #3966
closes #3965
check constraint pr depends on this.

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #3969
2025-11-16 19:59:59 -05:00
PThorpe92
ced5aec2c7 Add ColDef arguments to Column::new in collate.rs 2025-11-16 19:36:11 -05:00
PThorpe92
ca783ccdff Add ColDef struct to make schema::Column creation more ergonomic 2025-11-16 19:10:42 -05:00
Preston Thorpe
22810f1186 Merge 'correct order in column creation in join tests' from Pavan Nambi
see
https://github.com/tursodatabase/turso/pull/3905#discussion_r2531910624
order was misplaced during refactor
ideally it would be nice if we change this to something like
```
name:xxx
rowid:xxx
pkey:xx
```
instead of relying on inlay hints. but or now just changing order back.

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #3970
2025-11-16 19:09:59 -05:00
Pavan-Nambi
134f898952 correct order in column creation tests 2025-11-16 18:36:10 +05:30
Pavan-Nambi
3de37d3f64 dont validate fkey on parent add tests
correct msitake and add null test issue

add fkey test
2025-11-16 09:41:57 +05:30
Pavan-Nambi
8edea305f6 allow null for unique columns 2025-11-16 08:37:57 +05:30
Mikaël Francoeur
d309e6ddb3 generate self-inserts with nested subqueries 2025-11-14 09:16:17 -05:00
Mikaël Francoeur
23d6080531 make FromClause recursive 2025-11-14 09:16:17 -05:00
Mikaël Francoeur
156693ce95 enable self-inserts 2025-11-14 09:16:17 -05:00
Mikaël Francoeur
8ea038c00b fix join 2025-11-14 09:16:17 -05:00