Skip to content

Commit

Permalink
upgrade JdbcExtensions with support for uuid arrays, currency and @Jv…
Browse files Browse the repository at this point in the history
…mInline classes

(cherry picked from commit ccc8094d9cdc5c125042137d41e9bf692cfb4ef2)
  • Loading branch information
angryziber authored and karnilaev committed Aug 6, 2021
1 parent 18e4758 commit bf6b604
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 18 deletions.
13 changes: 10 additions & 3 deletions src/db/BaseModel.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package db

import util.c
import util.toType
import java.net.URL
import java.sql.Date
Expand All @@ -12,6 +13,7 @@ import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
Expand All @@ -25,7 +27,8 @@ interface BaseModel {
inline fun <reified T: Any> T.toValues() = toValuesSkipping()

inline fun <reified T: Any> T.toValuesSkipping(vararg skip: KProperty1<T, *>): Map<String, Any?> =
(T::class.memberProperties - skip).filter { it.javaField != null }.map { it.name to it.get(this) }.toMap()
(T::class.memberProperties - skip).filter { it.javaField != null }
.associate { it.name to it.javaField?.apply { trySetAccessible() }?.get(this) }

inline fun <reified T: Any> ResultSet.fromValues(vararg values: Pair<KProperty1<T, *>, Any?>) = fromValues(T::class, *values)

Expand All @@ -40,8 +43,12 @@ private fun fromDBType(v: Any?, target: KType): Any? = when(target.jvmErasure) {
LocalDate::class -> (v as? Date)?.toLocalDate()
LocalDateTime::class -> (v as Timestamp).toLocalDateTime()
URL::class -> v?.let { URL(v as String) }
Currency::class -> (v as String?)?.c
List::class -> ((v as java.sql.Array).array as Array<*>).map { fromDBType(it, target.arguments[0].type!!) }.toList()
Set::class -> ((v as java.sql.Array).array as Array<*>).map { fromDBType(it, target.arguments[0].type!!) }.toSet()
else -> if (target.jvmErasure.isSubclassOf(Enum::class)) (v as String).toType(target)
else v
else -> when {
target.jvmErasure.isSubclassOf(Enum::class) -> (v as String?)?.toType(target)
target.jvmErasure.hasAnnotation<JvmInline>() -> (v as String?)?.let { target.jvmErasure.primaryConstructor!!.call(it) }
else -> v
}
}
27 changes: 16 additions & 11 deletions src/db/JdbcExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import org.intellij.lang.annotations.Language
import java.net.URL
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.time.Instant
Expand Down Expand Up @@ -31,20 +32,19 @@ fun DataSource.exec(@Language("SQL") expr: String, values: Sequence<Any?> = empt
}

fun DataSource.insert(table: String, values: Map<String, *>): Int =
exec(insertExpr(table, values), values.values.asSequence())
exec(insertExpr(table, values), setValues(values))

fun DataSource.upsert(table: String, values: Map<String, *>, uniqueFields: String = "id"): Int {
val valuesByIndex = values.values.asSequence()
return exec(insertExpr(table, values) +
" on conflict ($uniqueFields) do update set ${setExpr(values)}", valuesByIndex + valuesByIndex)
" on conflict ($uniqueFields) do update set ${setExpr(values)}", setValues(values) + setValues(values))
}

private fun insertExpr(table: String, values: Map<String, *>) = """
insert into $table (${values.keys.joinToString()})
values (${values.entries.joinToString { (it.value as? SqlExpr)?.expr(it.key) ?: "?" }})""".trimIndent()

fun DataSource.update(table: String, where: Map<String, Any?>, values: Map<String, *>): Int =
exec("update $table set ${setExpr(values)}${whereExpr(where)}", values.values.asSequence() + whereValues(where))
exec("update $table set ${setExpr(values)}${whereExpr(where)}", setValues(values) + whereValues(where))

fun DataSource.delete(table: String, where: Map<String, Any?>): Int =
exec("delete from $table${whereExpr(where)}", whereValues(where))
Expand All @@ -64,23 +64,24 @@ private fun whereExpr(k: String, v: Any?) = when(v) {

private fun inExpr(k: String, v: Iterable<*>) = "$k in (${v.joinToString { "?" }})"

private fun whereValues(where: Map<String, Any?>) = where.values.asSequence().filterNotNull().flatMap { it.toIterable() }
private fun setValues(values: Map<String, Any?>) = values.values.asSequence().flatMap { it.flatExpr() }
private fun Any?.flatExpr(): Iterable<Any?> = if (this is SqlExpr) values else listOf(this)

private fun whereValues(where: Map<String, Any?>) = where.values.asSequence().filterNotNull().flatMap { it.toIterable() }
private fun Any?.toIterable(): Iterable<Any?> = when (this) {
is Array<*> -> toList()
is Iterable<*> -> this
is SqlExpr -> values
else -> listOf(this)
else -> flatExpr()
}

operator fun PreparedStatement.set(i: Int, value: Any?) = setObject(i, toDBType(value))
operator fun PreparedStatement.set(i: Int, value: Any?) = setObject(i, connection.toDBType(value))
fun PreparedStatement.setAll(values: Sequence<Any?>) = values.forEachIndexed { i, v -> this[i + 1] = v }

private fun toDBType(v: Any?): Any? = when(v) {
private fun Connection.toDBType(v: Any?): Any? = when(v) {
is Enum<*> -> v.name
is Instant -> v.atOffset(UTC)
is Period, is URL -> v.toString()
is Collection<*> -> v.map { it.toString() }.toTypedArray()
is Period, is URL, is Currency -> v.toString()
is Collection<*> -> createArrayOf(if (v.firstOrNull() is UUID) "uuid" else "varchar", v.toTypedArray())
else -> v
}

Expand Down Expand Up @@ -116,6 +117,10 @@ class Between(from: Any, to: Any): SqlExpr("", from, to) {
override fun expr(key: String) = "$key between ? and ?"
}

class BetweenExcl(from: Any, to: Any): SqlExpr("", from, to) {
override fun expr(key: String) = "$key >= ? and $key < ?"
}

class NullOrOp(operator: String, value: Any?): SqlOp(operator, value) {
override fun expr(key: String) = "($key is null or $key $operator ?)"
}
Expand Down
9 changes: 9 additions & 0 deletions src/util/Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package util

import java.util.*

inline val Int.d get() = toBigDecimal()
inline val Double.d get() = toBigDecimal()
inline val String.d get() = toBigDecimal()

inline val String.c get() = Currency.getInstance(this)
4 changes: 0 additions & 4 deletions src/util/d.kt

This file was deleted.

0 comments on commit bf6b604

Please sign in to comment.