or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

column-adapters.mddatabase-driver.mdindex.mdlogging-utilities.mdquery-system.mdschema-management.mdtransaction-management.md

column-adapters.mddocs/

0

# Column Type Adapters

1

2

SQLDelight's column adapter system provides bidirectional type conversion between Kotlin types and database column types. It enables custom serialization/deserialization and provides built-in adapters for common use cases like enums and complex types.

3

4

## Capabilities

5

6

### Column Adapter Interface

7

8

Core interface for bidirectional type conversion between Kotlin and database types.

9

10

```kotlin { .api }

11

/**

12

* Marshal and map the type T to and from a database type S which is one of

13

* Long, Double, String, ByteArray

14

* @param T The Kotlin type to convert to/from

15

* @param S The database type (Long, Double, String, or ByteArray)

16

*/

17

interface ColumnAdapter<T : Any, S> {

18

/**

19

* Convert database value to Kotlin type

20

* @param databaseValue The value from the database column

21

* @returns databaseValue decoded as type T

22

*/

23

fun decode(databaseValue: S): T

24

25

/**

26

* Convert Kotlin type to database value

27

* @param value The Kotlin value to store

28

* @returns value encoded as database type S

29

*/

30

fun encode(value: T): S

31

}

32

```

33

34

**Usage Examples:**

35

36

```kotlin

37

import app.cash.sqldelight.ColumnAdapter

38

39

// Custom date adapter using Long for timestamp storage

40

class DateAdapter : ColumnAdapter<Date, Long> {

41

override fun decode(databaseValue: Long): Date {

42

return Date(databaseValue)

43

}

44

45

override fun encode(value: Date): Long {

46

return value.time

47

}

48

}

49

50

// JSON adapter for complex objects using String storage

51

class JsonAdapter<T>(

52

private val serializer: KSerializer<T>

53

) : ColumnAdapter<T, String> {

54

override fun decode(databaseValue: String): T {

55

return Json.decodeFromString(serializer, databaseValue)

56

}

57

58

override fun encode(value: T): String {

59

return Json.encodeToString(serializer, value)

60

}

61

}

62

63

// UUID adapter using String storage

64

class UuidAdapter : ColumnAdapter<UUID, String> {

65

override fun decode(databaseValue: String): UUID {

66

return UUID.fromString(databaseValue)

67

}

68

69

override fun encode(value: UUID): String {

70

return value.toString()

71

}

72

}

73

74

// Usage in generated code context

75

val database = Database(

76

driver = driver,

77

userAdapter = User.Adapter(

78

id = UuidAdapter(),

79

createdAt = DateAdapter(),

80

preferences = JsonAdapter(UserPreferences.serializer())

81

)

82

)

83

```

84

85

### Built-in Enum Column Adapter

86

87

Pre-built adapter for mapping enum classes to database strings using enum names.

88

89

```kotlin { .api }

90

/**

91

* A ColumnAdapter which maps the enum class T to a string in the database

92

* @param enumValues Array of all enum values for the type T

93

*/

94

class EnumColumnAdapter<T : Enum<T>>(

95

private val enumValues: Array<out T>

96

) : ColumnAdapter<T, String> {

97

/**

98

* Convert database string to enum value by matching enum name

99

* @param databaseValue The enum name string from database

100

* @returns The enum value with matching name

101

* @throws NoSuchElementException if no enum value matches the database string

102

*/

103

override fun decode(databaseValue: String): T

104

105

/**

106

* Convert enum value to database string using enum name

107

* @param value The enum value to convert

108

* @returns The name property of the enum value

109

*/

110

override fun encode(value: T): String

111

}

112

113

/**

114

* Factory function to create EnumColumnAdapter for reified enum type

115

* A ColumnAdapter which maps the enum class T to a string in the database

116

* @returns EnumColumnAdapter instance for type T

117

*/

118

inline fun <reified T : Enum<T>> EnumColumnAdapter(): EnumColumnAdapter<T>

119

```

120

121

**Usage Examples:**

122

123

```kotlin

124

import app.cash.sqldelight.EnumColumnAdapter

125

126

// Define enum type

127

enum class UserStatus {

128

ACTIVE, INACTIVE, SUSPENDED, DELETED

129

}

130

131

enum class Priority {

132

LOW, MEDIUM, HIGH, CRITICAL

133

}

134

135

// Create adapters using factory function (preferred)

136

val statusAdapter = EnumColumnAdapter<UserStatus>()

137

val priorityAdapter = EnumColumnAdapter<Priority>()

138

139

// Create adapters manually

140

val manualStatusAdapter = EnumColumnAdapter(UserStatus.values())

141

142

// Usage in database schema

143

val database = Database(

144

driver = driver,

145

userAdapter = User.Adapter(

146

status = statusAdapter

147

),

148

taskAdapter = Task.Adapter(

149

priority = priorityAdapter

150

)

151

)

152

153

// The adapter handles conversion automatically

154

val activeUsers = userQueries.selectByStatus(UserStatus.ACTIVE).executeAsList()

155

// Database query: SELECT * FROM users WHERE status = 'ACTIVE'

156

157

userQueries.updateStatus(userId = 1, status = UserStatus.SUSPENDED)

158

// Database query: UPDATE users SET status = 'SUSPENDED' WHERE id = 1

159

160

// Error handling for invalid database values

161

try {

162

val invalidUser = userQueries.selectById(invalidId).executeAsOne()

163

} catch (e: NoSuchElementException) {

164

// Thrown if database contains enum name not in current enum definition

165

println("Invalid enum value in database: ${e.message}")

166

}

167

```

168

169

### Complex Type Adapters

170

171

Examples of adapters for handling complex data types and serialization scenarios.

172

173

**Usage Examples:**

174

175

```kotlin

176

import app.cash.sqldelight.ColumnAdapter

177

import kotlinx.serialization.*

178

import kotlinx.serialization.json.*

179

180

// List adapter using JSON serialization

181

class ListAdapter<T>(

182

private val elementSerializer: KSerializer<T>

183

) : ColumnAdapter<List<T>, String> {

184

override fun decode(databaseValue: String): List<T> {

185

return Json.decodeFromString(ListSerializer(elementSerializer), databaseValue)

186

}

187

188

override fun encode(value: List<T>): String {

189

return Json.encodeToString(ListSerializer(elementSerializer), value)

190

}

191

}

192

193

// Map adapter for key-value storage

194

class MapAdapter<K, V>(

195

private val keySerializer: KSerializer<K>,

196

private val valueSerializer: KSerializer<V>

197

) : ColumnAdapter<Map<K, V>, String> {

198

override fun decode(databaseValue: String): Map<K, V> {

199

return Json.decodeFromString(

200

MapSerializer(keySerializer, valueSerializer),

201

databaseValue

202

)

203

}

204

205

override fun encode(value: Map<K, V>): String {

206

return Json.encodeToString(

207

MapSerializer(keySerializer, valueSerializer),

208

value

209

)

210

}

211

}

212

213

// BigDecimal adapter using String for precision

214

class BigDecimalAdapter : ColumnAdapter<BigDecimal, String> {

215

override fun decode(databaseValue: String): BigDecimal {

216

return BigDecimal(databaseValue)

217

}

218

219

override fun encode(value: BigDecimal): String {

220

return value.toPlainString()

221

}

222

}

223

224

// Instant adapter using Long for epoch milliseconds

225

class InstantAdapter : ColumnAdapter<Instant, Long> {

226

override fun decode(databaseValue: Long): Instant {

227

return Instant.fromEpochMilliseconds(databaseValue)

228

}

229

230

override fun encode(value: Instant): Long {

231

return value.toEpochMilliseconds()

232

}

233

}

234

235

// Usage with complex types

236

@Serializable

237

data class UserPreferences(

238

val theme: String,

239

val notifications: Boolean,

240

val language: String

241

)

242

243

@Serializable

244

data class ContactInfo(

245

val email: String,

246

val phone: String?,

247

val socialLinks: Map<String, String>

248

)

249

250

val database = Database(

251

driver = driver,

252

userAdapter = User.Adapter(

253

tags = ListAdapter(String.serializer()),

254

preferences = JsonAdapter(UserPreferences.serializer()),

255

contactInfo = JsonAdapter(ContactInfo.serializer()),

256

metadata = MapAdapter(String.serializer(), String.serializer()),

257

balance = BigDecimalAdapter(),

258

lastSeen = InstantAdapter()

259

)

260

)

261

```

262

263

### Error Handling in Adapters

264

265

Implement robust error handling for data conversion failures.

266

267

**Usage Examples:**

268

269

```kotlin

270

import app.cash.sqldelight.ColumnAdapter

271

272

// Adapter with comprehensive error handling

273

class SafeJsonAdapter<T>(

274

private val serializer: KSerializer<T>,

275

private val defaultValue: T

276

) : ColumnAdapter<T, String> {

277

override fun decode(databaseValue: String): T {

278

return try {

279

Json.decodeFromString(serializer, databaseValue)

280

} catch (e: SerializationException) {

281

println("Failed to decode JSON: $databaseValue, using default: $defaultValue")

282

defaultValue

283

} catch (e: IllegalArgumentException) {

284

println("Invalid JSON format: $databaseValue, using default: $defaultValue")

285

defaultValue

286

}

287

}

288

289

override fun encode(value: T): String {

290

return try {

291

Json.encodeToString(serializer, value)

292

} catch (e: SerializationException) {

293

println("Failed to encode value: $value, using default JSON")

294

Json.encodeToString(serializer, defaultValue)

295

}

296

}

297

}

298

299

// Enum adapter with fallback for unknown values

300

class SafeEnumAdapter<T : Enum<T>>(

301

private val enumValues: Array<out T>,

302

private val defaultValue: T

303

) : ColumnAdapter<T, String> {

304

override fun decode(databaseValue: String): T {

305

return enumValues.firstOrNull { it.name == databaseValue }

306

?: run {

307

println("Unknown enum value: $databaseValue, using default: $defaultValue")

308

defaultValue

309

}

310

}

311

312

override fun encode(value: T): String {

313

return value.name

314

}

315

}

316

317

// Numeric adapter with validation

318

class ValidatedIntAdapter(

319

private val min: Int = Int.MIN_VALUE,

320

private val max: Int = Int.MAX_VALUE

321

) : ColumnAdapter<Int, Long> {

322

override fun decode(databaseValue: Long): Int {

323

val intValue = databaseValue.toInt()

324

return when {

325

intValue < min -> {

326

println("Value $intValue below minimum $min, clamping")

327

min

328

}

329

intValue > max -> {

330

println("Value $intValue above maximum $max, clamping")

331

max

332

}

333

else -> intValue

334

}

335

}

336

337

override fun encode(value: Int): Long {

338

val clampedValue = value.coerceIn(min, max)

339

if (clampedValue != value) {

340

println("Value $value outside range [$min, $max], clamped to $clampedValue")

341

}

342

return clampedValue.toLong()

343

}

344

}

345

346

// Usage with error handling

347

val database = Database(

348

driver = driver,

349

userAdapter = User.Adapter(

350

status = SafeEnumAdapter(UserStatus.values(), UserStatus.ACTIVE),

351

preferences = SafeJsonAdapter(

352

UserPreferences.serializer(),

353

UserPreferences(theme = "default", notifications = true, language = "en")

354

),

355

score = ValidatedIntAdapter(min = 0, max = 100)

356

)

357

)

358

```

359

360

### Migration and Schema Evolution

361

362

Handle schema changes and data migration with column adapters.

363

364

**Usage Examples:**

365

366

```kotlin

367

import app.cash.sqldelight.ColumnAdapter

368

369

// Versioned adapter for handling schema evolution

370

class VersionedUserPreferencesAdapter : ColumnAdapter<UserPreferences, String> {

371

override fun decode(databaseValue: String): UserPreferences {

372

return try {

373

// Try current version first

374

Json.decodeFromString<UserPreferences>(databaseValue)

375

} catch (e: SerializationException) {

376

try {

377

// Fallback to previous version

378

val oldPrefs = Json.decodeFromString<OldUserPreferences>(databaseValue)

379

migrateFromOldVersion(oldPrefs)

380

} catch (e: SerializationException) {

381

// Final fallback to defaults

382

UserPreferences.default()

383

}

384

}

385

}

386

387

override fun encode(value: UserPreferences): String {

388

return Json.encodeToString(value)

389

}

390

391

private fun migrateFromOldVersion(old: OldUserPreferences): UserPreferences {

392

return UserPreferences(

393

theme = old.theme,

394

notifications = old.notifications,

395

language = old.language ?: "en", // New field with default

396

// New fields get default values

397

darkMode = false,

398

fontSize = FontSize.MEDIUM

399

)

400

}

401

}

402

403

// Backward-compatible enum adapter

404

class BackwardCompatibleStatusAdapter : ColumnAdapter<UserStatus, String> {

405

override fun decode(databaseValue: String): UserStatus {

406

return when (databaseValue) {

407

"ACTIVE" -> UserStatus.ACTIVE

408

"INACTIVE" -> UserStatus.INACTIVE

409

"SUSPENDED" -> UserStatus.SUSPENDED

410

"DELETED" -> UserStatus.DELETED

411

// Handle old enum values that no longer exist

412

"BANNED" -> UserStatus.SUSPENDED // Map old value to new equivalent

413

"PENDING" -> UserStatus.INACTIVE // Map old value to new equivalent

414

else -> {

415

println("Unknown status: $databaseValue, defaulting to INACTIVE")

416

UserStatus.INACTIVE

417

}

418

}

419

}

420

421

override fun encode(value: UserStatus): String {

422

return value.name

423

}

424

}

425

426

// Usage during database migration

427

val legacyDatabase = Database(

428

driver = driver,

429

userAdapter = User.Adapter(

430

status = BackwardCompatibleStatusAdapter(),

431

preferences = VersionedUserPreferencesAdapter()

432

)

433

)

434

435

// Migration script example

436

fun migrateUserData() {

437

val users = legacyDatabase.userQueries.selectAll().executeAsList()

438

users.forEach { user ->

439

// Adapters handle the conversion automatically

440

val updatedUser = user.copy(

441

// Any necessary transformations

442

)

443

legacyDatabase.userQueries.update(updatedUser)

444

}

445

}

446

```