## 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-3547999449Closes#3979
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
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
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
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