From 2d494d6e3e4626ec20024380f64f9fd470ef121b Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Thu, 13 Jun 2019 15:26:52 +0200 Subject: [PATCH] Big refactoring and implement Function --- src/main/kotlin/fr/postgresjson/Serializer.kt | 11 +- .../fr/postgresjson/connexion/Connection.kt | 181 ++++++++++++++---- .../fr/postgresjson/repository/Repository.kt | 17 +- .../kotlin/fr/postgresjson/RequestTest.kt | 2 +- 4 files changed, 170 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/fr/postgresjson/Serializer.kt b/src/main/kotlin/fr/postgresjson/Serializer.kt index c2487f4..8d67c3c 100644 --- a/src/main/kotlin/fr/postgresjson/Serializer.kt +++ b/src/main/kotlin/fr/postgresjson/Serializer.kt @@ -2,6 +2,7 @@ package fr.postgresjson import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper @@ -32,12 +33,20 @@ class Serializer(val mapper: ObjectMapper = jacksonObjectMapper()) { return mapper.writeValueAsString(source) } + fun ?> deserialize(json: String, valueTypeRef: TypeReference): E { + return this.mapper.readValue(json, valueTypeRef) + } + inline fun ?> deserialize(json: String): E { return this.mapper.readValue(json) } + fun deserializeList(json: String, valueTypeRef: TypeReference): E { + return mapper.readValue(json, valueTypeRef) + } + inline fun deserializeList(json: String): E { - return mapper.readValue(json) + return deserializeList(json, object: TypeReference() {}) } fun > deserialize(json: String, target: E): E { diff --git a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt index 94780f0..2c4b228 100644 --- a/src/main/kotlin/fr/postgresjson/connexion/Connection.kt +++ b/src/main/kotlin/fr/postgresjson/connexion/Connection.kt @@ -1,12 +1,14 @@ package fr.postgresjson.connexion +import com.fasterxml.jackson.core.type.TypeReference 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 fr.postgresjson.Serializer import fr.postgresjson.entity.EntityI import java.io.File - +import kotlin.text.RegexOption.IGNORE_CASE +import kotlin.text.RegexOption.MULTILINE class Connection( private val host: String = "localhost", @@ -14,11 +16,13 @@ class Connection( private val database: String = "dc-project", private val username: String = "dc-project", private val password: String = "dc-project", - queriesDirectory: File? = null + queriesDirectory: File? = null, + functionsDirectory: File? = null ) { - private val queries = mutableMapOf>() + private val queries = mutableMapOf() + private val functions = mutableMapOf() private lateinit var connection: ConnectionPool - val serializer = Serializer() + private val serializer = Serializer() init { if (queriesDirectory === null) { @@ -29,6 +33,77 @@ class Connection( } else { fetchQueries(queriesDirectory) } + + if (functionsDirectory === null) { + val resource = this::class.java.getResource("/sql/function") + if (resource !== null) { + fetchFunctions(File(resource.toURI())) + } + } else { + fetchFunctions(functionsDirectory) + } + } + + class Query(private val sql: String, private val connection : Connection) { + override fun toString(): String { + return sql + } + + fun ?> selectOne(typeReference: TypeReference, values: List = emptyList()): R? { + return connection.selectOne(this.toString(), typeReference, values) + } + + inline fun ?> selectOne(values: List = emptyList()): R? = selectOne(object: TypeReference() {}, values) + + fun ?>> select(typeReference: TypeReference, values: List = emptyList()): R? { + return connection.select(this.toString(), typeReference, values) + } + + inline fun ?>> select(values: List = emptyList()): R? = select(object: TypeReference() {}, values) + } + + class Function(val name: String, val parameters: List, private val connection : Connection) { + + class Parameter(val name: String, val type: String, direction: Direction? = Direction.IN) + { + val direction: Direction + + init { + if (direction === null) { + this.direction = Direction.IN + } else { + this.direction = direction + } + } + constructor(name: String, type: String, direction: String? = "IN") : this( + name = name, + type = type, + direction = direction?.let { Direction.valueOf(direction.toUpperCase())} + ) + enum class Direction { IN, OUT, INOUT } + } + + override fun toString(): String { + return name + } + + fun ?> selectOne(typeReference: TypeReference, values: List = emptyList()): R? { + val args = values.joinToString() + val sql = "SELECT * FROM $name ($args)" + + return connection.selectOne(sql, typeReference, values) + } + + inline fun ?> selectOne(values: List = emptyList()): R? = selectOne(object: TypeReference() {}, values) + + fun ?>> select(typeReference: TypeReference, values: List = emptyList()): R? { + val args = values.joinToString() + val sql = "SELECT * FROM $name ($args)" + + return connection.select(sql, typeReference, values) + } + + inline fun ?>> select(values: List = emptyList()): R? = select(object: TypeReference() {}, values) } fun connect(): ConnectionPool { @@ -40,53 +115,93 @@ class Connection( return connection } - inline fun ?> selectOne(group: String, name: String, values: List = emptyList()): R? { - val sql: String = getQuery(group, name) - - return selectOne(sql, values) - } - - inline fun ?> selectOne(sql: String, values: List = emptyList()): R? { + fun ?> selectOne(sql: String, typeReference: TypeReference, values: List = emptyList()): R? { val future = connect().sendPreparedStatement(sql, values) val json = future.get().rows[0].getString(0) - if (json === null) { - return null + return if (json === null) { + null } else { - val obj = serializer.deserialize(json) - - return obj + serializer.deserialize(json, typeReference) } } - inline fun >> select(sql: String, values: List = emptyList()): R { + inline fun ?> selectOne(sql: String, values: List = emptyList()): R? = selectOne(sql, object: TypeReference() {}, values) + + fun ?>> select(sql: String, typeReference: TypeReference, values: List = emptyList()): R { val future = connect().sendPreparedStatement(sql, values) val json = future.get().rows[0].getString(0) - if (json === null) { - return listOf>() as R + return if (json === null) { + listOf?>() as R } else { - val obj = serializer.deserializeList(json) - - return obj + serializer.deserializeList(json, typeReference) } } + inline fun ?>> select(sql: String, values: List = emptyList()): R = select(sql, object : TypeReference() {}, values) + private fun fetchQueries(queriesDirectory: File) { - queriesDirectory.walk().filter{it.isDirectory}.forEach { directory -> - val group = directory.name - directory.walk().filter{it.isFile}.forEach { file -> + queriesDirectory.walk().filter { it.isDirectory }.forEach { directory -> + val path = directory.name + directory.walk().filter { it.isFile }.forEach { file -> val sql = file.readText() - if (queries[group] === null) { - queries[group] = mutableMapOf() - } - queries[group]!![file.nameWithoutExtension] = sql + val fullpath = "$path/${file.nameWithoutExtension}" + queries[fullpath] = Query(sql, this) } } } - fun getQuery(group: String, name: String): String { - if (queries[group] === null || queries[group]!![name] === null) { - throw Exception("No query defined for $group/$name") + private fun fetchFunctions(functionsDirectory: File) { + functionsDirectory.walk().filter { it.isDirectory }.forEach { directory -> + directory.walk().filter { it.isFile }.forEach { file -> + val fileContent = file.readText() + getDefinitions(fileContent).forEach { + functions[it.name] = it + } + } } - return queries[group]!![name]!! + } + + private fun getDefinitions(functionContent: String): List + { + val functionRegex = """create .*(procedure|function) *(?[^(\s]+)\s*\((?(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?RETURNS *[^ ]+)?""" + .toRegex(setOf(IGNORE_CASE, MULTILINE)) + + val paramsRegex = """\s*(?((?IN|OUT|INOUT|VARIADIC)?\s+)?(?[^\s,)]+\s+)?(?[^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?)\s*(,|$)""" + .toRegex(setOf(IGNORE_CASE, MULTILINE)) + + return functionRegex.findAll(functionContent).map { queryMatch -> + val functionName = queryMatch.groups["name"]?.value?.trim() + val functionParameters = queryMatch.groups["params"]?.value?.trim() + val returns = queryMatch.groups["return"]?.value?.trim() + + /* Create parameters definition */ + val parameters = if (functionParameters !== null) { + val matchesParams = paramsRegex.findAll(functionParameters) + matchesParams.map { paramsMatch -> + Function.Parameter( + paramsMatch.groups["name"]!!.value.trim(), + paramsMatch.groups["type"]!!.value.trim(), + paramsMatch.groups["direction"]?.value?.trim()) + }.toList() + } else { + listOf() + } + + Function(functionName!!, parameters, this) + }.toList() + } + + fun getFunction(name: String): Function { + if (functions[name] === null) { + throw Exception("No function defined for $name") + } + return functions[name]!! + } + + fun getQuery(path: String): Query { + if (queries[path] === null) { + throw Exception("No query defined for $path") + } + return queries[path]!! } } \ No newline at end of file diff --git a/src/main/kotlin/fr/postgresjson/repository/Repository.kt b/src/main/kotlin/fr/postgresjson/repository/Repository.kt index d4228ff..0ca2428 100644 --- a/src/main/kotlin/fr/postgresjson/repository/Repository.kt +++ b/src/main/kotlin/fr/postgresjson/repository/Repository.kt @@ -1,17 +1,22 @@ package fr.postgresjson.repository -import com.github.jasync.sql.db.pool.ConnectionPool -import com.github.jasync.sql.db.postgresql.PostgreSQLConnection import fr.postgresjson.Serializer +import fr.postgresjson.connexion.Connection import fr.postgresjson.entity.EntityCollection import fr.postgresjson.entity.EntityI +import kotlin.reflect.KClass -interface RepositoryI> +interface RepositoryI> { + val entityName: KClass +} -abstract class Repository> : RepositoryI { - abstract var connection: ConnectionPool +abstract class Repository>(override val entityName: KClass) : RepositoryI { + + abstract var connection: Connection + abstract fun getClassName(): String fun findById(id: T): EntityI? { + val sql = this.connection.getQuery(entityName.toString()) return when (val e = EntityCollection().get(id)) { null -> { // TODO create Request @@ -20,4 +25,4 @@ abstract class Repository> : RepositoryI { else -> e } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/fr/postgresjson/RequestTest.kt b/src/test/kotlin/fr/postgresjson/RequestTest.kt index 85cb30d..374fb43 100644 --- a/src/test/kotlin/fr/postgresjson/RequestTest.kt +++ b/src/test/kotlin/fr/postgresjson/RequestTest.kt @@ -12,7 +12,7 @@ class RequestTest { @Test fun getRequestFromFile() { val resources = File(this::class.java.getResource("/sql/query").toURI()) - val objTest: ObjTest? = Connection(queriesDirectory = resources).selectOne("Test", "test") + val objTest: ObjTest? = Connection(queriesDirectory = resources).getQuery("Test/test").selectOne() assertTrue(objTest!!.id == 2) assertTrue(objTest.name == "test") }