28 Commits
jdbc ... 1.0.1

Author SHA1 Message Date
Fabrice Lecomte
d3bcfdd627 config package 2020-03-25 15:26:19 +01:00
Fabrice Lecomte
deea5153f2 add method function.perform 2020-03-25 00:30:54 +01:00
Fabrice Lecomte
05fb868574 Add serialise list 2020-03-15 20:18:55 +01:00
Fabrice Lecomte
bbeec7bb60 add methode$ "isLastPage" to pagination 2020-02-24 20:41:29 +01:00
Fabrice Lecomte
012beb6884 Improve SQL log message 2020-02-19 16:46:20 +01:00
Fabrice Lecomte
45707f9734 change Idea config 2020-02-19 12:46:04 +01:00
Fabrice Lecomte
4e89aa072d RepositoryI::requester is now readonly 2020-02-11 20:39:43 +01:00
Fabrice Lecomte
b65e82a52f Improve UuidEntityVersioning 2020-01-29 17:03:22 +01:00
Fabrice Lecomte
898951e91a Add Immutable Entities 2020-01-23 00:40:47 +01:00
dbf5fbcfc5 Add Serializable Parameter 2019-10-22 11:10:02 +02:00
8eeb1e9e9d update jasync 2019-10-11 22:05:15 +02:00
c4125f1429 Lint 2019-10-10 21:44:56 +02:00
21c05f72f5 remove EntityI.className && RepositoryI.entityName 2019-10-09 22:50:04 +02:00
2a738e0595 remove id in EntityI interface & remove usless EntitiesCollections 2019-10-02 11:16:28 +02:00
6641ed78e7 remove returns in function definition 2019-09-20 02:36:21 +02:00
8810c1f08d Drop function if already exists and conflict 2019-09-20 02:27:15 +02:00
88c45aaf77 fix return function definition 2019-09-16 23:12:28 +02:00
2f9f397f50 add EntityDeletedBy & EntityDeletedAt 2019-08-30 15:14:54 +02:00
215c054f27 rename interface UpdatedBy to EntityUpdatedBy 2019-08-27 22:57:49 +02:00
65d8e00902 add sendQuery(vararg) 2019-08-24 23:42:08 +02:00
2922c9309c set UuidEntity open 2019-08-24 23:41:45 +02:00
ae96bfd737 replace params begin by _ 2019-08-24 23:39:23 +02:00
e42f3b07cb clean entities 2019-08-07 22:45:40 +02:00
19e98250b7 change UuidEntityVersioning 2019-08-05 20:29:15 +02:00
f56c84c675 Fix function replaceArgsIntoSql 2019-08-04 22:24:24 +02:00
13f0f12c1b Fix migration if executed two or more times 2019-08-04 22:23:57 +02:00
01a0000b15 clean code 2019-08-04 22:23:12 +02:00
24349fc71f use sendQuery if no return is expected 2019-08-04 21:09:24 +02:00
44 changed files with 761 additions and 483 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
/build/
.gradle
/var/log/
/.idea/*

1
.idea/.gitignore generated vendored
View File

@@ -7,3 +7,4 @@
/uiDesigner.xml
/dataSources.xml
/sonarlint/
/jarRepositories.xml

View File

@@ -1,7 +1,13 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" withSubpackages="true" static="false" />
</value>
</option>
<option name="SPACE_BEFORE_EXTEND_COLON" value="false" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">

2
.idea/gradle.xml generated
View File

@@ -8,13 +8,11 @@
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="useAutoImport" value="true" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>

6
.idea/misc.xml generated
View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="adopt-openjdk-11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="TaskProjectConfiguration">

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/postgres-json.iml" filepath="$PROJECT_DIR$/.idea/postgres-json.iml" />
</modules>
</component>
</project>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/var" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/runConfigurations/Check.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Check" type="CompoundRunConfigurationType">
<toRun name="tests" type="JUnit" />
<toRun name="Lint" type="GradleRunConfiguration" />
<method v="2" />
</configuration>
</component>

31
.idea/runConfigurations/Lint.xml generated Normal file
View File

@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Lint" type="GradleRunConfiguration" factoryName="Gradle" singleton="false">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="ktlintCheck" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish To MavenLocal" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="publishToMavenLocal" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,34 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test and Publish To MavenLocal" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="publishToMavenLocal" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</extension>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="tests" run_configuration_type="JUnit" />
</method>
</configuration>
</component>

View File

@@ -1,6 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests" type="JUnit" factoryName="JUnit">
<configuration default="false" name="tests" type="JUnit" factoryName="JUnit" singleton="false">
<module name="postgresjson.test" />
<useClassPathOnly />
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
@@ -12,7 +13,7 @@
</ENTRIES>
</extension>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="11" />
<option name="ALTERNATIVE_JRE_PATH" value="$USER_HOME$/.jdks/adopt-openjdk-11.0.5" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="directory" />

View File

@@ -1,12 +1,16 @@
plugins {
jacoco
id("maven-publish")
id("org.jetbrains.kotlin.jvm") version "1.3.31"
id("org.jetbrains.kotlin.jvm") version "1.3.50"
id("org.jlleitschuh.gradle.ktlint") version "8.2.0"
id("org.owasp.dependencycheck") version "5.1.0"
id("fr.coppernic.versioning") version "3.1.2"
}
apply(plugin = "kotlin")
group = "fr.postgresjson"
version = "0.1"
group = "flecomte"
version = versioning.info.tag
repositories {
mavenCentral()
@@ -18,8 +22,8 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.31")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9")
implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
implementation("org.slf4j:slf4j-api:1.7.26")
implementation("org.postgresql:postgresql:42.2.6")
testImplementation("ch.qos.logback:logback-classic:1.2.3")
testImplementation("ch.qos.logback:logback-core:1.2.3")
@@ -28,14 +32,27 @@ dependencies {
testImplementation("org.amshove.kluent:kluent:1.47")
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "fr.postgresjson"
artifactId = "postgresjson-jdbc"
version = "0.1"
val sourcesJar by tasks.creating(Jar::class) {
archiveClassifier.set("sources")
from(sourceSets.getByName("main").allSource)
}
publishing {
repositories {
maven {
name = "postgres-json"
url = uri("https://maven.pkg.github.com/flecomte/postgres-json")
credentials {
username = System.getenv("GITHUB_USERNAME")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("postgres-json") {
from(components["java"])
artifact(sourcesJar)
}
}
}

View File

@@ -1,6 +1,5 @@
#Wed May 29 17:08:27 CEST 2019
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
zipStoreBase=GRADLE_USER_HOME

View File

@@ -1 +1 @@
rootProject.name = 'postgresjson'
rootProject.name = 'postgres-json'

View File

@@ -1,17 +1,22 @@
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.pool.ConnectionPool
import com.github.jasync.sql.db.postgresql.PostgreSQLConnection
import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder
import com.github.jasync.sql.db.util.length
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.Serializable
import fr.postgresjson.serializer.Serializer
import fr.postgresjson.utils.LoggerDelegate
import org.slf4j.Logger
import java.sql.DriverManager
import java.sql.ResultSet
import java.sql.Connection as JDBCConnection
import java.util.concurrent.*
typealias SelectOneCallback<T> = ResultSet.(T?) -> Unit
typealias SelectCallback<T> = ResultSet.(List<T>) -> Unit
typealias SelectPaginatedCallback<T> = ResultSet.(Paginated<T>) -> Unit
typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit
typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit
typealias SelectPaginatedCallback<T> = QueryResult.(Paginated<T>) -> Unit
class Connection(
private val database: String,
@@ -19,35 +24,33 @@ class Connection(
private val password: String,
private val host: String = "localhost",
private val port: Int = 5432
): Executable {
private lateinit var connection: JDBCConnection
) : Executable {
private lateinit var connection: ConnectionPool<PostgreSQLConnection>
private val serializer = Serializer()
private val logger: Logger? by LoggerDelegate()
internal fun connect(): JDBCConnection {
if (!::connection.isInitialized || connection.isClosed) {
connection = DriverManager.getConnection("jdbc:postgresql://$host:$port/$database", username, password)
internal fun connect(): ConnectionPool<PostgreSQLConnection> {
if (!::connection.isInitialized || !connection.isConnected()) {
connection = PostgreSQLConnectionBuilder.createConnectionPool(
"jdbc:postgresql://$host:$port/$database?user=$username&password=$password"
)
}
return connection
}
fun <T> inTransaction(f: (Connection) -> T) {
sendQuery("BEGIN")
f(this)
sendQuery("COMMIT")
}
fun <A> inTransaction(f: (Connection) -> CompletableFuture<A>) = connect().inTransaction(f)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<R>,
values: List<Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
val primaryObject = values.firstOrNull {
it is EntityI<*> && typeReference.type.typeName == it::class.java.name
it is EntityI && typeReference.type.typeName == it::class.java.name
} as R?
val result = exec(sql, compileArgs(values))
val json = result.getString(1)
val json = result.rows[0].getString(0)
return if (json === null) {
null
} else {
@@ -61,41 +64,41 @@ class Connection(
}
}
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
sql: String,
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? =
select(sql, object: TypeReference<R>() {}, values, block)
select(sql, object : TypeReference<R>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block)
}
}
inline fun <reified R: EntityI<*>> selectOne(
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(sql, object : TypeReference<R>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: List<Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
val result = exec(sql, compileArgs(values))
val json = result.getString(1)
val json = result.rows[0].getString(0)
return if (json === null) {
listOf<EntityI<*>>() as List<R>
listOf<EntityI>() as List<R>
} else {
serializer.deserializeList(json, typeReference)
}.also {
@@ -103,20 +106,20 @@ class Connection(
}
}
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
sql: String,
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> =
select(sql, object: TypeReference<List<R>>() {}, values, block)
select(sql, object : TypeReference<List<R>>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
sql: String,
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, Paginated<R>) -> Unit
block: (QueryResult, Paginated<R>) -> Unit
): Paginated<R> {
val offset = (page - 1) * limit
val newValues = values
@@ -128,9 +131,9 @@ class Connection(
}
return line.run {
val json = getString(1)
val json = rows[0].getString(0)
val entities = if (json === null) {
listOf<EntityI<*>>() as List<R>
listOf<EntityI>() as List<R>
} else {
serializer.deserializeList(json, typeReference)
}
@@ -138,72 +141,60 @@ class Connection(
entities,
offset,
limit,
getInt("total")
rows[0].getInt("total") ?: error("The query not return total")
)
}.also {
block(line, it)
}
}
inline fun <reified R: EntityI<*>> select(
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)
select(sql, page, limit, object : TypeReference<List<R>>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
return replaceArgs(sql, values) {
select(this.sql, typeReference, this.parameters, block)
}
}
inline fun <reified R: EntityI<*>> select(
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)
select(sql, object : TypeReference<List<R>>() {}, values, block)
override fun exec(sql: String, values: List<Any?>): ResultSet {
return stopwatchQuery(sql, values) {
connect().prepareStatement(sql).apply {
compileArgs(values).forEachIndexed { i, v ->
when (v) {
is String -> setString(i+1, v)
is Int -> setInt(i+1, v)
else -> setString(i+1, v.toString())
}
}
}.executeQuery().apply { next() }
override fun exec(sql: String, values: List<Any?>): QueryResult {
val compiledValues = compileArgs(values)
return stopwatchQuery(sql, compiledValues) {
connect().sendPreparedStatement(sql, compiledValues).join()
}
}
override fun exec(sql: String, values: Map<String, Any?>): ResultSet {
override fun exec(sql: String, values: Map<String, Any?>): QueryResult {
return replaceArgs(sql, values) {
exec(this.sql, this.parameters)
}
}
override fun sendQuery(sql: String, values: List<Any?>): Int {
return stopwatchQuery(sql, values) {
connect().prepareStatement(sql).apply {
compileArgs(values).forEachIndexed { i, v ->
when (v) {
is String -> setString(i+1, v)
is Int -> setInt(i+1, v)
else -> setString(i+1, v.toString())
val compiledValues = compileArgs(values)
return stopwatchQuery(sql, compiledValues) {
replaceArgsIntoSql(sql, compiledValues) {
connect().sendQuery(it).join().rowsAffected.toInt()
}
}
}.executeUpdate()
}
}
override fun sendQuery(sql: String, values: Map<String, Any?>): Int {
@@ -214,10 +205,8 @@ class Connection(
private fun compileArgs(values: List<Any?>): List<Any?> {
return values.map {
if (it is EntityI<*>) {
serializer.serialize(it).apply {
serializer.collection.set<Any?, EntityI<Any?>>(it as EntityI<Any?>)
}
if (it is Serializable || (it is List<*> && it.firstOrNull() is Serializable)) {
serializer.serialize(it)
} else {
it
}
@@ -228,32 +217,66 @@ class Connection(
val paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE)
val newArgs = paramRegex.findAll(sql).map { match ->
val name = match.groups[1]!!.value
values[name] ?: error("Parameter $name missing")
values[name] ?: values[name.trimStart('_')] ?: error("Parameter $name missing")
}.toList()
var newSql = sql
values.forEach { (key, _) ->
val regex = ":$key".toRegex()
val regex = ":_?$key".toRegex()
newSql = newSql.replace(regex, "?")
}
return block(ParametersQuery(newSql, newArgs))
}
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 block(newSql)
}
return block(sql)
}
data class ParametersQuery(val sql: String, val parameters: List<Any?>)
private fun <T> stopwatchQuery(sql: String, values: List<Any?> = emptyList(), callback: () -> T): T {
val sqlForLog = "\n${sql.prependIndent()}"
try {
val start = System.currentTimeMillis()
val result = callback()
val duration = (System.currentTimeMillis() - start)
logger?.debug("$duration ms for query: $sqlForLog", values)
val resultText = when (result) {
null -> "with no result"
is QueryResult -> result.rows.firstOrNull()?.joinToString(", ")?.let { text ->
if (text.length > 100) "${text.take(100)}... (size: ${text.length})" else text
} ?: "with no result"
else -> "unknown"
}
val args = """
|Query ($duration ms):
|${sql.trimIndent().prependIndent()}
|Arguments (${values.length}):
|${values.joinToString("\n").ifBlank { "No arguments" }.prependIndent()}
|Result:
|${resultText.trimIndent().prependIndent()}
""".trimMargin().prependIndent(" > ")
logger?.debug("Query executed in $duration ms \n{}", args)
return result
} catch (e: Throwable) {
logger?.info("Query Error: $sqlForLog, $values", e)
logger?.info("""
Query Error:
${sql.prependIndent()},
${values.joinToString(", ").prependIndent()}
""".trimIndent(), e)
throw e
}
}
}

View File

@@ -1,8 +1,8 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI
import java.sql.ResultSet
interface EmbedExecutable {
val connection: Connection
@@ -13,33 +13,33 @@ interface EmbedExecutable {
/**
* Select One entity with list of parameters
*/
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: List<Any?> = emptyList(),
block: SelectOneCallback<R> = {}
): R?
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: SelectOneCallback<R> = {}
): R?
/* Select Miltiples */
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(),
block: SelectCallback<R> = {}
): List<R>
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: SelectCallback<R> = {}
): List<R>
/* Select Paginated */
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
@@ -47,8 +47,16 @@ interface EmbedExecutable {
block: SelectPaginatedCallback<R> = {}
): Paginated<R>
fun exec(values: List<Any?> = emptyList()): ResultSet
fun exec(values: Map<String, Any?>): ResultSet
fun exec(values: List<Any?> = emptyList()): 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

@@ -1,20 +1,20 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI
import java.sql.ResultSet
interface Executable {
/* Select One */
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<R>,
values: List<Any?> = emptyList(),
block: SelectOneCallback<R> = {}
): R?
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<R>,
values: Map<String, Any?>,
@@ -23,14 +23,14 @@ interface Executable {
/* Select Miltiples */
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(),
block: SelectCallback<R> = {}
): List<R>
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
sql: String,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
@@ -39,7 +39,7 @@ interface Executable {
/* Select Paginated */
fun <R: EntityI<*>> select(
fun <R : EntityI> select(
sql: String,
page: Int,
limit: Int,
@@ -48,8 +48,8 @@ interface Executable {
block: SelectPaginatedCallback<R> = {}
): Paginated<R>
fun exec(sql: String, values: List<Any?> = emptyList()): ResultSet
fun exec(sql: String, values: Map<String, Any?>): ResultSet
fun exec(sql: String, values: List<Any?> = emptyList()): 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
}

View File

@@ -1,11 +1,11 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.definition.Function
import fr.postgresjson.entity.EntityI
import java.sql.ResultSet
class Function(val definition: Function, override val connection: Connection): EmbedExecutable {
class Function(val definition: Function, override val connection: Connection) : EmbedExecutable {
override fun toString(): String {
return definition.name
}
@@ -17,10 +17,10 @@ class Function(val definition: Function, override val connection: Connection): E
/**
* Select One entity with list of parameters
*/
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: List<Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -28,25 +28,25 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? =
select(object: TypeReference<R>() {}, values, block)
select(object : TypeReference<R>() {}, values, block)
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
value: R,
noinline block: SelectOneCallback<R> = {}
): R? =
select(object: TypeReference<R>() {}, listOf(value), block)
select(object : TypeReference<R>() {}, listOf(value), block)
/**
* Select One entity with named parameters
*/
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -54,13 +54,13 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
select(object: TypeReference<R>() {}, values, block)
select(object : TypeReference<R>() {}, values, block)
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
@@ -71,10 +71,10 @@ class Function(val definition: Function, override val connection: Connection): E
/**
* Select list of entities with list of parameters
*/
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -82,19 +82,19 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> =
select(object: TypeReference<List<R>>() {}, values, block)
select(object : TypeReference<List<R>>() {}, values, block)
/**
* Select list of entities with named parameters
*/
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -102,13 +102,13 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block)
}
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(object: TypeReference<List<R>>() {}, values, block)
select(object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
@@ -119,12 +119,12 @@ class Function(val definition: Function, override val connection: Connection): E
/**
* Select Multiple with pagination
*/
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, Paginated<R>) -> Unit
block: (QueryResult, Paginated<R>) -> Unit
): Paginated<R> {
val offset = (page - 1) * limit
val newValues = values
@@ -137,32 +137,32 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, page, limit, typeReference, values, block)
}
inline fun <reified R: EntityI<*>> select(
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)
select(page, limit, object : TypeReference<List<R>>() {}, values, block)
inline fun <reified R: EntityI<*>> select(
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)
select(page, limit, object : TypeReference<List<R>>() {}, values.toMap(), block)
/* Execute function without traitements */
override fun exec(values: List<Any?>): ResultSet {
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?>): ResultSet {
override fun exec(values: Map<String, Any?>): QueryResult {
val args = compileArgs(values)
val sql = "SELECT * FROM ${definition.name} ($args)"
@@ -195,11 +195,11 @@ class Function(val definition: Function, override val connection: Connection): E
val parameters = definition.getParametersIndexedByName()
val placeholders = values
.filter { entry ->
val parameter = parameters[entry.key] ?: error("Parameter ${entry.key} not exist")
val parameter = parameters[entry.key] ?: parameters["_" + entry.key] ?: error("Parameter ${entry.key} of function ${definition.name} not exist")
parameter.default === null || entry.value !== null
}
.map { entry ->
val parameter = parameters[entry.key]!!
val parameter = parameters[entry.key] ?: parameters["_" + entry.key] ?: error("Parameter ${entry.key} of function ${definition.name} not exist")
""""${parameter.name}" := :${parameter.name}::${parameter.type}"""
}

View File

@@ -1,8 +1,9 @@
package fr.postgresjson.connexion
import fr.postgresjson.entity.EntityI
import kotlin.math.ceil
data class Paginated<T: EntityI<*>>(
data class Paginated<T : EntityI>(
val result: List<T>,
val offset: Int,
val limit: Int,
@@ -10,10 +11,15 @@ data class Paginated<T: EntityI<*>>(
) {
val currentPage: Int = (offset / limit) + 1
val count: Int = result.size
val totalPages: Int = (total.toDouble() / limit.toDouble()).ceil()
init {
if (offset < 0) error("offset must be greather or equal than 0")
if (limit < 1) error("limit must be greather or equal than 1")
if (total < 0) error("total must be greather or equal than 0")
}
fun isLastPage(): Boolean = currentPage >= totalPages
private fun Double.ceil(): Int = ceil(this).toInt()
}

View File

@@ -1,102 +1,101 @@
package fr.postgresjson.connexion
import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI
import java.sql.ResultSet
class Query(override val name: String, private val sql: String, override val connection: Connection): EmbedExecutable {
class Query(override val name: String, private val sql: String, override val connection: Connection) : EmbedExecutable {
override fun toString(): String {
return sql
}
/* Select One */
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: List<Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {}
): R? =
select(object: TypeReference<R>() {}, values, block)
select(object : TypeReference<R>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<R>,
values: Map<String, Any?>,
block: (ResultSet, R?) -> Unit
block: (QueryResult, R?) -> Unit
): R? {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R: EntityI<*>> selectOne(
inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {}
): R? =
select(object: TypeReference<R>() {}, values, block)
select(object : TypeReference<R>() {}, values, block)
/* Select Multiples */
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: List<Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {}
): List<R> =
select(object: TypeReference<List<R>>() {}, values, block)
select(object : TypeReference<List<R>>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, List<R>) -> Unit
block: (QueryResult, List<R>) -> Unit
): List<R> {
return connection.select(this.toString(), typeReference, values, block)
}
inline fun <reified R: EntityI<*>> select(
inline fun <reified R : EntityI> select(
values: Map<String, Any?>,
noinline block: SelectCallback<R> = {}
): List<R> =
select(object: TypeReference<List<R>>() {}, values, block)
select(object : TypeReference<List<R>>() {}, values, block)
override fun <R: EntityI<*>> select(
override fun <R : EntityI> select(
page: Int,
limit: Int,
typeReference: TypeReference<List<R>>,
values: Map<String, Any?>,
block: (ResultSet, 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(
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)
select(page, limit, object : TypeReference<List<R>>() {}, values, block)
/* Execute function without traitements */
override fun exec(values: List<Any?>): ResultSet {
override fun exec(values: List<Any?>): QueryResult {
return connection.exec(sql, values)
}
override fun exec(values: Map<String, Any?>): ResultSet {
override fun exec(values: Map<String, Any?>): QueryResult {
return connection.exec(sql, values)
}

View File

@@ -80,7 +80,7 @@ class Requester(
password: String = "dc-project",
queriesDirectory: File? = null,
functionsDirectory: File? = null
): this(
) : this(
Connection(host = host, port = port, database = database, username = username, password = password),
queriesDirectory,
functionsDirectory

View File

@@ -2,18 +2,17 @@ package fr.postgresjson.definition
import java.io.File
open class Function(
override val script: String
): Resource, ParametersInterface {
val returns: String?
) : Resource, ParametersInterface {
val returns: String
override val name: String
override val parameters: List<Parameter>
override var source: File? = null
init {
val functionRegex =
"""create (or replace )?(procedure|function) *(?<name>[^(\s]+)\s*\((?<params>(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?<return>RETURNS *[^ ]+)?"""
"""create (or replace )?(procedure|function) *(?<name>[^(\s]+)\s*\((?<params>(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?<return>RETURNS *[^ \n]+)?"""
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
val paramsRegex =
@@ -22,9 +21,9 @@ open class Function(
val queryMatch = functionRegex.find(script)
if (queryMatch !== null) {
val functionName = queryMatch.groups.get("name")?.value?.trim()
val functionName = queryMatch.groups.get("name")?.value?.trim() ?: error("Function name not found")
val functionParameters = queryMatch.groups["params"]?.value?.trim()
this.returns = queryMatch.groups["return"]?.value?.trim()
this.returns = queryMatch.groups["return"]?.value?.trim() ?: ""
/* Create parameters definition */
val parameters = if (functionParameters !== null) {
@@ -40,23 +39,22 @@ open class Function(
} else {
listOf()
}
this.name = functionName!!
this.name = functionName
this.parameters = parameters
} else {
throw FunctionNotFound()
}
}
abstract class ParseException(message: String, cause: Throwable? = null): Exception(message, cause)
class FunctionNotFound(cause: Throwable? = null): ParseException("Function not found in script", cause)
abstract class ParseException(message: String, cause: Throwable? = null) : Exception(message, cause)
class FunctionNotFound(cause: Throwable? = null) : ParseException("Function not found in script", cause)
fun getDefinition(): String {
return parameters
.filter { it.direction == Parameter.Direction.IN }
.joinToString(", ") { "${it.name} ${it.type}" }.let {
"$name ($it) $returns"
"$name ($it)"
}
}
fun getParametersIndexedByName(): Map<String, Parameter> {

View File

@@ -18,7 +18,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,
type = type,
direction = direction?.let { Direction.valueOf(direction.toUpperCase()) },

View File

@@ -1,42 +0,0 @@
package fr.postgresjson.entity
import kotlin.reflect.KClass
class EntitiesCollections {
private val collections: MutableMap<KClass<*>, EntityCollection<Any, EntityI<Any?>>> = mutableMapOf()
fun <I, R: EntityI<I?>> get(id: I, className: KClass<R>): R? {
val collection = collections[className]
val entity = collection?.get(id!!)
return entity as R?
}
inline fun <I, reified R: EntityI<I?>> get(id: I): R? {
return get(id, R::class)
}
fun <I, R: EntityI<out I?>> set(entity: R): EntitiesCollections {
if (collections[entity.className] == null) {
collections[entity.className] = EntityCollection()
}
collections[entity.className]!!.set(entity as EntityI<Any?>)
return this
}
class EntityCollection<T, E: EntityI<T?>> {
private var collection: MutableMap<T, E> = mutableMapOf()
fun get(id: T): E? {
return collection[id]
}
fun set(entity: E) {
val id = entity.id
if (id !== null) {
collection[id] = entity
}
}
}
}

View File

@@ -1,90 +1,5 @@
package fr.postgresjson.entity
import com.fasterxml.jackson.annotation.JsonIgnore
import org.joda.time.DateTime
import java.util.*
import kotlin.reflect.KClass
/* ID */
interface EntityI<T> {
var id: T?
val className: KClass<EntityI<T?>>
@JsonIgnore() get() = this::class as KClass<EntityI<T?>>
}
abstract class Entity<T>(override var id: T? = null): EntityI<T?>
abstract class UuidEntity(override var id: UUID? = UUID.randomUUID()): Entity<UUID?>(id)
abstract class IdEntity(override var id: Int? = null): Entity<Int?>(id)
/* Version */
interface EntityVersioning<T> {
var version: T
}
interface EntityVersioningIncrement: EntityVersioning<Int?>
class EntityVersioningIncrementImp: EntityVersioningIncrement {
override var version: Int? = null
}
interface EntityVersioningDate: EntityVersioning<DateTime?>
class EntityVersioningDateImp: EntityVersioningDate {
override var version: DateTime? = null
}
/* Dates */
interface EntityCreatedAt {
var createdAt: DateTime?
}
interface EntityUpdatedAt {
var updatedAt: DateTime?
}
class EntityCreatedAtImp: EntityCreatedAt {
override var createdAt: DateTime? = null
}
class EntityUpdatedAtImp: EntityUpdatedAt {
override var updatedAt: DateTime? = null
}
/* Author */
interface CreatedBy<T: EntityI<*>> {
var createdBy: T?
}
interface UpdatedBy<T: EntityI<*>> {
var updatedBy: T?
}
class EntityCreatedByImp<UserT: EntityI<*>>: CreatedBy<UserT> {
override var createdBy: UserT? = null
}
class EntityUpdatedByImp<UserT: EntityI<*>>: UpdatedBy<UserT> {
override var updatedBy: UserT? = null
}
/* Published */
interface Published<UserT: EntityI<*>> {
var publishedAt: DateTime?
var publishedBy: UserT?
}
class EntityPublishedImp<UserT: EntityI<*>>: Published<UserT> {
override var publishedAt: DateTime? = null
override var publishedBy: UserT? = null
}
/* Implementation */
abstract class EntityImp<T, UserT: EntityI<*>>: Entity<T>(),
EntityCreatedAt by EntityCreatedAtImp(),
EntityUpdatedAt by EntityUpdatedAtImp(),
CreatedBy<UserT> by EntityCreatedByImp(),
UpdatedBy<UserT> by EntityUpdatedByImp()
abstract class EntityExtended<T, UserT: EntityI<*>>:
EntityImp<T, UserT>(),
EntityVersioningIncrement by EntityVersioningIncrementImp(),
Published<UserT> by EntityPublishedImp()
interface Serializable
interface EntityI : Serializable
interface Parameter : Serializable

View File

@@ -0,0 +1,88 @@
package fr.postgresjson.entity.immutable
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.mutable.EntityDeletedAt
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
import fr.postgresjson.entity.mutable.EntityDeletedBy
import fr.postgresjson.entity.mutable.EntityDeletedByImp
import org.joda.time.DateTime
import java.util.*
interface EntityRefI<T> : EntityI {
val id: T
}
interface UuidEntityI : EntityRefI<UUID> {
override val id: UUID
}
abstract class Entity<T>(override val id: T) : EntityRefI<T>
open class UuidEntity(override val id: UUID = UUID.randomUUID()) : UuidEntityI, Entity<UUID>(id)
/* Version */
interface EntityVersioning<ID, NUMBER> {
val versionNumber: NUMBER
val versionId: ID
}
class UuidEntityVersioning(
override var versionNumber: Int? = null,
override val versionId: UUID = UUID.randomUUID()
) : EntityVersioning<UUID, Int?>
/* Dates */
interface EntityCreatedAt {
val createdAt: DateTime
}
interface EntityUpdatedAt {
var updatedAt: DateTime
}
class EntityCreatedAtImp(
override val createdAt: DateTime = DateTime.now()
) : EntityCreatedAt
class EntityUpdatedAtImp(
override var updatedAt: DateTime = DateTime.now()
) : EntityUpdatedAt
/* Author */
interface EntityCreatedBy<T : EntityI> {
val createdBy: T
}
interface EntityUpdatedBy<T : EntityI> {
var updatedBy: T
}
class EntityCreatedByImp<UserT : EntityI>(
override val createdBy: UserT
) : EntityCreatedBy<UserT>
class EntityUpdatedByImp<UserT : EntityI>(
override var updatedBy: UserT
) : EntityUpdatedBy<UserT>
/* Mixed */
class EntityCreatedImp<UserT : EntityI>(
override val createdAt: DateTime = DateTime.now(),
createdBy: UserT
) : EntityCreatedBy<UserT> by EntityCreatedByImp(createdBy),
EntityCreatedAt by EntityCreatedAtImp()
class EntityUpdatedImp<UserT : EntityI>(
updatedAt: DateTime = DateTime.now(),
override var updatedBy: UserT
) : EntityUpdatedBy<UserT>,
EntityUpdatedAt by EntityUpdatedAtImp(updatedAt)
/* Implementation */
abstract class EntityImp<T, UserT : EntityI>(
updatedBy: UserT,
updatedAt: DateTime = DateTime.now()
) : UuidEntity(),
EntityCreatedAt by EntityCreatedAtImp(updatedAt),
EntityUpdatedAt by EntityUpdatedAtImp(updatedAt),
EntityDeletedAt by EntityDeletedAtImp(),
EntityCreatedBy<UserT> by EntityCreatedByImp(updatedBy),
EntityUpdatedBy<UserT> by EntityUpdatedByImp(updatedBy),
EntityDeletedBy<UserT> by EntityDeletedByImp(updatedBy)

View File

@@ -0,0 +1,135 @@
package fr.postgresjson.entity.mutable
import fr.postgresjson.entity.EntityI
import org.joda.time.DateTime
import java.util.*
interface EntityRefI<T> : EntityI {
var id: T?
}
interface UuidEntityI : EntityRefI<UUID> {
override var id: UUID?
}
interface IdEntityI : EntityRefI<Int> {
override var id: Int?
}
abstract class Entity<T>(override var id: T? = null) : EntityRefI<T>
open class UuidEntity(override var id: UUID? = UUID.randomUUID()) : UuidEntityI, Entity<UUID>(id)
open class IdEntity(override var id: Int? = null) : IdEntityI, Entity<Int>(id)
/* Version */
interface EntityVersioning<ID, NUMBER> {
var versionId: ID
var versionNumber: NUMBER?
}
class UuidEntityVersioning(
override var versionNumber: Int? = null,
override var versionId: UUID = UUID.randomUUID()
) : EntityVersioning<UUID, Int>
/* Dates */
interface EntityCreatedAt {
var createdAt: DateTime?
}
interface EntityUpdatedAt {
var updatedAt: DateTime?
}
interface EntityDeletedAt {
var deletedAt: DateTime?
fun isDeleted(): Boolean {
val deletedAt = deletedAt
return deletedAt != null && deletedAt < DateTime.now()
}
}
class EntityCreatedAtImp : EntityCreatedAt {
override var createdAt: DateTime? = null
}
class EntityUpdatedAtImp : EntityUpdatedAt {
override var updatedAt: DateTime? = null
}
class EntityDeletedAtImp : EntityDeletedAt {
override var deletedAt: DateTime? = null
}
/* Author */
interface EntityCreatedBy<T : EntityI> {
var createdBy: T?
}
interface EntityUpdatedBy<T : EntityI> {
var updatedBy: T?
}
interface EntityDeletedBy<T : EntityI> {
var deletedBy: T?
}
class EntityCreatedByImp<UserT : EntityI>(
override var createdBy: UserT?
) : EntityCreatedBy<UserT>
class EntityUpdatedByImp<UserT : EntityI>(
override var updatedBy: UserT?
) : EntityUpdatedBy<UserT>
class EntityDeletedByImp<UserT : EntityI>(
override var deletedBy: UserT?
) : EntityDeletedBy<UserT>
/* Mixed */
class EntityDeletedImp<UserT : EntityI>(
override var deletedBy: UserT? = null
) : EntityDeletedBy<UserT>,
EntityDeletedAt by EntityDeletedAtImp()
class EntityUpdatedImp<UserT : EntityI>(
override var updatedAt: DateTime? = null,
override var updatedBy: UserT? = null
) : EntityUpdatedBy<UserT>,
EntityUpdatedAt by EntityUpdatedAtImp()
class EntityCreatedImp<UserT : EntityI>(
override var createdAt: DateTime? = null,
override var createdBy: UserT? = null
) : EntityCreatedBy<UserT>,
EntityCreatedAt by EntityCreatedAtImp()
/* Published */
interface Published<UserT : EntityI> {
var publishedAt: DateTime?
var publishedBy: UserT?
}
class EntityPublishedImp<UserT : EntityI>(
override var publishedBy: UserT?
) : Published<UserT> {
override var publishedAt: DateTime? = null
}
/* Implementation */
abstract class EntityImp<T, UserT : EntityI>(
updatedBy: UserT?
) : Entity<T>(),
EntityCreatedAt by EntityCreatedAtImp(),
EntityUpdatedAt by EntityUpdatedAtImp(),
EntityDeletedAt by EntityDeletedAtImp(),
EntityCreatedBy<UserT> by EntityCreatedByImp(updatedBy),
EntityUpdatedBy<UserT> by EntityUpdatedByImp(updatedBy),
EntityDeletedBy<UserT> by EntityDeletedByImp(updatedBy)
abstract class UuidEntityExtended<T, UserT : EntityI>(
updatedBy: UserT?,
publishedBy: UserT?
) :
EntityImp<T, UserT>(updatedBy),
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
Published<UserT> by EntityPublishedImp(publishedBy)

View File

@@ -1,9 +1,11 @@
package fr.postgresjson.migration
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.postgresjson.connexion.Connection
import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status
import java.util.*
import java.util.concurrent.*
import fr.postgresjson.definition.Function as DefinitionFunction
data class Function(
@@ -11,7 +13,7 @@ data class Function(
val down: DefinitionFunction,
private val connection: Connection,
override var executedAt: Date? = null
): Migration {
) : Migration {
val name = up.name
override var doExecute: Action? = null
@@ -26,7 +28,7 @@ data class Function(
down: String,
connection: Connection,
executedAt: Date? = null
): this(
) : this(
DefinitionFunction(up),
DefinitionFunction(down),
connection,
@@ -34,7 +36,15 @@ data class Function(
)
override fun up(): Status {
try {
connection.sendQuery(up.script)
} catch (e: CompletionException) {
val cause = e.cause
if (cause is GenericDatabaseException && cause.errorMessage.fields['C'] == "42P13") {
connection.sendQuery("drop function ${down.getDefinition()}")
connection.sendQuery(up.script)
}
}
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 ->
@@ -49,7 +59,7 @@ data class Function(
connection.sendQuery(down.script)
this::class.java.classLoader.getResource("sql/migration/deleteFunction.sql")!!.readText().let {
connection.sendQuery(it, listOf(down))
connection.sendQuery(it, listOf(down.name))
}
return Status.OK
}
@@ -59,17 +69,17 @@ data class Function(
up()
down()
it.sendQuery("ROLLBACK")
}
}.join()
return Status.OK // TODO
}
override fun status(): Status {
val result = connection.inTransaction {
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}
}.join()
return Status.OK // TODO
}

View File

@@ -3,7 +3,7 @@ package fr.postgresjson.migration
import com.fasterxml.jackson.core.type.TypeReference
import fr.postgresjson.connexion.Connection
import fr.postgresjson.definition.Function.FunctionNotFound
import fr.postgresjson.entity.Entity
import fr.postgresjson.entity.mutable.Entity
import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status
import fr.postgresjson.utils.LoggerDelegate
@@ -19,7 +19,7 @@ class MigrationEntity(
val up: String,
val down: String,
val version: Int
): Entity<String?>(filename)
) : Entity<String?>(filename)
interface Migration {
var executedAt: Date?
@@ -38,13 +38,23 @@ data class Migrations private constructor(
private val queries: MutableMap<String, Query> = mutableMapOf(),
private val functions: MutableMap<String, Function> = mutableMapOf()
) {
private var directories: List<File> = emptyList()
private val logger: Logger? by LoggerDelegate()
constructor(directory: File, connection: Connection): this(listOf(directory), connection)
constructor(directory: File, connection: Connection) : this(listOf(directory), connection)
constructor(directories: List<File>, connection: Connection): this(connection) {
constructor(directories: List<File>, connection: Connection) : this(connection) {
initDB()
this.directories = directories
reset()
}
fun reset() {
queries.clear()
functions.clear()
getMigrationFromDB()
getMigrationFromDirectory(directories)
queries.forEach { (_, query) ->
if (query.doExecute === null) {
query.doExecute = Action.DOWN
@@ -65,14 +75,14 @@ data class Migrations private constructor(
*/
private fun getMigrationFromDB() {
this::class.java.classLoader.getResource("sql/migration/findAllFunction.sql")!!.readText().let {
connection.select<MigrationEntity>(it, object: TypeReference<List<MigrationEntity>>() {})
connection.select<MigrationEntity>(it, object : TypeReference<List<MigrationEntity>>() {})
.map { function ->
functions[function.filename] = Function(function.up, function.down, connection, function.executedAt)
}
}
this::class.java.classLoader.getResource("sql/migration/findAllHistory.sql")!!.readText().let {
connection.select<MigrationEntity>(it, object: TypeReference<List<MigrationEntity>>() {})
connection.select<MigrationEntity>(it, object : TypeReference<List<MigrationEntity>>() {})
.map { query ->
queries[query.filename] = Query(query.filename, query.up, query.down, connection, query.executedAt)
}
@@ -112,7 +122,7 @@ data class Migrations private constructor(
val fileContent = file.readText()
try {
addFunction(fileContent)
} catch(e: FunctionNotFound) {
} catch (e: FunctionNotFound) {
// Nothing
}
}
@@ -121,13 +131,14 @@ data class Migrations private constructor(
enum class Direction { UP, DOWN }
internal class DownMigrationNotDefined(path: String, cause: FileNotFoundException):
internal class DownMigrationNotDefined(path: String, cause: FileNotFoundException) :
Throwable("The file $path whas not found", cause)
fun addFunction(newDefinition: DefinitionFunction, callback: (Function) -> Unit = {}): Migrations {
val currentFunction = functions[newDefinition.name]
if (currentFunction === null || currentFunction `is different from` newDefinition) {
functions[newDefinition.name] = Function(newDefinition, newDefinition, connection).apply {
val oldDefinition = functions[newDefinition.name]?.up ?: newDefinition
functions[newDefinition.name] = Function(newDefinition, oldDefinition, connection).apply {
doExecute = Action.UP
}
} else {
@@ -243,6 +254,7 @@ data class Migrations private constructor(
sendQuery("COMMIT")
}
logger?.info("Migration done")
reset()
return list.toMap()
}
@@ -263,6 +275,7 @@ data class Migrations private constructor(
sendQuery("COMMIT")
}
logger?.info("Migration DOWN done")
reset()
return list.toMap()
}

View File

@@ -1,7 +1,7 @@
package fr.postgresjson.migration
import fr.postgresjson.connexion.Connection
import fr.postgresjson.entity.Entity
import fr.postgresjson.entity.mutable.Entity
import fr.postgresjson.migration.Migration.Action
import java.util.*
@@ -11,7 +11,7 @@ data class Query(
val down: String,
private val connection: Connection,
override var executedAt: Date? = null
): Migration, Entity<String?>(name) {
) : Migration, Entity<String?>(name) {
override var doExecute: Action? = null
override fun up(): Migration.Status {
@@ -31,7 +31,7 @@ data class Query(
connection.sendQuery(down)
this::class.java.classLoader.getResource("sql/migration/deleteHistory.sql")!!.readText().let {
connection.sendQuery(it, listOf(name))
connection.exec(it, listOf(name))
}
return Migration.Status.OK
@@ -42,17 +42,17 @@ data class Query(
up()
down()
it.sendQuery("ROLLBACK")
}
}.join()
return Migration.Status.OK // TODO
}
override fun status(): Migration.Status {
val result = connection.inTransaction {
connection.inTransaction {
up()
down()
it.sendQuery("ROLLBACK")
}
}.join()
return Migration.Status.OK // TODO
}

View File

@@ -1,13 +0,0 @@
package fr.postgresjson.repository
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.EntityI
import kotlin.reflect.KClass
interface RepositoryI<E: EntityI<*>> {
val entityName: KClass<E>
var requester: Requester
fun getClassName(): String {
return entityName.simpleName!!
}
}

View File

@@ -0,0 +1,12 @@
package fr.postgresjson.repository
import fr.postgresjson.connexion.Requester
interface RepositoryI {
val requester: Requester
enum class Direction {
asc,
desc
}
}

View File

@@ -1,47 +1,37 @@
package fr.postgresjson.serializer
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import fr.postgresjson.entity.EntitiesCollections
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.IdEntity
import fr.postgresjson.entity.UuidEntity
import java.io.IOException
import java.util.*
import fr.postgresjson.entity.Serializable
class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
var collection: EntitiesCollections = EntitiesCollections()
init {
val module = SimpleModule()
module.addDeserializer(UuidEntity::class.java, EntityUuidDeserializer(collection))
module.addDeserializer(IdEntity::class.java, EntityIdDeserializer(collection))
mapper.registerModule(module)
mapper.propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
mapper.registerModule(JodaModule())
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
fun <T> serialize(source: EntityI<T>, pretty: Boolean = false): String {
fun serialize(source: Any, pretty: Boolean = false): String {
return if (pretty) mapper.writerWithDefaultPrettyPrinter().writeValueAsString(source)
else mapper.writeValueAsString(source)
}
fun <E: EntityI<*>> deserialize(json: String, valueTypeRef: TypeReference<E>): E {
fun <E> deserialize(json: String, valueTypeRef: TypeReference<E>): E {
return this.mapper.readValue(json, valueTypeRef)
}
inline fun <reified E: EntityI<*>> deserialize(json: String): E? {
inline fun <reified E> deserialize(json: String): E? {
return this.mapper.readValue(json)
}
@@ -50,53 +40,15 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
}
inline fun <reified E> deserializeList(json: String): E {
return deserializeList(json, object: TypeReference<E>() {})
return deserializeList(json, object : TypeReference<E>() {})
}
fun <E: EntityI<*>> deserialize(json: String, target: E): E {
fun <E> deserialize(json: String, target: E): E {
return mapper.readerForUpdating(target).readValue<E>(json)
}
}
fun <T> EntityI<T?>.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty)
inline fun <reified E: EntityI<*>> E.deserialize(json: String) = Serializer().deserialize(json, this)
inline fun <reified E: EntityI<*>> String.deserialize() = Serializer().deserialize<E>(this)
class EntityUuidDeserializer<T: UuidEntity> @JvmOverloads constructor(vc: Class<*>? = null): StdDeserializer<T>(vc) {
var collection: EntitiesCollections = EntitiesCollections()
constructor(collection: EntitiesCollections): this() {
this.collection = collection
}
@Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): T {
val node = jp.codec.readTree<JsonNode>(jp)
val id = node.get("id").asText()
val entity = collection.get<UUID, UuidEntity>(UUID.fromString(id))
return (entity ?: ctxt.readValue(jp, UuidEntity::class.javaObjectType)) as T
}
}
class EntityIdDeserializer<T: IdEntity> @JvmOverloads constructor(vc: Class<*>? = null): StdDeserializer<T>(vc) {
var collection: EntitiesCollections = EntitiesCollections()
constructor(collection: EntitiesCollections): this() {
this.collection = collection
}
@Throws(IOException::class, JsonProcessingException::class)
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): T {
val node = jp.codec.readTree<JsonNode>(jp)
val id = node.get("id").asInt()
val entity = collection.get<Int?, IdEntity>(id)
val obj = (entity ?: ctxt.readValue(jp, UuidEntity::class.javaObjectType)) as EntityI<Int?>
collection.set(obj)
return obj as T
}
}
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)

View File

@@ -5,6 +5,6 @@ import org.slf4j.LoggerFactory
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
internal class LoggerDelegate<in R: Any>: ReadOnlyProperty<R, Logger> {
internal class LoggerDelegate<in R : Any> : ReadOnlyProperty<R, Logger> {
override fun getValue(thisRef: R, property: KProperty<*>) = LoggerFactory.getLogger(thisRef.javaClass.packageName)
}

View File

@@ -1,17 +1,20 @@
package fr.postgresjson
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.entity.IdEntity
import fr.postgresjson.entity.mutable.IdEntity
import fr.postgresjson.entity.Parameter
import org.junit.Assert.*
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ConnectionTest(): TestAbstract() {
private class ObjTest(var name: String): IdEntity()
private class ObjTest2(var title: String, var test: ObjTest?): IdEntity()
private class ObjTest3(var first: String, var seconde: String, var third: Int): IdEntity()
class ConnectionTest() : TestAbstract() {
private class ObjTest(var name: String) : IdEntity()
private class ObjTest2(var title: String, var test: ObjTest?) : IdEntity()
private class ObjTest3(var first: String, var seconde: String, var third: Int) : IdEntity()
private class ObjTestWithParameterObject(var first: ParameterObject, var seconde: ParameterObject) : IdEntity()
private class ParameterObject(var third: String) : Parameter
@Test
fun getObject() {
@@ -35,8 +38,7 @@ class ConnectionTest(): TestAbstract() {
""".trimIndent()
)
assertNotNull(objs)
assertTrue(objs is List<ObjTest2>)
assertEquals(objs!!.size, 2)
assertEquals(objs.size, 2)
assertEquals(objs[0].id, 1)
assertEquals(objs[0].test!!.id, 1)
}
@@ -63,7 +65,7 @@ class ConnectionTest(): TestAbstract() {
fun callExec() {
val o = ObjTest("myName")
val result = connection.exec("select json_build_object('id', 1, 'name', ?::json->>'name')", listOf(o))
Assertions.assertNotNull(result.getString(1))
Assertions.assertEquals(1, result.rowsAffected)
}
@Test
@@ -81,6 +83,19 @@ class ConnectionTest(): TestAbstract() {
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)",
mapOf(
"first" to ParameterObject("one"),
"seconde" to ParameterObject("two")
)
)
assertEquals("one", result!!.first.third)
assertEquals("two", result.seconde.third)
}
@Test
fun `select with named parameters`() {
val params: Map<String, Any?> = mapOf(
@@ -157,7 +172,7 @@ class ConnectionTest(): TestAbstract() {
params
) {
assertEquals("ff", it!!.first)
assertEquals("plop", getString("other"))
assertEquals("plop", rows[0].getString("other"))
}
assertNotNull(result)
assertEquals("ff", result!!.first)

View File

@@ -1,25 +1,26 @@
package fr.postgresjson
import fr.postgresjson.entity.*
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.mutable.*
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class EntityTest() {
private class User(override var id: Int?): EntityI<Int?>
private class ObjTest(var name: String): EntityExtended<Int?, User>()
private class User(id: Int?) : Entity<Int?>(id)
private class ObjTest(var name: String) : UuidEntityExtended<Int?, User>(User(1), User(2))
@Test
fun getObject() {
val obj: ObjTest? = ObjTest("plop")
assertTrue(obj is ObjTest)
assertTrue(obj is EntityExtended<Int?, User>)
assertTrue(obj is EntityI<Int?>)
assertTrue(obj is UuidEntityExtended<Int?, User>)
assertTrue(obj is EntityI)
assertTrue(obj is Entity<Int?>)
assertTrue(obj is Published<User>)
assertTrue(obj is CreatedBy<User>)
assertTrue(obj is UpdatedBy<User>)
assertTrue(obj is EntityCreatedBy<User>)
assertTrue(obj is EntityUpdatedBy<User>)
assertTrue(obj is EntityCreatedAt)
assertTrue(obj is EntityUpdatedAt)
}

View File

@@ -13,7 +13,7 @@ import org.junit.jupiter.api.TestInstance
import java.io.File
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MigrationTest(): TestAbstract() {
class MigrationTest() : TestAbstract() {
@Test
fun `run up query`() {
val resources = File(this::class.java.getResource("/sql/migrations").toURI())
@@ -79,7 +79,7 @@ class MigrationTest(): TestAbstract() {
@Test
fun `run migrations force down`() {
val resources = File(this::class.java.getResource("/sql/real_migrations").toURI())
val resourcesFunctions = File(this::class.java.getResource("/sql/function").toURI())
val resourcesFunctions = File(this::class.java.getResource("/sql/function/Test").toURI())
Migrations(listOf(resources, resourcesFunctions), connection).apply {
up().apply {
size `should be equal to` 6
@@ -94,7 +94,7 @@ class MigrationTest(): TestAbstract() {
@Test
fun `run functions migrations`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
Migrations(resources, connection).apply {
run().size `should be equal to` 5
}
@@ -107,4 +107,25 @@ class MigrationTest(): TestAbstract() {
Assertions.assertEquals(objTest!!.id, 3)
Assertions.assertEquals(objTest.name, "test")
}
@Test
fun `run functions migrations and drop if exist`() {
val resources = File(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)
.getFunction("test_function_duplicate")
.selectOne(listOf("test"))
Assertions.assertEquals(objTest!!.id, 3)
Assertions.assertEquals(objTest.name, "test")
val resources2 = File(this::class.java.getResource("/sql/function/Test2").toURI())
Migrations(resources2, connection).apply {
run().size `should be equal to` 1
}
}
}

View File

@@ -2,15 +2,14 @@ package fr.postgresjson
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.IdEntity
import fr.postgresjson.entity.mutable.IdEntity
import org.junit.Assert
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import java.io.File
class RequesterTest: TestAbstract() {
class ObjTest(var name:String): IdEntity(1)
class RequesterTest : TestAbstract() {
class ObjTest(var name: String) : IdEntity(1)
@Test
fun `get query from file`() {
@@ -26,7 +25,7 @@ class RequesterTest: TestAbstract() {
@Test
fun `get function from file`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val objTest: ObjTest? = Requester(connection)
.addFunction(resources)
.getFunction("test_function")
@@ -44,18 +43,18 @@ class RequesterTest: TestAbstract() {
.getQuery("Test/selectOne")
.exec()
assertNotNull(result.getString(1))
assertEquals(1, result.rowsAffected)
}
@Test
fun `call exec on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val result = Requester(connection)
.addFunction(resources)
.getFunction("test_function")
.exec(listOf("test", "plip"))
assertNotNull(result.getString(1))
assertEquals(1, result.rowsAffected)
}
@Test
@@ -71,7 +70,7 @@ class RequesterTest: TestAbstract() {
@Test
fun `call sendQuery on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val result = Requester(connection)
.addFunction(resources)
.getFunction("function_void")
@@ -82,7 +81,7 @@ class RequesterTest: TestAbstract() {
@Test
fun `call selectOne on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val obj: ObjTest = Requester(connection)
.addFunction(resources)
.getFunction("test_function")
@@ -93,7 +92,7 @@ class RequesterTest: TestAbstract() {
@Test
fun `call selectOne on function with object`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val obj2 = ObjTest("original")
val obj: ObjTest = Requester(connection)
.addFunction(resources)
@@ -117,7 +116,7 @@ class RequesterTest: TestAbstract() {
@Test
fun `call select (multiple) on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI())
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val obj: List<ObjTest>? = Requester(connection)
.addFunction(resources)
.getFunction("test_function_multiple")
@@ -134,10 +133,10 @@ class RequesterTest: TestAbstract() {
.getQuery("Test/selectPaginated")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff")
Assert.assertEquals(result.result[1].name, "ff-2")
Assert.assertEquals(result.total, 10)
Assert.assertEquals(result.offset, 0)
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)
}
@Test
@@ -148,10 +147,10 @@ class RequesterTest: TestAbstract() {
.getFunction("test_function_paginated")
.select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff")
Assert.assertEquals(result.result[1].name, "ff-2")
Assert.assertEquals(result.total, 10)
Assert.assertEquals(result.offset, 0)
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)
}
@Test
@@ -162,7 +161,7 @@ class RequesterTest: TestAbstract() {
.getQuery("Test/selectOneWithParameters")
.selectOne(mapOf("name" to "myName")) {
assertEquals("myName", it!!.name)
Assert.assertEquals("plop", getString("other"))
Assert.assertEquals("plop", rows[0].getString("other"))
}!!
assertEquals("myName", obj.name)

View File

@@ -1,6 +1,6 @@
package fr.postgresjson
import fr.postgresjson.entity.IdEntity
import fr.postgresjson.entity.mutable.IdEntity
import fr.postgresjson.serializer.Serializer
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
@@ -41,6 +41,14 @@ internal class SerializerTest {
assertTrue(json.contains(""""val1":"plop","val2":123"""))
}
@Test
fun serializeList() {
val list = listOf(ObjTest("one", 1), ObjTest("two", 2))
val json = list.serialize()
assertTrue(json.contains(""""val1":"one","val2":1"""))
assertTrue(json.contains(""""val1":"two","val2":2"""))
}
@Test
fun serializeDate() {
val objDate = ObjTestDate(DateTime.parse("2019-07-30T14:08:51.420108+04:00"))

View File

@@ -9,23 +9,22 @@ import java.io.File
@TestInstance(PER_CLASS)
abstract class TestAbstract {
protected val connection = Connection(database = "test", username = "test", password = "test")
protected val connection = Connection(database = "test_json", username = "test", password = "test")
@BeforeEach
fun beforeAll() {
val initSQL = File(this::class.java.getResource("/fixtures/init.sql").toURI())
connection
.connect()
.createStatement()
.executeUpdate(initSQL.readText())
.sendQuery(initSQL.readText())
.join()
}
@AfterEach
fun afterAll() {
val downSQL = File(this::class.java.getResource("/fixtures/down.sql").toURI())
connection.connect().apply {
createStatement()
.executeUpdate(downSQL.readText())
}.close()
sendQuery(downSQL.readText()).join()
}.disconnect()
}
}

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE FUNCTION test_function_duplicate (name text default 'plop') returns json
LANGUAGE plpgsql
AS
$$
BEGIN
return json_build_object('id', 3, 'name', name);
END;
$$

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE FUNCTION test_function_duplicate (name text default 'plop', out result text)
LANGUAGE plpgsql
AS
$$
BEGIN
result = name;
END;
$$