Skip to content

Commit

Permalink
jdbc: support for automatic persisting/mapping of arrays of @JvmInlin…
Browse files Browse the repository at this point in the history
…e classes (e.g. TSID)
  • Loading branch information
angryziber committed Dec 15, 2024
1 parent 3e8fb2c commit 3c31a79
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* jdbc: @NoTransaction can now be used on jobs
* jdbc: fixed usage of multiple different DataSources when there is an active transaction
* jdbc: allow calling of `PooledConnection.close()` multiple times #80
* jdbc: support for automatic persisting/mapping of arrays of @JvmInline classes (e.g. TSID)
* json: fix TSGenerator on Windows
* json: TSGenerator will now use more type-safe string template types for java.time classes, e.g. `${number}-${number}-${number}` instead of `string`
* slf4j: allow providing of `LOGGER_CLASS` via `.env` file if `Config.useEnvFile()` is called before any logging calls #92
Expand Down
12 changes: 7 additions & 5 deletions jdbc/src/JdbcConverter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import klite.Decimal
import klite.annotations.annotation
import klite.d
import klite.unboxInline
import java.lang.reflect.Modifier.STATIC
import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
Expand Down Expand Up @@ -46,11 +47,11 @@ object JdbcConverter {
operator fun <T: Any> set(type: KClass<T>, converter: ToJdbcConverter<T>) { converters[type] = converter }
inline fun <reified T: Any> use(noinline converter: ToJdbcConverter<T>) = set(T::class, converter)

fun to(v: Any?, conn: Connection? = null) = when (v) {
fun to(v: Any?, conn: Connection? = null): Any? = when (v) {
null -> null
is Enum<*> -> v.name
is Collection<*> -> conn!!.createArrayOf(arrayType(v.firstOrNull()?.javaClass), v.toTypedArray())
is Array<*> -> conn!!.createArrayOf(arrayType(v.javaClass.componentType), v)
is Collection<*> -> conn!!.createArrayOf(arrayType(v.firstOrNull()?.javaClass), v.map { to(it, conn) }.toTypedArray())
is Array<*> -> conn!!.createArrayOf(arrayType(v.javaClass.componentType), v.map { to(it, conn) }.toTypedArray())
else -> {
val cls = v::class
@Suppress("UNCHECKED_CAST") when {
Expand All @@ -63,14 +64,15 @@ object JdbcConverter {
}
}

private fun arrayType(c: Class<*>?) = when {
private fun arrayType(c: Class<*>?): String = when {
c == null -> "varchar"
UUID::class.java.isAssignableFrom(c) -> "uuid"
Number::class.java.isAssignableFrom(c) -> "numeric"
Number::class.java.isAssignableFrom(c) || c.isPrimitive -> "numeric"
LocalDate::class.java.isAssignableFrom(c) -> "date"
LocalTime::class.java.isAssignableFrom(c) -> "time"
LocalDateTime::class.java.isAssignableFrom(c) -> "timestamp"
Instant::class.java.isAssignableFrom(c) -> "timestamptz"
c.isAnnotationPresent(JvmInline::class.java) -> arrayType(c.declaredFields.find { it.modifiers and STATIC == 0 }?.type)
else -> "varchar"
}

Expand Down
30 changes: 30 additions & 0 deletions jdbc/test/JdbcConverterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import klite.Decimal
import klite.TSID
import klite.d
import org.junit.jupiter.api.Test
import java.math.BigDecimal
Expand All @@ -17,6 +18,8 @@ import java.sql.Connection
import java.sql.Date
import java.sql.Timestamp
import java.time.*
import java.time.Month.DECEMBER
import java.time.Month.OCTOBER
import java.time.ZoneOffset.UTC
import java.util.*
import java.util.UUID.randomUUID
Expand Down Expand Up @@ -72,6 +75,33 @@ class JdbcConverterTest {
expect(JdbcConverter.from(array, typeOf<List<Decimal>>())).toEqual(listOf(1.d, 10.d))
}

@Test fun `to array of convertible types`() {
val conn = mockk<Connection>(relaxed = true)
expect(JdbcConverter.to(listOf(OCTOBER, Year.of(2024)), conn)).toBeAnInstanceOf<java.sql.Array>()
verify { conn.createArrayOf("varchar", arrayOf(OCTOBER.toString(), "2024")) }
}

@Test fun `from array of convertible types`() {
val array = mockk<java.sql.Array>(relaxed = true) {
every { array } returns arrayOf(OCTOBER.toString(), DECEMBER.toString())
}
expect(JdbcConverter.from(array, typeOf<Set<Month>>())).toEqual(setOf(OCTOBER, DECEMBER))
expect(JdbcConverter.from(array, typeOf<List<Month>>())).toEqual(listOf(OCTOBER, DECEMBER))
}

@Test fun `to array of inline TSID`() {
val conn = mockk<Connection>(relaxed = true)
expect(JdbcConverter.to(listOf(TSID<Any>(12345)), conn)).toBeAnInstanceOf<java.sql.Array>()
verify { conn.createArrayOf("numeric", arrayOf(12345L)) }
}

@Test fun `from array of inline TSID`() {
val array = mockk<java.sql.Array>(relaxed = true) {
every { array } returns arrayOf(12345L)
}
expect(JdbcConverter.from(array, typeOf<List<TSID<Any>>>())).toEqual(listOf(TSID<Any>(12345L)))
}

@Test fun `to local date and time`() {
expect(JdbcConverter.to(LocalDate.of(2021, 10, 21))).toEqual(LocalDate.of(2021, 10, 21))
expect(JdbcConverter.to(LocalDateTime.MIN)).toBeTheInstance(LocalDateTime.MIN)
Expand Down

0 comments on commit 3c31a79

Please sign in to comment.