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
@@ -1,12 +1,26 @@
package io.exoquery.controller

import kotlin.jvm.JvmInline
import kotlin.reflect.KClass

open class DecoderAny<T: Any, Session, Row>(
open class DecoderAny<T: Any, Session, Row> @PublishedApi internal constructor(
open override val type: KClass<T>,
/**
* The original type of this decoder at the time of its initial creation.
* This field remains unchanged through transformations like [map] and [transformInto].
* Used in R2DBC's get operation where the driver requires the actual Java class
* of the original type for proper type conversion.
*/
open val originalType: KClass<*>,
open val isNull: (Int, Row) -> Boolean,
open val f: (DecodingContext<Session, Row>, Int) -> T?,
): SqlDecoder<Session, Row, T>() {

constructor(
type: KClass<T>,
isNull: (Int, Row) -> Boolean,
f: (DecodingContext<Session, Row>, Int) -> T?
) : this(type, type, isNull, f)
override fun isNullable(): Boolean = false
override fun decode(ctx: DecodingContext<Session, Row>, index: Int): T {
val value =
Expand All @@ -32,11 +46,14 @@ open class DecoderAny<T: Any, Session, Row>(
* Transforms this decoder into another decoder by applying the given function to the decoded value.
* Alias for [map].
*/
inline fun <reified R: Any> transformInto(crossinline into: (T) -> R): DecoderAny<R, Session, Row> =
inline fun <reified R: Any> transformInto(crossinline into: MapContext<Session, Row>.(T) -> R): DecoderAny<R, Session, Row> =
map(into)

inline fun <reified R: Any> map(crossinline into: (T) -> R): DecoderAny<R, Session, Row> =
DecoderAny<R, Session, Row>(R::class, isNull) { ctx, index -> into(this.decode(ctx, index)) }
@JvmInline
final value class MapContext<Session, Row>(val ctx: DecodingContext<Session, Row>)

inline fun <reified R: Any> map(crossinline into: MapContext<Session, Row>.(T) -> R): DecoderAny<R, Session, Row> =
DecoderAny<R, Session, Row>(R::class, this@DecoderAny.originalType, isNull) { ctx, index -> into(MapContext(ctx), this.decode(ctx, index)) }

override fun asNullable(): SqlDecoder<Session, Row, T?> =
object: SqlDecoder<Session, Row, T?>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
package io.exoquery.controller

import kotlin.jvm.JvmInline
import kotlin.reflect.KClass

open class EncoderAny<T: Any, TypeId: Any, Session, Stmt>(
open class EncoderAny<T: Any, TypeId: Any, Session, Stmt> @PublishedApi internal constructor(
open val dataType: TypeId,
open override val type: KClass<T>,
/**
* The original type of this encoder at the time of its initial creation.
* This field remains unchanged through transformations like [contramap] and [transformFrom].
* Used in R2DBC's bindNull operation where the driver requires the actual Java class
* of the original type rather than a JDBC type integer.
*/
open val originalType: KClass<*>,
open val setNull: (Int, Stmt, TypeId) -> Unit,
open val f: (EncodingContext<Session, Stmt>, T, Int) -> Unit
): SqlEncoder<Session, Stmt, T>() {

constructor(
dataType: TypeId,
type: KClass<T>,
setNull: (Int, Stmt, TypeId) -> Unit,
f: (EncodingContext<Session, Stmt>, T, Int) -> Unit
) : this(dataType, type, type, setNull, f)
override fun encode(ctx: EncodingContext<Session, Stmt>, value: T, index: Int) =
f(ctx, value, index)

Expand All @@ -33,9 +48,12 @@ open class EncoderAny<T: Any, TypeId: Any, Session, Stmt>(
* Transforms this encoder into another encoder by applying the given function to the value before encoding it.
* Alias for [contramap].
*/
inline fun <reified R: Any> transformFrom(crossinline from: (R) -> T): EncoderAny<R, TypeId, Session, Stmt> =
inline fun <reified R: Any> transformFrom(crossinline from: ContrmapContext<Session, Stmt>.(R) -> T): EncoderAny<R, TypeId, Session, Stmt> =
contramap(from)

inline fun <reified R: Any> contramap(crossinline from: (R) -> T): EncoderAny<R, TypeId, Session, Stmt> =
EncoderAny<R, TypeId, Session, Stmt>(this@EncoderAny.dataType, R::class, this@EncoderAny.setNull) { ctx, value, i -> this.f(ctx, from(value), i) }
@JvmInline
final value class ContrmapContext<Session, Stmt>(val ctx: EncodingContext<Session, Stmt>)

inline fun <reified R: Any> contramap(crossinline from: ContrmapContext<Session, Stmt>.(R) -> T): EncoderAny<R, TypeId, Session, Stmt> =
EncoderAny<R, TypeId, Session, Stmt>(this@EncoderAny.dataType, R::class, this@EncoderAny.originalType, this@EncoderAny.setNull) { ctx, value, i -> this.f(ctx, from(ContrmapContext(ctx), value), i) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.exoquery.controller.JavaTimeEncoding
import io.exoquery.controller.JavaUuidEncoding
import io.exoquery.controller.*
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.toJavaZoneId
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
Expand Down Expand Up @@ -197,9 +198,22 @@ object JdbcControllers {
override val encodingApi: JdbcSqlEncoding =
object : JavaSqlEncoding<Connection, PreparedStatement, ResultSet>,
BasicEncoding<Connection, PreparedStatement, ResultSet> by JdbcBasicEncoding,
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by JdbcTimeEncoding(),
JavaTimeEncoding<Connection, PreparedStatement, ResultSet> by SqlServerTimeEncoding,
JavaUuidEncoding<Connection, PreparedStatement, ResultSet> by JdbcUuidStringEncoding {}

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 ->
val instant = v.toInstant()
val offsetDateTime = java.time.OffsetDateTime.ofInstant(instant, ctx.timeZone.toJavaZoneId())
ctx.stmt.setObject(i, offsetDateTime, Types.TIMESTAMP_WITH_TIMEZONE)
}
override val JDateDecoder: JdbcDecoderAny<java.util.Date> = JdbcDecoderAny(java.util.Date::class) { ctx, i ->
val offsetDateTime = ctx.row.getObject(i, java.time.OffsetDateTime::class.java)
java.util.Date.from(offsetDateTime.toInstant())
}
}

override suspend fun <T> runActionReturningScoped(act: ControllerActionReturning<T>, options: JdbcExecutionOptions): Flow<T> =
flowWithConnection(options) {
val conn = localConnection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import io.r2dbc.spi.Connection
import io.r2dbc.spi.Row
import kotlin.reflect.KClass

typealias R2dbcDecoder<T> = DecoderAny<T, Connection, Row>

class R2dbcDecoderAny<T: Any>(
override val type: KClass<T>,
override val f: (R2dbcDecodingContext, Int) -> T?
Expand All @@ -21,7 +23,7 @@ class R2dbcDecoderAny<T: Any>(

object R2dbcDecoders {
@Suppress("UNCHECKED_CAST")
val decoders: Set<SqlDecoder<Connection, Row, out Any>> = setOf(
val decoders: Set<R2dbcDecoder<out Any>> = setOf(
R2dbcBasicEncoding.BooleanDecoder,
R2dbcBasicEncoding.ByteDecoder,
R2dbcBasicEncoding.CharDecoder,
Expand Down
Loading