feature #8: Pagination for Query & Function
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
$$
|
||||||
@@ -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;
|
||||||
|
$$
|
||||||
5
src/test/resources/sql/query/Test/selectPaginated.sql
Normal file
5
src/test/resources/sql/query/Test/selectPaginated.sql
Normal 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
|
||||||
Reference in New Issue
Block a user