Skip to content

SQLDelight

Databases & CachingDatabaseKotlin

What it is

SQLDelight generates typesafe Kotlin APIs from .sq files containing plain SQL, working across Android, JVM, iOS and JS.

Write schema and queries in .sq files. The Gradle plugin generates a typed API, and queries can be observed as Flow.

Installation

plugins { id("app.cash.sqldelight") version "2.0.2" }

Getting started

The smallest useful thing you can do with it, and what each part means.

SQL as the source of truth
-- src/commonMain/sqldelight/com/example/Book.sq
CREATE TABLE book (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    year INTEGER NOT NULL
);

CREATE INDEX book_year ON book(year);

selectSince:
SELECT * FROM book WHERE year >= ? ORDER BY year DESC;

insertBook:
INSERT INTO book(title, year) VALUES (?, ?);

countByYear:
SELECT year, COUNT(*) AS total FROM book GROUP BY year;
Each named query becomes a Kotlin function with typed parameters and a generated result class — including for aggregates like countByYear, which gets its own data class.
Using the generated API
val driver = AndroidSqliteDriver(Database.Schema, context, "app.db")
val database = Database(driver)

// Typed, and observable as a Flow.
database.bookQueries.selectSince(1990)
    .asFlow()
    .mapToList(Dispatchers.IO)
    .collect { books -> render(books) }

database.bookQueries.insertBook(title = "Dune", year = 1965)

database.transaction {
    database.bookQueries.insertBook("A", 2000)
    database.bookQueries.insertBook("B", 2001)
}
The driver is the only platform-specific piece — swap AndroidSqliteDriver for NativeSqliteDriver and the identical query code runs on iOS.

Advanced usage

Where the library earns its place over a simpler alternative.

Migrations and custom column types
-- 1.sqm — migrations are numbered files
ALTER TABLE book ADD COLUMN isbn TEXT;

-- Map a column to a Kotlin type at the schema level.
CREATE TABLE event (
    id INTEGER PRIMARY KEY,
    occurred_at INTEGER AS kotlinx.datetime.Instant NOT NULL,
    payload TEXT AS kotlin.collections.List<String> NOT NULL
);
Column adapters convert at the boundary, so the generated API exposes Instant and List rather than Long and String — the mapping lives in one place instead of at every call site.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Generated code is missing after adding a query
The Gradle plugin runs on build. Sync or run the generateSqlDelightInterface task.
Schema version mismatch at runtime
A .sqm migration is missing for the new version. SQLDelight verifies migrations against the schema if you enable verifyMigrations.

Best practices

  • Choose SQLDelight for Multiplatform and Room for Android-only projects.
  • Keep migrations as numbered .sqm files and verify them with the schema task.
  • Use asFlow().mapToList() for observable queries.
  • Wrap multi-statement writes in transaction { } for atomicity and speed.

Background

Why it exists, and what it was reacting to.

From Square, SQLDelight inverts Room's model: you write SQL in its own files and the tooling generates Kotlin from it. Because it does not depend on Android APIs, the same data layer runs on iOS in a Multiplatform project.