Data Handling

Custom Type Encoding

Using custom encoders and decoders for domain-specific types

ExoQuery supports custom encoders and decoders for domain-specific types, allowing you to map custom Kotlin types to database columns seamlessly.

JDBC Custom Encoding

For JDBC, you can add custom encoders and decoders to your database controller:

import io.exoquery.controller.JdbcControllers
import io.exoquery.controller.jdbc.JdbcEncoderAny
import io.exoquery.controller.jdbc.JdbcDecoderAny
import java.sql.Types

// Define a custom type
data class PersonId(val value: Int)

// Create a controller with custom encoding
val myDatabase = object : JdbcControllers.Postgres(dataSource) {
  override val additionalDecoders =
    super.additionalDecoders + JdbcDecoderAny { ctx, i -> PersonId(ctx.row.getInt(i)) }
  override val additionalEncoders =
    super.additionalEncoders + JdbcEncoderAny(Types.INTEGER) { ctx, v: PersonId, i ->
      ctx.stmt.setInt(i, v.value)
    }
}

R2DBC Custom Encoding

For R2DBC, you can configure custom encoders and decoders through the encoding config:

import io.exoquery.controller.r2dbc.R2dbcControllers
import io.exoquery.controller.r2dbc.R2dbcEncodingConfig
import io.exoquery.controller.r2dbc.R2dbcBasicEncoding

// Define a custom type
data class PersonId(val value: Int)

// Create a controller with custom encoding
val controller = R2dbcControllers.Postgres(
  encodingConfig = R2dbcEncodingConfig.Default(
    encoders = setOf(
      R2dbcBasicEncoding.IntEncoder.contramap { id: PersonId -> id.value }
    ),
    decoders = setOf(
      R2dbcBasicEncoding.IntDecoder.map { PersonId(it) }
    )
  ),
  connectionFactory = connectionFactory
)

Using Custom Types in Queries

Once youโ€™ve configured custom encoding, you can use your custom types in queries with paramCtx:

val personId = PersonId(123)
val q = sql {
  Table<Person>().filter { p -> p.id == paramCtx(personId) }
}
q.buildFor.Postgres().runOn(myDatabase)
//> SELECT p.id, p.name, p.age FROM Person p WHERE p.id = ?

Remember that for custom types used in entity classes, youโ€™ll need to mark the field with @Contextual:

@Serializable
data class Person(
  @Contextual val id: PersonId,
  val name: String,
  val age: Int
)