refactoring: move Query/Function logic into Requester class

This commit is contained in:
2019-06-14 17:09:52 +02:00
parent baa2976e80
commit 00d2fa335d
8 changed files with 235 additions and 160 deletions

View File

@@ -11,39 +11,148 @@ import kotlin.text.RegexOption.IGNORE_CASE
import kotlin.text.RegexOption.MULTILINE
class Connection(
private val database: String,
private val username: String,
private val password: String,
private val host: String = "localhost",
private val port: Int = 5432,
private val database: String = "dc-project",
private val username: String = "dc-project",
private val password: String = "dc-project",
queriesDirectory: File? = null,
functionsDirectory: File? = null
private val port: Int = 5432
) {
private val queries = mutableMapOf<String, Query>()
private val functions = mutableMapOf<String, Function>()
private lateinit var connection: ConnectionPool<PostgreSQLConnection>
private val serializer = Serializer()
init {
if (queriesDirectory === null) {
val resource = this::class.java.getResource("/sql/query")
if (resource !== null) {
fetchQueries(File(resource.toURI()))
}
} else {
fetchQueries(queriesDirectory)
fun connect(): ConnectionPool<PostgreSQLConnection> {
if (!::connection.isInitialized || !connection.isConnected()) {
connection = PostgreSQLConnectionBuilder.createConnectionPool(
"jdbc:postgresql://$host:$port/$database?user=$username&password=$password"
)
}
return connection
}
if (functionsDirectory === null) {
val resource = this::class.java.getResource("/sql/function")
if (resource !== null) {
fetchFunctions(File(resource.toURI()))
}
fun <T, R : EntityI<T?>?> selectOne(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R? {
val future = connect().sendPreparedStatement(sql, values)
val json = future.get().rows[0].getString(0)
return if (json === null) {
null
} else {
fetchFunctions(functionsDirectory)
serializer.deserialize(json, typeReference)
}
}
inline fun <T, reified R : EntityI<T?>?> selectOne(sql: String, values: List<Any?> = emptyList()): R? = selectOne(sql, object: TypeReference<R>() {}, values)
fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R {
val future = connect().sendPreparedStatement(sql, values)
val json = future.get().rows[0].getString(0)
return if (json === null) {
listOf<EntityI<T?>?>() as R
} else {
serializer.deserializeList(json, typeReference)
}
}
inline fun <T, reified R : List<EntityI<T?>?>> select(sql: String, values: List<Any?> = emptyList()): R = select(sql, object : TypeReference<R>() {}, values)
}
class Requester (
private val connection: Connection,
queries: List<Query> = listOf(),
functions: List<Function> = listOf())
{
private val queries = mutableMapOf<String, Query>()
private val functions = mutableMapOf<String, Function>()
fun addQuery(name: String, query: Query): Requester {
queries[name] = query
return this
}
fun addQuery(name: String, sql: String): Requester {
queries[name] = Query(sql, connection)
return this
}
fun addQuery(queriesDirectory: File): Requester {
queriesDirectory.walk().filter { it.isDirectory }.forEach { directory ->
val path = directory.name
directory.walk().filter { it.isFile }.forEach { file ->
val sql = file.readText()
val fullpath = "$path/${file.nameWithoutExtension}"
queries[fullpath] = Query(sql, connection)
}
}
return this
}
fun addFunction(function: Function): Requester {
functions[function.name] = function
return this
}
fun addFunction(sql: String): Requester {
getDefinitions(sql).forEach {
functions[it.name] = it
}
return this
}
fun addFunction(functionsDirectory: File): Requester {
functionsDirectory.walk().filter {
it.isDirectory
}.forEach { directory ->
directory.walk().filter {
it.isFile
}.forEach { file ->
val fileContent = file.readText()
addFunction(fileContent)
}
}
return this
}
private fun getDefinitions(functionContent: String): List<Function> {
val functionRegex = """create .*(procedure|function) *(?<name>[^(\s]+)\s*\((?<params>(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?<return>RETURNS *[^ ]+)?"""
.toRegex(setOf(IGNORE_CASE, MULTILINE))
val paramsRegex = """\s*(?<param>((?<direction>IN|OUT|INOUT|VARIADIC)?\s+)?(?<name>[^\s,)]+\s+)?(?<type>[^\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, connection)
}.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]!!
}
class Query(private val sql: String, private val connection : Connection) {
override fun toString(): String {
return sql
@@ -87,125 +196,64 @@ class Connection(
return name
}
fun <T, R : EntityI<T?>?> selectOne(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R? {
val args = values.joinToString()
val sql = "SELECT * FROM $name ($args)"
fun <T, R : EntityI<T?>?> selectOne(typeReference: TypeReference<R>, values: List<String?> = emptyList()): R? {
val placeholder = List(values.size) {"?"}.joinToString(separator=", ")
val sql = "SELECT * FROM $name (${placeholder})"
return connection.selectOne(sql, typeReference, values)
}
inline fun <T, reified R: EntityI<T?>?> selectOne(values: List<Any?> = emptyList()): R? = selectOne(object: TypeReference<R>() {}, values)
inline fun <T, reified R: EntityI<T?>?> selectOne(values: List<String?> = emptyList()): R? = selectOne(object: TypeReference<R>() {}, values)
fun <T, R : List<EntityI<T?>?>> select(typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R? {
val args = values.joinToString()
val sql = "SELECT * FROM $name ($args)"
val placeholder = List(values.size) {"?"}.joinToString(separator=", ")
val sql = "SELECT * FROM $name ($placeholder)"
return connection.select(sql, typeReference, values)
}
inline fun <T, reified R: List<EntityI<T?>?>> select(values: List<Any?> = emptyList()): R? = select(object: TypeReference<R>() {}, values)
}
}
fun connect(): ConnectionPool<PostgreSQLConnection> {
if (!::connection.isInitialized || !connection.isConnected()) {
connection = PostgreSQLConnectionBuilder.createConnectionPool(
"jdbc:postgresql://$host:$port/$database?user=$username&password=$password"
)
}
return connection
}
fun <T, R : EntityI<T?>?> selectOne(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R? {
val future = connect().sendPreparedStatement(sql, values)
val json = future.get().rows[0].getString(0)
return if (json === null) {
null
} else {
serializer.deserialize<T, R>(json, typeReference)
}
}
inline fun <T, reified R : EntityI<T?>?> selectOne(sql: String, values: List<Any?> = emptyList()): R? = selectOne(sql, object: TypeReference<R>() {}, values)
fun <T, R : List<EntityI<T?>?>> select(sql: String, typeReference: TypeReference<R>, values: List<Any?> = emptyList()): R {
val future = connect().sendPreparedStatement(sql, values)
val json = future.get().rows[0].getString(0)
return if (json === null) {
listOf<EntityI<T?>?>() as R
} else {
serializer.deserializeList(json, typeReference)
}
}
inline fun <T, reified R : List<EntityI<T?>?>> select(sql: String, values: List<Any?> = emptyList()): R = select(sql, object : TypeReference<R>() {}, values)
private fun fetchQueries(queriesDirectory: File) {
queriesDirectory.walk().filter { it.isDirectory }.forEach { directory ->
val path = directory.name
directory.walk().filter { it.isFile }.forEach { file ->
val sql = file.readText()
val fullpath = "$path/${file.nameWithoutExtension}"
queries[fullpath] = Query(sql, this)
}
}
}
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
}
}
}
}
private fun getDefinitions(functionContent: String): List<Function>
class RequesterFactory(
private val host: String = "localhost",
private val port: Int = 5432,
private val database: String = "dc-project",
private val username: String = "dc-project",
private val password: String = "dc-project",
private val queriesDirectory: File? = null,
private val functionsDirectory: File? = null
)
{
fun createRequester(): Requester
{
val functionRegex = """create .*(procedure|function) *(?<name>[^(\s]+)\s*\((?<params>(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?<return>RETURNS *[^ ]+)?"""
.toRegex(setOf(IGNORE_CASE, MULTILINE))
val con = Connection(host = host, port = port, database = database, username = username, password = password)
val req = Requester(con)
val paramsRegex = """\s*(?<param>((?<direction>IN|OUT|INOUT|VARIADIC)?\s+)?(?<name>[^\s,)]+\s+)?(?<type>[^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?)\s*(,|$)"""
.toRegex(setOf(IGNORE_CASE, MULTILINE))
return initRequester(req)
}
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()
private fun initRequester(req: Requester): Requester
{
if (queriesDirectory === null) {
val resource = this::class.java.getResource("/sql/query")
if (resource !== null) {
req.addQuery(File(resource.toURI()))
}
Function(functionName!!, parameters, this)
}.toList()
}
fun getFunction(name: String): Function {
if (functions[name] === null) {
throw Exception("No function defined for $name")
} else {
req.addQuery(queriesDirectory)
}
return functions[name]!!
}
fun getQuery(path: String): Query {
if (queries[path] === null) {
throw Exception("No query defined for $path")
if (functionsDirectory === null) {
val resource = this::class.java.getResource("/sql/function")
if (resource !== null) {
req.addFunction(File(resource.toURI()))
}
} else {
req.addFunction(functionsDirectory)
}
return queries[path]!!
return req
}
}
}

View File

@@ -1,7 +1,7 @@
package fr.postgresjson.repository
import fr.postgresjson.Serializer
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.EntityCollection
import fr.postgresjson.entity.EntityI
import kotlin.reflect.KClass
@@ -12,11 +12,11 @@ interface RepositoryI<T, E : EntityI<T?>> {
abstract class Repository<T, E : EntityI<T?>>(override val entityName: KClass<E>) : RepositoryI<T, E> {
abstract var connection: Connection
abstract var requester: Requester
abstract fun getClassName(): String
fun <T> findById(id: T): EntityI<T?>? {
val sql = this.connection.getQuery(entityName.toString())
val sql = requester.getQuery(entityName.toString())
return when (val e = EntityCollection().get(id)) {
null -> {
// TODO create Request