feature #8: Pagination for request

This commit is contained in:
2019-07-16 09:44:43 +02:00
parent ea4dac5d97
commit fda41deeb9
2 changed files with 114 additions and 21 deletions

View File

@@ -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 <R : EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <R : EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <R : List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <R : List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R
fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R?
fun <R: List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R?
fun <R: List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R
fun exec(sql: String, values: List<Any?> = emptyList()): CompletableFuture<QueryResult>
fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult>
}
@@ -41,7 +42,7 @@ class Connection(
fun <A> inTransaction(f: (Connection) -> CompletableFuture<A>) = connect().inTransaction(f)
override fun <R : EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?>): R? {
override fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?>): 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 <reified R : EntityI<*>> selectOne(sql: String, values: List<Any?> = emptyList()): R? = select(sql, object: TypeReference<R>() {}, values)
override fun <R : EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
val replacedQuery = replaceArgs(sql, values)
return select(replacedQuery.sql, typeReference, replacedQuery.parameters)
inline fun <reified R: EntityI<*>> selectOne(sql: String, values: List<Any?> = emptyList()): R? =
select(sql, object: TypeReference<R>() {}, values)
override fun <R: EntityI<*>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R? {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters)
}
}
inline fun <reified R : EntityI<*>> selectOne(sql: String, values: Map<String, Any?>): R? = select(sql, object: TypeReference<R>() {}, values)
override fun <R : List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?>): R {
inline fun <reified R: EntityI<*>> selectOne(sql: String, values: Map<String, Any?>): R? =
select(sql, object: TypeReference<R>() {}, values)
override fun <R: List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?>): 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 <reified R : List<EntityI<*>>> select(sql: String, values: List<Any?> = emptyList()): R = select(sql, object : TypeReference<R>() {}, values)
override fun <R : List<EntityI<*>>> select(sql: String, typeReference: TypeReference<R>, values: Map<String, Any?>): R {
val replacedQuery = replaceArgs(sql, values)
return select(replacedQuery.sql, typeReference, replacedQuery.parameters)
inline fun <reified R: List<EntityI<*>>> select(sql: String, values: List<Any?> = emptyList()): R =
select(sql, object: TypeReference<R>() {}, values)
fun <R: EntityI<*>> select(
sql: String,
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 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<EntityI<*>>() as List<R>
} else {
serializer.deserializeList(json, typeReference)
}
Paginated(
entities,
offset,
limit,
getInt("total") ?: error("The query not return total")
)
}
}
inline fun <reified R : List<EntityI<*>>> select(sql: String, values: Map<String, Any?>): R = select(sql, object : TypeReference<R>() {}, values)
inline fun <reified R: EntityI<*>> select(
sql: String,
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap()
): Paginated<R> =
select(sql, page, limit, object: TypeReference<List<R>>() {}, values)
override fun <R: List<EntityI<*>>> select(
sql: String,
typeReference: TypeReference<R>,
values: Map<String, Any?>
): R {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters)
}
}
inline fun <reified R: List<EntityI<*>>> select(sql: String, values: Map<String, Any?>): R =
select(sql, object: TypeReference<R>() {}, values)
override fun exec(sql: String, values: List<Any?>): CompletableFuture<QueryResult> {
return connect().sendPreparedStatement(sql, compileArgs(values))
}
override fun exec(sql: String, values: Map<String, Any?>): CompletableFuture<QueryResult> {
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<Any?>): List<Any?> {
@@ -96,7 +151,7 @@ class Connection(
}
}
private fun replaceArgs(sql: String, values: Map<String, Any?>): ParametersQuery {
private fun <T> replaceArgs(sql: String, values: Map<String, Any?>, block: ParametersQuery.() -> T): T {
val paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE)
val newArgs = paramRegex.findAll(sql).map { match ->
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<Any?>)
}
data class Paginated<T: EntityI<*>>(
val result: List<T>,
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")
}
}

View File

@@ -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<ObjTest> = 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)
}
}