Skip to content

Generated Methods

Every @DbEntity class gets one generated repository. This page documents every method on the generated <Entity>Repository class.

Repository signature

class ProductRepository(private val driver: SqlDriver)

The generated class is concrete (not an interface). Inject the SqlDriver directly — see Initialization.


createTable()

fun createTable()

Creates the table if it does not already exist. Call once per repository during app startup, before any other method.

  • Uses CREATE TABLE IF NOT EXISTS — safe to call more than once.
  • Delegates to SchemaMigrator.sync() — adds, renames, removes, or recreates columns when the entity changes. See Auto-migration.

insert(entity: T)

suspend fun insert(entity: T)

Inserts a single row. Binds all non-@Ignore properties as ? parameters.

Auto-generated primary key: pass id = 0 (or whatever the Kotlin default is). SQLite assigns the real id.

productRepo.insert(Product(name = "Widget", price = 9.99))

Note

insert does not return the generated id. Query by a unique field if you need the assigned id immediately.


update(entity: T)

suspend fun update(entity: T)

Updates the row whose primary key matches entity.<pkProperty>. All non-PK, non-@Ignore columns are set to the entity's current values.

val p = productRepo.findById("abc") ?: return
productRepo.update(p.copy(price = 12.99))

If no row with that primary key exists, the statement is a no-op (zero rows affected, no error).


delete(id: ID)

suspend fun delete(id: ID)

Deletes the row with the given primary key. ID is the Kotlin type of the @PrimaryKey property.

productRepo.delete("abc")         // String PK
taskRepo.delete(42L)              // Long PK

If no row exists with that id, the statement is a no-op.


deleteWhere { predicate }

suspend fun deleteWhere(predicate: ProductColumns.() -> Predicate): Int

Deletes all rows matching the DSL predicate. Returns the number of rows deleted.

val deleted = taskRepo.deleteWhere {
    (TaskColumns.projectId eq projectId) and
    (TaskColumns.isCompleted eq true)
}

See DSL Operators for the full predicate DSL.


findById(id: ID): T?

suspend fun findById(id: ID): T?

Returns the matching row, or null if not found. Never throws for a missing row.

val product: Product? = productRepo.findById("abc")

findAll(): List<T>

suspend fun findAll(): List<T>

Returns all rows in insertion order. Returns an empty list (not null) when the table is empty.

val products: List<Product> = productRepo.findAll()

findWhere { predicate }: List<T>

suspend fun findWhere(predicate: ProductColumns.() -> Predicate): List<T>

Returns all rows matching the DSL predicate. Returns an empty list when no rows match.

val inStock = productRepo.findWhere { ProductColumns.inStock eq true }

observeAll(): Flow<List<T>>

fun observeAll(): Flow<List<T>>

Returns a cold Flow that emits the full table on collection and re-emits on every subsequent write (insert, update, delete, deleteWhere) to the same table.

Uses SQLDelight Query.Listener internally — no polling.

productRepo.observeAll()
    .collect { products -> adapter.submitList(products) }

observeWhere { predicate }: Flow<List<T>>

fun observeWhere(predicate: ProductColumns.() -> Predicate): Flow<List<T>>

Like observeAll(), but filters by the DSL predicate on every emission.

val activeTasks: Flow<List<Task>> = taskRepo.observeWhere {
    TaskColumns.status inList listOf("TODO", "IN_PROGRESS")
}

Note

The predicate is re-evaluated on every emission — not just the first. Changes to the underlying data that affect the predicate result in an updated list.


count(): Long

suspend fun count(): Long

Returns the total number of rows in the table.


count { predicate }: Long

suspend fun count(predicate: ProductColumns.() -> Predicate): Long

Returns the number of rows matching the predicate.

val openCount: Long = taskRepo.count {
    (TaskColumns.projectId eq projectId) and
    (TaskColumns.isCompleted eq false)
}

insertAll(entities: List<T>)

suspend fun insertAll(entities: List<T>)

Batch-inserts a list of entities inside a single transaction. Reactive observers receive one Flow emission after all rows are committed rather than one per insert call.

taskRepo.insertAll(importedTasks)

Internally calls driver.withTransaction { entities.forEach { insert(it) } }. If any insert fails, the entire batch is rolled back.


findBy<Parent>(parentId: PK): List<T>

Generated for each @Relation-annotated foreign-key property. Returns all child rows whose FK column equals parentId.

// Task has: @Relation val projectId: Long
val tasks: List<Task> = taskRepo.findByProject(projectId)

observeBy<Parent>(parentId: PK): Flow<List<T>>

Reactive variant of findBy<Parent>. Re-emits whenever the child table changes.

val liveTasksFlow: Flow<List<Task>> = taskRepo.observeByProject(projectId)

deleteBy<Parent>(parentId: PK)

Deletes all child rows whose FK column equals parentId. Used for cascade deletes.

// Remove all tasks before deleting the parent project
taskRepo.deleteByProject(projectId)
projectRepo.delete(projectId)

For cleaner cascade semantics, wrap both calls in driver.withTransaction { … } so both tables are notified in a single Flow emission.


Transactions

driver.withTransaction { }

suspend fun SqlDriver.withTransaction(
    context: CoroutineContext = Dispatchers.Default,
    block: suspend () -> Unit
)

Executes block inside a BEGIN TRANSACTION … COMMIT. On any exception the transaction is rolled back and the exception re-thrown.

All Kiln repository write methods (insert, update, delete, deleteWhere, insertAll) defer their Flow listener notifications when called inside withTransaction. After a successful commit, each affected table is notified exactly once — reactive Flows receive one emission for the whole transaction rather than one per operation.

No notifications are sent when a transaction is rolled back.

// Cascade delete — observers see one emission each, not four
driver.withTransaction {
    taskRepo.deleteByProject(projectId)   // deferred
    projectRepo.delete(projectId)         // deferred
}
// notifyListeners fires here, once per table

// Batch import — observers see all rows appear atomically
driver.withTransaction {
    taskRepo.insertAll(newTasks)
}

driver.notifyOrDefer(tableName)

suspend fun SqlDriver.notifyOrDefer(tableName: String)

Called automatically by generated repository code. Outside a transaction it calls SqlDriver.notifyListeners immediately. Inside a withTransaction block it adds the table name to the transaction's dirty set, deferring the notification to after commit. Not intended for direct use.


Companion: <Entity>Columns

Each entity also generates a companion <Entity>Columns object used inside DSL lambdas:

object ProductColumns {
    val id: Column<String>
    val name: Column<String>
    val price: Column<Double>
    val inStock: Column<Boolean>
}

Column names reflect the actual SQL column names (accounting for @Column(name = …) overrides). Use these inside findWhere, observeWhere, deleteWhere, and count lambdas.