Commit graph

11507 commits

Author SHA1 Message Date
pedrocarlo
ffbbd4c270 add exec rows trait for more ergonomic testing in core_tester 2025-12-10 15:21:03 -03:00
pedrocarlo
c207eddd3f remove unused TempDatabase argument requirement for limbo_exec_rows 2025-12-10 15:21:03 -03:00
Jussi Saurio
2b7adabb21
Merge 'Sync better error messages' from Nikita Sivukhin
Improve errors messages for sync engine:
- Error for unexpected metadata file format
```
sync engine operation failed: deserialization error: unexpected metadata file format, 'version' field must be present and have string type
```
- Error from the server:
```
sync engine operation failed: database sync engine error: remote server returned an error: status=401, body={"error":"Unauthorized: `unauthorized access attempt on database: empty JWT token`"}
```

Closes #4155
2025-12-10 19:05:21 +02:00
Jussi Saurio
83de40cea3
Merge 'core/mvcc/cursor: implement count' from Pere Diaz Bou
Sadly, due to how we use dual cursors, we cannot use optimization under
btree cursor to count rows without first checking if the row in btree is
valid. So this is a slow count implementation.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds a state-driven `count()` implementation that iterates via dual
cursors, validating B-Tree keys and tallying visible rows.
>
> - **Core (MVCC Cursor)**:
>   - **Counting**:
>     - Implement `count()` using a small state machine (`CountState`)
to iterate (`rewind` → `next`) and tally rows, ensuring B-Tree keys are
validated via existing dual-cursor logic.
>   - **State Management**:
>     - Add `CountState` enum and `count_state` field to
`MvccLazyCursor` to keep count logic isolated from other cursor states.
>     - Initialize `count_state` in `new()`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
356ea0869d. 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 #4160
2025-12-10 19:05:01 +02:00
Jussi Saurio
f4d06dea01
Merge 'support libsql:// protocol as a sync url in python driver' from Nikita Sivukhin
turso cloud generate urls with `libsql://` protocol by default

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Reviewed-by: Pedro Muniz (@pedrocarlo)

Closes #4159
2025-12-10 19:04:37 +02:00
Jussi Saurio
64dba96c60
Merge 'initialize global header on bootstrap' from Pedro Muniz
On bootstrap just store the header but not flush it to disk. Only try to
flush it when we start an MVCC transaction. Also applied fix in
`OpenDup` where we should not wrap an ephemeral table with an MvCursor

Reviewed-by: Mikaël Francoeur (@LeMikaelF)
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #4151
2025-12-10 19:04:23 +02:00
Jussi Saurio
0d35366f5d
Merge 'Fix CTE scope propagation for compound SELECTs' from Martin Mauch
CTEs now work correctly when combined with UNION, UNION ALL, INTERSECT,
and EXCEPT.
**Before:**
```sql
WITH t AS (SELECT 1 as x) SELECT * FROM t UNION ALL SELECT 2 as x
-- Error: Parse error: no such table: t
```
**After:**
```sql
WITH t AS (SELECT 1 as x) SELECT * FROM t UNION ALL SELECT 2 as x
-- Works correctly, returns rows (1) and (2)
```

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

Closes #4123
2025-12-10 19:04:03 +02:00
Jussi Saurio
ac1b0b056d
Merge 'Rust binding add prepare to transaction' from Dave Warnock
This significantly simplifies using the Rust binding (and adds a more
complete example).
By adding prepare to Transaction (that just passes it onto the
Transactions connection) we can write simpler application code that is
passed a transaction. This allows multiple database update methods to be
flexibly grouped into transactions, even when they use prepared
statements.

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

Closes #4113
2025-12-10 19:03:50 +02:00
pedrocarlo
8f28aafa3c apply claude fix for OpenDup 2025-12-10 13:33:48 -03:00
pedrocarlo
d09b48c0e6 test page1 init 2025-12-10 12:53:25 -03:00
Pere Diaz Bou
356ea0869d core/mvcc/cursor: implement count 2025-12-10 16:41:43 +01:00
pedrocarlo
ee3d2bc863 maybe write page1 when reading an already initialized header 2025-12-10 11:33:49 -03:00
Pekka Enberg
a0123f62e5 Turso 0.4.0-pre.11 2025-12-10 16:14:58 +02:00
Pekka Enberg
1cf36f8270
Merge 'upgrade cargo dist to 0.30.2' from Nikita Sivukhin
in order to use macos-15-intel as macos-13 runners were deprecated by
Github
- https://github.com/axodotdev/cargo-dist/issues/2167
- https://github.com/axodotdev/cargo-dist/releases/tag/v0.30.1

Closes #4156
2025-12-10 16:14:40 +02:00
Nikita Sivukhin
0a5715e70e uvx ruff check --fix 2025-12-10 17:58:49 +04:00
Nikita Sivukhin
337e3247d4 support libsql:// protocol as a sync url in python driver 2025-12-10 17:50:24 +04:00
Jussi Saurio
2d64ab42a6
Merge 'add turso bot config' from Pedro Muniz
Closes #4152
2025-12-10 15:31:10 +02:00
Nikita Sivukhin
ceb754b3b8 dist init
- upgrade cargo dist to 0.30.2 in order to use macos-15-intel as macos-13 runners were deprecated by Github
- https://github.com/axodotdev/cargo-dist/issues/2167
- https://github.com/axodotdev/cargo-dist/releases/tag/v0.30.1
2025-12-10 17:29:07 +04:00
Pekka Enberg
1ce09b26ea Turso 0.4.0-pre.10 2025-12-10 15:09:56 +02:00
Nikita Sivukhin
7cdc17db00 add comment 2025-12-10 17:09:29 +04:00
Nikita Sivukhin
4a180dd659 improve error messages at sync engine 2025-12-10 17:08:24 +04:00
Jussi Saurio
50905f7acb
Merge 'Sqlite3 compat fix' from Nikita Sivukhin
This PR introduces fuzz test which maintains 2 database files and
periodically switch the "engine" which process operations over this
files between sqlite and turso. The purpose of this fuzz test is to
ensure compatibility with on-disk data format and logic depending on it
between sqlite and turso.
With this test, few bugs were identified:
1. automatic indices order were incompatible with sqlite in turso
because we processed constraints first and columns later (so, for cases
like `CREATE TABLE t (x UNIQUE, y, UNIQUE (y))` the order of indices
were `y` and `x` while sqlite expected that first autoindex is over `x`
and second is over `y`
2. after collecting `unique_sets` we need to process them in exactly
same order for the same reason describe above
3. now all write queries require subtransaction journal because without
this (with previous extra condition `if
!self.table_references.is_empty()`) we incorrectly processed `CREATE
UNIQUE INDEX` queries in case when they failed with `CONSTRAINT` error
(because column is not unique)
4. Table access method decision fixed in order to not use index with
collation different from table column collation
We still have at least one more bug which can be identified by
uncommenting fuzz test configuration (see issue
https://github.com/tursodatabase/turso/issues/4154):
```
// temporary disable this action - because right now we still have bug with affinity for insertion to the indices
// 35..=55 => Action::InsertData,  // ~20%
```
## AI disclosure
The fuzz test was mainly written by `openai/gpt-5` model with the
following prompt
<details>
[Code output="mod.rs" model="openai/gpt-5" language="rust"]
Write fuzz test which will simulate different data layout in SQLite and
switch connections between each other periodically.
The purpose of the test is to identify potential incompatibilities in
data layou between sqlite3 and turso.
From the layout perspective you MUST randomize following things:
1. UNIQUE constraint placement:
    * Mix 3 different options:
```sql
CREATE TABLE t (x, y UNIQUE);
CREATE TABLE t (x, y, UNIQUE (y));
CREATE UNIQUE INDEX t_idx ON t (y);
```
2. UNIQUE constraint column order - it must be randomized and multiple
column constraints must be fuzzed
3. Create INDEX column order
4. Column collation
5. Sort order for index columns
6. Amount of columns
7. AUTOINCREMENT columns (they are affecting data on disk as they force
creation of sqlite_sequence table)
Test also must execute read queries in order to be able to capture
potential layout mismatch:
Use SELECT with random subset of columns from the table for that
purpose.
The test should be structured like this:
```rs
struct FuzzTestState {
    tables: Vec<FuzzTestTable>,
    indices: Vec<FuzzTestIndex>,
}

#[test]
pub fn test_data_layout_compatibility() {
    const OUTER: usize = 100;
    const INNER: usize = 100;
    let (mut rng, seed) = rng_from_time_or_env();
    tracing::info!("test_data_layout_compatibility seed: {}", seed);

    let left = NamedTempFile::new().unwrap();
    let right = NamedTempFile::new().unwrap();
    let state = FuzzTestState::new();
    for i in 0..OUTER {
        let turso_path = if i % 2 == 0 { left.path() } else { right.path() };
        let sqlite_path = if i % 2 == 1 { left.path() } else { right.path() };
        let turso = TempDatabase::builder().with_db_path(turso_path).build();
        let sqlite = TempDatabase::builder().with_db_path(sqlite_path).build();
        for _ in 0..INNER {
            // use state to pick random valid action for a database among the list:
            // 1. create table
            // 2. create index
            // 3. select data from existing table
            let sql = "...";
            // execute sql against turso and sqlite and compare the generated rows
        }
    }
}
```
Use following snippet to understand style of tests and availble methods:
[Shell cmd="cat ./mod.rs | head -n 1000" /]
[/Code]
</details>

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

Closes #4153
2025-12-10 14:45:40 +02:00
Nikita Sivukhin
cc1b405b94 refine comment 2025-12-10 16:02:42 +04:00
Nikita Sivukhin
d6db3e19f0 fix clippy 2025-12-10 15:40:37 +04:00
Nikita Sivukhin
3b197e2c11 fix toy index implementation 2025-12-10 15:39:18 +04:00
Nikita Sivukhin
c1370a3930 avoid unnecessary code movement 2025-12-10 15:27:18 +04:00
Nikita Sivukhin
3fe88e6ab0 fix bug in the bugfix
- we must analyze constraints first but we must also preserve order of unique sets
2025-12-10 15:25:21 +04:00
Nikita Sivukhin
8cc40949a5 fix clippy 2025-12-10 15:08:44 +04:00
Nikita Sivukhin
e70428e976 add explicit insert action to the fuzz test and disable it for now 2025-12-10 14:53:32 +04:00
Nikita Sivukhin
df2e72e2c5 do not use index range search as access method if index collation differs from table column collation 2025-12-10 14:53:32 +04:00
Nikita Sivukhin
56674cd0b5 process unique_sets in proper order 2025-12-10 14:53:32 +04:00
Nikita Sivukhin
7279fa67c8 create automatic indices in correct order 2025-12-10 14:53:32 +04:00
Nikita Sivukhin
70b1e5716d add fuzz test which maintain sqlite3 and turso db and periodically switch them between each other in order to validate compatibility 2025-12-10 14:53:32 +04:00
Jussi Saurio
283131929c
Merge 'Sim transaction fixes' from Jussi Saurio
Some checks are pending
Build & publish @tursodatabase/database / db-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-apple-darwin - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-wasm32-wasip1-threads - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / Test DB bindings on Linux-x64-gnu - node@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Test DB bindings on browser@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Publish (push) Blocked by required conditions
Python / configure-strategy (push) Waiting to run
Python / test (push) Blocked by required conditions
Python / lint (push) Waiting to run
Python / linux (x86_64) (push) Waiting to run
Python / macos-arm64 (aarch64) (push) Waiting to run
Python / sdist (push) Waiting to run
Python / Release (push) Blocked by required conditions
Rust / cargo-fmt-check (push) Waiting to run
Rust / build-native (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Rust / build-native (macos-latest) (push) Waiting to run
Rust / build-native (windows-latest) (push) Waiting to run
Rust / clippy (push) Waiting to run
Rust / simulator (push) Waiting to run
Rust / test-limbo (push) Waiting to run
Rust / test-sqlite (push) Waiting to run
Rust Benchmarks+Nyrkiö / bench (push) Waiting to run
Rust Benchmarks+Nyrkiö / clickbench (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h-criterion (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h (push) Waiting to run
Rust Benchmarks+Nyrkiö / vfs-bench-compile (push) Waiting to run
## FIxes to simulator transactions
1. Rollback all sim transactions when `REOPEN_DATABASE` happens
2. Handle recoverable errors: `WriteWriteConflict` causes a rollback,
`TxError` (e.g. no transaction is active when trying to `COMMIT`) does
not -- in either case, don't shadow the results of the query but don't
fail the sim either
3. The issue with `apply_snapshot` in the current sim was that the sim
was executing queries on commit, instead of applying their effects. For
example, if transaction T1 did a `DELETE FROM t WHERE TRUE` it would
delete all the rows even if another mvcc transaction T2 had inserted
rows concurrently that should not be visible to T1. This was not a
problem in non-MVCC due to single writer, but it doesn't work for MVCC.
Instead, record the operations and then apply them in order on commit.
## Fixes to property / assertion handling
- e.g. in "ReadYourUpdatesBack", we first do an UPDATE and then a SELECT
to verify that those updates were actually made. Problem is the SELECT
assertion was made even if the UPDATE failed. Fix: change interaction
handling so that if a precondition in a property fails, the entire
property is abandoned.
## AI usage
Pretty much all of the PR is again generated by prompting Claude Code +
Opus 4.5 by giving it failing simulator seeds and CLI flags.

Reviewed-by: Pedro Muniz (@pedrocarlo)

Closes #4145
2025-12-10 09:14:33 +02:00
Jussi Saurio
683bb89765
Merge 'sim/aws: comment on existing issues instead of skipping duplicates' from Jussi Saurio
Currently the simulator running on AWS will skip posting issues that are
too similar to existing ones, to avoid spamming the issue tracker.
However, this loses some information about how frequent a given failure
is, so a better solution is to comment on the existing issue whenever a
similar failure occurs.
Changes:
- Track issue numbers alongside titles in openIssues
- Add commentOnIssue() to post comments on duplicate issues
- Extract shared createFaultDetails() from issue/comment body creation
## AI usage
Whole PR written using Opus 4.5. Prompt:
> @simulator-docker-runner/docker-entrypoint.simulator.ts @simulator-
docker-runner/github.ts Let's change this so that if it finds a
duplicate issue, it comments on the existing issue.
https://github.com/octokit/octokit.js
and
>  Can we share code between createCommentBody and createIssueBody? They
should be able to be very similar

Reviewed-by: Pekka Enberg <penberg@iki.fi>

Closes #4143
2025-12-10 08:59:23 +02:00
Jussi Saurio
750a0f5be8
Merge 'Fix complex unique sqlite3 compat' from Nikita Sivukhin
This PR fixes the sqlite3 compatibility bug as turso right now always
sort unique constraint columns in the order of **table** columns
definition.
This makes tursodb incompatible with sqlite3 - because sqlite3 expect
unique autoindex to have columns in the order of **constraint**
definition while turso will reorder them.
Consider following simple example:
```
$> sqlite3 no-compat.db
sqlite> create table t(a, b, UNIQUE (b, a));
sqlite> insert into t values (1, 2);
sqlite> select a, b from t;
1|2
$> tursodb no-compat.db
turso> select a, b from t;
2|1
```

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

Closes #4149
2025-12-10 08:58:50 +02:00
Jussi Saurio
70eeae0d88 expect instead of if let 2025-12-10 08:52:55 +02:00
Jussi Saurio
b10c85f4cb Ensure that 1. insert rows 2. table rename 3. insert more rows is tracked correctly 2025-12-10 08:51:59 +02:00
pedrocarlo
90fc218931 add turso bot config 2025-12-10 01:05:56 -03:00
pedrocarlo
2578723939 initialize global header on bootstrap 2025-12-10 00:48:32 -03:00
Dave Warnock
f3f8fe9b97 Added transaction.prepare test
This should check that ypou can call prepare on a transaction, execute,
commit and read back
2025-12-09 23:50:09 +00:00
Dave Warnock
9718c1f876
Merge branch 'tursodatabase:main' into Rust-Binding-add-prepare-to-transaction 2025-12-09 22:28:22 +00:00
Nikita Sivukhin
8c99c6923d fix clippy 2025-12-10 01:53:26 +04:00
Nikita Sivukhin
c528a07c52 preserve order of columns in the unique constraint 2025-12-10 01:46:45 +04:00
Nikita Sivukhin
9acf541e28 add compatibility test for multiple-columns unique constraint 2025-12-10 01:46:25 +04:00
Preston Thorpe
9e75e23eb6
Merge 'feat: adding check for unquoted literals in values()' from Rohith Suresh
Some checks are pending
Build & publish @tursodatabase/database / sync-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-apple-darwin - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-wasm32-wasip1-threads - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Python / configure-strategy (push) Waiting to run
Build & publish @tursodatabase/database / Test DB bindings on Linux-x64-gnu - node@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Test DB bindings on browser@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Publish (push) Blocked by required conditions
Python / test (push) Blocked by required conditions
Python / lint (push) Waiting to run
Python / linux (x86_64) (push) Waiting to run
Python / macos-arm64 (aarch64) (push) Waiting to run
Python / sdist (push) Waiting to run
Python / Release (push) Blocked by required conditions
Rust / cargo-fmt-check (push) Waiting to run
Rust / build-native (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Rust / build-native (macos-latest) (push) Waiting to run
Rust / build-native (windows-latest) (push) Waiting to run
Rust / clippy (push) Waiting to run
Rust / simulator (push) Waiting to run
Rust / test-limbo (push) Waiting to run
Rust / test-sqlite (push) Waiting to run
Rust Benchmarks+Nyrkiö / clickbench (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h-criterion (push) Waiting to run
Rust Benchmarks+Nyrkiö / vfs-bench-compile (push) Waiting to run
Rust Benchmarks+Nyrkiö / bench (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h (push) Waiting to run
Closes #3949
Closes #4018
```
turso> values(asdf);
  × Parse error: Unquoted identifier in VALUES clause: asdf
  
turso> select * from users;
┌────┬──────┐
│ id │ name │
├────┼──────┤
│  1 │ jack │
├────┼──────┤
│  2 │ jill │
└────┴──────┘
  turso> select id, (values(name)) as name_again from users;
┌────┬────────────┐
│ id │ name_again │
├────┼────────────┤
│  1 │ jack       │
├────┼────────────┤
│  2 │ jill       │
└────┴────────────┘
```

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

Closes #4003
2025-12-09 10:15:11 -05:00
Jussi Saurio
f8d291402a fix/sim: abandon the entire property if precondition fails
e.g. in "ReadYourUpdatesBack", we first do an UPDATE and then a
SELECT to verify that those updates were actually made.

Problem is the SELECT assertion was made even if the UPDATE failed.

Fix: change interaction handling so that if a precondition in a
property fails, the entire property is abandoned.
2025-12-09 16:06:14 +02:00
Pekka Enberg
01fc636da4 Turso 0.4.0-pre.9 2025-12-09 14:55:30 +02:00
Pekka Enberg
d2e26f944c Turso 0.4.0-pre.8 2025-12-09 14:39:23 +02:00
Pekka Enberg
e3b0b91de1
Merge 'Improved Python driver with opt-in asyncio support' from Nikita Sivukhin
This PR adds asyncio support to the python driver
We still ship single package but user can choose the configuration by
using different submodules:
```py
import turso # blocking driver for turso database
import turso.sync # blocking driver for syncrhonization engine on top of the turso database
import turso.aio # non-blocking driver for turso database
import turso.aio.sync # non-blocking driver for syncrhonization engine on top of the turso database
```

Closes #4137
2025-12-09 14:37:59 +02:00