SQLDelight generates a common database API but needs a platform `SqlDriver`. Name the drivers per platform and explain how the generated `Database` is constructed from one.
answer
- SQLDelight generates Database + Schema from .sq
- You supply SqlDriver per platform
- Android = AndroidSqliteDriver, iOS = NativeSqliteDriver
- JVM = JdbcSqliteDriver (in-memory for tests)
- Database(driver); pass Schema to driver
basics
~20 sSQLDelight turns your .sq files into a common typed database API, but you must hand it a platform-specific SqlDriver to actually talk to SQLite: Android, native (iOS), and a web/JS driver. You build the driver per platform and pass it into the generated Database.
solid answer
~40 sSQLDelight generates a typesafe `Database`/queries API in common code from your SQL, plus a `Schema`. The runtime piece it cannot generate portably is the `SqlDriver` — the bridge to a real SQLite engine — so you supply one per target: **`AndroidSqliteDriver`** (`sqldelight-android-driver`, wraps the Android SQLite framework / SupportSQLite), **`NativeSqliteDriver`** (`sqldelight-native-driver`, for iOS/macOS, backed by SQLite on Kotlin/Native), and the **web worker driver** (`sqldelight-web-worker-driver`) for JS/WASM. A typical pattern is `expect class DriverFactory { fun create(): SqlDriver }` with platform actuals; the generated `Database(driver)` (or `Database.invoke(driver)`) wires queries to it. You also pass the generated `Schema` so the driver can create/migrate tables. Common code only sees the generated queries; the SQLite backend is platform-specific.
code
kotlin · 19 lines// commonMain
expect class DriverFactory { fun create(): SqlDriver }
fun database(f: DriverFactory) = Database(f.create())
// androidMain
actual class DriverFactory(private val ctx: Context) {
actual fun create(): SqlDriver =
AndroidSqliteDriver(Database.Schema, ctx, "app.db")
}
// iosMain
actual class DriverFactory {
actual fun create(): SqlDriver =
NativeSqliteDriver(Database.Schema, "app.db")
}
// jvmTest
val testDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
.also { Database.Schema.create(it) }go deeper
Knows SQLDelight needs a driver and that drivers differ by platform.
Names the per-platform drivers and constructs Database(driver) with a DriverFactory.
Explains Schema/migrations, in-memory JDBC driver for tests, and the expect/actual DriverFactory pattern.
Reasons about Native concurrency/connection handling, reactive Flow extensions, and how to keep the driver boundary injectable and testable in a shared module.
## What SQLDelight generates vs what you supply SQLDelight reads your **`.sq` files** (SQL with typed queries) and **generates** at build time: - a typesafe `Database` interface plus query objects (each labeled SQL statement becomes a Kotlin function returning typed results), - a `Schema` object (`Database.Schema`) describing table creation and migrations. What it does **not** generate is the actual SQLite runtime — that is the **`SqlDriver`**, the abstraction over a concrete SQLite engine. You provide a platform-specific driver; this is the expect/actual seam for the database. ## Drivers per platform - **Android / JVM (Android)** — `AndroidSqliteDriver` from `app.cash.sqldelight:android-driver`. Wraps the Android SQLite framework (`SupportSQLiteOpenHelper`). You pass the generated schema, an Android `Context`, and a db name. - **iOS / macOS (Kotlin/Native)** — `NativeSqliteDriver` from `app.cash.sqldelight:native-driver`. Uses SQLite via a native binding on Kotlin/Native. - **JVM desktop / server** — `JdbcSqliteDriver` from `app.cash.sqldelight:sqlite-driver` (JDBC over SQLite); often used for tests too: `JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)`. - **JS / Web (and WASM)** — `app.cash.sqldelight:web-worker-driver` running sql.js in a worker; you create a driver bound to a worker script. ## Constructing the generated `Database` The generated type is constructed from a driver: ```kotlin val driver: SqlDriver = /* platform-specific */ val database = Database(driver) // generated factory val users = database.userQueries.selectAll().executeAsList() ``` The **schema** is handed to the driver at creation so it can create tables / run migrations: ```kotlin // androidMain actual class DriverFactory(private val context: Context) { actual fun create(): SqlDriver = AndroidSqliteDriver(Database.Schema, context, "app.db") } ``` ```kotlin // iosMain actual class DriverFactory { actual fun create(): SqlDriver = NativeSqliteDriver(Database.Schema, "app.db") } ``` ```kotlin // commonMain expect class DriverFactory { fun create(): SqlDriver } fun makeDatabase(factory: DriverFactory): Database = Database(factory.create()) ``` ## Important details - **Schema migrations:** the `Schema` exposes `version` and `migrate(...)`; drivers run `create`/`migrate` when opening. You add `.sqm` migration files; SQLDelight verifies them at compile time. - **Threading / Native:** historically the Native driver dealt with Kotlin/Native memory and concurrency constraints; modern SQLDelight + the new memory model make this transparent, but senior candidates should know the Native driver manages its own connection pool. - **Coroutines extension:** `app.cash.sqldelight:coroutines-extensions` adds `asFlow()` so a query can be observed as a `Flow`, portable across platforms. - **Generated API is fully common:** all your query calls live in `commonMain`; only the `DriverFactory` actuals are platform-specific — the textbook one-common-API-per-target shape. ## Why a driver abstraction at all SQLite exists on every target but is reached through different host APIs (Android framework helper, native binding, JDBC, sql.js in a worker). `SqlDriver` normalizes prepare/execute/cursor semantics so the generated code is identical everywhere; the driver is the platform-specific actual.
- Why pass the generated `Schema` to the driver?The driver uses it to create the tables and run versioned migrations when the database is opened, keeping schema setup consistent across platforms.
- How would you observe a query reactively across platforms?Use the coroutines-extensions `asFlow()` (plus `mapToList`/`mapToOne`) to turn a SQLDelight query into a common `Flow` that emits on data changes.
saying these in an interview costs you the question
- Thinking SQLDelight talks to SQLite without any platform driver
- Using AndroidSqliteDriver on iOS or vice versa
- Not knowing the Schema is needed for table creation/migration
- Believing the generated query API differs per platform
- Claiming migrations are written in Kotlin rather than .sqm files verified at compile time