com.sxtanna.db.struct.Resolver.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Kuery Show documentation
Show all versions of Kuery Show documentation
MySQL Kotlin DSL/ORM based on HikariCP
package com.sxtanna.db.struct
import com.sxtanna.db.ext.*
import com.sxtanna.db.struct.SqlType.*
import java.math.BigDecimal
import java.math.BigInteger
import java.sql.ResultSet
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.jvmErasure
/**
* Resolve types for [SqlType] going to and from the database
*/
@Suppress("RemoveExplicitTypeArguments")
object Resolver {
/**
* Defines how to resolve objects from a [ResultSet]
*
* Has default implementations for
* * [Char]
* * [UUID]
* * [Boolean]
* * Any [Enum]
* * [String]
* * [Byte]
* * [Short]
* * [Int]
* * [Long]
* * [BigInteger]
* * [Float]
* * [Double]
* * [BigDecimal]
*
* New implementations can be added using
* [SqlI.resolve]
*/
object SqlI {
@PublishedApi
internal val adapters = mutableMapOf, ResultSet.(KProperty1<*, *>) -> Any>()
init {
resolve {
getString(it.name)[0]
}
resolve {
UUID.fromString(getString(it.name))
}
resolve {
getBoolean(it.name)
}
resolve> {
val value = getString(it.name)
checkNotNull((it.returnType.jvmErasure as Enum<*>).javaClass.enumConstants.find { it.name == value })
}
resolve {
getString(it.name)
}
resolve {
getByte(it.name)
}
resolve {
getShort(it.name)
}
resolve {
getInt(it.name)
}
resolve {
getLong(it.name)
}
resolve {
BigInteger(getString(it.name))
}
resolve {
getFloat(it.name)
}
resolve {
getDouble(it.name)
}
resolve {
getBigDecimal(it.name)
}
}
internal operator fun get(resultSet : ResultSet, property : KProperty1<*, T>) : T {
val type = property.returnType.jvmErasure
val adapter = adapters[type] ?: adapters[if (type.isSubclassOf(Enum::class)) Enum::class else Any::class]
return checkNotNull(adapter) { "No adapter for $type" }.invoke(resultSet, property) as T
}
/**
* Define how to resolve [T] from a [ResultSet]
*/
inline fun resolve(noinline block : ResultSet.(KProperty1<*, T>) -> T) {
adapters[T::class] = (block as ResultSet.(KProperty1<*, *>) -> Any)
}
}
/**
* Defines how to resolve [SqlType.Cache] from an object's properties
*
* Has default implementations for
* * [Char]
* * [UUID]
* * [Boolean]
* * Any [Enum]
* * [String]
* * [Byte]
* * [Short]
* * [Int]
* * [Long]
* * [BigInteger]
* * [Float]
* * [Double]
* * [BigDecimal]
* * If the object has no resolver, a [SqlVarChar] is used with its [Object.toString] result
*
* New implementations can be added using
* [SqlO.resolve]
*/
object SqlO {
@PublishedApi
internal val adapters = mutableMapOf, KProperty1<*, *>.() -> SqlType.Cache>()
init {
resolve {
SqlChar[1, isNotNull(), isPrimary()]
}
resolve {
SqlChar[36, isNotNull(), isPrimary()]
}
resolve {
SqlBoolean[isNotNull(), isPrimary()]
}
resolve> {
val clazz = returnType.jvmErasure as KClass>
SqlEnum[clazz, isNotNull(), isPrimary()]
}
resolve(Any::class, String::class) {
val fixed = findAnnotation()?.length
val type = when {
findAnnotation() != null -> SqlTinyText
findAnnotation() != null -> SqlText
findAnnotation() != null -> SqlMediumText
findAnnotation() != null -> SqlLongText
else -> null
}
if (type != null) {
return@resolve type.get(isNotNull(), isPrimary())
}
val sizedType = (if (fixed != null) SqlChar else SqlVarChar)
sizedType[(fixed ?: findAnnotation()?.length ?: 255).coerceIn(1, 255), isNotNull(), isPrimary()]
}
resolve {
val size = findAnnotation()?.length ?: 3
SqlTinyInt[size.coerceIn(1, 3), isNotNull(), isPrimary(), isUnsigned()]
}
resolve {
val size = findAnnotation()?.length ?: 5
SqlSmallInt[size.coerceIn(1, 5), isNotNull(), isPrimary(), isUnsigned()]
}
resolve {
val type = when {
findAnnotation() != null -> SqlTinyInt
findAnnotation() != null -> SqlSmallInt
findAnnotation() != null -> SqlMediumInt
findAnnotation() != null -> SqlBigInt
else -> SqlInt
}
val max = when(type) {
SqlTinyInt -> 3
SqlSmallInt -> 5
SqlMediumInt -> 7
SqlBigInt -> 19
else -> 10
}
val size = findAnnotation()?.length ?: max
type[size.coerceIn(1, max), isNotNull(), isPrimary(), isUnsigned()]
}
resolve(Long::class, BigInteger::class) {
val size = findAnnotation()?.length ?: 19
SqlBigInt[size.coerceIn(1, 19), isNotNull(), isPrimary(), isUnsigned()]
}
resolve {
val size = findAnnotation()
val length = (size?.length ?: 14)
val places = (size?.places ?: 7).coerceAtLeast(0)
SqlFloat[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
}
resolve {
val size = findAnnotation()
val length = (size?.length ?: 30)
val places = (size?.places ?: 15).coerceAtLeast(0)
SqlDouble[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
}
resolve {
val size = findAnnotation()
val length = (size?.length ?: 10).coerceIn(1, 65)
val places = (size?.places ?: 0).coerceIn(0, 30)
SqlDecimal[length.coerceAtLeast(places), places, isNotNull(), isPrimary(), isUnsigned()]
}
// start declared
resolveWith()
resolveWith()
resolve {
val size = findAnnotation()?.length ?: 7
SqlMediumInt[size.coerceIn(1, 7), isNotNull(), isPrimary()]
}
resolveWith()
resolveWith()
resolveWith()
resolveWith()
resolveWith()
resolve {
val size = findAnnotation()
SqlChar[size?.length ?: 1, isNotNull(), isPrimary()]
}
resolveWith()
resolve(SqlTinyText::class, SqlText::class, SqlMediumText::class, SqlLongText::class) {
(returnType.jvmErasure.objectInstance as SqlType)[isNotNull(), isPrimary()]
}
resolveWith()
resolve {
val values = requireNotNull(findAnnotation()) { "You must specify the set values" }
SqlSet[values.types, isNotNull(), isPrimary()]
}
resolve {
val clazz = requireNotNull(findAnnotation()) { "You must specify which enum this is for" }
SqlEnum[clazz.clazz, isNotNull(), isPrimary()]
}
}
internal operator fun get(property : KProperty1<*, *>) : SqlType.Cache {
val type = property.returnType.jvmErasure
val adapter = adapters[type] ?: adapters[if (type.isSubclassOf(Enum::class)) Enum::class else Any::class]
return checkNotNull(adapter) { "Impossible... but for type $type" }.invoke(property)
}
/**
* Resolve type [T] with [block]
*/
inline fun resolve(noinline block : KProperty1<*, *>.() -> SqlType.Cache) {
adapters[T::class] = block
}
/**
* Resolve many [types] using the same [block]
*/
fun resolve(vararg types : KClass<*>, block : KProperty1<*, *>.() -> SqlType.Cache) {
types.forEach { adapters[it] = block }
}
private inline fun resolveWith() {
adapters[T::class] = requireNotNull(adapters[O::class]) { "Oops, no adapter for ${O::class}" }
}
}
}