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

@@ -10,8 +10,8 @@ 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
@@ -82,23 +82,33 @@ class Requester (
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) 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> { 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)
} }
@@ -122,6 +132,7 @@ class Requester (
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)
/** /**
@@ -133,6 +144,7 @@ class Requester (
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)
/** /**
@@ -144,6 +156,7 @@ class Requester (
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)"
@@ -192,7 +220,7 @@ 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 = ", ")
@@ -209,6 +237,8 @@ class Requester (
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