feature #8: Pagination for Query & Function

This commit is contained in:
2019-07-17 10:55:32 +02:00
parent 362a2a6617
commit 09c20fc385
6 changed files with 123 additions and 30 deletions

View File

@@ -17,6 +17,7 @@ interface Executable {
fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R? fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<List<R>>, values: List<Any?> = emptyList()): List<R> fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<List<R>>, values: List<Any?> = emptyList()): List<R>
fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R> fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R>
fun <R: EntityI<*>> select(sql: String, page: Int, limit: Int, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): Paginated<R>
fun exec(sql: String, values: List<Any?> = emptyList()): CompletableFuture<QueryResult> fun exec(sql: String, values: List<Any?> = emptyList()): CompletableFuture<QueryResult>
fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult> fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult>
} }
@@ -77,7 +78,7 @@ class Connection(
inline fun <reified R: EntityI<*>> select(sql: String, values: List<Any?> = emptyList()): List<R> = inline fun <reified R: EntityI<*>> select(sql: String, values: List<Any?> = emptyList()): List<R> =
select(sql, object: TypeReference<List<R>>() {}, values) select(sql, object: TypeReference<List<R>>() {}, values)
fun <R: EntityI<*>> select( override fun <R: EntityI<*>> select(
sql: String, sql: String,
page: Int, page: Int,
limit: Int, limit: Int,
@@ -108,6 +109,7 @@ class Connection(
) )
} }
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R: EntityI<*>> select(
sql: String, sql: String,
page: Int, page: Int,

View File

@@ -7,11 +7,11 @@ import java.io.File
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import fr.postgresjson.definition.Function as DefinitionFunction import fr.postgresjson.definition.Function as DefinitionFunction
class Requester ( class Requester(
private val connection: Connection, private val connection: Connection,
private val queries: MutableMap<String, Query> = mutableMapOf(), private val queries: MutableMap<String, Query> = mutableMapOf(),
private val functions: MutableMap<String, Function> = mutableMapOf()) private val functions: MutableMap<String, Function> = mutableMapOf()
{ ) {
fun addQuery(name: String, query: Query): Requester { fun addQuery(name: String, query: Query): Requester {
queries[name] = query queries[name] = query
return this return this
@@ -74,30 +74,40 @@ class Requester (
return queries[path]!! return queries[path]!!
} }
class Query(private val sql: String, override val connection : Connection): Executable { class Query(private val sql: String, override val connection: Connection): Executable {
override fun toString(): String { override fun toString(): String {
return sql return sql
} }
override fun <R : EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?>): R? { override fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
return connection.select(this.toString(), typeReference, values) return connection.select(this.toString(), typeReference, values)
} }
inline fun <reified R : EntityI<*>> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
inline fun <reified R: EntityI<*>> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
override fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? { override fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
return connection.select(this.toString(), typeReference, values) return connection.select(this.toString(), typeReference, values)
} }
inline fun <reified R : EntityI<*>> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
override fun <R : EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?>): List<R> { inline fun <reified R: EntityI<*>> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
override fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?>): List<R> {
return connection.select(this.toString(), typeReference, values) return connection.select(this.toString(), typeReference, values)
} }
inline fun <reified R : EntityI<*>> select(values: List<Any?> = emptyList()): List<R> = select(object: TypeReference<List<R>>() {}, values)
inline fun <reified R: EntityI<*>> select(values: List<Any?> = emptyList()): List<R> = select(object: TypeReference<List<R>>() {}, values)
override fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R> { override fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R> {
return connection.select(this.toString(), typeReference, values) return connection.select(this.toString(), typeReference, values)
} }
inline fun <reified R : EntityI<*>> select(values: Map<String, Any?>): List<R> = select(object: TypeReference<List<R>>() {}, values)
inline fun <reified R: EntityI<*>> select(values: Map<String, Any?>): List<R> = select(object: TypeReference<List<R>>() {}, values)
override fun <R: EntityI<*>> select(page: Int, limit: Int, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): Paginated<R> {
return connection.select(this.toString(), page, limit, typeReference, values)
}
inline fun <reified R: EntityI<*>> select(page: Int, limit: Int, values: Map<String, Any?> = emptyMap()): Paginated<R> =
select(page, limit, object: TypeReference<List<R>>() {}, values)
override fun exec(values: List<Any?>): CompletableFuture<QueryResult> { override fun exec(values: List<Any?>): CompletableFuture<QueryResult> {
return connection.exec(sql, values) return connection.exec(sql, values)
@@ -108,7 +118,7 @@ class Requester (
} }
} }
class Function(val definition: DefinitionFunction, override val connection : Connection): Executable { class Function(val definition: DefinitionFunction, override val connection: Connection): Executable {
override fun toString(): String { override fun toString(): String {
return definition.name return definition.name
} }
@@ -116,34 +126,37 @@ class Requester (
/** /**
* Select One entity with list of parameters * Select One entity with list of parameters
*/ */
override fun <R : EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?>): R? { override fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?>): R? {
val args = compileArgs(values) val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)" val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values) return connection.select(sql, typeReference, values)
} }
inline fun <reified R: EntityI<*>> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values) inline fun <reified R: EntityI<*>> selectOne(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
/** /**
* Select One entity with named parameters * Select One entity with named parameters
*/ */
override fun <R : EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? { override fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
val args = compileArgs(values) val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)" val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values) return connection.select(sql, typeReference, values)
} }
inline fun <reified R: EntityI<*>> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values) inline fun <reified R: EntityI<*>> selectOne(values: Map<String, Any?>): R? = select(object: TypeReference<R>() {}, values)
/** /**
* Select list of entities with list of parameters * Select list of entities with list of parameters
*/ */
override fun <R : EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?>): List<R> { override fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?>): List<R> {
val args = compileArgs(values) val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)" val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values) return connection.select(sql, typeReference, values)
} }
inline fun <reified R: EntityI<*>> select(values: List<Any?> = emptyList()): List<R> = select(object: TypeReference<List<R>>() {}, values) inline fun <reified R: EntityI<*>> select(values: List<Any?> = emptyList()): List<R> = select(object: TypeReference<List<R>>() {}, values)
/** /**
@@ -155,8 +168,23 @@ class Requester (
return connection.select(sql, typeReference, values) return connection.select(sql, typeReference, values)
} }
inline fun <reified R: EntityI<*>> select(values: Map<String, Any?>): List<R> = select(object: TypeReference<List<R>>() {}, values) inline fun <reified R: EntityI<*>> select(values: Map<String, Any?>): List<R> = select(object: TypeReference<List<R>>() {}, values)
override fun <R: EntityI<*>> select(page: Int, limit: Int, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): Paginated<R> {
val offset = (page - 1) * limit
val newValues = values
.plus("offset" to offset)
.plus("limit" to limit)
val args = compileArgs(newValues)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, page, limit, typeReference, values)
}
inline fun <reified R: EntityI<*>> select(page: Int, limit: Int, values: Map<String, Any?> = emptyMap()): Paginated<R> =
select(page, limit, object: TypeReference<List<R>>() {}, values)
override fun exec(values: List<Any?>): CompletableFuture<QueryResult> { override fun exec(values: List<Any?>): CompletableFuture<QueryResult> {
val args = compileArgs(values) val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)" val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -180,7 +208,7 @@ class Requester (
"?::" + definition.parameters[index].type "?::" + definition.parameters[index].type
} }
return placeholders.joinToString(separator=", ") return placeholders.joinToString(separator = ", ")
} }
private fun compileArgs(values: Map<String, Any?>): String { private fun compileArgs(values: Map<String, Any?>): String {
@@ -192,22 +220,24 @@ class Requester (
} }
.map { entry -> .map { entry ->
val parameter = parameters[entry.key]!! val parameter = parameters[entry.key]!!
"${parameter.name} := :${parameter.name}::" + parameter.type """"${parameter.name}" := :${parameter.name}::${parameter.type}"""
} }
return placeholders.joinToString(separator=", ") return placeholders.joinToString(separator = ", ")
} }
} }
interface Executable { interface Executable {
val connection : Connection val connection: Connection
override fun toString(): String override fun toString(): String
fun <R : EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R? fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <R : EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R? fun <R: EntityI<*>> select(typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <R : EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?> = emptyList()): List<R> fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: List<Any?> = emptyList()): List<R>
fun <R : EntityI<*>> select(typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R> fun <R: EntityI<*>> select(typeReference: TypeReference<List<R>>, values: Map<String, Any?>): List<R>
fun <R: EntityI<*>> select(page: Int, limit: Int, typeReference: TypeReference<List<R>>, values: Map<String, Any?>): Paginated<R>
fun exec(values: List<Any?> = emptyList()): CompletableFuture<QueryResult> fun exec(values: List<Any?> = emptyList()): CompletableFuture<QueryResult>
fun exec(values: Map<String, Any?>): CompletableFuture<QueryResult> fun exec(values: Map<String, Any?>): CompletableFuture<QueryResult>
@@ -221,18 +251,15 @@ class Requester (
private val password: String = "dc-project", private val password: String = "dc-project",
private val queriesDirectory: File? = null, private val queriesDirectory: File? = null,
private val functionsDirectory: File? = null private val functionsDirectory: File? = null
) ) {
{ fun createRequester(): Requester {
fun createRequester(): Requester
{
val con = Connection(host = host, port = port, database = database, username = username, password = password) val con = Connection(host = host, port = port, database = database, username = username, password = password)
val req = Requester(con) val req = Requester(con)
return initRequester(req) return initRequester(req)
} }
private fun initRequester(req: Requester): Requester private fun initRequester(req: Requester): Requester {
{
if (queriesDirectory === null) { if (queriesDirectory === null) {
val resource = this::class.java.getResource("/sql/query") val resource = this::class.java.getResource("/sql/query")
if (resource !== null) { if (resource !== null) {

View File

@@ -2,8 +2,10 @@ package fr.postgresjson
import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.QueryResult
import com.github.jasync.sql.db.util.isCompleted import com.github.jasync.sql.db.util.isCompleted
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.IdEntity import fr.postgresjson.entity.IdEntity
import org.junit.Assert
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -93,4 +95,32 @@ class RequesterTest: TestAbstract() {
assertEquals("myName", obj!![0].name) assertEquals("myName", obj!![0].name)
} }
@Test
fun `call select paginated on query`() {
val resources = File(this::class.java.getResource("/sql/query").toURI())
val result: Paginated<ObjTest> = Requester(getConnextion())
.addQuery(resources)
.getQuery("Test/selectPaginated")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff")
Assert.assertEquals(result.result[1].name, "ff-2")
Assert.assertEquals(result.total, 10)
Assert.assertEquals(result.offset, 0)
}
@Test
fun `call select paginated on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val result: Paginated<ObjTest> = Requester(getConnextion())
.addFunction(resources)
.getFunction("test_function_paginated")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff")
Assert.assertEquals(result.result[1].name, "ff-2")
Assert.assertEquals(result.total, 10)
Assert.assertEquals(result.offset, 0)
}
} }

View File

@@ -49,3 +49,18 @@ BEGIN
); );
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION test_function_paginated (name text default 'plop', IN "limit" int default 10, IN "offset" int default 0, out result json, out total int)
LANGUAGE plpgsql
AS
$$
BEGIN
SELECT json_build_array(
json_build_object('id', 3, 'name', name::text),
json_build_object('id', 4, 'name', name::text || '-2')
),
10
INTO result, total
LIMIT "limit" OFFSET "offset";
END;
$$

View File

@@ -0,0 +1,14 @@
CREATE OR REPLACE FUNCTION test_function_paginated (name text default 'plop', IN "limit" int default 10, IN "offset" int default 0, out result json, out total int)
LANGUAGE plpgsql
AS
$$
BEGIN
SELECT json_build_array(
json_build_object('id', 3, 'name', name::text),
json_build_object('id', 4, 'name', name::text || '-2')
),
10
INTO result, total
LIMIT "limit" OFFSET "offset";
END;
$$

View File

@@ -0,0 +1,5 @@
SELECT json_build_array(
json_build_object('id', 3, 'name', :name::text),
json_build_object('id', 4, 'name', :name::text || '-2')
), 10 as total
LIMIT :limit OFFSET :offset