feature #8: Pagination for request
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user