Commit Graph

6125 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
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
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
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
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
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
Jussi Saurio
ec149865f1 Merge 'Support DELETE ... RETURNING' from Jussi Saurio
Closes #3948
2025-11-14 13:53:57 +02:00
Jussi Saurio
3f970d5d95 Merge 'Return parse error if NULLS LAST used in ORDER BY' from Jussi Saurio
we don't support this yet, so let's not silently ignore it either

Closes #3945
2025-11-14 13:36:34 +02:00
Jussi Saurio
46440ad58d Merge 'don't overwrite col mappings' from Pavan Nambi
closes https://github.com/tursodatabase/turso/issues/3951

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

Closes #3956
2025-11-14 13:36:07 +02:00
Jussi Saurio
272dadc4bc Support DELETE ... RETURNING
I didn't end up having to use the RowSet instructions for this after
all. Maybe there's some edge cases where it's required -- sqlite uses
ephemeral tables or rowsets in all RETURNING handling, but I haven't
found a need to do that yet.
2025-11-14 13:35:24 +02:00
Jussi Saurio
fca32737e7 Merge 'Refactor RETURNING to support arbitrary expressions' from Jussi Saurio
Note: contains 1000 lines of TCL tests generated by cursor :] runtime
changes are smaller and this actually deletes code in aggregate.
---
Main change is to support arbitrary expressions in RETURNING instead of
a specialcased subset, but also e.g. disallow returning TABLE.* which is
illegal syntax in SQLite.
Main idea is we add the columns of the target table (the table affected
by INSERT/UPDATE/DELETE) into `expr_to_reg_cache`) and then just
translate the RETURNING expressions as normal

Closes #3942
2025-11-14 13:34:53 +02:00
Pavan-Nambi
8d2b06e6bf remove stupid files,clippy and tcl-syntax 2025-11-14 08:24:01 +05:30
Pavan-Nambi
eaa8edb6f7 don't overwrite col mappings 2025-11-14 07:41:41 +05:30
Pekka Enberg
1f79fbc22c Merge 'Partial sync basic' from Nikita Sivukhin
This PR implements basic support for partial sync. Right now the scope
is limited to only `:memory:` IO and later will be properly expanded to
the file based IO later.
The main addition is `PartialDatabaseStorage` which make request to the
remote server for missing local pages on demand.
The main change is that now tursodatabase JS bindings accept optional
"external" IO event loop which in case of sync will drive `ProtocolIo`
internal work associated with remote page fetching tasks.

Closes #3931
2025-11-13 16:38:04 +02:00
Jussi Saurio
950184a2c6 Return parse error if NULLS LAST used in ORDER BY
we don't support this yet, so let's not silently ignore it either
2025-11-13 10:47:17 +02:00
Jussi Saurio
eee7fa5f95 Refactor RETURNING to support arbitrary expressions
Currently RETURNING was a bit of a hack since it had a special
translate_expr_for_returning() function that only supported a subset
of expressions.

Instead, we can store the columns of the target table of the INSERT/UPDATE/DELETE
we are RETURNING from in `Resolver::expr_to_reg_cache` and make those columns point
to the registers that hold the OLD/NEW column values (depending on the operation).
2025-11-13 10:32:38 +02:00