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">
<module name="postgres-json.test" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="fr.postgresjson.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />

View File

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

View File

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

View File

@@ -6,4 +6,6 @@ systemProp.sonar.projectName=PostgresJson
systemProp.sonar.organization=flecomte
systemProp.sonar.java.coveragePlugin=jacoco
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
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.ResultSet
import com.github.jasync.sql.db.general.ArrayRowData
import com.github.jasync.sql.db.pool.ConnectionPool
import com.github.jasync.sql.db.postgresql.PostgreSQLConnection
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.utils.LoggerDelegate
import org.slf4j.Logger
import java.util.concurrent.CompletableFuture
import kotlin.random.Random
typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit
typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit
@@ -44,67 +45,65 @@ class Connection(
}
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,
typeReference: TypeReference<R>,
values: List<Any?>,
block: (QueryResult, R?) -> Unit
): R? {
val primaryObject = values.firstOrNull {
it is EntityI && typeReference.type.typeName == it::class.java.name
} as R?
val result = exec(sql, compileArgs(values))
val json = result.rows.firstOrNull()?.getString(0)
return if (json === null) {
null
} else {
if (primaryObject != null) {
serializer.deserialize(json, primaryObject)
} else {
serializer.deserialize(json, typeReference)
}
serializer.deserialize(json, typeReference)
}.also {
block(result, it)
}
}
inline fun <reified R : EntityI> selectOne(
sql: String,
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? =
select(sql, object : TypeReference<R>() {}, values, block)
override fun <R : EntityI> select(
/**
* Select One [EntityI] with named parameters
*/
override fun <R : EntityI> selectOne(
sql: String,
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit
): R? {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block)
selectOne(this.sql, typeReference, parameters, block)
}
}
inline fun <reified R : EntityI> selectOne(
sql: String,
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
select(sql, object : TypeReference<R>() {}, values, block)
/* Select Multiples */
/**
* Select multiple [EntityI] with [List] of parameters
*/
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: List<Any?>,
block: (QueryResult, List<R>) -> Unit
block: QueryResult.(List<R>) -> Unit
): List<R> {
val result = exec(sql, compileArgs(values))
val result = exec(sql, values)
val json = result.rows[0].getString(0)
return if (json === null) {
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,
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object : TypeReference<List<R>>() {}, values, block)
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)
}
}
/* Select Paginated */
/**
* Select Multiple [EntityI] with pagination
*/
override fun <R : EntityI> select(
sql: String,
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (QueryResult, Paginated<R>) -> Unit
block: QueryResult.(Paginated<R>) -> Unit
): Paginated<R> {
val offset = (page - 1) * limit
val newValues = values
@@ -140,8 +151,15 @@ class Connection(
}
return line.run {
val json = rows[0].getString(0)
val entities = if (json === null) {
val firstLine = rows.firstOrNull() ?: queryError("The query has no return", sql, newValues)
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>
} else {
serializer.deserializeList(json, typeReference)
@@ -150,44 +168,17 @@ class Connection(
entities,
offset,
limit,
rows[0].getInt("total") ?: error("The query not return total")
total
)
}.also {
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 {
val compiledValues = compileArgs(values)
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)
return stopwatchQuery(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) {
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 {
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
values[name] ?: values[name.trimStart('_')] ?: error("Parameter $name missing")
values[name] ?: values[name.trimStart('_')] ?: queryError("""Parameter "$name" missing""", sql, values)
}.toList()
var newSql = sql
values.forEach { (key, _) ->
val regex = ":_?$key".toRegex()
newSql = newSql.replace(regex, "?")
}
return block(ParametersQuery(replaceNamedArgByQuestionMark(sql), orderedArgs))
}
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 {
val paramRegex = "(?<!\\?)(\\?)(?!\\?)".toRegex(RegexOption.IGNORE_CASE)
var i = 0
if (values.isNotEmpty()) {
val newSql = paramRegex.replace(sql) {
values[i] ?: error("Parameter $i missing")
val valToReplace = values[i].toString()
++i
"'$valToReplace'"
}
return if (values.isNotEmpty()) {
sql
.let(this::replaceNamedArgByQuestionMark)
.let { insertArgsValuesIntoSql(it, values) }
.let(block)
} else block(sql)
}
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?>)
@@ -291,4 +316,40 @@ class Connection(
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 fr.postgresjson.entity.EntityI
interface EmbedExecutable {
sealed interface EmbedExecutable {
val connection: Connection
override fun toString(): String
val name: String
/* 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>,
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> = {}
): R?
fun <R : EntityI> select(
/**
* Select One [EntityI] with [Map] of parameters
*/
fun <R : EntityI> selectOne(
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: SelectOneCallback<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 Multiple [EntityI] with [List] of parameters
*/
fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(),
values: List<Any?>,
block: SelectCallback<R> = {}
): List<R>
/**
* Select Multiple [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: SelectCallback<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 [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select(
page: Int,
limit: Int,
@@ -47,16 +92,19 @@ interface EmbedExecutable {
block: SelectPaginatedCallback<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(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
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 */
fun <R : EntityI> select(
/**
* Select One [EntityI] with [List] of parameters
*/
fun <R : EntityI> selectOne(
sql: String,
typeReference: TypeReference<R>,
values: List<Any?> = emptyList(),
values: List<Any?>,
block: SelectOneCallback<R> = {}
): R?
fun <R : EntityI> select(
/**
* Select One [EntityI] with [Map] of parameters
*/
fun <R : EntityI> selectOne(
sql: String,
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: SelectOneCallback<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(
sql: String,
typeReference: TypeReference<List<R>>,
@@ -30,6 +64,9 @@ interface Executable {
block: SelectCallback<R> = {}
): List<R>
/**
* Select Multiple [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
@@ -37,8 +74,22 @@ interface Executable {
block: SelectCallback<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 [EntityI] with [Map] of parameters
*/
fun <R : EntityI> select(
sql: String,
page: Int,
@@ -48,8 +99,38 @@ interface Executable {
block: SelectPaginatedCallback<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 sendQuery(sql: String, values: List<Any?> = emptyList()): Int
fun sendQuery(sql: String, values: Map<String, Any?>): Int
fun exec(sql: String, vararg values: Pair<String, Any?>): QueryResult = exec(sql, values.toMap())
/**
* 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 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>,
values: List<Any?>,
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? =
select(object : TypeReference<R>() {}, values, block)
inline fun <reified R : EntityI> selectOne(
value: R,
noinline block: SelectOneCallback<R> = {}
): R? =
select(object : TypeReference<R>() {}, listOf(value), block)
connection.selectOne(compileSql(values), typeReference, values, 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>,
values: Map<String, Any?>,
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? =
select(object : TypeReference<R>() {}, values, block)
inline fun <reified R : EntityI> selectOne(
vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
selectOne(values.toMap(), block)
connection.selectOne(compileSql(values), typeReference, values, block)
/* Select Multiples */
/**
* Select list of entities with list of parameters
* Select multiple [EntityI] with [List] of parameters
*/
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?>,
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> =
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(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
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> =
select(object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R : EntityI> select(
vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(values.toMap(), block)
connection.select(compileSql(values), typeReference, values, block)
/* Select Paginated */
/**
* Select Multiple with pagination
* Select Multiple [EntityI] with pagination
*/
override fun <R : EntityI> select(
page: Int,
@@ -131,53 +73,16 @@ class Function(val definition: Function, override val connection: Connection) :
.plus("offset" to offset)
.plus("limit" to limit)
val args = compileArgs(newValues)
val sql = "SELECT * FROM ${definition.name} ($args)"
return connection.select(sql, page, limit, typeReference, values, block)
return connection.select(compileSql(newValues), page, limit, typeReference, values, block)
}
inline fun <reified R : EntityI> 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)
/* Execute function without treatments */
inline fun <reified R : EntityI> select(
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)
override fun exec(values: List<Any?>): QueryResult = connection.exec(compileSql(values), values)
/* Execute function without traitements */
override fun exec(values: Map<String, Any?>): QueryResult = connection.exec(compileSql(values), values)
override fun exec(values: List<Any?>): QueryResult {
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 <R : EntityI> compileArgs(value: R): String = compileArgs(listOf(value))
private fun compileArgs(values: List<Any?>): String {
val placeholders = values
@@ -205,4 +110,8 @@ class Function(val definition: Function, override val connection: Connection) :
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 */
override fun <R : EntityI> select(
/**
* Select One [EntityI] with [List] of parameters
*/
override fun <R : EntityI> selectOne(
typeReference: TypeReference<R>,
values: List<Any?>,
block: (QueryResult, R?) -> Unit
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
block: SelectOneCallback<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>,
values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
block: SelectOneCallback<R>
): R? =
select(object : TypeReference<R>() {}, values, block)
connection.selectOne(sql, typeReference, values, block)
/* Select Multiples */
/**
* Select multiple [EntityI] with [List] of parameters
*/
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?>,
block: (QueryResult, List<R>) -> Unit
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
block: SelectCallback<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(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R : EntityI> select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
block: SelectCallback<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(
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
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> =
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 {
return connection.exec(sql, values)
}
override fun exec(values: List<Any?>): QueryResult = connection.exec(sql, values)
override fun exec(values: Map<String, Any?>): QueryResult {
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): QueryResult = 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 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
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))
return this
}
fun addQuery(queriesDirectory: URI): Requester {
queriesDirectory.searchSqlFiles()
.forEach {
if (it is QueryDefinition) {
addQuery(it)
}
}
return this
fun addQuery(queriesDirectory: URI) {
queriesDirectory
.searchSqlFiles()
.filterIsInstance(QueryDefinition::class.java)
.forEach(this::addQuery)
}
fun getQueries(): List<Query> {
return queries.map { it.value }
fun getQueries(): List<Query> = queries.map { it.value }
fun addFunction(definition: DefinitionFunction) {
definition
.run { toRunnable(connection) }
.run { functions[name] = this }
}
fun addFunction(definition: DefinitionFunction): Requester {
functions[definition.name] = Function(definition, connection)
return this
fun addFunction(sql: String) {
DefinitionFunction(sql)
.run { toRunnable(connection) }
.run { functions[name] = this }
}
fun addFunction(sql: String): Requester {
DefinitionFunction(sql).let {
functions[it.name] = Function(it, connection)
}
return this
}
fun addFunction(functionsDirectory: URI): Requester {
fun addFunctions(functionsDirectory: URI) {
functionsDirectory.searchSqlFiles()
.forEach {
if (it is DefinitionFunction) {
addFunction(it)
}
}
return this
.filterIsInstance(DefinitionFunction::class.java)
.forEach(this::addFunction)
}
fun getFunction(name: String): Function {
if (functions[name] === null) {
throw Exception("No function defined for $name")
}
return functions[name]!!
}
fun getFunction(name: String): Function = functions[name] ?: throw NoFunctionDefined(name)
fun getQuery(path: String): Query {
if (queries[path] === null) {
throw Exception("No query defined in $path")
}
return queries[path]!!
}
fun getQuery(path: String): Query = queries[path] ?: throw NoQueryDefined(path)
class RequesterFactory(
private val connection: Connection,
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
}
}
class NoFunctionDefined(name: String) : Exception("No function defined for $name")
class NoQueryDefined(path: String) : Exception("No query defined in $path")
}

View File

@@ -1,11 +1,10 @@
package fr.postgresjson.definition
import java.io.File
import java.nio.file.Path
class Function(
override val script: String,
override var source: Path? = null
override val source: Path? = null
) : Resource, ParametersInterface {
val returns: String
override val name: String
@@ -70,21 +69,4 @@ class Function(
infix fun `is different from`(other: Function): Boolean {
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(
override val script: String,
source: Path
override var source: Path
) : Resource {
override val name: String
val direction: Direction
override var source: Path? = null
init {
this.source = source
this.direction = source.fileName.toString()
.let {
when {

View File

@@ -1,5 +1,7 @@
package fr.postgresjson.definition
import java.util.Locale
interface ParameterI {
val name: 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(
name = name,
type = type,
direction = direction?.let { Direction.valueOf(direction.toUpperCase()) },
direction = direction?.let { Direction.valueOf(direction.uppercase(Locale.getDefault())) },
default = default
)

View File

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

View File

@@ -4,10 +4,10 @@ import java.io.File
import java.net.URL
import java.nio.file.Path
interface Resource {
sealed interface Resource {
val name: String
val script: String
var source: Path?
val source: Path?
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 fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status
import java.util.Date
@@ -46,21 +47,25 @@ data class Function(
}
}
this::class.java.classLoader.getResource("sql/migration/insertFunction.sql")!!.readText().let {
connection.selectOne<MigrationEntity>(it, listOf(up.name, up.getDefinition(), up.script, down.script))?.let { function ->
this::class.java.classLoader
.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
doExecute = Action.OK
}
}
return Status.OK
}
override fun down(): Status {
connection.sendQuery(down.script)
this::class.java.classLoader.getResource("sql/migration/deleteFunction.sql")!!.readText().let {
connection.sendQuery(it, listOf(down.name))
}
this::class.java.classLoader
.getResource("sql/migration/deleteFunction.sql")!!
.readText()
.let { connection.sendQuery(it, listOf(down.name)) }
return Status.OK
}
@@ -68,29 +73,15 @@ data class Function(
connection.inTransaction {
up()
down()
it.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
sendQuery("ROLLBACK")
}
return Status.OK
}
infix fun `is different from`(other: DefinitionFunction): Boolean {
return other.script != this.up.script
}
fun copy(): Function = this
.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
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.entity.Entity
import fr.postgresjson.migration.Migration.Action
import java.util.Date
@@ -41,20 +42,10 @@ data class MigrationScript(
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}.join()
sendQuery("ROLLBACK")
}
return Migration.Status.OK // TODO
}
override fun status(): Migration.Status {
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}.join()
return Migration.Status.OK // TODO
return Migration.Status.OK
}
fun copy(): MigrationScript {

View File

@@ -28,7 +28,6 @@ interface Migration {
fun up(): Status
fun down(): Status
fun test(): Status
fun status(): Status
enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) }
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.databind.DeserializationFeature
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.module.SimpleModule
import com.fasterxml.jackson.datatype.joda.JodaModule
@@ -15,7 +15,7 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
init {
val module = SimpleModule()
mapper.registerModule(module)
mapper.propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
mapper.propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
mapper.registerModule(JodaModule())
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 {
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 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 T : Serializable> T.toTypeReference(): TypeReference<T> = object : TypeReference<T>() {}

View File

@@ -1,22 +1,31 @@
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.select
import fr.postgresjson.connexion.selectOne
import fr.postgresjson.entity.Parameter
import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.toTypeReference
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.util.UUID
import kotlin.test.assertContains
import kotlin.test.assertNull
@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 ObjTest2(val title: String, var test: ObjTest?) : UuidEntity()
private class ObjTest3(val first: String, var seconde: String, var third: Int) : UuidEntity()
private class ObjTestWithParameterObject(var first: ParameterObject, var seconde: ParameterObject) : UuidEntity()
private class ObjTest3(val first: String, var second: String, var third: Int) : UuidEntity()
private class ObjTestWithParameterObject(var first: ParameterObject, var second: ParameterObject) : UuidEntity()
private class ParameterObject(var third: String) : Parameter
@Test
@@ -48,12 +57,33 @@ class ConnectionTest() : TestAbstract() {
}
@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"))
assertNotNull(result)
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
fun callRequestWithArgsEntity() {
val o = ObjTest("myName", id = UUID.fromString("2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00"))
@@ -64,6 +94,15 @@ class ConnectionTest() : TestAbstract() {
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
fun callExec() {
val o = ObjTest("myName")
@@ -74,69 +113,67 @@ class ConnectionTest() : TestAbstract() {
@Test
fun `select one with named parameters`() {
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(
"first" to "ff",
"seconde" to "sec",
"second" to "sec",
"third" to 123
)
)
assertEquals(result!!.first, "ff")
assertEquals(result.seconde, "sec")
assertEquals(result.second, "sec")
assertEquals(result.third, 123)
}
@Test
fun `select one with named parameters object`() {
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(
"first" to ParameterObject("one"),
"seconde" to ParameterObject("two")
"second" to ParameterObject("two")
)
)
assertEquals("one", result!!.first.third)
assertEquals("two", result.seconde.third)
assertEquals("two", result.second.third)
}
@Test
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(
"""
SELECT json_build_array(
json_build_object('first', :first::text, 'seconde', :seconde::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),
json_build_object('first', :first::text, 'second', :second::text, 'third', :third::int)
)
""".trimIndent(),
params
mapOf(
"first" to "ff",
"third" to 123,
"second" to "sec"
)
)
assertEquals(result[0].first, "ff")
assertEquals(result[0].seconde, "sec")
assertEquals(result[0].second, "sec")
assertEquals(result[0].third, 123)
}
@Test
fun `selectOne with named parameters`() {
val params: Map<String, Any?> = mapOf(
fun `select with named parameters as vararg of Pair`() {
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",
"third" to 123,
"seconde" to "sec"
"second" to "sec"
)
val result: ObjTest3? = connection.selectOne(
"""
SELECT json_build_object('first', :first::text, 'seconde', :seconde::text, 'third', :third::int)
""".trimIndent(),
params
)
assertNotNull(result)
assertEquals(result!!.first, "ff")
assertEquals(result.seconde, "sec")
assertEquals(result.third, 123)
assertEquals(result[0].first, "ff")
assertEquals(result[0].second, "sec")
assertEquals(result[0].third, 123)
}
@Test
@@ -161,16 +198,141 @@ class ConnectionTest() : TestAbstract() {
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
fun `selectOne with extra parameters`() {
val params: Map<String, Any?> = mapOf(
"first" to "ff",
"third" to 123,
"seconde" to "sec"
"second" to "sec"
)
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(),
params
) {
@@ -179,7 +341,39 @@ class ConnectionTest() : TestAbstract() {
}
assertNotNull(result)
assertEquals("ff", result!!.first)
assertEquals("sec", result.seconde)
assertEquals("sec", result.second)
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)
class EntityTest() {
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
fun getObject() {
val obj: ObjTest? = ObjTest("plop")
val obj = ObjTest("plop")
assertTrue(obj is ObjTest)
assertTrue(obj is UuidEntityExtended<Int?, User>)
assertTrue(obj is EntityI)

View File

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

View File

@@ -1,21 +1,102 @@
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.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 org.junit.Assert
import fr.postgresjson.serializer.deserialize
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import java.util.UUID
import kotlin.test.assertNotNull
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
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)
.addQuery(resources)
.apply { addQuery(resources) }
.getQuery("selectOne")
.selectOne()
@@ -23,11 +104,37 @@ class RequesterTest : TestAbstract() {
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
fun `get function from file`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
val objTest: ObjTest? = Requester(connection)
.addFunction(resources)
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val objTest: ObjTest? = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.selectOne(listOf("test", "plip"))
@@ -37,20 +144,28 @@ class RequesterTest : TestAbstract() {
@Test
fun `call exec on query`() {
val resources = this::class.java.getResource("/sql/query").toURI()
val result = Requester(connection)
.addQuery(resources)
val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection, queriesDirectory = resources)
.getQuery("selectOne")
.exec()
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
fun `call exec on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
val result = Requester(connection)
.addFunction(resources)
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val result = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.exec(listOf("test", "plip"))
@@ -58,32 +173,118 @@ class RequesterTest : TestAbstract() {
}
@Test
fun `call sendQuery on query with name`() {
val resources = this::class.java.getResource("/sql/query").toURI()
val result = Requester(connection)
.addQuery(resources)
fun `call exec on query with name`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
val result = Requester(connection, queriesDirectory = resources)
.getQuery("DeleteTest")
.sendQuery()
.exec()
assertEquals(0, result)
assertEquals(0, result.rowsAffected)
}
@Test
fun `call sendQuery on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
val result = Requester(connection)
.addFunction(resources)
.getFunction("function_void")
.sendQuery(listOf("test"))
fun `call sendQuery with same name of arguments`() {
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectMultipleWithSameArgs")
.sendQuery("name" to "myName").run {
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
fun `call selectOne on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
val obj: ObjTest = Requester(connection)
.addFunction(resources)
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.getFunction("test_function")
.selectOne(mapOf("name" to "myName"))!!
@@ -91,23 +292,43 @@ class RequesterTest : TestAbstract() {
}
@Test
fun `call selectOne on function with object`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
fun `call selectOne on function with object and named argument`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj2 = ObjTest("original")
val obj: ObjTest = Requester(connection)
.addFunction(resources)
val obj: ObjTest = Requester(connection, functionsDirectory = resources)
.getFunction("test_function_object")
.selectOne("resource" to obj2)!!
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
fun `call selectOne on query`() {
val resources = this::class.java.getResource("/sql/query").toURI()
val obj: ObjTest = Requester(connection)
.addQuery(resources)
val resources = this::class.java.getResource("/sql/query")?.toURI()
val obj: ObjTest = Requester(connection, queriesDirectory = resources)
.getQuery("selectOneWithParameters")
.selectOne(mapOf("name" to "myName"))!!
@@ -115,55 +336,160 @@ class RequesterTest : TestAbstract() {
}
@Test
fun `call select (multiple) on function`() {
val resources = this::class.java.getResource("/sql/function/Test").toURI()
val obj: List<ObjTest>? = Requester(connection)
.addFunction(resources)
fun `call select (multiple) on function with named argument`() {
val resources = this::class.java.getResource("/sql/function/Test")?.toURI()
val obj: List<ObjTest> = Requester(connection, functionsDirectory = resources)
.getFunction("test_function_multiple")
.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
fun `call select paginated on query`() {
val resources = this::class.java.getResource("/sql/query").toURI()
val result: Paginated<ObjTest> = Requester(connection)
.addQuery(resources)
val resources = this::class.java.getResource("/sql/query")?.toURI()
val result: Paginated<ObjTest> = Requester(connection, queriesDirectory = resources)
.getQuery("selectPaginated")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals("ff", result.result[0].name)
Assert.assertEquals("ff-2", result.result[1].name)
Assert.assertEquals(10, result.total)
Assert.assertEquals(0, result.offset)
assertNotNull(result)
assertEquals("ff", result.result[0].name)
assertEquals("ff-2", result.result[1].name)
assertEquals(10, result.total)
assertEquals(0, result.offset)
}
@Test
fun `call select paginated on function`() {
val resources = this::class.java.getResource("/sql/function").toURI()
val result: Paginated<ObjTest> = Requester(connection)
.addFunction(resources)
val resources = this::class.java.getResource("/sql/function")?.toURI()
Requester(connection, functionsDirectory = 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")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals("ff", result.result[0].name)
Assert.assertEquals("ff-2", result.result[1].name)
Assert.assertEquals(10, result.total)
Assert.assertEquals(0, result.offset)
.select<ObjTest>(1, 2, "name" to "ff").run {
assertNotNull(result)
assertEquals("ff", result[0].name)
assertEquals("ff-2", result[1].name)
assertEquals(10, total)
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
fun `call selectOne on query with extra parameter`() {
val resources = this::class.java.getResource("/sql/query").toURI()
val obj: ObjTest = Requester(connection)
.addQuery(resources)
.getQuery("selectOneWithParameters")
.selectOne(mapOf("name" to "myName")) {
assertEquals("myName", it!!.name)
Assert.assertEquals("plop", rows[0].getString("other"))
}!!
assertEquals("myName", obj.name)
val resources = this::class.java.getResource("/sql/query")?.toURI()
Requester(connection, queriesDirectory = resources)
.getQuery("selectOneWithParameters").apply {
selectOne<ObjTest>(mapOf("name" to "myName")) {
assertEquals("myName", it!!.name)
assertEquals("plop", rows[0].getString("other"))
}!!.run {
assertEquals("myName", 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.serializer.Serializer
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import org.joda.time.DateTime
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 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
@BeforeEach
@@ -69,20 +67,4 @@ internal class SerializerTest {
assertEquals(obj.val1, objDeserialized!!.val1)
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
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
.connect()
.sendQuery(initSQL.readText())
@@ -22,9 +22,9 @@ abstract class TestAbstract {
@AfterEach
fun afterAll() {
val downSQL = File(this::class.java.getResource("/fixtures/down.sql").toURI())
connection.connect().apply {
sendQuery(downSQL.readText()).join()
}.disconnect()
val downSQL = File(this::class.java.getResource("/fixtures/down.sql")!!.toURI())
connection
.apply { connect().sendQuery(downSQL.readText()).join() }
.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";