Merge pull request #28 from flecomte/improve-tests

improve-tests and clean library
This commit was merged in pull request #28.
This commit is contained in:
2021-07-20 02:24:22 +02:00
committed by GitHub
33 changed files with 1317 additions and 596 deletions

View File

@@ -2,6 +2,12 @@
<configuration default="false" name="tests" type="JUnit" factoryName="JUnit" singleton="false"> <configuration default="false" name="tests" type="JUnit" factoryName="JUnit" singleton="false">
<module name="postgres-json.test" /> <module name="postgres-json.test" />
<useClassPathOnly /> <useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="fr.postgresjson.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<extension name="net.ashald.envfile"> <extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" /> <option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" /> <option name="IS_SUBST" value="false" />

View File

@@ -1,4 +1,6 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val containerAlwaysOn: String by project
val disableLint: String by project
plugins { plugins {
jacoco jacoco
@@ -9,7 +11,7 @@ plugins {
id("org.jlleitschuh.gradle.ktlint") version "10.0.0" id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
id("org.owasp.dependencycheck") version "6.1.1" id("org.owasp.dependencycheck") version "6.1.1"
id("fr.coppernic.versioning") version "3.2.1" id("fr.coppernic.versioning") version "3.2.1"
id("com.avast.gradle.docker-compose") version "0.14.0" id("com.avast.gradle.docker-compose") version "0.14.4"
id("org.sonarqube") version "+" id("org.sonarqube") version "+"
} }
@@ -42,7 +44,9 @@ tasks.test {
useJUnit() useJUnit()
useJUnitPlatform() useJUnitPlatform()
systemProperty("junit.jupiter.execution.parallel.enabled", true) systemProperty("junit.jupiter.execution.parallel.enabled", true)
finalizedBy(tasks.ktlintCheck) if (disableLint.toBoolean() == false) {
finalizedBy(tasks.ktlintCheck)
}
} }
tasks.jacocoTestReport { tasks.jacocoTestReport {
@@ -86,7 +90,7 @@ apply(plugin = "docker-compose")
dockerCompose { dockerCompose {
projectName = "postgres-json" projectName = "postgres-json"
useComposeFiles = listOf("docker-compose.yml") useComposeFiles = listOf("docker-compose.yml")
stopContainers = true stopContainers = !containerAlwaysOn.toBoolean()
isRequiredBy(project.tasks.test) isRequiredBy(project.tasks.test)
} }

View File

@@ -12,7 +12,7 @@ import fr.postgresjson.connexion.Connection
val connection: Connection = TODO() val connection: Connection = TODO()
val requester = Requester.RequesterFactory( val requester = Requester(
connection = connection, connection = connection,
functionsDirectory = this::class.java.getResource("/sql/functions")?.toURI() ?: error("No sql function found") functionsDirectory = this::class.java.getResource("/sql/functions")?.toURI() ?: error("No sql function found")
).createRequester() ).createRequester()
@@ -42,7 +42,7 @@ class UserForCreate(
): Serializable ): Serializable
``` ```
3. and, define Repositories 3. and, define Repositories
*[See SQL function](./migrations.md#Stored procedure migrations)* [See SQL function](../migrations/migrations.md)
```kotlin ```kotlin
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester

View File

@@ -6,4 +6,6 @@ systemProp.sonar.projectName=PostgresJson
systemProp.sonar.organization=flecomte systemProp.sonar.organization=flecomte
systemProp.sonar.java.coveragePlugin=jacoco systemProp.sonar.java.coveragePlugin=jacoco
systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml
org.gradle.jvmargs=-Xmx4096M org.gradle.jvmargs=-Xmx4096M
containerAlwaysOn=false
disableLint=false

View File

@@ -1,8 +1,9 @@
package fr.postgresjson.connexion package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.Connection
import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.QueryResult
import com.github.jasync.sql.db.ResultSet
import com.github.jasync.sql.db.general.ArrayRowData
import com.github.jasync.sql.db.pool.ConnectionPool import com.github.jasync.sql.db.pool.ConnectionPool
import com.github.jasync.sql.db.postgresql.PostgreSQLConnection import com.github.jasync.sql.db.postgresql.PostgreSQLConnection
import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder
@@ -12,7 +13,7 @@ import fr.postgresjson.entity.Serializable
import fr.postgresjson.serializer.Serializer import fr.postgresjson.serializer.Serializer
import fr.postgresjson.utils.LoggerDelegate import fr.postgresjson.utils.LoggerDelegate
import org.slf4j.Logger import org.slf4j.Logger
import java.util.concurrent.CompletableFuture import kotlin.random.Random
typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit
typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit
@@ -44,67 +45,65 @@ class Connection(
} }
fun disconnect() { fun disconnect() {
connection?.run { disconnect() } connection?.disconnect()
} }
fun <A> inTransaction(f: (Connection) -> CompletableFuture<A>) = connect().inTransaction(f) fun <A> inTransaction(block: Connection.() -> A?): A? = connect().run {
sendQuery("BEGIN")
try {
block().apply { sendQuery("COMMIT") }
} catch (e: Throwable) {
sendQuery("ROLLBACK")
throw e
}
}
override fun <R : EntityI> select( /**
* Select One [EntityI] with [List] of parameters
*/
override fun <R : EntityI> selectOne(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
): R? { ): R? {
val primaryObject = values.firstOrNull {
it is EntityI && typeReference.type.typeName == it::class.java.name
} as R?
val result = exec(sql, compileArgs(values)) val result = exec(sql, compileArgs(values))
val json = result.rows.firstOrNull()?.getString(0) val json = result.rows.firstOrNull()?.getString(0)
return if (json === null) { return if (json === null) {
null null
} else { } else {
if (primaryObject != null) { serializer.deserialize(json, typeReference)
serializer.deserialize(json, primaryObject)
} else {
serializer.deserialize(json, typeReference)
}
}.also { }.also {
block(result, it) block(result, it)
} }
} }
inline fun <reified R : EntityI> selectOne( /**
sql: String, * Select One [EntityI] with named parameters
values: List<Any?> = emptyList(), */
noinline block: SelectOneCallback<R> = {} override fun <R : EntityI> selectOne(
): R? =
select(sql, object : TypeReference<R>() {}, values, block)
override fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
): R? { ): R? {
return replaceArgs(sql, values) { return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block) selectOne(this.sql, typeReference, parameters, block)
} }
} }
inline fun <reified R : EntityI> selectOne( /* Select Multiples */
sql: String,
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
select(sql, object : TypeReference<R>() {}, values, block)
/**
* Select multiple [EntityI] with [List] of parameters
*/
override fun <R : EntityI> select( override fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, List<R>) -> Unit block: QueryResult.(List<R>) -> Unit
): List<R> { ): List<R> {
val result = exec(sql, compileArgs(values)) val result = exec(sql, values)
val json = result.rows[0].getString(0) val json = result.rows[0].getString(0)
return if (json === null) { return if (json === null) {
listOf<EntityI>() as List<R> listOf<EntityI>() as List<R>
@@ -115,20 +114,32 @@ class Connection(
} }
} }
inline fun <reified R : EntityI> select( /**
* Select multiple [EntityI] with [Map] of parameters
*/
override fun <R : EntityI> select(
sql: String, sql: String,
values: List<Any?> = emptyList(), typeReference: TypeReference<List<R>>,
noinline block: SelectCallback<R> = {} values: Map<String, Any?>,
): List<R> = block: QueryResult.(List<R>) -> Unit
select(sql, object : TypeReference<List<R>>() {}, values, block) ): List<R> {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block)
}
}
/* Select Paginated */
/**
* Select Multiple [EntityI] with pagination
*/
override fun <R : EntityI> select( override fun <R : EntityI> select(
sql: String, sql: String,
page: Int, page: Int,
limit: Int, limit: Int,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, Paginated<R>) -> Unit block: QueryResult.(Paginated<R>) -> Unit
): Paginated<R> { ): Paginated<R> {
val offset = (page - 1) * limit val offset = (page - 1) * limit
val newValues = values val newValues = values
@@ -140,8 +151,15 @@ class Connection(
} }
return line.run { return line.run {
val json = rows[0].getString(0) val firstLine = rows.firstOrNull() ?: queryError("The query has no return", sql, newValues)
val entities = if (json === null) { if (!(firstLine as ArrayRowData).mapping.keys.contains("total")) queryError("""The query not return the "total" column""", sql, newValues, rows)
val total = try {
firstLine.getInt("total") ?: queryError("The query return \"total\" must not be null", sql, newValues, rows)
} catch (e: ClassCastException) {
queryError("""Column "total" must be an integer""", sql, newValues, rows)
}
val json = firstLine.getString(0)
val entities = if (json == null) {
listOf<EntityI>() as List<R> listOf<EntityI>() as List<R>
} else { } else {
serializer.deserializeList(json, typeReference) serializer.deserializeList(json, typeReference)
@@ -150,44 +168,17 @@ class Connection(
entities, entities,
offset, offset,
limit, limit,
rows[0].getInt("total") ?: error("The query not return total") total
) )
}.also { }.also {
block(line, it) block(line, it)
} }
} }
inline fun <reified R : EntityI> select(
sql: String,
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap(),
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(sql, page, limit, object : TypeReference<List<R>>() {}, values, block)
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit
): List<R> {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block)
}
}
inline fun <reified R : EntityI> select(
sql: String,
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object : TypeReference<List<R>>() {}, values, block)
override fun exec(sql: String, values: List<Any?>): QueryResult { override fun exec(sql: String, values: List<Any?>): QueryResult {
val compiledValues = compileArgs(values) val compiledValues = compileArgs(values)
return stopwatchQuery(sql, compiledValues) { return stopwatchQuery(sql, compiledValues) {
connect().sendPreparedStatement(sql, compiledValues).join() connect().sendPreparedStatement(replaceNamedArgByQuestionMark(sql), compiledValues).join()
} }
} }
@@ -197,16 +188,22 @@ class Connection(
} }
} }
override fun sendQuery(sql: String, values: List<Any?>): Int { /**
* Warning: this method not use prepared statement
*/
override fun sendQuery(sql: String, values: List<Any?>): QueryResult {
val compiledValues = compileArgs(values) val compiledValues = compileArgs(values)
return stopwatchQuery(sql, compiledValues) { return stopwatchQuery(sql, compiledValues) {
replaceArgsIntoSql(sql, compiledValues) { replaceArgsIntoSql(sql, compiledValues) {
connect().sendQuery(it).join().rowsAffected.toInt() connect().sendQuery(it).join()
} }
} }
} }
override fun sendQuery(sql: String, values: Map<String, Any?>): Int { /**
* Warning: this method not use prepared statement
*/
override fun sendQuery(sql: String, values: Map<String, Any?>): QueryResult {
return replaceArgs(sql, values) { return replaceArgs(sql, values) {
sendQuery(this.sql, this.parameters) sendQuery(this.sql, this.parameters)
} }
@@ -224,35 +221,63 @@ class Connection(
private fun <T> replaceArgs(sql: String, values: Map<String, Any?>, block: ParametersQuery.() -> T): T { 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 paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE)
val newArgs = paramRegex.findAll(sql).map { match -> val orderedArgs = paramRegex.findAll(sql).map { match ->
val name = match.groups[1]!!.value val name = match.groups[1]!!.value
values[name] ?: values[name.trimStart('_')] ?: error("Parameter $name missing") values[name] ?: values[name.trimStart('_')] ?: queryError("""Parameter "$name" missing""", sql, values)
}.toList() }.toList()
var newSql = sql return block(ParametersQuery(replaceNamedArgByQuestionMark(sql), orderedArgs))
values.forEach { (key, _) -> }
val regex = ":_?$key".toRegex()
newSql = newSql.replace(regex, "?")
}
return block(ParametersQuery(newSql, newArgs)) private fun replaceNamedArgByQuestionMark(sql: String): String =
"(?<!:):([a-zA-Z0-9_-]+)"
.toRegex(RegexOption.IGNORE_CASE)
.replace(sql, "?")
private fun insertArgsValuesIntoSql(sql: String, values: List<Any?>): String {
var i = 0
/* The regular expression matches a question mark "?" alone, not preceded or followed by another question mark */
return """(?<!\?)(\?)(?!\?)"""
.toRegex(RegexOption.IGNORE_CASE)
.replace(sql) {
values.getOrNull(i)
?.toString()
?.also { ++i }
?.let(this::escapeParameter)
?: queryError("Parameter $i missing", sql, values)
}
} }
private fun <T> replaceArgsIntoSql(sql: String, values: List<Any?>, block: (String) -> T): T { private fun <T> replaceArgsIntoSql(sql: String, values: List<Any?>, block: (String) -> T): T {
val paramRegex = "(?<!\\?)(\\?)(?!\\?)".toRegex(RegexOption.IGNORE_CASE) return if (values.isNotEmpty()) {
var i = 0 sql
if (values.isNotEmpty()) { .let(this::replaceNamedArgByQuestionMark)
val newSql = paramRegex.replace(sql) { .let { insertArgsValuesIntoSql(it, values) }
values[i] ?: error("Parameter $i missing") .let(block)
val valToReplace = values[i].toString() } else block(sql)
++i }
"'$valToReplace'"
}
return block(newSql) /**
* Escape parameter by generate a random tag to prevent SQL injection
*/
private fun escapeParameter(parameter: String): String {
val escapeTag = escapeTag().let {
if (parameter.indexOf(it) >= 0) escapeParameter(parameter) else it
} }
return """$escapeTag$parameter$escapeTag"""
}
return block(sql) /**
* Generate a random alphaNum tag of 8 characters
*/
private fun escapeTag(): String {
val charPool: List<Char> = ('a'..'z') + ('A'..'Z')
val tagName = (1..8)
.map { _ -> Random.nextInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
return "\$$tagName\$"
} }
data class ParametersQuery(val sql: String, val parameters: List<Any?>) data class ParametersQuery(val sql: String, val parameters: List<Any?>)
@@ -291,4 +316,40 @@ class Connection(
throw e throw e
} }
} }
class QueryError(msg: String) : Exception(msg)
private fun queryError(
msg: String,
sql: String,
parameters: List<Any?>,
result: ResultSet? = null
): Nothing = throw QueryError(
"""
|$msg
|
|${parameters.joinToString(", ") { it.toString() }.prependIndent(" > ") ?: ""}
|${sql.prependIndent(" > ")}
|${result?.let { "-----" }?.prependIndent(" > ") ?: ""}
|${result?.columnNames()?.joinToString(" | ")?.prependIndent(" > ") ?: ""}
|${result?.map { it.joinToString(" | ") }?.joinToString("\n")?.prependIndent(" > ") ?: ""}
""".trimMargin().trim(' ', '\n')
)
private fun queryError(
msg: String,
sql: String,
parameters: Map<String, Any?>,
result: ResultSet? = null
): Nothing = throw QueryError(
"""
|$msg
|
|${parameters.map { ":" + it.key + " = " + it.value }.joinToString(", ").prependIndent(" > ") ?: ""}
|${sql.prependIndent(" > ")}
|${result?.let { "-----" }?.prependIndent(" > ") ?: ""}
|${result?.columnNames()?.joinToString(" | ")?.prependIndent(" > ") ?: ""}
|${result?.map { it.joinToString(" | ") }?.joinToString("\n")?.prependIndent(" > ") ?: ""}
""".trimMargin().trim(' ', '\n')
)
} }

View File

@@ -4,41 +4,86 @@ import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
interface EmbedExecutable { sealed interface EmbedExecutable {
val connection: Connection val connection: Connection
override fun toString(): String override fun toString(): String
val name: String val name: String
/* Select One */ /* Select One */
/** /**
* Select One entity with list of parameters * Update [EntityI] with one entity as argument
*/ */
fun <R : EntityI> select( fun <R : EntityI> update(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?> = emptyList(), value: R,
block: SelectOneCallback<R> = {}
): R? =
selectOne(typeReference, listOf(value), block)
/**
* Select One [EntityI] with [List] of parameters
*/
fun <R : EntityI> selectOne(
typeReference: TypeReference<R>,
values: List<Any?>,
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
fun <R : EntityI> select( /**
* Select One [EntityI] with [Map] of parameters
*/
fun <R : EntityI> selectOne(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
/**
* Select One [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> selectOne(
typeReference: TypeReference<R>,
vararg values: Pair<String, Any?>,
block: SelectOneCallback<R> = {}
): R? =
selectOne(typeReference, values.toMap(), block)
/* Select Multiples */ /* Select Multiples */
/**
* Select Multiple [EntityI] with [List] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(), values: List<Any?>,
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
/**
* Select Multiple [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
/**
* Select Multiple [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
vararg values: Pair<String, Any?>,
block: SelectCallback<R> = {}
): List<R> =
select(typeReference, values.toMap(), block)
/* Select Paginated */ /* Select Paginated */
/**
* Select Paginated [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
@@ -47,16 +92,19 @@ interface EmbedExecutable {
block: SelectPaginatedCallback<R> = {} block: SelectPaginatedCallback<R> = {}
): Paginated<R> ): Paginated<R>
fun exec(values: List<Any?> = emptyList()): QueryResult /**
* Select Paginated [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> select(
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
vararg values: Pair<String, Any?>,
block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(page, limit, typeReference, values.toMap(), block)
fun exec(values: List<Any?>): QueryResult
fun exec(values: Map<String, Any?>): QueryResult fun exec(values: Map<String, Any?>): QueryResult
fun exec(vararg values: Pair<String, Any?>): QueryResult = exec(values.toMap()) fun exec(vararg values: Pair<String, Any?>): QueryResult = exec(values.toMap())
fun perform(values: List<Any?>) { exec(values) }
fun perform(values: Map<String, Any?>) { exec(values) }
fun perform(vararg values: Pair<String, Any?>) = perform(values.toMap())
fun sendQuery(values: List<Any?> = emptyList()): Int
fun sendQuery(values: Map<String, Any?>): Int
fun sendQuery(vararg values: Pair<String, Any?>): Int =
sendQuery(values.toMap())
} }

View File

@@ -0,0 +1,68 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.entity.EntityI
/* Select One */
inline fun <reified R : EntityI> EmbedExecutable.update(
value: R,
noinline block: SelectOneCallback<R> = {}
): R? =
update(object : TypeReference<R>() {}, value, block)
inline fun <reified R : EntityI> EmbedExecutable.selectOne(
values: List<Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(object : TypeReference<R>() {}, values, block)
inline fun <reified R : EntityI> EmbedExecutable.selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(object : TypeReference<R>() {}, values, block)
inline fun <reified R : EntityI> EmbedExecutable.selectOne(
vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(object : TypeReference<R>() {}, values = values, block)
/* Select Multiples */
inline fun <reified R : EntityI> EmbedExecutable.select(
values: List<Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R : EntityI> EmbedExecutable.select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R : EntityI> EmbedExecutable.select(
vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(object : TypeReference<List<R>>() {}, values = values, block)
/* Select Paginated */
inline fun <reified R : EntityI> EmbedExecutable.select(
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap(),
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(page, limit, object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R : EntityI> EmbedExecutable.select(
page: Int,
limit: Int,
vararg values: Pair<String, Any?>,
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(page, limit, object : TypeReference<List<R>>() {}, values = values, block)

View File

@@ -5,24 +5,58 @@ import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
interface Executable { interface Executable {
/* Update */
/**
* Update [EntityI] with one entity as argument
*/
fun <R : EntityI> update(
sql: String,
typeReference: TypeReference<R>,
value: R,
block: SelectOneCallback<R> = {}
): R? =
selectOne(sql, typeReference, listOf(value), block)
/* Select One */ /* Select One */
fun <R : EntityI> select( /**
* Select One [EntityI] with [List] of parameters
*/
fun <R : EntityI> selectOne(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?> = emptyList(), values: List<Any?>,
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
fun <R : EntityI> select( /**
* Select One [EntityI] with [Map] of parameters
*/
fun <R : EntityI> selectOne(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
/* Select Miltiples */ /**
* Select One [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> selectOne(
sql: String,
typeReference: TypeReference<R>,
vararg values: Pair<String, Any?>,
block: SelectOneCallback<R> = {}
): R? =
selectOne(sql, typeReference, values.toMap(), block)
/* Select Multiples */
/**
* Select Multiple [EntityI] with [List] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
@@ -30,6 +64,9 @@ interface Executable {
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
/**
* Select Multiple [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
@@ -37,8 +74,22 @@ interface Executable {
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
/**
* Select Multiple [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
vararg values: Pair<String, Any?>,
block: SelectCallback<R> = {}
): List<R> =
select(sql, typeReference, values.toMap(), block)
/* Select Paginated */ /* Select Paginated */
/**
* Select Paginated [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select( fun <R : EntityI> select(
sql: String, sql: String,
page: Int, page: Int,
@@ -48,8 +99,38 @@ interface Executable {
block: SelectPaginatedCallback<R> = {} block: SelectPaginatedCallback<R> = {}
): Paginated<R> ): Paginated<R>
fun exec(sql: String, values: List<Any?> = emptyList()): QueryResult /**
* Select Paginated [EntityI] with multiple [Pair] of parameters
*/
fun <R : EntityI> select(
sql: String,
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
vararg values: Pair<String, Any?>,
block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(sql, page, limit, typeReference, values.toMap(), block)
fun <R : EntityI> exec(sql: String, value: R): QueryResult = exec(sql, listOf(value))
fun exec(sql: String, values: List<Any?>): QueryResult
fun exec(sql: String, values: Map<String, Any?>): QueryResult fun exec(sql: String, values: Map<String, Any?>): QueryResult
fun sendQuery(sql: String, values: List<Any?> = emptyList()): Int fun exec(sql: String, vararg values: Pair<String, Any?>): QueryResult = exec(sql, values.toMap())
fun sendQuery(sql: String, values: Map<String, Any?>): Int
/**
* Warning: this method not use prepared statement
*/
fun <R : EntityI> sendQuery(sql: String, value: R): QueryResult = sendQuery(sql, listOf(value))
/**
* Warning: this method not use prepared statement
*/
fun sendQuery(sql: String, values: List<Any?>): QueryResult
/**
* Warning: this method not use prepared statement
*/
fun sendQuery(sql: String, values: Map<String, Any?>): QueryResult
/**
* Warning: this method not use prepared statement
*/
fun sendQuery(sql: String, vararg values: Pair<String, Any?>): QueryResult = sendQuery(sql, values.toMap())
} }

View File

@@ -0,0 +1,106 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.entity.EntityI
/* Update */
/**
* Update [EntityI] with one entity as argument
*/
inline fun <reified R : EntityI> Executable.update(
sql: String,
value: R,
noinline block: SelectOneCallback<R> = {}
): R? =
update(sql, object : TypeReference<R>() {}, value, block)
/* Select One */
/**
* Select One [EntityI] with [List] of parameters
*/
inline fun <reified R : EntityI> Executable.selectOne(
sql: String,
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(sql, object : TypeReference<R>() {}, values, block)
/**
* Select One [EntityI] with [Map] of parameters
*/
inline fun <reified R : EntityI> Executable.selectOne(
sql: String,
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(sql, object : TypeReference<R>() {}, values, block)
/**
* Select One [EntityI] with multiple [Pair] of parameters
*/
inline fun <reified R : EntityI> Executable.selectOne(
sql: String,
vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(sql, object : TypeReference<R>() {}, values = values, block)
/* Select Multiples */
/**
* Select Multiple [EntityI] with [List] of parameters
*/
inline fun <reified R : EntityI> Executable.select(
sql: String,
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object : TypeReference<List<R>>() {}, values, block)
/**
* Select Multiple [EntityI] with [Map] of parameters
*/
inline fun <reified R : EntityI> Executable.select(
sql: String,
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object : TypeReference<List<R>>() {}, values, block)
/**
* Select Multiple [EntityI] with multiple [Pair] of parameters
*/
inline fun <reified R : EntityI> Executable.select(
sql: String,
vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object : TypeReference<List<R>>() {}, values = values, block)
/* Select Paginated */
/**
* Select Paginated [EntityI] with [Map] of parameters
*/
inline fun <reified R : EntityI> Executable.select(
sql: String,
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap(),
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(sql, page, limit, object : TypeReference<List<R>>() {}, values, block)
/**
* Select Paginated [EntityI] with multiple [Pair] of parameters
*/
inline fun <reified R : EntityI> Executable.select(
sql: String,
page: Int,
limit: Int,
vararg values: Pair<String, Any?>,
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(sql, page, limit, object : TypeReference<List<R>>() {}, values = values, block)

View File

@@ -15,109 +15,51 @@ class Function(val definition: Function, override val connection: Connection) :
/* Select One */ /* Select One */
/** /**
* Select One entity with list of parameters * Select One [EntityI] with [List] of parameters
*/ */
override fun <R : EntityI> select( override fun <R : EntityI> selectOne(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
select(object : TypeReference<R>() {}, values, block) connection.selectOne(compileSql(values), typeReference, values, block)
inline fun <reified R : EntityI> selectOne(
value: R,
noinline block: SelectOneCallback<R> = {}
): R? =
select(object : TypeReference<R>() {}, listOf(value), block)
/** /**
* Select One entity with named parameters * Select One [EntityI] with named parameters
*/ */
override fun <R : EntityI> select( override fun <R : EntityI> selectOne(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
select(object : TypeReference<R>() {}, values, block) connection.selectOne(compileSql(values), typeReference, values, block)
inline fun <reified R : EntityI> selectOne(
vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(values.toMap(), block)
/* Select Multiples */ /* Select Multiples */
/** /**
* Select list of entities with list of parameters * Select multiple [EntityI] with [List] of parameters
*/ */
override fun <R : EntityI> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
): List<R> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
select(object : TypeReference<List<R>>() {}, values, block) connection.select(compileSql(values), typeReference, values, block)
/** /**
* Select list of entities with named parameters * Select multiple [EntityI] with named parameters
*/ */
override fun <R : EntityI> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
): List<R> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
select(object : TypeReference<List<R>>() {}, values, block) connection.select(compileSql(values), typeReference, values, block)
inline fun <reified R : EntityI> select(
vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(values.toMap(), block)
/* Select Paginated */ /* Select Paginated */
/** /**
* Select Multiple with pagination * Select Multiple [EntityI] with pagination
*/ */
override fun <R : EntityI> select( override fun <R : EntityI> select(
page: Int, page: Int,
@@ -131,53 +73,16 @@ class Function(val definition: Function, override val connection: Connection) :
.plus("offset" to offset) .plus("offset" to offset)
.plus("limit" to limit) .plus("limit" to limit)
val args = compileArgs(newValues) return connection.select(compileSql(newValues), page, limit, typeReference, values, block)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, page, limit, typeReference, values, block)
} }
inline fun <reified R : EntityI> select( /* Execute function without treatments */
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap(),
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(page, limit, object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R : EntityI> select( override fun exec(values: List<Any?>): QueryResult = connection.exec(compileSql(values), values)
page: Int,
limit: Int,
vararg values: Pair<String, Any?>,
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> =
select(page, limit, object : TypeReference<List<R>>() {}, values.toMap(), block)
/* Execute function without traitements */ override fun exec(values: Map<String, Any?>): QueryResult = connection.exec(compileSql(values), values)
override fun exec(values: List<Any?>): QueryResult { private fun <R : EntityI> compileArgs(value: R): String = compileArgs(listOf(value))
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): QueryResult {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.exec(sql, values)
}
override fun sendQuery(values: List<Any?>): Int {
exec(values)
return 0
}
override fun sendQuery(values: Map<String, Any?>): Int {
exec(values)
return 0
}
private fun compileArgs(values: List<Any?>): String { private fun compileArgs(values: List<Any?>): String {
val placeholders = values val placeholders = values
@@ -205,4 +110,8 @@ class Function(val definition: Function, override val connection: Connection) :
return placeholders.joinToString(separator = ", ") return placeholders.joinToString(separator = ", ")
} }
private fun <R : EntityI> compileSql(value: R): String = "SELECT * FROM ${definition.name} (${compileArgs(value)})"
private fun compileSql(values: List<Any?>): String = "SELECT * FROM ${definition.name} (${compileArgs(values)})"
private fun compileSql(values: Map<String, Any?>): String = "SELECT * FROM ${definition.name} (${compileArgs(values)})"
} }

View File

@@ -0,0 +1,16 @@
package fr.postgresjson.connexion
import fr.postgresjson.utils.searchSqlFiles
import java.net.URI
import fr.postgresjson.definition.Function as DefinitionFunction
fun DefinitionFunction.toRunnable(connection: Connection): Function = Function(this, connection)
fun Sequence<DefinitionFunction>.toRunnable(connection: Connection): Sequence<Function> = map { it.toRunnable(connection) }
fun Sequence<Function>.toMutableMap(): MutableMap<String, Function> = map { it.name to it }.toMap().toMutableMap()
internal fun URI.toFunction(connection: Connection): MutableMap<String, Function> = searchSqlFiles()
.filterIsInstance(DefinitionFunction::class.java)
.toRunnable(connection)
.toMutableMap()

View File

@@ -11,99 +11,79 @@ class Query(override val name: String, private val sql: String, override val con
/* Select One */ /* Select One */
override fun <R : EntityI> select( /**
* Select One [EntityI] with [List] of parameters
*/
override fun <R : EntityI> selectOne(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: SelectOneCallback<R>
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
select(object : TypeReference<R>() {}, values, block) connection.selectOne(sql, typeReference, values, block)
override fun <R : EntityI> select( /**
* Select One [EntityI] with named parameters
*/
override fun <R : EntityI> selectOne(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit block: SelectOneCallback<R>
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
select(object : TypeReference<R>() {}, values, block) connection.selectOne(sql, typeReference, values, block)
/* Select Multiples */ /* Select Multiples */
/**
* Select multiple [EntityI] with [List] of parameters
*/
override fun <R : EntityI> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, List<R>) -> Unit block: SelectCallback<R>
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
select(object : TypeReference<List<R>>() {}, values, block) connection.select(sql, typeReference, values, block)
/**
* Select multiple [EntityI] with [Map] of parameters
*/
override fun <R : EntityI> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit block: SelectCallback<R>
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
select(object : TypeReference<List<R>>() {}, values, block) connection.select(sql, typeReference, values, block)
/* Select Paginated */
/**
* Select Multiple [EntityI] with pagination
*/
override fun <R : EntityI> select( override fun <R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, Paginated<R>) -> Unit block: (QueryResult, Paginated<R>) -> Unit
): Paginated<R> {
return connection.select(this.toString(), page, limit, typeReference, values, block)
}
/* Select Paginated */
inline fun <reified R : EntityI> select(
page: Int,
limit: Int,
values: Map<String, Any?> = emptyMap(),
noinline block: SelectPaginatedCallback<R> = {}
): Paginated<R> = ): Paginated<R> =
select(page, limit, object : TypeReference<List<R>>() {}, values, block) connection.select(sql, page, limit, typeReference, values, block)
/* Execute function without traitements */ /* Execute function without treatments */
override fun exec(values: List<Any?>): QueryResult { override fun exec(values: List<Any?>): QueryResult = connection.exec(sql, values)
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): QueryResult { override fun exec(values: Map<String, Any?>): QueryResult = connection.exec(sql, values)
return connection.exec(sql, values)
}
override fun sendQuery(values: List<Any?>): Int { /**
return connection.sendQuery(sql, values) * Warning: this method not use prepared statement
} */
fun sendQuery(values: List<Any?>): QueryResult = connection.sendQuery(sql, values)
override fun sendQuery(values: Map<String, Any?>): Int { /**
return connection.sendQuery(sql, values) * Warning: this method not use prepared statement
} */
fun sendQuery(values: Map<String, Any?>): QueryResult = connection.sendQuery(sql, values)
/**
* Warning: this method not use prepared statement
*/
fun sendQuery(vararg values: Pair<String, Any?>): QueryResult = sendQuery(values.toMap())
} }

View File

@@ -0,0 +1,16 @@
package fr.postgresjson.connexion
import fr.postgresjson.utils.searchSqlFiles
import java.net.URI
import fr.postgresjson.definition.Query as QueryDefinition
fun QueryDefinition.toRunnable(connection: Connection): Query = Query(name, script, connection)
fun Sequence<QueryDefinition>.toRunnable(connection: Connection): Sequence<Query> = map { it.toRunnable(connection) }
fun Sequence<Query>.toMutableMap(): MutableMap<String, Query> = map { it.name to it }.toMap().toMutableMap()
internal fun URI.toQuery(connection: Connection): MutableMap<String, Query> = searchSqlFiles()
.filterIsInstance(QueryDefinition::class.java)
.toRunnable(connection)
.toMutableMap()

View File

@@ -10,101 +10,59 @@ class Requester(
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(query: Query): Requester { constructor(connection: Connection) : this(connection, mutableMapOf(), mutableMapOf())
constructor(
connection: Connection,
queriesDirectory: URI? = null,
functionsDirectory: URI? = null
) : this(
connection = connection,
queries = queriesDirectory?.toQuery(connection) ?: mutableMapOf(),
functions = functionsDirectory?.toFunction(connection) ?: mutableMapOf(),
)
fun addQuery(query: Query) {
queries[query.name] = query queries[query.name] = query
return this
} }
fun addQuery(query: QueryDefinition): Requester = addQuery(query.name, query.script) fun addQuery(query: QueryDefinition) = addQuery(query.toRunnable(connection))
fun addQuery(name: String, sql: String): Requester { fun addQuery(name: String, sql: String) {
addQuery(Query(name, sql, connection)) addQuery(Query(name, sql, connection))
return this
} }
fun addQuery(queriesDirectory: URI): Requester { fun addQuery(queriesDirectory: URI) {
queriesDirectory.searchSqlFiles() queriesDirectory
.forEach { .searchSqlFiles()
if (it is QueryDefinition) { .filterIsInstance(QueryDefinition::class.java)
addQuery(it) .forEach(this::addQuery)
}
}
return this
} }
fun getQueries(): List<Query> { fun getQueries(): List<Query> = queries.map { it.value }
return queries.map { it.value }
fun addFunction(definition: DefinitionFunction) {
definition
.run { toRunnable(connection) }
.run { functions[name] = this }
} }
fun addFunction(definition: DefinitionFunction): Requester { fun addFunction(sql: String) {
functions[definition.name] = Function(definition, connection) DefinitionFunction(sql)
return this .run { toRunnable(connection) }
.run { functions[name] = this }
} }
fun addFunction(sql: String): Requester { fun addFunctions(functionsDirectory: URI) {
DefinitionFunction(sql).let {
functions[it.name] = Function(it, connection)
}
return this
}
fun addFunction(functionsDirectory: URI): Requester {
functionsDirectory.searchSqlFiles() functionsDirectory.searchSqlFiles()
.forEach { .filterIsInstance(DefinitionFunction::class.java)
if (it is DefinitionFunction) { .forEach(this::addFunction)
addFunction(it)
}
}
return this
} }
fun getFunction(name: String): Function { fun getFunction(name: String): Function = functions[name] ?: throw NoFunctionDefined(name)
if (functions[name] === null) {
throw Exception("No function defined for $name")
}
return functions[name]!!
}
fun getQuery(path: String): Query { fun getQuery(path: String): Query = queries[path] ?: throw NoQueryDefined(path)
if (queries[path] === null) {
throw Exception("No query defined in $path")
}
return queries[path]!!
}
class RequesterFactory( class NoFunctionDefined(name: String) : Exception("No function defined for $name")
private val connection: Connection, class NoQueryDefined(path: String) : Exception("No query defined in $path")
private val queriesDirectory: URI? = null,
private val functionsDirectory: URI? = null
) {
constructor(
host: String = "localhost",
port: Int = 5432,
database: String,
username: String,
password: String,
queriesDirectory: URI? = null,
functionsDirectory: URI? = null
) : this(
Connection(host = host, port = port, database = database, username = username, password = password),
queriesDirectory,
functionsDirectory
)
fun createRequester(): Requester {
return initRequester(Requester(connection))
}
private fun initRequester(req: Requester): Requester {
if (queriesDirectory !== null) {
req.addQuery(queriesDirectory)
}
if (functionsDirectory !== null) {
req.addFunction(functionsDirectory)
}
return req
}
}
} }

View File

@@ -1,11 +1,10 @@
package fr.postgresjson.definition package fr.postgresjson.definition
import java.io.File
import java.nio.file.Path import java.nio.file.Path
class Function( class Function(
override val script: String, override val script: String,
override var source: Path? = null override val source: Path? = null
) : Resource, ParametersInterface { ) : Resource, ParametersInterface {
val returns: String val returns: String
override val name: String override val name: String
@@ -70,21 +69,4 @@ class Function(
infix fun `is different from`(other: Function): Boolean { infix fun `is different from`(other: Function): Boolean {
return other.script != this.script return other.script != this.script
} }
companion object {
fun build(source: File): List<Function> {
return source.readText()
.split(
"CREATE +(OR REPLACE +)?(PROCEDURE|FUNCTION)".toRegex(
setOf(
RegexOption.IGNORE_CASE,
RegexOption.MULTILINE
)
)
)
.map {
Function("CREATE OR REPLACE FUNCTION $it")
}
}
}
} }

View File

@@ -4,14 +4,12 @@ import java.nio.file.Path
class Migration( class Migration(
override val script: String, override val script: String,
source: Path override var source: Path
) : Resource { ) : Resource {
override val name: String override val name: String
val direction: Direction val direction: Direction
override var source: Path? = null
init { init {
this.source = source
this.direction = source.fileName.toString() this.direction = source.fileName.toString()
.let { .let {
when { when {

View File

@@ -1,5 +1,7 @@
package fr.postgresjson.definition package fr.postgresjson.definition
import java.util.Locale
interface ParameterI { interface ParameterI {
val name: String val name: String
val type: String val type: String
@@ -21,7 +23,7 @@ class Parameter(val name: String, val type: String, direction: Direction? = Dire
constructor(name: String, type: String, direction: String? = "IN", default: Any? = null) : this( constructor(name: String, type: String, direction: String? = "IN", default: Any? = null) : this(
name = name, name = name,
type = type, type = type,
direction = direction?.let { Direction.valueOf(direction.toUpperCase()) }, direction = direction?.let { Direction.valueOf(direction.uppercase(Locale.getDefault())) },
default = default default = default
) )

View File

@@ -4,9 +4,8 @@ import java.nio.file.Path
class Query( class Query(
override val script: String, override val script: String,
source: Path override var source: Path
) : Resource { ) : Resource {
override var source: Path? = source
override val name: String = getNameFromComment(script) ?: getNameFromFile(source) override val name: String = getNameFromComment(script) ?: getNameFromFile(source)
/** Try to get name from comment in file */ /** Try to get name from comment in file */

View File

@@ -4,10 +4,10 @@ import java.io.File
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
interface Resource { sealed interface Resource {
val name: String val name: String
val script: String val script: String
var source: Path? val source: Path?
open class ParseException(message: String, cause: Throwable? = null) : Exception(message, cause) open class ParseException(message: String, cause: Throwable? = null) : Exception(message, cause)
@@ -34,7 +34,3 @@ interface Resource {
} }
} }
} }
interface ResourceCollection {
val parameters: List<Parameter>
}

View File

@@ -2,6 +2,7 @@ package fr.postgresjson.migration
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.migration.Migration.Action import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status import fr.postgresjson.migration.Migration.Status
import java.util.Date import java.util.Date
@@ -46,21 +47,25 @@ data class Function(
} }
} }
this::class.java.classLoader.getResource("sql/migration/insertFunction.sql")!!.readText().let { this::class.java.classLoader
connection.selectOne<MigrationEntity>(it, listOf(up.name, up.getDefinition(), up.script, down.script))?.let { function -> .getResource("sql/migration/insertFunction.sql")!!.readText()
.let { connection.selectOne<MigrationEntity>(it, listOf(up.name, up.getDefinition(), up.script, down.script)) }
?.let { function ->
executedAt = function.executedAt executedAt = function.executedAt
doExecute = Action.OK doExecute = Action.OK
} }
}
return Status.OK return Status.OK
} }
override fun down(): Status { override fun down(): Status {
connection.sendQuery(down.script) connection.sendQuery(down.script)
this::class.java.classLoader.getResource("sql/migration/deleteFunction.sql")!!.readText().let { this::class.java.classLoader
connection.sendQuery(it, listOf(down.name)) .getResource("sql/migration/deleteFunction.sql")!!
} .readText()
.let { connection.sendQuery(it, listOf(down.name)) }
return Status.OK return Status.OK
} }
@@ -68,29 +73,15 @@ data class Function(
connection.inTransaction { connection.inTransaction {
up() up()
down() down()
it.sendQuery("ROLLBACK") sendQuery("ROLLBACK")
}.join()
return Status.OK // TODO
}
override fun status(): Status {
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}.join()
return Status.OK // TODO
}
fun copy(): Function {
return this.copy(up = up, down = down, connection = connection, executedAt = executedAt).also {
it.doExecute = this.doExecute
} }
return Status.OK
} }
infix fun `is different from`(other: DefinitionFunction): Boolean { fun copy(): Function = this
return other.script != this.up.script .copy(up = up, down = down, connection = connection, executedAt = executedAt)
} .also { it.doExecute = this.doExecute }
infix fun `is different from`(other: DefinitionFunction): Boolean = other.script != this.up.script
} }

View File

@@ -1,6 +1,7 @@
package fr.postgresjson.migration package fr.postgresjson.migration
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.entity.Entity import fr.postgresjson.entity.Entity
import fr.postgresjson.migration.Migration.Action import fr.postgresjson.migration.Migration.Action
import java.util.Date import java.util.Date
@@ -41,20 +42,10 @@ data class MigrationScript(
connection.inTransaction { connection.inTransaction {
up() up()
down() down()
it.sendQuery("ROLLBACK") sendQuery("ROLLBACK")
}.join() }
return Migration.Status.OK // TODO return Migration.Status.OK
}
override fun status(): Migration.Status {
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}.join()
return Migration.Status.OK // TODO
} }
fun copy(): MigrationScript { fun copy(): MigrationScript {

View File

@@ -28,7 +28,6 @@ interface Migration {
fun up(): Status fun up(): Status
fun down(): Status fun down(): Status
fun test(): Status fun test(): Status
fun status(): Status
enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) }
enum class Action { OK, UP, DOWN } enum class Action { OK, UP, DOWN }

View File

@@ -3,7 +3,7 @@ package fr.postgresjson.serializer
import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.joda.JodaModule import com.fasterxml.jackson.datatype.joda.JodaModule
@@ -15,7 +15,7 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
init { init {
val module = SimpleModule() val module = SimpleModule()
mapper.registerModule(module) mapper.registerModule(module)
mapper.propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE mapper.propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
mapper.registerModule(JodaModule()) mapper.registerModule(JodaModule())
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
@@ -42,13 +42,10 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
inline fun <reified E> deserializeList(json: String): E { inline fun <reified E> deserializeList(json: String): E {
return deserializeList(json, object : TypeReference<E>() {}) return deserializeList(json, object : TypeReference<E>() {})
} }
fun <E> deserialize(json: String, target: E): E {
return mapper.readerForUpdating(target).readValue<E>(json)
}
} }
fun Serializable.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty) fun Serializable.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty)
fun List<Serializable>.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty) fun List<Serializable>.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty)
inline fun <reified E : Serializable> E.deserialize(json: String) = Serializer().deserialize(json, this)
inline fun <reified E : Serializable> String.deserialize() = Serializer().deserialize<E>(this) inline fun <reified E : Serializable> String.deserialize() = Serializer().deserialize<E>(this)
inline fun <reified T : Serializable> T.toTypeReference(): TypeReference<T> = object : TypeReference<T>() {}

View File

@@ -1,22 +1,31 @@
package fr.postgresjson package fr.postgresjson
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.connexion.Connection.QueryError
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.select
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.entity.Parameter import fr.postgresjson.entity.Parameter
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.toTypeReference
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.util.UUID import java.util.UUID
import kotlin.test.assertContains
import kotlin.test.assertNull
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ConnectionTest() : TestAbstract() { class ConnectionTest : TestAbstract() {
private class ObjTest(val name: String, id: UUID = UUID.fromString("2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00")) : UuidEntity(id) private class ObjTest(val name: String, id: UUID = UUID.fromString("2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00")) : UuidEntity(id)
private class ObjTest2(val title: String, var test: ObjTest?) : UuidEntity() private class ObjTest2(val title: String, var test: ObjTest?) : UuidEntity()
private class ObjTest3(val first: String, var seconde: String, var third: Int) : UuidEntity() private class ObjTest3(val first: String, var second: String, var third: Int) : UuidEntity()
private class ObjTestWithParameterObject(var first: ParameterObject, var seconde: ParameterObject) : UuidEntity() private class ObjTestWithParameterObject(var first: ParameterObject, var second: ParameterObject) : UuidEntity()
private class ParameterObject(var third: String) : Parameter private class ParameterObject(var third: String) : Parameter
@Test @Test
@@ -48,12 +57,33 @@ class ConnectionTest() : TestAbstract() {
} }
@Test @Test
fun callRequestWithArgs() { fun `test call request with args`() {
val result: ObjTest? = connection.selectOne("select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', ?::text)", listOf("myName")) val result: ObjTest? = connection.selectOne("select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', ?::text)", listOf("myName"))
assertNotNull(result) assertNotNull(result)
assertEquals("myName", result!!.name) assertEquals("myName", result!!.name)
} }
@Test
fun `test call request without args`() {
val result: ObjTest? = connection.selectOne("select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', 'myName')", object : TypeReference<ObjTest>() {}) {
assertEquals("myName", this.rows[0].getString(0)?.deserialize<ObjTest>()?.name)
}
assertNotNull(result)
assertEquals("myName", result!!.name)
}
@Test
fun `test call request return null`() {
val result: ObjTest? = connection.selectOne("select null;", object : TypeReference<ObjTest>() {})
assertNull(result)
}
@Test
fun `test call request return nothing`() {
val result: ObjTest? = connection.selectOne("select * from test where false;", object : TypeReference<ObjTest>() {})
assertNull(result)
}
@Test @Test
fun callRequestWithArgsEntity() { fun callRequestWithArgsEntity() {
val o = ObjTest("myName", id = UUID.fromString("2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00")) val o = ObjTest("myName", id = UUID.fromString("2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00"))
@@ -64,6 +94,15 @@ class ConnectionTest() : TestAbstract() {
assertEquals(obj.name, "myName") assertEquals(obj.name, "myName")
} }
@Test
fun `test update Entity`() {
val obj = ObjTest("before", id = UUID.fromString("1e5f5d41-6d14-4007-897b-0ed2616bec96"))
val objUpdated: ObjTest? = connection.update("select ?::jsonb || jsonb_build_object('name', 'after');", obj.toTypeReference(), obj)
assertTrue(objUpdated is ObjTest)
assertTrue(objUpdated!!.id == UUID.fromString("1e5f5d41-6d14-4007-897b-0ed2616bec96"))
assertTrue(objUpdated.name == "after")
}
@Test @Test
fun callExec() { fun callExec() {
val o = ObjTest("myName") val o = ObjTest("myName")
@@ -74,69 +113,67 @@ class ConnectionTest() : TestAbstract() {
@Test @Test
fun `select one with named parameters`() { fun `select one with named parameters`() {
val result: ObjTest3? = connection.selectOne( val result: ObjTest3? = connection.selectOne(
"SELECT json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int)", "SELECT json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int)",
mapOf( mapOf(
"first" to "ff", "first" to "ff",
"seconde" to "sec", "second" to "sec",
"third" to 123 "third" to 123
) )
) )
assertEquals(result!!.first, "ff") assertEquals(result!!.first, "ff")
assertEquals(result.seconde, "sec") assertEquals(result.second, "sec")
assertEquals(result.third, 123) assertEquals(result.third, 123)
} }
@Test @Test
fun `select one with named parameters object`() { fun `select one with named parameters object`() {
val result: ObjTestWithParameterObject? = connection.selectOne( val result: ObjTestWithParameterObject? = connection.selectOne(
"SELECT json_build_object('first', :first::json, 'seconde', :seconde::json)", "SELECT json_build_object('first', :first::json, 'second', :second::json)",
mapOf( mapOf(
"first" to ParameterObject("one"), "first" to ParameterObject("one"),
"seconde" to ParameterObject("two") "second" to ParameterObject("two")
) )
) )
assertEquals("one", result!!.first.third) assertEquals("one", result!!.first.third)
assertEquals("two", result.seconde.third) assertEquals("two", result.second.third)
} }
@Test @Test
fun `select with named parameters`() { fun `select with named parameters`() {
val params: Map<String, Any?> = mapOf(
"first" to "ff",
"third" to 123,
"seconde" to "sec"
)
val result: List<ObjTest3> = connection.select( val result: List<ObjTest3> = connection.select(
""" """
SELECT json_build_array( SELECT json_build_array(
json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int), json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int),
json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int) json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int)
) )
""".trimIndent(), """.trimIndent(),
params mapOf(
"first" to "ff",
"third" to 123,
"second" to "sec"
)
) )
assertEquals(result[0].first, "ff") assertEquals(result[0].first, "ff")
assertEquals(result[0].seconde, "sec") assertEquals(result[0].second, "sec")
assertEquals(result[0].third, 123) assertEquals(result[0].third, 123)
} }
@Test @Test
fun `selectOne with named parameters`() { fun `select with named parameters as vararg of Pair`() {
val params: Map<String, Any?> = mapOf( val result: List<ObjTest3> = connection.select(
"""
SELECT json_build_array(
json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int),
json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int)
)
""".trimIndent(),
"first" to "ff", "first" to "ff",
"third" to 123, "third" to 123,
"seconde" to "sec" "second" to "sec"
) )
val result: ObjTest3? = connection.selectOne( assertEquals(result[0].first, "ff")
""" assertEquals(result[0].second, "sec")
SELECT json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int) assertEquals(result[0].third, 123)
""".trimIndent(),
params
)
assertNotNull(result)
assertEquals(result!!.first, "ff")
assertEquals(result.seconde, "sec")
assertEquals(result.third, 123)
} }
@Test @Test
@@ -161,16 +198,141 @@ class ConnectionTest() : TestAbstract() {
assertEquals(result.offset, 0) assertEquals(result.offset, 0)
} }
@Test
fun `test select paginated without result`() {
val result: Paginated<ObjTest> = connection.select(
"""
SELECT null,
10 as total
LIMIT :limit
OFFSET :offset
""".trimIndent(),
1,
2,
object : TypeReference<List<ObjTest>>() {}
)
assertNotNull(result)
assertTrue(result.result.isEmpty())
assertEquals(0, result.result.size)
assertEquals(result.total, 10)
assertEquals(result.offset, 0)
}
@Test
fun `test select paginated`() {
val result: Paginated<ObjTest> = connection.select(
"""
SELECT json_build_array(
jsonb_build_object(
'name', :name::text,
'id', 'e9f9a0f0-237c-47cf-98c5-be353f2f2ce3'
)
),
10 as total
LIMIT :limit
OFFSET :offset
""".trimIndent(),
1,
2,
object : TypeReference<List<ObjTest>>() {},
mapOf(
"name" to "myName"
)
)
assertNotNull(result)
assertEquals("myName", result.result[0].name)
assertEquals(1, result.result.size)
assertEquals(result.total, 10)
assertEquals(result.offset, 0)
}
@Test
fun `test select paginated with no result`() {
assertThrows<QueryError> {
connection.select(
"""
SELECT :name as name,
10 as total
LIMIT :limit
OFFSET :offset
""".trimIndent(),
100,
10,
object : TypeReference<List<ObjTest>>() {},
mapOf(
"name" to "myName"
)
)
}.run {
assertNotNull(message)
assertContains(message!!, "The query has no return")
}
}
@Test
fun `test select paginated with total was not integer`() {
assertThrows<QueryError> {
connection.select(
"""
SELECT :name as name,
'plop' as total
LIMIT :limit
OFFSET :offset
""".trimIndent(),
1,
10,
object : TypeReference<List<ObjTest>>() {},
mapOf(
"name" to "myName"
)
)
}.run {
assertNotNull(message)
assertContains(message!!, """Column "total" must be an integer""")
}
}
@Test
fun `test select paginated without total`() {
val exception = assertThrows<QueryError> {
val result: Paginated<ObjTest> = connection.select(
"""
SELECT null
LIMIT :limit
OFFSET :offset
""".trimIndent(),
1,
2,
object : TypeReference<List<ObjTest>>() {}
)
}
assertEquals(
"""
The query not return the "total" column
> :offset = 0, :limit = 2
> SELECT null
> LIMIT :limit
> OFFSET :offset
> -----
> ?column?
> null
""".trimIndent(),
exception.message
)
}
@Test @Test
fun `selectOne with extra parameters`() { fun `selectOne with extra parameters`() {
val params: Map<String, Any?> = mapOf( val params: Map<String, Any?> = mapOf(
"first" to "ff", "first" to "ff",
"third" to 123, "third" to 123,
"seconde" to "sec" "second" to "sec"
) )
val result: ObjTest3? = connection.selectOne( val result: ObjTest3? = connection.selectOne(
""" """
SELECT json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int), 'plop'::text as other SELECT json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int), 'plop'::text as other
""".trimIndent(), """.trimIndent(),
params params
) { ) {
@@ -179,7 +341,39 @@ class ConnectionTest() : TestAbstract() {
} }
assertNotNull(result) assertNotNull(result)
assertEquals("ff", result!!.first) assertEquals("ff", result!!.first)
assertEquals("sec", result.seconde) assertEquals("sec", result.second)
assertEquals(123, result.third) assertEquals(123, result.third)
} }
@Test
fun `test exec without parameters`() {
connection.exec("select 42, 'hello';").run {
assertEquals(42, rows[0].getInt(0))
assertEquals("hello", rows[0].getString(1))
}
}
@Test
fun `test exec with one object as parameter`() {
val obj = ObjTest("myName", UUID.fromString("c606e216-53b3-43c8-a900-e727cb4a017c"))
connection.exec("select ?::jsonb->>'name'", obj).run {
assertEquals("myName", rows[0].getString(0))
}
}
@Test
fun `select one in transaction`() {
connection.inTransaction {
selectOne<ObjTestWithParameterObject>(
"SELECT json_build_object('first', :first::json, 'second', :second::json)",
mapOf(
"first" to ParameterObject("one"),
"second" to ParameterObject("two")
)
).let { result ->
assertEquals("one", result!!.first.third)
assertEquals("two", result.second.third)
}
}
}
} }

View File

@@ -16,11 +16,11 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class EntityTest() { class EntityTest() {
private class User(id: UUID = UUID.randomUUID()) : Entity<UUID>(id) private class User(id: UUID = UUID.randomUUID()) : Entity<UUID>(id)
private class ObjTest(var name: String) : UuidEntityExtended<Int?, User>(User(), User()) private class ObjTest(val name: String) : UuidEntityExtended<Int?, User>(User(), User())
@Test @Test
fun getObject() { fun getObject() {
val obj: ObjTest? = ObjTest("plop") val obj = ObjTest("plop")
assertTrue(obj is ObjTest) assertTrue(obj is ObjTest)
assertTrue(obj is UuidEntityExtended<Int?, User>) assertTrue(obj is UuidEntityExtended<Int?, User>)
assertTrue(obj is EntityI) assertTrue(obj is EntityI)

View File

@@ -1,6 +1,7 @@
package fr.postgresjson package fr.postgresjson
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.migration.Migration import fr.postgresjson.migration.Migration
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should be equal to`
@@ -13,10 +14,10 @@ import org.junit.jupiter.api.TestInstance
import java.util.UUID import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MigrationTest() : TestAbstract() { class MigrationTest : TestAbstract() {
@Test @Test
fun `run up query`() { fun `run up query`() {
val resources = this::class.java.getResource("/sql/migrations").toURI() val resources = this::class.java.getResource("/sql/migrations")!!.toURI()
val m = Migrations(connection, resources) val m = Migrations(connection, resources)
m.up().apply { m.up().apply {
this `should contain` Pair("1", Migration.Status.OK) this `should contain` Pair("1", Migration.Status.OK)
@@ -28,7 +29,7 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `migration up Query should throw error if no down`() { fun `migration up Query should throw error if no down`() {
val resources = this::class.java.getResource("/sql/migration_without_down").toURI() val resources = this::class.java.getResource("/sql/migration_without_down")!!.toURI()
invoking { invoking {
Migrations(resources, connection) Migrations(resources, connection)
} shouldThrow Migrations.DownMigrationNotDefined::class } shouldThrow Migrations.DownMigrationNotDefined::class
@@ -36,7 +37,7 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run forced down query`() { fun `run forced down query`() {
val resources = this::class.java.getResource("/sql/migrations").toURI() val resources = this::class.java.getResource("/sql/migrations")!!.toURI()
val m = Migrations(resources, connection) val m = Migrations(resources, connection)
repeat(3) { repeat(3) {
m.down(true).apply { m.down(true).apply {
@@ -48,7 +49,7 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run dry migrations`() { fun `run dry migrations`() {
val resources = this::class.java.getResource("/sql/real_migrations").toURI() val resources = this::class.java.getResource("/sql/real_migrations")!!.toURI()
Migrations(resources, connection).apply { Migrations(resources, connection).apply {
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
} }
@@ -59,7 +60,7 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run dry migrations launch twice`() { fun `run dry migrations launch twice`() {
val resources = this::class.java.getResource("/sql/real_migrations").toURI() val resources = this::class.java.getResource("/sql/real_migrations")!!.toURI()
Migrations(resources, connection).apply { Migrations(resources, connection).apply {
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
@@ -68,7 +69,7 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run migrations`() { fun `run migrations`() {
val resources = this::class.java.getResource("/sql/real_migrations").toURI() val resources = this::class.java.getResource("/sql/real_migrations")!!.toURI()
Migrations(resources, connection).apply { Migrations(resources, connection).apply {
run().apply { run().apply {
size `should be equal to` 1 size `should be equal to` 1
@@ -78,8 +79,8 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run migrations force down`() { fun `run migrations force down`() {
val resources = this::class.java.getResource("/sql/real_migrations").toURI() val resources = this::class.java.getResource("/sql/real_migrations")!!.toURI()
val resourcesFunctions = this::class.java.getResource("/sql/function/Test").toURI() val resourcesFunctions = this::class.java.getResource("/sql/function/Test")!!.toURI()
Migrations(listOf(resources, resourcesFunctions), connection).apply { Migrations(listOf(resources, resourcesFunctions), connection).apply {
up().apply { up().apply {
size `should be equal to` 6 size `should be equal to` 6
@@ -94,13 +95,12 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run functions migrations`() { fun `run functions migrations`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")!!.toURI()
Migrations(resources, connection).apply { Migrations(resources, connection).apply {
run().size `should be equal to` 5 run().size `should be equal to` 5
} }
val objTest: RequesterTest.ObjTest? = Requester(connection) val objTest: RequesterTest.ObjTest? = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(listOf("test", "plip")) .selectOne(listOf("test", "plip"))
@@ -110,20 +110,19 @@ class MigrationTest() : TestAbstract() {
@Test @Test
fun `run functions migrations and drop if exist`() { fun `run functions migrations and drop if exist`() {
val resources = this::class.java.getResource("/sql/function/Test1").toURI() val resources = this::class.java.getResource("/sql/function/Test1")!!.toURI()
Migrations(resources, connection).apply { Migrations(resources, connection).apply {
run().size `should be equal to` 1 run().size `should be equal to` 1
} }
val objTest: RequesterTest.ObjTest? = Requester(connection) val objTest: RequesterTest.ObjTest? = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function_duplicate") .getFunction("test_function_duplicate")
.selectOne(listOf("test")) .selectOne(listOf("test"))
Assertions.assertEquals(objTest!!.id, UUID.fromString("457daad5-4f1b-4eb7-80ec-6882adb8cc7d")) Assertions.assertEquals(objTest!!.id, UUID.fromString("457daad5-4f1b-4eb7-80ec-6882adb8cc7d"))
Assertions.assertEquals(objTest.name, "test") Assertions.assertEquals(objTest.name, "test")
val resources2 = this::class.java.getResource("/sql/function/Test2").toURI() val resources2 = this::class.java.getResource("/sql/function/Test2")!!.toURI()
Migrations(resources2, connection).apply { Migrations(resources2, connection).apply {
run().size `should be equal to` 1 run().size `should be equal to` 1
} }

View File

@@ -1,21 +1,102 @@
package fr.postgresjson package fr.postgresjson
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.connexion.Connection.QueryError
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.connexion.Requester.NoFunctionDefined
import fr.postgresjson.connexion.Requester.NoQueryDefined
import fr.postgresjson.connexion.select
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.connexion.update
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import org.junit.Assert import fr.postgresjson.serializer.deserialize
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.util.UUID import java.util.UUID
import kotlin.test.assertNotNull
class RequesterTest : TestAbstract() { class RequesterTest : TestAbstract() {
class ObjTest(var name: String, id: UUID = UUID.fromString("5623d902-3067-42f3-bfd9-095dbb12c29f")) : UuidEntity(id) class ObjTest(val name: String, id: UUID = UUID.fromString("5623d902-3067-42f3-bfd9-095dbb12c29f")) : UuidEntity(id)
@Test
fun `requester constructor empty`() {
val resources = this::class.java.getResource("/sql/function/Test")!!.toURI()
val name: String = Requester(connection)
.apply { addFunctions(resources) }
.getFunction("test_function")
.name
assertEquals("test_function", name)
}
@Test
fun `requester constructor function directory`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val name: String = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.name
assertEquals("test_function", name)
}
@Test
fun `requester constructor query directory`() {
val resources = this::class.java.getResource("/sql/query/Test")?.toURI()
val name: String = Requester(connection, queriesDirectory = resources)
.getQuery("DeleteTest")
.name
assertEquals("DeleteTest", name)
}
@Test
fun `function toString`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val name: String = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.toString()
assertEquals("test_function", name)
}
@Test
fun `add function as string`() {
val sql = """
CREATE OR REPLACE FUNCTION test_function (name text default 'plop', IN hi text default 'hello', out result json)
LANGUAGE plpgsql
AS
$$
BEGIN
result = json_build_object('id', '457daad5-4f1b-4eb7-80ec-6882adb8cc7d', 'name', name);
END;
$$
""".trimIndent()
val name: String = Requester(connection)
.apply { addFunction(sql) }
.getFunction("test_function")
.name
assertEquals("test_function", name)
}
@Test
fun `add query from string`() {
val result: Int = Requester(connection)
.apply { addQuery("simpleTest", "select 42;") }
.getQuery("simpleTest")
.exec()
.rows[0].getInt(0)!!
assertEquals(result, 42)
}
@Test @Test
fun `get query from file`() { fun `get query from file`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")!!.toURI()
val objTest: ObjTest? = Requester(connection) val objTest: ObjTest? = Requester(connection)
.addQuery(resources) .apply { addQuery(resources) }
.getQuery("selectOne") .getQuery("selectOne")
.selectOne() .selectOne()
@@ -23,11 +104,37 @@ class RequesterTest : TestAbstract() {
assertEquals(objTest.name, "test") assertEquals(objTest.name, "test")
} }
@Test
fun `get query from file with wrong name throw exception`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
assertThrows(NoQueryDefined::class.java) {
Requester(connection, queriesDirectory = resources)
.getQuery("wrongName")
}
}
@Test
fun `get queries from file`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
val name: String = Requester(connection, queriesDirectory = resources)
.getQueries()[0].name
assertEquals(name, "DeleteTest")
}
@Test
fun `get function from file with wrong name throw exception`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
assertThrows(NoFunctionDefined::class.java) {
Requester(connection, functionsDirectory = resources)
.getFunction("wrongName")
}
}
@Test @Test
fun `get function from file`() { fun `get function from file`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val objTest: ObjTest? = Requester(connection) val objTest: ObjTest? = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(listOf("test", "plip")) .selectOne(listOf("test", "plip"))
@@ -37,20 +144,28 @@ class RequesterTest : TestAbstract() {
@Test @Test
fun `call exec on query`() { fun `call exec on query`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection) val result = Requester(connection, queriesDirectory = resources)
.addQuery(resources)
.getQuery("selectOne") .getQuery("selectOne")
.exec() .exec()
assertEquals(1, result.rowsAffected) assertEquals(1, result.rowsAffected)
} }
@Test
fun `call exec on query with a list of arguments`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection, queriesDirectory = resources)
.getQuery("selectOneWithParameters")
.exec(listOf("myName"))
assertEquals("myName", result.rows[0].getString(0)?.deserialize<ObjTest>()?.name)
}
@Test @Test
fun `call exec on function`() { fun `call exec on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val result = Requester(connection) val result = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.exec(listOf("test", "plip")) .exec(listOf("test", "plip"))
@@ -58,32 +173,118 @@ class RequesterTest : TestAbstract() {
} }
@Test @Test
fun `call sendQuery on query with name`() { fun `call exec on query with name`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection) val result = Requester(connection, queriesDirectory = resources)
.addQuery(resources)
.getQuery("DeleteTest") .getQuery("DeleteTest")
.sendQuery() .exec()
assertEquals(0, result) assertEquals(0, result.rowsAffected)
} }
@Test @Test
fun `call sendQuery on function`() { fun `call sendQuery with same name of arguments`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection) Requester(connection, queriesDirectory = resources)
.addFunction(resources) .getQuery("selectMultipleWithSameArgs")
.getFunction("function_void") .sendQuery("name" to "myName").run {
.sendQuery(listOf("test")) assertEquals("myName", rows[0].getString("firstName"))
assertEquals("myName", rows[0].getString("secondName"))
}
}
assertEquals(0, result) @Test
fun `call sendQuery with same name of arguments as list`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleWithSameArgs")
.sendQuery(listOf("myName", "myName2")).run {
assertEquals("myName", rows[0].getString("firstName"))
assertEquals("myName2", rows[0].getString("secondName"))
}
}
@Test
fun `call sendQuery with arguments on not same orders`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleDifferentArgs")
.sendQuery("first" to "firstName", "second" to "secondName").run {
assertEquals("firstName", rows[0].getString("firstName"))
assertEquals("secondName", rows[0].getString("secondName"))
}
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleDifferentArgs")
.sendQuery("second" to "secondName", "first" to "firstName").run {
assertEquals("firstName", rows[0].getString("firstName"))
assertEquals("secondName", rows[0].getString("secondName"))
}
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleDifferentArgs")
.sendQuery("second" to "secondName", "first" to "firstName").run {
assertEquals("firstName", rows[0].getString(0))
assertEquals("secondName", rows[0].getString(1))
}
}
@Test
fun `call sendQuery with wrong number of arguments`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
assertThrows(QueryError::class.java) {
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleDifferentArgs")
.sendQuery("first" to "firstName")
}.let {
assertEquals(
"""
Parameter "second" missing
> :first = firstName
> SELECT :first::text as "firstName", :second::text as "secondName";
""".trimIndent(),
it.message
)
}
}
@Test
fun `call sendQuery with wrong number of arguments as list`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
assertThrows(QueryError::class.java) {
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleDifferentArgs")
.sendQuery(listOf("firstName"))
}.let {
assertEquals(
"""
Parameter 1 missing
> firstName
> SELECT ?::text as "firstName", ?::text as "secondName";
""".trimIndent(),
it.message
)
}
}
@Test
fun `call exec on function with pair as arguments`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val result = Requester(connection, functionsDirectory = resources)
.getFunction("function_void")
.exec("name" to "test")
assertEquals(1, result.rowsAffected)
} }
@Test @Test
fun `call selectOne on function`() { fun `call selectOne on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: ObjTest = Requester(connection) val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(mapOf("name" to "myName"))!! .selectOne(mapOf("name" to "myName"))!!
@@ -91,23 +292,43 @@ class RequesterTest : TestAbstract() {
} }
@Test @Test
fun `call selectOne on function with object`() { fun `call selectOne on function with object and named argument`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj2 = ObjTest("original") val obj2 = ObjTest("original")
val obj: ObjTest = Requester(connection) val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function_object") .getFunction("test_function_object")
.selectOne("resource" to obj2)!! .selectOne("resource" to obj2)!!
assertEquals("changedName", obj.name) assertEquals("changedName", obj.name)
assertEquals("changedName", obj2.name) assertEquals("original", obj2.name)
}
@Test
fun `call selectOne on function with object`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj2 = ObjTest("original")
val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.getFunction("test_function_object")
.update(obj2)!!
assertEquals("changedName", obj.name)
assertEquals("original", obj2.name)
}
@Test
fun `call selectOne on function with object and no arguments`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.selectOne()!!
assertEquals("plop", obj.name)
} }
@Test @Test
fun `call selectOne on query`() { fun `call selectOne on query`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val obj: ObjTest = Requester(connection) val obj: ObjTest = Requester(connection, queriesDirectory = resources)
.addQuery(resources)
.getQuery("selectOneWithParameters") .getQuery("selectOneWithParameters")
.selectOne(mapOf("name" to "myName"))!! .selectOne(mapOf("name" to "myName"))!!
@@ -115,55 +336,160 @@ class RequesterTest : TestAbstract() {
} }
@Test @Test
fun `call select (multiple) on function`() { fun `call select (multiple) on function with named argument`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI() val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: List<ObjTest>? = Requester(connection) val obj: List<ObjTest> = Requester(connection, functionsDirectory = resources)
.addFunction(resources)
.getFunction("test_function_multiple") .getFunction("test_function_multiple")
.select(mapOf("name" to "myName")) .select(mapOf("name" to "myName"))
assertEquals("myName", obj!![0].name) assertEquals("myName", obj[0].name)
}
@Test
fun `call select (multiple) on function with ordered arguments`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: List<ObjTest> = Requester(connection, functionsDirectory = resources)
.getFunction("test_function_multiple")
.select(listOf("myName"))
assertEquals("myName", obj[0].name)
}
@Test
fun `call select multiple (named arguments)`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultiple").apply {
select<ObjTest>(mapOf("name" to "ff")).let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
}
}.apply {
select<ObjTest>(object : TypeReference<List<ObjTest>>() {}, mapOf("name" to "ff")).let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
}
}
}
@Test
fun `call select multiple (named arguments as pair)`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultiple").apply {
select<ObjTest>("name" to "ff").let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
}
}.apply {
select<ObjTest>(object : TypeReference<List<ObjTest>>() {}, "name" to "ff").let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
}
}
}
@Test
fun `call select multiple (ordered argument)`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleOrderedArgs").apply {
select<ObjTest>(listOf("ff", "aa")).let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("aa-2", result[1].name)
}
}.apply {
select<ObjTest>(object : TypeReference<List<ObjTest>>() {}, listOf("ff", "aa")).let { result ->
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("aa-2", result[1].name)
}
}
} }
@Test @Test
fun `call select paginated on query`() { fun `call select paginated on query`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val result: Paginated<ObjTest> = Requester(connection) val result: Paginated<ObjTest> = Requester(connection, queriesDirectory = resources)
.addQuery(resources)
.getQuery("selectPaginated") .getQuery("selectPaginated")
.select(1, 2, mapOf("name" to "ff")) .select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result) assertNotNull(result)
Assert.assertEquals("ff", result.result[0].name) assertEquals("ff", result.result[0].name)
Assert.assertEquals("ff-2", result.result[1].name) assertEquals("ff-2", result.result[1].name)
Assert.assertEquals(10, result.total) assertEquals(10, result.total)
Assert.assertEquals(0, result.offset) assertEquals(0, result.offset)
} }
@Test @Test
fun `call select paginated on function`() { fun `call select paginated on function`() {
val resources = this::class.java.getResource("/sql/function").toURI() val resources = this::class.java.getResource("/sql/function")?.toURI()
val result: Paginated<ObjTest> = Requester(connection) Requester(connection, functionsDirectory = resources)
.addFunction(resources) .getFunction("test_function_paginated").apply {
select<ObjTest>(1, 2, mapOf("name" to "ff")).run {
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
assertEquals(10, total)
assertEquals(0, offset)
}
}.apply {
select<ObjTest>(1, 2, object : TypeReference<List<ObjTest>>() {}, mapOf("name" to "ff")).run {
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
assertEquals(10, total)
assertEquals(0, offset)
}
}
}
@Test
fun `call select paginated on function with vararg`() {
val resources = this::class.java.getResource("/sql/function")?.toURI()
Requester(connection, functionsDirectory = resources)
.getFunction("test_function_paginated") .getFunction("test_function_paginated")
.select(1, 2, mapOf("name" to "ff")) .select<ObjTest>(1, 2, "name" to "ff").run {
Assert.assertNotNull(result) assertNotNull(result)
Assert.assertEquals("ff", result.result[0].name) assertEquals("ff", result[0].name)
Assert.assertEquals("ff-2", result.result[1].name) assertEquals("ff-2", result[1].name)
Assert.assertEquals(10, result.total) assertEquals(10, total)
Assert.assertEquals(0, result.offset) assertEquals(0, offset)
}
Requester(connection, functionsDirectory = resources)
.getFunction("test_function_paginated")
.select(1, 2, object : TypeReference<List<ObjTest>>() {}, "name" to "ff").run {
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
assertEquals(10, total)
assertEquals(0, offset)
}
} }
@Test @Test
fun `call selectOne on query with extra parameter`() { fun `call selectOne on query with extra parameter`() {
val resources = this::class.java.getResource("/sql/query").toURI() val resources = this::class.java.getResource("/sql/query")?.toURI()
val obj: ObjTest = Requester(connection) Requester(connection, queriesDirectory = resources)
.addQuery(resources) .getQuery("selectOneWithParameters").apply {
.getQuery("selectOneWithParameters") selectOne<ObjTest>(mapOf("name" to "myName")) {
.selectOne(mapOf("name" to "myName")) { assertEquals("myName", it!!.name)
assertEquals("myName", it!!.name) assertEquals("plop", rows[0].getString("other"))
Assert.assertEquals("plop", rows[0].getString("other")) }!!.run {
}!! assertEquals("myName", name)
}
assertEquals("myName", obj.name) }.apply {
selectOne<ObjTest>(typeReference = object : TypeReference<ObjTest>() {}, values = mapOf("name" to "myName")) { it ->
assertEquals("myName", it!!.name)
assertEquals("plop", rows[0].getString("other"))
}!!.run {
assertEquals("myName", name)
}
}
} }
} }

View File

@@ -2,7 +2,6 @@ package fr.postgresjson
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.serializer.Serializer import fr.postgresjson.serializer.Serializer
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize import fr.postgresjson.serializer.serialize
import org.joda.time.DateTime import org.joda.time.DateTime
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
@@ -21,7 +20,6 @@ internal class SerializerTest {
private val objSerialized: String = """{"val1":"plop","val2":123,"id":"829b1a29-5db8-47f9-9562-961c561ac528"}""" private val objSerialized: String = """{"val1":"plop","val2":123,"id":"829b1a29-5db8-47f9-9562-961c561ac528"}"""
private val objSerializedWithExtra: String = """{"val1":"plop","val2":123,"id":"829b1a29-5db8-47f9-9562-961c561ac528","toto":"tata"}""" private val objSerializedWithExtra: String = """{"val1":"plop","val2":123,"id":"829b1a29-5db8-47f9-9562-961c561ac528","toto":"tata"}"""
private val objSerializedUpdate = """{"val1":"update","val2":123}"""
private lateinit var obj: ObjTest private lateinit var obj: ObjTest
@BeforeEach @BeforeEach
@@ -69,20 +67,4 @@ internal class SerializerTest {
assertEquals(obj.val1, objDeserialized!!.val1) assertEquals(obj.val1, objDeserialized!!.val1)
assertEquals(obj.val2, objDeserialized.val2) assertEquals(obj.val2, objDeserialized.val2)
} }
@Test
fun deserializeUpdate() {
val objDeserialized: ObjTest = serializer.deserialize(objSerializedUpdate, obj)
assertTrue(obj === objDeserialized)
assertEquals("update", objDeserialized.val1)
assertEquals(123, objDeserialized.val2)
}
@Test
fun deserializeUpdate2() {
val objDeserialized = obj.deserialize(objSerializedUpdate)
assertTrue(obj === objDeserialized)
assertEquals("update", objDeserialized.val1)
assertEquals(123, objDeserialized.val2)
}
} }

View File

@@ -13,7 +13,7 @@ abstract class TestAbstract {
@BeforeEach @BeforeEach
fun beforeAll() { fun beforeAll() {
val initSQL = File(this::class.java.getResource("/fixtures/init.sql").toURI()) val initSQL = File(this::class.java.getResource("/fixtures/init.sql")!!.toURI())
connection connection
.connect() .connect()
.sendQuery(initSQL.readText()) .sendQuery(initSQL.readText())
@@ -22,9 +22,9 @@ abstract class TestAbstract {
@AfterEach @AfterEach
fun afterAll() { fun afterAll() {
val downSQL = File(this::class.java.getResource("/fixtures/down.sql").toURI()) val downSQL = File(this::class.java.getResource("/fixtures/down.sql")!!.toURI())
connection.connect().apply { connection
sendQuery(downSQL.readText()).join() .apply { connect().sendQuery(downSQL.readText()).join() }
}.disconnect() .disconnect()
} }
} }

View File

@@ -0,0 +1,4 @@
SELECT json_build_array(
json_build_object('id', '457daad5-4f1b-4eb7-80ec-6882adb8cc7d', 'name', :name::text),
json_build_object('id', '6085c12e-e94d-4ae1-b7ad-23acc7a82a98', 'name', :name::text || '-2')
)

View File

@@ -0,0 +1 @@
SELECT :first::text as "firstName", :second::text as "secondName";

View File

@@ -0,0 +1,4 @@
SELECT json_build_array(
json_build_object('id', '457daad5-4f1b-4eb7-80ec-6882adb8cc7d', 'name', ?::text),
json_build_object('id', '6085c12e-e94d-4ae1-b7ad-23acc7a82a98', 'name', ?::text || '-2')
)

View File

@@ -0,0 +1 @@
SELECT :name::text as "firstName", :name::text as "secondName";