Commit Graph

7229 Commits

Author SHA1 Message Date
Pekka Enberg
3f181c9145 Merge 'btree: Use correct byte offsets for page 1 in defragmentation ' from Jussi Saurio
## Beef
`defragment_page_fast()` incorrectly didn't use the version of
read/write methods on `PageContent` that does NOT add the 100 byte
database header into the requested byte offset.
this resulted in defragment of page 1 in reading 2nd/3rd freeblocks
from the wrong offset and reading/writing freeblock sizes and cell
offsets to the wrong location.
## Testing
Adds fuzz test for CREATE TABLE / DROP TABLE / ALTER TABLE, which I was
able to reproduce this with.

Closes #2491
2025-08-07 16:52:11 +03:00
Jussi Saurio
6cd7334afc btree/fix: use correct byte offsets for page1 in defragmentation
`defragment_page_fast()` incorrectly didn't use the version of
read/write methods on `PageContent` that does NOT add the 100 byte
database header into the requested byte offset.

this resulted in defragment of page 1 in reading 2nd/3rd freeblocks
from the wrong offset and writing cell offsets to the wrong location.
2025-08-07 15:42:06 +03:00
Jussi Saurio
aca02c9969 tests/fuzz: add schema operations fuzz test 2025-08-07 15:35:39 +03:00
Pekka Enberg
a1df9365a2 Merge 'javascript: Implement Statement.iterate()' from Pekka Enberg
Closes #2486
2025-08-07 15:05:56 +03:00
Pekka Enberg
1358e0be50 Merge 'bench/insert: use PRAGMA synchronous=full' from Jussi Saurio
synchronous=FULL means WAL is fsynced on every commit, which is what we
also do.
however we do not do the padding to sector alignment that sqlite does
(see #2450), which makes sqlite do more work than we do.

Closes #2485
2025-08-07 14:30:42 +03:00
Pekka Enberg
bae4406e32 testing/javascript: Enable iterate() test cases 2025-08-07 14:28:34 +03:00
Pekka Enberg
ab7b0dd1aa bindings/javascript: Implement Statement.iterate() 2025-08-07 14:28:34 +03:00
Pekka Enberg
b603ee7062 Merge 'JavaScript improvements' from Pekka Enberg
Closes #2467
2025-08-07 14:01:07 +03:00
Jussi Saurio
95c6c7581b bench/insert: use PRAGMA synchronous=full
synchronous=FULL means WAL is fsynced on every commit,
which is what we also do.

however we do not do the padding to sector alignment that
sqlite does (see #2450), which makes sqlite do more work
than we do.
2025-08-07 13:40:14 +03:00
Jussi Saurio
edd7c45c22 Merge 'Fix segfault on schema update for virtual tables' from Preston Thorpe
Closes #2478
```console
turso>create table t(a,b);
turso>insert into t select 1,2 from generate_series(1,20);
turso>create index idxa on t(a);
turso>create index idxb on t(b);
# segfault
```
The issue was the `turso_ext::Conn` pointer was stored on the
`VirtualTable`, which lives longer than necessary and the underlying
core `Connection` is not guaranteed pinned. Moving the `turso_ext::Conn`
on to the cursor fixes this issue because it's independent of the
Schema.
This also fixes the issue that comes up when that issue is fixed, and
some of the relevant code for this hadn't been updated when other
surrounding changes had been made.

Closes #2479
2025-08-07 09:02:11 +03:00
Jussi Saurio
eb7fa9693d Merge 'Return error on attempting to drop index associated with PRIMARY KEY and UNIQUE constraints' from
Closes issue #2455. Also includes tests.

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

Closes #2461
2025-08-07 09:00:02 +03:00
Pekka Enberg
be8e8ff7c0 Merge 'turso-sync: rewrite' from Nikita Sivukhin
This PR rewrites `turso-sync` package introduced in the #2334 and
renames it to the `turso-sync-engine` (anyway the diff will be
unreadable).
The purpose of rewrite is to get rid of Tokio because this makes things
harder when we wants to export bindings to WASM.
In order to achieve "runtime"-agnostic sync core but still be able to
use async/await machiner - this PR introduce usage of `genawaiter` crate
which allows to transfer async/await Rust state machines to the
generators. So, sync operations are just generators which can yield `IO`
command in case where there is a need for it.
Also, this PR introduces separate `ProtocolIo` in the `turso-sync-
engine` which defines extra IO methods:
1. HTTP interaction
2. Atomic read/writes to the file. This is not strictly necessary and
`turso_core::IO` methods can be extended to support few more things
(like `delete`/`rename`) - but I decided that it will be simpler to just
expose 2 more methods for sync protocol for the sake of atomic metadata
update (which is very small - dozens of bytes).
    * As a bonus, we can store metadata for browser in the
`LocalStorage` which may be more natural thing to do(?) (user can reset
everything by just clearing local storage)
The `ProtocolIo` works similarly to the `IO` in a sense that it gives
the caller `Completion` which it can check periodically for new data.

Closes #2457
2025-08-07 07:58:02 +03:00
Pekka Enberg
fa6c925751 bindings/javascript: Switch from RefCell to Cell 2025-08-07 07:47:10 +03:00
Preston Thorpe
f55d34a3db Merge 'Fix panic on loading extension on brand new connection' from Preston Thorpe
Closes #2476

Closes #2477
2025-08-06 23:44:38 -04:00
Preston Thorpe
0777fd9082 Merge 'implement the MaxPgCount opcode' from Glauber Costa
It is used by the pragma max_page_count, which is also implemented.

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

Closes #2472
2025-08-06 23:44:05 -04:00
PThorpe92
df2c39b98e Use load_insn macro for op_journal_mode 2025-08-06 23:42:47 -04:00
Glauber Costa
071330a739 implement the JournalMode vdbe instruction
We do this already, but not through any opcode.
Move it to an opcode for compatibility reasons.
2025-08-06 19:30:19 -05:00
rajajisai
44ba3caaaa Remove miscellaneous characters generated due varying CLI size 2025-08-06 16:02:15 -07:00
PThorpe92
ae99edf4a6 Adjust test to test for behavior of loading extension on brand new connection 2025-08-06 16:55:54 -04:00
PThorpe92
6cc5c66964 Remove useless close method on extension Connection 2025-08-06 16:46:44 -04:00
PThorpe92
e9838daa28 Update macro to use const conn pointer 2025-08-06 16:27:26 -04:00
PThorpe92
273c12b2b3 Remove extension Conn from VirtualTable to survive schema changes 2025-08-06 16:27:26 -04:00
PThorpe92
657f3f3095 Fix panic on loading extension on brand new connection 2025-08-06 15:51:49 -04:00
Jussi Saurio
7b4703eba4 Merge 'bench/insert: use locking_mode EXCLUSIVE and journal_mode=WAL for sqlite' from Jussi Saurio
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #2475
2025-08-06 21:56:05 +03:00
Jussi Saurio
ff128e2f20 bench/insert: use locking_mode EXCLUSIVE and journal_mode=WAL for sqlite 2025-08-06 21:40:43 +03:00
Jussi Saurio
64c8587f27 Merge 'IO More State Machine' from Pedro Muniz
I swear we just need one more state machine. Just more state machine
until we achieve IO tracking. (PS: this is a meme)

Closes #2462
2025-08-06 21:26:19 +03:00
Glauber Costa
f36974f086 implement the MaxPgCount opcode
It is used by the pragma max_page_count, which is also implemented.
2025-08-06 13:20:15 -05:00
Nikita Sivukhin
bfacc141b0 remove unnecessary change 2025-08-06 22:09:29 +04:00
Nikita Sivukhin
ae0dff6a55 cargo clippy --tests --fix 2025-08-06 22:09:29 +04:00
Nikita Sivukhin
09daa97150 format fixes 2025-08-06 21:53:03 +04:00
Nikita Sivukhin
b1f1526673 add DB file for tests 2025-08-06 21:47:18 +04:00
Jussi Saurio
cc98f9f88b Merge 'Direct schema mutation – add instruction' from Levy A.
**86%** performance improvement. We are **25x** faster than SQLite.
<img width="953" height="511" alt="image" src="https://github.com/user-
attachments/assets/fd717d1e-bbbe-4959-ae48-41afc73e5e9f" />
```
ALTER TABLE _ DROP COLUMN _`/limbo_drop_column/
                        time:   [1.8821 ms 1.8929 ms 1.9047 ms]
                        change: [-86.850% -86.733% -86.614%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  3 (3.00%) high mild
  2 (2.00%) high severe
Benchmarking `ALTER TABLE _ DROP COLUMN _`/sqlite_drop_column/: Warming up for 3.0000 s

`ALTER TABLE _ DROP COLUMN _`/sqlite_drop_column/
                        time:   [46.227 ms 46.258 ms 46.291 ms]
                        change: [-1.3202% -1.0505% -0.8109%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 15 outliers among 100 measurements (15.00%)
  10 (10.00%) high mild
  5 (5.00%) high severe
  ```

Closes #2452
2025-08-06 20:32:22 +03:00
Jussi Saurio
f8f2ad1e7a Merge 'refactor/btree: cleanup write/delete/balancing states' from Jussi Saurio
## Problem:
Currently `WriteState`, usually triggered by an insert operation, "owns"
the balancing state machine, even though a delete operation (tracked by
a separate `DeleteState`) can also trigger balancing, which results in
awkward back-and-forth switching between `CursorState::Write` and
`CursorState::Delete` during balancing.
## Fix:
1. Extract `balance_state` as a separate state machine, since its state
transitions are exactly the same regardless of whether an insert or a
delete triggered the balancing.
2. This allows to remove the different 'Balance-xxx' variants from
`WriteState`, as well as removing `WriteInfo` and `DeleteInfo`, as the
delete&insert states become just simple enums now. Each of them now has
a substate called `Balancing` which just delegates work to the balancing
state machine.
3. This further allows us to remove the awkward switching between
`CursorState::Delete` and `CursorState::Write` during a balance that
happens as a result of a deletion.

Reviewed-by: Nikita Sivukhin (@sivukhin)
Reviewed-by: Avinash Sajjanshetty (@avinassh)

Closes #2468
2025-08-06 20:19:17 +03:00
Levy A.
c9e1eca8dc feat: add DropColumn instruction 2025-08-06 13:39:30 -03:00
Levy A.
3bc1001a93 feat(bench): complete ALTER TABLE benchmarks 2025-08-06 13:38:26 -03:00
Nikita Sivukhin
253b4933f7 more small fixes 2025-08-06 19:30:16 +04:00
Nikita Sivukhin
5b9f3816b3 fix after rebase 2025-08-06 19:27:10 +04:00
Nikita Sivukhin
1763e9bbf9 small fixes 2025-08-06 19:26:55 +04:00
Nikita Sivukhin
b612259a3a more friendly copmletely runtime agnostic turso-sync-engine crate 2025-08-06 19:26:55 +04:00
pedrocarlo
7b746ccc65 adjust state machine for ptrmap_get 2025-08-06 11:32:21 -03:00
pedrocarlo
b529305b82 state machine for ptrmap_put 2025-08-06 11:32:21 -03:00
pedrocarlo
931384afb6 state machine fix for btree create for AutoVacuum::Full 2025-08-06 11:32:21 -03:00
pedrocarlo
f656d0bc20 header ref state machine 2025-08-06 11:32:21 -03:00
Pekka Enberg
0c9216d1cc Merge 'cdc: emit entries for schema changes' from Nikita Sivukhin
This PR emit CDC entries as changes in `sqlite_schema` table for DDL
statements: `CREATE TABLE` / `CREATE INDEX` / etc.
The logic is a bit tricky as under the hood `turso` can do some implicit
DDL operations like:
1. Creating auto-indexes in case of `CREATE TABLE`
2. Deletion of all attached indices in case of `DROP TABLE`
```
turso> PRAGMA unstable_capture_data_changes_conn('full');
turso> CREATE TABLE t(x, y, z UNIQUE, q, PRIMARY KEY (x, y));
turso> CREATE INDEX t_xy ON t(x, y);
turso> CREATE TABLE q(a, b, c);
turso> ALTER TABLE q DROP COLUMN b;

turso> SELECT
change_id, 
id,
change_type, 
table_name,
bin_record_json_object(table_columns_json_array(table_name), before) AS before,
bin_record_json_object(table_columns_json_array(table_name), after) AS after
FROM turso_cdc;
┌───────────┬────┬─────────────┬───────────────┬─────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────┐
│ change_id │ id │ change_type │ table_name    │ before                                                              │ after                                                               │
├───────────┼────┼─────────────┼───────────────┼─────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────┤
│         1 │  2 │           1 │ sqlite_schema │                                                                     │ {"type":"table","name":"t","tbl_name":"t","rootpage":3,"sql":"CREA… │
├───────────┼────┼─────────────┼───────────────┼─────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────┤
│         2 │  5 │           1 │ sqlite_schema │                                                                     │ {"type":"index","name":"t_xy","tbl_name":"t","rootpage":6,"sql":"C… │
├───────────┼────┼─────────────┼───────────────┼─────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────┤
│         3 │  6 │           1 │ sqlite_schema │                                                                     │ {"type":"table","name":"q","tbl_name":"q","rootpage":7,"sql":"CREA… │
├───────────┼────┼─────────────┼───────────────┼─────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────┤
│         4 │  6 │           0 │ sqlite_schema │ {"type":"table","name":"q","tbl_name":"q","rootpage":7,"sql":"CREA… │ {"type":"table","name":"q","tbl_name":"q","rootpage":7,"sql":"CREA… │
└───────────┴────┴─────────────┴───────────────┴─────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────┘
```
For now, CDC capture only all explicit operations and ignore all
implicit operations. The reasoning for that is that one use case for CDC
is to apply logical changes as is with simple SQL statements - but if
implicit operations will be logged to the CDC table too - we can have
hard times using simple SQL statement (for example, creation of
`autoindices` will always work; implicit deletion of indices for `DROP
TABLE` also can lead to some troubles and force us to is `DROP INDEX IF
EXISTS ...` statements + we will need to filter out autoindices in this
case too).
Also, to simplify PR, for now `DatabaseTape` from `turso-sync` package
just ignore all schema changes from CDC table.

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

Closes #2426
2025-08-06 14:48:27 +03:00
Jussi Saurio
c8d2a1a480 btree: add a few more assertions about balance state 2025-08-06 13:39:20 +03:00
Jussi Saurio
a86a0e194d refactor/btree: cleanup write/delete/balancing states
Problem:

Currently `WriteState` "owns" the balancing state machine, even
though a separate `DeleteState` can also trigger balancing, which
results in awkward back-and-forth switching between `CursorState::Write`
and `CursorState::Delete` during balancing.

Fix:

1. Extract `balance_state` as a separate state machine, since its
state transitions are exactly the same regardless of whether an
insert or a delete triggered the balancing.
2. This allows to remove the different 'Balance-xxx' variants from
`WriteState`, as well as removing `WriteInfo` and `DeleteInfo`, as
those states become just simple enums now. Each of them now has a state
called `Balancing` which just delegates work to the balancing state
machine.
3. This further allows us to remove the awkward switching between
`CursorState::Delete` and `CursorState::Write` during a balance that
happens as a result of a deletion.
2025-08-06 13:37:35 +03:00
Jussi Saurio
aaa9ed1d9f Merge 'Add regexp capture' from bit-aloo
This PR adds RegExp Capture to regexp module

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

Closes #2465
2025-08-06 12:13:19 +03:00
Jussi Saurio
8e4597d11b Merge 'Add load_insn macro for compiler hint in vdbe::execute hot path' from Preston Thorpe
The built-in `unreachable!` macro, believe it or not is just an alias
for `panic!` and does not actually provide the compiler with a hint that
the path is not reachable.
This provides a wrapper around the actual
`std::hint::unreachable_unchecked()`, to be used only in the very hot
path of `execute` where it is not possible to be the incorrect variant.

Closes #2459
2025-08-06 12:05:33 +03:00
Jussi Saurio
9cf8a9b533 Merge 'refactor/btree: don't clone WriteState during balancing' from Jussi Saurio
## Beef
Removes cloning of `WriteState` in `balance_non_root()` and removes the
`Clone` implementation from `WriteState`.  Not only is it unnecessary to
clone the state, but even having the `Clone` implementation available is
a real regression risk in `insert_into_page()` as it will deep clone
vectors that we require stable raw pointers to in `fill_cell_payload()`
## Details
In removing the cloning of `WriteState` in `balance_non_root()`, this
replaces the `next_write_state` logic with a plain loop, which ends up
looking like a lot of changes, but it's really not. Main things that
were changed:
- Loop and mutate state instead of create new state and return
- Make `WriteInfo` an `Option` in `WriteState` for ergonomics so it can
be `.take()`:n instead of `.clone()`:d
- Clone a `Rc<Pager>` in `balance_non_root()` so that we can call
`pager.do_allocate_page()` directly instead of `self.allocate_page()`
which is just a facade for the pager method, and would violate borrowing
rules.
- assign `let usable_space = self.usable_space()` at the beginning of
the balance loop for similar reasons.
- Make the balance debug validation methods static so they don't require
a reference to self, for similar reasons

Reviewed-by: Nikita Sivukhin (@sivukhin)
Reviewed-by: Avinash Sajjanshetty (@avinassh)

Closes #2466
2025-08-06 12:03:23 +03:00
Jussi Saurio
5f3cfaac60 refactor/btree: don't clone WriteState in balance_non_root() 2025-08-06 11:30:09 +03:00