diff --git a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt index 17eb952..5100816 100644 --- a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt +++ b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt @@ -6,16 +6,17 @@ import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.pool.ConnectionPool import com.github.jasync.sql.db.postgresql.PostgreSQLConnection import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder +import com.github.jasync.sql.db.util.length import fr.postgresjson.entity.EntityI import fr.postgresjson.serializer.Serializer import java.util.concurrent.CompletableFuture interface Executable { - fun > select(sql: String, typeReference: TypeReference, values: List = emptyList()): R? - fun > select(sql: String, typeReference: TypeReference, values: Map): R? - fun >> select(sql: String, typeReference: TypeReference, values: List = emptyList()): R? - fun >> select(sql: String, typeReference: TypeReference, values: Map): R + fun > select(sql: String, typeReference: TypeReference, values: List = emptyList()): R? + fun > select(sql: String, typeReference: TypeReference, values: Map): R? + fun >> select(sql: String, typeReference: TypeReference, values: List = emptyList()): R? + fun >> select(sql: String, typeReference: TypeReference, values: Map): R fun exec(sql: String, values: List = emptyList()): CompletableFuture fun exec(sql: String, values: Map): CompletableFuture } @@ -41,7 +42,7 @@ class Connection( fun inTransaction(f: (Connection) -> CompletableFuture) = connect().inTransaction(f) - override fun > select(sql: String, typeReference: TypeReference, values: List): R? { + override fun > select(sql: String, typeReference: TypeReference, values: List): R? { val future = connect().sendPreparedStatement(sql, compileArgs(values)) val json = future.get().rows[0].getString(0) return if (json === null) { @@ -50,15 +51,20 @@ class Connection( serializer.deserialize(json, typeReference) } } - inline fun > selectOne(sql: String, values: List = emptyList()): R? = select(sql, object: TypeReference() {}, values) - override fun > select(sql: String, typeReference: TypeReference, values: Map): R? { - val replacedQuery = replaceArgs(sql, values) - return select(replacedQuery.sql, typeReference, replacedQuery.parameters) + inline fun > selectOne(sql: String, values: List = emptyList()): R? = + select(sql, object: TypeReference() {}, values) + + override fun > select(sql: String, typeReference: TypeReference, values: Map): R? { + return replaceArgs(sql, values) { + select(this.sql, typeReference, this.parameters) + } } - inline fun > selectOne(sql: String, values: Map): R? = select(sql, object: TypeReference() {}, values) - override fun >> select(sql: String, typeReference: TypeReference, values: List): R { + inline fun > selectOne(sql: String, values: Map): R? = + select(sql, object: TypeReference() {}, values) + + override fun >> select(sql: String, typeReference: TypeReference, values: List): R { val future = connect().sendPreparedStatement(sql, compileArgs(values)) val json = future.get().rows[0].getString(0) return if (json === null) { @@ -67,21 +73,70 @@ class Connection( serializer.deserializeList(json, typeReference) } } - inline fun >> select(sql: String, values: List = emptyList()): R = select(sql, object : TypeReference() {}, values) - override fun >> select(sql: String, typeReference: TypeReference, values: Map): R { - val replacedQuery = replaceArgs(sql, values) - return select(replacedQuery.sql, typeReference, replacedQuery.parameters) + inline fun >> select(sql: String, values: List = emptyList()): R = + select(sql, object: TypeReference() {}, values) + + fun > select( + sql: String, + 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 line = replaceArgs(sql, newValues) { + connect().sendPreparedStatement(this.sql, compileArgs(this.parameters)).get().rows[0] + } + + return line.run { + val json = getString(0) + val entities = if (json === null) { + listOf>() as List + } else { + serializer.deserializeList(json, typeReference) + } + Paginated( + entities, + offset, + limit, + getInt("total") ?: error("The query not return total") + ) + } } - inline fun >> select(sql: String, values: Map): R = select(sql, object : TypeReference() {}, values) + inline fun > select( + sql: String, + page: Int, + limit: Int, + values: Map = emptyMap() + ): Paginated = + select(sql, page, limit, object: TypeReference>() {}, values) + + override fun >> select( + sql: String, + typeReference: TypeReference, + values: Map + ): R { + return replaceArgs(sql, values) { + select(this.sql, typeReference, this.parameters) + } + } + + inline fun >> select(sql: String, values: Map): R = + select(sql, object: TypeReference() {}, values) override fun exec(sql: String, values: List): CompletableFuture { return connect().sendPreparedStatement(sql, compileArgs(values)) } override fun exec(sql: String, values: Map): CompletableFuture { - val replacedQuery = replaceArgs(sql, values) - return exec(replacedQuery.sql, replacedQuery.parameters) + return replaceArgs(sql, values) { + exec(this.sql, this.parameters) + } } private fun compileArgs(values: List): List { @@ -96,7 +151,7 @@ class Connection( } } - private fun replaceArgs(sql: String, values: Map): ParametersQuery { + private fun replaceArgs(sql: String, values: Map, block: ParametersQuery.() -> T): T { val paramRegex = "(? val name = match.groups[1]!!.value @@ -109,10 +164,25 @@ class Connection( newSql = newSql.replace(regex, "?") } - - return ParametersQuery(newSql, newArgs) + return block(ParametersQuery(newSql, newArgs)) } data class ParametersQuery(val sql: String, val parameters: List) } +data class Paginated>( + val result: List, + val offset: Int, + val limit: Int, + val total: Int +) { + val currentPage: Int = (offset / limit) + 1 + val count: Int = result.length + + init { + if (offset < 0) error("offset must be greather or equal than 0") + if (limit < 1) error("limit must be greather than 1") + if (total < 1) error("total must be greather or equal than 0") + } +} + diff --git a/src/test/kotlin/fr/postgresjson/ConnectionTest.kt b/src/test/kotlin/fr/postgresjson/ConnectionTest.kt index ca1caf5..8b708d7 100644 --- a/src/test/kotlin/fr/postgresjson/ConnectionTest.kt +++ b/src/test/kotlin/fr/postgresjson/ConnectionTest.kt @@ -2,6 +2,7 @@ package fr.postgresjson import com.github.jasync.sql.db.util.isCompleted import fr.postgresjson.connexion.Connection +import fr.postgresjson.connexion.Paginated import fr.postgresjson.entity.IdEntity import org.junit.Assert.* import org.junit.jupiter.api.BeforeEach @@ -129,4 +130,26 @@ class ConnectionTest(): TestAbstract() { assertEquals(result.seconde, "sec") assertEquals(result.third, 123) } + + @Test + fun `select paginated`() { + val result: Paginated = connection.select( + """ + 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 + """.trimIndent(), + 1, + 2, + mapOf("name" to "ff") + + ) + assertNotNull(result) + assertEquals(result.result[0].name, "ff") + assertEquals(result.result[1].name, "ff-2") + assertEquals(result.total, 10) + assertEquals(result.offset, 0) + } } \ No newline at end of file