diff --git a/src/main/kotlin/fr/postgresjson/connexion/Requester.kt b/src/main/kotlin/fr/postgresjson/connexion/Requester.kt index 2239d63..5f2d432 100644 --- a/src/main/kotlin/fr/postgresjson/connexion/Requester.kt +++ b/src/main/kotlin/fr/postgresjson/connexion/Requester.kt @@ -40,7 +40,7 @@ class Requester ( } fun addFunction(sql: String): Requester { - DefinitionFunction.build(sql).forEach { + DefinitionFunction(sql).let { functions[it.name] = Function(it, connection) } return this diff --git a/src/main/kotlin/fr/postgresjson/definition/Function.kt b/src/main/kotlin/fr/postgresjson/definition/Function.kt index aff088a..ba17844 100644 --- a/src/main/kotlin/fr/postgresjson/definition/Function.kt +++ b/src/main/kotlin/fr/postgresjson/definition/Function.kt @@ -4,45 +4,54 @@ import java.io.File open class Function ( - override val name: String, - override val script: String, - override val parameters: List + override val script: String ) : Resource, ParametersInterface { + override val name: String + override val parameters: List override var source: File? = null + init { + 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(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) + + val paramsRegex = """\s*(?((?IN|OUT|INOUT|VARIADIC)?\s+)?(?[^\s,)]+\s+)?(?[^\s,)]+)(\s+(?default\s|=)\s*[^\s,)]+)?)\s*(,|$)""" + .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) + + val queryMatch = functionRegex.find(script) + if (queryMatch !== null) { + val functionName = queryMatch.groups.get("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 -> + Parameter( + paramsMatch.groups["name"]!!.value.trim(), + paramsMatch.groups["type"]!!.value.trim(), + paramsMatch.groups["direction"]?.value?.trim(), + paramsMatch.groups["default"]?.value?.trim()) + }.toList() + } else { + listOf() + } + this.name = functionName!! + this.parameters = parameters + } else { + throw FunctionNotFound() + } + } + abstract class ParseException(message: String, cause: Throwable? = null): Exception(message, cause) + class FunctionNotFound(cause: Throwable? = null): ParseException("Function not found in script", cause) + companion object { fun build(source: File): List { - return build(source.readText()) - } - - fun build(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(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) - - val paramsRegex = """\s*(?((?IN|OUT|INOUT|VARIADIC)?\s+)?(?[^\s,)]+\s+)?(?[^\s,)]+)(\s+(?default\s|=)\s*[^\s,)]+)?)\s*(,|$)""" - .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.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 -> - Parameter( - paramsMatch.groups["name"]!!.value.trim(), - paramsMatch.groups["type"]!!.value.trim(), - paramsMatch.groups["direction"]?.value?.trim(), - paramsMatch.groups["default"]?.value?.trim()) - }.toList() - } else { - listOf() - } - - Function(functionName!!, functionContent, parameters) - }.toList() + return source.readText() + .split("CREATE +(OR REPLACE +)?(PROCEDURE|FUNCTION)".toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))) + .map { + Function("CREATE OR REPLACE FUNCTION $it") + } } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/postgresjson/migration/Function.kt b/src/main/kotlin/fr/postgresjson/migration/Function.kt index 7af0574..c61268c 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Function.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Function.kt @@ -4,12 +4,19 @@ import fr.postgresjson.connexion.Connection import fr.postgresjson.definition.Function as DefinitionFunction class Function( - private val up: DefinitionFunction, - private val down: DefinitionFunction, + val up: DefinitionFunction, + val down: DefinitionFunction, private val connection: Connection ): Migration { + val name = up.name enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } + init { + if (up.name !== down.name) { + throw Exception("UP and DOWN migration must be the same") + } + } + override fun up(): Int { connection.exec(up.script) return 1 diff --git a/src/main/kotlin/fr/postgresjson/migration/Migrations.kt b/src/main/kotlin/fr/postgresjson/migration/Migrations.kt index ae80ee2..5420abc 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Migrations.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Migrations.kt @@ -17,6 +17,7 @@ interface Migration { class Migrations(directory: File, private val connection: Connection): Migration { private val queries: MutableList = mutableListOf() private val functions: MutableMap = mutableMapOf() + private var initialized = false init { directory.walk().filter { @@ -26,15 +27,15 @@ class Migrations(directory: File, private val connection: Connection): Migration it.isFile }.forEach { file -> if (file.name.endsWith(".up.sql")) { - val up = file.readText() - val down = file.path.substring(0, file.path.size - 7).let { + file.path.substring(0, file.path.size - 7).let { try { - File("$it.down.sql").readText() + val down = File("$it.down.sql").readText() + val up = file.readText() + addQuery(file.name, up, down) } catch (e: FileNotFoundException) { throw DownMigrationNotDefined("$it.down.sql", e) } } - addQuery(up, down) } else if (file.name.endsWith(".down.sql")) { // Nothing } else { @@ -53,14 +54,14 @@ class Migrations(directory: File, private val connection: Connection): Migration } fun addFunction(sql: String): Migrations { - DefinitionFunction.build(sql).forEach { + DefinitionFunction(sql).let { functions[it.name] = Function(it, it, connection) } return this } - fun addQuery(up: String, down: String): Migrations { - queries.add(Query(up, down, connection)) + fun addQuery(name: String, up: String, down: String): Migrations { + queries.add(Query(name, up, down, connection)) return this } diff --git a/src/main/kotlin/fr/postgresjson/migration/Query.kt b/src/main/kotlin/fr/postgresjson/migration/Query.kt index 225d668..94b2405 100644 --- a/src/main/kotlin/fr/postgresjson/migration/Query.kt +++ b/src/main/kotlin/fr/postgresjson/migration/Query.kt @@ -3,8 +3,9 @@ package fr.postgresjson.migration import fr.postgresjson.connexion.Connection class Query( - private val up: String, - private val down: String, + val name: String, + val up: String, + val down: String, private val connection: Connection ): Migration { enum class Status(i: Int) { OK(2), UP_FAIL(0), DOWN_FAIL(1) } diff --git a/src/test/resources/fixtures/init.sql b/src/test/resources/fixtures/init.sql index 2e4699f..fc974ce 100644 --- a/src/test/resources/fixtures/init.sql +++ b/src/test/resources/fixtures/init.sql @@ -3,6 +3,9 @@ -- grant all privileges on database test to test; -- ALTER SCHEMA public owner to test; +drop schema IF EXISTS public cascade; +create schema if not exists public; + create table if not exists test ( id serial not null