WIP: First implement of compiled function

This commit is contained in:
2023-01-13 00:29:08 +01:00
parent 72a7aa7273
commit 39bae86307
7 changed files with 189 additions and 15 deletions

View File

@@ -12,29 +12,32 @@ class Function(
init {
val functionRegex =
"""create (or replace )?(procedure|function) *(?<name>[^(\s]+)\s*\((?<params>(\s*((IN|OUT|INOUT|VARIADIC)?\s+)?([^\s,)]+\s+)?([^\s,)]+)(\s+(?:default\s|=)\s*[^\s,)]+)?\s*(,|(?=\))))*)\) *(?<return>RETURNS *[^ \n]+)?"""
"""create (or replace )?(procedure|function) *(?<fname>[^(\s]+)\s*\(\s*(?<params>\s*([^()]+(\([^)]+\))*)*)\s*\)(RETURNS *(?<return>[^ \n]+))?"""
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
val paramsRegex =
"""\s*(?<param>((?<direction>IN|OUT|INOUT|VARIADIC)?\s+)?("?(?<name>[^\s,")]+)"?\s+)?(?<type>[^\s,)]+)(\s+(?<default>default\s|=)\s*[^\s,)]+)?)\s*(,|$)"""
"""\s*(?<param>((?<direction>IN|OUT|INOUT|VARIADIC)?\s+)?("?(?<pname>[^\s,")]+)"?\s+)?(?<type>((?!default)[a-z0-9]+\s?)+(\((?<precision>[0-9]+)(, (?<scale>[0-9]+))?\))?)(\s+(default\s|=)\s*(?<default>('[^']+?'|[0-9]+|true|false))(?<defaultType>\s*::\s*[a-z0-9]+(\([0-9]+(\s?,\s?[0-9]+\s?)?\))?)?)?)\s*(,|${'$'})"""
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
val queryMatch = functionRegex.find(script)
if (queryMatch !== null) {
val functionName = queryMatch.groups["name"]?.value?.trim() ?: error("Function name not found")
val functionName = queryMatch.groups["fname"]?.value?.trim() ?: error("Function name not found")
val functionParameters = queryMatch.groups["params"]?.value?.trim()
this.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()
)
paramsRegex
.findAll(functionParameters)
.mapIndexed { index, paramsMatch ->
Parameter(
paramsMatch.groups["pname"]?.value?.trim() ?: """arg${index+1}""",
paramsMatch.groups["type"]?.value?.trim() ?: throw ArgumentNotFound(),
paramsMatch.groups["direction"]?.value?.trim(),
paramsMatch.groups["default"]?.value?.trim(),
paramsMatch.groups["precision"]?.value?.trim()?.toInt(),
paramsMatch.groups["scale"]?.value?.trim()?.toInt()
)
}.toList()
} else {
listOf()
@@ -47,6 +50,7 @@ class Function(
}
class FunctionNotFound(cause: Throwable? = null) : Resource.ParseException("Function not found in script", cause)
class ArgumentNotFound(cause: Throwable? = null) : Resource.ParseException("Argument not found in script", cause)
fun getDefinition(): String {
return parameters

View File

@@ -9,7 +9,7 @@ interface ParameterI {
val default: String
}
class Parameter(val name: String, val type: String, direction: Direction? = Direction.IN, val default: Any? = null) {
class Parameter(val name: String, val type: String, direction: Direction? = Direction.IN, val default: String? = null, val precision: Int? = null, val scale: Int? = null) {
val direction: Direction
init {
@@ -20,11 +20,13 @@ class Parameter(val name: String, val type: String, direction: Direction? = Dire
}
}
constructor(name: String, type: String, direction: String? = "IN", default: Any? = null) : this(
constructor(name: String, type: String, direction: String? = "IN", default: String? = null, precision: Int? = null, scale: Int? = null) : this(
name = name,
type = type,
direction = direction?.let { Direction.valueOf(direction.uppercase(Locale.getDefault())) },
default = default
default = default,
precision = precision,
scale = scale
)
enum class Direction { IN, OUT, INOUT }

View File

@@ -0,0 +1,113 @@
package fr.postgresjson.functionGenerator
import fr.postgresjson.definition.Function
import fr.postgresjson.definition.Parameter
import fr.postgresjson.definition.Parameter.Direction.IN
import fr.postgresjson.definition.Parameter.Direction.INOUT
import fr.postgresjson.definition.Parameter.Direction.OUT
import fr.postgresjson.utils.searchSqlFiles
import fr.postgresjson.utils.toCamelCase
import java.io.File
import java.net.URI
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class FunctionGenerator(private val functionsDirectories: List<URI>) {
constructor(functionsDirectories: URI): this(listOf(functionsDirectories))
private val logger: Logger = LoggerFactory.getLogger("sqlFilesSearch")
private fun List<Parameter>.toKotlinArgs(): String {
return filter { it.direction == IN || it.direction == INOUT }
.joinToString(", ") {
val base = """${it.kotlinName}: ${it.kotlinType}"""
val default = if (it.default == null) {
""
} else {
when (it.kotlinType) {
"String" -> """ = "${it.default.trim('\'')}""""
"Int" -> """ = ${it.default}"""
"Boolean" -> """ = ${it.default.lowercase()}"""
else -> ""
}
}
base+default
}
}
private fun List<Parameter>.toMapOf(): String {
return filter { it.direction == IN || it.direction == INOUT }
.joinToString(", ", prefix = "mapOf(", postfix = ")") { """"${it.kotlinName}" to ${it.kotlinName}""" }
}
private val Parameter.kotlinType: String
get() {
return when (type.lowercase()) {
"text" -> "String"
"varchar" -> "String"
"character varying" -> "String"
"character" -> "String"
"char" -> "String"
"int" -> "Int"
"boolean" -> "Boolean"
"json" -> "S"
"jsonb" -> "S"
"any" -> "Any"
"anyelement" -> "Any"
"anyarray" -> "List<*>"
else -> "String"
}
}
private val Parameter.kotlinName: String
get() {
return name.toCamelCase().trimStart('_')
}
private val Function.kotlinName: String
get() {
return name.toCamelCase().trimStart('_')
}
fun generate(outputDirectory: URI) {
File(outputDirectory.path).apply {
logger.debug("Create Directory: $absolutePath")
mkdirs()
}
this.functionsDirectories
.flatMap { it.searchSqlFiles() }
.filterIsInstance<Function>()
.map { it.run {
val args = parameters.toKotlinArgs()
File("${outputDirectory.path}${kotlinName}.kt").apply {
logger.debug("Create kotlin file: $absolutePath")
val hasGenerics: Boolean = parameters.filter { it.direction != OUT }.any { it.kotlinType == "S" }
val genericsType = if (hasGenerics) ", S: Serializable" else ""
val hasReturn = parameters.any { it.direction != IN } || (it.returns != "" && it.returns != "void")
val returnTypeGenerics = if (hasReturn) "reified E: EntityI" else ""
val returnType = if (hasReturn) ": List<E>" else ""
val returnWord = if (hasReturn) "return " else ""
val select = if (hasReturn) "select<E>" else "exec"
val function = if (hasGenerics || hasReturn) """inline fun <$returnTypeGenerics$genericsType>""" else "fun"
val importEntityI = if (hasReturn) "import fr.postgresjson.entity.EntityI\n" else ""
val importSerializable = if (hasGenerics) "import fr.postgresjson.entity.Serializable\n" else ""
val importSelect = if (hasReturn) "import fr.postgresjson.connexion.select\n" else ""
writeText("""
|package fr.postgresjson.functionGenerator.generated
|
|import fr.postgresjson.connexion.Requester
|$importSelect$importSerializable$importEntityI
|$function Requester.$kotlinName($args)$returnType {
| ${returnWord}getFunction("${it.name}")
| .$select(${parameters.toMapOf()})
|}
""".trimMargin())
}}
}
}
}

View File

@@ -0,0 +1,7 @@
package fr.postgresjson.utils
fun String.toCamelCase(): String {
return "_[a-zA-Z]".toRegex().replace(this) {
it.value.replace("_", "").uppercase()
}
}

View File

@@ -14,7 +14,7 @@ import kotlin.streams.asSequence
fun URL.searchSqlFiles() = this.toURI().searchSqlFiles()
fun URI.searchSqlFiles() = sequence {
fun URI.searchSqlFiles(): Sequence<Resource> = sequence {
val logger: Logger = LoggerFactory.getLogger("sqlFilesSearch")
val uri: URI = this@searchSqlFiles
logger.debug("""SQL files found in "${uri.toString().substringAfter('!')}" :""")