diff --git a/build.gradle.kts b/build.gradle.kts index 1c24a43..bacd380 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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(projectName) { + create(projectName.toString()) { from(components["java"]) artifact(sourcesJar) } diff --git a/gradle.properties b/gradle.properties index c9ef82b..2701bb5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,5 @@ systemProp.sonar.java.coveragePlugin=jacoco systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml org.gradle.jvmargs=-Xmx4096M containerAlwaysOn=false -disableLint=false \ No newline at end of file +disableLint=false +projectName=postgres-json \ No newline at end of file diff --git a/src/main/kotlin/fr/postgresjson/definition/Returns.kt b/src/main/kotlin/fr/postgresjson/definition/Returns.kt index 7559f60..403efab 100644 --- a/src/main/kotlin/fr/postgresjson/definition/Returns.kt +++ b/src/main/kotlin/fr/postgresjson/definition/Returns.kt @@ -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) } diff --git a/src/main/kotlin/fr/postgresjson/definition/parse/ParsingFunction.kt b/src/main/kotlin/fr/postgresjson/definition/parse/ParsingFunction.kt index 7953310..732da48 100644 --- a/src/main/kotlin/fr/postgresjson/definition/parse/ParsingFunction.kt +++ b/src/main/kotlin/fr/postgresjson/definition/parse/ParsingFunction.kt @@ -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 { 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 { return when { restOfScript.startsWith("inout ", true) -> NextScript(INOUT, restOfScript.drop("inout ".length)) @@ -95,6 +99,7 @@ private fun ScriptPart.getParameterName(): NextScript { 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 { - return NextScript(Void(), "") + 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) diff --git a/src/main/kotlin/fr/postgresjson/definition/parse/ParsingHelper.kt b/src/main/kotlin/fr/postgresjson/definition/parse/ParsingHelper.kt index 0663eff..6ec75de 100644 --- a/src/main/kotlin/fr/postgresjson/definition/parse/ParsingHelper.kt +++ b/src/main/kotlin/fr/postgresjson/definition/parse/ParsingHelper.kt @@ -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) diff --git a/src/test/kotlin/fr/postgresjson/ConnectionTest.kt b/src/test/kotlin/fr/postgresjson/ConnectionTest.kt index 789c888..cdb6261 100644 --- a/src/test/kotlin/fr/postgresjson/ConnectionTest.kt +++ b/src/test/kotlin/fr/postgresjson/ConnectionTest.kt @@ -9,16 +9,16 @@ 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({ +class ConnectionTest : StringSpec({ val connection = TestConnection() @SqlSerializable @@ -116,7 +116,7 @@ class ConnectionTest: StringSpec({ "test call request without args" { val result: ObjTest? = connection.execute( "select json_build_object('id', '2c0243ed-ff4d-4b9f-a52b-e38c71b0ed00', 'name', 'myName')", - object: TypeReference() {} + object : TypeReference() {} ) { assertEquals("myName", this.deserialize()?.name) } @@ -125,7 +125,7 @@ class ConnectionTest: StringSpec({ } "test call request return null" { - val result: ObjTest? = connection.execute("select null;", object: TypeReference() {}) + val result: ObjTest? = connection.execute("select null;", object : TypeReference() {}) result.shouldBeNull() } @@ -141,7 +141,7 @@ class ConnectionTest: StringSpec({ ) assertThrows { - execute("select * from test where false;", object: TypeReference() {}) + execute("select * from test where false;", object : TypeReference() {}) } } diff --git a/src/test/kotlin/fr/postgresjson/definition/FunctionTest.kt b/src/test/kotlin/fr/postgresjson/definition/FunctionTest.kt index f999826..a35dfdf 100644 --- a/src/test/kotlin/fr/postgresjson/definition/FunctionTest.kt +++ b/src/test/kotlin/fr/postgresjson/definition/FunctionTest.kt @@ -1,11 +1,13 @@ 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({ +class FunctionTest : FreeSpec({ "Function name" - { "all in lower" { parseFunction( @@ -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(