Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ object JdbcControllers {
// Postgrees comes with its own default encoders, to exclude them need to override this property direct
override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + AdditionalPostgresEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + AdditionalPostgresEncoding.decoders
additionalEncoders = encodingConfig.additionalEncoders + JsonObjectEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonObjectEncoding.decoders
)

// Postgres does not support Types.TIME_WITH_TIMEZONE as a JDBC type but does have a `TIME WITH TIMEZONE` datatype this is puzzling.
Expand All @@ -59,29 +59,35 @@ object JdbcControllers {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + AdditionalPostgresEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + AdditionalPostgresEncoding.decoders
additionalEncoders = encodingConfig.additionalEncoders + JsonObjectEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonObjectEncoding.decoders
)

companion object { }
}

open class H2(
override val database: DataSource,
override val encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
): JdbcController(database) {
override val encodingApi: JdbcSqlEncoding =
object: JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
BasicEncoding<Connection, PreparedStatement, ResultSet> by JdbcBasicEncoding,
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by JdbcTimeEncoding(),
JavaUuidEncoding<Connection, PreparedStatement, ResultSet> by JdbcUuidObjectEncoding {}

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + JsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonTextEncoding.decoders
)

companion object { }
}

open class Mysql(
override val database: DataSource,
override val encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
): JdbcController(database) {
override val encodingApi: JdbcSqlEncoding =
object : JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
Expand All @@ -95,19 +101,32 @@ object JdbcControllers {
override val jdbcTypeOfOffsetTime = Types.TIME
override val jdbcTypeOfOffsetDateTime = Types.TIMESTAMP
}

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + JsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonTextEncoding.decoders
)

companion object { }
}

open class Sqlite(
override val database: DataSource,
override val encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
): JdbcController(database) {
override val encodingApi: JdbcSqlEncoding =
object : JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
BasicEncoding<Connection, PreparedStatement, ResultSet> by JdbcBasicEncoding,
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by JdbcTimeEncodingLegacy,
JavaUuidEncoding<Connection, PreparedStatement, ResultSet> by JdbcUuidStringEncoding {}

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + JsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonTextEncoding.decoders
)

protected override open suspend fun <T> runActionReturningScoped(act: ControllerActionReturning<T>, options: JdbcExecutionOptions): Flow<T> =
flowWithConnection(options) {
val conn = localConnection()
Expand Down Expand Up @@ -159,14 +178,20 @@ object JdbcControllers {

open class Oracle(
override val database: DataSource,
override val encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
): JdbcController(database) {
override val encodingApi: JdbcSqlEncoding =
object : JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
BasicEncoding<Connection, PreparedStatement, ResultSet> by JdbcEncodingOracle,
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by OracleTimeEncoding,
JavaUuidEncoding<Connection, PreparedStatement, ResultSet> by JdbcUuidStringEncoding {}

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + JsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonTextEncoding.decoders
)

object OracleTimeEncoding: JdbcTimeEncoding() {
// Normally it is Types.TIME by in that case Oracle truncates the milliseconds
override val jdbcTypeOfLocalTime = Types.TIMESTAMP
Expand All @@ -193,14 +218,20 @@ object JdbcControllers {

open class SqlServer(
override val database: DataSource,
override val encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
encodingConfig: JdbcEncodingConfig = JdbcEncodingConfig.Default
): JdbcController(database) {
override val encodingApi: JdbcSqlEncoding =
object : JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
BasicEncoding<Connection, PreparedStatement, ResultSet> by JdbcBasicEncoding,
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by SqlServerTimeEncoding,
JavaUuidEncoding<Connection, PreparedStatement, ResultSet> by JdbcUuidStringEncoding {}

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + JsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + JsonTextEncoding.decoders
)

object SqlServerTimeEncoding: JdbcTimeEncoding() {
// Override java.util.Date encoding to use TIMESTAMP_WITH_TIMEZONE for DATETIMEOFFSET columns
override val JDateEncoder: JdbcEncoderAny<java.util.Date> = JdbcEncoderAny(Types.TIMESTAMP_WITH_TIMEZONE, java.util.Date::class) { ctx, v, i ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,22 @@ open class JdbcBasicEncoding:
override val ByteArrayDecoder: JdbcDecoderAny<ByteArray> = JdbcDecoderAny(ByteArray::class) { ctx, i -> ctx.row.getBytes(i) }
}

object AdditionalPostgresEncoding {
object JsonObjectEncoding {
val SqlJsonEncoder: JdbcEncoder<SqlJson> = JdbcEncoderAny(Types.OTHER, SqlJson::class) { ctx, v, i -> ctx.stmt.setObject(i, v.value, Types.OTHER) }
val SqlJsonDecoder: JdbcDecoder<SqlJson> = JdbcDecoderAny(SqlJson::class) { ctx, i -> SqlJson(ctx.row.getString(i)) }

val encoders = setOf(SqlJsonEncoder)
val decoders = setOf(SqlJsonDecoder)
}

object JsonTextEncoding {
val SqlJsonEncoder: JdbcEncoder<SqlJson> = JdbcEncoderAny(Types.VARCHAR, SqlJson::class) { ctx, v, i -> ctx.stmt.setObject(i, v.value, Types.VARCHAR) }
val SqlJsonDecoder: JdbcDecoder<SqlJson> = JdbcDecoderAny(SqlJson::class) { ctx, i -> SqlJson(ctx.row.getString(i)) }

val encoders = setOf(SqlJsonEncoder)
val decoders = setOf(SqlJsonDecoder)
}

object AdditionalJdbcEncoding {
val BigDecimalEncoder: JdbcEncoderAny<BigDecimal> = JdbcEncoderAny(Types.NUMERIC, BigDecimal::class) { ctx, v, i -> ctx.stmt.setBigDecimal(i, v) }
val BigDecimalDecoder: JdbcDecoderAny<BigDecimal> = JdbcDecoderAny(BigDecimal::class) { ctx, i -> ctx.row.getBigDecimal(i) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import io.r2dbc.spi.Row
import io.r2dbc.spi.Statement

object R2dbcControllers {
class Postgres(
open class Postgres(
encodingConfig: R2dbcEncodingConfig = R2dbcEncodingConfig.Default(),
override val connectionFactory: ConnectionFactory
): R2dbcController(encodingConfig,connectionFactory) {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + R2dbcPostgresAdditionalEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcPostgresAdditionalEncoding.decoders
additionalEncoders = encodingConfig.additionalEncoders + R2dbcJsonObjectEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcJsonObjectEncoding.decoders
)

override val encodingApi: R2dbcSqlEncoding =
Expand All @@ -32,11 +32,17 @@ object R2dbcControllers {
changePlaceholdersIn(sql) { index -> "$${index + 1}" }
}

class SqlServer(
open class SqlServer(
encodingConfig: R2dbcEncodingConfig = R2dbcEncodingConfig.Default(),
override val connectionFactory: ConnectionFactory
): R2dbcController(encodingConfig,connectionFactory) {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + R2dbcJsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcJsonTextEncoding.decoders
)

override val encodingApi: R2dbcSqlEncoding =
object: JavaSqlEncoding<Connection, Statement, Row>,
BasicEncoding<Connection, Statement, Row> by R2dbcBasicEncoding,
Expand All @@ -54,11 +60,17 @@ object R2dbcControllers {
changePlaceholdersIn(sql) { index -> "@Param${index + startingStatementIndex.value}" }
}

class Mysql(
open class Mysql(
encodingConfig: R2dbcEncodingConfig = R2dbcEncodingConfig.Default(),
override val connectionFactory: ConnectionFactory
): R2dbcController(encodingConfig, connectionFactory) {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + R2dbcJsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcJsonTextEncoding.decoders
)

override val encodingApi: R2dbcSqlEncoding =
object: JavaSqlEncoding<Connection, Statement, Row>,
BasicEncoding<Connection, Statement, Row> by R2dbcBasicEncoding,
Expand All @@ -69,11 +81,17 @@ object R2dbcControllers {
override fun changePlaceholders(sql: String): String = sql
}

class H2(
open class H2(
encodingConfig: R2dbcEncodingConfig = R2dbcEncodingConfig.Default(),
override val connectionFactory: ConnectionFactory
): R2dbcController(encodingConfig, connectionFactory) {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + R2dbcJsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcJsonTextEncoding.decoders
)

override val startingResultRowIndex: StartingIndex get() = StartingIndex.Zero

override val encodingApi: R2dbcSqlEncoding =
Expand All @@ -86,11 +104,17 @@ object R2dbcControllers {
changePlaceholdersIn(sql) { index -> "$${index + 1}" }
}

class Oracle(
open class Oracle(
encodingConfig: R2dbcEncodingConfig = R2dbcEncodingConfig.Default(),
override val connectionFactory: ConnectionFactory
): R2dbcController(encodingConfig, connectionFactory) {

override val encodingConfig =
encodingConfig.copy(
additionalEncoders = encodingConfig.additionalEncoders + R2dbcJsonTextEncoding.encoders,
additionalDecoders = encodingConfig.additionalDecoders + R2dbcJsonTextEncoding.decoders
)

override val encodingApi: R2dbcSqlEncoding =
object: JavaSqlEncoding<Connection, Statement, Row>,
BasicEncoding<Connection, Statement, Row> by R2dbcBasicEncodingOracle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ package io.exoquery.controller.r2dbc

import io.exoquery.controller.SqlJson

object R2dbcPostgresAdditionalEncoding {
object R2dbcJsonObjectEncoding {
private const val NA = 0
val SqlJsonEncoder: R2dbcEncoderAny<SqlJson> = R2dbcEncoderAny(NA, SqlJson::class) { ctx, v, i -> ctx.stmt.bind(i, io.r2dbc.postgresql.codec.Json.of(v.value)) }
val SqlJsonDecoder: R2dbcDecoderAny<SqlJson> = R2dbcDecoderAny(SqlJson::class) { ctx, i -> SqlJson(ctx.row.get(i, io.r2dbc.postgresql.codec.Json::class.java).asString()) }

val encoders: Set<R2dbcEncoderAny<*>> = setOf(SqlJsonEncoder)
val decoders: Set<R2dbcDecoderAny<*>> = setOf(SqlJsonDecoder)
}

object R2dbcJsonTextEncoding {
private const val NA = 0
val SqlJsonEncoder: R2dbcEncoderAny<SqlJson> = R2dbcEncoderAny(NA, SqlJson::class) { ctx, v, i -> ctx.stmt.bind(i, v.value) }
val SqlJsonDecoder: R2dbcDecoderAny<SqlJson> = R2dbcDecoderAny(SqlJson::class) { ctx, i -> SqlJson(ctx.row.get(i, String::class.java)) }

val encoders: Set<R2dbcEncoderAny<*>> = setOf(SqlJsonEncoder)
val decoders: Set<R2dbcDecoderAny<*>> = setOf(SqlJsonDecoder)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.exoquery.sql.h2

import io.exoquery.controller.JsonValue
import io.exoquery.controller.SqlJsonValue
import io.exoquery.sql.*
import io.exoquery.sql.Sql
import io.exoquery.controller.jdbc.JdbcControllers
import io.exoquery.controller.runOn
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import kotlinx.serialization.Serializable

class JsonSpec: FreeSpec({
val ds = TestDatabases.h2
val ctx by lazy {
JdbcControllers.H2(ds)
}

beforeEach {
ds.run("DELETE FROM JsonExample")
}

"SqlJsonValue annotation works on" - {
"inner data class" - {
@SqlJsonValue
@Serializable
data class MyPerson(val name: String, val age: Int)

@Serializable
data class Example(val id: Int, val value: MyPerson)

val je = Example(1, MyPerson("Alice", 30))

"should encode in json (with explicit serializer) and decode" {
Sql("INSERT INTO JsonExample (id, \"value\") VALUES (1, ${Param.withSer(je.value, MyPerson.serializer())})").action().runOn(ctx)
Sql("SELECT id, \"value\" FROM JsonExample").queryOf<Example>().runOn(ctx) shouldBe listOf(je)
}
}

"annotated field" {
@Serializable
data class MyPerson(val name: String, val age: Int)

@Serializable
data class Example(val id: Int, @SqlJsonValue val value: MyPerson)

val je = Example(1, MyPerson("Joe", 123))
Sql("""INSERT INTO JsonExample (id, "value") VALUES (1, '{"name":"Joe", "age":123}')""").action().runOn(ctx)
val customers = Sql("SELECT id, \"value\" FROM JsonExample").queryOf<Example>().runOn(ctx)
customers shouldBe listOf(je)
}
}

})
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.exoquery.sql.mysql

import io.exoquery.controller.JsonValue
import io.exoquery.controller.SqlJsonValue
import io.exoquery.sql.*
import io.exoquery.sql.Sql
import io.exoquery.controller.jdbc.JdbcControllers
import io.exoquery.controller.runOn
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import kotlinx.serialization.Serializable

class JsonSpec: FreeSpec({
val ds = TestDatabases.mysql
val ctx by lazy {
JdbcControllers.Mysql(ds)
}

beforeEach {
ds.run("DELETE FROM JsonExample")
}

"SqlJsonValue annotation works on" - {
"inner data class" - {
@SqlJsonValue
@Serializable
data class MyPerson(val name: String, val age: Int)

@Serializable
data class Example(val id: Int, val value: MyPerson)

val je = Example(1, MyPerson("Alice", 30))

"should encode in json (with explicit serializer) and decode" {
Sql("INSERT INTO JsonExample (id, value) VALUES (1, ${Param.withSer(je.value, MyPerson.serializer())})").action().runOn(ctx)
Sql("SELECT id, value FROM JsonExample").queryOf<Example>().runOn(ctx) shouldBe listOf(je)
}
}

"annotated field" {
@Serializable
data class MyPerson(val name: String, val age: Int)

@Serializable
data class Example(val id: Int, @SqlJsonValue val value: MyPerson)

val je = Example(1, MyPerson("Joe", 123))
Sql("""INSERT INTO JsonExample (id, value) VALUES (1, '{"name":"Joe", "age":123}')""").action().runOn(ctx)
val customers = Sql("SELECT id, value FROM JsonExample").queryOf<Example>().runOn(ctx)
customers shouldBe listOf(je)
}
}

})
Loading