The idea is to have a custom `all-mvcc.test` so we can add `.test` files
that we expect to work with MVCC. In cases where files are not enough we
have `is_turso_mvcc` to check if we want to run a test.
For example we skip partial index tests like this:
```
if {![is_turso_mvcc]} {
do_execsql_test_on_specific_db {:memory:} autoinc-conflict-on-nothing {
CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, k TEXT);
CREATE UNIQUE INDEX idx_k_partial ON t(k) WHERE id > 1;
INSERT INTO t (k) VALUES ('a');
INSERT INTO t (k) VALUES ('a');
INSERT INTO t (k) VALUES ('a') ON CONFLICT DO NOTHING;
INSERT INTO t (k) VALUES ('b');
SELECT * FROM t ORDER BY id;
} {1|a 2|a 4|b}
}
```
`test-mvcc-compat` is not run under CI for now as we need to fix every
test anyways so no point in making every PR fail for now.
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#4139
Take e.g.
CREATE TABLE t(x); CREATE INDEX txdesc ON t(x desc);
INSERT INTO t values (1),(2),(3);
SELECT * FROM t WHERE x > NULL;
--
Our plan, like Sqlite, was to start iterating the descending index
from the beginning (Rewind) and stop once we hit a row where x is
<= than NULL using `IdxGe` instruction (GE in descending indexes
means LE).
However, `IdxGe` and other similar instructions use a sort comparison
where NULL is less than numbers/strings etc, so this would incorrectly
not jump.
Fix: we need to emit an explicit NULL check after rewinding.
After this fix, I ran the fuzz test for more than an hour with no
issues.
Closes https://github.com/tursodatabase/turso/issues/4075
## AI disclosure
Claude wrote the implementation and tests from just a copy/paste of the
Github issue.
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#4119
For an empty database, SQLite has the database header in memory on
startup. We will need to also have the Database Header on startup to
check if the DB is in WAL or MVCC mode. So this is necessary for #3536.
The major changes here is that we don't have a `DbState` variable
anymore, and instead with have an `init_page_1` that holds an in-memory
page1 that is flushed to disk on a `WriteTx`. In my opinion, this change
also reduced the complexity of our initialization process
Closes#3441
Reviewed-by: Mikaël Francoeur (@LeMikaelF)
Reviewed-by: Preston Thorpe <preston@turso.tech>
Closes#4092
SQLite rejects `CREATE INDEX idx ON t(rowid)` with "no such column: rowid"
because rowid is a pseudo-column, not an actual column. Limbo was
incorrectly allowing this.
The fix removes the special exception for ROWID_STRS (rowid, _rowid_, oid)
in validate_index_expression(). Now these identifiers are only allowed
if they match an actual column name in the table (i.e., when shadowed).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Previously compound select would not start a transaction if
the rightmost subselect had no table references, e.g.
SELECT * FROM t UNION VALUES(1)
2. Previously the column names for the query were taken from the
rightmost subselect - instead, they should be taken from the
leftmost subselect.
Fixes#4030
```
turso> CREATE TABLE t (
a INTEGER,
b TEXT
);
CREATE TABLE t1 (
x INTEGER,
y TEXT
);
CREATE TRIGGER trg
AFTER UPDATE ON t1
BEGIN
INSERT INTO t
SELECT * FROM t
WHERE t.a = 42;
END;
insert into t VALUES (42, 'abc');
INSERT INTO t1 VALUES (0, '');
UPDATE t1 SET y = 'z' WHERE TRUE;
turso> select * from t union all select * from t1;
┌────┬─────┐
│ x │ y │
├────┼─────┤
│ 42 │ abc │
├────┼─────┤
│ 42 │ abc │
├────┼─────┤
│ 0 │ z │
└────┴─────┘
```
```
sqlite> CREATE TABLE t (
(x1...> a INTEGER,
(x1...> b TEXT
(x1...> );
sqlite>
sqlite> CREATE TABLE t1 (
(x1...> x INTEGER,
(x1...> y TEXT
(x1...> );
sqlite>
sqlite> CREATE TRIGGER trg
...> AFTER UPDATE ON t1
...> BEGIN
...> INSERT INTO t
...> SELECT * FROM t
...> WHERE t.a = 42;
...> END;
sqlite> insert into t VALUES (42, 'abc');
sqlite> INSERT INTO t1 VALUES (0, '');
sqlite>
sqlite> UPDATE t1 SET y = 'z' WHERE TRUE;
sqlite> select * from t union all select * from t1;
42|abc
42|abc
0|z
```
Reviewed-by: Preston Thorpe <preston@turso.tech>
Closes#4058
Closes#3318
LIKE(X,Y) is now case-sensitive for characters that are beyond ASCII
range.
I did not make any change in LIKE(X,Y,Z), please let me know if I should
do that!
Closes#3902
To avoid overflows, use i64 for p1,p2,p3,p5 in EXPLAIN output. This
matches SQLite's behaviour (https://github.com/sqlite/sqlite/blob/master
/src/vdbeaux.c#L2460-L2476).
I also changed the column type string for p4, there was a preexisting
mismatch between the value type and the explicit type string.
Closes https://github.com/tursodatabase/turso/issues/3944
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#3961
This PR implements the functionality to create an index on an expression
instead of just a column, and makes an attempt for the planner to be
able to use these indexes.
https://sqlite.org/expridx.html
This adds the field:
`expr: Option<Box<Expr>>` to `IndexedColumn`
which is `None` for regular indexed columns.
This also adds the sentinel value `EXPR_INDEX_SENTINEL` for the
`position_in_table` field of indexed col expressions, since they are
not, actually in the table.. and similarly: `Constraint` now has the
`table_col_position` as an Option now, because it is no longer a
guarantee that it refers to a col expression.
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#3990
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