feature #6: Implement named parameters

This commit is contained in:
2019-07-15 13:37:17 +02:00
parent b00f8cb5d0
commit e6b8e66b28
8 changed files with 263 additions and 17 deletions

View File

@@ -13,8 +13,11 @@ import java.util.concurrent.CompletableFuture
interface Executable {
fun <T, R : EntityI<T?>?> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <T, R : EntityI<T?>?> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R
fun exec(sql: String, values: List<Any?> = emptyList()): CompletableFuture<QueryResult>
fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult>
}
class Connection(
@@ -47,9 +50,21 @@ class Connection(
serializer.deserialize(json, typeReference)
}
}
inline fun <T, reified R : EntityI<T?>?> selectOne(sql: String, values: List<Any?> = emptyList()): R? = select(sql, object: TypeReference<R>() {}, values)
override fun <T, R : EntityI<T?>?> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
val args = compileArgs(values)
val replacedQuery = replaceArgs(sql, args)
val future = connect().sendPreparedStatement(replacedQuery.sql, replacedQuery.parameters)
val json = future.get().rows[0].getString(0)
return if (json === null) {
null
} else {
serializer.deserialize(json, typeReference)
}
}
inline fun <T, reified R : EntityI<T?>?> selectOne(sql: String, values: Map<String, Any?>): R? = select(sql, object: TypeReference<R>() {}, values)
override fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?>): R {
val future = connect().sendPreparedStatement(sql, compileArgs(values))
val json = future.get().rows[0].getString(0)
@@ -59,13 +74,30 @@ class Connection(
serializer.deserializeList(json, typeReference)
}
}
inline fun <T, reified R : List<EntityI<T?>?>> select(sql: String, values: List<Any?> = emptyList()): R = select(sql, object : TypeReference<R>() {}, values)
override fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R {
val args = compileArgs(values)
val replacedQuery = replaceArgs(sql, args)
val future = connect().sendPreparedStatement(replacedQuery.sql, replacedQuery.parameters)
val json = future.get().rows[0].getString(0)
return if (json === null) {
listOf<EntityI<T?>?>() as R
} else {
serializer.deserializeList(json, typeReference)
}
}
inline fun <T, reified R : List<EntityI<T?>?>> select(sql: String, values: Map<String, Any?>): R = select(sql, object : TypeReference<R>() {}, values)
override fun exec(sql: String, values: List<Any?>): CompletableFuture<QueryResult> {
return connect().sendPreparedStatement(sql, compileArgs(values))
}
override fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult> {
val replacedQuery = replaceArgs(sql, values)
return exec(replacedQuery.sql, replacedQuery.parameters)
}
private fun compileArgs(values: List<Any?>): List<Any?> {
return values.map {
if (it is EntityI<*>) {
@@ -77,5 +109,36 @@ class Connection(
}
}
}
private fun compileArgs(values: Map<String, Any?>): Map<String, Any?> {
return values.map {(key, value) ->
if (value is EntityI<*>) {
val json = serializer.serialize(value)
serializer.collection.set<Any?, EntityI<Any?>>(value as EntityI<Any?>)
key to json
} else {
key to value
}
}.toMap()
}
private fun replaceArgs(sql: String, values: Map<String, Any?>): ParametersQuery {
val paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE)
val newArgs = paramRegex.findAll(sql).map { match ->
val name = match.groups[1]!!.value
values[name] ?: error("Parameter $name missing")
}.toList()
var newSql = sql
values.forEach { (key, _) ->
val regex = ":$key".toRegex()
newSql = newSql.replace(regex, "?")
}
return ParametersQuery(newSql, newArgs)
}
data class ParametersQuery(val sql: String, val parameters: List<Any?>)
}

View File

@@ -82,18 +82,30 @@ class Requester (
override fun <T, R : EntityI<T?>?> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
return connection.select(this.toString(), typeReference, values)
}
inline fun <T, reified R : EntityI<T?>?> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
override fun <T, R: EntityI<T?>?> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
return connection.select(this.toString(), typeReference, values)
}
inline fun <T, reified R : EntityI<T?>?> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
override fun <T, R : List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
return connection.select(this.toString(), typeReference, values)
}
inline fun <T, reified R : List<EntityI<T?>?>> select(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
override fun <T, R: List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R {
return connection.select(this.toString(), typeReference, values)
}
inline fun <T, reified R : List<EntityI<T?>?>> select(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
override fun exec(values: List<Any?>): CompletableFuture<QueryResult> {
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): CompletableFuture<QueryResult> {
return connection.exec(sql, values)
}
}
class Function(val definition: DefinitionFunction, override val connection : Connection): Executable {
@@ -101,24 +113,50 @@ class Requester (
return definition.name
}
/**
* Select One entity with list of parameters
*/
override fun <T, R : EntityI<T?>?> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values)
}
inline fun <T, reified R: EntityI<T?>?> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
/**
* Select One entity with named parameters
*/
override fun <T, R : EntityI<T?>?> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values)
}
inline fun <T, reified R: EntityI<T?>?> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
/**
* Select list of entities with list of parameters
*/
override fun <T, R : List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values)
}
inline fun <T, reified R: List<EntityI<T?>?>> select(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
/**
* Select list of entities with named parameters
*/
override fun <T, R: List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values)
}
inline fun <T, reified R: List<EntityI<T?>?>> select(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
override fun exec(values: List<Any?>): CompletableFuture<QueryResult> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -126,6 +164,13 @@ class Requester (
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): CompletableFuture<QueryResult> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.exec(sql, values)
}
private fun compileArgs(values: List<Any?>): String {
val placeholders = values
.filterIndexed { index, any ->
@@ -137,6 +182,21 @@ class Requester (
return placeholders.joinToString(separator=", ")
}
private fun compileArgs(values: Map<String, Any?>): String {
val parameters = definition.getParametersIndexedByName()
val placeholders = values
.filter { entry ->
val parameter = parameters[entry.key] ?: error("Parameter ${entry.key} not exist")
parameter.default === null || entry.value !== null
}
.map { entry ->
val parameter = parameters[entry.key]!!
"${parameter.name} := :${parameter.name}::" + parameter.type
}
return placeholders.joinToString(separator=", ")
}
}
interface Executable {
@@ -144,10 +204,13 @@ class Requester (
override fun toString(): String
fun <T, R : EntityI<T?>?> select(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <T, R : EntityI<T?>?> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <T, R : List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <T, R : List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R
fun exec(values: List<Any?> = emptyList()): CompletableFuture<QueryResult>
fun exec(values: Map<String, Any?>): CompletableFuture<QueryResult>
}
class RequesterFactory(