Register now for early access to concurrent writes in the Turso Cloud. Join the waitlist

Turso v0.7.0

Cover image for Turso v0.7.0

Today we are announcing the release of Turso 0.7. Where 0.6 was the release that brought Turso to parity with the major features of SQLite, 0.7 is about making the engine faster and more robust under real-life workloads. Turso now runs in production at multiple organizations, and while we are not yet at 1.0, we feel comfortable enough to officially drop the beta warning. Of course, Turso is still under active development, and some features are explicitly marked experimental. Our bar is SQLite-level reliability, one of the most rigorously tested pieces of software in the world. Until we get there, we recommend keeping regular independent backups, which is good practice for any database.

In this release, concurrent writes through the MVCC engine are significantly faster, recovery is leaner, and the memory cost of keeping multiple row versions around is much lower. A lot of work also went into ensuring Turso behaves well as a library embedded in someone else's process, as part of our integration with Turso Cloud. The core engine no longer blocks the calling thread on I/O, no longer aborts when it runs out of memory, and yields the CPU during long operations so a busy statement cannot starve other connections sharing the same runtime. Beyond the engine, 0.7 improves the SQL interface with sequences, ICU collations, ordered-set aggregates, and the groundwork for window functions, and it brings a SQLite-compatible .NET provider and full-text search to the Python SDK.

#Core

A lot of 0.7 went into making the engine behave well as a library embedded in someone else's process: never blocking the calling thread, never aborting on allocation failure, and staying responsive during long operations.

#CPU yielding

Long-running operations no longer monopolize the thread. Several MVCC code paths that previously ran a loop to completion now yield cooperatively after a bounded amount of work, including serializing a large transaction (#7033, Pere Diaz Bou), collecting rows during a checkpoint (#7248, Pere Diaz Bou), and garbage collection (#7318, Pere Diaz Bou). Those early yields reused the existing I/O suspension path, so the caller could not tell a deliberate CPU yield apart from waiting on I/O. A dedicated StepResult::Yield now makes yielding first-class, surfacing an explicit suspension to every step loop in the workspace (#7458, Nikita Sivukhin). Together this lets a busy statement give the CPU back so it cannot starve other connections sharing the same runtime.

#No more blocking I/O

We removed the internal io.block and wait_for_completion calls from the core engine. These were problematic in two settings: on platforms like WASM where I/O is driven externally by the runtime, and for embedders running Turso in a multi-tenant async context, where a blocking call stalls unrelated work. The engine now yields on a pending completion instead of parking the thread, including the cache-spilling path inside Pager::read_page that previously had no choice but to block (#7277, #7446, Preston Thorpe). A last blocking spill in the page cache, which could hang the WASM build running on OPFS, was eliminated late in the cycle (#7821, Nikita Sivukhin).

#Memory limits

Turso is moving to fallible allocation throughout the core so that running out of memory surfaces as an error rather than a panic. This is what lets one tenant hit a memory limit without taking down a server shared with others. The work starts with a SQLite-style global allocator and a set of extension traits for allocation-aware, fallible APIs (#6853, Pedro Muniz), and a proc-macro out-of-memory fault injector that annotates allocation sites so the failure paths are actually exercised under test (#7517, Pedro Muniz). Hot data structures including the sorter, hash table, bitset, and skiplist were migrated to propagate allocation errors instead of aborting, and the effort has since spread through the translator, the VDBE, and the schema layer, making statement translation, schema cloning, and schema bootstrap allocation-fallible (#7715, #7734, #7746, #7790, Pedro Muniz). The MVCC version store can also run on a custom allocator (#7632, Pedro Muniz).

#Page cache as a soft limit

The page cache now treats its configured capacity as a soft limit, matching SQLite. Previously, reading or allocating a page while every cache-resident page was pinned, held by a cursor, or dirty and unspillable failed the statement with a busy or cache-full error. The pager now spills dirty pages to the WAL first to make room, and when nothing can be evicted it admits the page over capacity and drains the excess back under the limit as pages become evictable again (#7804, Jussi Saurio).

#One write statement at a time

Because Turso's I/O is asynchronous, a write statement can be suspended mid-flight — and if the application abandons it, the transaction contains changes from a statement that never finished. A connection now runs one write statement at a time: starting a second write while another is mid-flight returns busy, and abandoning a half-done write inside an interactive transaction poisons the transaction so it refuses to commit a partial statement. This is a deliberate, documented divergence from SQLite, which never pauses mid-mutation and so never faces the problem (#7420, Jussi Saurio).

#Index-aware join planning

The greedy join planner used to pick its starting table by how many other tables reference it, which in long chain joins of a dozen or more tables could select a table at the wrong end of the chain and force a cascade of full scans. Starting-table selection is now index-aware, prioritizing tables that unlock indexed seeks into the rest of the join (#6607, Akira Noda).

#Pluggable I/O backends

Custom storage backends written in Rust can now be registered at runtime through a global registry, without modifying Turso's core. A backend registered under a name is resolved wherever a vfs= parameter is accepted, so it becomes available through every language binding (#7258, Robert Sturla).

#Faster statement preparation

Preparing a statement with many bound parameters used to do a linear scan per parameter, which turned quadratic and became painful past ten thousand parameters. Resolving parameters through an index cuts the prepare time for a thousand-parameter insert roughly in half (#7563, Nikita Sivukhin).

#Concurrent Writes

Turso's MVCC engine took a big step forward in 0.7. Concurrent writes are faster, recovery is leaner, and the memory cost of keeping multiple row versions around is much lower.

#Faster reads under concurrent writes

The largest single win came from how the MVCC cursor reconciles the durable B-tree against in-memory row versions. The dual cursor walks both structures together, and for every B-tree row it stepped over it used to run a fresh skiplist lookup to decide whether a newer version shadowed that row. On write-heavy workloads, roughly a quarter of on-CPU time was spent comparing keys in that path. Since both structures are ordered by the same key, the check can be answered by a finger co-advanced with the cursor instead of a point lookup per row, turning an N·log(N) scan into a linear merge (#7501, Pere Diaz Bou). A series of related changes removed redundant seeking on contiguous writes and cut record cloning along the hot path.

#Configurable garbage collection and lighter checkpoints

Reclaiming obsolete row versions is now decoupled from checkpointing and driven by watermarks, so memory can be released as soon as a version becomes invisible to every active and future transaction rather than waiting for the next checkpoint (#7493, Preston Thorpe). Checkpoint finalization additionally shrinks oversized version chains and drops empty slots, preventing hot rows from pinning peak allocations indefinitely (#7444, Pedro Muniz).

#Experimental passive checkpoints

An MVCC checkpoint used to stop the world for the entire write-out. A new experimental passive checkpoint mode, gated behind --experimental-mvcc-passive-checkpoint, runs the write-out phase — collecting row versions and materializing them into the B-tree and WAL — without holding the global lock, so concurrent BEGIN CONCURRENT transactions keep committing while the checkpoint runs. Only a brief publish window at the end is exclusive (#7583, Pere Diaz Bou).

#Smaller per-version footprint

Several changes reduced the memory each row version costs. Begin and end timestamps are now packed into a tighter RowVersion layout (#7467, Preston Thorpe), record keys are shared via Arc (#7465, Pere Diaz Bou), and transaction serialization writes directly into the destination buffer instead of allocating a second copy (#7041, Pere Diaz Bou).

#Encrypted MVCC on pluggable storage

Durable storage backends can now run encrypted, which makes MVCC usable with custom storage implementations such as object stores (#7353, Avinash Sajjanshetty).

#MVCC autoincrement

AUTOINCREMENT now behaves correctly under concurrent transactions, built on the new PostgreSQL-style sequences (#7137, Glauber Costa). Because a transaction can allocate a sequence value, stay open, and commit after another transaction with a higher value, a new sequence_watermark() function exposes the highest value that is safe for cursor-based readers to pass, which keeps Change Data Capture consumers from skipping rows (#7423, Preston Thorpe).

#Syncing MVCC databases

The sync engine can now sync MVCC databases. After an initial page bootstrap, it pulls the MVCC logical log directly, decodes the portable change metadata that Turso attaches to commits — stable table names, schema changes, rowids, and record bytes — and replays those changes through the existing replay path instead of shipping pages (#7500, Preston Thorpe).

#SQL features

0.7 widened the SQL surface with sequences, ICU collations, ordered-set aggregates, and the groundwork for window functions.

#PostgreSQL-style sequences

Sequences are now first-class schema objects. CREATE SEQUENCE and DROP SEQUENCE define them, and nextval(), setval(), and currval() operate on them, with the usual INCREMENT BY, MINVALUE, MAXVALUE, START, and CYCLE options. AUTOINCREMENT was reimplemented on top of the same infrastructure, so an autoincrement table implicitly owns a sequence and the high-water-mark logic is unified across both (#7137, Glauber Costa).

#Locale-backed collations

Turso now supports ICU collations, so text can be sorted in a specific locale with configurable behavior such as case ordering and accent sensitivity (#7174, Aziz Batihk). These build on the core custom-collation support added the same cycle, which lets a collation be registered by name and used anywhere a built-in collation can (#7207, Marc-André Moreau).

#REINDEX

REINDEX repopulates indexes after a collation or definition change. It works at every granularity: REINDEX rebuilds all indexes, REINDEX table_name rebuilds the indexes of one table, REINDEX index_name rebuilds a single index, and REINDEX collation_name rebuilds every index that uses a given collation (#6674, Jussi Saurio).

#Window functions

Window function support got its foundation in 0.7. The row_number() special case was rerouted through the same VDBE aggregate machinery that the remaining window functions will use, and stubs for the builtin window functions were put in place (#7358, Jussi Saurio). On top of that, window clauses now accept a FILTER clause (#7265, Jussi Saurio), and declaration order is preserved when window subqueries are nested (#7389, Jussi Saurio).

#More SQL surface

Several smaller additions rounded out compatibility. Ordered-set aggregates using the SQL-standard f(args) WITHIN GROUP (ORDER BY x) syntax are supported for mode(), percentile_cont(), and percentile_disc() (#7293, Glauber Costa). FULL OUTER JOIN now works with non-equality and absent ON clauses, not just equi-joins (#7208, russell romney). A set of SQL-standard scalar functions and PostgreSQL-compatible aliases such as chr, strpos, btrim, and char_length were added (#7491, Glauber Costa). And UPSERT can now target partial indexes (#7225, Pedro Muniz).

#SDKs

Turso gained a .NET provider, full-text search in Python, richer transaction support in JavaScript, and signed Windows builds.

#SQLite-compatible .NET bindings

0.7 introduces an experimental .NET provider built around a Turso.Data.Sqlite facade and a lower-level Turso.Raw package. The goal is to make existing Microsoft.Data.Sqlite and ADO.NET code portable to Turso with minimal changes (#7036, Marc-André Moreau). The provider also exposes Turso's managed extension APIs, so .NET code can register scalar functions, aggregate functions, and custom collations directly (#7306, Marc-André Moreau). The packages ship through a NuGet publishing workflow, including native targets for mobile (#7510, Marc-André Moreau).

There is now an Entity Framework Core provider, Turso.EntityFrameworkCore.Sqlite, which plugs into EF Core through UseTurso(...) and reuses EF Core's SQLite translation (#7621, Marc-André Moreau). The provider can also talk to a remote Turso database over Hrana, with a managed HTTP pipeline client backing ExecuteReader, ExecuteScalar, and ExecuteNonQuery (#7633, Marc-André Moreau), plus remote transactions and ADO.NET batch execution (#7635, Marc-André Moreau). For NativeAOT deployments, opt-in per-platform companion packages statically link the native library, producing a single self-contained executable with no turso_sdk_kit sidecar (#7793, Marc-André Moreau).

#Python

The Python SDK now compiles in Tantivy-backed full-text search, so CREATE INDEX ... USING fts works from Python when the index_method experimental feature is enabled (#7412, Pekka Enberg). The binding also moved to a single abi3 wheel that runs across Python versions instead of one wheel per version (#7098, Pekka Enberg).

The Python binding used to hold the GIL for the entire duration of query execution, which serialized concurrent reads even across separate connections and made query cancellation impossible. The GIL is now released during execution, so threaded and async servers get actual parallelism, and connections gained interrupt() — a drop-in match for sqlite3.Connection.interrupt() — along with a Turso-specific set_query_timeout() that enforces a per-statement deadline with no watchdog thread required (#7691, Glauber Costa).

#JavaScript

The JavaScript SDK gained a concurrent transaction mode, so db.transaction(fn).concurrent() runs the body under BEGIN CONCURRENT alongside the existing deferred, immediate, and exclusive modes (#6721, Pekka Enberg). A new Database.batch() method executes an array of statements over a single connection, with per-statement results and an optional transactional mode (#7133, #7343, Pekka Enberg).

#Signed Windows builds

Windows binaries are now code-signed with Azure Trusted Signing, covering both tursodb.exe in the CLI release and the native turso_sdk_kit.dll used by the .NET packages (#7618, Pekka Enberg). The CLI also gained a Windows ARM64 target, building and signing aarch64-pc-windows-msvc artifacts and wiring up the matching win32-arm64 npm package (#7634, Marc-André Moreau).