diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt index 35401dec02..39b97e10d5 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt @@ -244,6 +244,9 @@ internal object RelConverter { override fun visitFromJoin(node: From.Join, nil: Rel): Rel { val lhs = visitFrom(node.lhs, nil) val rhs = visitFrom(node.rhs, nil) + val schema = lhs.type.schema + rhs.type.schema + val props = emptySet() + val condition = node.condition?.let { RexConverter.apply(it, env) } ?: rex(StaticType.BOOL, rexOpLit(boolValue(true))) val joinType = when (node.type) { From.Join.Type.LEFT_OUTER, From.Join.Type.LEFT -> Rel.Op.Join.Type.LEFT @@ -254,17 +257,6 @@ internal object RelConverter { From.Join.Type.CROSS -> Rel.Op.Join.Type.INNER // Cross Joins are just INNER JOIN ON TRUE null -> Rel.Op.Join.Type.INNER // a JOIN b ON a.id = b.id <--> a INNER JOIN b ON a.id = b.id } - val l = lhs.type.schema - val r = rhs.type.schema - - val schema = when (joinType) { - Rel.Op.Join.Type.INNER -> l + r - Rel.Op.Join.Type.LEFT -> l + r.pad() - Rel.Op.Join.Type.RIGHT -> l.pad() + r - Rel.Op.Join.Type.FULL -> l.pad() + r.pad() - } - val props = emptySet() - val condition = node.condition?.let { RexConverter.apply(it, env) } ?: rex(StaticType.BOOL, rexOpLit(boolValue(true))) val type = relType(schema, props) val op = relOpJoin(lhs, rhs, condition, joinType) @@ -277,14 +269,6 @@ internal object RelConverter { return copy(fields.map { it.copy(value = it.value.asNullable()) }) } - private fun List.pad() = map { - val type = when (val t = it.type) { - is StructType -> t.withNullableFields() - else -> t.asNullable() - } - relBinding(it.name, type) - } - private fun convertScan(rex: Rex, binding: Rel.Binding): Rel { val schema = listOf(binding) val props = emptySet() 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 5614d916e9..6395e4aaf0 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 @@ -263,34 +263,6 @@ internal object RexConverter { return newRoot } - val relFromDefault: (Rex, Int) -> Rel = { path, index -> - val schema = listOf( - relBinding( - name = "_v$index", // fresh variable - type = path.type - ) - ) - val props = emptySet() - val relType = relType(schema, props) - rel(relType, relOpScan(path)) - } - - val relFromUnpivot: (Rex, Int) -> Rel = { path, index -> - val schema = listOf( - relBinding( - name = "_k$index", // fresh variable - type = StaticType.STRING - ), - relBinding( - name = "_v$index", // fresh variable - type = path.type - ) - ) - val props = emptySet() - val relType = relType(schema, props) - rel(relType, relOpUnpivot(path)) - } - val fromList = mutableListOf() val pathNavi = newSteps.fold(newRoot) { current, step -> @@ -327,13 +299,16 @@ internal object RexConverter { } is Expr.Path.Step.Unpivot -> { - val op = rexOpVarLocal(1, -1) + // Unpivot produces two binding, in this context we want the value, + // which always going to be the second binding + val op = rexOpVarLocal(1, 1) val index = fromList.size fromList.add(relFromUnpivot(current, index)) op } is Expr.Path.Step.Wildcard -> { - val op = rexOpVarLocal(1, -1) + // Scan produce only one binding + val op = rexOpVarLocal(1, 0) val index = fromList.size fromList.add(relFromDefault(current, index)) op @@ -350,17 +325,59 @@ internal object RexConverter { rel(type, relOpJoin(acc, scan, rex(StaticType.BOOL, rexOpLit(boolValue(true))), Rel.Op.Join.Type.INNER)) } + // compute the ref used by select construct + // always going to be the last binding + val selectRef = fromNode.type.schema.size - 1 + val constructor = when (val op = pathNavi.op) { - is Rex.Op.Path.Index -> rex(pathNavi.type, rexOpPathIndex(rex(op.root.type, rexOpVarLocal(0, -1)), op.key)) - is Rex.Op.Path.Key -> rex(pathNavi.type, rexOpPathKey(rex(op.root.type, rexOpVarLocal(0, -1)), op.key)) - is Rex.Op.Path.Symbol -> rex(pathNavi.type, rexOpPathSymbol(rex(op.root.type, rexOpVarLocal(0, -1)), op.key)) - is Rex.Op.Var.Local -> rex(pathNavi.type, rexOpVarLocal(0, -1)) + is Rex.Op.Path.Index -> rex(pathNavi.type, rexOpPathIndex(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) + is Rex.Op.Path.Key -> rex(pathNavi.type, rexOpPathKey(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) + is Rex.Op.Path.Symbol -> rex(pathNavi.type, rexOpPathSymbol(rex(op.root.type, rexOpVarLocal(0, selectRef)), op.key)) + is Rex.Op.Var.Local -> rex(pathNavi.type, rexOpVarLocal(0, selectRef)) else -> throw IllegalStateException() } val op = rexOpSelect(constructor, fromNode) return rex(StaticType.ANY, op) } + /** + * Construct Rel(Scan([path])). + * + * The constructed rel would produce one binding: _v$[index] + */ + private fun relFromDefault(path: Rex, index: Int): Rel { + val schema = listOf( + relBinding( + name = "_v$index", // fresh variable + type = path.type + ) + ) + val props = emptySet() + val relType = relType(schema, props) + return rel(relType, relOpScan(path)) + } + + /** + * Construct Rel(Unpivot([path])). + * + * The constructed rel would produce two bindings: _k$[index] and _v$[index] + */ + private fun relFromUnpivot(path: Rex, index: Int): Rel { + val schema = listOf( + relBinding( + name = "_k$index", // fresh variable + type = StaticType.STRING + ), + relBinding( + name = "_v$index", // fresh variable + type = path.type + ) + ) + val props = emptySet() + val relType = relType(schema, props) + return rel(relType, relOpUnpivot(path)) + } + private fun rexString(str: String) = rex(StaticType.STRING, rexOpLit(stringValue(str))) override fun visitExprCall(node: Expr.Call, context: Env): Rex { 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 0a3fdc269a..c0580e7071 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 @@ -60,7 +60,6 @@ import org.partiql.planner.internal.ir.rexOpStruct import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpSubquery import org.partiql.planner.internal.ir.rexOpTupleUnion -import org.partiql.planner.internal.ir.rexOpVarLocal import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.ir.util.PlanRewriter import org.partiql.spi.BindingCase @@ -94,7 +93,6 @@ import org.partiql.value.TextValue import org.partiql.value.boolValue import org.partiql.value.missingValue import org.partiql.value.stringValue -import kotlin.math.absoluteValue /** * Rewrites an untyped algebraic translation of the query to be both typed and have resolved variables. @@ -438,9 +436,8 @@ internal class PlanTyper( assert(node.ref < scope.schema.size) { "Invalid resolved variable (var ${node.ref}, stack frame ${node.depth}) in env: $locals" } - val nodeRef = if (node.ref < 0) scope.schema.size - node.ref.absoluteValue else node.ref - val type = scope.schema.getOrNull(nodeRef)?.type ?: error("Can't find locals value.") - return rex(type, rexOpVarLocal(node.depth, nodeRef)) + val type = scope.schema.getOrNull(node.ref)?.type ?: error("Can't find locals value.") + return rex(type, node) } override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: StaticType?): Rex { 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 fbc282b205..43db6a7650 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 @@ -29,8 +29,7 @@ internal data class TypeEnv( internal fun getScope(depth: Int): TypeEnv { return when (depth) { 0 -> this - // TODO: Consider resolve the variable to the correct index - else -> outer.reversed()[depth - 1] + else -> outer[outer.size - depth] } }