SQLDelight multiplatform runtime library providing Kotlin APIs for type-safe database operations with compile-time SQL verification
—
Type marshaling system for converting between Kotlin types and database-supported primitives, with built-in enum support and extensible custom type handling.
Core interface for marshaling custom types to and from database-supported types.
/**
* Marshal and map the type T to and from a database type S which is one of
* Long, Double, String, byte[]
* @param T The Kotlin type to marshal
* @param S The database-supported type (Long, Double, String, ByteArray)
*/
interface ColumnAdapter<T : Any, S> {
/**
* Decode a database value to the Kotlin type
* @param databaseValue Raw value from the database
* @return Decoded value as type T
*/
fun decode(databaseValue: S): T
/**
* Encode a Kotlin value to the database type
* @param value Value to encode for database storage
* @return Encoded value as database type S
*/
fun encode(value: T): S
}Built-in column adapter for mapping enum classes to strings in the database.
/**
* A ColumnAdapter which maps the enum class T to a string in the database
* @param T The enum type to adapt
*/
class EnumColumnAdapter<T : Enum<T>> internal constructor(
private val enumValues: Array<out T>
) : ColumnAdapter<T, String> {
/**
* Decode a string database value to the enum instance
* @param databaseValue String representation of the enum name
* @return Enum instance matching the name
*/
override fun decode(databaseValue: String): T
/**
* Encode an enum instance to its string name
* @param value Enum instance to encode
* @return String name of the enum
*/
override fun encode(value: T): String
}
/**
* Factory function for creating EnumColumnAdapter instances
* @return EnumColumnAdapter instance for the reified enum type T
*/
inline fun <reified T : Enum<T>> EnumColumnAdapter(): EnumColumnAdapter<T>Usage Examples:
import app.cash.sqldelight.ColumnAdapter
import app.cash.sqldelight.EnumColumnAdapter
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.UUID
// Basic enum adapter usage
enum class UserStatus {
ACTIVE, INACTIVE, SUSPENDED, DELETED
}
val statusAdapter = EnumColumnAdapter<UserStatus>()
// Using the adapter
val activeStatus = statusAdapter.decode("ACTIVE") // UserStatus.ACTIVE
val statusString = statusAdapter.encode(UserStatus.SUSPENDED) // "SUSPENDED"
// Custom date adapter
class LocalDateAdapter : ColumnAdapter<LocalDate, String> {
override fun decode(databaseValue: String): LocalDate {
return LocalDate.parse(databaseValue)
}
override fun encode(value: LocalDate): String {
return value.toString()
}
}
// Custom datetime adapter with formatting
class LocalDateTimeAdapter : ColumnAdapter<LocalDateTime, String> {
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
override fun decode(databaseValue: String): LocalDateTime {
return LocalDateTime.parse(databaseValue, formatter)
}
override fun encode(value: LocalDateTime): String {
return value.format(formatter)
}
}
// UUID adapter
class UuidAdapter : ColumnAdapter<UUID, String> {
override fun decode(databaseValue: String): UUID {
return UUID.fromString(databaseValue)
}
override fun encode(value: UUID): String {
return value.toString()
}
}
// JSON adapter for complex objects
class JsonAdapter<T>(
private val serializer: (T) -> String,
private val deserializer: (String) -> T
) : ColumnAdapter<T, String> {
override fun decode(databaseValue: String): T {
return deserializer(databaseValue)
}
override fun encode(value: T): String {
return serializer(value)
}
}
// Using JSON adapter with kotlinx.serialization
@Serializable
data class UserPreferences(
val theme: String,
val language: String,
val notifications: Boolean
)
val preferencesAdapter = JsonAdapter<UserPreferences>(
serializer = { Json.encodeToString(it) },
deserializer = { Json.decodeFromString(it) }
)
// Numeric type adapters
class BigDecimalAdapter : ColumnAdapter<BigDecimal, String> {
override fun decode(databaseValue: String): BigDecimal {
return BigDecimal(databaseValue)
}
override fun encode(value: BigDecimal): String {
return value.toString()
}
}
// Boolean to integer adapter
class BooleanIntAdapter : ColumnAdapter<Boolean, Long> {
override fun decode(databaseValue: Long): Boolean {
return databaseValue != 0L
}
override fun encode(value: Boolean): Long {
return if (value) 1L else 0L
}
}
// List adapter for comma-separated values
class StringListAdapter : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String): List<String> {
return if (databaseValue.isEmpty()) {
emptyList()
} else {
databaseValue.split(",")
}
}
override fun encode(value: List<String>): String {
return value.joinToString(",")
}
}
// Using adapters in generated SQLDelight code
// In your .sq file:
// CREATE TABLE users (
// id INTEGER PRIMARY KEY,
// uuid TEXT NOT NULL,
// status TEXT NOT NULL,
// created_at TEXT NOT NULL,
// preferences TEXT NOT NULL,
// tags TEXT NOT NULL
// );
// In your database setup:
class UserDatabase(driver: SqlDriver) : Database(
driver = driver,
userAdapter = User.Adapter(
uuidAdapter = UuidAdapter(),
statusAdapter = EnumColumnAdapter<UserStatus>(),
created_atAdapter = LocalDateTimeAdapter(),
preferencesAdapter = preferencesAdapter,
tagsAdapter = StringListAdapter()
)
)
// Nullable adapters
class NullableLocalDateAdapter : ColumnAdapter<LocalDate?, String?> {
override fun decode(databaseValue: String?): LocalDate? {
return databaseValue?.let { LocalDate.parse(it) }
}
override fun encode(value: LocalDate?): String? {
return value?.toString()
}
}
// Binary data adapter
class ByteArrayAdapter : ColumnAdapter<ByteArray, ByteArray> {
override fun decode(databaseValue: ByteArray): ByteArray {
return databaseValue
}
override fun encode(value: ByteArray): ByteArray {
return value
}
}
// Encrypted string adapter
class EncryptedStringAdapter(
private val encryptor: (String) -> String,
private val decryptor: (String) -> String
) : ColumnAdapter<String, String> {
override fun decode(databaseValue: String): String {
return decryptor(databaseValue)
}
override fun encode(value: String): String {
return encryptor(value)
}
}Column adapters are typically used in three main contexts:
Column adapters should handle malformed data gracefully:
class SafeLocalDateAdapter : ColumnAdapter<LocalDate?, String?> {
override fun decode(databaseValue: String?): LocalDate? {
return try {
databaseValue?.let { LocalDate.parse(it) }
} catch (e: DateTimeParseException) {
null // or throw a more specific exception
}
}
override fun encode(value: LocalDate?): String? {
return value?.toString()
}
}Install with Tessl CLI
npx tessl i tessl/maven-app-cash-sqldelight--runtime-iossimulatorarm64