SQLDelight multiplatform runtime library providing typesafe Kotlin APIs from SQL statements with compile-time schema verification
npx @tessl/cli install tessl/maven-app-cash-sqldelight--runtime@2.1.0SQLDelight Runtime is the multiplatform runtime library for SQLDelight, providing typesafe Kotlin APIs from SQL statements. It serves as the foundational layer that enables compile-time schema verification, reactive query results, and thread-safe database operations across JVM, Android, Native, and JavaScript platforms.
implementation "app.cash.sqldelight:runtime:2.1.0"import app.cash.sqldelight.Query
import app.cash.sqldelight.Transacter
import app.cash.sqldelight.SuspendingTransacter
import app.cash.sqldelight.ColumnAdapter
import app.cash.sqldelight.EnumColumnAdapter
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlCursor
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlSchema
import app.cash.sqldelight.db.AfterVersion
import app.cash.sqldelight.logs.LogSqliteDriver
import app.cash.sqldelight.logs.StatementParameterInterceptorimport app.cash.sqldelight.Query
import app.cash.sqldelight.Transacter
import app.cash.sqldelight.db.SqlDriver
// Execute a query and get results
val users: List<User> = userQueries.selectAll().executeAsList()
// Execute a single result query
val user: User? = userQueries.selectById(123).executeAsOneOrNull()
// Listen to query result changes
userQueries.selectAll().addListener(object : Query.Listener {
override fun queryResultsChanged() {
// Handle query result updates
updateUI()
}
})
// Execute operations in a transaction
database.transaction {
userQueries.insertUser("Alice", 25)
userQueries.insertUser("Bob", 30)
// Both inserts succeed or both are rolled back
}SQLDelight Runtime is built around several key components:
Core query functionality for executing SQL statements and managing result sets. Provides type-safe query execution with reactive updates and lifecycle management.
abstract class Query<out RowType : Any>(
mapper: (SqlCursor) -> RowType
) : ExecutableQuery<RowType>(mapper) {
abstract fun addListener(listener: Listener)
abstract fun removeListener(listener: Listener)
fun interface Listener {
fun queryResultsChanged()
}
}
abstract class ExecutableQuery<out RowType : Any>(
val mapper: (SqlCursor) -> RowType
) {
abstract fun <R> execute(mapper: (SqlCursor) -> QueryResult<R>): QueryResult<R>
fun executeAsList(): List<RowType>
fun executeAsOne(): RowType
fun executeAsOneOrNull(): RowType?
}
// Factory functions for creating queries
fun <RowType : Any> Query(
identifier: Int,
queryKeys: Array<out String>,
driver: SqlDriver,
query: String,
mapper: (SqlCursor) -> RowType
): Query<RowType>Comprehensive transaction support with ACID guarantees, nested transactions, and automatic resource management. Supports both synchronous and coroutine-based asynchronous operations.
interface Transacter : TransacterBase {
fun <R> transactionWithResult(
noEnclosing: Boolean = false,
bodyWithReturn: TransactionWithReturn<R>.() -> R
): R
fun transaction(
noEnclosing: Boolean = false,
body: TransactionWithoutReturn.() -> Unit
)
}
interface SuspendingTransacter : TransacterBase {
suspend fun <R> transactionWithResult(
noEnclosing: Boolean = false,
bodyWithReturn: suspend SuspendingTransactionWithReturn<R>.() -> R
): R
suspend fun transaction(
noEnclosing: Boolean = false,
body: suspend SuspendingTransactionWithoutReturn.() -> Unit
)
}
abstract class Transacter.Transaction : TransactionCallbacks {
protected abstract val enclosingTransaction: Transaction?
fun afterCommit(function: () -> Unit)
fun afterRollback(function: () -> Unit)
protected abstract fun endTransaction(successful: Boolean): QueryResult<Unit>
}Platform-agnostic database driver abstraction that provides SQL execution, connection management, and query result handling. Supports both synchronous and asynchronous operation modes.
interface SqlDriver : Closeable {
fun <R> executeQuery(
identifier: Int?,
sql: String,
mapper: (SqlCursor) -> QueryResult<R>,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)? = null
): QueryResult<R>
fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)? = null
): QueryResult<Long>
fun newTransaction(): QueryResult<Transacter.Transaction>
fun currentTransaction(): Transacter.Transaction?
fun addListener(vararg queryKeys: String, listener: Query.Listener)
fun removeListener(vararg queryKeys: String, listener: Query.Listener)
fun notifyListeners(vararg queryKeys: String)
}
interface SqlCursor {
fun next(): QueryResult<Boolean>
fun getString(index: Int): String?
fun getLong(index: Int): Long?
fun getBytes(index: Int): ByteArray?
fun getDouble(index: Int): Double?
fun getBoolean(index: Int): Boolean?
}
sealed interface QueryResult<T> {
val value: T
suspend fun await(): T
value class Value<T>(override val value: T) : QueryResult<T>
value class AsyncValue<T>(private val getter: suspend () -> T) : QueryResult<T>
}Type conversion system for mapping between Kotlin types and database column types. Enables custom type serialization and provides built-in adapters for common use cases.
interface ColumnAdapter<T : Any, S> {
fun decode(databaseValue: S): T
fun encode(value: T): S
}
class EnumColumnAdapter<T : Enum<T>>(
private val enumValues: Array<out T>
) : ColumnAdapter<T, String> {
override fun decode(databaseValue: String): T
override fun encode(value: T): String
}
// Factory function for enum adapters
inline fun <reified T : Enum<T>> EnumColumnAdapter(): EnumColumnAdapter<T>Comprehensive logging and debugging tools for monitoring database operations, troubleshooting issues, and performance analysis.
class LogSqliteDriver(
private val sqlDriver: SqlDriver,
private val logger: (String) -> Unit
) : SqlDriver {
// Logs all SQL operations including queries, executions, transactions
override fun <R> executeQuery(
identifier: Int?,
sql: String,
mapper: (SqlCursor) -> QueryResult<R>,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?
): QueryResult<R>
override fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?
): QueryResult<Long>
}
class StatementParameterInterceptor : SqlPreparedStatement {
fun getAndClearParameters(): List<Any?>
// Intercepts and stores parameter values for debugging
}Database schema creation, migration, and version management system. Provides APIs for initializing databases and handling schema changes across application versions.
interface SqlSchema<T : QueryResult<Unit>> {
val version: Long
fun create(driver: SqlDriver): T
fun migrate(
driver: SqlDriver,
oldVersion: Long,
newVersion: Long,
vararg callbacks: AfterVersion
): T
}
class AfterVersion(
val afterVersion: Long,
val block: (SqlDriver) -> Unit
)// Core interfaces
sealed interface TransacterBase {
// Base interface for all transaction-capable components
// Implemented by Transacter and SuspendingTransacter
}
interface TransactionCallbacks {
fun afterCommit(function: () -> Unit)
fun afterRollback(function: () -> Unit)
}
// Transaction context interfaces
interface TransactionWithReturn<R> : TransactionCallbacks {
fun rollback(returnValue: R): Nothing
fun <R> transaction(body: TransactionWithReturn<R>.() -> R): R
}
interface TransactionWithoutReturn : TransactionCallbacks {
fun rollback(): Nothing
fun transaction(body: TransactionWithoutReturn.() -> Unit)
}
interface SuspendingTransactionWithReturn<R> : TransactionCallbacks {
fun rollback(returnValue: R): Nothing
suspend fun <R> transaction(body: suspend SuspendingTransactionWithReturn<R>.() -> R): R
}
interface SuspendingTransactionWithoutReturn : TransactionCallbacks {
fun rollback(): Nothing
suspend fun transactionWithResult(body: suspend SuspendingTransactionWithoutReturn.() -> Unit)
}
// Prepared statement interface
interface SqlPreparedStatement {
fun bindBytes(index: Int, bytes: ByteArray?)
fun bindLong(index: Int, long: Long?)
fun bindDouble(index: Int, double: Double?)
fun bindString(index: Int, string: String?)
fun bindBoolean(index: Int, boolean: Boolean?)
}
// Platform abstraction
expect interface Closeable {
fun close()
}
expect inline fun <T : Closeable?, R> T.use(body: (T) -> R): R
// Exception types
class OptimisticLockException(
message: String?,
cause: Throwable? = null
) : IllegalStateException(message, cause)
// Internal APIs
internal expect fun currentThreadId(): Long