diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 2976d02e45..1e970807f3 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -90,30 +90,16 @@ rex::{ ref: '.catalog.symbol.ref' }, - path::{ - root: rex, - steps: list::[step], - _: [ - step::[ - // The key MUST be an integer expression. Ex: a[0], a[1 + 1] - index::{ key: rex }, - - // Case-sensitive lookup. The key MUST be a string expression. Ex: a["b"], a."b", a[CAST(b AS STRING)] - key::{ key: rex }, - - // Case-insensitive lookup. The key MUST be a literal string. Ex: a.b - symbol::{ key: string }, + path::[ + // The key MUST be an integer expression. Ex: a[0], a[1 + 1] + index::{ root: rex, key: rex }, - // For arrays. Ex: a[*] - // TODO: Do we need this? According to specification: [1,2,3][*] ⇔ SELECT VALUE v FROM [1, 2, 3] AS v - wildcard::{}, + // Case-sensitive lookup. The key MUST be a string expression. Ex: a["b"], a."b", a[CAST(b AS STRING)] + key::{ root: rex, key: rex }, - // For tuples. Ex: a.* - // TODO: Do we need this? According to specification: {'a':1, 'b':2}.* ⇔ SELECT VALUE v FROM UNPIVOT {'a':1, 'b':2} AS v - unpivot::{}, - ], - ], - }, + // Case-insensitive lookup. The key MUST be a literal string. Ex: a.b + symbol::{ root: rex, key: string }, + ], call::[ static::{ diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt index 828de7ff39..80a7eff38a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -64,11 +64,13 @@ internal class TypeEnv( /** * Metadata regarding a resolved variable. + * @property depth The depth/level of the path match. */ internal sealed interface ResolvedVar { public val type: StaticType public val ordinal: Int + public val depth: Int /** * Metadata for a resolved local variable. @@ -83,7 +85,7 @@ internal sealed interface ResolvedVar { override val ordinal: Int, val rootType: StaticType, val replacementSteps: List, - val depth: Int + override val depth: Int ) : ResolvedVar /** @@ -97,7 +99,7 @@ internal sealed interface ResolvedVar { class Global( override val type: StaticType, override val ordinal: Int, - val depth: Int, + override val depth: Int, val position: Int ) : ResolvedVar } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 5509d80fe6..0d23c2a5ca 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -48,11 +48,9 @@ import org.partiql.planner.internal.ir.builder.RexOpCollectionBuilder import org.partiql.planner.internal.ir.builder.RexOpErrBuilder import org.partiql.planner.internal.ir.builder.RexOpGlobalBuilder import org.partiql.planner.internal.ir.builder.RexOpLitBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathStepIndexBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathStepSymbolBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathStepUnpivotBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathStepWildcardBuilder +import org.partiql.planner.internal.ir.builder.RexOpPathIndexBuilder +import org.partiql.planner.internal.ir.builder.RexOpPathKeyBuilder +import org.partiql.planner.internal.ir.builder.RexOpPathSymbolBuilder import org.partiql.planner.internal.ir.builder.RexOpPivotBuilder import org.partiql.planner.internal.ir.builder.RexOpSelectBuilder import org.partiql.planner.internal.ir.builder.RexOpStructBuilder @@ -433,142 +431,77 @@ internal data class Rex( } } - internal data class Path( - @JvmField - internal val root: Rex, - @JvmField - internal val steps: List, - ) : Op() { - internal override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.addAll(steps) - kids.filterNotNull() + internal sealed class Path : Op() { + internal override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Index -> visitor.visitRexOpPathIndex(this, ctx) + is Key -> visitor.visitRexOpPathKey(this, ctx) + is Symbol -> visitor.visitRexOpPathSymbol(this, ctx) } - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPath(this, ctx) - - internal sealed class Step : PlanNode() { - internal override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Index -> visitor.visitRexOpPathStepIndex(this, ctx) - is Symbol -> visitor.visitRexOpPathStepSymbol(this, ctx) - is Wildcard -> visitor.visitRexOpPathStepWildcard(this, ctx) - is Unpivot -> visitor.visitRexOpPathStepUnpivot(this, ctx) - is Key -> visitor.visitRexOpPathStepKey(this, ctx) + internal data class Index( + @JvmField + internal val root: Rex, + @JvmField + internal val key: Rex, + ) : Path() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.add(key) + kids.filterNotNull() } - internal data class Index( - @JvmField - internal val key: Rex, - ) : Step() { - internal override val children: List by lazy { - val kids = mutableListOf() - kids.add(key) - kids.filterNotNull() - } - - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathStepIndex(this, ctx) + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathIndex(this, ctx) - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathStepIndexBuilder = RexOpPathStepIndexBuilder() - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpPathIndexBuilder = RexOpPathIndexBuilder() } + } - /** - * This represents a case-sensitive lookup on a tuple. Ex: a['b'] or a[CAST('a' || 'b' AS STRING)]. - * This would normally contain the dot notation for case-sensitive lookup, however, due to - * limitations -- we cannot consolidate these. See [Symbol] for more information. - * - * The main difference is that this does NOT include `a."b"` - */ - internal data class Key( - @JvmField - internal val key: Rex, - ) : Step() { - internal override val children: List by lazy { - val kids = mutableListOf() - kids.add(key) - kids.filterNotNull() - } - - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathStepKey(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathStepIndexBuilder = RexOpPathStepIndexBuilder() - } + internal data class Key( + @JvmField + internal val root: Rex, + @JvmField + internal val key: Rex, + ) : Path() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.add(key) + kids.filterNotNull() } - /** - * This represents a lookup on a tuple. We differentiate a [Key] and a [Symbol] at this point in the - * pipeline because we NEED to retain some syntactic knowledge for the following reason: we cannot - * use the syntactic index operation on a schema -- as it is not synonymous with a tuple. In other words, - * `.""` is not interchangeable with `['']`. - * - * So, in order to temporarily differentiate the `a."b"` from `a['b']` (see [Key]), we need to maintain - * the syntactic difference here. Note that this would potentially be mitigated by typing during the AST to Plan - * transformation. - * - * That being said, this represents a lookup on a tuple such as `a.b` or `a."b"`. - */ - internal data class Symbol( - @JvmField - internal val identifier: Identifier.Symbol, - ) : Step() { - internal override val children: List by lazy { - val kids = mutableListOf() - kids.add(identifier) - kids.filterNotNull() - } - - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathStepSymbol(this, ctx) + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathKey(this, ctx) - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathStepSymbolBuilder = RexOpPathStepSymbolBuilder() - } + internal companion object { + @JvmStatic + fun builder(): RexOpPathKeyBuilder = RexOpPathKeyBuilder() } + } - internal data class Wildcard( - @JvmField - internal val ` `: Char = ' ', - ) : Step() { - internal override val children: List = emptyList() - - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathStepWildcard(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathStepWildcardBuilder = RexOpPathStepWildcardBuilder() - } + internal data class Symbol( + @JvmField + internal val root: Rex, + @JvmField + internal val key: String, + ) : Path() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.filterNotNull() } - internal data class Unpivot( - @JvmField - internal val ` `: Char = ' ', - ) : Step() { - internal override val children: List = emptyList() - - internal override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathStepUnpivot(this, ctx) + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathSymbol(this, ctx) - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathStepUnpivotBuilder = RexOpPathStepUnpivotBuilder() - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpPathSymbolBuilder = RexOpPathSymbolBuilder() } } - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathBuilder = RexOpPathBuilder() - } } internal sealed class Call : Op() { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Plan.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Plan.kt index c47cd518df..e7516fba9f 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Plan.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Plan.kt @@ -52,21 +52,14 @@ internal fun rexOpVarUnresolved(identifier: Identifier, scope: Rex.Op.Var.Scope) internal fun rexOpGlobal(ref: Catalog.Symbol.Ref): Rex.Op.Global = Rex.Op.Global(ref) -internal fun rexOpPath(root: Rex, steps: List): Rex.Op.Path = Rex.Op.Path( - root, - steps -) - -internal fun rexOpPathStepIndex(key: Rex): Rex.Op.Path.Step.Index = Rex.Op.Path.Step.Index(key) - -internal fun rexOpPathStepKey(key: Rex): Rex.Op.Path.Step.Key = Rex.Op.Path.Step.Key(key) +internal fun rexOpPathIndex(root: Rex, key: Rex): Rex.Op.Path.Index = Rex.Op.Path.Index(root, key) -internal fun rexOpPathStepSymbol(identifier: Identifier.Symbol): Rex.Op.Path.Step.Symbol = - Rex.Op.Path.Step.Symbol(identifier) +internal fun rexOpPathKey(root: Rex, key: Rex): Rex.Op.Path.Key = Rex.Op.Path.Key(root, key) -internal fun rexOpPathStepWildcard(): Rex.Op.Path.Step.Wildcard = Rex.Op.Path.Step.Wildcard() - -internal fun rexOpPathStepUnpivot(): Rex.Op.Path.Step.Unpivot = Rex.Op.Path.Step.Unpivot() +internal fun rexOpPathSymbol(root: Rex, key: String): Rex.Op.Path.Symbol = Rex.Op.Path.Symbol( + root, + key +) internal fun rexOpCallStatic(fn: Fn, args: List): Rex.Op.Call.Static = Rex.Op.Call.Static( fn, diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilder.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilder.kt index 76da173db0..bbfd68e420 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilder.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilder.kt @@ -169,51 +169,32 @@ internal class PlanBuilder { return builder.build() } - internal fun rexOpPath( + internal fun rexOpPathIndex( root: Rex? = null, - steps: MutableList = mutableListOf(), - block: RexOpPathBuilder.() -> Unit = {}, - ): Rex.Op.Path { - val builder = RexOpPathBuilder(root, steps) - builder.block() - return builder.build() - } - - internal fun rexOpPathStepIndex( key: Rex? = null, - block: RexOpPathStepIndexBuilder.() -> Unit = {}, - ): Rex.Op.Path.Step.Index { - val builder = RexOpPathStepIndexBuilder(key) + block: RexOpPathIndexBuilder.() -> Unit = {}, + ): Rex.Op.Path.Index { + val builder = RexOpPathIndexBuilder(root, key) builder.block() return builder.build() } - internal fun rexOpPathStepKey( + internal fun rexOpPathKey( + root: Rex? = null, key: Rex? = null, - block: RexOpPathStepKeyBuilder.() -> Unit = {}, - ): Rex.Op.Path.Step.Key { - val builder = RexOpPathStepKeyBuilder(key) - builder.block() - return builder.build() - } - - internal fun rexOpPathStepSymbol( - identifier: Identifier.Symbol? = null, - block: RexOpPathStepSymbolBuilder.() -> Unit = {}, - ): Rex.Op.Path.Step.Symbol { - val builder = RexOpPathStepSymbolBuilder(identifier) + block: RexOpPathKeyBuilder.() -> Unit = {}, + ): Rex.Op.Path.Key { + val builder = RexOpPathKeyBuilder(root, key) builder.block() return builder.build() } - internal fun rexOpPathStepWildcard(block: RexOpPathStepWildcardBuilder.() -> Unit = {}): Rex.Op.Path.Step.Wildcard { - val builder = RexOpPathStepWildcardBuilder() - builder.block() - return builder.build() - } - - internal fun rexOpPathStepUnpivot(block: RexOpPathStepUnpivotBuilder.() -> Unit = {}): Rex.Op.Path.Step.Unpivot { - val builder = RexOpPathStepUnpivotBuilder() + internal fun rexOpPathSymbol( + root: Rex? = null, + key: String? = null, + block: RexOpPathSymbolBuilder.() -> Unit = {}, + ): Rex.Op.Path.Symbol { + val builder = RexOpPathSymbolBuilder(root, key) builder.block() return builder.build() } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilders.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilders.kt index 534b6553c8..be428c8c75 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilders.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilders.kt @@ -248,57 +248,49 @@ internal class RexOpGlobalBuilder( internal fun build(): Rex.Op.Global = Rex.Op.Global(ref = ref!!) } -internal class RexOpPathBuilder( +internal class RexOpPathIndexBuilder( internal var root: Rex? = null, - internal var steps: MutableList = mutableListOf(), + internal var key: Rex? = null, ) { - internal fun root(root: Rex?): RexOpPathBuilder = this.apply { + internal fun root(root: Rex?): RexOpPathIndexBuilder = this.apply { this.root = root } - internal fun steps(steps: MutableList): RexOpPathBuilder = this.apply { - this.steps = steps + internal fun key(key: Rex?): RexOpPathIndexBuilder = this.apply { + this.key = key } - internal fun build(): Rex.Op.Path = Rex.Op.Path(root = root!!, steps = steps) + internal fun build(): Rex.Op.Path.Index = Rex.Op.Path.Index(root = root!!, key = key!!) } -internal class RexOpPathStepIndexBuilder( +internal class RexOpPathKeyBuilder( + internal var root: Rex? = null, internal var key: Rex? = null, ) { - internal fun key(key: Rex?): RexOpPathStepIndexBuilder = this.apply { - this.key = key + internal fun root(root: Rex?): RexOpPathKeyBuilder = this.apply { + this.root = root } - internal fun build(): Rex.Op.Path.Step.Index = Rex.Op.Path.Step.Index(key = key!!) -} - -internal class RexOpPathStepKeyBuilder( - internal var key: Rex? = null, -) { - internal fun key(key: Rex?): RexOpPathStepKeyBuilder = this.apply { + internal fun key(key: Rex?): RexOpPathKeyBuilder = this.apply { this.key = key } - internal fun build(): Rex.Op.Path.Step.Key = Rex.Op.Path.Step.Key(key = key!!) + internal fun build(): Rex.Op.Path.Key = Rex.Op.Path.Key(root = root!!, key = key!!) } -internal class RexOpPathStepSymbolBuilder( - internal var identifier: Identifier.Symbol? = null, +internal class RexOpPathSymbolBuilder( + internal var root: Rex? = null, + internal var key: String? = null, ) { - internal fun identifier(identifier: Identifier.Symbol?): RexOpPathStepSymbolBuilder = this.apply { - this.identifier = identifier + internal fun root(root: Rex?): RexOpPathSymbolBuilder = this.apply { + this.root = root } - internal fun build(): Rex.Op.Path.Step.Symbol = Rex.Op.Path.Step.Symbol(identifier = identifier!!) -} - -internal class RexOpPathStepWildcardBuilder() { - internal fun build(): Rex.Op.Path.Step.Wildcard = Rex.Op.Path.Step.Wildcard() -} + internal fun key(key: String?): RexOpPathSymbolBuilder = this.apply { + this.key = key + } -internal class RexOpPathStepUnpivotBuilder() { - internal fun build(): Rex.Op.Path.Step.Unpivot = Rex.Op.Path.Step.Unpivot() + internal fun build(): Rex.Op.Path.Symbol = Rex.Op.Path.Symbol(root = root!!, key = key!!) } internal class RexOpCallStaticBuilder( diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/util/PlanRewriter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/util/PlanRewriter.kt index d8e593b4a5..a9e807160a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/util/PlanRewriter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/util/PlanRewriter.kt @@ -212,38 +212,36 @@ internal abstract class PlanRewriter : PlanBaseVisitor() { } } - override fun visitRexOpPath(node: Rex.Op.Path, ctx: C): PlanNode { + override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: C): PlanNode { val root = visitRex(node.root, ctx) as Rex - val steps = _visitList(node.steps, ctx, ::visitRexOpPathStep) - return if (root !== node.root || steps !== node.steps) { - Rex.Op.Path(root, steps) + val key = visitRex(node.key, ctx) as Rex + return if (root !== node.root || key !== node.key) { + Rex.Op.Path.Index(root, key) } else { node } } - override fun visitRexOpPathStepIndex(node: Rex.Op.Path.Step.Index, ctx: C): PlanNode { + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: C): PlanNode { + val root = visitRex(node.root, ctx) as Rex val key = visitRex(node.key, ctx) as Rex - return if (key !== node.key) { - Rex.Op.Path.Step.Index(key) + return if (root !== node.root || key !== node.key) { + Rex.Op.Path.Key(root, key) } else { node } } - override fun visitRexOpPathStepSymbol(node: Rex.Op.Path.Step.Symbol, ctx: C): PlanNode { - val identifier = visitIdentifierSymbol(node.identifier, ctx) as Identifier.Symbol - return if (identifier !== node.identifier) { - Rex.Op.Path.Step.Symbol(identifier) + override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: C): PlanNode { + val root = visitRex(node.root, ctx) as Rex + val key = node.key + return if (root !== node.root || key !== node.key) { + Rex.Op.Path.Symbol(root, key) } else { node } } - override fun visitRexOpPathStepWildcard(node: Rex.Op.Path.Step.Wildcard, ctx: C): PlanNode = node - - override fun visitRexOpPathStepUnpivot(node: Rex.Op.Path.Step.Unpivot, ctx: C): PlanNode = node - override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: C): PlanNode { val fn = visitFn(node.fn, ctx) as Fn val args = _visitList(node.args, ctx, ::visitRex) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanBaseVisitor.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanBaseVisitor.kt index 4db0bab414..901d17895a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanBaseVisitor.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanBaseVisitor.kt @@ -98,30 +98,23 @@ internal abstract class PlanBaseVisitor : PlanVisitor { override fun visitRexOpGlobal(node: Rex.Op.Global, ctx: C): R = defaultVisit(node, ctx) - override fun visitRexOpPath(node: Rex.Op.Path, ctx: C): R = defaultVisit(node, ctx) - - override fun visitRexOpPathStep(node: Rex.Op.Path.Step, ctx: C): R = when (node) { - is Rex.Op.Path.Step.Index -> visitRexOpPathStepIndex(node, ctx) - is Rex.Op.Path.Step.Key -> visitRexOpPathStepKey(node, ctx) - is Rex.Op.Path.Step.Symbol -> visitRexOpPathStepSymbol(node, ctx) - is Rex.Op.Path.Step.Wildcard -> visitRexOpPathStepWildcard(node, ctx) - is Rex.Op.Path.Step.Unpivot -> visitRexOpPathStepUnpivot(node, ctx) + override fun visitRexOpPath(node: Rex.Op.Path, ctx: C): R = when (node) { + is Rex.Op.Path.Index -> visitRexOpPathIndex(node, ctx) + is Rex.Op.Path.Key -> visitRexOpPathKey(node, ctx) + is Rex.Op.Path.Symbol -> visitRexOpPathSymbol(node, ctx) } - override fun visitRexOpPathStepIndex(node: Rex.Op.Path.Step.Index, ctx: C): R = - defaultVisit(node, ctx) - - override fun visitRexOpPathStepKey(node: Rex.Op.Path.Step.Key, ctx: C): R = - defaultVisit(node, ctx) - - override fun visitRexOpPathStepSymbol(node: Rex.Op.Path.Step.Symbol, ctx: C): R = - defaultVisit(node, ctx) + override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: C): R = defaultVisit( + node, + ctx + ) - override fun visitRexOpPathStepWildcard(node: Rex.Op.Path.Step.Wildcard, ctx: C): R = - defaultVisit(node, ctx) + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: C): R = defaultVisit(node, ctx) - override fun visitRexOpPathStepUnpivot(node: Rex.Op.Path.Step.Unpivot, ctx: C): R = - defaultVisit(node, ctx) + override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: C): R = defaultVisit( + node, + ctx + ) override fun visitRexOpCall(node: Rex.Op.Call, ctx: C): R = when (node) { is Rex.Op.Call.Static -> visitRexOpCallStatic(node, ctx) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanVisitor.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanVisitor.kt index e9366280e9..c2fd04e7b0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanVisitor.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/visitor/PlanVisitor.kt @@ -62,17 +62,11 @@ internal interface PlanVisitor { fun visitRexOpPath(node: Rex.Op.Path, ctx: C): R - fun visitRexOpPathStep(node: Rex.Op.Path.Step, ctx: C): R + fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: C): R - fun visitRexOpPathStepIndex(node: Rex.Op.Path.Step.Index, ctx: C): R + fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: C): R - fun visitRexOpPathStepKey(node: Rex.Op.Path.Step.Key, ctx: C): R - - fun visitRexOpPathStepSymbol(node: Rex.Op.Path.Step.Symbol, ctx: C): R - - fun visitRexOpPathStepWildcard(node: Rex.Op.Path.Step.Wildcard, ctx: C): R - - fun visitRexOpPathStepUnpivot(node: Rex.Op.Path.Step.Unpivot, ctx: C): R + fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: C): R fun visitRexOpCall(node: Rex.Op.Call, ctx: C): R diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt index 4418b2acf1..7bf954c873 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt @@ -3,10 +3,6 @@ package org.partiql.planner.internal.transforms import org.partiql.errors.ProblemCallback import org.partiql.plan.PlanNode import org.partiql.plan.partiQLPlan -import org.partiql.plan.rex -import org.partiql.plan.rexOpLit -import org.partiql.plan.rexOpPathStepKey -import org.partiql.plan.rexOpPathStepSymbol import org.partiql.planner.internal.ir.Agg import org.partiql.planner.internal.ir.Catalog import org.partiql.planner.internal.ir.Fn @@ -16,9 +12,7 @@ import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.Statement import org.partiql.planner.internal.ir.visitor.PlanBaseVisitor -import org.partiql.types.StaticType import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.stringValue /** * This is an internal utility to translate from the internal unresolved plan used for typing to the public plan IR. @@ -119,35 +113,22 @@ internal object PlanTransform : PlanBaseVisitor() { ref = visitCatalogSymbolRef(node.ref, ctx) ) - override fun visitRexOpPath(node: Rex.Op.Path, ctx: ProblemCallback): org.partiql.plan.Rex.Op.Path { + override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: ProblemCallback): PlanNode { val root = visitRex(node.root, ctx) - val steps = node.steps.map { visitRexOpPathStep(it, ctx) } - return org.partiql.plan.Rex.Op.Path(root, steps) + val key = visitRex(node.root, ctx) + return org.partiql.plan.Rex.Op.Path.Index(root, key) } - override fun visitRexOpPathStep(node: Rex.Op.Path.Step, ctx: ProblemCallback) = - super.visit(node, ctx) as org.partiql.plan.Rex.Op.Path.Step - - override fun visitRexOpPathStepIndex(node: Rex.Op.Path.Step.Index, ctx: ProblemCallback) = - org.partiql.plan.Rex.Op.Path.Step.Index( - key = visitRex(node.key, ctx), - ) - - @OptIn(PartiQLValueExperimental::class) - override fun visitRexOpPathStepSymbol(node: Rex.Op.Path.Step.Symbol, ctx: ProblemCallback) = when (node.identifier.caseSensitivity) { - Identifier.CaseSensitivity.SENSITIVE -> rexOpPathStepKey(rex(StaticType.STRING, rexOpLit(stringValue(node.identifier.symbol)))) - Identifier.CaseSensitivity.INSENSITIVE -> rexOpPathStepSymbol(node.identifier.symbol) + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: ProblemCallback): PlanNode { + val root = visitRex(node.root, ctx) + val key = visitRex(node.root, ctx) + return org.partiql.plan.Rex.Op.Path.Key(root, key) } - override fun visitRexOpPathStepKey(node: Rex.Op.Path.Step.Key, ctx: ProblemCallback): PlanNode = rexOpPathStepKey( - key = visitRex(node.key, ctx) - ) - - override fun visitRexOpPathStepWildcard(node: Rex.Op.Path.Step.Wildcard, ctx: ProblemCallback) = - org.partiql.plan.Rex.Op.Path.Step.Wildcard() - - override fun visitRexOpPathStepUnpivot(node: Rex.Op.Path.Step.Unpivot, ctx: ProblemCallback) = - org.partiql.plan.Rex.Op.Path.Step.Unpivot() + override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: ProblemCallback): PlanNode { + val root = visitRex(node.root, ctx) + return org.partiql.plan.Rex.Op.Path.Symbol(root, node.key) + } override fun visitRexOpCall(node: Rex.Op.Call, ctx: ProblemCallback) = super.visitRexOpCall(node, ctx) as org.partiql.plan.Rex.Op diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index c1264b1fc4..498dea8a2c 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -26,17 +26,15 @@ import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.builder.plan import org.partiql.planner.internal.ir.fnUnresolved +import org.partiql.planner.internal.ir.identifierQualified import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.rex import org.partiql.planner.internal.ir.rexOpCallStatic import org.partiql.planner.internal.ir.rexOpCollection import org.partiql.planner.internal.ir.rexOpLit -import org.partiql.planner.internal.ir.rexOpPath -import org.partiql.planner.internal.ir.rexOpPathStepIndex -import org.partiql.planner.internal.ir.rexOpPathStepKey -import org.partiql.planner.internal.ir.rexOpPathStepSymbol -import org.partiql.planner.internal.ir.rexOpPathStepUnpivot -import org.partiql.planner.internal.ir.rexOpPathStepWildcard +import org.partiql.planner.internal.ir.rexOpPathIndex +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.ir.rexOpStruct import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpSubquery @@ -53,6 +51,7 @@ import org.partiql.value.int32Value import org.partiql.value.int64Value import org.partiql.value.io.PartiQLValueIonReaderBuilder import org.partiql.value.nullValue +import org.partiql.value.stringValue /** * Converts an AST expression node to a Plan Rex node; ignoring any typing. @@ -158,39 +157,87 @@ internal object RexConverter { } } + private fun mergeIdentifiers(root: Identifier, steps: List): Identifier { + if (steps.isEmpty()) { + return root + } + val (newRoot, firstSteps) = when (root) { + is Identifier.Symbol -> root to emptyList() + is Identifier.Qualified -> root.root to root.steps + } + val followingSteps = steps.flatMap { step -> + when (step) { + is Identifier.Symbol -> listOf(step) + is Identifier.Qualified -> listOf(step.root) + step.steps + } + } + return identifierQualified(newRoot, firstSteps + followingSteps) + } + override fun visitExprPath(node: Expr.Path, context: Env): Rex { - val type = (StaticType.ANY) // Args val root = visitExprCoerce(node.root, context) - val steps = node.steps.map { - when (it) { - is Expr.Path.Step.Index -> { - val key = visitExprCoerce(it.key, context) - when (val astKey = it.key) { - is Expr.Lit -> when (astKey.value) { - is StringValue -> rexOpPathStepKey(key) - else -> rexOpPathStepIndex(key) - } - is Expr.Cast -> when (astKey.asType is Type.String) { - true -> rexOpPathStepKey(key) - false -> rexOpPathStepIndex(key) + + // Attempt to create qualified identifier + val (newRoot, newSteps) = when (val op = root.op) { + is Rex.Op.Var.Unresolved -> { + val identifierSteps = mutableListOf() + run { + node.steps.forEach { step -> + if (step !is Expr.Path.Step.Symbol) { + return@run } - else -> rexOpPathStepIndex(key) + identifierSteps.add(AstToPlan.convert(step.symbol)) } } - is Expr.Path.Step.Symbol -> { - val identifier = AstToPlan.convert(it.symbol) - rexOpPathStepSymbol(identifier) + when (identifierSteps.size) { + 0 -> root to node.steps + else -> { + val newRoot = rex(StaticType.ANY, rexOpVarUnresolved(mergeIdentifiers(op.identifier, identifierSteps), op.scope)) + val newSteps = node.steps.subList(identifierSteps.size, node.steps.size) + newRoot to newSteps + } } - is Expr.Path.Step.Unpivot -> rexOpPathStepUnpivot() - is Expr.Path.Step.Wildcard -> rexOpPathStepWildcard() + } + else -> root to node.steps + } + + // Return wrapped path + return when (newSteps.isEmpty()) { + true -> newRoot + false -> newSteps.fold(newRoot) { current, step -> + val path = when (step) { + is Expr.Path.Step.Index -> { + val key = visitExprCoerce(step.key, context) + when (val astKey = step.key) { + is Expr.Lit -> when (astKey.value) { + is StringValue -> rexOpPathKey(current, key) + else -> rexOpPathIndex(current, key) + } + is Expr.Cast -> when (astKey.asType is Type.String) { + true -> rexOpPathKey(current, key) + false -> rexOpPathIndex(current, key) + } + else -> rexOpPathIndex(current, key) + } + } + is Expr.Path.Step.Symbol -> { + val identifier = AstToPlan.convert(step.symbol) + when (identifier.caseSensitivity) { + Identifier.CaseSensitivity.SENSITIVE -> rexOpPathKey(current, rexString(identifier.symbol)) + Identifier.CaseSensitivity.INSENSITIVE -> rexOpPathSymbol(current, identifier.symbol) + } + } + is Expr.Path.Step.Unpivot -> error("Unpivot path not supported yet") + is Expr.Path.Step.Wildcard -> error("Wildcard path not supported yet") + } + rex(StaticType.ANY, path) } } - // Rex - val op = rexOpPath(root, steps) - return rex(type, op) } + private fun rexString(str: String) = rex(StaticType.STRING, rexOpLit(stringValue(str))) + override fun visitExprCall(node: Expr.Call, context: Env): Rex { val type = (StaticType.ANY) // Fn 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 6f93e88d2e..f9cfd4a9f4 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 @@ -62,8 +62,9 @@ import org.partiql.planner.internal.ir.rexOpCollection import org.partiql.planner.internal.ir.rexOpErr import org.partiql.planner.internal.ir.rexOpGlobal import org.partiql.planner.internal.ir.rexOpLit -import org.partiql.planner.internal.ir.rexOpPath -import org.partiql.planner.internal.ir.rexOpPathStepSymbol +import org.partiql.planner.internal.ir.rexOpPathIndex +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.ir.rexOpPivot import org.partiql.planner.internal.ir.rexOpSelect import org.partiql.planner.internal.ir.rexOpStruct @@ -91,6 +92,7 @@ import org.partiql.types.StaticType.Companion.BOOL import org.partiql.types.StaticType.Companion.MISSING import org.partiql.types.StaticType.Companion.NULL import org.partiql.types.StaticType.Companion.STRING +import org.partiql.types.StaticType.Companion.unionOf import org.partiql.types.StringType import org.partiql.types.StructType import org.partiql.types.TupleConstraint @@ -99,6 +101,8 @@ import org.partiql.value.BoolValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.TextValue import org.partiql.value.boolValue +import org.partiql.value.missingValue +import org.partiql.value.stringValue /** * Rewrites an untyped algebraic translation of the query to be both typed and have resolved variables. @@ -449,9 +453,21 @@ internal class PlanTyper( val type = resolvedVar.type val op = when (resolvedVar) { is ResolvedVar.Global -> rexOpGlobal(catalogSymbolRef(resolvedVar.ordinal, resolvedVar.position)) - is ResolvedVar.Local -> resolvedLocalPath(resolvedVar) + is ResolvedVar.Local -> rexOpVarResolved(resolvedVar.ordinal) // resolvedLocalPath(resolvedVar) + } + val variable = rex(type, op) + return when (resolvedVar.depth) { + path.steps.size -> variable + else -> { + val foldedPath = path.steps.subList(resolvedVar.depth, path.steps.size).fold(variable) { current, step -> + when (step.bindingCase) { + BindingCase.SENSITIVE -> rex(ANY, rexOpPathKey(current, rex(STRING, rexOpLit(stringValue(step.name))))) + BindingCase.INSENSITIVE -> rex(ANY, rexOpPathSymbol(current, step.name)) + } + } + visitRex(foldedPath, ctx) + } } - return rex(type, op) } override fun visitRexOpGlobal(node: Rex.Op.Global, ctx: StaticType?): Rex { @@ -460,88 +476,106 @@ internal class PlanTyper( return rex(type, node) } - /** - * Match path as far as possible (rewriting the steps), then infer based on resolved root and rewritten steps. - */ - override fun visitRexOpPath(node: Rex.Op.Path, ctx: StaticType?): Rex { - val visitedSteps = node.steps.map { visitRexOpPathStep(it, null) } - // 1. Resolve path prefix - val (root, steps) = when (val rootOp = node.root.op) { - is Rex.Op.Var.Unresolved -> { - // Rewrite the root - val path = rexPathToBindingPath(rootOp, visitedSteps) - val resolvedVar = env.resolve(path, locals, rootOp.scope) - if (resolvedVar == null) { - handleUndefinedVariable(path.steps.last()) - return rex(ANY, node) - } - val type = resolvedVar.type - val (op, steps) = when (resolvedVar) { - // Root (and some steps) was a local. Replace the matched nodes with disambiguated steps - // and return the remaining steps to continue typing. - is ResolvedVar.Local -> { - val amountRemaining = (visitedSteps.size + 1) - resolvedVar.depth - val remainingSteps = visitedSteps.takeLast(amountRemaining) - resolvedLocalPath(resolvedVar) to remainingSteps - } - is ResolvedVar.Global -> { - // Root (and some steps) was a global; replace root and re-calculate remaining steps. - val remainingFirstIndex = resolvedVar.depth - 1 - val remaining = when (remainingFirstIndex > visitedSteps.lastIndex) { - true -> emptyList() - false -> visitedSteps.subList(remainingFirstIndex, visitedSteps.size) - } - rexOpGlobal(catalogSymbolRef(resolvedVar.ordinal, resolvedVar.position)) to remaining - } - } - // rewrite root - rex(type, op) to steps - } - else -> visitRex(node.root, node.root.type) to visitedSteps + override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: StaticType?): Rex { + val root = visitRex(node.root, node.root.type) + val key = visitRex(node.key, node.key.type) + if (key.type !is IntType) { + handleAlwaysMissing() + return rex(MISSING, rexOpErr("Collections must be indexed with integers, found ${key.type}")) } + val elementTypes = root.type.allTypes.map { type -> + val rootType = type as? CollectionType ?: return@map MISSING + if (rootType !is ListType && rootType !is SexpType) { + return@map MISSING + } + rootType.elementType + }.toSet() + val finalType = unionOf(elementTypes).flatten() + return rex(finalType.swallowAny(), rexOpPathIndex(root, key)) + } - // short-circuit if whole path was matched - if (steps.isEmpty()) { - return root + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Rex { + val root = visitRex(node.root, node.root.type) + val key = visitRex(node.key, node.key.type) + + // Check Key Type + val toAddTypes = key.type.allTypes.mapNotNull { keyType -> + when (keyType) { + is StringType -> null + is NullType -> NULL + else -> MISSING + } + } + if (toAddTypes.size == key.type.allTypes.size && toAddTypes.all { it is MissingType }) { + handleAlwaysMissing() + return rex(MISSING, rexOpErr("Expected string but found: ${key.type}")) } - // 2. TODO rewrite and type the steps containing expressions - // val typedSteps = steps.map { - // if (it is Rex.Op.Path.Step.Index) { - // val key = visitRex(it.key, null) - // rexOpPathStepIndex(key) - // } else it - // } + val pathTypes = root.type.allTypes.map { type -> + val struct = type as? StructType ?: return@map MISSING - // 3. Walk the steps, determine the path type, and replace each step with the disambiguated equivalent - // (AKA made sensitive, if possible) - var type = root.type - val newSteps = steps.map { step -> - val (stepType, replacementStep) = inferPathStep(type, step) - type = stepType - replacementStep + if (key.op is Rex.Op.Lit) { + val lit = key.op.value + if (lit is TextValue<*> && !lit.isNull) { + val id = identifierSymbol(lit.string!!, Identifier.CaseSensitivity.SENSITIVE) + inferStructLookup(struct, id).first + } else { + error("Expected text literal, but got $lit") + } + } else { + // cannot infer type of non-literal path step because we don't know its value + // we might improve upon this with some constant folding prior to typing + ANY + } + }.toSet() + val finalType = unionOf(pathTypes + toAddTypes).flatten() + return rex(finalType.swallowAny(), rexOpPathKey(root, key)) + } + + override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: StaticType?): Rex { + val root = visitRex(node.root, node.root.type) + + val paths = root.type.allTypes.map { type -> + val struct = type as? StructType ?: return@map rex(MISSING, rexOpLit(missingValue())) + val (pathType, replacementId) = inferStructLookup(struct, identifierSymbol(node.key, Identifier.CaseSensitivity.INSENSITIVE)) + when (replacementId.caseSensitivity) { + Identifier.CaseSensitivity.INSENSITIVE -> rex(pathType, rexOpPathSymbol(root, replacementId.symbol)) + Identifier.CaseSensitivity.SENSITIVE -> rex(pathType, rexOpPathKey(root, rexString(replacementId.symbol))) + } } + val type = unionOf(paths.map { it.type }.toSet()).flatten() - // 4. Invalid path reference; always MISSING - if (type == MISSING) { - handleAlwaysMissing() - return rexErr("Unknown identifier $node") + // replace step only if all are disambiguated + val firstPathOp = paths.first().op + val replacementOp = when (paths.map { it.op }.all { it == firstPathOp }) { + true -> firstPathOp + false -> rexOpPathSymbol(root, node.key) } + return rex(type.swallowAny(), replacementOp) + } - // 5. Non-missing, root is resolved - return rex(type, rexOpPath(root, newSteps)) + /** + * "Swallows" ANY. If ANY is one of the types in the UNION type, we return ANY. If not, we flatten and return + * the [type]. + */ + private fun StaticType.swallowAny(): StaticType { + val flattened = this.flatten() + return when (flattened.allTypes.any { it is AnyType }) { + true -> ANY + false -> flattened + } } - // Default returns the original node, in some case we need the resolved node. - // i.e., the path step is a call node - override fun visitRexOpPathStep(node: Rex.Op.Path.Step, ctx: StaticType?): Rex.Op.Path.Step = - when (node) { - is Rex.Op.Path.Step.Index -> Rex.Op.Path.Step.Index(visitRex(node.key, ctx)) - is Rex.Op.Path.Step.Key -> Rex.Op.Path.Step.Key(visitRex(node.key, ctx)) - is Rex.Op.Path.Step.Symbol -> Rex.Op.Path.Step.Symbol(node.identifier) - is Rex.Op.Path.Step.Unpivot -> Rex.Op.Path.Step.Unpivot() - is Rex.Op.Path.Step.Wildcard -> Rex.Op.Path.Step.Wildcard() + private fun rexString(str: String) = rex(STRING, rexOpLit(stringValue(str))) + + override fun visitRexOpPath(node: Rex.Op.Path, ctx: StaticType?): Rex { + val path = super.visitRexOpPath(node, ctx) as Rex + if (path.type == MISSING) { + handleAlwaysMissing() + return rexErr("Path always returns missing $node") } + return path + } /** * Resolve and type scalar function calls. @@ -1076,88 +1110,6 @@ internal class PlanTyper( // Helpers - /** - * @return a [Pair] where the [Pair.first] represents the type of the [step] and the [Pair.second] represents - * the disambiguated [step]. - */ - private fun inferPathStep(type: StaticType, step: Rex.Op.Path.Step): Pair = - when (type) { - is AnyType -> ANY to step - is StructType -> inferPathStep(type, step) - is ListType, is SexpType -> inferPathStep(type as CollectionType, step) to step - is AnyOfType -> { - when (type.types.size) { - 0 -> throw IllegalStateException("Cannot path on an empty StaticType union") - else -> { - val prevTypes = type.allTypes - if (prevTypes.any { it is AnyType }) { - ANY to step - } else { - val results = prevTypes.map { inferPathStep(it, step) } - val types = results.map { it.first } - val firstResultStep = results.first().second - // replace step only if all are disambiguated - val replacementStep = when (results.map { it.second }.all { it == firstResultStep }) { - true -> firstResultStep - false -> step - } - AnyOfType(types.toSet()).flatten() to replacementStep - } - } - } - } - else -> MISSING to step - } - - /** - * @return a [Pair] where the [Pair.first] represents the type of the [step] and the [Pair.second] represents - * the disambiguated [step]. - */ - private fun inferPathStep(struct: StructType, step: Rex.Op.Path.Step): Pair = when (step) { - // { 'a': 1 }[0] should always return missing since tuples cannot be navigated via integer indexes - is Rex.Op.Path.Step.Index -> { - handleAlwaysMissing() - MISSING to step - } - is Rex.Op.Path.Step.Symbol -> { - val (type, replacementId) = inferStructLookup(struct, step.identifier) - type to replacementId.toPathStep() - } - is Rex.Op.Path.Step.Key -> { - if (step.key.type !is StringType) { - error("Expected string but found: ${step.key.type}") - } - if (step.key.op is Rex.Op.Lit) { - val lit = step.key.op.value - if (lit is TextValue<*> && !lit.isNull) { - val id = identifierSymbol(lit.string!!, Identifier.CaseSensitivity.SENSITIVE) - val (type, replacementId) = inferStructLookup(struct, id) - type to replacementId.toPathStep() - } else { - error("Expected text literal, but got $lit") - } - } else { - // cannot infer type of non-literal path step because we don't know its value - // we might improve upon this with some constant folding prior to typing - ANY to step - } - } - is Rex.Op.Path.Step.Unpivot -> error("Unpivot not supported") - is Rex.Op.Path.Step.Wildcard -> error("Wildcard not supported") - } - - private fun Identifier.Symbol.toPathStep() = rexOpPathStepSymbol(this) - - private fun inferPathStep(collection: CollectionType, step: Rex.Op.Path.Step): StaticType { - if (step !is Rex.Op.Path.Step.Index) { - error("Path step on a collection must be an expression") - } - if (step.key.type !is IntType) { - error("Collections must be indexed with integers, found ${step.key.type}") - } - return collection.elementType - } - /** * Logic is as follows: * 1. If [struct] is closed and ordered: @@ -1297,7 +1249,7 @@ internal class PlanTyper( is Identifier.Symbol -> BindingPath(listOf(this.toBindingName())) } - private fun Identifier.Qualified.toBindingPath() = BindingPath(steps = steps.map { it.toBindingName() }) + private fun Identifier.Qualified.toBindingPath() = BindingPath(steps = listOf(this.root.toBindingName()) + steps.map { it.toBindingName() }) private fun Identifier.Symbol.toBindingName() = BindingName( name = symbol, @@ -1314,29 +1266,6 @@ internal class PlanTyper( */ private fun List.toUnionType(): StaticType = AnyOfType(map { it.type }.toSet()).flatten() - /** - * Helper function which returns the literal string/symbol steps of a path expression as a [BindingPath]. - * - * TODO this does not handle constant expressions in `[]`, only literals - */ - @OptIn(PartiQLValueExperimental::class) - private fun rexPathToBindingPath(rootOp: Rex.Op.Var.Unresolved, steps: List): BindingPath { - if (rootOp.identifier !is Identifier.Symbol) { - throw IllegalArgumentException("Expected identifier symbol") - } - val bindingRoot = rootOp.identifier.toBindingName() - val bindingSteps = mutableListOf(bindingRoot) - for (step in steps) { - when (step) { - is Rex.Op.Path.Step.Index -> break - is Rex.Op.Path.Step.Symbol -> bindingSteps.add(step.identifier.toBindingName()) - is Rex.Op.Path.Step.Key -> break - else -> break // short-circuit - } - } - return BindingPath(bindingSteps) - } - private fun getElementTypeForFromSource(fromSourceType: StaticType): StaticType = when (fromSourceType) { is BagType -> fromSourceType.elementType is ListType -> fromSourceType.elementType @@ -1374,25 +1303,6 @@ internal class PlanTyper( } } - /** - * Constructs a Rex.Op.Path from a resolved local - */ - private fun resolvedLocalPath(local: ResolvedVar.Local): Rex.Op { - val root = rex(local.rootType, rexOpVarResolved(local.ordinal)) - val steps = local.replacementSteps.map { - val case = when (it.bindingCase) { - BindingCase.SENSITIVE -> Identifier.CaseSensitivity.SENSITIVE - BindingCase.INSENSITIVE -> Identifier.CaseSensitivity.INSENSITIVE - } - val symbol = identifierSymbol(it.name, case) - rexOpPathStepSymbol(symbol) - } - return when (steps.isEmpty()) { - true -> root.op - false -> rexOpPath(root, steps) - } - } - // ERRORS private fun handleUndefinedVariable(name: BindingName) { diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt index 913bc7759b..a2cc46dced 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt @@ -11,8 +11,8 @@ import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.rex import org.partiql.planner.internal.ir.rexOpGlobal import org.partiql.planner.internal.ir.rexOpLit -import org.partiql.planner.internal.ir.rexOpPath -import org.partiql.planner.internal.ir.rexOpPathStepSymbol +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.ir.rexOpStruct import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpVarUnresolved @@ -20,6 +20,12 @@ import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.util.ProblemCollector import org.partiql.plugins.local.LocalConnector import org.partiql.types.StaticType +import org.partiql.types.StaticType.Companion.ANY +import org.partiql.types.StaticType.Companion.DECIMAL +import org.partiql.types.StaticType.Companion.FLOAT +import org.partiql.types.StaticType.Companion.INT2 +import org.partiql.types.StaticType.Companion.INT4 +import org.partiql.types.StaticType.Companion.STRING import org.partiql.types.StructType import org.partiql.types.TupleConstraint import org.partiql.value.PartiQLValueExperimental @@ -35,6 +41,76 @@ class PlanTyperTest { private val root = this::class.java.getResource("/catalogs/default/pql")!!.toURI().toPath() + @OptIn(PartiQLValueExperimental::class) + private val LITERAL_STRUCT_1 = rex( + ANY, + rexOpStruct( + fields = listOf( + rexOpStructField( + k = rex(STRING, rexOpLit(stringValue("FiRsT_KeY"))), + v = rex( + ANY, + rexOpStruct( + fields = listOf( + rexOpStructField( + k = rex(STRING, rexOpLit(stringValue("sEcoNd_KEY"))), + v = rex(INT4, rexOpLit(int32Value(5))) + ) + ) + ) + ) + ) + ) + ) + ) + + private val LITERAL_STRUCT_1_FIRST_KEY_TYPE = StructType( + fields = mapOf( + "sEcoNd_KEY" to INT4 + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Open(false) + ) + ) + + @OptIn(PartiQLValueExperimental::class) + private val LITERAL_STRUCT_1_TYPED: Rex + get() { + val topLevelStruct = StructType( + fields = mapOf( + "FiRsT_KeY" to LITERAL_STRUCT_1_FIRST_KEY_TYPE + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Open(false) + ) + ) + return rex( + type = topLevelStruct, + rexOpStruct( + fields = listOf( + rexOpStructField( + k = rex(STRING, rexOpLit(stringValue("FiRsT_KeY"))), + v = rex( + type = LITERAL_STRUCT_1_FIRST_KEY_TYPE, + rexOpStruct( + fields = listOf( + rexOpStructField( + k = rex(STRING, rexOpLit(stringValue("sEcoNd_KEY"))), + v = rex(INT4, rexOpLit(int32Value(5))) + ) + ) + ) + ) + ) + ) + ) + ) + } + private val ORDERED_DUPLICATES_STRUCT = StructType( fields = listOf( StructType.Field("definition", StaticType.STRING), @@ -131,94 +207,12 @@ class PlanTyperTest { * It also checks that we type it all correctly as well. */ @Test - @OptIn(PartiQLValueExperimental::class) fun testReplacingStructs() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpStruct( - fields = listOf( - rexOpStructField( - k = rex(StaticType.STRING, rexOpLit(stringValue("FiRsT_KeY"))), - v = rex( - StaticType.ANY, - rexOpStruct( - fields = listOf( - rexOpStructField( - k = rex(StaticType.STRING, rexOpLit(stringValue("sEcoNd_KEY"))), - v = rex(StaticType.INT4, rexOpLit(int32Value(5))) - ) - ) - ) - ) - ) - ) - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("first_key", Identifier.CaseSensitivity.INSENSITIVE)), - rexOpPathStepSymbol(identifierSymbol("sEcoNd_KEY", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val firstKeyStruct = StructType( - fields = mapOf( - "sEcoNd_KEY" to StaticType.INT4 - ), - contentClosed = true, - constraints = setOf( - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Open(false) - ) - ) - val topLevelStruct = StructType( - fields = mapOf( - "FiRsT_KeY" to firstKeyStruct - ), - contentClosed = true, - constraints = setOf( - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Open(false) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.INT4, - op = rexOpPath( - root = rex( - type = topLevelStruct, - rexOpStruct( - fields = listOf( - rexOpStructField( - k = rex(StaticType.STRING, rexOpLit(stringValue("FiRsT_KeY"))), - v = rex( - type = firstKeyStruct, - rexOpStruct( - fields = listOf( - rexOpStructField( - k = rex(StaticType.STRING, rexOpLit(stringValue("sEcoNd_KEY"))), - v = rex(StaticType.INT4, rexOpLit(int32Value(5))) - ) - ) - ) - ) - ) - ) - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("FiRsT_KeY", Identifier.CaseSensitivity.SENSITIVE)), - rexOpPathStepSymbol(identifierSymbol("sEcoNd_KEY", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(LITERAL_STRUCT_1.pathSymbol("first_key").pathKey("sEcoNd_KEY")) + val expected = statementQuery(LITERAL_STRUCT_1_TYPED.pathKey("FiRsT_KeY", LITERAL_STRUCT_1_FIRST_KEY_TYPE).pathKey("sEcoNd_KEY", INT4)) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -228,36 +222,10 @@ class PlanTyperTest { val wrapper = getTyper() val typer = wrapper.typer val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_ordered_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.INSENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.STRING, - op = rexOpPath( - root = rex( - ORDERED_DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) + unresolvedSensitiveVar("closed_ordered_duplicates_struct").pathSymbol("DEFINITION") ) + val expected = statementQuery(global(ORDERED_DUPLICATES_STRUCT).pathKey("definition", STRING)) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -266,37 +234,9 @@ class PlanTyperTest { fun testOrderedDuplicatesWithSensitivity() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_ordered_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.DECIMAL, - op = rexOpPath( - root = rex( - ORDERED_DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_ordered_duplicates_struct").pathKey("DEFINITION")) + val expected = statementQuery(global(ORDERED_DUPLICATES_STRUCT).pathKey("DEFINITION", DECIMAL)) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -305,37 +245,9 @@ class PlanTyperTest { fun testUnorderedDuplicates() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.INSENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.unionOf(StaticType.STRING, StaticType.FLOAT, StaticType.DECIMAL), - op = rexOpPath( - root = rex( - DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.INSENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_duplicates_struct").pathSymbol("DEFINITION")) + val expected = statementQuery(global(DUPLICATES_STRUCT).pathSymbol("DEFINITION", StaticType.unionOf(STRING, FLOAT, DECIMAL))) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -344,37 +256,9 @@ class PlanTyperTest { fun testUnorderedDuplicatesWithSensitivity() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.DECIMAL, - op = rexOpPath( - root = rex( - DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("DEFINITION", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_duplicates_struct").pathKey("DEFINITION")) + val expected = statementQuery(global(DUPLICATES_STRUCT).pathKey("DEFINITION", DECIMAL)) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -383,37 +267,9 @@ class PlanTyperTest { fun testUnorderedDuplicatesWithSensitivityAndDuplicateResults() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.unionOf(StaticType.STRING, StaticType.FLOAT), - op = rexOpPath( - root = rex( - DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_duplicates_struct").pathKey("definition")) + val expected = statementQuery(global(DUPLICATES_STRUCT).pathKey("definition", StaticType.unionOf(StaticType.STRING, StaticType.FLOAT))) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -422,37 +278,9 @@ class PlanTyperTest { fun testOpenDuplicates() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("open_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - OPEN_DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("open_duplicates_struct").pathKey("definition")) + val expected = statementQuery(global(OPEN_DUPLICATES_STRUCT).pathKey("definition")) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -461,37 +289,9 @@ class PlanTyperTest { fun testUnionClosedDuplicates() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_union_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.INSENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.unionOf(StaticType.STRING, StaticType.FLOAT, StaticType.DECIMAL, StaticType.INT2), - op = rexOpPath( - root = rex( - CLOSED_UNION_DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.INSENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_union_duplicates_struct").pathSymbol("definition")) + val expected = statementQuery(global(CLOSED_UNION_DUPLICATES_STRUCT).pathSymbol("definition", StaticType.unionOf(STRING, FLOAT, DECIMAL, INT2))) + val actual = typer.resolve(input) assertEquals(expected, actual) } @@ -500,42 +300,34 @@ class PlanTyperTest { fun testUnionClosedDuplicatesWithSensitivity() { val wrapper = getTyper() val typer = wrapper.typer - val input = statementQuery( - root = rex( - type = StaticType.ANY, - op = rexOpPath( - root = rex( - StaticType.ANY, - rexOpVarUnresolved( - identifierSymbol("closed_union_duplicates_struct", Identifier.CaseSensitivity.SENSITIVE), - Rex.Op.Var.Scope.DEFAULT - ) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) - val expected = statementQuery( - root = rex( - type = StaticType.unionOf(StaticType.STRING, StaticType.FLOAT, StaticType.INT2), - op = rexOpPath( - root = rex( - CLOSED_UNION_DUPLICATES_STRUCT, - rexOpGlobal(0, 0) - ), - steps = listOf( - rexOpPathStepSymbol(identifierSymbol("definition", Identifier.CaseSensitivity.SENSITIVE)), - ) - ) - ) - ) + val input = statementQuery(unresolvedSensitiveVar("closed_union_duplicates_struct").pathKey("definition")) + val expected = statementQuery(global(CLOSED_UNION_DUPLICATES_STRUCT).pathKey("definition", StaticType.unionOf(STRING, FLOAT, INT2))) + val actual = typer.resolve(input) assertEquals(expected, actual) } - private fun rexOpGlobal(catalog: Int, ref: Int) = rexOpGlobal( - catalogSymbolRef(catalog, ref) - ) + @OptIn(PartiQLValueExperimental::class) + private fun rexString(str: String) = rex(STRING, rexOpLit(stringValue(str))) + + private fun Rex.pathKey(key: String, type: StaticType = ANY): Rex = Rex(type, rexOpPathKey(this, rexString(key))) + + private fun Rex.pathSymbol(key: String, type: StaticType = ANY): Rex = Rex(type, rexOpPathSymbol(this, key)) + + private fun unresolvedSensitiveVar(name: String, type: StaticType = ANY): Rex { + return rex( + type, + rexOpVarUnresolved( + identifierSymbol(name, Identifier.CaseSensitivity.SENSITIVE), + Rex.Op.Var.Scope.DEFAULT + ) + ) + } + + private fun global(type: StaticType, catalogIndex: Int = 0, symbolIndex: Int = 0): Rex { + return rex( + type, + rexOpGlobal(catalogSymbolRef(catalogIndex, symbolIndex)) + ) + } }