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

column-adapters.mddocs/

0

# Column Adapters

1

2

Type marshaling system for converting between Kotlin types and database-supported primitives, with built-in enum support and extensible custom type handling.

3

4

## Capabilities

5

6

### ColumnAdapter Interface

7

8

Core interface for marshaling custom types to and from database-supported 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, byte[]

14

* @param T The Kotlin type to marshal

15

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

16

*/

17

interface ColumnAdapter<T : Any, S> {

18

/**

19

* Decode a database value to the Kotlin type

20

* @param databaseValue Raw value from the database

21

* @return Decoded value as type T

22

*/

23

fun decode(databaseValue: S): T

24

25

/**

26

* Encode a Kotlin value to the database type

27

* @param value Value to encode for database storage

28

* @return Encoded value as database type S

29

*/

30

fun encode(value: T): S

31

}

32

```

33

34

### EnumColumnAdapter Class

35

36

Built-in column adapter for mapping enum classes to strings in the database.

37

38

```kotlin { .api }

39

/**

40

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

41

* @param T The enum type to adapt

42

*/

43

class EnumColumnAdapter<T : Enum<T>> internal constructor(

44

private val enumValues: Array<out T>

45

) : ColumnAdapter<T, String> {

46

/**

47

* Decode a string database value to the enum instance

48

* @param databaseValue String representation of the enum name

49

* @return Enum instance matching the name

50

*/

51

override fun decode(databaseValue: String): T

52

53

/**

54

* Encode an enum instance to its string name

55

* @param value Enum instance to encode

56

* @return String name of the enum

57

*/

58

override fun encode(value: T): String

59

}

60

61

/**

62

* Factory function for creating EnumColumnAdapter instances

63

* @return EnumColumnAdapter instance for the reified enum type T

64

*/

65

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

66

```

67

68

**Usage Examples:**

69

70

```kotlin

71

import app.cash.sqldelight.ColumnAdapter

72

import app.cash.sqldelight.EnumColumnAdapter

73

import java.time.LocalDate

74

import java.time.LocalDateTime

75

import java.time.format.DateTimeFormatter

76

import java.util.UUID

77

78

// Basic enum adapter usage

79

enum class UserStatus {

80

ACTIVE, INACTIVE, SUSPENDED, DELETED

81

}

82

83

val statusAdapter = EnumColumnAdapter<UserStatus>()

84

85

// Using the adapter

86

val activeStatus = statusAdapter.decode("ACTIVE") // UserStatus.ACTIVE

87

val statusString = statusAdapter.encode(UserStatus.SUSPENDED) // "SUSPENDED"

88

89

// Custom date adapter

90

class LocalDateAdapter : ColumnAdapter<LocalDate, String> {

91

override fun decode(databaseValue: String): LocalDate {

92

return LocalDate.parse(databaseValue)

93

}

94

95

override fun encode(value: LocalDate): String {

96

return value.toString()

97

}

98

}

99

100

// Custom datetime adapter with formatting

101

class LocalDateTimeAdapter : ColumnAdapter<LocalDateTime, String> {

102

private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME

103

104

override fun decode(databaseValue: String): LocalDateTime {

105

return LocalDateTime.parse(databaseValue, formatter)

106

}

107

108

override fun encode(value: LocalDateTime): String {

109

return value.format(formatter)

110

}

111

}

112

113

// UUID adapter

114

class UuidAdapter : ColumnAdapter<UUID, String> {

115

override fun decode(databaseValue: String): UUID {

116

return UUID.fromString(databaseValue)

117

}

118

119

override fun encode(value: UUID): String {

120

return value.toString()

121

}

122

}

123

124

// JSON adapter for complex objects

125

class JsonAdapter<T>(

126

private val serializer: (T) -> String,

127

private val deserializer: (String) -> T

128

) : ColumnAdapter<T, String> {

129

override fun decode(databaseValue: String): T {

130

return deserializer(databaseValue)

131

}

132

133

override fun encode(value: T): String {

134

return serializer(value)

135

}

136

}

137

138

// Using JSON adapter with kotlinx.serialization

139

@Serializable

140

data class UserPreferences(

141

val theme: String,

142

val language: String,

143

val notifications: Boolean

144

)

145

146

val preferencesAdapter = JsonAdapter<UserPreferences>(

147

serializer = { Json.encodeToString(it) },

148

deserializer = { Json.decodeFromString(it) }

149

)

150

151

// Numeric type adapters

152

class BigDecimalAdapter : ColumnAdapter<BigDecimal, String> {

153

override fun decode(databaseValue: String): BigDecimal {

154

return BigDecimal(databaseValue)

155

}

156

157

override fun encode(value: BigDecimal): String {

158

return value.toString()

159

}

160

}

161

162

// Boolean to integer adapter

163

class BooleanIntAdapter : ColumnAdapter<Boolean, Long> {

164

override fun decode(databaseValue: Long): Boolean {

165

return databaseValue != 0L

166

}

167

168

override fun encode(value: Boolean): Long {

169

return if (value) 1L else 0L

170

}

171

}

172

173

// List adapter for comma-separated values

174

class StringListAdapter : ColumnAdapter<List<String>, String> {

175

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

176

return if (databaseValue.isEmpty()) {

177

emptyList()

178

} else {

179

databaseValue.split(",")

180

}

181

}

182

183

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

184

return value.joinToString(",")

185

}

186

}

187

188

// Using adapters in generated SQLDelight code

189

// In your .sq file:

190

// CREATE TABLE users (

191

// id INTEGER PRIMARY KEY,

192

// uuid TEXT NOT NULL,

193

// status TEXT NOT NULL,

194

// created_at TEXT NOT NULL,

195

// preferences TEXT NOT NULL,

196

// tags TEXT NOT NULL

197

// );

198

199

// In your database setup:

200

class UserDatabase(driver: SqlDriver) : Database(

201

driver = driver,

202

userAdapter = User.Adapter(

203

uuidAdapter = UuidAdapter(),

204

statusAdapter = EnumColumnAdapter<UserStatus>(),

205

created_atAdapter = LocalDateTimeAdapter(),

206

preferencesAdapter = preferencesAdapter,

207

tagsAdapter = StringListAdapter()

208

)

209

)

210

211

// Nullable adapters

212

class NullableLocalDateAdapter : ColumnAdapter<LocalDate?, String?> {

213

override fun decode(databaseValue: String?): LocalDate? {

214

return databaseValue?.let { LocalDate.parse(it) }

215

}

216

217

override fun encode(value: LocalDate?): String? {

218

return value?.toString()

219

}

220

}

221

222

// Binary data adapter

223

class ByteArrayAdapter : ColumnAdapter<ByteArray, ByteArray> {

224

override fun decode(databaseValue: ByteArray): ByteArray {

225

return databaseValue

226

}

227

228

override fun encode(value: ByteArray): ByteArray {

229

return value

230

}

231

}

232

233

// Encrypted string adapter

234

class EncryptedStringAdapter(

235

private val encryptor: (String) -> String,

236

private val decryptor: (String) -> String

237

) : ColumnAdapter<String, String> {

238

override fun decode(databaseValue: String): String {

239

return decryptor(databaseValue)

240

}

241

242

override fun encode(value: String): String {

243

return encryptor(value)

244

}

245

}

246

```

247

248

### Adapter Integration Patterns

249

250

Column adapters are typically used in three main contexts:

251

252

1. **Generated Database Classes**: SQLDelight generates adapter parameters for custom types

253

2. **Manual Query Construction**: When building queries manually with custom types

254

3. **Migration Scenarios**: Converting between different type representations during schema migrations

255

256

### Performance Considerations

257

258

- **Encoding/Decoding Cost**: Adapters add serialization overhead on every database operation

259

- **Caching**: Consider caching expensive conversions when the same values are frequently accessed

260

- **Bulk Operations**: For large datasets, consider batch processing optimizations

261

- **Memory Usage**: Be mindful of memory allocation in adapters, especially for collections and complex objects

262

263

### Error Handling

264

265

Column adapters should handle malformed data gracefully:

266

267

```kotlin

268

class SafeLocalDateAdapter : ColumnAdapter<LocalDate?, String?> {

269

override fun decode(databaseValue: String?): LocalDate? {

270

return try {

271

databaseValue?.let { LocalDate.parse(it) }

272

} catch (e: DateTimeParseException) {

273

null // or throw a more specific exception

274

}

275

}

276

277

override fun encode(value: LocalDate?): String? {

278

return value?.toString()

279

}

280

}

281

```