diff --git a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt index a1f90df..07214e5 100644 --- a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt +++ b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt @@ -17,6 +17,7 @@ interface Executable { fun > select(sql: String, typeReference: TypeReference, values: Map): R? fun > select(sql: String, typeReference: TypeReference>, values: List = emptyList()): List fun > select(sql: String, typeReference: TypeReference>, values: Map): List + fun > select(sql: String, page: Int, limit: Int, typeReference: TypeReference>, values: Map): Paginated fun exec(sql: String, values: List = emptyList()): CompletableFuture fun exec(sql: String, values: Map): CompletableFuture } @@ -77,7 +78,7 @@ class Connection( inline fun > select(sql: String, values: List = emptyList()): List = select(sql, object: TypeReference>() {}, values) - fun > select( + override fun > select( sql: String, page: Int, limit: Int, @@ -108,6 +109,7 @@ class Connection( ) } } + inline fun > select( sql: String, page: Int, diff --git a/src/main/kotlin/fr/postgresjson/connexion/Requester.kt b/src/main/kotlin/fr/postgresjson/connexion/Requester.kt index ab3af37..0992f6c 100644 --- a/src/main/kotlin/fr/postgresjson/connexion/Requester.kt +++ b/src/main/kotlin/fr/postgresjson/connexion/Requester.kt @@ -7,11 +7,11 @@ import java.io.File import java.util.concurrent.CompletableFuture import fr.postgresjson.definition.Function as DefinitionFunction -class Requester ( +class Requester( private val connection: Connection, private val queries: MutableMap = mutableMapOf(), - private val functions: MutableMap = mutableMapOf()) -{ + private val functions: MutableMap = mutableMapOf() +) { fun addQuery(name: String, query: Query): Requester { queries[name] = query return this @@ -74,30 +74,40 @@ class Requester ( 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 { return sql } - override fun > select(typeReference: TypeReference, values: List): R? { + override fun > select(typeReference: TypeReference, values: List): R? { return connection.select(this.toString(), typeReference, values) } - inline fun > selectOne(values: List = emptyList()): R? = select(object: TypeReference() {}, values) + + inline fun > selectOne(values: List = emptyList()): R? = select(object: TypeReference() {}, values) override fun > select(typeReference: TypeReference, values: Map): R? { return connection.select(this.toString(), typeReference, values) } - inline fun > selectOne(values: Map): R? = select(object: TypeReference() {}, values) - override fun > select(typeReference: TypeReference>, values: List): List { + inline fun > selectOne(values: Map): R? = select(object: TypeReference() {}, values) + + override fun > select(typeReference: TypeReference>, values: List): List { return connection.select(this.toString(), typeReference, values) } - inline fun > select(values: List = emptyList()): List = select(object: TypeReference>() {}, values) + + inline fun > select(values: List = emptyList()): List = select(object: TypeReference>() {}, values) override fun > select(typeReference: TypeReference>, values: Map): List { return connection.select(this.toString(), typeReference, values) } - inline fun > select(values: Map): List = select(object: TypeReference>() {}, values) + + inline fun > select(values: Map): List = select(object: TypeReference>() {}, values) + + override fun > select(page: Int, limit: Int, typeReference: TypeReference>, values: Map): Paginated { + return connection.select(this.toString(), page, limit, typeReference, values) + } + inline fun > select(page: Int, limit: Int, values: Map = emptyMap()): Paginated = + select(page, limit, object: TypeReference>() {}, values) override fun exec(values: List): CompletableFuture { 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 { return definition.name } @@ -116,34 +126,37 @@ class Requester ( /** * Select One entity with list of parameters */ - override fun > select(typeReference: TypeReference, values: List): R? { + override fun > select(typeReference: TypeReference, values: List): R? { val args = compileArgs(values) val sql = "SELECT * FROM ${definition.name} ($args)" return connection.select(sql, typeReference, values) } + inline fun > selectOne(values: List = emptyList()): R? = select(object: TypeReference() {}, values) /** * Select One entity with named parameters */ - override fun > select(typeReference: TypeReference, values: Map): R? { + override fun > select(typeReference: TypeReference, values: Map): R? { val args = compileArgs(values) val sql = "SELECT * FROM ${definition.name} ($args)" return connection.select(sql, typeReference, values) } + inline fun > selectOne(values: Map): R? = select(object: TypeReference() {}, values) /** * Select list of entities with list of parameters */ - override fun > select(typeReference: TypeReference>, values: List): List { + override fun > select(typeReference: TypeReference>, values: List): List { val args = compileArgs(values) val sql = "SELECT * FROM ${definition.name} ($args)" return connection.select(sql, typeReference, values) } + inline fun > select(values: List = emptyList()): List = select(object: TypeReference>() {}, values) /** @@ -155,8 +168,23 @@ class Requester ( return connection.select(sql, typeReference, values) } + inline fun > select(values: Map): List = select(object: TypeReference>() {}, values) + override fun > select(page: Int, limit: Int, typeReference: TypeReference>, values: Map): Paginated { + 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 > select(page: Int, limit: Int, values: Map = emptyMap()): Paginated = + select(page, limit, object: TypeReference>() {}, values) + override fun exec(values: List): CompletableFuture { val args = compileArgs(values) val sql = "SELECT * FROM ${definition.name} ($args)" @@ -180,7 +208,7 @@ class Requester ( "?::" + definition.parameters[index].type } - return placeholders.joinToString(separator=", ") + return placeholders.joinToString(separator = ", ") } private fun compileArgs(values: Map): String { @@ -192,22 +220,24 @@ class Requester ( } .map { entry -> 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 { - val connection : Connection + val connection: Connection override fun toString(): String - fun > select(typeReference: TypeReference, values: List = emptyList()): R? - fun > select(typeReference: TypeReference, values: Map): R? + fun > select(typeReference: TypeReference, values: List = emptyList()): R? + fun > select(typeReference: TypeReference, values: Map): R? - fun > select(typeReference: TypeReference>, values: List = emptyList()): List - fun > select(typeReference: TypeReference>, values: Map): List + fun > select(typeReference: TypeReference>, values: List = emptyList()): List + fun > select(typeReference: TypeReference>, values: Map): List + + fun > select(page: Int, limit: Int, typeReference: TypeReference>, values: Map): Paginated fun exec(values: List = emptyList()): CompletableFuture fun exec(values: Map): CompletableFuture @@ -221,18 +251,15 @@ class Requester ( private val password: String = "dc-project", private val queriesDirectory: 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 req = Requester(con) return initRequester(req) } - private fun initRequester(req: Requester): Requester - { + private fun initRequester(req: Requester): Requester { if (queriesDirectory === null) { val resource = this::class.java.getResource("/sql/query") if (resource !== null) { diff --git a/src/test/kotlin/fr/postgresjson/RequesterTest.kt b/src/test/kotlin/fr/postgresjson/RequesterTest.kt index 61d28d9..60f88cb 100644 --- a/src/test/kotlin/fr/postgresjson/RequesterTest.kt +++ b/src/test/kotlin/fr/postgresjson/RequesterTest.kt @@ -2,8 +2,10 @@ package fr.postgresjson import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.util.isCompleted +import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester import fr.postgresjson.entity.IdEntity +import org.junit.Assert import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -93,4 +95,32 @@ class RequesterTest: TestAbstract() { 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 = 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 = 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) + } } \ No newline at end of file diff --git a/src/test/resources/fixtures/init.sql b/src/test/resources/fixtures/init.sql index ccd1dab..b8939a8 100644 --- a/src/test/resources/fixtures/init.sql +++ b/src/test/resources/fixtures/init.sql @@ -48,4 +48,19 @@ BEGIN json_build_object('id', 4, 'name', hi) ); END; -$$; \ No newline at end of file +$$; + +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; +$$ \ No newline at end of file diff --git a/src/test/resources/sql/function/Test/test_function_paginated.sql b/src/test/resources/sql/function/Test/test_function_paginated.sql new file mode 100644 index 0000000..68f8097 --- /dev/null +++ b/src/test/resources/sql/function/Test/test_function_paginated.sql @@ -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; +$$ \ No newline at end of file diff --git a/src/test/resources/sql/query/Test/selectPaginated.sql b/src/test/resources/sql/query/Test/selectPaginated.sql new file mode 100644 index 0000000..39e3eed --- /dev/null +++ b/src/test/resources/sql/query/Test/selectPaginated.sql @@ -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 \ No newline at end of file