From 304514ec92ba89ce12adeb5f7f21d4b84651f460 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Thu, 11 Jul 2024 14:11:00 -0700 Subject: [PATCH] Updates rules for variable resolution Adds support for casting from dynamic Updates tests to give greater visibility into errors --- .../eval/internal/operator/rex/ExprCast.kt | 25 +++++- .../eval/internal/PartiQLEngineDefaultTest.kt | 78 ++++++++----------- .../partiql/planner/internal/FnResolver.kt | 13 +++- .../planner/internal/typer/PlanTyper.kt | 20 ++--- .../partiql/planner/internal/typer/TypeEnv.kt | 55 ++++++++++--- .../planner/PlannerErrorReportingTests.kt | 68 ++++++---------- .../planner/internal/typer/TypeEnvTest.kt | 29 +++++++ .../partiql/spi/connector/sql/SqlBuiltins.kt | 1 - .../spi/connector/sql/builtins/FnNot.kt | 19 ----- .../value/PartiQLValueComparatorInternal.kt | 8 +- 10 files changed, 174 insertions(+), 142 deletions(-) diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt index eb6751729..fed0834d0 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt @@ -23,6 +23,7 @@ import org.partiql.value.Int64Value import org.partiql.value.Int8Value import org.partiql.value.IntValue import org.partiql.value.ListValue +import org.partiql.value.MissingValue import org.partiql.value.NullValue import org.partiql.value.NumericValue import org.partiql.value.PartiQLValue @@ -46,6 +47,7 @@ import org.partiql.value.int64Value import org.partiql.value.int8Value import org.partiql.value.intValue import org.partiql.value.listValue +import org.partiql.value.missingValue import org.partiql.value.sexpValue import org.partiql.value.stringValue import org.partiql.value.structValue @@ -59,7 +61,8 @@ import java.math.BigInteger internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.Expr { @OptIn(PartiQLValueExperimental::class) override fun eval(env: Environment): Datum { - val arg = arg.eval(env).toPartiQLValue() + val argDatum = arg.eval(env) + val arg = argDatum.toPartiQLValue() try { val partiqlValue = when (PType.fromPartiQLValueType(arg.type).kind) { PType.Kind.DYNAMIC -> TODO("Not Possible") @@ -86,9 +89,9 @@ internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.E PType.Kind.BAG -> castFromCollection(arg as BagValue<*>, cast.target) PType.Kind.LIST -> castFromCollection(arg as ListValue<*>, cast.target) PType.Kind.SEXP -> castFromCollection(arg as SexpValue<*>, cast.target) - PType.Kind.STRUCT -> TODO("CAST FROM STRUCT not yet implemented") + PType.Kind.STRUCT -> castFromStruct(argDatum, cast.target).toPartiQLValue() PType.Kind.ROW -> TODO("CAST FROM ROW not yet implemented") - PType.Kind.UNKNOWN -> TODO("CAST FROM UNKNOWN not yet implemented") + PType.Kind.UNKNOWN -> castFromUnknown(arg, cast.target) PType.Kind.VARCHAR -> TODO("CAST FROM VARCHAR not yet implemented") } return Datum.of(partiqlValue) @@ -97,6 +100,22 @@ internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.E } } + /** + * For now, we cannot cast from struct to anything else. Throw a type check exception. + */ + private fun castFromStruct(value: Datum, t: PType): Datum { + throw TypeCheckException() + } + + @OptIn(PartiQLValueExperimental::class) + private fun castFromUnknown(value: PartiQLValue, t: PType): PartiQLValue { + return when (value) { + is NullValue -> castFromNull(value, t) + is MissingValue -> missingValue() // TODO: Is this allowed? + else -> error("This shouldn't have happened") + } + } + @OptIn(PartiQLValueExperimental::class) private fun castFromNull(value: NullValue, t: PType): PartiQLValue { return when (t.kind) { diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt index b5049d0ae..95c70dffd 100644 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -1161,7 +1161,16 @@ class PartiQLEngineDefaultTest { internal fun assert() { val permissiveResult = run(mode = PartiQLEngine.Mode.PERMISSIVE) - assert(expectedPermissive == permissiveResult.first) { + val assertionCondition = try { + expectedPermissive == permissiveResult.first + } catch (t: Throwable) { + val str = buildString { + appendLine("Test Name: $name") + PlanPrinter.append(this, permissiveResult.second) + } + throw RuntimeException(str, t) + } + assert(assertionCondition) { comparisonString(expectedPermissive, permissiveResult.first, permissiveResult.second) } var error: Throwable? = null @@ -1194,7 +1203,13 @@ class PartiQLEngineDefaultTest { val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) when (val result = engine.execute(prepared)) { is PartiQLResult.Value -> return result.value to plan.plan - is PartiQLResult.Error -> throw result.cause + is PartiQLResult.Error -> { + val str = buildString { + appendLine("Execution resulted in an unexpected error. Plan:") + PlanPrinter.append(this, plan.plan) + } + throw RuntimeException(str, result.cause) + } } } @@ -1218,51 +1233,26 @@ class PartiQLEngineDefaultTest { } @Test + @Disabled fun developmentTest() { val tc = SuccessTestCase( input = """ - SELECT * - EXCLUDE - t.a.b.c[*].field_x - FROM [{ - 'a': { - 'b': { - 'c': [ - { -- c[0]; field_x to be removed - 'field_x': 0, - 'field_y': 0 - }, - { -- c[1]; field_x to be removed - 'field_x': 1, - 'field_y': 1 - }, - { -- c[2]; field_x to be removed - 'field_x': 2, - 'field_y': 2 - } - ] - } - } - }] AS t - """.trimIndent(), - expected = bagValue( - structValue( - "a" to structValue( - "b" to structValue( - "c" to listValue( - structValue( - "field_y" to int32Value(0) - ), - structValue( - "field_y" to int32Value(1) - ), - structValue( - "field_y" to int32Value(2) - ) - ) - ) - ) - ) + SELECT VALUE + CASE x + 1 + WHEN NULL THEN 'shouldnt be null' + WHEN MISSING THEN 'shouldnt be missing' + WHEN i THEN 'ONE' + WHEN f THEN 'TWO' + WHEN d THEN 'THREE' + ELSE '?' + END + FROM << i, f, d, null, missing >> AS x + """, + expected = boolValue(true), + globals = listOf( + Global("i", "1"), + Global("f", "2e0"), + Global("d", "3.") ) ) tc.assert() diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt index ceea6f1c5..33501e4d4 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt @@ -3,6 +3,7 @@ package org.partiql.planner.internal import org.partiql.planner.internal.casts.Coercions import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.typer.CompilerType +import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnSignature import org.partiql.types.PType.Kind @@ -144,10 +145,14 @@ internal object FnResolver { exactInputTypes++ continue } - // 2. Match ANY, no coercion needed - // TODO: Rewrite args in this scenario - arg.kind == Kind.UNKNOWN || p.type.kind == Kind.DYNAMIC || arg.kind == Kind.DYNAMIC -> continue - // 3. Check for a coercion + // 2. Match ANY parameter, no coercion needed + p.type.kind == Kind.DYNAMIC -> continue + arg.kind == Kind.UNKNOWN -> continue + // 3. Allow for ANY arguments + arg.kind == Kind.DYNAMIC -> { + mapping[i] = Ref.Cast(arg, p.type.toCType(), Ref.Cast.Safety.UNSAFE, true) + } + // 4. Check for a coercion else -> when (val coercion = Coercions.get(arg, p.type)) { null -> return null // short-circuit else -> mapping[i] = coercion diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 33383eeef..ec3dadb90 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -436,7 +436,7 @@ internal class PlanTyper(private val env: Env) { override fun visitRelOpJoin(node: Rel.Op.Join, ctx: Rel.Type?): Rel { // Rewrite LHS and RHS val lhs = visitRel(node.lhs, ctx) - val stack = outer + listOf(TypeEnv(lhs.type.schema, outer)) + val stack = outer + listOf(TypeEnv(env, lhs.type.schema, outer)) val rhs = RelTyper(stack, Scope.GLOBAL).visitRel(node.rhs, ctx) // Calculate output schema given JOIN type @@ -444,7 +444,7 @@ internal class PlanTyper(private val env: Env) { val type = relType(schema, ctx!!.props) // Type the condition on the output schema - val condition = node.rex.type(TypeEnv(type.schema, outer)) + val condition = node.rex.type(TypeEnv(env, type.schema, outer)) val op = relOpJoin(lhs, rhs, condition, node.type) return rel(type, op) @@ -497,7 +497,7 @@ internal class PlanTyper(private val env: Env) { val resolvedRoot = when (val root = path.root) { is Rex.Op.Var.Unresolved -> { // resolve `root` to local binding - val locals = TypeEnv(input.type.schema, outer) + val locals = TypeEnv(env, input.type.schema, outer) val path = root.identifier.toBindingPath() val resolved = locals.resolve(path) if (resolved == null) { @@ -537,7 +537,7 @@ internal class PlanTyper(private val env: Env) { val input = visitRel(node.input, ctx) // type the calls and groups - val typer = RexTyper(TypeEnv(input.type.schema, outer), Scope.LOCAL) + val typer = RexTyper(TypeEnv(env, input.type.schema, outer), Scope.LOCAL) // typing of aggregate calls is slightly more complicated because they are not expressions. val calls = node.calls.mapIndexed { i, call -> @@ -607,8 +607,8 @@ internal class PlanTyper(private val env: Env) { Rex.Op.Var.Scope.LOCAL -> Scope.LOCAL } val resolvedVar = when (scope) { - Scope.LOCAL -> locals.resolve(path) ?: env.resolveObj(path) - Scope.GLOBAL -> env.resolveObj(path) ?: locals.resolve(path) + Scope.LOCAL -> locals.resolve(path, TypeEnv.LookupStrategy.LOCALS_FIRST) + Scope.GLOBAL -> locals.resolve(path, TypeEnv.LookupStrategy.GLOBALS_FIRST) } if (resolvedVar == null) { val id = PlanUtils.externalize(node.identifier) @@ -1062,7 +1062,7 @@ internal class PlanTyper(private val env: Env) { override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: CompilerType?): Rex { val stack = locals.outer + listOf(locals) val rel = node.rel.type(stack) - val typeEnv = TypeEnv(rel.type.schema, stack) + val typeEnv = TypeEnv(env, rel.type.schema, stack) val typer = RexTyper(typeEnv, Scope.LOCAL) val key = typer.visitRex(node.key, null) val value = typer.visitRex(node.value, null) @@ -1072,7 +1072,7 @@ internal class PlanTyper(private val env: Env) { override fun visitRexOpSubquery(node: Rex.Op.Subquery, ctx: CompilerType?): Rex { val rel = node.rel.type(locals.outer + listOf(locals)) - val newTypeEnv = TypeEnv(schema = rel.type.schema, outer = locals.outer + listOf(locals)) + val newTypeEnv = TypeEnv(env, schema = rel.type.schema, outer = locals.outer + listOf(locals)) val constructor = node.constructor.type(newTypeEnv) val subquery = rexOpSubquery(constructor, rel, node.coercion) return when (node.coercion) { @@ -1119,7 +1119,7 @@ internal class PlanTyper(private val env: Env) { // TODO: Should we support the ROW type? override fun visitRexOpSelect(node: Rex.Op.Select, ctx: CompilerType?): Rex { val rel = node.rel.type(locals.outer + listOf(locals)) - val newTypeEnv = TypeEnv(schema = rel.type.schema, outer = locals.outer + listOf(locals)) + val newTypeEnv = TypeEnv(env, schema = rel.type.schema, outer = locals.outer + listOf(locals)) val constructor = node.constructor.type(newTypeEnv) val type = when (rel.isOrdered()) { true -> PType.typeList(constructor.type) @@ -1296,7 +1296,7 @@ internal class PlanTyper(private val env: Env) { * This types the [Rex] given the input record ([input]) and [stack] of [TypeEnv] (representing the outer scopes). */ private fun Rex.type(input: List, stack: List, strategy: Scope = Scope.LOCAL) = - RexTyper(TypeEnv(input, stack), strategy).visitRex(this, this.type) + RexTyper(TypeEnv(env, input, stack), strategy).visitRex(this, this.type) /** * This types the [Rex] given a [TypeEnv]. We use the [TypeEnv.schema] as the input schema and the [TypeEnv.outer] diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt index 17a7acb3c..97d0c4398 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt @@ -1,5 +1,6 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.rex @@ -22,10 +23,16 @@ import org.partiql.value.stringValue * @property outer refers to the outer variable scopes that we have access to. */ internal data class TypeEnv( + private val globals: Env, public val schema: List, public val outer: List ) { + enum class LookupStrategy { + LOCALS_FIRST, + GLOBALS_FIRST + } + internal fun getScope(depth: Int): TypeEnv { return when (depth) { 0 -> this @@ -34,24 +41,48 @@ internal data class TypeEnv( } /** - * We resolve a local with the following rules. See, PartiQL Specification p.35. - * - * 1) Check if the path root unambiguously matches a local binding name, set as root. - * 2) Check if the path root unambiguously matches a local binding struct value field. - * + * Search Algorithm (LOCALS_FIRST): + * 1. Match Binding Name + * - Match Locals + * - Match Globals + * 2. Match Nested Field + * - Match Locals + * Search Algorithm (GLOBALS_FIRST): + * 1. Match Binding Name + * - Match Globals + * - Match Locals + * 2. Match Nested Field + * - Match Locals + */ + fun resolve(path: BindingPath, strategy: LookupStrategy = LookupStrategy.LOCALS_FIRST): Rex? { + return when (strategy) { + LookupStrategy.LOCALS_FIRST -> resolveLocalName(path) ?: globals.resolveObj(path) ?: resolveLocalField(path) + LookupStrategy.GLOBALS_FIRST -> globals.resolveObj(path) ?: resolveLocalName(path) ?: resolveLocalField(path) + } + } + + /** + * Attempts to resolve using just the local binding name. + */ + private fun resolveLocalName(path: BindingPath): Rex? { + val head: BindingName = path.steps[0] + val tail: List = path.steps.drop(1) + val r = matchRoot(head) ?: return null + // Convert any remaining binding names (tail) to an untyped path expression. + return if (tail.isEmpty()) r else r.toPath(tail) + } + + /** + * Check if the path root unambiguously matches a local binding struct value field. * Convert any remaining binding names (tail) to a path expression. * * @param path * @return */ - fun resolve(path: BindingPath): Rex? { + private fun resolveLocalField(path: BindingPath): Rex? { val head: BindingName = path.steps[0] - var tail: List = path.steps.drop(1) - var r = matchRoot(head) - if (r == null) { - r = matchStruct(head) ?: return null - tail = path.steps - } + val r = matchStruct(head) ?: return null + val tail = path.steps // Convert any remaining binding names (tail) to an untyped path expression. return if (tail.isEmpty()) r else r.toPath(tail) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index 4d8f33a87..e97c546a9 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -18,6 +18,7 @@ import org.partiql.types.PType import org.partiql.types.StaticType import org.partiql.types.StructType import org.partiql.types.TupleConstraint +import java.lang.AssertionError import kotlin.test.assertEquals internal class PlannerErrorReportingTests { @@ -61,38 +62,39 @@ internal class PlannerErrorReportingTests { parser.parse(query).root } - fun assertProblem( + private fun assertProblem( plan: org.partiql.plan.PlanNode, problems: List, - vararg block: () -> Boolean + block: (List) -> Unit ) { - block.forEachIndexed { index, function -> - assert(function.invoke()) { - buildString { - this.appendLine("assertion #${index + 1} failed") + try { + block.invoke(problems) + } catch (e: Throwable) { + val str = buildString { + this.appendLine("Assertion failed") - this.appendLine("--------Plan---------") - PlanPrinter.append(this, plan) + this.appendLine("--------Plan---------") + PlanPrinter.append(this, plan) - this.appendLine("----------Problems---------") - problems.forEach { - this.appendLine(it.toString()) - } + this.appendLine("----------Problems---------") + problems.forEach { + this.appendLine(it.toString()) } } + throw AssertionError(str, e) } } data class TestCase( val query: String, val isSignal: Boolean, - val assertion: (List) -> List<() -> Boolean>, + val assertion: (List) -> Unit, val expectedType: CompilerType ) { constructor( query: String, isSignal: Boolean, - assertion: (List) -> List<() -> Boolean>, + assertion: (List) -> Unit, expectedType: StaticType = StaticType.ANY ) : this(query, isSignal, assertion, PType.fromStaticType(expectedType).toCType()) } @@ -110,11 +112,9 @@ internal class PlannerErrorReportingTests { ) ) - private fun assertOnProblemCount(warningCount: Int, errorCount: Int): (List) -> List<() -> Boolean> = { problems -> - listOf( - { problems.filter { it.details.severity == ProblemSeverity.WARNING }.size == warningCount }, - { problems.filter { it.details.severity == ProblemSeverity.ERROR }.size == errorCount }, - ) + private fun assertOnProblemCount(warningCount: Int, errorCount: Int): (List) -> Unit = { problems -> + assertEquals(warningCount, problems.filter { it.details.severity == ProblemSeverity.WARNING }.size, "Number of warnings is wrong.") + assertEquals(errorCount, problems.filter { it.details.severity == ProblemSeverity.ERROR }.size, "Number of errors is wrong.") } /** @@ -278,13 +278,13 @@ internal class PlannerErrorReportingTests { TestCase( "1 + not_a_function(1)", false, - assertOnProblemCount(1, 1), + assertOnProblemCount(0, 1), StaticType.INT4, ), TestCase( "1 + not_a_function(1)", true, - assertOnProblemCount(0, 2), + assertOnProblemCount(0, 1), StaticType.INT4, ), @@ -408,7 +408,7 @@ internal class PlannerErrorReportingTests { assertProblem( plan, problems, - *tc.assertion(problems).toTypedArray() + tc.assertion ) assertEquals(tc.expectedType, (plan.statement as org.partiql.plan.Statement.Query).root.type) } @@ -420,28 +420,4 @@ internal class PlannerErrorReportingTests { @ParameterizedTest @MethodSource("testContinuation") fun testContinuation(tc: TestCase) = runTestCase(tc) - - private fun StaticType.assertStaticTypeEqual(other: StaticType) { - val thisAll = this.allTypes.toSet() - val otherAll = other.allTypes.toSet() - val diff = (thisAll - otherAll) + (otherAll - thisAll) - assert(diff.isEmpty()) { - buildString { - this.appendLine("expected: ") - thisAll.forEach { - this.append("$it, ") - } - this.appendLine() - this.appendLine("actual") - otherAll.forEach { - this.append("$it, ") - } - this.appendLine() - this.appendLine("diff") - diff.forEach { - this.append("$it, ") - } - } - } - } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt index a5274f20d..7f41920e1 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt @@ -4,12 +4,17 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource +import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.spi.BindingCase import org.partiql.spi.BindingName import org.partiql.spi.BindingPath +import org.partiql.spi.connector.ConnectorHandle +import org.partiql.spi.connector.ConnectorMetadata +import org.partiql.spi.fn.FnExperimental import org.partiql.types.PType import kotlin.test.assertEquals import kotlin.test.fail @@ -30,6 +35,30 @@ internal class TypeEnvTest { */ @JvmStatic val locals = TypeEnv( + Env( + PartiQLPlanner.Session( + "queryId", + "userId", + "currentCatalog", + catalogs = mapOf( + "currentCatalog" to object : ConnectorMetadata { + override fun getObject(path: BindingPath): ConnectorHandle.Obj? { + return null + } + + @FnExperimental + override fun getFunction(path: BindingPath): ConnectorHandle.Fn? { + return null + } + + @FnExperimental + override fun getAggregation(path: BindingPath): ConnectorHandle.Agg? { + return null + } + } + ) + ) + ), listOf( relBinding("A", struct("B" to PType.typeBool().toCType())), relBinding("a", struct("b" to PType.typeBool().toCType())), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt index 6678cb084..7781f7f91 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt @@ -365,7 +365,6 @@ internal object SqlBuiltins { Fn_NEG__FLOAT32__FLOAT32, Fn_NEG__FLOAT64__FLOAT64, Fn_NEG__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY, - Fn_NOT__MISSING__BOOL, Fn_NOT__BOOL__BOOL, Fn_OR__BOOL_BOOL__BOOL, Fn_OCTET_LENGTH__STRING__INT32, diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt index 5642e5ae5..935857e92 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt @@ -11,7 +11,6 @@ import org.partiql.value.BoolValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.BOOL -import org.partiql.value.PartiQLValueType.MISSING import org.partiql.value.boolValue import org.partiql.value.check @@ -33,21 +32,3 @@ internal object Fn_NOT__BOOL__BOOL : Fn { return boolValue(value.not()) } } - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_NOT__MISSING__BOOL : Fn { - - override val signature = FnSignature( - name = "not", - returns = BOOL, - parameters = listOf(FnParameter("value", MISSING)), - isNullable = true, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - return boolValue(null) - } -} diff --git a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt index 604d03f19..474c0d4ce 100644 --- a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt +++ b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt @@ -8,9 +8,11 @@ import org.partiql.value.util.isZero @OptIn(PartiQLValueExperimental::class) internal class PartiQLValueComparatorInternal(private val nullsFirst: Boolean) : Comparator { - private val EQUAL = 0 - private val LESS = -1 - private val GREATER = 1 + companion object { + private const val EQUAL = 0 + private const val LESS = -1 + private const val GREATER = 1 + } private fun PartiQLValue.isNullOrMissing(): Boolean = this is NullValue || this is MissingValue || this.isNull private fun PartiQLValue.isLob(): Boolean = this is BlobValue || this is ClobValue