This format is based on previous discussions:
1. Log header
```rust
/// Log's Header, this will be the 64 bytes in any logical log file.
/// Log header is 64 bytes at maximum, fields added must not exceed that size. If it doesn't exceed
/// it, any bytes missing will be padded with zeroes.
struct LogHeader {
version: u8,
salt: u64,
encrypted: u8, // 0 is no
}
```
2. Transaction format:
* Transaction id
* Checksum u64
* Byte size of all rows combined
* Rows
* End marker (offset position after appending buffer)
3. Row format:
```rust
/// Serialize a row_version into on disk format.
/// Format of a "row" (maybe we could change the name because row is not general enough for
/// future type of values):
///
/// * table_id (root page) -> u64
/// * row type -> u8
///
/// (by row type)
/// Delete:
/// * Payload length -> u64
/// * Rowid -> varint
///
/// Insert:
/// * Payload length -> u64
/// * Data size -> varint
/// * Rowid -> varint
/// * Data -> [u8] (data size length)
fn serialize(&self, buffer: &mut Vec<u8>, row_version: &RowVersion) {
```
Closes#3245
fixes#3231
```zsh
❯ sqlite3
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> CREATE TABLE t1 (a);
ALTER TABLE t1 ADD COLUMN a;
Parse error: duplicate column name: a
sqlite> ALTER TABLE t1 ADD COLUMN name varchar(255);
SELECT sql FROM sqlite_schema WHERE name = 't1';
CREATE TABLE t1 (a, name varchar(255))
sqlite>
```
```zsh
turso>
turso> CREATE TABLE t1 (a);
ALTER TABLE t1 ADD COLUMN a;
x Parse error: duplicate column name: a
turso> ALTER TABLE t1 ADD COLUMN name varchar(255);
SELECT sql FROM sqlite_schema WHERE name = 't1';
┌─────────────────────────────────────────┐
│ sql │
├─────────────────────────────────────────┤
│ CREATE TABLE t1 (a, name varchar (255)) │
└─────────────────────────────────────────┘
turso>
```
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>
Closes#3249
This PR implements the `Sequence` and `SequenceTest` opcodes, although
does not yet add plumbing to emit the latter.
SQLite has two distinct mechanisms that determine the final row order
with aggregates:
Traversal order of GROUP BY, and ORDER BY tiebreaking. When ORDER BY
contains only aggregate expressions and/or constants, SQLite has no
extra tiebreak key, but when ORDER BY mixes aggregate and non-aggregate
terms, SQLite adds an implicit, stable row `sequence` so “ties” respect
the input order.
This PR also fixes an issue with a query like the following:
```sql
SELECT u.first_name, COUNT(*) AS c
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.first_name
ORDER BY c DESC;
```
Because ORDER BY has only an aggregate (COUNT(*) DESC) and no non-
aggregate terms, SQLite traverses the group key (u.first_name) in DESC
order in this case, so ties on c naturally appear with group keys in
descending order.
Previously tursodb would return the group key sorted in ASC order,
because it was used in all cases as the default
Closes#3287
`TransitionResult::Continue` is an internal implementation detail that
tells an invocation of `StateMachine::step()` to continue looping, but
it is of no use to upstream callers.
For this reason, just return an IOResult from StateMachine::step() which
simplifies the result handling.
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>
Closes#3248
TransitionResult is an internal implementation detail that tells
an invocation of StateMachine::step() to continue looping, but it
is of no use to other callers.
For this reason, just return an IOResult from StateMachine::step()
which simplifies the result handling.
This PR does not implement MVCC checkpoint yet, just adds a lock for it.
MVCC checkpoints are always TRUNCATE, plus they block all other
transactions. This guarantees that never need to let transactions read
from the SQLite WAL.
In MVCC, the checkpoint procedure is roughly as follows:
- Take the blocking_checkpoint_lock
- Write everything in the logical log to the pager, and from there
commit to the SQLite WAL.
- Immediately TRUNCATE checkpoint the WAL into the database file.
- Release the blocking_checkpoint_lock.
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>
Closes#3244
This PR adds support for partial indexes, e.g. `CREATE INDEX` with a
provided predicate
```sql
CREATE UNIQUE INDEX idx_expensive ON products(sku) where price > 100;
```
The PR does not yet implement support for using the partial indexes in
the optimizer.
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#3228