WIP: Compiled SQL function #33

Draft
flecomte wants to merge 37 commits from compiled_sql_function into master
7 changed files with 118 additions and 15 deletions
Showing only changes of commit 9cba119337 - Show all commits

View File

@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val containerAlwaysOn: String by project
val disableLint: String by project
val projectName = "postgres-json"
val projectName: String by project
plugins {
jacoco
@@ -87,7 +87,7 @@ val sourcesJar by tasks.creating(Jar::class) {
apply(plugin = "docker-compose")
dockerCompose {
setProjectName(projectName)
setProjectName(projectName.toString())
setProperty("useComposeFiles", listOf("docker-compose.yml"))
setProperty("stopContainers", !containerAlwaysOn.toBoolean())
isRequiredBy(project.tasks.test)
@@ -96,7 +96,7 @@ dockerCompose {
publishing {
repositories {
maven {
name = projectName
name = projectName.toString()
url = uri("https://maven.pkg.github.com/flecomte/postgres-json")
credentials {
username = System.getenv("GITHUB_USERNAME")
@@ -106,7 +106,7 @@ publishing {
}
publications {
create<MavenPublication>(projectName) {
create<MavenPublication>(projectName.toString()) {
from(components["java"])
artifact(sourcesJar)
}

View File

@@ -8,3 +8,4 @@ systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacoco
org.gradle.jvmargs=-Xmx4096M
containerAlwaysOn=false
disableLint=false
projectName=postgres-json

View File

@@ -36,5 +36,10 @@ sealed class Returns(
isSetOf: Boolean,
) : Returns("any", isSetOf)
class Unknown(
definition: String,
isSetOf: Boolean,
) : Returns(definition, isSetOf)
class Void : Returns("void", false)
}

View File

@@ -9,6 +9,8 @@ import fr.postgresjson.definition.Parameter.Direction.OUT
import fr.postgresjson.definition.ParameterType
import fr.postgresjson.definition.Resource.ParseException
import fr.postgresjson.definition.Returns
import fr.postgresjson.definition.Returns.Primitive
import fr.postgresjson.definition.Returns.Unknown
import fr.postgresjson.definition.Returns.Void
import java.nio.file.Path
import kotlin.text.RegexOption.IGNORE_CASE
@@ -34,6 +36,7 @@ internal fun ScriptPart.getFunctionName(): NextScript<String> {
throw FunctionNameMalformed(this, e)
}
}
internal class FunctionNameMalformed(val script: ScriptPart, cause: Throwable? = null) :
ParseException("Function name is malformed", cause)
@@ -78,6 +81,7 @@ private fun ScriptPart.toParameter(): Parameter {
default = script.getParameterDefault().trimSpace().apply { script = nextScriptPart }.value,
)
}
private fun ScriptPart.getParameterMode(): NextScript<Direction> {
return when {
restOfScript.startsWith("inout ", true) -> NextScript(INOUT, restOfScript.drop("inout ".length))
@@ -95,6 +99,7 @@ private fun ScriptPart.getParameterName(): NextScript<String> {
throw ParameterNameMalformed(this, e)
}
}
private class ParameterNameMalformed(val script: ScriptPart, cause: Throwable) :
ParseException("Parameter name is malformed", cause)
@@ -153,8 +158,52 @@ private class ParameterDefaultMalformed(val script: ScriptPart) :
* TODO Finalize this
*/
internal fun ScriptPart.getReturns(): NextScript<Returns> {
val rest = this.trimSpace()
if (!rest.restOfScript.startsWith("returns")) {
return NextScript(Void(), "")
}
var returns = ScriptPart(rest.restOfScript.drop("returns".length))
.getNextScript { this.afterBeginBy(Regex("\\s+language\\s+", IGNORE_CASE), Regex("\\s+as\\s+", IGNORE_CASE)) }
.trimSpace()
.value
.trimStart()
val isSetOf = returns.startsWith("SETOF", ignoreCase = true)
if (isSetOf) {
returns = returns.drop("SETOF".length).trimStart()
}
val returnsClass = if (returns.isBlank()) {
Void()
} else if (primitiveList.contains(ScriptPart(returns).getParameterType().value.name)) {
Primitive(returns, isSetOf)
} else {
Unknown(returns, isSetOf)
}
return NextScript(returnsClass, "")
}
private val primitiveList = listOf(
"text",
"varchar",
"character varying",
"character",
"char",
"int",
"smallint",
"integer",
"bigint",
"decimal",
"real",
"double precision",
"float",
"numeric",
"boolean",
"json",
"jsonb",
)
class ParseError(message: String? = null, cause: Throwable? = null) :
ParseException(message ?: "Parsing fail", cause)

View File

@@ -156,7 +156,10 @@ internal data class Context(
val script: String,
) {
fun afterBeginBy(vararg texts: String): Boolean = texts.any {
script.substring(index + 1).take(it.length) == it
script.drop(index + 1).take(it.length) == it
}
fun afterBeginBy(vararg texts: Regex): Boolean = texts.any {
it.matchAt(script, index + 1) != null
}
val nextChar: Char? get() = script.substring(index + 1).getOrNull(0)

View File

@@ -9,14 +9,14 @@ import fr.postgresjson.serializer.toTypeReference
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.assertThrows
import java.util.UUID
import kotlin.reflect.full.hasAnnotation
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.assertThrows
class ConnectionTest : StringSpec({
val connection = TestConnection()

View File

@@ -1,9 +1,11 @@
package fr.postgresjson.definition
import fr.postgresjson.definition.Returns.Primitive
import fr.postgresjson.definition.parse.parseFunction
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import org.amshove.kluent.shouldBeInstanceOf
class FunctionTest : FreeSpec({
"Function name" - {
@@ -83,7 +85,6 @@ class FunctionTest: FreeSpec({
}
}
"Parameters" - {
"One parameter text" - {
val param = parseFunction(
@@ -215,6 +216,50 @@ class FunctionTest: FreeSpec({
}
}
"Function Returns" - {
"should return the type text" {
val returns = parseFunction(
// language=PostgreSQL
"""
create or replace function myfun() returns text language plpgsql as
$$ begin; end$$;
""".trimIndent()
).returns
returns shouldBeInstanceOf Primitive::class
returns.definition shouldBe "text"
returns.isSetOf shouldBe false
}
"should return the type character varying" {
val returns = parseFunction(
// language=PostgreSQL
"""
create or replace function myfun() returns character varying language plpgsql as
$$ begin; end$$;
""".trimIndent()
).returns
returns shouldBeInstanceOf Primitive::class
returns.definition shouldBe "character varying"
returns.isSetOf shouldBe false
}
"should return the type character varying(255)" {
val returns = parseFunction(
// language=PostgreSQL
"""
create or replace function myfun() returns character varying(255) language plpgsql as
$$ begin; end$$;
""".trimIndent()
).returns
returns shouldBeInstanceOf Primitive::class
returns.definition shouldBe "character varying(255)"
returns.isSetOf shouldBe false
}
}
// "function returns" - {
// "should return the type text if function return text" {
// Function(