# How Zed's Database Layer Uses SQLite: Architecture and Implementation Guide

> Explore Zed's database layer architecture using SQLite. Discover how the db and sqlez crates manage thread-safe data access for the UI.

- Repository: [Zed Industries/zed](https://github.com/zed-industries/zed)
- Tags: architecture
- Published: 2026-03-01

---

**Zed splits its SQLite-based persistence layer into a high-level `db` crate that defines what data to store and a low-level `sqlez` crate that manages thread-safe access from the UI thread.**

Zed, the high-performance code editor from [Zed Industries](https://github.com/zed-industries/zed), maintains application state in local SQLite files through a carefully structured database architecture. The **Zed database layer using SQLite** employs a two-crate design that separates domain-specific data models from low-level connection management, enabling safe concurrent reads while serializing writes through background queues.

## High-Level State Management with the `db` Crate

The `db` crate provides the public API that Zed's application code consumes. It handles database initialization, connection lifecycle management, and domain-specific persistence logic.

### Database Initialization and Corruption Recovery

The entry point for all database access is the `open_db` function in `crates/db/src/db.rs#L46`. This routine creates or opens a SQLite file under the application state directory (incorporating the current `RELEASE_CHANNEL.dev_name()` in the path), applies PRAGMA settings via `DB_INITIALIZE_QUERY`, and executes pending migrations.

If the database file is corrupted, `open_db` automatically moves the corrupt file to a backup directory, falls back to an in-memory database, and sets the `ALL_FILE_DB_FAILED` flag to signal the failure state to the rest of the application.

### Global Connection Access via the `static_connection!` Macro

To avoid repeatedly passing connection objects through the codebase, the `db` crate provides the `static_connection!` macro defined in `crates/db/src/db.rs#L110-L151`. This macro generates a globally accessible, lazily-initialized static variable wrapped in `LazyLock<KeyValueStore>`.

The macro hides async bootstrap complexity behind a synchronous interface, constructing the `ThreadSafeConnection` on first access. For example, the key-value store is declared as:

```rust
// In crates/db/src/kvp.rs
crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);

```

### Domain-Specific Data Models

Each persistent domain implements the `Domain` trait from the `sqlez` crate, declaring a unique `NAME` and a list of `MIGRATIONS`. The `KeyValueStore` implementation in `crates/db/src/kvp.rs#L14-L33` demonstrates this pattern, providing SQL statements to create the initial schema and migrate between versions.

Domains like `WorkspacePersistence` follow the same structure, allowing the migration system to evolve schemas independently while maintaining backward compatibility.

### Type-Safe SQL with the `query!` Macro

The `query!` macro generates typed helper methods that wrap raw SQL operations. In `crates/db/src/kvp.rs#L60-L66`, the macro expands to create `read_kvp` and `write_kvp_inner` methods that handle prepared statement construction, parameter binding, and result mapping automatically.

Generated methods return `anyhow::Result<T>` types, allowing callers to handle errors idiomatically while benefiting from compile-time SQL validation.

### Asynchronous Write Operations

Because SQLite allows only one writer at a time, the `db` crate provides `write_and_log` in `crates/db/src/db.rs#L54-L60` to schedule database mutations. This function accepts a closure returning a `Future` and dispatches it to the GPUI background executor, detaching the task and logging any errors without blocking the UI thread.

```rust
use gpui::App;

let mut cx = App::new();
write_and_log(&cx, || async move {
    KEY_VALUE_STORE
        .write_kvp("settings".into(), "value".into())
        .await
});

```

## Low-Level SQLite Abstractions in `sqlez`

The `sqlez` crate implements the unsafe SQLite interactions and concurrency primitives that make the high-level API possible.

### Raw Connection Management

The `Connection` struct in `crates/sqlez/src/connection.rs#L49-L60` provides a thin, safe wrapper around `*mut sqlite3` from `libsqlite3_sys`. It exposes `open_file` and `open_memory` constructors, enables extended error codes, and provides helper methods like `exec`, `backup_main`, and `sql_has_syntax_error` for diagnostic checks.

### Prepared Statement Handling

SQL execution flows through the `Statement` type defined in `crates/sqlez/src/statement.rs#L40-L84`. This struct parses multi-statement SQL strings, prepares each segment via `sqlite3_prepare_v2`, determines write-ability, and exposes type-safe parameter binding and result extraction methods.

### Thread-Safe Connection Facade

The `ThreadSafeConnection` struct in `crates/sqlez/src/thread_safe_connection.rs#L40-44` is the primary interface used throughout Zed. It maintains:

- An `Arc<str>` URI for connection identity
- A `ThreadLocal<Connection>` providing each UI thread with isolated read-only connections
- A per-URI write queue ensuring only one write operation executes at a time

The `write` method queues closures onto a dedicated background thread, satisfying SQLite's single-writer requirement while keeping the main thread responsive.

### Migration System and Domain Traits

Database schema evolution is handled through the `Domain` trait in `crates/sqlez/src/domain.rs#L3-L30`. Implementors declare `const NAME: &str` and `const MIGRATIONS: &[&str]`, while the `Migrator` trait (automatically implemented for all `Domain` types) executes migration SQL inside transactions.

The `ThreadSafeConnection::initialize_queues` method ensures only one migration runner operates per URI, with `MIGRATION_RETRIES` handling parallel attempts from separate processes.

## Thread Safety and Concurrency Model

Zed's database architecture enforces safety through several coordinated mechanisms:

- **Concurrent reads**: Each UI thread maintains its own `ThreadLocal<Connection>`, allowing parallel read operations without locks
- **Serialized writes**: The `ThreadSafeConnection` queues all mutations to a background thread per database file, preventing write conflicts
- **Corruption recovery**: The `open_db` fallback mechanism ensures the application remains functional even when disk files are damaged
- **Error propagation**: All operations return `anyhow::Result`, with `.log_err()` and `write_and_log` providing consistent error visibility

## Practical Usage Examples

### Reading Persistent State

Accessing stored data uses the typed methods generated by the `query!` macro:

```rust
let maybe_value = KEY_VALUE_STORE
    .read_kvp("recent-workspace")?;

if let Some(path) = maybe_value {
    println!("Last opened: {}", path);
}

```

This executes a prepared `SELECT` statement on the thread-local connection, binding the key parameter and returning `Result<Option<String>>`.

### Implementing a Custom Domain

New persistent features define their schema through the `Domain` trait:

```rust
use sqlez::domain::Domain;
use sqlez_macros::sql;

struct RecentFilesDomain;

impl Domain for RecentFilesDomain {
    const NAME: &str = "RecentFiles";
    const MIGRATIONS: &[&str] = &[sql!(
        CREATE TABLE IF NOT EXISTS recent_files(
            id INTEGER PRIMARY KEY,
            path TEXT NOT NULL UNIQUE,
            opened_at INTEGER NOT NULL
        ) STRICT;
    )];
}

```

When `open_db::<RecentFilesDomain>` is called, migrations apply automatically before any queries execute.

### Direct Low-Level Access

For one-off operations bypassing the high-level API, use `ThreadSafeConnection` directly:

```rust
use sqlez::thread_safe_connection::ThreadSafeConnection;

let conn = ThreadSafeConnection::builder::<()>("app-state.db", false)
    .with_db_initialization_query("PRAGMA journal_mode=WAL;")
    .build()
    .await?;

let count = conn.select_rows::<i64>("SELECT COUNT(*) FROM sqlite_master")?;
println!("Database contains {} tables", count[0]);

```

## Summary

- **Two-crate architecture**: The `db` crate defines domain logic while `sqlez` handles unsafe SQLite interactions and concurrency
- **Thread-safe access**: `ThreadSafeConnection` provides thread-local reads and serialized background writes, respecting SQLite's single-writer constraint
- **Type-safe queries**: The `query!` macro generates Rust methods from SQL, handling parameter binding and result mapping automatically
- **Robust initialization**: `open_db` in [`crates/db/src/db.rs`](https://github.com/zed-industries/zed/blob/main/crates/db/src/db.rs) handles corruption recovery by falling back to in-memory storage when files are damaged
- **Automatic migrations**: The `Domain` trait system in [`crates/sqlez/src/domain.rs`](https://github.com/zed-industries/zed/blob/main/crates/sqlez/src/domain.rs) applies schema changes transactionally on first connection

## Frequently Asked Questions

### How does Zed handle concurrent database access from multiple threads?

According to the Zed source code, concurrent access is managed through `ThreadSafeConnection` in [`crates/sqlez/src/thread_safe_connection.rs`](https://github.com/zed-industries/zed/blob/main/crates/sqlez/src/thread_safe_connection.rs). Each UI thread maintains its own `ThreadLocal<Connection>` for instantaneous read operations, while all write operations are queued to a dedicated background thread per database URI. This design ensures SQLite's single-writer requirement is satisfied without blocking the main UI thread.

### What happens when the SQLite database file becomes corrupted?

The `open_db` function in `crates/db/src/db.rs#L46` detects corruption during initialization. When opening a file fails, the system moves the damaged file to a backup directory, creates a fresh in-memory database, and sets the `ALL_FILE_DB_FAILED` flag. This allows Zed to remain functional while preserving the corrupt data for potential recovery.

### How are database schema migrations managed in Zed?

Migrations are defined through the `Domain` trait in [`crates/sqlez/src/domain.rs`](https://github.com/zed-industries/zed/blob/main/crates/sqlez/src/domain.rs). Each domain declares a `NAME` and ordered list of `MIGRATIONS` (SQL strings). When `ThreadSafeConnection` initializes, it runs these migrations inside a transaction, with `MIGRATION_RETRIES` handling potential conflicts from multiple processes. The system automatically determines which migrations have already been applied based on the current schema version.

### Can Zed extensions or plugins use this database layer for custom persistence?

While the `db` and `sqlez` crates are primarily designed for internal Zed use, the architecture supports additional domains through the `Domain` trait system. Extensions would need to define their own domain implementation with migrations and use the `static_connection!` macro to create a global connection handle, following the pattern established in [`crates/db/src/kvp.rs`](https://github.com/zed-industries/zed/blob/main/crates/db/src/kvp.rs) and [`crates/workspace/src/persistence.rs`](https://github.com/zed-industries/zed/blob/main/crates/workspace/src/persistence.rs).