WIP: Compiled SQL function #33

Draft
flecomte wants to merge 37 commits from compiled_sql_function into master
2 changed files with 68 additions and 18 deletions
Showing only changes of commit 9f6c32375e - Show all commits

View File

@@ -1,11 +1,11 @@
package fr.postgresjson.definition package fr.postgresjson.definition
import com.github.jasync.sql.db.util.length
import fr.postgresjson.definition.Parameter.Direction import fr.postgresjson.definition.Parameter.Direction
import java.nio.file.Path import java.nio.file.Path
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.text.RegexOption.IGNORE_CASE
class Function( class Function(
override val script: String, override val script: String,
@@ -46,6 +46,8 @@ class Function(
private class NextScript<T>(val value: T, val restOfScript: String) { private class NextScript<T>(val value: T, val restOfScript: String) {
val nextScriptPart: ScriptPart = ScriptPart(restOfScript) val nextScriptPart: ScriptPart = ScriptPart(restOfScript)
fun isLast() = restOfScript == ""
fun isEmptyValue() = value == "" || value == null
} }
/** /**
@@ -79,7 +81,7 @@ class Function(
private fun ScriptPart.getFunctionName(): NextScript<String> { private fun ScriptPart.getFunctionName(): NextScript<String> {
try { try {
return getNextScript { status.isNotEscaped() && listOf("(", " ", "\n").any { afterBeginBy(it) } } return getNextScript { status.isNotEscaped() && afterBeginBy("(", " ", "\n") }
} catch (e: NameMalformed) { } catch (e: NameMalformed) {
throw FunctionNameMalformed(null, e) throw FunctionNameMalformed(null, e)
} }
@@ -122,7 +124,7 @@ class Function(
* Get next part of script. * Get next part of script.
* You can define a list of characters that end the part of script. Like `(` or space. * You can define a list of characters that end the part of script. Like `(` or space.
*/ */
private fun ScriptPart.getNextScript(isEnd: Context.() -> Boolean): NextScript<String> { private fun ScriptPart.getNextScript(isEnd: Context.() -> Boolean = { false }): NextScript<String> {
val status = Status() val status = Status()
fun String.unescape(): String { fun String.unescape(): String {
@@ -224,6 +226,22 @@ class Function(
return NextScript(value, restOfScript.apply { dropWhile { it in chars } }) return NextScript(value, restOfScript.apply { dropWhile { it in chars } })
} }
private fun <T> NextScript<T>.changeValue(block: (T) -> T): NextScript<T> {
return NextScript(block(value), restOfScript)
}
private fun <T> NextScript<T>.changeScript(block: (String) -> String): NextScript<T> {
return NextScript(value, block(restOfScript))
}
private fun <T> NextScript<T>.dropOneOf(vararg endTextList: String): NextScript<T> {
return changeScript { script ->
endTextList
.filter { script.startsWith(it) }
.let { script.drop(it.size) }
}
}
private fun ScriptPart.toArgument(): Parameter { private fun ScriptPart.toArgument(): Parameter {
var script: ScriptPart = this.trimSpace() var script: ScriptPart = this.trimSpace()
return Parameter( return Parameter(
@@ -245,10 +263,7 @@ class Function(
private fun ScriptPart.getArgName(): NextScript<String> { private fun ScriptPart.getArgName(): NextScript<String> {
try { try {
return getNextScript { return getNextScript { afterBeginBy(" ", "\n") }
listOf(" ", "\n")
.any { afterBeginBy(it) }
}
} catch (e: NameMalformed) { } catch (e: NameMalformed) {
throw ArgNameMalformed(null, e) throw ArgNameMalformed(null, e)
} }
@@ -256,10 +271,8 @@ class Function(
private fun ScriptPart.getArgType(): NextScript<ArgumentType> { private fun ScriptPart.getArgType(): NextScript<ArgumentType> {
val fullType = try { val fullType = try {
getNextScript { val endTextList = arrayOf(" default ", "=", ")")
listOf(" default ", "=", ")") getNextScript { afterBeginBy(texts = endTextList) }
.any { afterBeginBy(it) }
}
} catch (e: ParseError) { } catch (e: ParseError) {
throw ArgTypeMalformed(null, e) throw ArgTypeMalformed(null, e)
} }
@@ -285,11 +298,17 @@ class Function(
) )
} }
/**
* TODO implement this method
*/
private fun ScriptPart.getArgDefault(): NextScript<String?> { private fun ScriptPart.getArgDefault(): NextScript<String?> {
return NextScript("plop", "") return if (this.isEmpty() || this.restOfScript == ")") {
NextScript(null, "")
} else {
"""^(\s*=\s*|\s+default\s+)(.+)\s*$"""
.toRegex(IGNORE_CASE)
.find(restOfScript)
.let { it ?: throw ArgDefaultMalformed() }
.let { it.groups[2]!!.value }
.let { NextScript(it, "") }
}
} }
/** /**
@@ -303,11 +322,16 @@ class Function(
class ArgumentNotFound(cause: Throwable? = null): Resource.ParseException("Argument not found in script", cause) class ArgumentNotFound(cause: Throwable? = null): Resource.ParseException("Argument not found in script", cause)
class FunctionNameMalformed(message: String? = null, cause: Throwable? = null): class FunctionNameMalformed(message: String? = null, cause: Throwable? = null):
Resource.ParseException(message ?: "Function name is malformed", cause) Resource.ParseException(message ?: "Function name is malformed", cause)
class ArgNameMalformed(message: String? = null, cause: Throwable? = null): class ArgNameMalformed(message: String? = null, cause: Throwable? = null):
Resource.ParseException(message ?: "Arg name is malformed", cause) Resource.ParseException(message ?: "Arg name is malformed", cause)
class ArgTypeMalformed(message: String? = null, cause: Throwable? = null): class ArgTypeMalformed(message: String? = null, cause: Throwable? = null):
Resource.ParseException(message ?: "Arg type is malformed", cause) Resource.ParseException(message ?: "Arg type is malformed", cause)
class ArgDefaultMalformed(message: String? = null, cause: Throwable? = null):
Resource.ParseException(message ?: "Arg default is malformed", cause)
class NameMalformed(message: String? = null, cause: Throwable? = null): class NameMalformed(message: String? = null, cause: Throwable? = null):
Resource.ParseException(message ?: "name is malformed", cause) Resource.ParseException(message ?: "name is malformed", cause)

View File

@@ -186,6 +186,32 @@ class FunctionTest: FreeSpec({
param[0].type.scale shouldBe 8 param[0].type.scale shouldBe 8
} }
} }
"parameters with default text" - {
val param = Function(
// language=PostgreSQL
"""
create or replace function myfun(one text default 'example') returns text language plpgsql as
$$ begin end;$$;
""".trimIndent()
).parameters
"should have 1 parameters" {
param shouldHaveSize 1
}
"should have name" {
param[0].name shouldBe "one"
}
"should have type name" {
param[0].type.name shouldBe "text"
}
"should have default text" {
param[0].default shouldBe "'example'"
}
}
} }
// "function returns" - { // "function returns" - {