or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

column-adapters.mddatabase-driver.mdindex.mdlogging-debugging.mdquery-execution.mdschema-management.mdtransaction-management.md

database-driver.mddocs/

0

# Database Driver Interface

1

2

Low-level database driver abstraction providing platform-agnostic SQL execution, connection management, and prepared statement handling with support for both synchronous and asynchronous operations.

3

4

## Capabilities

5

6

### SqlDriver Interface

7

8

Core interface for database connections providing SQL execution and transaction management.

9

10

```kotlin { .api }

11

/**

12

* Maintains connections to an underlying SQL database and provides APIs for managing

13

* transactions and executing SQL statements

14

*/

15

interface SqlDriver : Closeable {

16

/**

17

* Execute a SQL statement and evaluate its result set using the given block

18

* @param identifier Opaque, unique value for driver-side caching of prepared statements. If null, a fresh statement is required

19

* @param sql The SQL string to be executed

20

* @param mapper Lambda called with the cursor when the statement is executed successfully. The cursor must not escape the block scope

21

* @param parameters The number of bindable parameters sql contains

22

* @param binders Lambda called before execution to bind any parameters to the SQL statement

23

* @return Generic result of the mapper lambda

24

*/

25

fun <R> executeQuery(

26

identifier: Int?,

27

sql: String,

28

mapper: (SqlCursor) -> QueryResult<R>,

29

parameters: Int,

30

binders: (SqlPreparedStatement.() -> Unit)? = null

31

): QueryResult<R>

32

33

/**

34

* Execute a SQL statement

35

* @param identifier Opaque, unique value for driver-side caching of prepared statements. If null, a fresh statement is required

36

* @param sql The SQL string to be executed

37

* @param parameters The number of bindable parameters sql contains

38

* @param binders Lambda called before execution to bind any parameters to the SQL statement

39

* @return The number of rows updated for an INSERT/DELETE/UPDATE, or 0 for other SQL statements

40

*/

41

fun execute(

42

identifier: Int?,

43

sql: String,

44

parameters: Int,

45

binders: (SqlPreparedStatement.() -> Unit)? = null

46

): QueryResult<Long>

47

48

/**

49

* Start a new Transaction on the database

50

* @return QueryResult containing the new Transaction instance

51

*/

52

fun newTransaction(): QueryResult<Transacter.Transaction>

53

54

/**

55

* The currently open Transaction on the database

56

* @return Current transaction or null if no transaction is active

57

*/

58

fun currentTransaction(): Transacter.Transaction?

59

60

/**

61

* Register a listener for changes to specific query keys

62

* @param queryKeys Table/view names to listen for changes

63

* @param listener Listener to notify when changes occur

64

*/

65

fun addListener(vararg queryKeys: String, listener: Query.Listener)

66

67

/**

68

* Remove a listener for specific query keys

69

* @param queryKeys Table/view names to stop listening for changes

70

* @param listener Listener to remove

71

*/

72

fun removeListener(vararg queryKeys: String, listener: Query.Listener)

73

74

/**

75

* Notify all listeners registered for the specified query keys

76

* @param queryKeys Table/view names that have changed

77

*/

78

fun notifyListeners(vararg queryKeys: String)

79

}

80

```

81

82

### SqlCursor Interface

83

84

Interface representing a SQL result set that can be iterated and provides typed column access.

85

86

```kotlin { .api }

87

/**

88

* Represents a SQL result set which can be iterated through with next().

89

* Initially the cursor will not point to any row, and calling next() once will iterate to the first row

90

*/

91

interface SqlCursor {

92

/**

93

* Move to the next row in the result set

94

* @return QueryResult containing true if the cursor successfully moved to a new row, false if there was no row to iterate to

95

*/

96

fun next(): QueryResult<Boolean>

97

98

/**

99

* Get the string or null value of column index for the current row of the result set

100

* @param index Zero-based column index

101

* @return String value or null

102

*/

103

fun getString(index: Int): String?

104

105

/**

106

* Get the long or null value of column index for the current row of the result set

107

* @param index Zero-based column index

108

* @return Long value or null

109

*/

110

fun getLong(index: Int): Long?

111

112

/**

113

* Get the bytes or null value of column index for the current row of the result set

114

* @param index Zero-based column index

115

* @return ByteArray value or null

116

*/

117

fun getBytes(index: Int): ByteArray?

118

119

/**

120

* Get the double or null value of column index for the current row of the result set

121

* @param index Zero-based column index

122

* @return Double value or null

123

*/

124

fun getDouble(index: Int): Double?

125

126

/**

127

* Get the boolean or null value of column index for the current row of the result set

128

* @param index Zero-based column index

129

* @return Boolean value or null

130

*/

131

fun getBoolean(index: Int): Boolean?

132

}

133

```

134

135

### SqlPreparedStatement Interface

136

137

Interface for binding parameters to prepared SQL statements with type-safe binding methods.

138

139

```kotlin { .api }

140

/**

141

* Represents a SQL statement that has been prepared by a driver to be executed.

142

* This type is not thread safe unless otherwise specified by the driver.

143

* Prepared statements should not be cached by client code. Drivers can implement

144

* caching by using the integer identifier passed to SqlDriver.execute or SqlDriver.executeQuery

145

*/

146

interface SqlPreparedStatement {

147

/**

148

* Bind bytes to the underlying statement at index

149

* @param index One-based parameter index

150

* @param bytes ByteArray value or null to bind

151

*/

152

fun bindBytes(index: Int, bytes: ByteArray?)

153

154

/**

155

* Bind long to the underlying statement at index

156

* @param index One-based parameter index

157

* @param long Long value or null to bind

158

*/

159

fun bindLong(index: Int, long: Long?)

160

161

/**

162

* Bind double to the underlying statement at index

163

* @param index One-based parameter index

164

* @param double Double value or null to bind

165

*/

166

fun bindDouble(index: Int, double: Double?)

167

168

/**

169

* Bind string to the underlying statement at index

170

* @param index One-based parameter index

171

* @param string String value or null to bind

172

*/

173

fun bindString(index: Int, string: String?)

174

175

/**

176

* Bind boolean to the underlying statement at index

177

* @param index One-based parameter index

178

* @param boolean Boolean value or null to bind

179

*/

180

fun bindBoolean(index: Int, boolean: Boolean?)

181

}

182

```

183

184

### QueryResult System

185

186

Unified result handling supporting both immediate values and suspending operations.

187

188

```kotlin { .api }

189

/**

190

* The returned value is the result of a database query or other database operation.

191

* This interface enables drivers to be based on non-blocking APIs where the result

192

* can be obtained using the suspending await method

193

*/

194

sealed interface QueryResult<T> {

195

/**

196

* Immediate access to the result value.

197

* Throws IllegalStateException if the driver is asynchronous and generateAsync is not configured

198

*/

199

val value: T

200

201

/**

202

* Suspending access to the result value, works with both sync and async drivers

203

* @return The result value

204

*/

205

suspend fun await(): T

206

207

/**

208

* Immediate result wrapper for synchronous operations

209

*/

210

@JvmInline

211

value class Value<T>(override val value: T) : QueryResult<T> {

212

override suspend fun await() = value

213

}

214

215

/**

216

* Asynchronous result wrapper for suspending operations

217

*/

218

@JvmInline

219

value class AsyncValue<T>(private val getter: suspend () -> T) : QueryResult<T> {

220

override suspend fun await() = getter()

221

}

222

223

companion object {

224

/**

225

* A QueryResult representation of a Kotlin Unit for convenience.

226

* Equivalent to QueryResult.Value(Unit)

227

*/

228

val Unit = Value(kotlin.Unit)

229

}

230

}

231

```

232

233

**Usage Examples:**

234

235

```kotlin

236

import app.cash.sqldelight.db.*

237

238

// Implementing a custom SqlDriver

239

class MyCustomDriver : SqlDriver {

240

private val connection: DatabaseConnection = createConnection()

241

private val listeners = mutableMapOf<String, MutableList<Query.Listener>>()

242

private var currentTx: Transacter.Transaction? = null

243

244

override fun <R> executeQuery(

245

identifier: Int?,

246

sql: String,

247

mapper: (SqlCursor) -> QueryResult<R>,

248

parameters: Int,

249

binders: (SqlPreparedStatement.() -> Unit)?

250

): QueryResult<R> {

251

val preparedStatement = connection.prepareStatement(sql)

252

binders?.invoke(preparedStatement)

253

254

val cursor = preparedStatement.executeQuery()

255

return mapper(cursor)

256

}

257

258

override fun execute(

259

identifier: Int?,

260

sql: String,

261

parameters: Int,

262

binders: (SqlPreparedStatement.() -> Unit)?

263

): QueryResult<Long> {

264

val preparedStatement = connection.prepareStatement(sql)

265

binders?.invoke(preparedStatement)

266

267

val rowsAffected = preparedStatement.executeUpdate()

268

return QueryResult.Value(rowsAffected.toLong())

269

}

270

271

override fun newTransaction(): QueryResult<Transacter.Transaction> {

272

val transaction = MyTransaction(connection, currentTx)

273

currentTx = transaction

274

return QueryResult.Value(transaction)

275

}

276

277

override fun currentTransaction(): Transacter.Transaction? = currentTx

278

279

override fun addListener(vararg queryKeys: String, listener: Query.Listener) {

280

queryKeys.forEach { key ->

281

listeners.getOrPut(key) { mutableListOf() }.add(listener)

282

}

283

}

284

285

override fun removeListener(vararg queryKeys: String, listener: Query.Listener) {

286

queryKeys.forEach { key ->

287

listeners[key]?.remove(listener)

288

}

289

}

290

291

override fun notifyListeners(vararg queryKeys: String) {

292

queryKeys.forEach { key ->

293

listeners[key]?.forEach { listener ->

294

listener.queryResultsChanged()

295

}

296

}

297

}

298

299

override fun close() {

300

connection.close()

301

}

302

}

303

304

// Using SqlDriver for raw database operations

305

class DatabaseManager(private val driver: SqlDriver) {

306

307

fun createUser(name: String, email: String): Long {

308

return driver.execute(

309

identifier = 1,

310

sql = "INSERT INTO users (name, email) VALUES (?, ?)",

311

parameters = 2

312

) { statement ->

313

statement.bindString(1, name)

314

statement.bindString(2, email)

315

}.value

316

}

317

318

fun findUsersByStatus(active: Boolean): List<User> {

319

return driver.executeQuery(

320

identifier = 2,

321

sql = "SELECT id, name, email, active FROM users WHERE active = ?",

322

mapper = { cursor ->

323

val users = mutableListOf<User>()

324

while (cursor.next().value) {

325

users.add(

326

User(

327

id = cursor.getLong(0)!!,

328

name = cursor.getString(1)!!,

329

email = cursor.getString(2)!!,

330

active = cursor.getBoolean(3)!!

331

)

332

)

333

}

334

QueryResult.Value(users)

335

},

336

parameters = 1

337

) { statement ->

338

statement.bindBoolean(1, active)

339

}.value

340

}

341

342

fun getUserCount(): Int {

343

return driver.executeQuery(

344

identifier = 3,

345

sql = "SELECT COUNT(*) FROM users",

346

mapper = { cursor ->

347

cursor.next()

348

val count = cursor.getLong(0)?.toInt() ?: 0

349

QueryResult.Value(count)

350

},

351

parameters = 0

352

).value

353

}

354

355

// Working with transactions at the driver level

356

fun transferUserData(fromUserId: Long, toUserId: Long) {

357

val transaction = driver.newTransaction().value

358

try {

359

// Perform operations within transaction

360

val userData = getUserData(fromUserId)

361

deleteUser(fromUserId)

362

createUserWithData(toUserId, userData)

363

364

// Commit is handled by transaction lifecycle

365

} catch (e: Exception) {

366

// Rollback is handled automatically

367

throw e

368

}

369

}

370

}

371

372

// Async driver example

373

class AsyncDatabaseManager(private val driver: SqlDriver) {

374

375

suspend fun createUserAsync(name: String, email: String): Long {

376

return driver.execute(

377

identifier = 1,

378

sql = "INSERT INTO users (name, email) VALUES (?, ?)",

379

parameters = 2

380

) { statement ->

381

statement.bindString(1, name)

382

statement.bindString(2, email)

383

}.await()

384

}

385

386

suspend fun findUsersAsync(active: Boolean): List<User> {

387

return driver.executeQuery(

388

identifier = 2,

389

sql = "SELECT id, name, email, active FROM users WHERE active = ?",

390

mapper = { cursor ->

391

QueryResult.AsyncValue {

392

val users = mutableListOf<User>()

393

while (cursor.next().await()) {

394

users.add(

395

User(

396

id = cursor.getLong(0)!!,

397

name = cursor.getString(1)!!,

398

email = cursor.getString(2)!!,

399

active = cursor.getBoolean(3)!!

400

)

401

)

402

}

403

users

404

}

405

},

406

parameters = 1

407

) { statement ->

408

statement.bindBoolean(1, active)

409

}.await()

410

}

411

}

412

```

413

414

### Platform Considerations

415

416

- **Prepared Statement Caching**: Drivers may implement caching using the identifier parameter

417

- **Thread Safety**: SqlPreparedStatement is not thread-safe unless specified by the driver

418

- **Cursor Lifecycle**: SqlCursor instances must not escape the mapper lambda scope

419

- **Connection Management**: SqlDriver implementations handle connection pooling and lifecycle

420

- **Error Handling**: Drivers should propagate SQL exceptions appropriately for the platform