Parse Function Returns

This commit is contained in:
2023-06-01 22:47:10 +02:00
parent 1def5ae095
commit 9cba119337
7 changed files with 118 additions and 15 deletions

View File

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

View File

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

View File

@@ -36,5 +36,10 @@ sealed class Returns(
isSetOf: Boolean, isSetOf: Boolean,
) : Returns("any", isSetOf) ) : Returns("any", isSetOf)
class Unknown(
definition: String,
isSetOf: Boolean,
) : Returns(definition, isSetOf)
class Void : Returns("void", false) 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.ParameterType
import fr.postgresjson.definition.Resource.ParseException import fr.postgresjson.definition.Resource.ParseException
import fr.postgresjson.definition.Returns import fr.postgresjson.definition.Returns
import fr.postgresjson.definition.Returns.Primitive
import fr.postgresjson.definition.Returns.Unknown
import fr.postgresjson.definition.Returns.Void import fr.postgresjson.definition.Returns.Void
import java.nio.file.Path import java.nio.file.Path
import kotlin.text.RegexOption.IGNORE_CASE import kotlin.text.RegexOption.IGNORE_CASE
@@ -34,6 +36,7 @@ internal fun ScriptPart.getFunctionName(): NextScript<String> {
throw FunctionNameMalformed(this, e) throw FunctionNameMalformed(this, e)
} }
} }
internal class FunctionNameMalformed(val script: ScriptPart, cause: Throwable? = null) : internal class FunctionNameMalformed(val script: ScriptPart, cause: Throwable? = null) :
ParseException("Function name is malformed", cause) ParseException("Function name is malformed", cause)
@@ -78,6 +81,7 @@ private fun ScriptPart.toParameter(): Parameter {
default = script.getParameterDefault().trimSpace().apply { script = nextScriptPart }.value, default = script.getParameterDefault().trimSpace().apply { script = nextScriptPart }.value,
) )
} }
private fun ScriptPart.getParameterMode(): NextScript<Direction> { private fun ScriptPart.getParameterMode(): NextScript<Direction> {
return when { return when {
restOfScript.startsWith("inout ", true) -> NextScript(INOUT, restOfScript.drop("inout ".length)) restOfScript.startsWith("inout ", true) -> NextScript(INOUT, restOfScript.drop("inout ".length))
@@ -95,6 +99,7 @@ private fun ScriptPart.getParameterName(): NextScript<String> {
throw ParameterNameMalformed(this, e) throw ParameterNameMalformed(this, e)
} }
} }
private class ParameterNameMalformed(val script: ScriptPart, cause: Throwable) : private class ParameterNameMalformed(val script: ScriptPart, cause: Throwable) :
ParseException("Parameter name is malformed", cause) ParseException("Parameter name is malformed", cause)
@@ -153,8 +158,52 @@ private class ParameterDefaultMalformed(val script: ScriptPart) :
* TODO Finalize this * TODO Finalize this
*/ */
internal fun ScriptPart.getReturns(): NextScript<Returns> { internal fun ScriptPart.getReturns(): NextScript<Returns> {
val rest = this.trimSpace()
if (!rest.restOfScript.startsWith("returns")) {
return NextScript(Void(), "") 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) : class ParseError(message: String? = null, cause: Throwable? = null) :
ParseException(message ?: "Parsing fail", cause) ParseException(message ?: "Parsing fail", cause)

View File

@@ -156,7 +156,10 @@ internal data class Context(
val script: String, val script: String,
) { ) {
fun afterBeginBy(vararg texts: String): Boolean = texts.any { 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) val nextChar: Char? get() = script.substring(index + 1).getOrNull(0)

View File

@@ -9,16 +9,16 @@ import fr.postgresjson.serializer.toTypeReference
import io.kotest.core.spec.style.StringSpec import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull 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 java.util.UUID
import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.hasAnnotation
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.assertThrows
class ConnectionTest: StringSpec({ class ConnectionTest : StringSpec({
val connection = TestConnection() val connection = TestConnection()
@SqlSerializable @SqlSerializable
@@ -116,7 +116,7 @@ class ConnectionTest: StringSpec({
"test call request without args" { "test call request without args" {
val result: ObjTest? = connection.execute( val result: ObjTest? = connection.execute(
"select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', 'myName')", "select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', 'myName')",
object: TypeReference<ObjTest>() {} object : TypeReference<ObjTest>() {}
) { ) {
assertEquals("myName", this.deserialize<ObjTest>()?.name) assertEquals("myName", this.deserialize<ObjTest>()?.name)
} }
@@ -125,7 +125,7 @@ class ConnectionTest: StringSpec({
} }
"test call request return null" { "test call request return null" {
val result: ObjTest? = connection.execute("select null;", object: TypeReference<ObjTest>() {}) val result: ObjTest? = connection.execute("select null;", object : TypeReference<ObjTest>() {})
result.shouldBeNull() result.shouldBeNull()
} }
@@ -141,7 +141,7 @@ class ConnectionTest: StringSpec({
) )
assertThrows<DataNotFoundException> { assertThrows<DataNotFoundException> {
execute("select * from test where false;", object: TypeReference<ObjTest>() {}) execute("select * from test where false;", object : TypeReference<ObjTest>() {})
} }
} }

View File

@@ -1,11 +1,13 @@
package fr.postgresjson.definition package fr.postgresjson.definition
import fr.postgresjson.definition.Returns.Primitive
import fr.postgresjson.definition.parse.parseFunction import fr.postgresjson.definition.parse.parseFunction
import io.kotest.core.spec.style.FreeSpec import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import org.amshove.kluent.shouldBeInstanceOf
class FunctionTest: FreeSpec({ class FunctionTest : FreeSpec({
"Function name" - { "Function name" - {
"all in lower" { "all in lower" {
parseFunction( parseFunction(
@@ -83,7 +85,6 @@ class FunctionTest: FreeSpec({
} }
} }
"Parameters" - { "Parameters" - {
"One parameter text" - { "One parameter text" - {
val param = parseFunction( 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" - { // "function returns" - {
// "should return the type text if function return text" { // "should return the type text if function return text" {
// Function( // Function(