31 Commits
jdbc ... 1.0.4

Author SHA1 Message Date
Fabrice Lecomte
4c73ef1e0f set JVM to 11 for jitpack 2020-03-25 16:35:18 +01:00
Fabrice Lecomte
4c03eb7c87 set JVM to 11 2020-03-25 16:25:55 +01:00
Fabrice Lecomte
07678afbe7 set JVM to 11 2020-03-25 16:18:17 +01:00
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
51 changed files with 836 additions and 458 deletions

1
.gitignore vendored
View File

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

1
.idea/.gitignore generated vendored
View File

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

View File

@@ -1,7 +1,13 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<JetCodeStyleSettings> <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="SPACE_BEFORE_EXTEND_COLON" value="false" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">

2
.idea/gradle.xml generated
View File

@@ -8,13 +8,11 @@
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
</set> </set>
</option> </option>
<option name="useAutoImport" value="true" />
<option name="useQualifiedModuleNames" value="true" /> <option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="FrameworkDetectionExcludesConfiguration">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK"> <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" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
<component name="TaskProjectConfiguration"> <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"> <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" /> <module name="postgresjson.test" />
<useClassPathOnly />
<extension name="net.ashald.envfile"> <extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" /> <option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" /> <option name="IS_SUBST" value="false" />
@@ -12,7 +13,7 @@
</ENTRIES> </ENTRIES>
</extension> </extension>
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" /> <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="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="directory" /> <option name="TEST_OBJECT" value="directory" />

View File

@@ -1,24 +1,38 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
jacoco
id("maven-publish") 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 = "flecomte"
version = versioning.info.tag
group = "fr.postgresjson"
version = "0.1"
repositories { repositories {
mavenCentral() mavenCentral()
jcenter() jcenter()
} }
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "11"
sourceCompatibility = "11"
targetCompatibility = "11"
}
}
dependencies { dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.31") implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.31")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9") implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9")
implementation("com.github.jasync-sql:jasync-postgresql:0.9.53") implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
implementation("org.slf4j:slf4j-api:1.7.26") implementation("org.slf4j:slf4j-api:1.7.26")
testImplementation("ch.qos.logback:logback-classic:1.2.3") testImplementation("ch.qos.logback:logback-classic:1.2.3")
@@ -28,14 +42,27 @@ dependencies {
testImplementation("org.amshove.kluent:kluent:1.47") testImplementation("org.amshove.kluent:kluent:1.47")
} }
publishing { val sourcesJar by tasks.creating(Jar::class) {
publications { archiveClassifier.set("sources")
create<MavenPublication>("maven") { from(sourceSets.getByName("main").allSource)
groupId = "fr.postgresjson" }
artifactId = "postgresjson"
version = "0.1"
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"]) 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip zipStoreBase=GRADLE_USER_HOME

2
jitpack.yml Normal file
View File

@@ -0,0 +1,2 @@
jdk:
- openjdk11

View File

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

View File

@@ -6,11 +6,13 @@ import com.github.jasync.sql.db.QueryResult
import com.github.jasync.sql.db.pool.ConnectionPool import com.github.jasync.sql.db.pool.ConnectionPool
import com.github.jasync.sql.db.postgresql.PostgreSQLConnection import com.github.jasync.sql.db.postgresql.PostgreSQLConnection
import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder
import com.github.jasync.sql.db.util.length
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.Serializable
import fr.postgresjson.serializer.Serializer import fr.postgresjson.serializer.Serializer
import fr.postgresjson.utils.LoggerDelegate import fr.postgresjson.utils.LoggerDelegate
import org.slf4j.Logger import org.slf4j.Logger
import java.util.concurrent.CompletableFuture import java.util.concurrent.*
typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit typealias SelectOneCallback<T> = QueryResult.(T?) -> Unit
typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit typealias SelectCallback<T> = QueryResult.(List<T>) -> Unit
@@ -38,14 +40,14 @@ class Connection(
fun <A> inTransaction(f: (Connection) -> CompletableFuture<A>) = connect().inTransaction(f) fun <A> inTransaction(f: (Connection) -> CompletableFuture<A>) = connect().inTransaction(f)
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
): R? { ): R? {
val primaryObject = values.firstOrNull { 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? } as R?
val result = exec(sql, compileArgs(values)) val result = exec(sql, compileArgs(values))
val json = result.rows[0].getString(0) val json = result.rows[0].getString(0)
@@ -62,14 +64,14 @@ class Connection(
} }
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
sql: String, sql: String,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): 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, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
@@ -80,14 +82,14 @@ class Connection(
} }
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
sql: String, sql: String,
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): 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, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
@@ -96,7 +98,7 @@ class Connection(
val result = exec(sql, compileArgs(values)) val result = exec(sql, compileArgs(values))
val json = result.rows[0].getString(0) val json = result.rows[0].getString(0)
return if (json === null) { return if (json === null) {
listOf<EntityI<*>>() as List<R> listOf<EntityI>() as List<R>
} else { } else {
serializer.deserializeList(json, typeReference) serializer.deserializeList(json, typeReference)
}.also { }.also {
@@ -104,14 +106,14 @@ class Connection(
} }
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
sql: String, sql: String,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<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, sql: String,
page: Int, page: Int,
limit: Int, limit: Int,
@@ -125,13 +127,13 @@ class Connection(
.plus("limit" to limit) .plus("limit" to limit)
val line = replaceArgs(sql, newValues) { val line = replaceArgs(sql, newValues) {
exec(this.sql, compileArgs(this.parameters)) exec(this.sql, this.parameters)
} }
return line.run { return line.run {
val json = rows[0].getString(0) val json = rows[0].getString(0)
val entities = if (json === null) { val entities = if (json === null) {
listOf<EntityI<*>>() as List<R> listOf<EntityI>() as List<R>
} else { } else {
serializer.deserializeList(json, typeReference) serializer.deserializeList(json, typeReference)
} }
@@ -146,7 +148,7 @@ class Connection(
} }
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
sql: String, sql: String,
page: Int, page: Int,
limit: Int, limit: Int,
@@ -155,7 +157,7 @@ class Connection(
): Paginated<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, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
@@ -166,7 +168,7 @@ class Connection(
} }
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
sql: String, sql: String,
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
@@ -174,8 +176,9 @@ class Connection(
select(sql, object : TypeReference<List<R>>() {}, values, block) select(sql, object : TypeReference<List<R>>() {}, values, block)
override fun exec(sql: String, values: List<Any?>): QueryResult { override fun exec(sql: String, values: List<Any?>): QueryResult {
return stopwatchQuery(sql, values) { val compiledValues = compileArgs(values)
connect().sendPreparedStatement(sql, compileArgs(values)).join() return stopwatchQuery(sql, compiledValues) {
connect().sendPreparedStatement(sql, compiledValues).join()
} }
} }
@@ -185,18 +188,25 @@ class Connection(
} }
} }
override fun sendQuery(sql: String): QueryResult { override fun sendQuery(sql: String, values: List<Any?>): Int {
return stopwatchQuery(sql) { val compiledValues = compileArgs(values)
connect().sendQuery(sql).join() return stopwatchQuery(sql, compiledValues) {
replaceArgsIntoSql(sql, compiledValues) {
connect().sendQuery(it).join().rowsAffected.toInt()
}
}
}
override fun sendQuery(sql: String, values: Map<String, Any?>): Int {
return replaceArgs(sql, values) {
sendQuery(this.sql, this.parameters)
} }
} }
private fun compileArgs(values: List<Any?>): List<Any?> { private fun compileArgs(values: List<Any?>): List<Any?> {
return values.map { return values.map {
if (it is EntityI<*>) { if (it is Serializable || (it is List<*> && it.firstOrNull() is Serializable)) {
serializer.serialize(it).apply { serializer.serialize(it)
serializer.collection.set<Any?, EntityI<Any?>>(it as EntityI<Any?>)
}
} else { } else {
it it
} }
@@ -207,32 +217,66 @@ class Connection(
val paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE) val paramRegex = "(?<!:):([a-zA-Z0-9_-]+)".toRegex(RegexOption.IGNORE_CASE)
val newArgs = paramRegex.findAll(sql).map { match -> val newArgs = paramRegex.findAll(sql).map { match ->
val name = match.groups[1]!!.value val name = match.groups[1]!!.value
values[name] ?: error("Parameter $name missing") values[name] ?: values[name.trimStart('_')] ?: error("Parameter $name missing")
}.toList() }.toList()
var newSql = sql var newSql = sql
values.forEach { (key, _) -> values.forEach { (key, _) ->
val regex = ":$key".toRegex() val regex = ":_?$key".toRegex()
newSql = newSql.replace(regex, "?") newSql = newSql.replace(regex, "?")
} }
return block(ParametersQuery(newSql, newArgs)) 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?>) data class ParametersQuery(val sql: String, val parameters: List<Any?>)
private fun <T> stopwatchQuery(sql: String, values: List<Any?> = emptyList(), callback: () -> T): T { private fun <T> stopwatchQuery(sql: String, values: List<Any?> = emptyList(), callback: () -> T): T {
val sqlForLog = "\n${sql.prependIndent()}"
try { try {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val result = callback() val result = callback()
val duration = (System.currentTimeMillis() - start) 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 return result
} catch (e: Throwable) { } catch (e: Throwable) {
logger?.info("Query Error: $sqlForLog, $values", e) logger?.info("""
Query Error:
${sql.prependIndent()},
${values.joinToString(", ").prependIndent()}
""".trimIndent(), e)
throw e throw e
} }
} }
} }

View File

@@ -13,33 +13,33 @@ interface EmbedExecutable {
/** /**
* Select One entity with list of parameters * Select One entity with list of parameters
*/ */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
fun <R: EntityI<*>> select( fun <R : EntityI> select(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
/* Select Miltiples */ /* Select Miltiples */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
fun <R: EntityI<*>> select( fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
/* Select Paginated */ /* Select Paginated */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
@@ -49,4 +49,14 @@ interface EmbedExecutable {
fun exec(values: List<Any?> = emptyList()): QueryResult fun exec(values: List<Any?> = emptyList()): QueryResult
fun exec(values: Map<String, Any?>): QueryResult fun exec(values: Map<String, Any?>): QueryResult
fun exec(vararg values: Pair<String, Any?>): QueryResult = exec(values.toMap())
fun 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

@@ -7,14 +7,14 @@ import fr.postgresjson.entity.EntityI
interface Executable { interface Executable {
/* Select One */ /* Select One */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
block: SelectOneCallback<R> = {} block: SelectOneCallback<R> = {}
): R? ): R?
fun <R: EntityI<*>> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
@@ -23,14 +23,14 @@ interface Executable {
/* Select Miltiples */ /* Select Miltiples */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
block: SelectCallback<R> = {} block: SelectCallback<R> = {}
): List<R> ): List<R>
fun <R: EntityI<*>> select( fun <R : EntityI> select(
sql: String, sql: String,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
@@ -39,7 +39,7 @@ interface Executable {
/* Select Paginated */ /* Select Paginated */
fun <R: EntityI<*>> select( fun <R : EntityI> select(
sql: String, sql: String,
page: Int, page: Int,
limit: Int, limit: Int,
@@ -50,5 +50,6 @@ interface Executable {
fun exec(sql: String, values: List<Any?> = emptyList()): QueryResult fun exec(sql: String, values: List<Any?> = emptyList()): QueryResult
fun exec(sql: String, values: Map<String, Any?>): QueryResult fun exec(sql: String, values: Map<String, Any?>): QueryResult
fun sendQuery(sql: String): QueryResult fun sendQuery(sql: String, values: List<Any?> = emptyList()): Int
fun sendQuery(sql: String, values: Map<String, Any?>): Int
} }

View File

@@ -17,7 +17,7 @@ class Function(val definition: Function, override val connection: Connection): E
/** /**
* Select One entity with list of parameters * Select One entity with list of parameters
*/ */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
@@ -28,13 +28,13 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block) return connection.select(sql, typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): 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, value: R,
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
@@ -43,7 +43,7 @@ class Function(val definition: Function, override val connection: Connection): E
/** /**
* Select One entity with named parameters * Select One entity with named parameters
*/ */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
@@ -54,13 +54,13 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block) return connection.select(sql, typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): 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?>, vararg values: Pair<String, Any?>,
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
@@ -71,7 +71,7 @@ class Function(val definition: Function, override val connection: Connection): E
/** /**
* Select list of entities with list of parameters * Select list of entities with list of parameters
*/ */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
@@ -82,7 +82,7 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block) return connection.select(sql, typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
@@ -91,7 +91,7 @@ class Function(val definition: Function, override val connection: Connection): E
/** /**
* Select list of entities with named parameters * Select list of entities with named parameters
*/ */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
@@ -102,13 +102,13 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, typeReference, values, block) return connection.select(sql, typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<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?>, vararg values: Pair<String, Any?>,
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<R> = ): List<R> =
@@ -119,7 +119,7 @@ class Function(val definition: Function, override val connection: Connection): E
/** /**
* Select Multiple with pagination * Select Multiple with pagination
*/ */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
@@ -137,7 +137,7 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.select(sql, page, limit, typeReference, values, block) return connection.select(sql, page, limit, typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
values: Map<String, Any?> = emptyMap(), values: Map<String, Any?> = emptyMap(),
@@ -145,7 +145,7 @@ class Function(val definition: Function, override val connection: Connection): E
): Paginated<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, page: Int,
limit: Int, limit: Int,
vararg values: Pair<String, Any?>, vararg values: Pair<String, Any?>,
@@ -169,12 +169,22 @@ class Function(val definition: Function, override val connection: Connection): E
return connection.exec(sql, values) return connection.exec(sql, values)
} }
override fun sendQuery(values: List<Any?>): Int {
exec(values)
return 0
}
override fun sendQuery(values: Map<String, Any?>): Int {
exec(values)
return 0
}
private fun compileArgs(values: List<Any?>): String { private fun compileArgs(values: List<Any?>): String {
val placeholders = values val placeholders = values
.filterIndexed { index, any -> .filterIndexed { index, value ->
definition.parameters[index].default === null || any !== null definition.parameters[index].default === null || value != null
} }
.mapIndexed { index, any -> .mapIndexed { index, _ ->
"?::" + definition.parameters[index].type "?::" + definition.parameters[index].type
} }
@@ -185,11 +195,11 @@ class Function(val definition: Function, override val connection: Connection): E
val parameters = definition.getParametersIndexedByName() val parameters = definition.getParametersIndexedByName()
val placeholders = values val placeholders = values
.filter { entry -> .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 parameter.default === null || entry.value !== null
} }
.map { entry -> .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}""" """"${parameter.name}" := :${parameter.name}::${parameter.type}"""
} }

View File

@@ -1,20 +1,25 @@
package fr.postgresjson.connexion package fr.postgresjson.connexion
import com.github.jasync.sql.db.util.length
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
import kotlin.math.ceil
data class Paginated<T: EntityI<*>>( data class Paginated<T : EntityI>(
val result: List<T>, val result: List<T>,
val offset: Int, val offset: Int,
val limit: Int, val limit: Int,
val total: Int val total: Int
) { ) {
val currentPage: Int = (offset / limit) + 1 val currentPage: Int = (offset / limit) + 1
val count: Int = result.length val count: Int = result.size
val totalPages: Int = (total.toDouble() / limit.toDouble()).ceil()
init { init {
if (offset < 0) error("offset must be greather or equal than 0") 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 (limit < 1) error("limit must be greather or equal than 1")
if (total < 0) error("total must be greather or equal than 0") 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

@@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.QueryResult import com.github.jasync.sql.db.QueryResult
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
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 { override fun toString(): String {
return sql return sql
@@ -12,7 +11,7 @@ class Query(override val name: String, private val sql: String, override val con
/* Select One */ /* Select One */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<R>, typeReference: TypeReference<R>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
@@ -20,13 +19,13 @@ class Query(override val name: String, private val sql: String, override val con
return connection.select(this.toString(), typeReference, values, block) return connection.select(this.toString(), typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): 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>, typeReference: TypeReference<R>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, R?) -> Unit block: (QueryResult, R?) -> Unit
@@ -34,7 +33,7 @@ class Query(override val name: String, private val sql: String, override val con
return connection.select(this.toString(), typeReference, values, block) return connection.select(this.toString(), typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> selectOne( inline fun <reified R : EntityI> selectOne(
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectOneCallback<R> = {} noinline block: SelectOneCallback<R> = {}
): R? = ): R? =
@@ -42,7 +41,7 @@ class Query(override val name: String, private val sql: String, override val con
/* Select Multiples */ /* Select Multiples */
override fun <R: EntityI<*>> select( override fun <R : EntityI> select(
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
values: List<Any?>, values: List<Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
@@ -50,13 +49,13 @@ class Query(override val name: String, private val sql: String, override val con
return connection.select(this.toString(), typeReference, values, block) return connection.select(this.toString(), typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
values: List<Any?> = emptyList(), values: List<Any?> = emptyList(),
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<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>>, typeReference: TypeReference<List<R>>,
values: Map<String, Any?>, values: Map<String, Any?>,
block: (QueryResult, List<R>) -> Unit block: (QueryResult, List<R>) -> Unit
@@ -64,13 +63,13 @@ class Query(override val name: String, private val sql: String, override val con
return connection.select(this.toString(), typeReference, values, block) return connection.select(this.toString(), typeReference, values, block)
} }
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
values: Map<String, Any?>, values: Map<String, Any?>,
noinline block: SelectCallback<R> = {} noinline block: SelectCallback<R> = {}
): List<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, page: Int,
limit: Int, limit: Int,
typeReference: TypeReference<List<R>>, typeReference: TypeReference<List<R>>,
@@ -82,7 +81,7 @@ class Query(override val name: String, private val sql: String, override val con
/* Select Paginated */ /* Select Paginated */
inline fun <reified R: EntityI<*>> select( inline fun <reified R : EntityI> select(
page: Int, page: Int,
limit: Int, limit: Int,
values: Map<String, Any?> = emptyMap(), values: Map<String, Any?> = emptyMap(),
@@ -99,4 +98,12 @@ class Query(override val name: String, private val sql: String, override val con
override fun exec(values: Map<String, Any?>): QueryResult { override fun exec(values: Map<String, Any?>): QueryResult {
return connection.exec(sql, values) return connection.exec(sql, values)
} }
override fun sendQuery(values: List<Any?>): Int {
return connection.sendQuery(sql, values)
}
override fun sendQuery(values: Map<String, Any?>): Int {
return connection.sendQuery(sql, values)
}
} }

View File

@@ -2,18 +2,17 @@ package fr.postgresjson.definition
import java.io.File import java.io.File
open class Function( open class Function(
override val script: String override val script: String
) : Resource, ParametersInterface { ) : Resource, ParametersInterface {
val returns: String? val returns: String
override val name: String override val name: String
override val parameters: List<Parameter> override val parameters: List<Parameter>
override var source: File? = null override var source: File? = null
init { init {
val functionRegex = 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)) .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
val paramsRegex = val paramsRegex =
@@ -22,9 +21,9 @@ open class Function(
val queryMatch = functionRegex.find(script) val queryMatch = functionRegex.find(script)
if (queryMatch !== null) { 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() val functionParameters = queryMatch.groups["params"]?.value?.trim()
this.returns = queryMatch.groups["return"]?.value?.trim() this.returns = queryMatch.groups["return"]?.value?.trim() ?: ""
/* Create parameters definition */ /* Create parameters definition */
val parameters = if (functionParameters !== null) { val parameters = if (functionParameters !== null) {
@@ -40,7 +39,7 @@ open class Function(
} else { } else {
listOf() listOf()
} }
this.name = functionName!! this.name = functionName
this.parameters = parameters this.parameters = parameters
} else { } else {
throw FunctionNotFound() throw FunctionNotFound()
@@ -54,9 +53,8 @@ open class Function(
return parameters return parameters
.filter { it.direction == Parameter.Direction.IN } .filter { it.direction == Parameter.Direction.IN }
.joinToString(", ") { "${it.name} ${it.type}" }.let { .joinToString(", ") { "${it.name} ${it.type}" }.let {
"$name ($it) $returns" "$name ($it)"
} }
} }
fun getParametersIndexedByName(): Map<String, Parameter> { fun getParametersIndexedByName(): Map<String, Parameter> {

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 package fr.postgresjson.entity
import com.fasterxml.jackson.annotation.JsonIgnore interface Serializable
import org.joda.time.DateTime interface EntityI : Serializable
import java.util.* interface Parameter : Serializable
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()

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 package fr.postgresjson.migration
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.migration.Migration.Action import fr.postgresjson.migration.Migration.Action
import fr.postgresjson.migration.Migration.Status import fr.postgresjson.migration.Migration.Status
import java.util.* import java.util.*
import java.util.concurrent.*
import fr.postgresjson.definition.Function as DefinitionFunction import fr.postgresjson.definition.Function as DefinitionFunction
data class Function( data class Function(
@@ -17,7 +19,7 @@ data class Function(
init { init {
if (up.name != down.name) { if (up.name != down.name) {
throw Exception("UP and DOWN migration must have the same name [${up.name} !== ${down.name}]") throw Exception("UP and DOWN migration must have the same name [${up.name} != ${down.name}]")
} }
} }
@@ -34,7 +36,15 @@ data class Function(
) )
override fun up(): Status { override fun up(): Status {
try {
connection.sendQuery(up.script) 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 { 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 -> 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) connection.sendQuery(down.script)
this::class.java.classLoader.getResource("sql/migration/deleteFunction.sql")!!.readText().let { this::class.java.classLoader.getResource("sql/migration/deleteFunction.sql")!!.readText().let {
connection.exec(it, listOf(down)) connection.sendQuery(it, listOf(down.name))
} }
return Status.OK return Status.OK
} }
@@ -65,7 +75,7 @@ data class Function(
} }
override fun status(): Status { override fun status(): Status {
val result = connection.inTransaction { connection.inTransaction {
up() up()
down() down()
it.sendQuery("ROLLBACK") it.sendQuery("ROLLBACK")

View File

@@ -1,10 +1,9 @@
package fr.postgresjson.migration package fr.postgresjson.migration
import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.core.type.TypeReference
import com.github.jasync.sql.db.util.size
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.definition.Function.FunctionNotFound 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.Action
import fr.postgresjson.migration.Migration.Status import fr.postgresjson.migration.Migration.Status
import fr.postgresjson.utils.LoggerDelegate import fr.postgresjson.utils.LoggerDelegate
@@ -39,13 +38,23 @@ data class Migrations private constructor(
private val queries: MutableMap<String, Query> = mutableMapOf(), private val queries: MutableMap<String, Query> = mutableMapOf(),
private val functions: MutableMap<String, Function> = mutableMapOf() private val functions: MutableMap<String, Function> = mutableMapOf()
) { ) {
private var directories: List<File> = emptyList()
private val logger: Logger? by LoggerDelegate() 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() initDB()
this.directories = directories
reset()
}
fun reset() {
queries.clear()
functions.clear()
getMigrationFromDB() getMigrationFromDB()
getMigrationFromDirectory(directories) getMigrationFromDirectory(directories)
queries.forEach { (_, query) -> queries.forEach { (_, query) ->
if (query.doExecute === null) { if (query.doExecute === null) {
query.doExecute = Action.DOWN query.doExecute = Action.DOWN
@@ -97,11 +106,11 @@ data class Migrations private constructor(
it.isFile it.isFile
}.forEach { file -> }.forEach { file ->
if (file.name.endsWith(".up.sql")) { if (file.name.endsWith(".up.sql")) {
file.path.substring(0, file.path.size - 7).let { file.path.substring(0, file.path.length - 7).let {
try { try {
val down = File("$it.down.sql").readText() val down = File("$it.down.sql").readText()
val up = file.readText() val up = file.readText()
val name = file.name.substring(0, file.name.size - 7) val name = file.name.substring(0, file.name.length - 7)
addQuery(name, up, down) addQuery(name, up, down)
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
throw DownMigrationNotDefined("$it.down.sql", e) throw DownMigrationNotDefined("$it.down.sql", e)
@@ -128,7 +137,8 @@ data class Migrations private constructor(
fun addFunction(newDefinition: DefinitionFunction, callback: (Function) -> Unit = {}): Migrations { fun addFunction(newDefinition: DefinitionFunction, callback: (Function) -> Unit = {}): Migrations {
val currentFunction = functions[newDefinition.name] val currentFunction = functions[newDefinition.name]
if (currentFunction === null || currentFunction `is different from` newDefinition) { 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 doExecute = Action.UP
} }
} else { } else {
@@ -244,6 +254,7 @@ data class Migrations private constructor(
sendQuery("COMMIT") sendQuery("COMMIT")
} }
logger?.info("Migration done") logger?.info("Migration done")
reset()
return list.toMap() return list.toMap()
} }
@@ -264,6 +275,7 @@ data class Migrations private constructor(
sendQuery("COMMIT") sendQuery("COMMIT")
} }
logger?.info("Migration DOWN done") logger?.info("Migration DOWN done")
reset()
return list.toMap() return list.toMap()
} }

View File

@@ -1,7 +1,7 @@
package fr.postgresjson.migration package fr.postgresjson.migration
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.entity.Entity import fr.postgresjson.entity.mutable.Entity
import fr.postgresjson.migration.Migration.Action import fr.postgresjson.migration.Migration.Action
import java.util.* import java.util.*
@@ -48,7 +48,7 @@ data class Query(
} }
override fun status(): Migration.Status { override fun status(): Migration.Status {
val result = connection.inTransaction { connection.inTransaction {
up() up()
down() down()
it.sendQuery("ROLLBACK") it.sendQuery("ROLLBACK")

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 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.core.type.TypeReference
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.deser.std.StdDeserializer 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.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.joda.JodaModule import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import fr.postgresjson.entity.EntitiesCollections import fr.postgresjson.entity.Serializable
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.IdEntity
import fr.postgresjson.entity.UuidEntity
import java.io.IOException
import java.util.*
class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) { class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
var collection: EntitiesCollections = EntitiesCollections()
init { init {
val module = SimpleModule() val module = SimpleModule()
module.addDeserializer(UuidEntity::class.java, EntityUuidDeserializer(collection))
module.addDeserializer(IdEntity::class.java, EntityIdDeserializer(collection))
mapper.registerModule(module) mapper.registerModule(module)
mapper.propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE mapper.propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
mapper.registerModule(JodaModule()) mapper.registerModule(JodaModule())
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 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) return if (pretty) mapper.writerWithDefaultPrettyPrinter().writeValueAsString(source)
else mapper.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) 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) return this.mapper.readValue(json)
} }
@@ -53,50 +43,12 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) {
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) return mapper.readerForUpdating(target).readValue<E>(json)
} }
} }
fun <T> EntityI<T?>.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty) fun Serializable.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty)
inline fun <reified E: EntityI<*>> E.deserialize(json: String) = Serializer().deserialize(json, this) fun List<Serializable>.serialize(pretty: Boolean = false) = Serializer().serialize(this, pretty)
inline fun <reified E: EntityI<*>> String.deserialize() = Serializer().deserialize<E>(this) inline fun <reified E : Serializable> E.deserialize(json: String) = Serializer().deserialize(json, this)
inline fun <reified E : Serializable> 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
}
}

View File

@@ -1,11 +1,10 @@
package fr.postgresjson package fr.postgresjson
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Paginated 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.Assert.*
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@@ -14,13 +13,8 @@ class ConnectionTest(): TestAbstract() {
private class ObjTest(var name: String) : IdEntity() private class ObjTest(var name: String) : IdEntity()
private class ObjTest2(var title: String, var test: ObjTest?) : 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 ObjTest3(var first: String, var seconde: String, var third: Int) : IdEntity()
private class ObjTestWithParameterObject(var first: ParameterObject, var seconde: ParameterObject) : IdEntity()
private lateinit var connection: Connection private class ParameterObject(var third: String) : Parameter
@BeforeEach
fun before() {
connection = getConnextion()
}
@Test @Test
fun getObject() { fun getObject() {
@@ -44,8 +38,7 @@ class ConnectionTest(): TestAbstract() {
""".trimIndent() """.trimIndent()
) )
assertNotNull(objs) assertNotNull(objs)
assertTrue(objs is List<ObjTest2>) assertEquals(objs.size, 2)
assertEquals(objs!!.size, 2)
assertEquals(objs[0].id, 1) assertEquals(objs[0].id, 1)
assertEquals(objs[0].test!!.id, 1) assertEquals(objs[0].test!!.id, 1)
} }
@@ -90,6 +83,19 @@ class ConnectionTest(): TestAbstract() {
assertEquals(result.third, 123) 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 @Test
fun `select with named parameters`() { fun `select with named parameters`() {
val params: Map<String, Any?> = mapOf( val params: Map<String, Any?> = mapOf(

View File

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

View File

@@ -17,7 +17,7 @@ class MigrationTest(): TestAbstract() {
@Test @Test
fun `run up query`() { fun `run up query`() {
val resources = File(this::class.java.getResource("/sql/migrations").toURI()) val resources = File(this::class.java.getResource("/sql/migrations").toURI())
val m = Migrations(resources, getConnextion()) val m = Migrations(resources, connection)
m.up().apply { m.up().apply {
this `should contain` Pair("1", Migration.Status.OK) this `should contain` Pair("1", Migration.Status.OK)
size `should be equal to` 1 size `should be equal to` 1
@@ -30,14 +30,14 @@ class MigrationTest(): TestAbstract() {
fun `migration up Query should throw error if no down`() { fun `migration up Query should throw error if no down`() {
val resources = File(this::class.java.getResource("/sql/migration_without_down").toURI()) val resources = File(this::class.java.getResource("/sql/migration_without_down").toURI())
invoking { invoking {
Migrations(resources, getConnextion()) Migrations(resources, connection)
} shouldThrow Migrations.DownMigrationNotDefined::class } shouldThrow Migrations.DownMigrationNotDefined::class
} }
@Test @Test
fun `run forced down query`() { fun `run forced down query`() {
val resources = File(this::class.java.getResource("/sql/migrations").toURI()) val resources = File(this::class.java.getResource("/sql/migrations").toURI())
val m = Migrations(resources, getConnextion()) val m = Migrations(resources, connection)
repeat(3) { repeat(3) {
m.down(true).apply { m.down(true).apply {
this `should contain` Pair("1", Migration.Status.OK) this `should contain` Pair("1", Migration.Status.OK)
@@ -49,10 +49,10 @@ class MigrationTest(): TestAbstract() {
@Test @Test
fun `run dry migrations`() { fun `run dry migrations`() {
val resources = File(this::class.java.getResource("/sql/real_migrations").toURI()) val resources = File(this::class.java.getResource("/sql/real_migrations").toURI())
Migrations(resources, getConnextion()).apply { Migrations(resources, connection).apply {
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
} }
Migrations(resources, getConnextion()).apply { Migrations(resources, connection).apply {
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
} }
} }
@@ -60,7 +60,7 @@ class MigrationTest(): TestAbstract() {
@Test @Test
fun `run dry migrations launch twice`() { fun `run dry migrations launch twice`() {
val resources = File(this::class.java.getResource("/sql/real_migrations").toURI()) val resources = File(this::class.java.getResource("/sql/real_migrations").toURI())
Migrations(resources, getConnextion()).apply { Migrations(resources, connection).apply {
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
runDry().size `should be equal to` 2 runDry().size `should be equal to` 2
} }
@@ -69,7 +69,7 @@ class MigrationTest(): TestAbstract() {
@Test @Test
fun `run migrations`() { fun `run migrations`() {
val resources = File(this::class.java.getResource("/sql/real_migrations").toURI()) val resources = File(this::class.java.getResource("/sql/real_migrations").toURI())
Migrations(resources, getConnextion()).apply { Migrations(resources, connection).apply {
run().apply { run().apply {
size `should be equal to` 1 size `should be equal to` 1
} }
@@ -79,26 +79,27 @@ class MigrationTest(): TestAbstract() {
@Test @Test
fun `run migrations force down`() { fun `run migrations force down`() {
val resources = File(this::class.java.getResource("/sql/real_migrations").toURI()) val resources = File(this::class.java.getResource("/sql/real_migrations").toURI())
Migrations(resources, getConnextion()).apply { val resourcesFunctions = File(this::class.java.getResource("/sql/function/Test").toURI())
Migrations(listOf(resources, resourcesFunctions), connection).apply {
up().apply { up().apply {
size `should be equal to` 1 size `should be equal to` 6
} }
} }
Migrations(resources, getConnextion()).apply { Migrations(listOf(resources, resourcesFunctions), connection).apply {
forceAllDown().apply { forceAllDown().apply {
size `should be equal to` 1 size `should be equal to` 6
} }
} }
} }
@Test @Test
fun `run functions migrations`() { 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, getConnextion()).apply { Migrations(resources, connection).apply {
run().size `should be equal to` 4 run().size `should be equal to` 5
} }
val objTest: RequesterTest.ObjTest? = Requester(getConnextion()) val objTest: RequesterTest.ObjTest? = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(listOf("test", "plip")) .selectOne(listOf("test", "plip"))
@@ -106,4 +107,25 @@ class MigrationTest(): TestAbstract() {
Assertions.assertEquals(objTest!!.id, 3) Assertions.assertEquals(objTest!!.id, 3)
Assertions.assertEquals(objTest.name, "test") 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,7 +2,7 @@ package fr.postgresjson
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.IdEntity import fr.postgresjson.entity.mutable.IdEntity
import org.junit.Assert import org.junit.Assert
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -14,7 +14,7 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `get query from file`() { fun `get query from file`() {
val resources = File(this::class.java.getResource("/sql/query").toURI()) val resources = File(this::class.java.getResource("/sql/query").toURI())
val objTest: ObjTest? = Requester(getConnextion()) val objTest: ObjTest? = Requester(connection)
.addQuery(resources) .addQuery(resources)
.getQuery("Test/selectOne") .getQuery("Test/selectOne")
.selectOne() .selectOne()
@@ -25,8 +25,8 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `get function from file`() { 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(getConnextion()) val objTest: ObjTest? = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(listOf("test", "plip")) .selectOne(listOf("test", "plip"))
@@ -38,7 +38,7 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call exec on query`() { fun `call exec on query`() {
val resources = File(this::class.java.getResource("/sql/query").toURI()) val resources = File(this::class.java.getResource("/sql/query").toURI())
val result = Requester(getConnextion()) val result = Requester(connection)
.addQuery(resources) .addQuery(resources)
.getQuery("Test/selectOne") .getQuery("Test/selectOne")
.exec() .exec()
@@ -48,8 +48,8 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call exec on function`() { 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(getConnextion()) val result = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.exec(listOf("test", "plip")) .exec(listOf("test", "plip"))
@@ -57,10 +57,32 @@ class RequesterTest: TestAbstract() {
assertEquals(1, result.rowsAffected) assertEquals(1, result.rowsAffected)
} }
@Test
fun `call sendQuery on query`() {
val resources = File(this::class.java.getResource("/sql/query").toURI())
val result = Requester(connection)
.addQuery(resources)
.getQuery("Test/exec")
.sendQuery()
assertEquals(0, result)
}
@Test
fun `call sendQuery on function`() {
val resources = File(this::class.java.getResource("/sql/function/Test").toURI())
val result = Requester(connection)
.addFunction(resources)
.getFunction("function_void")
.sendQuery(listOf("test"))
assertEquals(0, result)
}
@Test @Test
fun `call selectOne on function`() { 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(getConnextion()) val obj: ObjTest = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function") .getFunction("test_function")
.selectOne(mapOf("name" to "myName"))!! .selectOne(mapOf("name" to "myName"))!!
@@ -70,9 +92,9 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call selectOne on function with object`() { 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 obj2 = ObjTest("original")
val obj: ObjTest = Requester(getConnextion()) val obj: ObjTest = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function_object") .getFunction("test_function_object")
.selectOne("resource" to obj2)!! .selectOne("resource" to obj2)!!
@@ -84,7 +106,7 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call selectOne on query`() { fun `call selectOne on query`() {
val resources = File(this::class.java.getResource("/sql/query").toURI()) val resources = File(this::class.java.getResource("/sql/query").toURI())
val obj: ObjTest = Requester(getConnextion()) val obj: ObjTest = Requester(connection)
.addQuery(resources) .addQuery(resources)
.getQuery("Test/selectOneWithParameters") .getQuery("Test/selectOneWithParameters")
.selectOne(mapOf("name" to "myName"))!! .selectOne(mapOf("name" to "myName"))!!
@@ -94,8 +116,8 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call select (multiple) on function`() { 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(getConnextion()) val obj: List<ObjTest>? = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function_multiple") .getFunction("test_function_multiple")
.select(mapOf("name" to "myName")) .select(mapOf("name" to "myName"))
@@ -106,35 +128,35 @@ class RequesterTest: TestAbstract() {
@Test @Test
fun `call select paginated on query`() { fun `call select paginated on query`() {
val resources = File(this::class.java.getResource("/sql/query").toURI()) val resources = File(this::class.java.getResource("/sql/query").toURI())
val result: Paginated<ObjTest> = Requester(getConnextion()) val result: Paginated<ObjTest> = Requester(connection)
.addQuery(resources) .addQuery(resources)
.getQuery("Test/selectPaginated") .getQuery("Test/selectPaginated")
.select(1, 2, mapOf("name" to "ff")) .select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result) Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff") Assert.assertEquals("ff", result.result[0].name)
Assert.assertEquals(result.result[1].name, "ff-2") Assert.assertEquals("ff-2", result.result[1].name)
Assert.assertEquals(result.total, 10) Assert.assertEquals(10, result.total)
Assert.assertEquals(result.offset, 0) Assert.assertEquals(0, result.offset)
} }
@Test @Test
fun `call select paginated on function`() { fun `call select paginated on function`() {
val resources = File(this::class.java.getResource("/sql/function").toURI()) val resources = File(this::class.java.getResource("/sql/function").toURI())
val result: Paginated<ObjTest> = Requester(getConnextion()) val result: Paginated<ObjTest> = Requester(connection)
.addFunction(resources) .addFunction(resources)
.getFunction("test_function_paginated") .getFunction("test_function_paginated")
.select(1, 2, mapOf("name" to "ff")) .select(1, 2, mapOf("name" to "ff"))
Assert.assertNotNull(result) Assert.assertNotNull(result)
Assert.assertEquals(result.result[0].name, "ff") Assert.assertEquals("ff", result.result[0].name)
Assert.assertEquals(result.result[1].name, "ff-2") Assert.assertEquals("ff-2", result.result[1].name)
Assert.assertEquals(result.total, 10) Assert.assertEquals(10, result.total)
Assert.assertEquals(result.offset, 0) Assert.assertEquals(0, result.offset)
} }
@Test @Test
fun `call selectOne on query with extra parameter`() { fun `call selectOne on query with extra parameter`() {
val resources = File(this::class.java.getResource("/sql/query").toURI()) val resources = File(this::class.java.getResource("/sql/query").toURI())
val obj: ObjTest = Requester(getConnextion()) val obj: ObjTest = Requester(connection)
.addQuery(resources) .addQuery(resources)
.getQuery("Test/selectOneWithParameters") .getQuery("Test/selectOneWithParameters")
.selectOne(mapOf("name" to "myName")) { .selectOne(mapOf("name" to "myName")) {

View File

@@ -1,6 +1,6 @@
package fr.postgresjson package fr.postgresjson
import fr.postgresjson.entity.IdEntity import fr.postgresjson.entity.mutable.IdEntity
import fr.postgresjson.serializer.Serializer import fr.postgresjson.serializer.Serializer
import fr.postgresjson.serializer.deserialize import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize import fr.postgresjson.serializer.serialize
@@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class SerializerTest: TestAbstract() { internal class SerializerTest {
private class ObjTest(var val1: String, var val2: Int) : IdEntity(1) private class ObjTest(var val1: String, var val2: Int) : IdEntity(1)
private class ObjTestDate(var val1: DateTime) : IdEntity(2) private class ObjTestDate(var val1: DateTime) : IdEntity(2)
@@ -41,6 +41,14 @@ internal class SerializerTest: TestAbstract() {
assertTrue(json.contains(""""val1":"plop","val2":123""")) 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 @Test
fun serializeDate() { fun serializeDate() {
val objDate = ObjTestDate(DateTime.parse("2019-07-30T14:08:51.420108+04:00")) val objDate = ObjTestDate(DateTime.parse("2019-07-30T14:08:51.420108+04:00"))

View File

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

View File

@@ -73,3 +73,12 @@ BEGIN
resource = json_build_object('id', 1, 'name', 'changedName'); resource = json_build_object('id', 1, 'name', 'changedName');
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION function_void (name text default 'plop') returns void
LANGUAGE plpgsql
AS
$$
BEGIN
PERFORM 1;
END;
$$

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE FUNCTION function_void (name text default 'plop') returns void
LANGUAGE plpgsql
AS
$$
BEGIN
PERFORM 1;
END;
$$;

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;
$$

View File

@@ -1 +1,5 @@
SELECT 1; do $$
begin
PERFORM 1;
end;
$$

View File

@@ -1 +1,5 @@
SELECT 1; do $$
begin
PERFORM 1;
end;
$$

View File

@@ -1 +1,5 @@
SELECT 1; do $$
begin
PERFORM 1;
end;
$$

View File

@@ -0,0 +1 @@
delete FROM test where 2038538 = 2;