Mar 4, 2026

# Turso v0.5.0

[Pekka Enberg](/content/author/pekka-enberg/index.html)

Turso v0.5.0 is now released with the following changes:

- [Concurrent writes is now beta](/content/blog/turso-0.5.0#concurrent-writes-is-now-beta/index.html)
- [Experimental full-text search with Tantivy](/content/blog/turso-0.5.0#experimental-full-text-search-with-tantivy/index.html)
- [Query optimizer improvements](/content/blog/turso-0.5.0#query-optimizer-improvements/index.html)
- [STRICT tables and user-defined types](/content/blog/turso-0.5.0#strict-tables-and-user-defined-types/index.html)
- [Change data capture is now stable](/content/blog/turso-0.5.0#change-data-capture-is-now-stable/index.html)

You can find the full changelog [here](https://github.com/tursodatabase/turso/blob/main/CHANGELOG.md).

To install Turso, see the [getting started instructions](https://github.com/tursodatabase/turso?tab=readme-ov-file#getting-started).

## [#](/content/blog/turso-0.5.0#concurrent-writes-is-now-beta/index.html) Concurrent writes is now beta

SQLite is fast, but its single-writer model limits scalability. Turso solves this with a new transaction mode, `BEGIN CONCURRENT`, inspired by [SQLite's long-standing experimental feature](https://www.sqlite.org/cgi/src/doc/begin-concurrent/doc/begin_concurrent.md) that has not been merged to mainline, at least for now. We first introduced concurrent writes as a [tech preview](/content/blog/beyond-the-single-writer-limitation-with-tursos-concurrent-writes/index.html) built on multi-version concurrency control (MVCC), adapting the [Hekaton MVCC approach](https://www.vldb.org/pvldb/vol5/p298_per-aborge_vldb2012.pdf) to SQLite's B-Tree storage architecture.

In 0.5, concurrent writes move from tech preview to **beta**. You enable MVCC with `PRAGMA journal_mode = 'mvcc'`, and the feature is now available for limited production use. As with the rest of Turso, beta means: it may still contain bugs and unexpected behavior — use caution with production data and ensure you have backups. See the [concurrent writes documentation](https://docs.turso.tech/tursodb/concurrent-writes) for details.

The improvements to MVCC in this release are across the board:

**Commit dependency tracking.** Turso now implements Hekaton's speculative read protocol: transactions track commit dependencies at validation time rather than aborting eagerly, allowing more concurrency under contention ( [#5328](https://github.com/tursodatabase/turso/pull/5328)).

**Checkpoint improvements.** The MVCC logical log has been rewritten from scratch with defined crash recovery semantics and a rolling checksum for corruption detection ( [#5109](https://github.com/tursodatabase/turso/pull/5109)). The logical log is now automatically checkpointed when it grows beyond a threshold, mirroring the WAL autocheckpoint behavior ( [#5008](https://github.com/tursodatabase/turso/pull/5008)). Several checkpoint correctness fixes were also landed, including a panic when deleting index keys in interior B-tree nodes ( [#5363](https://github.com/tursodatabase/turso/pull/5363)) and ensuring dropped root page tracking survives restart ( [#5365](https://github.com/tursodatabase/turso/pull/5365)).

**Garbage collection.** The in-memory MVCC version index now performs low-watermark-based garbage collection, reclaiming row versions that are no longer visible to any active transaction ( [#5087](https://github.com/tursodatabase/turso/pull/5087)).

**Savepoint support.** MVCC now supports both implicit statement-level savepoints ( [#4524](https://github.com/tursodatabase/turso/pull/4524)) and named savepoints ( [#5206](https://github.com/tursodatabase/turso/pull/5206)), providing proper rollback on constraint violations and explicit `SAVEPOINT`/`ROLLBACK TO` within concurrent transactions.

**Conflict detection.** Conflict detection has been significantly hardened: integer primary key conflicts are now caught at commit time ( [#5014](https://github.com/tursodatabase/turso/pull/5014)), unique index conflicts are properly checked ( [#5091](https://github.com/tursodatabase/turso/pull/5091)), and delete-delete conflicts against B-tree-resident tombstones are now detected ( [#5572](https://github.com/tursodatabase/turso/pull/5572)). A snapshot isolation bug was also fixed ( [#5520](https://github.com/tursodatabase/turso/pull/5520)).

**Lock-free rowid allocation.** Concurrent transactions no longer contend on rowid allocation thanks to a monotonic lock-free allocator ( [#5598](https://github.com/tursodatabase/turso/pull/5598)).

**Per-database MVCC for attached databases.**`ATTACH`ed databases now each get their own independent MVCC transaction state ( [#5643](https://github.com/tursodatabase/turso/pull/5643)).

**Verification.** MVCC is now tested with a concurrent simulator that injects faults during crash recovery ( [#5570](https://github.com/tursodatabase/turso/pull/5570)), [Antithesis](https://antithesis.com/) for autonomous fault injection ( [#5380](https://github.com/tursodatabase/turso/pull/5380)), the [Hermitage](https://github.com/ept/hermitage) test suite for isolation level correctness ( [#5102](https://github.com/tursodatabase/turso/pull/5102)), and [Elle](https://github.com/jepsen-io/elle) for transactional consistency checking with both list-append and rw-register models under chaotic workloads ( [#5605](https://github.com/tursodatabase/turso/pull/5605), [#5628](https://github.com/tursodatabase/turso/pull/5628)).

## [#](/content/blog/turso-0.5.0#experimental-full-text-search-with-tantivy/index.html) Experimental full-text search with Tantivy

Turso 0.5 ships with experimental full-text search powered by [Tantivy](https://github.com/quickwit-oss/tantivy), a Rust-native search engine ( [#4593](https://github.com/tursodatabase/turso/pull/4593)). Unlike SQLite's FTS5, which uses shadow tables with its own tokenizer pipeline, Turso's FTS is implemented as a pluggable index method that stores Tantivy's inverted index segments directly inside the SQLite B-tree — no external files, no sidecar directories. Your full-text index lives in the same database file as your data.

The SQL interface follows the `CREATE INDEX ... USING` syntax:

```sql
CREATE INDEX idx_posts_search ON posts USING fts (title, body);

-- Query with relevance ranking
SELECT *, fts_score(title, body, 'turso database') AS score
FROM posts
WHERE fts_match(title, body, 'turso database')
ORDER BY score DESC
LIMIT 10;

-- Highlight matching terms
SELECT fts_highlight(body, 'turso', '<b>', '</b>') FROM posts;
```

Tokenizers and per-field weights are configurable at index creation time:

```sql
CREATE INDEX idx ON docs USING fts (title, body)
  WITH (tokenizer='simple', weights='title=2.0,body=1.0');
```

The feature is opt-in (behind the `fts` feature flag) and currently read-only during DML — queries work against a materialized snapshot of matching rowids, ensuring safe concurrent access ( [#5594](https://github.com/tursodatabase/turso/pull/5594), [#5641](https://github.com/tursodatabase/turso/pull/5641)). Performance work has eliminated redundant B-tree passes and added caching with a two-tier LRU (64 MB for metadata, 128 MB for index chunks) ( [#5256](https://github.com/tursodatabase/turso/pull/5256)). FTS is also available in the JavaScript SDK ( [#5557](https://github.com/tursodatabase/turso/pull/5557)).

## [#](/content/blog/turso-0.5.0#query-optimizer-improvements/index.html) Query optimizer improvements

The query optimizer and execution engine saw major work in this release, closing the gap with SQLite on complex analytical queries and pulling ahead on join strategies.

**Hash joins.** Hash joins landed in 0.4 but were inner-only. In 0.5 they gained bloom filters for early probe-side filtering ( [#4395](https://github.com/tursodatabase/turso/pull/4395)), LEFT and FULL OUTER join support with build/probe chaining ( [#5096](https://github.com/tursodatabase/turso/pull/5096)), partition spilling to disk when the build side exceeds the memory budget ( [#5001](https://github.com/tursodatabase/turso/pull/5001), [#4754](https://github.com/tursodatabase/turso/pull/4754)), safe build-side materialization planning ( [#4395](https://github.com/tursodatabase/turso/pull/4395)), and general performance improvements ( [#4747](https://github.com/tursodatabase/turso/pull/4747)). Several correctness fixes were also shipped for collation handling ( [#5387](https://github.com/tursodatabase/turso/pull/5387)), table mask calculations ( [#4901](https://github.com/tursodatabase/turso/pull/4901)), and WHERE term consumption ( [#5646](https://github.com/tursodatabase/turso/pull/5646)).

**Semi/anti joins.** Correlated `EXISTS` and `NOT EXISTS` subqueries are now decorrelated into semi and anti joins, avoiding repeated per-row subquery execution ( [#5604](https://github.com/tursodatabase/turso/pull/5604)).

**Multi-index OR scans.** Disjunctive predicates like `WHERE a = 1 OR b = 2` can now combine seeks across multiple indexes, with the results merged via a RowID bitmap ( [#4958](https://github.com/tursodatabase/turso/pull/4958)). Follow-up work enabled multi-index OR scans during joins, propagated output cardinality estimates, and stabilized DML on multi-index scan results ( [#5512](https://github.com/tursodatabase/turso/pull/5512), [#5204](https://github.com/tursodatabase/turso/pull/5204)).

**Cost estimation refactor.** The cost model was overhauled: `IN` lists and uncorrelated `IN` subqueries now participate in the constraint model, `LIKE` and closed-range predicates (`BETWEEN`, `a >= x AND a <= y`) constrain output cardinality, sort cost is properly accounted for instead of a hardcoded constant, and all fanout/selectivity constants were moved to a centralized `cost_params` module ( [#4815](https://github.com/tursodatabase/turso/pull/4815), [#4864](https://github.com/tursodatabase/turso/pull/4864)).

**FROM-clause and CTE materialization.** Subqueries in the `FROM` clause and common table expressions can now be materialized into ephemeral tables, giving the optimizer freedom to pick better join orders for the outer query ( [#4913](https://github.com/tursodatabase/turso/pull/4913)).

**TPC-H performance.**`AggContext` was refactored to eliminate allocation overhead in aggregate-heavy queries ( [#4779](https://github.com/tursodatabase/turso/pull/4779)). Combined with general query execution speedups ( [#4391](https://github.com/tursodatabase/turso/pull/4391)) and insert performance improvements ( [#4824](https://github.com/tursodatabase/turso/pull/4824)), several TPC-H queries saw significant end-to-end improvements.

## [#](/content/blog/turso-0.5.0#strict-tables-and-user-defined-types/index.html) STRICT tables and user-defined types

STRICT tables are no longer experimental ( [#5119](https://github.com/tursodatabase/turso/pull/5119)). In SQLite, STRICT tables enforce column type constraints at insertion time — a column declared `INTEGER` will reject a text value instead of silently coercing it. Turso now supports this fully, including CHECK constraints ( [#5040](https://github.com/tursodatabase/turso/pull/5040)).

SQLite has no type system beyond its five storage classes (`NULL`, `INTEGER`, `REAL`, `TEXT`, `BLOB`). Even with STRICT tables, you're limited to these base types — there's no way to define a `uuid` or `varchar(255)` that the database actually understands and enforces. Turso 0.5 changes this with user-defined types via `CREATE TYPE` ( [#5254](https://github.com/tursodatabase/turso/pull/5254)). A user-defined type maps a high-level value to a SQLite base type through `ENCODE`/`DECODE` expressions, with optional validation parameters:

```sql
CREATE TYPE uuid(value text) BASE blob
  ENCODE uuid_blob(value)
  DECODE uuid_str(value)
  DEFAULT uuid4_str()
  OPERATOR '<';

CREATE TYPE varchar(value text, maxlen integer) BASE text
  ENCODE CASE WHEN length(value) <= maxlen THEN value
         ELSE RAISE(ABORT, 'value too long for varchar') END
  DECODE value
  OPERATOR '<';

CREATE TABLE users (
  id uuid,
  name varchar(255)
) STRICT;
```

Turso ships with built-in types including `uuid`, `boolean`, `json`, `jsonb`, `date`, `time`, `timestamp`, `smallint`, `bigint`, `inet`, and `varchar`. Users can define their own with arbitrary SQL expressions for encoding/decoding logic. Types are stored in the database itself and loaded on open, so they travel with the file.

## [#](/content/blog/turso-0.5.0#change-data-capture-is-now-stable/index.html) Change data capture is now stable

[Change data capture (CDC)](/content/blog/introducing-change-data-capture-in-turso-sqlite-rewrite/index.html), which records row-level changes to a `turso_cdc` table for downstream sync consumers, has been promoted from unstable to stable. The PRAGMA has been renamed from `PRAGMA unstable_capture_data_changes_conn` to `PRAGMA capture_data_changes_conn` ( [#5610](https://github.com/tursodatabase/turso/pull/5610)), with the old name kept as an alias for backward compatibility. See the [CDC documentation](https://docs.turso.tech/tursodb/cdc) for details.

CDC also gained a v2 record format ( [#5340](https://github.com/tursodatabase/turso/pull/5340)). The new format adds a `change_txn_id` column and a `COMMIT` record type, enabling sync consumers to group changes by transaction and apply them atomically. Within a multi-row statement or an explicit `BEGIN`...`COMMIT` block, all rows share the same transaction ID with a single COMMIT record emitted at the end. A `turso_cdc_version` table now tracks the schema version per CDC table, with automatic detection and preservation of v1 tables on existing databases ( [#5271](https://github.com/tursodatabase/turso/pull/5271)).

## [#](/content/blog/turso-0.5.0#summary/index.html) Summary

Turso 0.5 also ships with improved `ATTACH` database support, `VACUUM INTO`, an experimental Windows IOCP backend, prepared statement caching, and hundreds of SQLite compatibility fixes. Over 3,000 commits from 50+ contributors went into this release since v0.4.0. The full changelog is [on GitHub](https://github.com/tursodatabase/turso/releases/tag/v0.5.0). If you run into issues or have ideas, open an issue on [GitHub](https://github.com/tursodatabase/turso) or join us on [Discord](https://discord.gg/turso).
