CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-app-cash-sqldelight--runtime-iossimulatorarm64

SQLDelight multiplatform runtime library providing Kotlin APIs for type-safe database operations with compile-time SQL verification

Pending
Overview
Eval results
Files

column-adapters.mddocs/

Column Adapters

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

Capabilities

ColumnAdapter Interface

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
}

EnumColumnAdapter Class

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)
    }
}

Adapter Integration Patterns

Column adapters are typically used in three main contexts:

  1. Generated Database Classes: SQLDelight generates adapter parameters for custom types
  2. Manual Query Construction: When building queries manually with custom types
  3. Migration Scenarios: Converting between different type representations during schema migrations

Performance Considerations

  • Encoding/Decoding Cost: Adapters add serialization overhead on every database operation
  • Caching: Consider caching expensive conversions when the same values are frequently accessed
  • Bulk Operations: For large datasets, consider batch processing optimizations
  • Memory Usage: Be mindful of memory allocation in adapters, especially for collections and complex objects

Error Handling

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

docs

column-adapters.md

database-driver.md

index.md

logging-debugging.md

query-execution.md

schema-management.md

transaction-management.md

tile.json