From 9b1b6771070b8cdca24215d60d994f19e06f1c59 Mon Sep 17 00:00:00 2001 From: John Ed Quinn <40360967+johnedquinn@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:45:31 -0700 Subject: [PATCH 01/10] Merge pull request #1430 from johnedquinn/v1-union Adds compilation/evaluation support for UNION/INTERSECT/EXCEPT ALL/DISTINCT --- .../org/partiql/eval/internal/Compiler.kt | 434 +++++ .../eval/internal/helpers/IteratorChain.kt | 27 + .../eval/internal/helpers/IteratorPeeking.kt | 36 + .../eval/internal/operator/rel/RelDistinct.kt | 31 + .../internal/operator/rel/RelExceptAll.kt | 53 + .../operator/rel/RelExceptDistinct.kt | 59 + .../eval/internal/operator/rel/RelFilter.kt | 39 + .../internal/operator/rel/RelIntersectAll.kt | 54 + .../operator/rel/RelIntersectDistinct.kt | 49 + .../operator/rel/RelJoinNestedLoop.kt | 98 ++ .../eval/internal/operator/rel/RelPeeking.kt | 39 + .../eval/internal/operator/rel/RelUnionAll.kt | 32 + .../internal/operator/rel/RelUnionDistinct.kt | 38 + .../eval/internal/PartiQLEngineDefaultTest.kt | 1515 +++++++++++++++++ .../src/main/resources/partiql_plan.ion | 33 +- .../org/partiql/planner/internal/ir/Nodes.kt | 117 +- .../internal/transforms/PlanTransform.kt | 14 +- .../internal/transforms/RelConverter.kt | 16 +- .../internal/transforms/RexConverter.kt | 38 + .../planner/internal/typer/PlanTyper.kt | 92 +- .../main/resources/partiql_plan_internal.ion | 42 +- 21 files changed, 2765 insertions(+), 91 deletions(-) create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt create mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt create mode 100644 partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt new file mode 100644 index 0000000000..ae2950342a --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt @@ -0,0 +1,434 @@ +package org.partiql.eval.internal + +import org.partiql.eval.PartiQLEngine +import org.partiql.eval.internal.operator.Operator +import org.partiql.eval.internal.operator.rel.RelAggregate +import org.partiql.eval.internal.operator.rel.RelDistinct +import org.partiql.eval.internal.operator.rel.RelExceptAll +import org.partiql.eval.internal.operator.rel.RelExceptDistinct +import org.partiql.eval.internal.operator.rel.RelExclude +import org.partiql.eval.internal.operator.rel.RelFilter +import org.partiql.eval.internal.operator.rel.RelIntersectAll +import org.partiql.eval.internal.operator.rel.RelIntersectDistinct +import org.partiql.eval.internal.operator.rel.RelJoinInner +import org.partiql.eval.internal.operator.rel.RelJoinLeft +import org.partiql.eval.internal.operator.rel.RelJoinOuterFull +import org.partiql.eval.internal.operator.rel.RelJoinRight +import org.partiql.eval.internal.operator.rel.RelLimit +import org.partiql.eval.internal.operator.rel.RelOffset +import org.partiql.eval.internal.operator.rel.RelProject +import org.partiql.eval.internal.operator.rel.RelScan +import org.partiql.eval.internal.operator.rel.RelScanIndexed +import org.partiql.eval.internal.operator.rel.RelScanIndexedPermissive +import org.partiql.eval.internal.operator.rel.RelScanPermissive +import org.partiql.eval.internal.operator.rel.RelSort +import org.partiql.eval.internal.operator.rel.RelUnionAll +import org.partiql.eval.internal.operator.rel.RelUnionDistinct +import org.partiql.eval.internal.operator.rel.RelUnpivot +import org.partiql.eval.internal.operator.rex.ExprCallDynamic +import org.partiql.eval.internal.operator.rex.ExprCallStatic +import org.partiql.eval.internal.operator.rex.ExprCase +import org.partiql.eval.internal.operator.rex.ExprCast +import org.partiql.eval.internal.operator.rex.ExprCoalesce +import org.partiql.eval.internal.operator.rex.ExprCollection +import org.partiql.eval.internal.operator.rex.ExprLiteral +import org.partiql.eval.internal.operator.rex.ExprMissing +import org.partiql.eval.internal.operator.rex.ExprNullIf +import org.partiql.eval.internal.operator.rex.ExprPathIndex +import org.partiql.eval.internal.operator.rex.ExprPathKey +import org.partiql.eval.internal.operator.rex.ExprPathSymbol +import org.partiql.eval.internal.operator.rex.ExprPermissive +import org.partiql.eval.internal.operator.rex.ExprPivot +import org.partiql.eval.internal.operator.rex.ExprPivotPermissive +import org.partiql.eval.internal.operator.rex.ExprSelect +import org.partiql.eval.internal.operator.rex.ExprStruct +import org.partiql.eval.internal.operator.rex.ExprSubquery +import org.partiql.eval.internal.operator.rex.ExprTupleUnion +import org.partiql.eval.internal.operator.rex.ExprVarLocal +import org.partiql.eval.internal.operator.rex.ExprVarOuter +import org.partiql.plan.Catalog +import org.partiql.plan.PartiQLPlan +import org.partiql.plan.PlanNode +import org.partiql.plan.Ref +import org.partiql.plan.Rel +import org.partiql.plan.Rex +import org.partiql.plan.Statement +import org.partiql.plan.debug.PlanPrinter +import org.partiql.plan.rexOpErr +import org.partiql.plan.visitor.PlanBaseVisitor +import org.partiql.spi.fn.Agg +import org.partiql.spi.fn.FnExperimental +import org.partiql.types.StaticType +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.PartiQLValueType +import java.lang.IllegalStateException + +internal class Compiler( + private val plan: PartiQLPlan, + private val session: PartiQLEngine.Session, + private val symbols: Symbols, +) : PlanBaseVisitor() { + + fun compile(): Operator.Expr { + return visitPartiQLPlan(plan, null) + } + + override fun defaultReturn(node: PlanNode, ctx: StaticType?): Operator { + TODO("Not yet implemented") + } + + override fun visitRexOpErr(node: Rex.Op.Err, ctx: StaticType?): Operator { + val message = buildString { + this.appendLine(node.message) + PlanPrinter.append(this, plan) + } + throw IllegalStateException(message) + } + + override fun visitRelOpErr(node: Rel.Op.Err, ctx: StaticType?): Operator { + throw IllegalStateException(node.message) + } + + override fun visitPartiQLPlan(node: PartiQLPlan, ctx: StaticType?): Operator.Expr { + return visitStatement(node.statement, ctx) as Operator.Expr + } + + override fun visitStatementQuery(node: Statement.Query, ctx: StaticType?): Operator.Expr { + return visitRex(node.root, ctx).modeHandled() + } + + // REX + + override fun visitRex(node: Rex, ctx: StaticType?): Operator.Expr { + return super.visitRexOp(node.op, node.type) as Operator.Expr + } + + override fun visitRexOpCollection(node: Rex.Op.Collection, ctx: StaticType?): Operator { + val values = node.values.map { visitRex(it, ctx).modeHandled() } + val type = ctx ?: error("No type provided in ctx") + return ExprCollection(values, type) + } + + override fun visitRexOpStruct(node: Rex.Op.Struct, ctx: StaticType?): Operator { + val fields = node.fields.map { + val value = visitRex(it.v, ctx).modeHandled() + ExprStruct.Field(visitRex(it.k, ctx), value) + } + return ExprStruct(fields) + } + + override fun visitRexOpSelect(node: Rex.Op.Select, ctx: StaticType?): Operator { + val rel = visitRel(node.rel, ctx) + val ordered = node.rel.type.props.contains(Rel.Prop.ORDERED) + val constructor = visitRex(node.constructor, ctx).modeHandled() + return ExprSelect(rel, constructor, ordered) + } + + override fun visitRexOpSubquery(node: Rex.Op.Subquery, ctx: StaticType?): Operator { + val constructor = visitRex(node.constructor, ctx) + val input = visitRel(node.rel, ctx) + return when (node.coercion) { + Rex.Op.Subquery.Coercion.SCALAR -> ExprSubquery.Scalar(constructor, input) + Rex.Op.Subquery.Coercion.ROW -> ExprSubquery.Row(constructor, input) + } + } + + override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: StaticType?): Operator { + val rel = visitRel(node.rel, ctx) + val key = visitRex(node.key, ctx) + val value = visitRex(node.value, ctx) + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> ExprPivotPermissive(rel, key, value) + PartiQLEngine.Mode.STRICT -> ExprPivot(rel, key, value) + } + } + + override fun visitRexOpCoalesce(node: Rex.Op.Coalesce, ctx: StaticType?): Operator { + val args = Array(node.args.size) { visitRex(node.args[it], node.args[it].type) } + return ExprCoalesce(args) + } + + override fun visitRexOpNullif(node: Rex.Op.Nullif, ctx: StaticType?): Operator { + val value = visitRex(node.value, node.value.type) + val nullifier = visitRex(node.nullifier, node.value.type) + return ExprNullIf(value, nullifier) + } + + /** + * All variables from the local scope have a depth of 0. + * + * All variables coming from the stack have a depth > 0. To slightly minimize computation at execution, we subtract + * the depth by 1 to account for the fact that the local scope is not kept on the stack. + */ + override fun visitRexOpVar(node: Rex.Op.Var, ctx: StaticType?): Operator { + return when (node.depth) { + 0 -> ExprVarLocal(node.ref) + else -> { + ExprVarOuter(node.depth, node.ref) + } + } + } + + override fun visitRexOpGlobal(node: Rex.Op.Global, ctx: StaticType?): Operator = symbols.getGlobal(node.ref) + + override fun visitRelOpAggregate(node: Rel.Op.Aggregate, ctx: StaticType?): Operator.Relation { + val input = visitRel(node.input, ctx) + val calls = node.calls.map { + visitRelOpAggregateCall(it, ctx) + } + val groups = node.groups.map { visitRex(it, ctx).modeHandled() } + return RelAggregate(input, groups, calls) + } + + @OptIn(FnExperimental::class) + override fun visitRelOpAggregateCall(node: Rel.Op.Aggregate.Call, ctx: StaticType?): Operator.Aggregation { + val args = node.args.map { visitRex(it, it.type).modeHandled() } + val setQuantifier: Operator.Aggregation.SetQuantifier = when (node.setQuantifier) { + Rel.Op.Aggregate.Call.SetQuantifier.ALL -> Operator.Aggregation.SetQuantifier.ALL + Rel.Op.Aggregate.Call.SetQuantifier.DISTINCT -> Operator.Aggregation.SetQuantifier.DISTINCT + } + val agg = symbols.getAgg(node.agg) + return object : Operator.Aggregation { + override val delegate: Agg = agg + override val args: List = args + override val setQuantifier: Operator.Aggregation.SetQuantifier = setQuantifier + } + } + + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Operator { + val root = visitRex(node.root, ctx) + val key = visitRex(node.key, ctx) + return ExprPathKey(root, key) + } + + override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: StaticType?): Operator { + val root = visitRex(node.root, ctx) + val symbol = node.key + return ExprPathSymbol(root, symbol) + } + + override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: StaticType?): Operator { + val root = visitRex(node.root, ctx) + val index = visitRex(node.key, ctx) + return ExprPathIndex(root, index) + } + + @OptIn(FnExperimental::class, PartiQLValueExperimental::class) + override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: StaticType?): Operator { + val fn = symbols.getFn(node.fn) + val args = node.args.map { visitRex(it, ctx) }.toTypedArray() + val fnTakesInMissing = fn.signature.parameters.any { + it.type == PartiQLValueType.MISSING || it.type == PartiQLValueType.ANY + } + return when (fnTakesInMissing) { + true -> ExprCallStatic(fn, args.map { it.modeHandled() }.toTypedArray()) + false -> ExprCallStatic(fn, args) + } + } + + @OptIn(FnExperimental::class) + override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: StaticType?): Operator { + val args = node.args.map { visitRex(it, ctx).modeHandled() }.toTypedArray() + // Check candidate list size + when (node.candidates.size) { + 0 -> error("Rex.Op.Call.Dynamic had an empty candidates list: $node.") + // TODO this seems like it should be an error, but is possible if the fn match was non-exhaustive + // 1 -> error("Rex.Op.Call.Dynamic had a single candidate; should be Rex.Op.Call.Static") + } + // Check candidate name and arity for uniformity + var arity: Int = -1 + var name: String = "unknown" + // Compile the candidates + val candidates = Array(node.candidates.size) { + val candidate = node.candidates[it] + val fn = symbols.getFn(candidate.fn) + val coercions = candidate.coercions.toTypedArray() + // Check this candidate + val fnArity = fn.signature.parameters.size + val fnName = fn.signature.name.uppercase() + if (arity == -1) { + arity = fnArity + name = fnName + } else { + if (fnArity != arity) { + error("Dynamic call candidate had different arity than others; found $fnArity but expected $arity") + } + if (fnName != name) { + error("Dynamic call candidate had different name than others; found $fnName but expected $name") + } + } + ExprCallDynamic.Candidate(fn, coercions) + } + return ExprCallDynamic(name, candidates, args) + } + + override fun visitRexOpCast(node: Rex.Op.Cast, ctx: StaticType?): Operator { + return ExprCast(visitRex(node.arg, ctx), node.cast) + } + + override fun visitRexOpMissing(node: Rex.Op.Missing, ctx: StaticType?): Operator { + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> { + // Make a runtime TypeCheckException. + ExprMissing(node.message) + } + PartiQLEngine.Mode.STRICT -> { + // Promote to error. + visitRexOpErr(rexOpErr(node.message, node.causes), null) + } + } + } + + // REL + override fun visitRel(node: Rel, ctx: StaticType?): Operator.Relation { + return super.visitRelOp(node.op, ctx) as Operator.Relation + } + + override fun visitRelOpScan(node: Rel.Op.Scan, ctx: StaticType?): Operator { + val rex = visitRex(node.rex, ctx) + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> RelScanPermissive(rex) + PartiQLEngine.Mode.STRICT -> RelScan(rex) + } + } + + override fun visitRelOpProject(node: Rel.Op.Project, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + val projections = node.projections.map { visitRex(it, ctx).modeHandled() } + return RelProject(input, projections) + } + + override fun visitRelOpScanIndexed(node: Rel.Op.ScanIndexed, ctx: StaticType?): Operator { + val rex = visitRex(node.rex, ctx) + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> RelScanIndexedPermissive(rex) + PartiQLEngine.Mode.STRICT -> RelScanIndexed(rex) + } + } + + override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: StaticType?): Operator { + val expr = visitRex(node.rex, ctx) + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> RelUnpivot.Permissive(expr) + PartiQLEngine.Mode.STRICT -> RelUnpivot.Strict(expr) + } + } + + override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: StaticType?): Operator { + val lhs = visitRel(node.lhs, ctx) + val rhs = visitRel(node.rhs, ctx) + return when (node.quantifier) { + Rel.Op.Set.Quantifier.ALL -> RelExceptAll(lhs, rhs) + Rel.Op.Set.Quantifier.DISTINCT -> RelExceptDistinct(lhs, rhs) + } + } + + override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: StaticType?): Operator { + val lhs = visitRel(node.lhs, ctx) + val rhs = visitRel(node.rhs, ctx) + return when (node.quantifier) { + Rel.Op.Set.Quantifier.ALL -> RelIntersectAll(lhs, rhs) + Rel.Op.Set.Quantifier.DISTINCT -> RelIntersectDistinct(lhs, rhs) + } + } + + override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: StaticType?): Operator { + val lhs = visitRel(node.lhs, ctx) + val rhs = visitRel(node.rhs, ctx) + return when (node.quantifier) { + Rel.Op.Set.Quantifier.ALL -> RelUnionAll(lhs, rhs) + Rel.Op.Set.Quantifier.DISTINCT -> RelUnionDistinct(lhs, rhs) + } + } + + override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + val limit = visitRex(node.limit, ctx) + return RelLimit(input, limit) + } + + override fun visitRelOpOffset(node: Rel.Op.Offset, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + val offset = visitRex(node.offset, ctx) + return RelOffset(input, offset) + } + + override fun visitRexOpTupleUnion(node: Rex.Op.TupleUnion, ctx: StaticType?): Operator { + val args = node.args.map { visitRex(it, ctx) }.toTypedArray() + return ExprTupleUnion(args) + } + + override fun visitRelOpJoin(node: Rel.Op.Join, ctx: StaticType?): Operator { + val lhs = visitRel(node.lhs, ctx) + val rhs = visitRel(node.rhs, ctx) + val condition = visitRex(node.rex, ctx) + return when (node.type) { + Rel.Op.Join.Type.INNER -> RelJoinInner(lhs, rhs, condition) + Rel.Op.Join.Type.LEFT -> RelJoinLeft(lhs, rhs, condition) + Rel.Op.Join.Type.RIGHT -> RelJoinRight(lhs, rhs, condition) + Rel.Op.Join.Type.FULL -> RelJoinOuterFull(lhs, rhs, condition) + } + } + + override fun visitRexOpCase(node: Rex.Op.Case, ctx: StaticType?): Operator { + val branches = node.branches.map { branch -> + visitRex(branch.condition, ctx) to visitRex(branch.rex, ctx) + } + val default = visitRex(node.default, ctx) + return ExprCase(branches, default) + } + + @OptIn(PartiQLValueExperimental::class) + override fun visitRexOpLit(node: Rex.Op.Lit, ctx: StaticType?): Operator { + return ExprLiteral(node.value) + } + + override fun visitRelOpDistinct(node: Rel.Op.Distinct, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + return RelDistinct(input) + } + + override fun visitRelOpFilter(node: Rel.Op.Filter, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + val condition = visitRex(node.predicate, ctx).modeHandled() + return RelFilter(input, condition) + } + + override fun visitRelOpExclude(node: Rel.Op.Exclude, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + return RelExclude(input, node.paths) + } + + override fun visitRelOpSort(node: Rel.Op.Sort, ctx: StaticType?): Operator { + val input = visitRel(node.input, ctx) + val compiledSpecs = node.specs.map { spec -> + val expr = visitRex(spec.rex, ctx) + val order = spec.order + expr to order + } + return RelSort(input, compiledSpecs) + } + + // HELPERS + + private fun Operator.Expr.modeHandled(): Operator.Expr { + return when (session.mode) { + PartiQLEngine.Mode.PERMISSIVE -> ExprPermissive(this) + PartiQLEngine.Mode.STRICT -> this + } + } + + /** + * Get a typed catalog item from a reference + * + * @param T + * @return + */ + private inline fun Ref.get(): T { + val item = plan.catalogs.getOrNull(catalog)?.items?.get(symbol) + if (item == null || item !is T) { + error("Invalid catalog reference, $this for type ${T::class}") + } + return item + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt new file mode 100644 index 0000000000..55a2c832e8 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt @@ -0,0 +1,27 @@ +package org.partiql.eval.internal.helpers + +internal class IteratorChain( + iterators: Array> +) : IteratorPeeking() { + + private var iterator: Iterator> = when (iterators.isEmpty()) { + true -> listOf(emptyList().iterator()).iterator() + false -> iterators.iterator() + } + private var current: Iterator = iterator.next() + + override fun peek(): T? { + return when (current.hasNext()) { + true -> current.next() + false -> { + while (iterator.hasNext()) { + current = iterator.next() + if (current.hasNext()) { + return current.next() + } + } + return null + } + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt new file mode 100644 index 0000000000..55f6c8fd2b --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt @@ -0,0 +1,36 @@ +package org.partiql.eval.internal.helpers + +/** + * For [Iterator]s that MUST materialize data in order to execute [hasNext], this abstract class caches the + * result of [peek] to implement both [hasNext] and [next]. + * + * With this implementation, invoking hasNext() multiple times will not iterate unnecessarily. Invoking next() without + * invoking hasNext() is allowed -- however, it is highly recommended to avoid doing so. + */ +internal abstract class IteratorPeeking : Iterator { + + internal var next: T? = null + + /** + * @return NULL when there is not another [T] to be produced. Returns a [T] when able to. + * + * @see IteratorPeeking + */ + abstract fun peek(): T? + + override fun hasNext(): Boolean { + if (next != null) { + return true + } + this.next = peek() + return this.next != null + } + + override fun next(): T { + val next = next + ?: peek() + ?: error("There were no more elements, however, next() was called. Please use hasNext() beforehand.") + this.next = null + return next + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt new file mode 100644 index 0000000000..15dcc1ba5a --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt @@ -0,0 +1,31 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +internal class RelDistinct( + val input: Operator.Relation +) : RelPeeking() { + + private val seen = mutableSetOf() + + override fun openPeeking(env: Environment) { + input.open(env) + } + + override fun peek(): Record? { + for (next in input) { + if (seen.contains(next).not()) { + seen.add(next) + return next + } + } + return null + } + + override fun closePeeking() { + seen.clear() + input.close() + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt new file mode 100644 index 0000000000..f62a433b32 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt @@ -0,0 +1,53 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +internal class RelExceptAll( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : RelPeeking() { + + private val seen: MutableMap = mutableMapOf() + private var init: Boolean = false + + override fun openPeeking(env: Environment) { + lhs.open(env) + rhs.open(env) + init = false + seen.clear() + } + + override fun peek(): Record? { + if (!init) { + seed() + } + for (row in lhs) { + val remaining = seen[row] ?: 0 + if (remaining > 0) { + seen[row] = remaining - 1 + continue + } + return row + } + return null + } + + override fun closePeeking() { + lhs.close() + rhs.close() + seen.clear() + } + + /** + * Read the entire right-hand-side into our search structure. + */ + private fun seed() { + init = true + for (row in rhs) { + val n = seen[row] ?: 0 + seen[row] = n + 1 + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt new file mode 100644 index 0000000000..9874aabaea --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt @@ -0,0 +1,59 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +/** + * Non-communicative, this performs better when [lhs] is larger than [rhs]. + * + * @property lhs + * @property rhs + */ +internal class RelExceptDistinct( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : RelPeeking() { + + private var seen: MutableSet = mutableSetOf() + private var init: Boolean = false + + override fun openPeeking(env: Environment) { + lhs.open(env) + rhs.open(env) + init = false + seen = mutableSetOf() + } + + override fun peek(): Record? { + if (!init) { + seed() + } + for (row in lhs) { + if (!seen.contains(row)) { + return row + } + } + return null + } + + override fun closePeeking() { + lhs.close() + rhs.close() + seen.clear() + } + + /** + * Read the entire right-hand-side into our search structure. + */ + private fun seed() { + init = true + while (true) { + if (rhs.hasNext().not()) { + break + } + val row = rhs.next() + seen.add(row) + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt new file mode 100644 index 0000000000..2081f14d58 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt @@ -0,0 +1,39 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator +import org.partiql.value.BoolValue +import org.partiql.value.PartiQLValueExperimental + +internal class RelFilter( + val input: Operator.Relation, + val expr: Operator.Expr +) : RelPeeking() { + + private lateinit var env: Environment + + override fun openPeeking(env: Environment) { + this.env = env + input.open(env) + } + + override fun peek(): Record? { + for (inputRecord in input) { + if (conditionIsTrue(inputRecord, expr)) { + return inputRecord + } + } + return null + } + + override fun closePeeking() { + input.close() + } + + @OptIn(PartiQLValueExperimental::class) + private fun conditionIsTrue(record: Record, expr: Operator.Expr): Boolean { + val condition = expr.eval(env.push(record)) + return condition is BoolValue && condition.value == true + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt new file mode 100644 index 0000000000..33f2ba869a --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt @@ -0,0 +1,54 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +internal class RelIntersectAll( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : RelPeeking() { + + private val seen: MutableMap = mutableMapOf() + private var init: Boolean = false + + override fun openPeeking(env: Environment) { + lhs.open(env) + rhs.open(env) + init = false + seen.clear() + } + + override fun peek(): Record? { + if (!init) { + seed() + } + for (row in rhs) { + seen.computeIfPresent(row) { _, y -> + when (y) { + 0 -> null + else -> y - 1 + } + }?.let { return row } + } + return null + } + + override fun closePeeking() { + lhs.close() + rhs.close() + seen.clear() + } + + /** + * Read the entire left-hand-side into our search structure. + */ + private fun seed() { + init = true + for (row in lhs) { + seen.computeIfPresent(row) { _, y -> + y + 1 + } ?: seen.put(row, 1) + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt new file mode 100644 index 0000000000..2700924104 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt @@ -0,0 +1,49 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +internal class RelIntersectDistinct( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : RelPeeking() { + + private val seen: MutableSet = mutableSetOf() + private var init: Boolean = false + + override fun openPeeking(env: Environment) { + lhs.open(env) + rhs.open(env) + init = false + seen.clear() + } + + override fun peek(): Record? { + if (!init) { + seed() + } + for (row in rhs) { + if (seen.remove(row)) { + return row + } + } + return null + } + + override fun closePeeking() { + lhs.close() + rhs.close() + seen.clear() + } + + /** + * Read the entire left-hand-side into our search structure. + */ + private fun seed() { + init = true + for (row in lhs) { + seen.add(row) + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt new file mode 100644 index 0000000000..cb39e48188 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt @@ -0,0 +1,98 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator +import org.partiql.value.BoolValue +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.StructValue +import org.partiql.value.nullValue +import org.partiql.value.structValue + +internal abstract class RelJoinNestedLoop : RelPeeking() { + + abstract val lhs: Operator.Relation + abstract val rhs: Operator.Relation + abstract val condition: Operator.Expr + + private var lhsRecord: Record? = null + private lateinit var env: Environment + + override fun openPeeking(env: Environment) { + this.env = env + lhs.open(env) + if (lhs.hasNext().not()) { + return + } + lhsRecord = lhs.next() + rhs.open(env.push(lhsRecord!!)) + } + + abstract fun join(condition: Boolean, lhs: Record, rhs: Record): Record? + + @OptIn(PartiQLValueExperimental::class) + override fun peek(): Record? { + if (lhsRecord == null) { + return null + } + var rhsRecord = when (rhs.hasNext()) { + true -> rhs.next() + false -> null + } + var toReturn: Record? = null + do { + // Acquire LHS and RHS Records + if (rhsRecord == null) { + rhs.close() + if (lhs.hasNext().not()) { + return null + } + lhsRecord = lhs.next() + rhs.open(env.push(lhsRecord!!)) + rhsRecord = when (rhs.hasNext()) { + true -> rhs.next() + false -> null + } + } + // Return Joined Record + if (rhsRecord != null && lhsRecord != null) { + val input = lhsRecord!! + rhsRecord + val result = condition.eval(env.push(input)) + toReturn = join(result.isTrue(), lhsRecord!!, rhsRecord) + } + // Move the pointer to the next row for the RHS + if (toReturn == null) rhsRecord = if (rhs.hasNext()) rhs.next() else null + } + while (toReturn == null) + return toReturn + } + + override fun closePeeking() { + lhs.close() + rhs.close() + } + + @OptIn(PartiQLValueExperimental::class) + private fun PartiQLValue.isTrue(): Boolean { + return this is BoolValue && this.value == true + } + + @OptIn(PartiQLValueExperimental::class) + internal fun Record.padNull() { + this.values.indices.forEach { index -> + this.values[index] = values[index].padNull() + } + } + + @OptIn(PartiQLValueExperimental::class) + private fun PartiQLValue.padNull(): PartiQLValue { + return when (this) { + is StructValue<*> -> { + val newFields = this.entries.map { it.first to nullValue() } + structValue(newFields) + } + else -> nullValue() + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt new file mode 100644 index 0000000000..6206b783bf --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt @@ -0,0 +1,39 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.IteratorPeeking +import org.partiql.eval.internal.operator.Operator + +/** + * For [Operator.Relation]'s that MUST materialize data in order to execute [hasNext], this abstract class caches the + * result of [peek] to implement both [hasNext] and [next]. + */ +internal abstract class RelPeeking : Operator.Relation, IteratorPeeking() { + + /** + * This shall have the same functionality as [open]. Implementers of [RelPeeking] shall not override [open]. + */ + abstract fun openPeeking(env: Environment) + + /** + * This shall have the same functionality as [close]. Implementers of [RelPeeking] shall not override [close]. + */ + abstract fun closePeeking() + + /** + * Implementers shall not override this method. + */ + override fun open(env: Environment) { + next = null + openPeeking(env) + } + + /** + * Implementers shall not override this method. + */ + override fun close() { + next = null + closePeeking() + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt new file mode 100644 index 0000000000..663abadd23 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt @@ -0,0 +1,32 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.operator.Operator + +internal class RelUnionAll( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : Operator.Relation { + + override fun open(env: Environment) { + lhs.open(env) + rhs.open(env) + } + + override fun hasNext(): Boolean { + return lhs.hasNext() || rhs.hasNext() + } + + override fun next(): Record { + return when (lhs.hasNext()) { + true -> lhs.next() + false -> rhs.next() + } + } + + override fun close() { + lhs.close() + rhs.close() + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt new file mode 100644 index 0000000000..bafa6cb28e --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt @@ -0,0 +1,38 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.IteratorChain +import org.partiql.eval.internal.operator.Operator + +internal class RelUnionDistinct( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, +) : RelPeeking() { + + private val seen: MutableSet = mutableSetOf() + private lateinit var input: Iterator + + override fun openPeeking(env: Environment) { + lhs.open(env) + rhs.open(env) + seen.clear() + input = IteratorChain(arrayOf(lhs, rhs)) + } + + override fun peek(): Record? { + for (record in input) { + if (!seen.contains(record)) { + seen.add(record) + return record + } + } + return null + } + + override fun closePeeking() { + lhs.close() + rhs.close() + seen.clear() + } +} 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 new file mode 100644 index 0000000000..c294c4b255 --- /dev/null +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -0,0 +1,1515 @@ +package org.partiql.eval.internal + +import com.amazon.ionelement.api.createIonElementLoader +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +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.eval.PartiQLEngine +import org.partiql.eval.PartiQLResult +import org.partiql.eval.internal.PartiQLEngineDefaultTest.SuccessTestCase.Global +import org.partiql.parser.PartiQLParser +import org.partiql.plan.PartiQLPlan +import org.partiql.plan.debug.PlanPrinter +import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.PartiQLPlannerBuilder +import org.partiql.plugins.memory.MemoryCatalog +import org.partiql.plugins.memory.MemoryConnector +import org.partiql.spi.connector.ConnectorSession +import org.partiql.types.StaticType +import org.partiql.value.CollectionValue +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.bagValue +import org.partiql.value.boolValue +import org.partiql.value.decimalValue +import org.partiql.value.int32Value +import org.partiql.value.int64Value +import org.partiql.value.intValue +import org.partiql.value.io.PartiQLValueIonWriterBuilder +import org.partiql.value.listValue +import org.partiql.value.missingValue +import org.partiql.value.nullValue +import org.partiql.value.stringValue +import org.partiql.value.structValue +import java.io.ByteArrayOutputStream +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.test.assertNotNull + +/** + * This holds sanity tests during the development of the [PartiQLEngine.default] implementation. + */ +@OptIn(PartiQLValueExperimental::class) +class PartiQLEngineDefaultTest { + + @ParameterizedTest + @MethodSource("sanityTestsCases") + @Execution(ExecutionMode.CONCURRENT) + fun sanityTests(tc: SuccessTestCase) = tc.assert() + + @ParameterizedTest + @MethodSource("typingModeTestCases") + @Execution(ExecutionMode.CONCURRENT) + fun typingModeTests(tc: TypingTestCase) = tc.assert() + + @ParameterizedTest + @MethodSource("subqueryTestCases") + @Execution(ExecutionMode.CONCURRENT) + fun subqueryTests(tc: SuccessTestCase) = tc.assert() + + @ParameterizedTest + @MethodSource("aggregationTestCases") + @Execution(ExecutionMode.CONCURRENT) + fun aggregationTests(tc: SuccessTestCase) = tc.assert() + + @ParameterizedTest + @MethodSource("globalsTestCases") + @Execution(ExecutionMode.CONCURRENT) + fun globalsTests(tc: SuccessTestCase) = tc.assert() + + companion object { + + @JvmStatic + fun globalsTestCases() = listOf( + SuccessTestCase( + input = """ + SELECT VALUE t.a + FROM t; + """.trimIndent(), + expected = bagValue( + intValue(BigInteger.valueOf(1)), + intValue(BigInteger.valueOf(2)), + ), + globals = listOf( + SuccessTestCase.Global( + name = "t", + value = """ + [ + { "a": 1 }, + { "a": 2 } + ] + """ + ) + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE t1.a + FROM t AS t1, t AS t2; + """.trimIndent(), + expected = bagValue( + intValue(BigInteger.valueOf(1)), + intValue(BigInteger.valueOf(1)), + intValue(BigInteger.valueOf(2)), + intValue(BigInteger.valueOf(2)), + ), + globals = listOf( + SuccessTestCase.Global( + name = "t", + value = """ + [ + { "a": 1 }, + { "a": 2 } + ] + """ + ) + ) + ), + SuccessTestCase( + input = """ + SELECT o.name AS orderName, + (SELECT c.name FROM customers c WHERE c.id=o.custId) AS customerName + FROM orders o + """.trimIndent(), + expected = bagValue( + structValue( + "orderName" to stringValue("foo") + ), + structValue( + "orderName" to stringValue("bar"), + "customerName" to stringValue("Helen") + ), + ), + globals = listOf( + SuccessTestCase.Global( + name = "customers", + value = """ + [{id:1, name: "Mary"}, + {id:2, name: "Helen"}, + {id:1, name: "John"} + ] + """ + ), + SuccessTestCase.Global( + name = "orders", + value = """ + [{custId:1, name: "foo"}, + {custId:2, name: "bar"} + ] + """ + ), + ) + ), + ) + + @JvmStatic + fun subqueryTestCases() = listOf( + SuccessTestCase( + input = """ + SELECT VALUE ( + SELECT VALUE t1 + t2 + FROM <<5, 6>> AS t2 + ) FROM <<0, 10>> AS t1; + """.trimIndent(), + expected = bagValue( + bagValue(int32Value(5), int32Value(6)), + bagValue(int32Value(15), int32Value(16)) + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE ( + SELECT t1 + t2 + FROM <<5>> AS t2 + ) FROM <<0, 10>> AS t1; + """.trimIndent(), + expected = bagValue(int32Value(5), int32Value(15)) + ), + SuccessTestCase( + input = """ + SELECT ( + SELECT VALUE t1 + t2 + FROM <<5>> AS t2 + ) AS t1_plus_t2 + FROM <<0, 10>> AS t1; + """.trimIndent(), + expected = bagValue( + structValue("t1_plus_t2" to bagValue(int32Value(5))), + structValue("t1_plus_t2" to bagValue(int32Value(15))) + ) + ), + SuccessTestCase( + input = """ + SELECT + ( + SELECT (t1 + t2) * ( + SELECT t1 + t3 + t2 + FROM <<7>> AS t3 + ) + FROM <<5>> AS t2 + ) AS t1_plus_t2 + FROM <<0, 10>> AS t1; + """.trimIndent(), + expected = bagValue( + structValue("t1_plus_t2" to int32Value(60)), + structValue("t1_plus_t2" to int32Value(330)) + ) + ), + SuccessTestCase( + input = """ + 1 + (SELECT t.a FROM << { 'a': 3 } >> AS t) + """.trimIndent(), + expected = int32Value(4) + ), + SuccessTestCase( + input = """ + SELECT VALUE element + FROM << { 'a': [0, 1, 2] }, { 'a': [3, 4, 5] } >> AS t, t.a AS element + """.trimIndent(), + expected = bagValue( + int32Value(0), + int32Value(1), + int32Value(2), + int32Value(3), + int32Value(4), + int32Value(5), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE element + FROM << { 'a': { 'c': [0, 1, 2] } }, { 'a': { 'c': [3, 4, 5] } } >> AS t, t.a AS b, b.c AS element + """.trimIndent(), + expected = bagValue( + int32Value(0), + int32Value(1), + int32Value(2), + int32Value(3), + int32Value(4), + int32Value(5), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE t_a_b + t_a_c + FROM << { 'a': { 'b': [100, 200], 'c': [0, 1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4, 5] } } >> + AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c + """.trimIndent(), + expected = bagValue( + int32Value(100), + int32Value(101), + int32Value(102), + int32Value(200), + int32Value(201), + int32Value(202), + int32Value(303), + int32Value(304), + int32Value(305), + int32Value(403), + int32Value(404), + int32Value(405), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE t_a_b + t_a_c + t_a_c_original + FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> + AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c, t.a.c AS t_a_c_original + """.trimIndent(), + expected = bagValue( + int32Value(102), + int32Value(103), + int32Value(103), + int32Value(104), + int32Value(202), + int32Value(203), + int32Value(203), + int32Value(204), + int32Value(306), + int32Value(307), + int32Value(307), + int32Value(308), + int32Value(406), + int32Value(407), + int32Value(407), + int32Value(408), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE t_a_b + t_a_c + t_a_c_original + FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> + AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c, (SELECT VALUE d FROM t.a.c AS d) AS t_a_c_original + """.trimIndent(), + expected = bagValue( + int32Value(102), + int32Value(103), + int32Value(103), + int32Value(104), + int32Value(202), + int32Value(203), + int32Value(203), + int32Value(204), + int32Value(306), + int32Value(307), + int32Value(307), + int32Value(308), + int32Value(406), + int32Value(407), + int32Value(407), + int32Value(408), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE t_a_b + t_a_c + t_a_c_original + FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> + AS t, + t.a AS t_a, + t_a.b AS t_a_b, + t_a.c AS t_a_c, + (SELECT VALUE d + (SELECT b_og FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original + """.trimIndent(), + expected = bagValue( + int32Value(302), + int32Value(303), + int32Value(303), + int32Value(304), + int32Value(402), + int32Value(403), + int32Value(403), + int32Value(404), + int32Value(706), + int32Value(707), + int32Value(707), + int32Value(708), + int32Value(806), + int32Value(807), + int32Value(807), + int32Value(808), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE + t_a_b + t_a_c + t_a_c_original + ( + SELECT t_a_c_inner + FROM t.a.c AS t_a_c_inner + WHERE t_a_c_inner = 2 OR t_a_c_inner = 4 + ) + FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> + AS t, + t.a AS t_a, + t_a.b AS t_a_b, + t_a.c AS t_a_c, + (SELECT VALUE d + (SELECT b_og FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original + """.trimIndent(), + expected = bagValue( + int32Value(304), + int32Value(305), + int32Value(305), + int32Value(306), + int32Value(404), + int32Value(405), + int32Value(405), + int32Value(406), + int32Value(710), + int32Value(711), + int32Value(711), + int32Value(712), + int32Value(810), + int32Value(811), + int32Value(811), + int32Value(812), + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE + t_a_b + t_a_c + t_a_c_original + ( + SELECT t_a_c_inner + t_a_c + FROM t.a.c AS t_a_c_inner + WHERE t_a_c_inner = 2 OR t_a_c_inner = 4 + ) + FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> + AS t, + t.a AS t_a, + t_a.b AS t_a_b, + t_a.c AS t_a_c, + (SELECT VALUE d + (SELECT b_og + t_a_c FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original + """.trimIndent(), + expected = bagValue( + int32Value(306), + int32Value(307), + int32Value(309), + int32Value(310), + int32Value(406), + int32Value(407), + int32Value(409), + int32Value(410), + int32Value(716), + int32Value(717), + int32Value(719), + int32Value(720), + int32Value(816), + int32Value(817), + int32Value(819), + int32Value(820), + ) + ) + ) + + @JvmStatic + fun aggregationTestCases() = kotlin.collections.listOf( + SuccessTestCase( + input = """ + SELECT + gk_0, SUM(t.c) AS t_c_sum + FROM << + { 'b': NULL, 'c': 1 }, + { 'b': MISSING, 'c': 2 }, + { 'b': 1, 'c': 1 }, + { 'b': 1, 'c': 2 }, + { 'b': 2, 'c': NULL }, + { 'b': 2, 'c': 2 }, + { 'b': 3, 'c': MISSING }, + { 'b': 3, 'c': 2 }, + { 'b': 4, 'c': MISSING }, + { 'b': 4, 'c': NULL } + >> AS t GROUP BY t.b AS gk_0; + """.trimIndent(), + expected = org.partiql.value.bagValue( + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(1), + "t_c_sum" to org.partiql.value.int32Value(3) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(2), + "t_c_sum" to org.partiql.value.int32Value(2) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(3), + "t_c_sum" to org.partiql.value.int32Value(2) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(4), + "t_c_sum" to org.partiql.value.int32Value(null) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.nullValue(), + "t_c_sum" to org.partiql.value.int32Value(3) + ), + ), + mode = org.partiql.eval.PartiQLEngine.Mode.PERMISSIVE + ), + SuccessTestCase( + input = """ + SELECT VALUE { 'sensor': sensor, + 'readings': (SELECT VALUE v.l.co FROM g AS v) + } + FROM [{'sensor':1, 'co':0.4}, {'sensor':1, 'co':0.2}, {'sensor':2, 'co':0.3}] AS l + GROUP BY l.sensor AS sensor GROUP AS g + """.trimIndent(), + expected = org.partiql.value.bagValue( + org.partiql.value.structValue( + "sensor" to org.partiql.value.int32Value(1), + "readings" to org.partiql.value.bagValue( + org.partiql.value.decimalValue(0.4.toBigDecimal()), + org.partiql.value.decimalValue(0.2.toBigDecimal()) + ) + ), + org.partiql.value.structValue( + "sensor" to org.partiql.value.int32Value(2), + "readings" to org.partiql.value.bagValue( + org.partiql.value.decimalValue(0.3.toBigDecimal()) + ) + ), + ) + ), + SuccessTestCase( + input = """ + SELECT col1, g + FROM [{ 'col1':1 }, { 'col1':1 }] simple_1_col_1_group + GROUP BY col1 GROUP AS g + """.trimIndent(), + expected = bagValue( + structValue( + "col1" to int32Value(1), + "g" to bagValue( + structValue( + "simple_1_col_1_group" to structValue("col1" to int32Value(1)) + ), + structValue( + "simple_1_col_1_group" to structValue("col1" to int32Value(1)) + ), + ) + ), + ) + ), + SuccessTestCase( + input = """ + SELECT p.supplierId_mixed + FROM [ + { 'productId': 5, 'categoryId': 21, 'regionId': 100, 'supplierId_nulls': null, 'price_nulls': null }, + { 'productId': 4, 'categoryId': 20, 'regionId': 100, 'supplierId_nulls': null, 'supplierId_mixed': null, 'price_nulls': null, 'price_mixed': null } + ] AS p + GROUP BY p.supplierId_mixed + """.trimIndent(), + expected = bagValue( + structValue( + "supplierId_mixed" to nullValue(), + ), + ) + ), + SuccessTestCase( + input = """ + SELECT * + FROM << { 'a': 1, 'b': 2 } >> AS t + GROUP BY a, b, a + b GROUP AS g + """.trimIndent(), + expected = bagValue( + structValue( + "a" to int32Value(1), + "b" to int32Value(2), + "_3" to int32Value(3), + "g" to bagValue( + structValue( + "t" to structValue( + "a" to int32Value(1), + "b" to int32Value(2), + ) + ) + ), + ), + ) + ), + ) + + @JvmStatic + fun sanityTestsCases() = listOf( + SuccessTestCase( + input = "SELECT VALUE 1 FROM <<0, 1>>;", + expected = bagValue(int32Value(1), int32Value(1)) + ), + SuccessTestCase( + input = "SELECT VALUE t FROM <<10, 20, 30>> AS t;", + expected = bagValue(int32Value(10), int32Value(20), int32Value(30)) + ), + SuccessTestCase( + input = "SELECT VALUE t FROM <> AS t WHERE t;", + expected = bagValue(boolValue(true), boolValue(true)) + ), + SuccessTestCase( + input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t, << { 'b': 2 } >> s;", + expected = bagValue(structValue("a" to int32Value(1), "b" to int32Value(2))) + ), + SuccessTestCase( + input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t LEFT JOIN << { 'b': 2 } >> s ON false;", + expected = bagValue(structValue("a" to int32Value(1), "b" to nullValue())) + ), + SuccessTestCase( + input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON false;", + expected = bagValue( + structValue( + "a" to int32Value(1), + "b" to nullValue() + ), + structValue( + "a" to nullValue(), + "b" to int32Value(2) + ), + ) + ), + SuccessTestCase( + input = """ + TUPLEUNION( + { 'a': 1 }, + { 'b': TRUE }, + { 'c': 'hello' } + ); + """.trimIndent(), + expected = structValue( + "a" to int32Value(1), + "b" to boolValue(true), + "c" to stringValue("hello") + ) + ), + SuccessTestCase( + input = """ + CASE + WHEN NULL THEN 'isNull' + WHEN MISSING THEN 'isMissing' + WHEN FALSE THEN 'isFalse' + WHEN TRUE THEN 'isTrue' + END + ; + """.trimIndent(), + expected = stringValue("isTrue") + ), + SuccessTestCase( + input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON TRUE;", + expected = bagValue( + structValue( + "a" to int32Value(1), + "b" to int32Value(2) + ), + ) + ), + SuccessTestCase( + input = """ + TUPLEUNION( + { 'a': 1 }, + NULL, + { 'c': 'hello' } + ); + """.trimIndent(), + expected = structValue(null) + ), + SuccessTestCase( + input = """ + CASE + WHEN NULL THEN 'isNull' + WHEN MISSING THEN 'isMissing' + WHEN FALSE THEN 'isFalse' + END + ; + """.trimIndent(), + expected = stringValue(null) + ), + SuccessTestCase( + input = """ + TUPLEUNION( + { 'a': 1 }, + 5, + { 'c': 'hello' } + ); + """.trimIndent(), + expected = missingValue() + ), + SuccessTestCase( + input = """ + TUPLEUNION( + { 'a': 1, 'b': FALSE }, + { 'b': TRUE }, + { 'c': 'hello' } + ); + """.trimIndent(), + expected = structValue( + "a" to int32Value(1), + "b" to boolValue(false), + "b" to boolValue(true), + "c" to stringValue("hello") + ) + ), + SuccessTestCase( + input = """ + SELECT * FROM + << + { 'a': 1, 'b': FALSE } + >> AS t, + << + { 'b': TRUE } + >> AS s + """.trimIndent(), + expected = bagValue( + structValue( + "a" to int32Value(1), + "b" to boolValue(false), + "b" to boolValue(true) + ) + ) + ), + SuccessTestCase( + input = """ + SELECT VALUE { + 'a': 1, + 'b': NULL, + t.c : t.d + } + FROM << + { 'c': 'hello', 'd': 'world' } + >> AS t + """.trimIndent(), + expected = bagValue( + structValue( + "a" to int32Value(1), + "b" to nullValue(), + "hello" to stringValue("world") + ) + ) + ), + SuccessTestCase( + input = "SELECT v, i FROM << 'a', 'b', 'c' >> AS v AT i", + expected = bagValue( + structValue( + "v" to stringValue("a"), + ), + structValue( + "v" to stringValue("b"), + ), + structValue( + "v" to stringValue("c"), + ), + ) + ), + SuccessTestCase( + input = "SELECT DISTINCT VALUE t FROM <> AS t;", + expected = bagValue(boolValue(true), boolValue(false)) + ), + SuccessTestCase( + input = "SELECT DISTINCT VALUE t FROM <> AS t WHERE t = TRUE;", + expected = bagValue(boolValue(true)) + ), + SuccessTestCase( + input = "100 + 50;", + expected = int32Value(150) + ), + SuccessTestCase( + input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2, 3>> AS t;", + expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300)) + ), + SuccessTestCase( + input = """ + PIVOT x.v AT x.k FROM << + { 'k': 'a', 'v': 'x' }, + { 'k': 'b', 'v': 'y' }, + { 'k': 'c', 'v': 'z' } + >> AS x + """.trimIndent(), + expected = structValue( + "a" to stringValue("x"), + "b" to stringValue("y"), + "c" to stringValue("z"), + ) + ), + SuccessTestCase( + input = """ + SELECT t + EXCLUDE t.a.b + FROM << + {'a': {'b': 2}, 'foo': 'bar', 'foo2': 'bar2'} + >> AS t + """.trimIndent(), + expected = bagValue( + structValue( + "t" to structValue( + "a" to structValue( + // field `b` excluded + ), + "foo" to stringValue("bar"), + "foo2" to stringValue("bar2") + ) + ), + ) + ), + 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) + ) + ) + ) + ) + ) + ) + ), + SuccessTestCase( + input = """ + CASE (1) + WHEN NULL THEN 'isNull' + WHEN MISSING THEN 'isMissing' + WHEN 2 THEN 'isTwo' + END + ; + """.trimIndent(), + expected = stringValue(null) + ), + SuccessTestCase( + input = """ + CASE (1) + WHEN NULL THEN 'isNull' + WHEN MISSING THEN 'isMissing' + WHEN 2 THEN 'isTwo' + WHEN 1 THEN 'isOne' + END + ; + """.trimIndent(), + expected = stringValue("isOne") + ), + SuccessTestCase( + input = """ + `null.bool` IS NULL + """.trimIndent(), + expected = boolValue(true) + ), + // SELECT * without nested coercion + SuccessTestCase( + input = """ + SELECT * + FROM ( + SELECT t.a AS "first", t.b AS "second" + FROM << { 'a': 3, 'b': 5 } >> AS t + ); + """.trimIndent(), + expected = bagValue( + structValue( + "first" to int32Value(3), + "second" to int32Value(5) + ) + ) + ), + // SELECT list without nested coercion + SuccessTestCase( + input = """ + SELECT "first", "second" + FROM ( + SELECT t.a AS "first", t.b AS "second" + FROM << { 'a': 3, 'b': 5 } >> AS t + ); + """.trimIndent(), + expected = bagValue( + structValue( + "first" to int32Value(3), + "second" to int32Value(5) + ) + ) + ), + // SELECT value without nested coercion + SuccessTestCase( + input = """ + SELECT VALUE "first" + FROM ( + SELECT t.a AS "first", t.b AS "second" + FROM << { 'a': 3, 'b': 5 } >> AS t + ); + """.trimIndent(), + expected = bagValue( + int32Value(3), + ) + ), + SuccessTestCase( + input = "MISSING IS MISSING;", + expected = boolValue(true) + ), + SuccessTestCase( + input = "MISSING IS MISSING;", + expected = boolValue(true), // TODO: Is this right? + mode = PartiQLEngine.Mode.STRICT + ), + SuccessTestCase( + input = "SELECT VALUE t.a IS MISSING FROM << { 'b': 1 }, { 'a': 2 } >> AS t;", + expected = bagValue(boolValue(true), boolValue(false)) + ), + // PartiQL Specification Section 7.1.1 -- Equality + SuccessTestCase( + input = "5 = 'a';", + expected = boolValue(false), + ), + // PartiQL Specification Section 7.1.1 -- Equality + SuccessTestCase( + input = "5 = 'a';", + expected = boolValue(false), // TODO: Is this correct? + mode = PartiQLEngine.Mode.STRICT + ), + // PartiQL Specification Section 8 + SuccessTestCase( + input = "MISSING AND TRUE;", + expected = boolValue(null), + ), + // PartiQL Specification Section 8 + SuccessTestCase( + input = "MISSING AND TRUE;", + expected = boolValue(null), // TODO: Is this right? + mode = PartiQLEngine.Mode.STRICT + ), + // PartiQL Specification Section 8 + SuccessTestCase( + input = "NULL IS MISSING;", + expected = boolValue(false), + ), + // PartiQL Specification Section 8 + SuccessTestCase( + input = "NULL IS MISSING;", + expected = boolValue(false), + mode = PartiQLEngine.Mode.STRICT + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': 10, 'b': 1}, {'a': 1, 'b': 2}>> AS t ORDER BY t.a;", + expected = listValue( + structValue("a" to int32Value(1), "b" to int32Value(2)), + structValue("a" to int32Value(10), "b" to int32Value(1)) + ) + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': 10, 'b': 1}, {'a': 1, 'b': 2}>> AS t ORDER BY t.a DESC;", + expected = listValue( + structValue("a" to int32Value(10), "b" to int32Value(1)), + structValue("a" to int32Value(1), "b" to int32Value(2)) + ) + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a NULLS LAST;", + expected = listValue( + structValue("a" to int32Value(1), "b" to int32Value(2)), + structValue("a" to int32Value(3), "b" to int32Value(4)), + structValue("a" to nullValue(), "b" to int32Value(1)) + ) + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a NULLS FIRST;", + expected = listValue( + structValue("a" to nullValue(), "b" to int32Value(1)), + structValue("a" to int32Value(1), "b" to int32Value(2)), + structValue("a" to int32Value(3), "b" to int32Value(4)) + ) + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a DESC NULLS LAST;", + expected = listValue( + structValue("a" to int32Value(3), "b" to int32Value(4)), + structValue("a" to int32Value(1), "b" to int32Value(2)), + structValue("a" to nullValue(), "b" to int32Value(1)) + ) + ), + SuccessTestCase( + input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a DESC NULLS FIRST;", + expected = listValue( + structValue("a" to nullValue(), "b" to int32Value(1)), + structValue("a" to int32Value(3), "b" to int32Value(4)), + structValue("a" to int32Value(1), "b" to int32Value(2)) + ) + ), + SuccessTestCase( // use multiple sort specs + input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 4}>> AS t ORDER BY t.a DESC NULLS FIRST, t.b DESC;", + expected = listValue( + structValue("a" to nullValue(), "b" to int32Value(1)), + structValue("a" to int32Value(1), "b" to int32Value(4)), + structValue("a" to int32Value(1), "b" to int32Value(2)) + ) + ), + ) + + @JvmStatic + fun typingModeTestCases() = listOf( + TypingTestCase( + name = "Expected missing value in collection", + input = "SELECT VALUE t.a FROM << { 'a': 1 }, { 'b': 2 } >> AS t;", + expectedPermissive = bagValue(int32Value(1), missingValue()) + ), + TypingTestCase( + name = "Expected missing value in tuple in collection", + input = "SELECT t.a AS \"a\" FROM << { 'a': 1 }, { 'b': 2 } >> AS t;", + expectedPermissive = bagValue( + structValue( + "a" to int32Value(1), + ), + structValue(), + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 4.2 -- index negative", + input = "[1,2,3][-1];", + expectedPermissive = missingValue() + ), + TypingTestCase( + name = "PartiQL Specification Section 4.2 -- out of bounds", + input = "[1,2,3][3];", + expectedPermissive = missingValue() + ), + TypingTestCase( + name = "PartiQL Spec Section 5.1.1 -- Position variable on bags", + input = "SELECT v, p FROM << 5 >> AS v AT p;", + expectedPermissive = bagValue( + structValue( + "v" to int32Value(5) + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 5.1.1 -- Iteration over a scalar value", + input = "SELECT v FROM 0 AS v;", + expectedPermissive = bagValue( + structValue( + "v" to int32Value(0) + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 5.1.1 -- Iteration over a scalar value (with at)", + input = "SELECT v, p FROM 0 AS v AT p;", + expectedPermissive = bagValue( + structValue( + "v" to int32Value(0) + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 5.1.1 -- Iteration over a tuple value", + input = "SELECT v.a AS a FROM { 'a': 1 } AS v;", + expectedPermissive = bagValue( + structValue( + "a" to int32Value(1) + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 5.1.1 -- Iteration over an absent value (missing)", + input = "SELECT v AS v FROM MISSING AS v;", + expectedPermissive = bagValue(structValue()) + ), + TypingTestCase( + name = "PartiQL Specification Section 5.1.1 -- Iteration over an absent value (null)", + input = "SELECT v AS v FROM NULL AS v;", + expectedPermissive = bagValue( + structValue( + "v" to nullValue() + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 6.1.4 -- when constructing tuples", + input = "SELECT VALUE {'a':v.a, 'b':v.b} FROM [{'a':1, 'b':1}, {'a':2}] AS v;", + expectedPermissive = bagValue( + structValue( + "a" to int32Value(1), + "b" to int32Value(1), + ), + structValue( + "a" to int32Value(2), + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 6.1.4 -- when constructing bags (1)", + input = "SELECT VALUE v.b FROM [{'a':1, 'b':1}, {'a':2}] AS v;", + expectedPermissive = bagValue( + int32Value(1), + missingValue() + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 6.1.4 -- when constructing bags (2)", + input = "SELECT VALUE <> FROM [{'a':1, 'b':1}, {'a':2}] AS v;", + expectedPermissive = bagValue( + bagValue( + int32Value(1), + int32Value(1), + ), + bagValue( + int32Value(2), + missingValue() + ) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 6.2 -- Pivoting a Collection into a Variable-Width Tuple", + input = "PIVOT t.price AT t.\"symbol\" FROM [{'symbol':25, 'price':31.52}, {'symbol':'amzn', 'price':840.05}] AS t;", + expectedPermissive = structValue( + "amzn" to decimalValue(BigDecimal.valueOf(840.05)) + ) + ), + TypingTestCase( + name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (1)", + input = "SELECT VALUE 5 + v FROM <<1, MISSING>> AS v;", + expectedPermissive = bagValue(int32Value(6), missingValue()) + ), + TypingTestCase( + name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (3)", + input = "SELECT VALUE NOT v FROM << false, {'a':1} >> AS v;", + expectedPermissive = bagValue(boolValue(true), missingValue()) + ), + TypingTestCase( + name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (2)", + input = "SELECT VALUE 5 > v FROM <<1, 'a'>> AS v;", + expectedPermissive = bagValue(boolValue(true), missingValue()) + ), + TypingTestCase( + name = "PartiQL Specification Section 9.1", + input = """ + SELECT + o.name AS orderName, + (SELECT c.name FROM << { 'name': 'John', 'id': 1 }, { 'name': 'Alan', 'id': 1 } >> c WHERE c.id=o.custId) AS customerName + FROM << { 'name': 'apples', 'custId': 1 } >> o + """.trimIndent(), + expectedPermissive = bagValue( + structValue( + "orderName" to stringValue("apples") + ) + ) + ) + ) + } + + public class SuccessTestCase @OptIn(PartiQLValueExperimental::class) constructor( + val input: String, + val expected: PartiQLValue, + val mode: PartiQLEngine.Mode = PartiQLEngine.Mode.PERMISSIVE, + val globals: List = emptyList(), + ) { + + private val engine = PartiQLEngine.builder().build() + private val planner = PartiQLPlannerBuilder().build() + private val parser = PartiQLParser.default() + private val loader = createIonElementLoader() + + /** + * @property value is a serialized Ion value. + */ + class Global( + val name: String, + val value: String, + val type: StaticType = StaticType.ANY, + ) + + internal fun assert() { + val statement = parser.parse(input).root + val catalogBuilder = MemoryCatalog.PartiQL().name("memory") + globals.forEach { global -> + catalogBuilder.define(global.name, global.type, loader.loadSingleElement(global.value)) + } + val catalog = catalogBuilder.build() + val connector = MemoryConnector(catalog) + val connectorSession = object : ConnectorSession { + override fun getQueryId(): String = "q" + override fun getUserId(): String = "u" + } + val session = PartiQLPlanner.Session( + "q", + "u", + "memory", + catalogs = mapOf("memory" to connector.getMetadata(connectorSession)) + ) + val plan = planner.plan(statement, session) + val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) + val result = when (val returned = engine.execute(prepared)) { + is PartiQLResult.Value -> returned + is PartiQLResult.Error -> { + PlanPrinter.append(System.err, plan.plan) + throw returned.cause + } + } + val output = result.value + assert(expected == output) { + comparisonString(expected, output, plan.plan) + } + } + + @OptIn(PartiQLValueExperimental::class) + private fun comparisonString(expected: PartiQLValue, actual: PartiQLValue, plan: PartiQLPlan): String { + val expectedBuffer = ByteArrayOutputStream() + val expectedWriter = PartiQLValueIonWriterBuilder.standardIonTextBuilder().build(expectedBuffer) + expectedWriter.append(expected) + return buildString { + PlanPrinter.append(this, plan) + appendLine("Expected : $expectedBuffer") + expectedBuffer.reset() + expectedWriter.append(actual) + appendLine("Actual : $expectedBuffer") + } + } + + override fun toString(): String { + return input + } + } + + public class TypingTestCase @OptIn(PartiQLValueExperimental::class) constructor( + val name: String, + val input: String, + val expectedPermissive: PartiQLValue, + ) { + + private val engine = PartiQLEngine.builder().build() + private val planner = PartiQLPlannerBuilder().build() + private val parser = PartiQLParser.default() + + internal fun assert() { + val permissiveResult = run(mode = PartiQLEngine.Mode.PERMISSIVE) + assert(expectedPermissive == permissiveResult.first) { + comparisonString(expectedPermissive, permissiveResult.first, permissiveResult.second) + } + var error: Throwable? = null + try { + when (val result = run(mode = PartiQLEngine.Mode.STRICT).first) { + is CollectionValue<*> -> result.toList() + else -> result + } + } catch (e: Throwable) { + error = e + } + assertNotNull(error) + } + + private fun run(mode: PartiQLEngine.Mode): Pair { + val statement = parser.parse(input).root + val catalog = MemoryCatalog.PartiQL().name("memory").build() + val connector = MemoryConnector(catalog) + val connectorSession = object : ConnectorSession { + override fun getQueryId(): String = "q" + override fun getUserId(): String = "u" + } + val session = PartiQLPlanner.Session( + "q", + "u", + "memory", + catalogs = mapOf("memory" to connector.getMetadata(connectorSession)) + ) + val plan = planner.plan(statement, session) + 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 + } + } + + @OptIn(PartiQLValueExperimental::class) + private fun comparisonString(expected: PartiQLValue, actual: PartiQLValue, plan: PartiQLPlan): String { + val expectedBuffer = ByteArrayOutputStream() + val expectedWriter = PartiQLValueIonWriterBuilder.standardIonTextBuilder().build(expectedBuffer) + expectedWriter.append(expected) + return buildString { + PlanPrinter.append(this, plan) + appendLine("Expected : $expectedBuffer") + expectedBuffer.reset() + expectedWriter.append(actual) + appendLine("Actual : $expectedBuffer") + } + } + + override fun toString(): String { + return "$name -- $input" + } + } + + @Test + @Disabled("CASTS have not yet been implemented.") + fun testCast1() = SuccessTestCase( + input = "1 + 2.0", + expected = int32Value(3), + ).assert() + + @Test + @Disabled("CASTS have not yet been implemented.") + fun testCasts() = SuccessTestCase( + input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2.0, 3.0>> AS t;", + expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300)) + ).assert() + + @Test + @Disabled("We need to support section 5.1") + fun testTypingOfPositionVariable() = TypingTestCase( + name = "PartiQL Spec Section 5.1.1 -- Position variable on bags", + input = "SELECT v, p FROM << 5 >> AS v AT p;", + expectedPermissive = bagValue( + structValue( + "v" to int32Value(5) + ) + ) + ).assert() + + @Test + @Disabled("This is just a placeholder. We should add support for this. Grouping is not yet supported.") + fun test3() = + TypingTestCase( + name = "PartiQL Specification Section 11.1", + input = """ + PLACEHOLDER FOR THE EXAMPLE IN THE RELEVANT SECTION. GROUPING NOT YET SUPPORTED. + """.trimIndent(), + expectedPermissive = missingValue() + ).assert() + + @Test + @Disabled("The planner fails this, though it should pass for permissive mode.") + fun test5() = + TypingTestCase( + name = "PartiQL Specification Section 5.2.1 -- Mistyping Cases", + input = "SELECT v, n FROM UNPIVOT 1 AS v AT n;", + expectedPermissive = bagValue( + structValue( + "v" to int32Value(1), + "n" to stringValue("_1") + ) + ) + ).assert() + + @Test + @Disabled("We don't yet support arrays.") + fun test7() = + TypingTestCase( + name = "PartiQL Specification Section 6.1.4 -- when constructing arrays", + input = "SELECT VALUE [v.a, v.b] FROM [{'a':1, 'b':1}, {'a':2}] AS v;", + expectedPermissive = bagValue( + listValue( + int32Value(1), + int32Value(1), + ), + listValue( + int32Value(2), + missingValue() + ) + ) + ).assert() + + @Test + @Disabled("There is a bug in the planner which makes this always return missing.") + fun test8() = + TypingTestCase( + name = "PartiQL Specification Section 4.2 -- non integer index", + input = "SELECT VALUE [1,2,3][v] FROM <<1, 1.0>> AS v;", + expectedPermissive = bagValue(int32Value(2), missingValue()) + ).assert() + + @Test + @Disabled("CASTs aren't supported yet.") + fun test9() = + TypingTestCase( + name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 27", + input = "SELECT VALUE {'a':3*v.a, 'b':3*(CAST (v.b AS INTEGER))} FROM [{'a':1, 'b':'1'}, {'a':2}] v;", + expectedPermissive = bagValue( + structValue( + "a" to int32Value(3), + "b" to int32Value(3), + ), + structValue( + "a" to int32Value(6), + ), + ) + ).assert() + + @Test + @Disabled("Arrays aren't supported yet.") + fun test10() = + SuccessTestCase( + input = "SELECT v, i FROM [ 'a', 'b', 'c' ] AS v AT i", + expected = bagValue( + structValue( + "v" to stringValue("a"), + "i" to int64Value(0), + ), + structValue( + "v" to stringValue("b"), + "i" to int64Value(1), + ), + structValue( + "v" to stringValue("c"), + "i" to int64Value(2), + ), + ) + ).assert() + + @Test + @Disabled("Support for ORDER BY needs to be added for this to pass.") + // PartiQL Specification says that SQL's SELECT is coerced, but SELECT VALUE is not. + fun selectValueNoCoercion() = + SuccessTestCase( + input = """ + (4, 5) < (SELECT VALUE t.a FROM << { 'a': 3 }, { 'a': 4 } >> AS t ORDER BY t.a) + """.trimIndent(), + expected = boolValue(false) + ).assert() + + @Test + @Disabled("This is appropriately coerced, but this test is failing because LT currently doesn't support LISTS.") + fun rowCoercion() = + SuccessTestCase( + input = """ + (4, 5) < (SELECT t.a, t.a FROM << { 'a': 3 } >> AS t) + """.trimIndent(), + expected = boolValue(false) + ).assert() + + @Test + @Disabled("This broke in its introduction to the codebase on merge. See 5fb9a1ccbc7e630b0df62aa8b161d319c763c1f6.") + // TODO: Add to conformance tests + fun wildCard() = + SuccessTestCase( + input = """ + [ + { 'id':'5', + 'books':[ + { 'title':'A', + 'price':5.0, + 'authors': [{'name': 'John'}, {'name': 'Doe'}] + }, + { 'title':'B', + 'price':2.0, + 'authors': [{'name': 'Zoe'}, {'name': 'Bill'}] + } + ] + }, + { 'id':'6', + 'books':[ + { 'title':'A', + 'price':5.0, + 'authors': [{'name': 'John'}, {'name': 'Doe'}] + }, + { 'title':'E', + 'price':2.0, + 'authors': [{'name': 'Zoe'}, {'name': 'Bill'}] + } + ] + }, + { 'id':7, + 'books':[] + } + ][*].books[*].authors[*].name + """.trimIndent(), + expected = bagValue( + listOf( + stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill"), + stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill") + ) + ) + ).assert() + + @Test + @Disabled("This broke in its introduction to the codebase on merge. See 5fb9a1ccbc7e630b0df62aa8b161d319c763c1f6.") + // TODO: add to conformance tests + // Note that the existing pipeline produced identical result when supplying with + // SELECT VALUE v2.name FROM e as v0, v0.books as v1, unpivot v1.authors as v2; + // But it produces different result when supplying with e[*].books[*].authors.* + // << + // <<{ 'name': 'John'},{'name': 'Doe'} >>, + // ... + // >> + fun unpivot() = + SuccessTestCase( + input = """ + [ + { 'id':'5', + 'books':[ + { 'title':'A', + 'price':5.0, + 'authors': { + 'first': {'name': 'John'}, + 'second': {'name': 'Doe'} + } + }, + { 'title':'B', + 'price':2.0, + 'authors': { + 'first': {'name': 'Zoe'}, + 'second': {'name': 'Bill'} + } + } + ] + }, + { 'id':'6', + 'books':[ + { 'title':'A', + 'price':5.0, + 'authors': { + 'first': {'name': 'John'}, + 'second': {'name': 'Doe'} + } + }, + { 'title':'E', + 'price':2.0, + 'authors': { + 'first': {'name': 'Zoe'}, + 'second': {'name': 'Bill'} + } + } + ] + }, + { 'id':7, + 'books':[] + } + ][*].books[*].authors.*.name + """.trimIndent(), + expected = bagValue( + listOf( + stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill"), + stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill") + ) + ) + ).assert() +} diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 78ab9f5ab2..36ae106ad1 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -240,20 +240,29 @@ rel::{ ], }, - union::{ - lhs: rel, - rhs: rel, - }, + set::[ + union::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + }, - intersect::{ - lhs: rel, - rhs: rel, - }, + intersect::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + }, - except::{ - lhs: rel, - rhs: rel, - }, + except::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + }, + + _::[ + quantifier::[ ALL, DISTINCT ], + ] + ], limit::{ input: rel, 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 db65156fa8..1c5ff36704 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 @@ -25,7 +25,6 @@ import org.partiql.planner.internal.ir.builder.RelOpAggregateBuilder import org.partiql.planner.internal.ir.builder.RelOpAggregateCallBuilder import org.partiql.planner.internal.ir.builder.RelOpDistinctBuilder import org.partiql.planner.internal.ir.builder.RelOpErrBuilder -import org.partiql.planner.internal.ir.builder.RelOpExceptBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeItemBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeStepCollIndexBuilder @@ -33,16 +32,17 @@ import org.partiql.planner.internal.ir.builder.RelOpExcludeStepCollWildcardBuild import org.partiql.planner.internal.ir.builder.RelOpExcludeStepStructFieldBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeStepStructWildcardBuilder import org.partiql.planner.internal.ir.builder.RelOpFilterBuilder -import org.partiql.planner.internal.ir.builder.RelOpIntersectBuilder import org.partiql.planner.internal.ir.builder.RelOpJoinBuilder import org.partiql.planner.internal.ir.builder.RelOpLimitBuilder import org.partiql.planner.internal.ir.builder.RelOpOffsetBuilder import org.partiql.planner.internal.ir.builder.RelOpProjectBuilder import org.partiql.planner.internal.ir.builder.RelOpScanBuilder import org.partiql.planner.internal.ir.builder.RelOpScanIndexedBuilder +import org.partiql.planner.internal.ir.builder.RelOpSetExceptBuilder +import org.partiql.planner.internal.ir.builder.RelOpSetIntersectBuilder +import org.partiql.planner.internal.ir.builder.RelOpSetUnionBuilder import org.partiql.planner.internal.ir.builder.RelOpSortBuilder import org.partiql.planner.internal.ir.builder.RelOpSortSpecBuilder -import org.partiql.planner.internal.ir.builder.RelOpUnionBuilder import org.partiql.planner.internal.ir.builder.RelOpUnpivotBuilder import org.partiql.planner.internal.ir.builder.RelTypeBuilder import org.partiql.planner.internal.ir.builder.RexBuilder @@ -810,9 +810,7 @@ internal data class Rel( is Distinct -> visitor.visitRelOpDistinct(this, ctx) is Filter -> visitor.visitRelOpFilter(this, ctx) is Sort -> visitor.visitRelOpSort(this, ctx) - is Union -> visitor.visitRelOpUnion(this, ctx) - is Intersect -> visitor.visitRelOpIntersect(this, ctx) - is Except -> visitor.visitRelOpExcept(this, ctx) + is Set -> visitor.visitRelOpSet(this, ctx) is Limit -> visitor.visitRelOpLimit(this, ctx) is Offset -> visitor.visitRelOpOffset(this, ctx) is Project -> visitor.visitRelOpProject(this, ctx) @@ -950,61 +948,82 @@ internal data class Rel( internal fun builder(): RelOpSortBuilder = RelOpSortBuilder() } } + internal sealed class Set : Op() { - internal data class Union( - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - ) : Op() { - override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Union -> visitor.visitRelOpSetUnion(this, ctx) + is Intersect -> visitor.visitRelOpSetIntersect(this, ctx) + is Except -> visitor.visitRelOpSetExcept(this, ctx) } - override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpUnion(this, ctx) + internal data class Union( + @JvmField internal val quantifier: Quantifier, + @JvmField internal val lhs: Rel, + @JvmField internal val rhs: Rel, + @JvmField internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } - internal companion object { - @JvmStatic - internal fun builder(): RelOpUnionBuilder = RelOpUnionBuilder() - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetUnion(this, ctx) - internal data class Intersect( - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - ) : Op() { - override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetUnionBuilder = RelOpSetUnionBuilder() + } } - override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpIntersect(this, ctx) + internal data class Intersect( + @JvmField internal val quantifier: Quantifier, + @JvmField internal val lhs: Rel, + @JvmField internal val rhs: Rel, + @JvmField internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } - internal companion object { - @JvmStatic - internal fun builder(): RelOpIntersectBuilder = RelOpIntersectBuilder() - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetIntersect(this, ctx) - internal data class Except( - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - ) : Op() { - override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetIntersectBuilder = RelOpSetIntersectBuilder() + } } - override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpExcept(this, ctx) + internal data class Except( + @JvmField internal val quantifier: Quantifier, + @JvmField internal val lhs: Rel, + @JvmField internal val rhs: Rel, + @JvmField internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } - internal companion object { - @JvmStatic - internal fun builder(): RelOpExceptBuilder = RelOpExceptBuilder() + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetExcept(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetExceptBuilder = RelOpSetExceptBuilder() + } + } + + internal enum class Quantifier { + ALL, DISTINCT } } 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 a814cc7a35..039bca50cc 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 @@ -296,21 +296,29 @@ internal object PlanTransform : PlanBaseVisitor() { } ) - override fun visitRelOpUnion(node: Rel.Op.Union, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Union( + override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Except( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), + quantifier = visitRelOpSetQuantifier(node.quantifier) ) - override fun visitRelOpIntersect(node: Rel.Op.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Intersect( + override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Intersect( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), + quantifier = visitRelOpSetQuantifier(node.quantifier) ) - override fun visitRelOpExcept(node: Rel.Op.Except, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Except( + override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Union( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), + quantifier = visitRelOpSetQuantifier(node.quantifier) ) + private fun visitRelOpSetQuantifier(node: Rel.Op.Set.Quantifier) = when (node) { + Rel.Op.Set.Quantifier.ALL -> org.partiql.plan.Rel.Op.Set.Quantifier.ALL + Rel.Op.Set.Quantifier.DISTINCT -> org.partiql.plan.Rel.Op.Set.Quantifier.DISTINCT + } + override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Limit( input = visitRel(node.input, ctx), limit = visitRex(node.limit, ctx), 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 73cba006fc..71e86921a4 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 @@ -40,7 +40,6 @@ import org.partiql.planner.internal.ir.relOpAggregate import org.partiql.planner.internal.ir.relOpAggregateCall import org.partiql.planner.internal.ir.relOpDistinct import org.partiql.planner.internal.ir.relOpErr -import org.partiql.planner.internal.ir.relOpExcept import org.partiql.planner.internal.ir.relOpExclude import org.partiql.planner.internal.ir.relOpExcludeItem import org.partiql.planner.internal.ir.relOpExcludeStepCollIndex @@ -48,7 +47,6 @@ import org.partiql.planner.internal.ir.relOpExcludeStepCollWildcard import org.partiql.planner.internal.ir.relOpExcludeStepStructField import org.partiql.planner.internal.ir.relOpExcludeStepStructWildcard import org.partiql.planner.internal.ir.relOpFilter -import org.partiql.planner.internal.ir.relOpIntersect import org.partiql.planner.internal.ir.relOpJoin import org.partiql.planner.internal.ir.relOpLimit import org.partiql.planner.internal.ir.relOpOffset @@ -57,7 +55,6 @@ import org.partiql.planner.internal.ir.relOpScan import org.partiql.planner.internal.ir.relOpScanIndexed import org.partiql.planner.internal.ir.relOpSort import org.partiql.planner.internal.ir.relOpSortSpec -import org.partiql.planner.internal.ir.relOpUnion import org.partiql.planner.internal.ir.relOpUnpivot import org.partiql.planner.internal.ir.relType import org.partiql.planner.internal.ir.rex @@ -407,9 +404,6 @@ internal object RelConverter { /** * Append SQL set operator if present - * - * TODO combine/compare schemas - * TODO set quantifier */ private fun convertSetOp(input: Rel, setOp: Expr.SFW.SetOp?): Rel { if (setOp == null) { @@ -418,10 +412,14 @@ internal object RelConverter { val type = input.type.copy(props = emptySet()) val lhs = input val rhs = visitExprSFW(setOp.operand, nil) + val quantifier = when (setOp.type.setq) { + SetQuantifier.ALL -> Rel.Op.Set.Quantifier.ALL + null, SetQuantifier.DISTINCT -> Rel.Op.Set.Quantifier.DISTINCT + } val op = when (setOp.type.type) { - SetOp.Type.UNION -> relOpUnion(lhs, rhs) - SetOp.Type.INTERSECT -> relOpIntersect(lhs, rhs) - SetOp.Type.EXCEPT -> relOpExcept(lhs, rhs) + SetOp.Type.UNION -> Rel.Op.Set.Union(quantifier, lhs, rhs, false) + SetOp.Type.EXCEPT -> Rel.Op.Set.Except(quantifier, lhs, rhs, false) + SetOp.Type.INTERSECT -> Rel.Op.Set.Intersect(quantifier, lhs, rhs, false) } return rel(type, 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 f7501d2768..cd31603e89 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 @@ -19,10 +19,13 @@ package org.partiql.planner.internal.transforms import org.partiql.ast.AstNode import org.partiql.ast.DatetimeField import org.partiql.ast.Expr +import org.partiql.ast.SetOp +import org.partiql.ast.SetQuantifier import org.partiql.ast.Type import org.partiql.ast.visitor.AstBaseVisitor import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Identifier +import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.builder.plan import org.partiql.planner.internal.ir.fnUnresolved @@ -631,6 +634,41 @@ internal object RexConverter { override fun visitExprSFW(node: Expr.SFW, context: Env): Rex = RelConverter.apply(node, context) + override fun visitExprBagOp(node: Expr.BagOp, ctx: Env): Rex { + val lhs = Rel( + type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), + op = Rel.Op.Scan(visitExpr(node.lhs, ctx)) + ) + val rhs = Rel( + type = Rel.Type(listOf(Rel.Binding("_1", StaticType.ANY)), props = emptySet()), + op = Rel.Op.Scan(visitExpr(node.rhs, ctx)) + ) + val quantifier = when (node.type.setq) { + SetQuantifier.ALL -> Rel.Op.Set.Quantifier.ALL + null, SetQuantifier.DISTINCT -> Rel.Op.Set.Quantifier.DISTINCT + } + val isOuter = node.outer == true + val op = when (node.type.type) { + SetOp.Type.UNION -> Rel.Op.Set.Union(quantifier, lhs, rhs, isOuter) + SetOp.Type.EXCEPT -> Rel.Op.Set.Except(quantifier, lhs, rhs, isOuter) + SetOp.Type.INTERSECT -> Rel.Op.Set.Intersect(quantifier, lhs, rhs, isOuter) + } + val rel = Rel( + type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), + op = op + ) + return Rex( + type = StaticType.ANY, + op = Rex.Op.Select( + constructor = Rex( + StaticType.ANY, + Rex.Op.Var.Unresolved(Identifier.Symbol("_0", Identifier.CaseSensitivity.SENSITIVE), Rex.Op.Var.Scope.LOCAL) + ), + rel = rel + ) + ) + } + // Helpers private fun bool(v: Boolean): 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 199caf2027..bff0ef4b04 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 @@ -103,6 +103,7 @@ import org.partiql.value.TextValue import org.partiql.value.boolValue import org.partiql.value.missingValue import org.partiql.value.stringValue +import kotlin.math.max /** * Rewrites an untyped algebraic translation of the query to be both typed and have resolved variables. @@ -241,16 +242,95 @@ internal class PlanTyper( return rel(type, op) } - override fun visitRelOpUnion(node: Rel.Op.Union, ctx: Rel.Type?): Rel { - TODO("Type RelOp Union") + override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: Rel.Type?): Rel { + val lhs = visitRel(node.lhs, node.lhs.type) + val rhs = visitRel(node.rhs, node.rhs.type) + // Check for Compatibility + if (!setOpSchemaSizesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchSizes() + } + if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchTypes() + } + // Compute Schema + val type = Rel.Type(lhs.type.schema, props = emptySet()) + return Rel(type, node.copy(lhs = lhs, rhs = rhs)) + } + + override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: Rel.Type?): Rel { + val lhs = visitRel(node.lhs, node.lhs.type) + val rhs = visitRel(node.rhs, node.rhs.type) + // Check for Compatibility + if (!setOpSchemaSizesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchSizes() + } + if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchTypes() + } + // Compute Schema + val type = Rel.Type(lhs.type.schema, props = emptySet()) + return Rel(type, node.copy(lhs = lhs, rhs = rhs)) + } + + override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: Rel.Type?): Rel { + val lhs = visitRel(node.lhs, node.lhs.type) + val rhs = visitRel(node.rhs, node.rhs.type) + // Check for Compatibility + if (!setOpSchemaSizesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchSizes() + } + if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + return createRelErrForSetOpMismatchTypes() + } + // Compute Schema + val size = max(lhs.type.schema.size, rhs.type.schema.size) + val schema = List(size) { + val lhsBinding = lhs.type.schema.getOrNull(it) ?: Rel.Binding("_$it", MISSING) + val rhsBinding = rhs.type.schema.getOrNull(it) ?: Rel.Binding("_$it", MISSING) + val bindingName = when (lhsBinding.name == rhsBinding.name) { + true -> lhsBinding.name + false -> "_$it" + } + Rel.Binding(bindingName, unionOf(lhsBinding.type, rhsBinding.type)) + } + val type = Rel.Type(schema, props = emptySet()) + return Rel(type, node.copy(lhs = lhs, rhs = rhs)) + } + + /** + * @return whether each type of the [lhs] is equal to its counterpart on the [rhs] + * @param lhs should be typed already + * @param rhs should be typed already + */ + private fun setOpSchemaTypesMatch(lhs: Rel, rhs: Rel): Boolean { + // TODO: [RFC-0007](https://github.com/partiql/partiql-lang/blob/main/RFCs/0007-rfc-bag-operators.md) + // states that the types must be "comparable". The below code ONLY makes sure that types need to be + // the same. In the future, we need to add support for checking comparable types. + for (i in 0..lhs.type.schema.lastIndex) { + val lhsBindingType = lhs.type.schema[i].type + val rhsBindingType = rhs.type.schema[i].type + if (lhsBindingType != rhsBindingType) { + return false + } + } + return true + } + + /** + * @return whether the [lhs] and [rhs] schemas are of equal size + * @param lhs should be typed already + * @param rhs should be typed already + */ + private fun setOpSchemaSizesMatch(lhs: Rel, rhs: Rel): Boolean { + return lhs.type.schema.size == rhs.type.schema.size } - override fun visitRelOpIntersect(node: Rel.Op.Intersect, ctx: Rel.Type?): Rel { - TODO("Type RelOp Intersect") + private fun createRelErrForSetOpMismatchSizes(): Rel { + return Rel(Rel.Type(emptyList(), emptySet()), Rel.Op.Err("LHS and RHS of SET OP do not have the same number of bindings.")) } - override fun visitRelOpExcept(node: Rel.Op.Except, ctx: Rel.Type?): Rel { - TODO("Type RelOp Except") + private fun createRelErrForSetOpMismatchTypes(): Rel { + return Rel(Rel.Type(emptyList(), emptySet()), Rel.Op.Err("LHS and RHS of SET OP do not have the same type.")) } override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: Rel.Type?): Rel { diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 667922ed6a..7ececd8f34 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -263,20 +263,38 @@ rel::{ ], }, - union::{ - lhs: rel, - rhs: rel, - }, - intersect::{ - lhs: rel, - rhs: rel, - }, + // In each variant, is_outer is an internal-only field. It is specifically used to aid in typing the plan and throwing potential errors. + // For example, if a user were to write: `<< { 'a': 1 } >>` UNION << { 'b': 'hello' } >>, then this would FAIL + // due to [RFC-0007](https://github.com/partiql/partiql-lang/blob/main/RFCs/0007-rfc-bag-operators.md). However, + // if a user were to use OUTER UNION, then it would work. Under the hood at execution, the operator is the same -- + // however, at planning time, with static type analysis, we can fail queries prior to their execution. + set::[ + union::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + is_outer: bool + }, - except::{ - lhs: rel, - rhs: rel, - }, + intersect::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + is_outer: bool + }, + + except::{ + quantifier: quantifier, + lhs: rel, + rhs: rel, + is_outer: bool + }, + + _::[ + quantifier::[ ALL, DISTINCT ], + ] + ], limit::{ input: rel, From 35760f77d4a22d11823ca78b738f61ee60ab7701 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Mon, 8 Jul 2024 14:09:30 -0700 Subject: [PATCH 02/10] Delete partiql-eval specific changes --- .../org/partiql/eval/internal/Compiler.kt | 434 ----- .../eval/internal/helpers/IteratorChain.kt | 27 - .../eval/internal/helpers/IteratorPeeking.kt | 36 - .../eval/internal/operator/rel/RelDistinct.kt | 31 - .../internal/operator/rel/RelExceptAll.kt | 53 - .../operator/rel/RelExceptDistinct.kt | 59 - .../eval/internal/operator/rel/RelFilter.kt | 39 - .../internal/operator/rel/RelIntersectAll.kt | 54 - .../operator/rel/RelIntersectDistinct.kt | 49 - .../operator/rel/RelJoinNestedLoop.kt | 98 -- .../eval/internal/operator/rel/RelPeeking.kt | 39 - .../eval/internal/operator/rel/RelUnionAll.kt | 32 - .../internal/operator/rel/RelUnionDistinct.kt | 38 - .../eval/internal/PartiQLEngineDefaultTest.kt | 1515 ----------------- 14 files changed, 2504 deletions(-) delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt delete mode 100644 partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt delete mode 100644 partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt deleted file mode 100644 index ae2950342a..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt +++ /dev/null @@ -1,434 +0,0 @@ -package org.partiql.eval.internal - -import org.partiql.eval.PartiQLEngine -import org.partiql.eval.internal.operator.Operator -import org.partiql.eval.internal.operator.rel.RelAggregate -import org.partiql.eval.internal.operator.rel.RelDistinct -import org.partiql.eval.internal.operator.rel.RelExceptAll -import org.partiql.eval.internal.operator.rel.RelExceptDistinct -import org.partiql.eval.internal.operator.rel.RelExclude -import org.partiql.eval.internal.operator.rel.RelFilter -import org.partiql.eval.internal.operator.rel.RelIntersectAll -import org.partiql.eval.internal.operator.rel.RelIntersectDistinct -import org.partiql.eval.internal.operator.rel.RelJoinInner -import org.partiql.eval.internal.operator.rel.RelJoinLeft -import org.partiql.eval.internal.operator.rel.RelJoinOuterFull -import org.partiql.eval.internal.operator.rel.RelJoinRight -import org.partiql.eval.internal.operator.rel.RelLimit -import org.partiql.eval.internal.operator.rel.RelOffset -import org.partiql.eval.internal.operator.rel.RelProject -import org.partiql.eval.internal.operator.rel.RelScan -import org.partiql.eval.internal.operator.rel.RelScanIndexed -import org.partiql.eval.internal.operator.rel.RelScanIndexedPermissive -import org.partiql.eval.internal.operator.rel.RelScanPermissive -import org.partiql.eval.internal.operator.rel.RelSort -import org.partiql.eval.internal.operator.rel.RelUnionAll -import org.partiql.eval.internal.operator.rel.RelUnionDistinct -import org.partiql.eval.internal.operator.rel.RelUnpivot -import org.partiql.eval.internal.operator.rex.ExprCallDynamic -import org.partiql.eval.internal.operator.rex.ExprCallStatic -import org.partiql.eval.internal.operator.rex.ExprCase -import org.partiql.eval.internal.operator.rex.ExprCast -import org.partiql.eval.internal.operator.rex.ExprCoalesce -import org.partiql.eval.internal.operator.rex.ExprCollection -import org.partiql.eval.internal.operator.rex.ExprLiteral -import org.partiql.eval.internal.operator.rex.ExprMissing -import org.partiql.eval.internal.operator.rex.ExprNullIf -import org.partiql.eval.internal.operator.rex.ExprPathIndex -import org.partiql.eval.internal.operator.rex.ExprPathKey -import org.partiql.eval.internal.operator.rex.ExprPathSymbol -import org.partiql.eval.internal.operator.rex.ExprPermissive -import org.partiql.eval.internal.operator.rex.ExprPivot -import org.partiql.eval.internal.operator.rex.ExprPivotPermissive -import org.partiql.eval.internal.operator.rex.ExprSelect -import org.partiql.eval.internal.operator.rex.ExprStruct -import org.partiql.eval.internal.operator.rex.ExprSubquery -import org.partiql.eval.internal.operator.rex.ExprTupleUnion -import org.partiql.eval.internal.operator.rex.ExprVarLocal -import org.partiql.eval.internal.operator.rex.ExprVarOuter -import org.partiql.plan.Catalog -import org.partiql.plan.PartiQLPlan -import org.partiql.plan.PlanNode -import org.partiql.plan.Ref -import org.partiql.plan.Rel -import org.partiql.plan.Rex -import org.partiql.plan.Statement -import org.partiql.plan.debug.PlanPrinter -import org.partiql.plan.rexOpErr -import org.partiql.plan.visitor.PlanBaseVisitor -import org.partiql.spi.fn.Agg -import org.partiql.spi.fn.FnExperimental -import org.partiql.types.StaticType -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType -import java.lang.IllegalStateException - -internal class Compiler( - private val plan: PartiQLPlan, - private val session: PartiQLEngine.Session, - private val symbols: Symbols, -) : PlanBaseVisitor() { - - fun compile(): Operator.Expr { - return visitPartiQLPlan(plan, null) - } - - override fun defaultReturn(node: PlanNode, ctx: StaticType?): Operator { - TODO("Not yet implemented") - } - - override fun visitRexOpErr(node: Rex.Op.Err, ctx: StaticType?): Operator { - val message = buildString { - this.appendLine(node.message) - PlanPrinter.append(this, plan) - } - throw IllegalStateException(message) - } - - override fun visitRelOpErr(node: Rel.Op.Err, ctx: StaticType?): Operator { - throw IllegalStateException(node.message) - } - - override fun visitPartiQLPlan(node: PartiQLPlan, ctx: StaticType?): Operator.Expr { - return visitStatement(node.statement, ctx) as Operator.Expr - } - - override fun visitStatementQuery(node: Statement.Query, ctx: StaticType?): Operator.Expr { - return visitRex(node.root, ctx).modeHandled() - } - - // REX - - override fun visitRex(node: Rex, ctx: StaticType?): Operator.Expr { - return super.visitRexOp(node.op, node.type) as Operator.Expr - } - - override fun visitRexOpCollection(node: Rex.Op.Collection, ctx: StaticType?): Operator { - val values = node.values.map { visitRex(it, ctx).modeHandled() } - val type = ctx ?: error("No type provided in ctx") - return ExprCollection(values, type) - } - - override fun visitRexOpStruct(node: Rex.Op.Struct, ctx: StaticType?): Operator { - val fields = node.fields.map { - val value = visitRex(it.v, ctx).modeHandled() - ExprStruct.Field(visitRex(it.k, ctx), value) - } - return ExprStruct(fields) - } - - override fun visitRexOpSelect(node: Rex.Op.Select, ctx: StaticType?): Operator { - val rel = visitRel(node.rel, ctx) - val ordered = node.rel.type.props.contains(Rel.Prop.ORDERED) - val constructor = visitRex(node.constructor, ctx).modeHandled() - return ExprSelect(rel, constructor, ordered) - } - - override fun visitRexOpSubquery(node: Rex.Op.Subquery, ctx: StaticType?): Operator { - val constructor = visitRex(node.constructor, ctx) - val input = visitRel(node.rel, ctx) - return when (node.coercion) { - Rex.Op.Subquery.Coercion.SCALAR -> ExprSubquery.Scalar(constructor, input) - Rex.Op.Subquery.Coercion.ROW -> ExprSubquery.Row(constructor, input) - } - } - - override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: StaticType?): Operator { - val rel = visitRel(node.rel, ctx) - val key = visitRex(node.key, ctx) - val value = visitRex(node.value, ctx) - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> ExprPivotPermissive(rel, key, value) - PartiQLEngine.Mode.STRICT -> ExprPivot(rel, key, value) - } - } - - override fun visitRexOpCoalesce(node: Rex.Op.Coalesce, ctx: StaticType?): Operator { - val args = Array(node.args.size) { visitRex(node.args[it], node.args[it].type) } - return ExprCoalesce(args) - } - - override fun visitRexOpNullif(node: Rex.Op.Nullif, ctx: StaticType?): Operator { - val value = visitRex(node.value, node.value.type) - val nullifier = visitRex(node.nullifier, node.value.type) - return ExprNullIf(value, nullifier) - } - - /** - * All variables from the local scope have a depth of 0. - * - * All variables coming from the stack have a depth > 0. To slightly minimize computation at execution, we subtract - * the depth by 1 to account for the fact that the local scope is not kept on the stack. - */ - override fun visitRexOpVar(node: Rex.Op.Var, ctx: StaticType?): Operator { - return when (node.depth) { - 0 -> ExprVarLocal(node.ref) - else -> { - ExprVarOuter(node.depth, node.ref) - } - } - } - - override fun visitRexOpGlobal(node: Rex.Op.Global, ctx: StaticType?): Operator = symbols.getGlobal(node.ref) - - override fun visitRelOpAggregate(node: Rel.Op.Aggregate, ctx: StaticType?): Operator.Relation { - val input = visitRel(node.input, ctx) - val calls = node.calls.map { - visitRelOpAggregateCall(it, ctx) - } - val groups = node.groups.map { visitRex(it, ctx).modeHandled() } - return RelAggregate(input, groups, calls) - } - - @OptIn(FnExperimental::class) - override fun visitRelOpAggregateCall(node: Rel.Op.Aggregate.Call, ctx: StaticType?): Operator.Aggregation { - val args = node.args.map { visitRex(it, it.type).modeHandled() } - val setQuantifier: Operator.Aggregation.SetQuantifier = when (node.setQuantifier) { - Rel.Op.Aggregate.Call.SetQuantifier.ALL -> Operator.Aggregation.SetQuantifier.ALL - Rel.Op.Aggregate.Call.SetQuantifier.DISTINCT -> Operator.Aggregation.SetQuantifier.DISTINCT - } - val agg = symbols.getAgg(node.agg) - return object : Operator.Aggregation { - override val delegate: Agg = agg - override val args: List = args - override val setQuantifier: Operator.Aggregation.SetQuantifier = setQuantifier - } - } - - override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Operator { - val root = visitRex(node.root, ctx) - val key = visitRex(node.key, ctx) - return ExprPathKey(root, key) - } - - override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: StaticType?): Operator { - val root = visitRex(node.root, ctx) - val symbol = node.key - return ExprPathSymbol(root, symbol) - } - - override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: StaticType?): Operator { - val root = visitRex(node.root, ctx) - val index = visitRex(node.key, ctx) - return ExprPathIndex(root, index) - } - - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) - override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: StaticType?): Operator { - val fn = symbols.getFn(node.fn) - val args = node.args.map { visitRex(it, ctx) }.toTypedArray() - val fnTakesInMissing = fn.signature.parameters.any { - it.type == PartiQLValueType.MISSING || it.type == PartiQLValueType.ANY - } - return when (fnTakesInMissing) { - true -> ExprCallStatic(fn, args.map { it.modeHandled() }.toTypedArray()) - false -> ExprCallStatic(fn, args) - } - } - - @OptIn(FnExperimental::class) - override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: StaticType?): Operator { - val args = node.args.map { visitRex(it, ctx).modeHandled() }.toTypedArray() - // Check candidate list size - when (node.candidates.size) { - 0 -> error("Rex.Op.Call.Dynamic had an empty candidates list: $node.") - // TODO this seems like it should be an error, but is possible if the fn match was non-exhaustive - // 1 -> error("Rex.Op.Call.Dynamic had a single candidate; should be Rex.Op.Call.Static") - } - // Check candidate name and arity for uniformity - var arity: Int = -1 - var name: String = "unknown" - // Compile the candidates - val candidates = Array(node.candidates.size) { - val candidate = node.candidates[it] - val fn = symbols.getFn(candidate.fn) - val coercions = candidate.coercions.toTypedArray() - // Check this candidate - val fnArity = fn.signature.parameters.size - val fnName = fn.signature.name.uppercase() - if (arity == -1) { - arity = fnArity - name = fnName - } else { - if (fnArity != arity) { - error("Dynamic call candidate had different arity than others; found $fnArity but expected $arity") - } - if (fnName != name) { - error("Dynamic call candidate had different name than others; found $fnName but expected $name") - } - } - ExprCallDynamic.Candidate(fn, coercions) - } - return ExprCallDynamic(name, candidates, args) - } - - override fun visitRexOpCast(node: Rex.Op.Cast, ctx: StaticType?): Operator { - return ExprCast(visitRex(node.arg, ctx), node.cast) - } - - override fun visitRexOpMissing(node: Rex.Op.Missing, ctx: StaticType?): Operator { - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> { - // Make a runtime TypeCheckException. - ExprMissing(node.message) - } - PartiQLEngine.Mode.STRICT -> { - // Promote to error. - visitRexOpErr(rexOpErr(node.message, node.causes), null) - } - } - } - - // REL - override fun visitRel(node: Rel, ctx: StaticType?): Operator.Relation { - return super.visitRelOp(node.op, ctx) as Operator.Relation - } - - override fun visitRelOpScan(node: Rel.Op.Scan, ctx: StaticType?): Operator { - val rex = visitRex(node.rex, ctx) - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> RelScanPermissive(rex) - PartiQLEngine.Mode.STRICT -> RelScan(rex) - } - } - - override fun visitRelOpProject(node: Rel.Op.Project, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - val projections = node.projections.map { visitRex(it, ctx).modeHandled() } - return RelProject(input, projections) - } - - override fun visitRelOpScanIndexed(node: Rel.Op.ScanIndexed, ctx: StaticType?): Operator { - val rex = visitRex(node.rex, ctx) - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> RelScanIndexedPermissive(rex) - PartiQLEngine.Mode.STRICT -> RelScanIndexed(rex) - } - } - - override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: StaticType?): Operator { - val expr = visitRex(node.rex, ctx) - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> RelUnpivot.Permissive(expr) - PartiQLEngine.Mode.STRICT -> RelUnpivot.Strict(expr) - } - } - - override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: StaticType?): Operator { - val lhs = visitRel(node.lhs, ctx) - val rhs = visitRel(node.rhs, ctx) - return when (node.quantifier) { - Rel.Op.Set.Quantifier.ALL -> RelExceptAll(lhs, rhs) - Rel.Op.Set.Quantifier.DISTINCT -> RelExceptDistinct(lhs, rhs) - } - } - - override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: StaticType?): Operator { - val lhs = visitRel(node.lhs, ctx) - val rhs = visitRel(node.rhs, ctx) - return when (node.quantifier) { - Rel.Op.Set.Quantifier.ALL -> RelIntersectAll(lhs, rhs) - Rel.Op.Set.Quantifier.DISTINCT -> RelIntersectDistinct(lhs, rhs) - } - } - - override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: StaticType?): Operator { - val lhs = visitRel(node.lhs, ctx) - val rhs = visitRel(node.rhs, ctx) - return when (node.quantifier) { - Rel.Op.Set.Quantifier.ALL -> RelUnionAll(lhs, rhs) - Rel.Op.Set.Quantifier.DISTINCT -> RelUnionDistinct(lhs, rhs) - } - } - - override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - val limit = visitRex(node.limit, ctx) - return RelLimit(input, limit) - } - - override fun visitRelOpOffset(node: Rel.Op.Offset, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - val offset = visitRex(node.offset, ctx) - return RelOffset(input, offset) - } - - override fun visitRexOpTupleUnion(node: Rex.Op.TupleUnion, ctx: StaticType?): Operator { - val args = node.args.map { visitRex(it, ctx) }.toTypedArray() - return ExprTupleUnion(args) - } - - override fun visitRelOpJoin(node: Rel.Op.Join, ctx: StaticType?): Operator { - val lhs = visitRel(node.lhs, ctx) - val rhs = visitRel(node.rhs, ctx) - val condition = visitRex(node.rex, ctx) - return when (node.type) { - Rel.Op.Join.Type.INNER -> RelJoinInner(lhs, rhs, condition) - Rel.Op.Join.Type.LEFT -> RelJoinLeft(lhs, rhs, condition) - Rel.Op.Join.Type.RIGHT -> RelJoinRight(lhs, rhs, condition) - Rel.Op.Join.Type.FULL -> RelJoinOuterFull(lhs, rhs, condition) - } - } - - override fun visitRexOpCase(node: Rex.Op.Case, ctx: StaticType?): Operator { - val branches = node.branches.map { branch -> - visitRex(branch.condition, ctx) to visitRex(branch.rex, ctx) - } - val default = visitRex(node.default, ctx) - return ExprCase(branches, default) - } - - @OptIn(PartiQLValueExperimental::class) - override fun visitRexOpLit(node: Rex.Op.Lit, ctx: StaticType?): Operator { - return ExprLiteral(node.value) - } - - override fun visitRelOpDistinct(node: Rel.Op.Distinct, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - return RelDistinct(input) - } - - override fun visitRelOpFilter(node: Rel.Op.Filter, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - val condition = visitRex(node.predicate, ctx).modeHandled() - return RelFilter(input, condition) - } - - override fun visitRelOpExclude(node: Rel.Op.Exclude, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - return RelExclude(input, node.paths) - } - - override fun visitRelOpSort(node: Rel.Op.Sort, ctx: StaticType?): Operator { - val input = visitRel(node.input, ctx) - val compiledSpecs = node.specs.map { spec -> - val expr = visitRex(spec.rex, ctx) - val order = spec.order - expr to order - } - return RelSort(input, compiledSpecs) - } - - // HELPERS - - private fun Operator.Expr.modeHandled(): Operator.Expr { - return when (session.mode) { - PartiQLEngine.Mode.PERMISSIVE -> ExprPermissive(this) - PartiQLEngine.Mode.STRICT -> this - } - } - - /** - * Get a typed catalog item from a reference - * - * @param T - * @return - */ - private inline fun Ref.get(): T { - val item = plan.catalogs.getOrNull(catalog)?.items?.get(symbol) - if (item == null || item !is T) { - error("Invalid catalog reference, $this for type ${T::class}") - } - return item - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt deleted file mode 100644 index 55a2c832e8..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorChain.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.partiql.eval.internal.helpers - -internal class IteratorChain( - iterators: Array> -) : IteratorPeeking() { - - private var iterator: Iterator> = when (iterators.isEmpty()) { - true -> listOf(emptyList().iterator()).iterator() - false -> iterators.iterator() - } - private var current: Iterator = iterator.next() - - override fun peek(): T? { - return when (current.hasNext()) { - true -> current.next() - false -> { - while (iterator.hasNext()) { - current = iterator.next() - if (current.hasNext()) { - return current.next() - } - } - return null - } - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt deleted file mode 100644 index 55f6c8fd2b..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/IteratorPeeking.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.partiql.eval.internal.helpers - -/** - * For [Iterator]s that MUST materialize data in order to execute [hasNext], this abstract class caches the - * result of [peek] to implement both [hasNext] and [next]. - * - * With this implementation, invoking hasNext() multiple times will not iterate unnecessarily. Invoking next() without - * invoking hasNext() is allowed -- however, it is highly recommended to avoid doing so. - */ -internal abstract class IteratorPeeking : Iterator { - - internal var next: T? = null - - /** - * @return NULL when there is not another [T] to be produced. Returns a [T] when able to. - * - * @see IteratorPeeking - */ - abstract fun peek(): T? - - override fun hasNext(): Boolean { - if (next != null) { - return true - } - this.next = peek() - return this.next != null - } - - override fun next(): T { - val next = next - ?: peek() - ?: error("There were no more elements, however, next() was called. Please use hasNext() beforehand.") - this.next = null - return next - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt deleted file mode 100644 index 15dcc1ba5a..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelDistinct.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelDistinct( - val input: Operator.Relation -) : RelPeeking() { - - private val seen = mutableSetOf() - - override fun openPeeking(env: Environment) { - input.open(env) - } - - override fun peek(): Record? { - for (next in input) { - if (seen.contains(next).not()) { - seen.add(next) - return next - } - } - return null - } - - override fun closePeeking() { - seen.clear() - input.close() - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt deleted file mode 100644 index f62a433b32..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptAll.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelExceptAll( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : RelPeeking() { - - private val seen: MutableMap = mutableMapOf() - private var init: Boolean = false - - override fun openPeeking(env: Environment) { - lhs.open(env) - rhs.open(env) - init = false - seen.clear() - } - - override fun peek(): Record? { - if (!init) { - seed() - } - for (row in lhs) { - val remaining = seen[row] ?: 0 - if (remaining > 0) { - seen[row] = remaining - 1 - continue - } - return row - } - return null - } - - override fun closePeeking() { - lhs.close() - rhs.close() - seen.clear() - } - - /** - * Read the entire right-hand-side into our search structure. - */ - private fun seed() { - init = true - for (row in rhs) { - val n = seen[row] ?: 0 - seen[row] = n + 1 - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt deleted file mode 100644 index 9874aabaea..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelExceptDistinct.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -/** - * Non-communicative, this performs better when [lhs] is larger than [rhs]. - * - * @property lhs - * @property rhs - */ -internal class RelExceptDistinct( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : RelPeeking() { - - private var seen: MutableSet = mutableSetOf() - private var init: Boolean = false - - override fun openPeeking(env: Environment) { - lhs.open(env) - rhs.open(env) - init = false - seen = mutableSetOf() - } - - override fun peek(): Record? { - if (!init) { - seed() - } - for (row in lhs) { - if (!seen.contains(row)) { - return row - } - } - return null - } - - override fun closePeeking() { - lhs.close() - rhs.close() - seen.clear() - } - - /** - * Read the entire right-hand-side into our search structure. - */ - private fun seed() { - init = true - while (true) { - if (rhs.hasNext().not()) { - break - } - val row = rhs.next() - seen.add(row) - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt deleted file mode 100644 index 2081f14d58..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelFilter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator -import org.partiql.value.BoolValue -import org.partiql.value.PartiQLValueExperimental - -internal class RelFilter( - val input: Operator.Relation, - val expr: Operator.Expr -) : RelPeeking() { - - private lateinit var env: Environment - - override fun openPeeking(env: Environment) { - this.env = env - input.open(env) - } - - override fun peek(): Record? { - for (inputRecord in input) { - if (conditionIsTrue(inputRecord, expr)) { - return inputRecord - } - } - return null - } - - override fun closePeeking() { - input.close() - } - - @OptIn(PartiQLValueExperimental::class) - private fun conditionIsTrue(record: Record, expr: Operator.Expr): Boolean { - val condition = expr.eval(env.push(record)) - return condition is BoolValue && condition.value == true - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt deleted file mode 100644 index 33f2ba869a..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectAll.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelIntersectAll( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : RelPeeking() { - - private val seen: MutableMap = mutableMapOf() - private var init: Boolean = false - - override fun openPeeking(env: Environment) { - lhs.open(env) - rhs.open(env) - init = false - seen.clear() - } - - override fun peek(): Record? { - if (!init) { - seed() - } - for (row in rhs) { - seen.computeIfPresent(row) { _, y -> - when (y) { - 0 -> null - else -> y - 1 - } - }?.let { return row } - } - return null - } - - override fun closePeeking() { - lhs.close() - rhs.close() - seen.clear() - } - - /** - * Read the entire left-hand-side into our search structure. - */ - private fun seed() { - init = true - for (row in lhs) { - seen.computeIfPresent(row) { _, y -> - y + 1 - } ?: seen.put(row, 1) - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt deleted file mode 100644 index 2700924104..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelIntersectDistinct.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelIntersectDistinct( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : RelPeeking() { - - private val seen: MutableSet = mutableSetOf() - private var init: Boolean = false - - override fun openPeeking(env: Environment) { - lhs.open(env) - rhs.open(env) - init = false - seen.clear() - } - - override fun peek(): Record? { - if (!init) { - seed() - } - for (row in rhs) { - if (seen.remove(row)) { - return row - } - } - return null - } - - override fun closePeeking() { - lhs.close() - rhs.close() - seen.clear() - } - - /** - * Read the entire left-hand-side into our search structure. - */ - private fun seed() { - init = true - for (row in lhs) { - seen.add(row) - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt deleted file mode 100644 index cb39e48188..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator -import org.partiql.value.BoolValue -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.StructValue -import org.partiql.value.nullValue -import org.partiql.value.structValue - -internal abstract class RelJoinNestedLoop : RelPeeking() { - - abstract val lhs: Operator.Relation - abstract val rhs: Operator.Relation - abstract val condition: Operator.Expr - - private var lhsRecord: Record? = null - private lateinit var env: Environment - - override fun openPeeking(env: Environment) { - this.env = env - lhs.open(env) - if (lhs.hasNext().not()) { - return - } - lhsRecord = lhs.next() - rhs.open(env.push(lhsRecord!!)) - } - - abstract fun join(condition: Boolean, lhs: Record, rhs: Record): Record? - - @OptIn(PartiQLValueExperimental::class) - override fun peek(): Record? { - if (lhsRecord == null) { - return null - } - var rhsRecord = when (rhs.hasNext()) { - true -> rhs.next() - false -> null - } - var toReturn: Record? = null - do { - // Acquire LHS and RHS Records - if (rhsRecord == null) { - rhs.close() - if (lhs.hasNext().not()) { - return null - } - lhsRecord = lhs.next() - rhs.open(env.push(lhsRecord!!)) - rhsRecord = when (rhs.hasNext()) { - true -> rhs.next() - false -> null - } - } - // Return Joined Record - if (rhsRecord != null && lhsRecord != null) { - val input = lhsRecord!! + rhsRecord - val result = condition.eval(env.push(input)) - toReturn = join(result.isTrue(), lhsRecord!!, rhsRecord) - } - // Move the pointer to the next row for the RHS - if (toReturn == null) rhsRecord = if (rhs.hasNext()) rhs.next() else null - } - while (toReturn == null) - return toReturn - } - - override fun closePeeking() { - lhs.close() - rhs.close() - } - - @OptIn(PartiQLValueExperimental::class) - private fun PartiQLValue.isTrue(): Boolean { - return this is BoolValue && this.value == true - } - - @OptIn(PartiQLValueExperimental::class) - internal fun Record.padNull() { - this.values.indices.forEach { index -> - this.values[index] = values[index].padNull() - } - } - - @OptIn(PartiQLValueExperimental::class) - private fun PartiQLValue.padNull(): PartiQLValue { - return when (this) { - is StructValue<*> -> { - val newFields = this.entries.map { it.first to nullValue() } - structValue(newFields) - } - else -> nullValue() - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt deleted file mode 100644 index 6206b783bf..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelPeeking.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.helpers.IteratorPeeking -import org.partiql.eval.internal.operator.Operator - -/** - * For [Operator.Relation]'s that MUST materialize data in order to execute [hasNext], this abstract class caches the - * result of [peek] to implement both [hasNext] and [next]. - */ -internal abstract class RelPeeking : Operator.Relation, IteratorPeeking() { - - /** - * This shall have the same functionality as [open]. Implementers of [RelPeeking] shall not override [open]. - */ - abstract fun openPeeking(env: Environment) - - /** - * This shall have the same functionality as [close]. Implementers of [RelPeeking] shall not override [close]. - */ - abstract fun closePeeking() - - /** - * Implementers shall not override this method. - */ - override fun open(env: Environment) { - next = null - openPeeking(env) - } - - /** - * Implementers shall not override this method. - */ - override fun close() { - next = null - closePeeking() - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt deleted file mode 100644 index 663abadd23..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionAll.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelUnionAll( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : Operator.Relation { - - override fun open(env: Environment) { - lhs.open(env) - rhs.open(env) - } - - override fun hasNext(): Boolean { - return lhs.hasNext() || rhs.hasNext() - } - - override fun next(): Record { - return when (lhs.hasNext()) { - true -> lhs.next() - false -> rhs.next() - } - } - - override fun close() { - lhs.close() - rhs.close() - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt deleted file mode 100644 index bafa6cb28e..0000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelUnionDistinct.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.helpers.IteratorChain -import org.partiql.eval.internal.operator.Operator - -internal class RelUnionDistinct( - private val lhs: Operator.Relation, - private val rhs: Operator.Relation, -) : RelPeeking() { - - private val seen: MutableSet = mutableSetOf() - private lateinit var input: Iterator - - override fun openPeeking(env: Environment) { - lhs.open(env) - rhs.open(env) - seen.clear() - input = IteratorChain(arrayOf(lhs, rhs)) - } - - override fun peek(): Record? { - for (record in input) { - if (!seen.contains(record)) { - seen.add(record) - return record - } - } - return null - } - - override fun closePeeking() { - lhs.close() - rhs.close() - seen.clear() - } -} 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 deleted file mode 100644 index c294c4b255..0000000000 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ /dev/null @@ -1,1515 +0,0 @@ -package org.partiql.eval.internal - -import com.amazon.ionelement.api.createIonElementLoader -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -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.eval.PartiQLEngine -import org.partiql.eval.PartiQLResult -import org.partiql.eval.internal.PartiQLEngineDefaultTest.SuccessTestCase.Global -import org.partiql.parser.PartiQLParser -import org.partiql.plan.PartiQLPlan -import org.partiql.plan.debug.PlanPrinter -import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.PartiQLPlannerBuilder -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.spi.connector.ConnectorSession -import org.partiql.types.StaticType -import org.partiql.value.CollectionValue -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.bagValue -import org.partiql.value.boolValue -import org.partiql.value.decimalValue -import org.partiql.value.int32Value -import org.partiql.value.int64Value -import org.partiql.value.intValue -import org.partiql.value.io.PartiQLValueIonWriterBuilder -import org.partiql.value.listValue -import org.partiql.value.missingValue -import org.partiql.value.nullValue -import org.partiql.value.stringValue -import org.partiql.value.structValue -import java.io.ByteArrayOutputStream -import java.math.BigDecimal -import java.math.BigInteger -import kotlin.test.assertNotNull - -/** - * This holds sanity tests during the development of the [PartiQLEngine.default] implementation. - */ -@OptIn(PartiQLValueExperimental::class) -class PartiQLEngineDefaultTest { - - @ParameterizedTest - @MethodSource("sanityTestsCases") - @Execution(ExecutionMode.CONCURRENT) - fun sanityTests(tc: SuccessTestCase) = tc.assert() - - @ParameterizedTest - @MethodSource("typingModeTestCases") - @Execution(ExecutionMode.CONCURRENT) - fun typingModeTests(tc: TypingTestCase) = tc.assert() - - @ParameterizedTest - @MethodSource("subqueryTestCases") - @Execution(ExecutionMode.CONCURRENT) - fun subqueryTests(tc: SuccessTestCase) = tc.assert() - - @ParameterizedTest - @MethodSource("aggregationTestCases") - @Execution(ExecutionMode.CONCURRENT) - fun aggregationTests(tc: SuccessTestCase) = tc.assert() - - @ParameterizedTest - @MethodSource("globalsTestCases") - @Execution(ExecutionMode.CONCURRENT) - fun globalsTests(tc: SuccessTestCase) = tc.assert() - - companion object { - - @JvmStatic - fun globalsTestCases() = listOf( - SuccessTestCase( - input = """ - SELECT VALUE t.a - FROM t; - """.trimIndent(), - expected = bagValue( - intValue(BigInteger.valueOf(1)), - intValue(BigInteger.valueOf(2)), - ), - globals = listOf( - SuccessTestCase.Global( - name = "t", - value = """ - [ - { "a": 1 }, - { "a": 2 } - ] - """ - ) - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE t1.a - FROM t AS t1, t AS t2; - """.trimIndent(), - expected = bagValue( - intValue(BigInteger.valueOf(1)), - intValue(BigInteger.valueOf(1)), - intValue(BigInteger.valueOf(2)), - intValue(BigInteger.valueOf(2)), - ), - globals = listOf( - SuccessTestCase.Global( - name = "t", - value = """ - [ - { "a": 1 }, - { "a": 2 } - ] - """ - ) - ) - ), - SuccessTestCase( - input = """ - SELECT o.name AS orderName, - (SELECT c.name FROM customers c WHERE c.id=o.custId) AS customerName - FROM orders o - """.trimIndent(), - expected = bagValue( - structValue( - "orderName" to stringValue("foo") - ), - structValue( - "orderName" to stringValue("bar"), - "customerName" to stringValue("Helen") - ), - ), - globals = listOf( - SuccessTestCase.Global( - name = "customers", - value = """ - [{id:1, name: "Mary"}, - {id:2, name: "Helen"}, - {id:1, name: "John"} - ] - """ - ), - SuccessTestCase.Global( - name = "orders", - value = """ - [{custId:1, name: "foo"}, - {custId:2, name: "bar"} - ] - """ - ), - ) - ), - ) - - @JvmStatic - fun subqueryTestCases() = listOf( - SuccessTestCase( - input = """ - SELECT VALUE ( - SELECT VALUE t1 + t2 - FROM <<5, 6>> AS t2 - ) FROM <<0, 10>> AS t1; - """.trimIndent(), - expected = bagValue( - bagValue(int32Value(5), int32Value(6)), - bagValue(int32Value(15), int32Value(16)) - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE ( - SELECT t1 + t2 - FROM <<5>> AS t2 - ) FROM <<0, 10>> AS t1; - """.trimIndent(), - expected = bagValue(int32Value(5), int32Value(15)) - ), - SuccessTestCase( - input = """ - SELECT ( - SELECT VALUE t1 + t2 - FROM <<5>> AS t2 - ) AS t1_plus_t2 - FROM <<0, 10>> AS t1; - """.trimIndent(), - expected = bagValue( - structValue("t1_plus_t2" to bagValue(int32Value(5))), - structValue("t1_plus_t2" to bagValue(int32Value(15))) - ) - ), - SuccessTestCase( - input = """ - SELECT - ( - SELECT (t1 + t2) * ( - SELECT t1 + t3 + t2 - FROM <<7>> AS t3 - ) - FROM <<5>> AS t2 - ) AS t1_plus_t2 - FROM <<0, 10>> AS t1; - """.trimIndent(), - expected = bagValue( - structValue("t1_plus_t2" to int32Value(60)), - structValue("t1_plus_t2" to int32Value(330)) - ) - ), - SuccessTestCase( - input = """ - 1 + (SELECT t.a FROM << { 'a': 3 } >> AS t) - """.trimIndent(), - expected = int32Value(4) - ), - SuccessTestCase( - input = """ - SELECT VALUE element - FROM << { 'a': [0, 1, 2] }, { 'a': [3, 4, 5] } >> AS t, t.a AS element - """.trimIndent(), - expected = bagValue( - int32Value(0), - int32Value(1), - int32Value(2), - int32Value(3), - int32Value(4), - int32Value(5), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE element - FROM << { 'a': { 'c': [0, 1, 2] } }, { 'a': { 'c': [3, 4, 5] } } >> AS t, t.a AS b, b.c AS element - """.trimIndent(), - expected = bagValue( - int32Value(0), - int32Value(1), - int32Value(2), - int32Value(3), - int32Value(4), - int32Value(5), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE t_a_b + t_a_c - FROM << { 'a': { 'b': [100, 200], 'c': [0, 1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4, 5] } } >> - AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c - """.trimIndent(), - expected = bagValue( - int32Value(100), - int32Value(101), - int32Value(102), - int32Value(200), - int32Value(201), - int32Value(202), - int32Value(303), - int32Value(304), - int32Value(305), - int32Value(403), - int32Value(404), - int32Value(405), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE t_a_b + t_a_c + t_a_c_original - FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> - AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c, t.a.c AS t_a_c_original - """.trimIndent(), - expected = bagValue( - int32Value(102), - int32Value(103), - int32Value(103), - int32Value(104), - int32Value(202), - int32Value(203), - int32Value(203), - int32Value(204), - int32Value(306), - int32Value(307), - int32Value(307), - int32Value(308), - int32Value(406), - int32Value(407), - int32Value(407), - int32Value(408), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE t_a_b + t_a_c + t_a_c_original - FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> - AS t, t.a AS t_a, t_a.b AS t_a_b, t_a.c AS t_a_c, (SELECT VALUE d FROM t.a.c AS d) AS t_a_c_original - """.trimIndent(), - expected = bagValue( - int32Value(102), - int32Value(103), - int32Value(103), - int32Value(104), - int32Value(202), - int32Value(203), - int32Value(203), - int32Value(204), - int32Value(306), - int32Value(307), - int32Value(307), - int32Value(308), - int32Value(406), - int32Value(407), - int32Value(407), - int32Value(408), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE t_a_b + t_a_c + t_a_c_original - FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> - AS t, - t.a AS t_a, - t_a.b AS t_a_b, - t_a.c AS t_a_c, - (SELECT VALUE d + (SELECT b_og FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original - """.trimIndent(), - expected = bagValue( - int32Value(302), - int32Value(303), - int32Value(303), - int32Value(304), - int32Value(402), - int32Value(403), - int32Value(403), - int32Value(404), - int32Value(706), - int32Value(707), - int32Value(707), - int32Value(708), - int32Value(806), - int32Value(807), - int32Value(807), - int32Value(808), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE - t_a_b + t_a_c + t_a_c_original + ( - SELECT t_a_c_inner - FROM t.a.c AS t_a_c_inner - WHERE t_a_c_inner = 2 OR t_a_c_inner = 4 - ) - FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> - AS t, - t.a AS t_a, - t_a.b AS t_a_b, - t_a.c AS t_a_c, - (SELECT VALUE d + (SELECT b_og FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original - """.trimIndent(), - expected = bagValue( - int32Value(304), - int32Value(305), - int32Value(305), - int32Value(306), - int32Value(404), - int32Value(405), - int32Value(405), - int32Value(406), - int32Value(710), - int32Value(711), - int32Value(711), - int32Value(712), - int32Value(810), - int32Value(811), - int32Value(811), - int32Value(812), - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE - t_a_b + t_a_c + t_a_c_original + ( - SELECT t_a_c_inner + t_a_c - FROM t.a.c AS t_a_c_inner - WHERE t_a_c_inner = 2 OR t_a_c_inner = 4 - ) - FROM << { 'a': { 'b': [100, 200], 'c': [1, 2] } }, { 'a': { 'b': [300, 400], 'c': [3, 4] } } >> - AS t, - t.a AS t_a, - t_a.b AS t_a_b, - t_a.c AS t_a_c, - (SELECT VALUE d + (SELECT b_og + t_a_c FROM t.a.b AS b_og WHERE b_og = 200 OR b_og = 400) FROM t.a.c AS d) AS t_a_c_original - """.trimIndent(), - expected = bagValue( - int32Value(306), - int32Value(307), - int32Value(309), - int32Value(310), - int32Value(406), - int32Value(407), - int32Value(409), - int32Value(410), - int32Value(716), - int32Value(717), - int32Value(719), - int32Value(720), - int32Value(816), - int32Value(817), - int32Value(819), - int32Value(820), - ) - ) - ) - - @JvmStatic - fun aggregationTestCases() = kotlin.collections.listOf( - SuccessTestCase( - input = """ - SELECT - gk_0, SUM(t.c) AS t_c_sum - FROM << - { 'b': NULL, 'c': 1 }, - { 'b': MISSING, 'c': 2 }, - { 'b': 1, 'c': 1 }, - { 'b': 1, 'c': 2 }, - { 'b': 2, 'c': NULL }, - { 'b': 2, 'c': 2 }, - { 'b': 3, 'c': MISSING }, - { 'b': 3, 'c': 2 }, - { 'b': 4, 'c': MISSING }, - { 'b': 4, 'c': NULL } - >> AS t GROUP BY t.b AS gk_0; - """.trimIndent(), - expected = org.partiql.value.bagValue( - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(1), - "t_c_sum" to org.partiql.value.int32Value(3) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(2), - "t_c_sum" to org.partiql.value.int32Value(2) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(3), - "t_c_sum" to org.partiql.value.int32Value(2) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(4), - "t_c_sum" to org.partiql.value.int32Value(null) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.nullValue(), - "t_c_sum" to org.partiql.value.int32Value(3) - ), - ), - mode = org.partiql.eval.PartiQLEngine.Mode.PERMISSIVE - ), - SuccessTestCase( - input = """ - SELECT VALUE { 'sensor': sensor, - 'readings': (SELECT VALUE v.l.co FROM g AS v) - } - FROM [{'sensor':1, 'co':0.4}, {'sensor':1, 'co':0.2}, {'sensor':2, 'co':0.3}] AS l - GROUP BY l.sensor AS sensor GROUP AS g - """.trimIndent(), - expected = org.partiql.value.bagValue( - org.partiql.value.structValue( - "sensor" to org.partiql.value.int32Value(1), - "readings" to org.partiql.value.bagValue( - org.partiql.value.decimalValue(0.4.toBigDecimal()), - org.partiql.value.decimalValue(0.2.toBigDecimal()) - ) - ), - org.partiql.value.structValue( - "sensor" to org.partiql.value.int32Value(2), - "readings" to org.partiql.value.bagValue( - org.partiql.value.decimalValue(0.3.toBigDecimal()) - ) - ), - ) - ), - SuccessTestCase( - input = """ - SELECT col1, g - FROM [{ 'col1':1 }, { 'col1':1 }] simple_1_col_1_group - GROUP BY col1 GROUP AS g - """.trimIndent(), - expected = bagValue( - structValue( - "col1" to int32Value(1), - "g" to bagValue( - structValue( - "simple_1_col_1_group" to structValue("col1" to int32Value(1)) - ), - structValue( - "simple_1_col_1_group" to structValue("col1" to int32Value(1)) - ), - ) - ), - ) - ), - SuccessTestCase( - input = """ - SELECT p.supplierId_mixed - FROM [ - { 'productId': 5, 'categoryId': 21, 'regionId': 100, 'supplierId_nulls': null, 'price_nulls': null }, - { 'productId': 4, 'categoryId': 20, 'regionId': 100, 'supplierId_nulls': null, 'supplierId_mixed': null, 'price_nulls': null, 'price_mixed': null } - ] AS p - GROUP BY p.supplierId_mixed - """.trimIndent(), - expected = bagValue( - structValue( - "supplierId_mixed" to nullValue(), - ), - ) - ), - SuccessTestCase( - input = """ - SELECT * - FROM << { 'a': 1, 'b': 2 } >> AS t - GROUP BY a, b, a + b GROUP AS g - """.trimIndent(), - expected = bagValue( - structValue( - "a" to int32Value(1), - "b" to int32Value(2), - "_3" to int32Value(3), - "g" to bagValue( - structValue( - "t" to structValue( - "a" to int32Value(1), - "b" to int32Value(2), - ) - ) - ), - ), - ) - ), - ) - - @JvmStatic - fun sanityTestsCases() = listOf( - SuccessTestCase( - input = "SELECT VALUE 1 FROM <<0, 1>>;", - expected = bagValue(int32Value(1), int32Value(1)) - ), - SuccessTestCase( - input = "SELECT VALUE t FROM <<10, 20, 30>> AS t;", - expected = bagValue(int32Value(10), int32Value(20), int32Value(30)) - ), - SuccessTestCase( - input = "SELECT VALUE t FROM <> AS t WHERE t;", - expected = bagValue(boolValue(true), boolValue(true)) - ), - SuccessTestCase( - input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t, << { 'b': 2 } >> s;", - expected = bagValue(structValue("a" to int32Value(1), "b" to int32Value(2))) - ), - SuccessTestCase( - input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t LEFT JOIN << { 'b': 2 } >> s ON false;", - expected = bagValue(structValue("a" to int32Value(1), "b" to nullValue())) - ), - SuccessTestCase( - input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON false;", - expected = bagValue( - structValue( - "a" to int32Value(1), - "b" to nullValue() - ), - structValue( - "a" to nullValue(), - "b" to int32Value(2) - ), - ) - ), - SuccessTestCase( - input = """ - TUPLEUNION( - { 'a': 1 }, - { 'b': TRUE }, - { 'c': 'hello' } - ); - """.trimIndent(), - expected = structValue( - "a" to int32Value(1), - "b" to boolValue(true), - "c" to stringValue("hello") - ) - ), - SuccessTestCase( - input = """ - CASE - WHEN NULL THEN 'isNull' - WHEN MISSING THEN 'isMissing' - WHEN FALSE THEN 'isFalse' - WHEN TRUE THEN 'isTrue' - END - ; - """.trimIndent(), - expected = stringValue("isTrue") - ), - SuccessTestCase( - input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON TRUE;", - expected = bagValue( - structValue( - "a" to int32Value(1), - "b" to int32Value(2) - ), - ) - ), - SuccessTestCase( - input = """ - TUPLEUNION( - { 'a': 1 }, - NULL, - { 'c': 'hello' } - ); - """.trimIndent(), - expected = structValue(null) - ), - SuccessTestCase( - input = """ - CASE - WHEN NULL THEN 'isNull' - WHEN MISSING THEN 'isMissing' - WHEN FALSE THEN 'isFalse' - END - ; - """.trimIndent(), - expected = stringValue(null) - ), - SuccessTestCase( - input = """ - TUPLEUNION( - { 'a': 1 }, - 5, - { 'c': 'hello' } - ); - """.trimIndent(), - expected = missingValue() - ), - SuccessTestCase( - input = """ - TUPLEUNION( - { 'a': 1, 'b': FALSE }, - { 'b': TRUE }, - { 'c': 'hello' } - ); - """.trimIndent(), - expected = structValue( - "a" to int32Value(1), - "b" to boolValue(false), - "b" to boolValue(true), - "c" to stringValue("hello") - ) - ), - SuccessTestCase( - input = """ - SELECT * FROM - << - { 'a': 1, 'b': FALSE } - >> AS t, - << - { 'b': TRUE } - >> AS s - """.trimIndent(), - expected = bagValue( - structValue( - "a" to int32Value(1), - "b" to boolValue(false), - "b" to boolValue(true) - ) - ) - ), - SuccessTestCase( - input = """ - SELECT VALUE { - 'a': 1, - 'b': NULL, - t.c : t.d - } - FROM << - { 'c': 'hello', 'd': 'world' } - >> AS t - """.trimIndent(), - expected = bagValue( - structValue( - "a" to int32Value(1), - "b" to nullValue(), - "hello" to stringValue("world") - ) - ) - ), - SuccessTestCase( - input = "SELECT v, i FROM << 'a', 'b', 'c' >> AS v AT i", - expected = bagValue( - structValue( - "v" to stringValue("a"), - ), - structValue( - "v" to stringValue("b"), - ), - structValue( - "v" to stringValue("c"), - ), - ) - ), - SuccessTestCase( - input = "SELECT DISTINCT VALUE t FROM <> AS t;", - expected = bagValue(boolValue(true), boolValue(false)) - ), - SuccessTestCase( - input = "SELECT DISTINCT VALUE t FROM <> AS t WHERE t = TRUE;", - expected = bagValue(boolValue(true)) - ), - SuccessTestCase( - input = "100 + 50;", - expected = int32Value(150) - ), - SuccessTestCase( - input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2, 3>> AS t;", - expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300)) - ), - SuccessTestCase( - input = """ - PIVOT x.v AT x.k FROM << - { 'k': 'a', 'v': 'x' }, - { 'k': 'b', 'v': 'y' }, - { 'k': 'c', 'v': 'z' } - >> AS x - """.trimIndent(), - expected = structValue( - "a" to stringValue("x"), - "b" to stringValue("y"), - "c" to stringValue("z"), - ) - ), - SuccessTestCase( - input = """ - SELECT t - EXCLUDE t.a.b - FROM << - {'a': {'b': 2}, 'foo': 'bar', 'foo2': 'bar2'} - >> AS t - """.trimIndent(), - expected = bagValue( - structValue( - "t" to structValue( - "a" to structValue( - // field `b` excluded - ), - "foo" to stringValue("bar"), - "foo2" to stringValue("bar2") - ) - ), - ) - ), - 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) - ) - ) - ) - ) - ) - ) - ), - SuccessTestCase( - input = """ - CASE (1) - WHEN NULL THEN 'isNull' - WHEN MISSING THEN 'isMissing' - WHEN 2 THEN 'isTwo' - END - ; - """.trimIndent(), - expected = stringValue(null) - ), - SuccessTestCase( - input = """ - CASE (1) - WHEN NULL THEN 'isNull' - WHEN MISSING THEN 'isMissing' - WHEN 2 THEN 'isTwo' - WHEN 1 THEN 'isOne' - END - ; - """.trimIndent(), - expected = stringValue("isOne") - ), - SuccessTestCase( - input = """ - `null.bool` IS NULL - """.trimIndent(), - expected = boolValue(true) - ), - // SELECT * without nested coercion - SuccessTestCase( - input = """ - SELECT * - FROM ( - SELECT t.a AS "first", t.b AS "second" - FROM << { 'a': 3, 'b': 5 } >> AS t - ); - """.trimIndent(), - expected = bagValue( - structValue( - "first" to int32Value(3), - "second" to int32Value(5) - ) - ) - ), - // SELECT list without nested coercion - SuccessTestCase( - input = """ - SELECT "first", "second" - FROM ( - SELECT t.a AS "first", t.b AS "second" - FROM << { 'a': 3, 'b': 5 } >> AS t - ); - """.trimIndent(), - expected = bagValue( - structValue( - "first" to int32Value(3), - "second" to int32Value(5) - ) - ) - ), - // SELECT value without nested coercion - SuccessTestCase( - input = """ - SELECT VALUE "first" - FROM ( - SELECT t.a AS "first", t.b AS "second" - FROM << { 'a': 3, 'b': 5 } >> AS t - ); - """.trimIndent(), - expected = bagValue( - int32Value(3), - ) - ), - SuccessTestCase( - input = "MISSING IS MISSING;", - expected = boolValue(true) - ), - SuccessTestCase( - input = "MISSING IS MISSING;", - expected = boolValue(true), // TODO: Is this right? - mode = PartiQLEngine.Mode.STRICT - ), - SuccessTestCase( - input = "SELECT VALUE t.a IS MISSING FROM << { 'b': 1 }, { 'a': 2 } >> AS t;", - expected = bagValue(boolValue(true), boolValue(false)) - ), - // PartiQL Specification Section 7.1.1 -- Equality - SuccessTestCase( - input = "5 = 'a';", - expected = boolValue(false), - ), - // PartiQL Specification Section 7.1.1 -- Equality - SuccessTestCase( - input = "5 = 'a';", - expected = boolValue(false), // TODO: Is this correct? - mode = PartiQLEngine.Mode.STRICT - ), - // PartiQL Specification Section 8 - SuccessTestCase( - input = "MISSING AND TRUE;", - expected = boolValue(null), - ), - // PartiQL Specification Section 8 - SuccessTestCase( - input = "MISSING AND TRUE;", - expected = boolValue(null), // TODO: Is this right? - mode = PartiQLEngine.Mode.STRICT - ), - // PartiQL Specification Section 8 - SuccessTestCase( - input = "NULL IS MISSING;", - expected = boolValue(false), - ), - // PartiQL Specification Section 8 - SuccessTestCase( - input = "NULL IS MISSING;", - expected = boolValue(false), - mode = PartiQLEngine.Mode.STRICT - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': 10, 'b': 1}, {'a': 1, 'b': 2}>> AS t ORDER BY t.a;", - expected = listValue( - structValue("a" to int32Value(1), "b" to int32Value(2)), - structValue("a" to int32Value(10), "b" to int32Value(1)) - ) - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': 10, 'b': 1}, {'a': 1, 'b': 2}>> AS t ORDER BY t.a DESC;", - expected = listValue( - structValue("a" to int32Value(10), "b" to int32Value(1)), - structValue("a" to int32Value(1), "b" to int32Value(2)) - ) - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a NULLS LAST;", - expected = listValue( - structValue("a" to int32Value(1), "b" to int32Value(2)), - structValue("a" to int32Value(3), "b" to int32Value(4)), - structValue("a" to nullValue(), "b" to int32Value(1)) - ) - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a NULLS FIRST;", - expected = listValue( - structValue("a" to nullValue(), "b" to int32Value(1)), - structValue("a" to int32Value(1), "b" to int32Value(2)), - structValue("a" to int32Value(3), "b" to int32Value(4)) - ) - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a DESC NULLS LAST;", - expected = listValue( - structValue("a" to int32Value(3), "b" to int32Value(4)), - structValue("a" to int32Value(1), "b" to int32Value(2)), - structValue("a" to nullValue(), "b" to int32Value(1)) - ) - ), - SuccessTestCase( - input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 3, 'b': 4}>> AS t ORDER BY t.a DESC NULLS FIRST;", - expected = listValue( - structValue("a" to nullValue(), "b" to int32Value(1)), - structValue("a" to int32Value(3), "b" to int32Value(4)), - structValue("a" to int32Value(1), "b" to int32Value(2)) - ) - ), - SuccessTestCase( // use multiple sort specs - input = "SELECT * FROM <<{'a': NULL, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 4}>> AS t ORDER BY t.a DESC NULLS FIRST, t.b DESC;", - expected = listValue( - structValue("a" to nullValue(), "b" to int32Value(1)), - structValue("a" to int32Value(1), "b" to int32Value(4)), - structValue("a" to int32Value(1), "b" to int32Value(2)) - ) - ), - ) - - @JvmStatic - fun typingModeTestCases() = listOf( - TypingTestCase( - name = "Expected missing value in collection", - input = "SELECT VALUE t.a FROM << { 'a': 1 }, { 'b': 2 } >> AS t;", - expectedPermissive = bagValue(int32Value(1), missingValue()) - ), - TypingTestCase( - name = "Expected missing value in tuple in collection", - input = "SELECT t.a AS \"a\" FROM << { 'a': 1 }, { 'b': 2 } >> AS t;", - expectedPermissive = bagValue( - structValue( - "a" to int32Value(1), - ), - structValue(), - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 4.2 -- index negative", - input = "[1,2,3][-1];", - expectedPermissive = missingValue() - ), - TypingTestCase( - name = "PartiQL Specification Section 4.2 -- out of bounds", - input = "[1,2,3][3];", - expectedPermissive = missingValue() - ), - TypingTestCase( - name = "PartiQL Spec Section 5.1.1 -- Position variable on bags", - input = "SELECT v, p FROM << 5 >> AS v AT p;", - expectedPermissive = bagValue( - structValue( - "v" to int32Value(5) - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 5.1.1 -- Iteration over a scalar value", - input = "SELECT v FROM 0 AS v;", - expectedPermissive = bagValue( - structValue( - "v" to int32Value(0) - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 5.1.1 -- Iteration over a scalar value (with at)", - input = "SELECT v, p FROM 0 AS v AT p;", - expectedPermissive = bagValue( - structValue( - "v" to int32Value(0) - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 5.1.1 -- Iteration over a tuple value", - input = "SELECT v.a AS a FROM { 'a': 1 } AS v;", - expectedPermissive = bagValue( - structValue( - "a" to int32Value(1) - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 5.1.1 -- Iteration over an absent value (missing)", - input = "SELECT v AS v FROM MISSING AS v;", - expectedPermissive = bagValue(structValue()) - ), - TypingTestCase( - name = "PartiQL Specification Section 5.1.1 -- Iteration over an absent value (null)", - input = "SELECT v AS v FROM NULL AS v;", - expectedPermissive = bagValue( - structValue( - "v" to nullValue() - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 6.1.4 -- when constructing tuples", - input = "SELECT VALUE {'a':v.a, 'b':v.b} FROM [{'a':1, 'b':1}, {'a':2}] AS v;", - expectedPermissive = bagValue( - structValue( - "a" to int32Value(1), - "b" to int32Value(1), - ), - structValue( - "a" to int32Value(2), - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 6.1.4 -- when constructing bags (1)", - input = "SELECT VALUE v.b FROM [{'a':1, 'b':1}, {'a':2}] AS v;", - expectedPermissive = bagValue( - int32Value(1), - missingValue() - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 6.1.4 -- when constructing bags (2)", - input = "SELECT VALUE <> FROM [{'a':1, 'b':1}, {'a':2}] AS v;", - expectedPermissive = bagValue( - bagValue( - int32Value(1), - int32Value(1), - ), - bagValue( - int32Value(2), - missingValue() - ) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 6.2 -- Pivoting a Collection into a Variable-Width Tuple", - input = "PIVOT t.price AT t.\"symbol\" FROM [{'symbol':25, 'price':31.52}, {'symbol':'amzn', 'price':840.05}] AS t;", - expectedPermissive = structValue( - "amzn" to decimalValue(BigDecimal.valueOf(840.05)) - ) - ), - TypingTestCase( - name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (1)", - input = "SELECT VALUE 5 + v FROM <<1, MISSING>> AS v;", - expectedPermissive = bagValue(int32Value(6), missingValue()) - ), - TypingTestCase( - name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (3)", - input = "SELECT VALUE NOT v FROM << false, {'a':1} >> AS v;", - expectedPermissive = bagValue(boolValue(true), missingValue()) - ), - TypingTestCase( - name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 28 (2)", - input = "SELECT VALUE 5 > v FROM <<1, 'a'>> AS v;", - expectedPermissive = bagValue(boolValue(true), missingValue()) - ), - TypingTestCase( - name = "PartiQL Specification Section 9.1", - input = """ - SELECT - o.name AS orderName, - (SELECT c.name FROM << { 'name': 'John', 'id': 1 }, { 'name': 'Alan', 'id': 1 } >> c WHERE c.id=o.custId) AS customerName - FROM << { 'name': 'apples', 'custId': 1 } >> o - """.trimIndent(), - expectedPermissive = bagValue( - structValue( - "orderName" to stringValue("apples") - ) - ) - ) - ) - } - - public class SuccessTestCase @OptIn(PartiQLValueExperimental::class) constructor( - val input: String, - val expected: PartiQLValue, - val mode: PartiQLEngine.Mode = PartiQLEngine.Mode.PERMISSIVE, - val globals: List = emptyList(), - ) { - - private val engine = PartiQLEngine.builder().build() - private val planner = PartiQLPlannerBuilder().build() - private val parser = PartiQLParser.default() - private val loader = createIonElementLoader() - - /** - * @property value is a serialized Ion value. - */ - class Global( - val name: String, - val value: String, - val type: StaticType = StaticType.ANY, - ) - - internal fun assert() { - val statement = parser.parse(input).root - val catalogBuilder = MemoryCatalog.PartiQL().name("memory") - globals.forEach { global -> - catalogBuilder.define(global.name, global.type, loader.loadSingleElement(global.value)) - } - val catalog = catalogBuilder.build() - val connector = MemoryConnector(catalog) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "u" - } - val session = PartiQLPlanner.Session( - "q", - "u", - "memory", - catalogs = mapOf("memory" to connector.getMetadata(connectorSession)) - ) - val plan = planner.plan(statement, session) - val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) - val result = when (val returned = engine.execute(prepared)) { - is PartiQLResult.Value -> returned - is PartiQLResult.Error -> { - PlanPrinter.append(System.err, plan.plan) - throw returned.cause - } - } - val output = result.value - assert(expected == output) { - comparisonString(expected, output, plan.plan) - } - } - - @OptIn(PartiQLValueExperimental::class) - private fun comparisonString(expected: PartiQLValue, actual: PartiQLValue, plan: PartiQLPlan): String { - val expectedBuffer = ByteArrayOutputStream() - val expectedWriter = PartiQLValueIonWriterBuilder.standardIonTextBuilder().build(expectedBuffer) - expectedWriter.append(expected) - return buildString { - PlanPrinter.append(this, plan) - appendLine("Expected : $expectedBuffer") - expectedBuffer.reset() - expectedWriter.append(actual) - appendLine("Actual : $expectedBuffer") - } - } - - override fun toString(): String { - return input - } - } - - public class TypingTestCase @OptIn(PartiQLValueExperimental::class) constructor( - val name: String, - val input: String, - val expectedPermissive: PartiQLValue, - ) { - - private val engine = PartiQLEngine.builder().build() - private val planner = PartiQLPlannerBuilder().build() - private val parser = PartiQLParser.default() - - internal fun assert() { - val permissiveResult = run(mode = PartiQLEngine.Mode.PERMISSIVE) - assert(expectedPermissive == permissiveResult.first) { - comparisonString(expectedPermissive, permissiveResult.first, permissiveResult.second) - } - var error: Throwable? = null - try { - when (val result = run(mode = PartiQLEngine.Mode.STRICT).first) { - is CollectionValue<*> -> result.toList() - else -> result - } - } catch (e: Throwable) { - error = e - } - assertNotNull(error) - } - - private fun run(mode: PartiQLEngine.Mode): Pair { - val statement = parser.parse(input).root - val catalog = MemoryCatalog.PartiQL().name("memory").build() - val connector = MemoryConnector(catalog) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "u" - } - val session = PartiQLPlanner.Session( - "q", - "u", - "memory", - catalogs = mapOf("memory" to connector.getMetadata(connectorSession)) - ) - val plan = planner.plan(statement, session) - 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 - } - } - - @OptIn(PartiQLValueExperimental::class) - private fun comparisonString(expected: PartiQLValue, actual: PartiQLValue, plan: PartiQLPlan): String { - val expectedBuffer = ByteArrayOutputStream() - val expectedWriter = PartiQLValueIonWriterBuilder.standardIonTextBuilder().build(expectedBuffer) - expectedWriter.append(expected) - return buildString { - PlanPrinter.append(this, plan) - appendLine("Expected : $expectedBuffer") - expectedBuffer.reset() - expectedWriter.append(actual) - appendLine("Actual : $expectedBuffer") - } - } - - override fun toString(): String { - return "$name -- $input" - } - } - - @Test - @Disabled("CASTS have not yet been implemented.") - fun testCast1() = SuccessTestCase( - input = "1 + 2.0", - expected = int32Value(3), - ).assert() - - @Test - @Disabled("CASTS have not yet been implemented.") - fun testCasts() = SuccessTestCase( - input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2.0, 3.0>> AS t;", - expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300)) - ).assert() - - @Test - @Disabled("We need to support section 5.1") - fun testTypingOfPositionVariable() = TypingTestCase( - name = "PartiQL Spec Section 5.1.1 -- Position variable on bags", - input = "SELECT v, p FROM << 5 >> AS v AT p;", - expectedPermissive = bagValue( - structValue( - "v" to int32Value(5) - ) - ) - ).assert() - - @Test - @Disabled("This is just a placeholder. We should add support for this. Grouping is not yet supported.") - fun test3() = - TypingTestCase( - name = "PartiQL Specification Section 11.1", - input = """ - PLACEHOLDER FOR THE EXAMPLE IN THE RELEVANT SECTION. GROUPING NOT YET SUPPORTED. - """.trimIndent(), - expectedPermissive = missingValue() - ).assert() - - @Test - @Disabled("The planner fails this, though it should pass for permissive mode.") - fun test5() = - TypingTestCase( - name = "PartiQL Specification Section 5.2.1 -- Mistyping Cases", - input = "SELECT v, n FROM UNPIVOT 1 AS v AT n;", - expectedPermissive = bagValue( - structValue( - "v" to int32Value(1), - "n" to stringValue("_1") - ) - ) - ).assert() - - @Test - @Disabled("We don't yet support arrays.") - fun test7() = - TypingTestCase( - name = "PartiQL Specification Section 6.1.4 -- when constructing arrays", - input = "SELECT VALUE [v.a, v.b] FROM [{'a':1, 'b':1}, {'a':2}] AS v;", - expectedPermissive = bagValue( - listValue( - int32Value(1), - int32Value(1), - ), - listValue( - int32Value(2), - missingValue() - ) - ) - ).assert() - - @Test - @Disabled("There is a bug in the planner which makes this always return missing.") - fun test8() = - TypingTestCase( - name = "PartiQL Specification Section 4.2 -- non integer index", - input = "SELECT VALUE [1,2,3][v] FROM <<1, 1.0>> AS v;", - expectedPermissive = bagValue(int32Value(2), missingValue()) - ).assert() - - @Test - @Disabled("CASTs aren't supported yet.") - fun test9() = - TypingTestCase( - name = "PartiQL Specification Section 7.1 -- Inputs with wrong types Example 27", - input = "SELECT VALUE {'a':3*v.a, 'b':3*(CAST (v.b AS INTEGER))} FROM [{'a':1, 'b':'1'}, {'a':2}] v;", - expectedPermissive = bagValue( - structValue( - "a" to int32Value(3), - "b" to int32Value(3), - ), - structValue( - "a" to int32Value(6), - ), - ) - ).assert() - - @Test - @Disabled("Arrays aren't supported yet.") - fun test10() = - SuccessTestCase( - input = "SELECT v, i FROM [ 'a', 'b', 'c' ] AS v AT i", - expected = bagValue( - structValue( - "v" to stringValue("a"), - "i" to int64Value(0), - ), - structValue( - "v" to stringValue("b"), - "i" to int64Value(1), - ), - structValue( - "v" to stringValue("c"), - "i" to int64Value(2), - ), - ) - ).assert() - - @Test - @Disabled("Support for ORDER BY needs to be added for this to pass.") - // PartiQL Specification says that SQL's SELECT is coerced, but SELECT VALUE is not. - fun selectValueNoCoercion() = - SuccessTestCase( - input = """ - (4, 5) < (SELECT VALUE t.a FROM << { 'a': 3 }, { 'a': 4 } >> AS t ORDER BY t.a) - """.trimIndent(), - expected = boolValue(false) - ).assert() - - @Test - @Disabled("This is appropriately coerced, but this test is failing because LT currently doesn't support LISTS.") - fun rowCoercion() = - SuccessTestCase( - input = """ - (4, 5) < (SELECT t.a, t.a FROM << { 'a': 3 } >> AS t) - """.trimIndent(), - expected = boolValue(false) - ).assert() - - @Test - @Disabled("This broke in its introduction to the codebase on merge. See 5fb9a1ccbc7e630b0df62aa8b161d319c763c1f6.") - // TODO: Add to conformance tests - fun wildCard() = - SuccessTestCase( - input = """ - [ - { 'id':'5', - 'books':[ - { 'title':'A', - 'price':5.0, - 'authors': [{'name': 'John'}, {'name': 'Doe'}] - }, - { 'title':'B', - 'price':2.0, - 'authors': [{'name': 'Zoe'}, {'name': 'Bill'}] - } - ] - }, - { 'id':'6', - 'books':[ - { 'title':'A', - 'price':5.0, - 'authors': [{'name': 'John'}, {'name': 'Doe'}] - }, - { 'title':'E', - 'price':2.0, - 'authors': [{'name': 'Zoe'}, {'name': 'Bill'}] - } - ] - }, - { 'id':7, - 'books':[] - } - ][*].books[*].authors[*].name - """.trimIndent(), - expected = bagValue( - listOf( - stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill"), - stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill") - ) - ) - ).assert() - - @Test - @Disabled("This broke in its introduction to the codebase on merge. See 5fb9a1ccbc7e630b0df62aa8b161d319c763c1f6.") - // TODO: add to conformance tests - // Note that the existing pipeline produced identical result when supplying with - // SELECT VALUE v2.name FROM e as v0, v0.books as v1, unpivot v1.authors as v2; - // But it produces different result when supplying with e[*].books[*].authors.* - // << - // <<{ 'name': 'John'},{'name': 'Doe'} >>, - // ... - // >> - fun unpivot() = - SuccessTestCase( - input = """ - [ - { 'id':'5', - 'books':[ - { 'title':'A', - 'price':5.0, - 'authors': { - 'first': {'name': 'John'}, - 'second': {'name': 'Doe'} - } - }, - { 'title':'B', - 'price':2.0, - 'authors': { - 'first': {'name': 'Zoe'}, - 'second': {'name': 'Bill'} - } - } - ] - }, - { 'id':'6', - 'books':[ - { 'title':'A', - 'price':5.0, - 'authors': { - 'first': {'name': 'John'}, - 'second': {'name': 'Doe'} - } - }, - { 'title':'E', - 'price':2.0, - 'authors': { - 'first': {'name': 'Zoe'}, - 'second': {'name': 'Bill'} - } - } - ] - }, - { 'id':7, - 'books':[] - } - ][*].books[*].authors.*.name - """.trimIndent(), - expected = bagValue( - listOf( - stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill"), - stringValue("John"), stringValue("Doe"), stringValue("Zoe"), stringValue("Bill") - ) - ) - ).assert() -} From f46dd2295b113d65dede9030cc7e957860c337f8 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Wed, 10 Jul 2024 11:28:44 -0700 Subject: [PATCH 03/10] Use simplified set op representation --- .../src/main/resources/partiql_plan.ion | 42 ++--- .../org/partiql/planner/internal/ir/Nodes.kt | 170 +++++++++--------- .../internal/transforms/PlanTransform.kt | 19 +- .../internal/transforms/RelConverter.kt | 10 +- .../internal/transforms/RexConverter.kt | 10 +- .../planner/internal/typer/PlanTyper.kt | 6 +- .../main/resources/partiql_plan_internal.ion | 48 ++--- 7 files changed, 158 insertions(+), 147 deletions(-) diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 36ae106ad1..2dc6afe791 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -73,6 +73,12 @@ identifier::[ ], ] +// [ ALL | DISTINCT ] +set_quantifier::[ + ALL, + DISTINCT, +] + // Rex rex::{ type: static_type, @@ -240,29 +246,23 @@ rel::{ ], }, - set::[ - union::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - }, - - intersect::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - }, + union::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + }, - except::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - }, + intersect::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + }, - _::[ - quantifier::[ ALL, DISTINCT ], - ] - ], + except::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + }, limit::{ input: rel, 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 1c5ff36704..1fc96d44fd 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 @@ -25,6 +25,7 @@ import org.partiql.planner.internal.ir.builder.RelOpAggregateBuilder import org.partiql.planner.internal.ir.builder.RelOpAggregateCallBuilder import org.partiql.planner.internal.ir.builder.RelOpDistinctBuilder import org.partiql.planner.internal.ir.builder.RelOpErrBuilder +import org.partiql.planner.internal.ir.builder.RelOpExceptBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeItemBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeStepCollIndexBuilder @@ -32,17 +33,16 @@ import org.partiql.planner.internal.ir.builder.RelOpExcludeStepCollWildcardBuild import org.partiql.planner.internal.ir.builder.RelOpExcludeStepStructFieldBuilder import org.partiql.planner.internal.ir.builder.RelOpExcludeStepStructWildcardBuilder import org.partiql.planner.internal.ir.builder.RelOpFilterBuilder +import org.partiql.planner.internal.ir.builder.RelOpIntersectBuilder import org.partiql.planner.internal.ir.builder.RelOpJoinBuilder import org.partiql.planner.internal.ir.builder.RelOpLimitBuilder import org.partiql.planner.internal.ir.builder.RelOpOffsetBuilder import org.partiql.planner.internal.ir.builder.RelOpProjectBuilder import org.partiql.planner.internal.ir.builder.RelOpScanBuilder import org.partiql.planner.internal.ir.builder.RelOpScanIndexedBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetExceptBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetIntersectBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetUnionBuilder import org.partiql.planner.internal.ir.builder.RelOpSortBuilder import org.partiql.planner.internal.ir.builder.RelOpSortSpecBuilder +import org.partiql.planner.internal.ir.builder.RelOpUnionBuilder import org.partiql.planner.internal.ir.builder.RelOpUnpivotBuilder import org.partiql.planner.internal.ir.builder.RelTypeBuilder import org.partiql.planner.internal.ir.builder.RexBuilder @@ -810,7 +810,9 @@ internal data class Rel( is Distinct -> visitor.visitRelOpDistinct(this, ctx) is Filter -> visitor.visitRelOpFilter(this, ctx) is Sort -> visitor.visitRelOpSort(this, ctx) - is Set -> visitor.visitRelOpSet(this, ctx) + is Union -> visitor.visitRelOpUnion(this, ctx) + is Intersect -> visitor.visitRelOpIntersect(this, ctx) + is Except -> visitor.visitRelOpExcept(this, ctx) is Limit -> visitor.visitRelOpLimit(this, ctx) is Offset -> visitor.visitRelOpOffset(this, ctx) is Project -> visitor.visitRelOpProject(this, ctx) @@ -948,83 +950,86 @@ internal data class Rel( internal fun builder(): RelOpSortBuilder = RelOpSortBuilder() } } - internal sealed class Set : Op() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Union -> visitor.visitRelOpSetUnion(this, ctx) - is Intersect -> visitor.visitRelOpSetIntersect(this, ctx) - is Except -> visitor.visitRelOpSetExcept(this, ctx) - } - - internal data class Union( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetUnion(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetUnionBuilder = RelOpSetUnionBuilder() - } - } - - internal data class Intersect( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetIntersect(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetIntersectBuilder = RelOpSetIntersectBuilder() - } - } - - internal data class Except( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetExcept(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetExceptBuilder = RelOpSetExceptBuilder() - } - } - - internal enum class Quantifier { - ALL, DISTINCT - } + internal data class Union( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpUnion(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpUnionBuilder = RelOpUnionBuilder() + } + } + + internal data class Intersect( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpIntersect(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpIntersectBuilder = RelOpIntersectBuilder() + } + } + + internal data class Except( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcept(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExceptBuilder = RelOpExceptBuilder() + } } internal data class Limit( @@ -1300,3 +1305,8 @@ internal data class Rel( internal fun builder(): RelBuilder = RelBuilder() } } + +internal enum class SetQuantifier { + ALL, + DISTINCT, +} 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 039bca50cc..0f622081f6 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 @@ -13,6 +13,7 @@ import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.PartiQLPlan import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.SetQuantifier import org.partiql.planner.internal.ir.Statement import org.partiql.planner.internal.ir.visitor.PlanBaseVisitor import org.partiql.planner.internal.utils.PlanUtils @@ -296,27 +297,27 @@ internal object PlanTransform : PlanBaseVisitor() { } ) - override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Except( + override fun visitRelOpExcept(node: Rel.Op.Except, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Except( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - quantifier = visitRelOpSetQuantifier(node.quantifier) + setq = visitRelOpSetQuantifier(node.setq) ) - override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Intersect( + override fun visitRelOpIntersect(node: Rel.Op.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Intersect( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - quantifier = visitRelOpSetQuantifier(node.quantifier) + setq = visitRelOpSetQuantifier(node.setq) ) - override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Set.Union( + override fun visitRelOpUnion(node: Rel.Op.Union, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Union( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - quantifier = visitRelOpSetQuantifier(node.quantifier) + setq = visitRelOpSetQuantifier(node.setq) ) - private fun visitRelOpSetQuantifier(node: Rel.Op.Set.Quantifier) = when (node) { - Rel.Op.Set.Quantifier.ALL -> org.partiql.plan.Rel.Op.Set.Quantifier.ALL - Rel.Op.Set.Quantifier.DISTINCT -> org.partiql.plan.Rel.Op.Set.Quantifier.DISTINCT + private fun visitRelOpSetQuantifier(node: SetQuantifier) = when (node) { + SetQuantifier.ALL -> org.partiql.plan.SetQuantifier.ALL + SetQuantifier.DISTINCT -> org.partiql.plan.SetQuantifier.DISTINCT } override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Limit( 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 71e86921a4..bb15cd7d4c 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 @@ -413,13 +413,13 @@ internal object RelConverter { val lhs = input val rhs = visitExprSFW(setOp.operand, nil) val quantifier = when (setOp.type.setq) { - SetQuantifier.ALL -> Rel.Op.Set.Quantifier.ALL - null, SetQuantifier.DISTINCT -> Rel.Op.Set.Quantifier.DISTINCT + SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL + null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT } val op = when (setOp.type.type) { - SetOp.Type.UNION -> Rel.Op.Set.Union(quantifier, lhs, rhs, false) - SetOp.Type.EXCEPT -> Rel.Op.Set.Except(quantifier, lhs, rhs, false) - SetOp.Type.INTERSECT -> Rel.Op.Set.Intersect(quantifier, lhs, rhs, false) + SetOp.Type.UNION -> Rel.Op.Union(quantifier, lhs, rhs, false) + SetOp.Type.EXCEPT -> Rel.Op.Except(quantifier, lhs, rhs, false) + SetOp.Type.INTERSECT -> Rel.Op.Intersect(quantifier, lhs, rhs, false) } return rel(type, 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 cd31603e89..e212182c0d 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 @@ -644,14 +644,14 @@ internal object RexConverter { op = Rel.Op.Scan(visitExpr(node.rhs, ctx)) ) val quantifier = when (node.type.setq) { - SetQuantifier.ALL -> Rel.Op.Set.Quantifier.ALL - null, SetQuantifier.DISTINCT -> Rel.Op.Set.Quantifier.DISTINCT + SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL + null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT } val isOuter = node.outer == true val op = when (node.type.type) { - SetOp.Type.UNION -> Rel.Op.Set.Union(quantifier, lhs, rhs, isOuter) - SetOp.Type.EXCEPT -> Rel.Op.Set.Except(quantifier, lhs, rhs, isOuter) - SetOp.Type.INTERSECT -> Rel.Op.Set.Intersect(quantifier, lhs, rhs, isOuter) + SetOp.Type.UNION -> Rel.Op.Union(quantifier, lhs, rhs, isOuter) + SetOp.Type.EXCEPT -> Rel.Op.Except(quantifier, lhs, rhs, isOuter) + SetOp.Type.INTERSECT -> Rel.Op.Intersect(quantifier, lhs, rhs, isOuter) } val rel = Rel( type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), 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 bff0ef4b04..5cd18aeb98 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 @@ -242,7 +242,7 @@ internal class PlanTyper( return rel(type, op) } - override fun visitRelOpSetExcept(node: Rel.Op.Set.Except, ctx: Rel.Type?): Rel { + override fun visitRelOpExcept(node: Rel.Op.Except, ctx: Rel.Type?): Rel { val lhs = visitRel(node.lhs, node.lhs.type) val rhs = visitRel(node.rhs, node.rhs.type) // Check for Compatibility @@ -257,7 +257,7 @@ internal class PlanTyper( return Rel(type, node.copy(lhs = lhs, rhs = rhs)) } - override fun visitRelOpSetIntersect(node: Rel.Op.Set.Intersect, ctx: Rel.Type?): Rel { + override fun visitRelOpIntersect(node: Rel.Op.Intersect, ctx: Rel.Type?): Rel { val lhs = visitRel(node.lhs, node.lhs.type) val rhs = visitRel(node.rhs, node.rhs.type) // Check for Compatibility @@ -272,7 +272,7 @@ internal class PlanTyper( return Rel(type, node.copy(lhs = lhs, rhs = rhs)) } - override fun visitRelOpSetUnion(node: Rel.Op.Set.Union, ctx: Rel.Type?): Rel { + override fun visitRelOpUnion(node: Rel.Op.Union, ctx: Rel.Type?): Rel { val lhs = visitRel(node.lhs, node.lhs.type) val rhs = visitRel(node.rhs, node.rhs.type) // Check for Compatibility diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 7ececd8f34..7fc8a247ca 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -84,6 +84,12 @@ identifier::[ ], ] +// [ ALL | DISTINCT ] +set_quantifier::[ + ALL, + DISTINCT, +] + // Rex rex::{ type: static_type, @@ -269,32 +275,26 @@ rel::{ // due to [RFC-0007](https://github.com/partiql/partiql-lang/blob/main/RFCs/0007-rfc-bag-operators.md). However, // if a user were to use OUTER UNION, then it would work. Under the hood at execution, the operator is the same -- // however, at planning time, with static type analysis, we can fail queries prior to their execution. - set::[ - union::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - is_outer: bool - }, - - intersect::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - is_outer: bool - }, + union::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + is_outer: bool, + }, - except::{ - quantifier: quantifier, - lhs: rel, - rhs: rel, - is_outer: bool - }, + intersect::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + is_outer: bool, + }, - _::[ - quantifier::[ ALL, DISTINCT ], - ] - ], + except::{ + setq: set_quantifier, + lhs: rel, + rhs: rel, + is_outer: bool, + }, limit::{ input: rel, From 58df6f73dfafff234627718e0ecf3fee93550ddb Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Wed, 10 Jul 2024 11:36:01 -0700 Subject: [PATCH 04/10] Prepare 0.14.6 release --- CHANGELOG.md | 6 +++++- README.md | 2 +- gradle.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b7c31647..c43975506d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ unconstrained which is not SQL-conformant and is causing issues in integrating w INTEGER an alias for INT4 which is the internal type name. In a later release, we will make INTEGER the default 32-bit integer with INT/INT4/INTEGER4 being aliases per other systems. This change only applies to org.partiql.parser.PartiQLParser, not the org.partiql.lang.syntax.PartiQLParser. +- **Breaking change**: partiql-plan: adds a set quantifier field to set operators `UNION`, `INTERSECT` and `EXCEPT` +- partiql-planner: Adds typing support for set operators ### Deprecated @@ -1082,7 +1084,9 @@ breaking changes if migrating from v0.9.2. The breaking changes accidentally int ### Added Initial alpha release of PartiQL. -[Unreleased]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.4...HEAD +[Unreleased]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.6...HEAD +[0.14.6]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.5...v0.14.6 +[0.14.5]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.4...v0.14.5 [0.14.4]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.3...v0.14.4 [0.14.3]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.2...v0.14.3 [0.14.2]: https://github.com/partiql/partiql-lang-kotlin/compare/v0.14.1...v0.14.2 diff --git a/README.md b/README.md index 6818fc481a..26eccb7741 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This project is published to [Maven Central](https://search.maven.org/artifact/o | Group ID | Artifact ID | Recommended Version | |---------------|-----------------------|---------------------| -| `org.partiql` | `partiql-lang-kotlin` | `0.14.5` | +| `org.partiql` | `partiql-lang-kotlin` | `0.14.6` | For Maven builds, add the following to your `pom.xml`: diff --git a/gradle.properties b/gradle.properties index e6bdc6d7bd..9c0c032835 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=org.partiql -version=0.14.6-SNAPSHOT +version=0.14.6 ossrhUsername=EMPTY ossrhPassword=EMPTY From af7bc0b599cdc1149ca24a864bbe32c08d8801a0 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Wed, 10 Jul 2024 11:57:08 -0700 Subject: [PATCH 05/10] Ignore types of set op schemas --- .../org/partiql/planner/internal/typer/PlanTyper.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) 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 5cd18aeb98..3250f82509 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 @@ -298,21 +298,14 @@ internal class PlanTyper( } /** - * @return whether each type of the [lhs] is equal to its counterpart on the [rhs] + * @return whether each type of the [lhs] is comparable to its counterpart on the [rhs] * @param lhs should be typed already * @param rhs should be typed already */ private fun setOpSchemaTypesMatch(lhs: Rel, rhs: Rel): Boolean { // TODO: [RFC-0007](https://github.com/partiql/partiql-lang/blob/main/RFCs/0007-rfc-bag-operators.md) - // states that the types must be "comparable". The below code ONLY makes sure that types need to be - // the same. In the future, we need to add support for checking comparable types. - for (i in 0..lhs.type.schema.lastIndex) { - val lhsBindingType = lhs.type.schema[i].type - val rhsBindingType = rhs.type.schema[i].type - if (lhsBindingType != rhsBindingType) { - return false - } - } + // states that the types must be "comparable". For now, we will always return true. In the future, we need + // to add support for checking comparable types. return true } From 0bd057200d6736f56b04a8435f2ebdf7a13cec41 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Thu, 11 Jul 2024 17:34:36 -0700 Subject: [PATCH 06/10] Adds separate plan node for PartiQL bag ops; fixes parsing of non-SFW exprs; add/update tests --- CHANGELOG.md | 4 +- .../src/main/resources/partiql_ast.ion | 2 +- .../lang/prettyprint/QueryPrettyPrinter.kt | 9 +- .../lang/syntax/impl/PartiQLPigVisitor.kt | 39 +- .../lang/prettyprint/ASTPrettyPrinterTest.kt | 6 +- .../prettyprint/QueryPrettyPrinterTest.kt | 14 +- .../lang/syntax/PartiQLParserMatchTest.kt | 4 +- .../syntax/PartiQLParserPrecedenceTest.kt | 610 +++++++++--------- .../partiql/lang/syntax/PartiQLParserTest.kt | 18 +- .../parser/internal/PartiQLParserDefault.kt | 17 +- .../internal/PartiQLParserBagOpTests.kt | 510 +++++++++++++++ .../src/main/resources/partiql_plan.ion | 18 + .../org/partiql/planner/internal/ir/Nodes.kt | 87 ++- .../internal/transforms/PlanTransform.kt | 26 +- .../internal/transforms/RelConverter.kt | 33 +- .../internal/transforms/RexConverter.kt | 64 +- .../planner/internal/typer/PlanTyper.kt | 32 +- .../main/resources/partiql_plan_internal.ion | 21 +- 18 files changed, 1125 insertions(+), 389 deletions(-) create mode 100644 partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index c43975506d..2e2f9d53fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,8 +35,10 @@ unconstrained which is not SQL-conformant and is causing issues in integrating w INTEGER an alias for INT4 which is the internal type name. In a later release, we will make INTEGER the default 32-bit integer with INT/INT4/INTEGER4 being aliases per other systems. This change only applies to org.partiql.parser.PartiQLParser, not the org.partiql.lang.syntax.PartiQLParser. -- **Breaking change**: partiql-plan: adds a set quantifier field to set operators `UNION`, `INTERSECT` and `EXCEPT` +- **Breaking change**: partiql-plan: adds a set quantifier field to SQL set operators `UNION`, `INTERSECT`, and `EXCEPT` +- partiql-plan: adds a dedicated Rex node for PartiQL bag operators `UNION`, `INTERSECT`, and `EXCEPT` - partiql-planner: Adds typing support for set operators +- partiql-parser: parses non-SFW expressions to be PartiQL `OUTER` bag operators ### Deprecated diff --git a/partiql-ast/src/main/resources/partiql_ast.ion b/partiql-ast/src/main/resources/partiql_ast.ion index 67c48663c8..37d833d96d 100644 --- a/partiql-ast/src/main/resources/partiql_ast.ion +++ b/partiql-ast/src/main/resources/partiql_ast.ion @@ -500,7 +500,7 @@ expr::[ where: optional::expr, group_by: optional::group_by, having: optional::expr, - set_op: optional::{ + set_op: optional::{ // TODO modeling of `set_op` needs updated to support left-associative set ops https://github.com/partiql/partiql-lang-kotlin/issues/1507 type: '.set_op', operand: '.expr.s_f_w', }, diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt index ce4d31f479..4e40444a47 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinter.kt @@ -320,7 +320,14 @@ class QueryPrettyPrinter { is PartiqlAst.Expr.Or -> writeNAryOperator("OR", node.operands, sb, level) is PartiqlAst.Expr.InCollection -> writeNAryOperator("IN", node.operands, sb, level) is PartiqlAst.Expr.BagOp -> { - var name = node.op.javaClass.simpleName.toUpperCase().replace("_", " ") + var name = when (node.op) { + is PartiqlAst.BagOpType.Except -> "EXCEPT" + is PartiqlAst.BagOpType.Intersect -> "INTERSECT" + is PartiqlAst.BagOpType.Union -> "UNION" + is PartiqlAst.BagOpType.OuterExcept -> "OUTER EXCEPT" + is PartiqlAst.BagOpType.OuterIntersect -> "OUTER INTERSECT" + is PartiqlAst.BagOpType.OuterUnion -> "OUTER UNION" + } if (node.quantifier is PartiqlAst.SetQuantifier.All) { name += " ALL" } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index a74ef956d4..07eb7ea0b4 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -752,12 +752,33 @@ internal class PartiQLPigVisitor( * */ + /** + * Verifies if all of the [args] are + * 1. [PartiqlAst.Expr.Select] or + * 2. [PartiqlAst.Expr.BagOp] and is a SQL Set op (i.e. not an `OUTER` bag op) + */ + private fun argsAreSFW(args: List): Boolean { + return args.all { arg -> + arg is PartiqlAst.Expr.Select || (arg is PartiqlAst.Expr.BagOp && isOuter(arg.op)) + } + } + + private fun isOuter(op: PartiqlAst.BagOpType): Boolean { + return op is PartiqlAst.BagOpType.OuterUnion || op is PartiqlAst.BagOpType.OuterExcept || op is PartiqlAst.BagOpType.OuterIntersect + } + override fun visitIntersect(ctx: PartiQLParser.IntersectContext) = PartiqlAst.build { val lhs = visit(ctx.lhs) as PartiqlAst.Expr val rhs = visit(ctx.rhs) as PartiqlAst.Expr val quantifier = if (ctx.ALL() != null) all() else distinct() val (intersect, metas) = when (ctx.OUTER()) { - null -> intersect() to ctx.INTERSECT().getSourceMetaContainer() + null -> { + if (argsAreSFW(listOf(lhs, rhs))) { + intersect() to ctx.INTERSECT().getSourceMetaContainer() + } else { + outerIntersect() to ctx.OUTER().getSourceMetaContainer() + } + } else -> outerIntersect() to ctx.OUTER().getSourceMetaContainer() } bagOp(intersect, quantifier, listOf(lhs, rhs), metas) @@ -768,7 +789,13 @@ internal class PartiQLPigVisitor( val rhs = visit(ctx.rhs) as PartiqlAst.Expr val quantifier = if (ctx.ALL() != null) all() else distinct() val (except, metas) = when (ctx.OUTER()) { - null -> except() to ctx.EXCEPT().getSourceMetaContainer() + null -> { + if (argsAreSFW(listOf(lhs, rhs))) { + except() to ctx.EXCEPT().getSourceMetaContainer() + } else { + outerExcept() to ctx.OUTER().getSourceMetaContainer() + } + } else -> outerExcept() to ctx.OUTER().getSourceMetaContainer() } bagOp(except, quantifier, listOf(lhs, rhs), metas) @@ -779,7 +806,13 @@ internal class PartiQLPigVisitor( val rhs = visit(ctx.rhs) as PartiqlAst.Expr val quantifier = if (ctx.ALL() != null) all() else distinct() val (union, metas) = when (ctx.OUTER()) { - null -> union() to ctx.UNION().getSourceMetaContainer() + null -> { + if (argsAreSFW(listOf(lhs, rhs))) { + union() to ctx.UNION().getSourceMetaContainer() + } else { + outerUnion() to ctx.OUTER().getSourceMetaContainer() + } + } else -> outerUnion() to ctx.OUTER().getSourceMetaContainer() } bagOp(union, quantifier, listOf(lhs, rhs), metas) diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinterTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinterTest.kt index e49fa8500a..fdd7c18012 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinterTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/ASTPrettyPrinterTest.kt @@ -539,7 +539,7 @@ class ASTPrettyPrinterTest { checkPrettyPrintAst( "a UNION b", """ - Union + OuterUnion Id a (case_insensitive) (unqualified) Id b (case_insensitive) (unqualified) """.trimIndent() @@ -551,7 +551,7 @@ class ASTPrettyPrinterTest { checkPrettyPrintAst( "a EXCEPT b", """ - Except + OuterExcept Id a (case_insensitive) (unqualified) Id b (case_insensitive) (unqualified) """.trimIndent() @@ -563,7 +563,7 @@ class ASTPrettyPrinterTest { checkPrettyPrintAst( "a INTERSECT b", """ - Intersect + OuterIntersect Id a (case_insensitive) (unqualified) Id b (case_insensitive) (unqualified) """.trimIndent() diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinterTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinterTest.kt index 828d49ef36..29e73d7241 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinterTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/prettyprint/QueryPrettyPrinterTest.kt @@ -465,22 +465,22 @@ class QueryPrettyPrinterTest { @Test fun union1() { - checkPrettyPrintQuery("a UNION b", "a UNION b") + checkPrettyPrintQuery("a UNION b", "a OUTER UNION b") } @Test fun union2() { - checkPrettyPrintQuery("a UNION ALL b", "a UNION ALL b") + checkPrettyPrintQuery("a UNION ALL b", "a OUTER UNION ALL b") } @Test fun except() { - checkPrettyPrintQuery("a EXCEPT b", "a EXCEPT b") + checkPrettyPrintQuery("a EXCEPT b", "a OUTER EXCEPT b") } @Test fun intersect() { - checkPrettyPrintQuery("a INTERSECT b", "a INTERSECT b") + checkPrettyPrintQuery("a INTERSECT b", "a OUTER INTERSECT b") } @Test @@ -822,7 +822,7 @@ class QueryPrettyPrinterTest { checkPrettyPrintQuery( "(SELECT a FROM b) UNION (SELECT c FROM d) UNION (SELECT e FROM f)", """ - ((SELECT a FROM b) UNION (SELECT c FROM d)) UNION (SELECT e FROM f) + ((SELECT a FROM b) UNION (SELECT c FROM d)) OUTER UNION (SELECT e FROM f) """.trimIndent() ) } @@ -832,7 +832,7 @@ class QueryPrettyPrinterTest { checkPrettyPrintQuery( "(SELECT a FROM b) UNION c", """ - (SELECT a FROM b) UNION c + (SELECT a FROM b) OUTER UNION c """.trimIndent() ) } @@ -873,7 +873,7 @@ class QueryPrettyPrinterTest { "CASE (SELECT name FROM t) WHEN (SELECT a FROM b) UNION c THEN 1 WHEN (SELECT c FROM d) THEN 2 ELSE (SELECT e FROM f) END", """ CASE (SELECT name FROM t) - WHEN (SELECT a FROM b) UNION c THEN 1 + WHEN (SELECT a FROM b) OUTER UNION c THEN 1 WHEN (SELECT c FROM d) THEN 2 ELSE (SELECT e FROM f) END diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserMatchTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserMatchTest.kt index d45c4a4fb1..16b2bb4db5 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserMatchTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserMatchTest.kt @@ -109,7 +109,7 @@ class PartiQLParserMatchTest : PartiQLParserTestBase() { "(MyGraph MATCH (x)) UNION SELECT * FROM tbl1" ) { bagOp( - op = union(), + op = outerUnion(), // TODO decide if graph match set op maps to PartiQL or SQL quantifier = distinct(), operands = listOf( astMygraphMatchAllNodes, @@ -142,7 +142,7 @@ class PartiQLParserMatchTest : PartiQLParserTestBase() { "SELECT * FROM tbl1 UNION (MyGraph MATCH (x))" ) { bagOp( - op = union(), + op = outerUnion(), // TODO decide if graph match set op maps to PartiQL or SQL quantifier = distinct(), operands = listOf( astSelectStarFromTbl1, diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserPrecedenceTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserPrecedenceTest.kt index 48b5d05022..815e833196 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserPrecedenceTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserPrecedenceTest.kt @@ -30,33 +30,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { fun intersectPrecedence(pair: Pair) = runTest(pair) fun parametersForIntersectPrecedence(): List> = listOf( // two by two binary operators - /* (intersect, intersect_all) */ "a intersect b intersect all c" to "(bag_op (intersect) (all) (bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect, except) */ "a intersect b except c" to "(bag_op (except) (distinct) (bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect, except_all) */ "a intersect b except all c" to "(bag_op (except) (all) (bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect, union) */ "a intersect b union c" to "(bag_op (union) (distinct) (bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect, union_all) */ "a intersect b union all c" to "(bag_op (union) (all) (bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect, and) */ "a intersect b and c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, or) */ "a intersect b or c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, =) */ "a intersect b = c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, <>) */ "a intersect b <> c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, in) */ "a intersect b in c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, not_in) */ "a intersect b not in c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (intersect, <) */ "a intersect b < c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, <=) */ "a intersect b <= c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, >) */ "a intersect b > c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, >=) */ "a intersect b >= c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, between) */ "a intersect b between w and c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, not_between) */ "a intersect b not between y and c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (intersect, like) */ "a intersect b like c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (intersect, not_like) */ "a intersect b not like c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (intersect, +) */ "a intersect b + c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, -) */ "a intersect b - c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, ||) */ "a intersect b || c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, *) */ "a intersect b * c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, /) */ "a intersect b / c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, %) */ "a intersect b % c" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect, is) */ "a intersect b is boolean" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (intersect, is_not) */ "a intersect b is not boolean" to "(bag_op (intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (intersect, intersect_all) */ "a intersect b intersect all c" to "(bag_op (outer_intersect) (all) (bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect, except) */ "a intersect b except c" to "(bag_op (outer_except) (distinct) (bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect, except_all) */ "a intersect b except all c" to "(bag_op (outer_except) (all) (bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect, union) */ "a intersect b union c" to "(bag_op (outer_union) (distinct) (bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect, union_all) */ "a intersect b union all c" to "(bag_op (outer_union) (all) (bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect, and) */ "a intersect b and c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, or) */ "a intersect b or c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, =) */ "a intersect b = c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, <>) */ "a intersect b <> c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, in) */ "a intersect b in c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, not_in) */ "a intersect b not in c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (intersect, <) */ "a intersect b < c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, <=) */ "a intersect b <= c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, >) */ "a intersect b > c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, >=) */ "a intersect b >= c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, between) */ "a intersect b between w and c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, not_between) */ "a intersect b not between y and c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (intersect, like) */ "a intersect b like c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (intersect, not_like) */ "a intersect b not like c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (intersect, +) */ "a intersect b + c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, -) */ "a intersect b - c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, ||) */ "a intersect b || c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, *) */ "a intersect b * c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, /) */ "a intersect b / c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, %) */ "a intersect b % c" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect, is) */ "a intersect b is boolean" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (intersect, is_not) */ "a intersect b is not boolean" to "(bag_op (outer_intersect) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -64,33 +64,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun intersectAllPrecedence(pair: Pair) = runTest(pair) fun parametersForIntersectAllPrecedence() = listOf( - /* (intersect_all, intersect) */ "a intersect all b intersect c" to "(bag_op (intersect) (distinct) (bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect_all, except) */ "a intersect all b except c" to "(bag_op (except) (distinct) (bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect_all, except_all) */ "a intersect all b except all c" to "(bag_op (except) (all) (bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect_all, union) */ "a intersect all b union c" to "(bag_op (union) (distinct) (bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect_all, union_all) */ "a intersect all b union all c" to "(bag_op (union) (all) (bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (intersect_all, and) */ "a intersect all b and c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, or) */ "a intersect all b or c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, =) */ "a intersect all b = c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, <>) */ "a intersect all b <> c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, in) */ "a intersect all b in c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, not_in) */ "a intersect all b not in c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (intersect_all, <) */ "a intersect all b < c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, <=) */ "a intersect all b <= c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, >) */ "a intersect all b > c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, >=) */ "a intersect all b >= c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, between) */ "a intersect all b between w and c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, not_between) */ "a intersect all b not between y and c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (intersect_all, like) */ "a intersect all b like c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (intersect_all, not_like) */ "a intersect all b not like c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (intersect_all, +) */ "a intersect all b + c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, -) */ "a intersect all b - c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, ||) */ "a intersect all b || c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, *) */ "a intersect all b * c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, /) */ "a intersect all b / c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, %) */ "a intersect all b % c" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (intersect_all, is) */ "a intersect all b is boolean" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (intersect_all, is_not) */ "a intersect all b is not boolean" to "(bag_op (intersect) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (intersect_all, intersect) */ "a intersect all b intersect c" to "(bag_op (outer_intersect) (distinct) (bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect_all, except) */ "a intersect all b except c" to "(bag_op (outer_except) (distinct) (bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect_all, except_all) */ "a intersect all b except all c" to "(bag_op (outer_except) (all) (bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect_all, union) */ "a intersect all b union c" to "(bag_op (outer_union) (distinct) (bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect_all, union_all) */ "a intersect all b union all c" to "(bag_op (outer_union) (all) (bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (intersect_all, and) */ "a intersect all b and c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, or) */ "a intersect all b or c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, =) */ "a intersect all b = c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, <>) */ "a intersect all b <> c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, in) */ "a intersect all b in c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, not_in) */ "a intersect all b not in c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (intersect_all, <) */ "a intersect all b < c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, <=) */ "a intersect all b <= c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, >) */ "a intersect all b > c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, >=) */ "a intersect all b >= c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, between) */ "a intersect all b between w and c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, not_between) */ "a intersect all b not between y and c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (intersect_all, like) */ "a intersect all b like c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (intersect_all, not_like) */ "a intersect all b not like c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (intersect_all, +) */ "a intersect all b + c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, -) */ "a intersect all b - c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, ||) */ "a intersect all b || c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, *) */ "a intersect all b * c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, /) */ "a intersect all b / c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, %) */ "a intersect all b % c" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (intersect_all, is) */ "a intersect all b is boolean" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (intersect_all, is_not) */ "a intersect all b is not boolean" to "(bag_op (outer_intersect) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -98,33 +98,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun exceptPrecedence(pair: Pair) = runTest(pair) fun parametersForExceptPrecedence() = listOf( - /* (except, intersect) */ "a except b intersect c" to "(bag_op (intersect) (distinct) (bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except, intersect_all) */ "a except b intersect all c" to "(bag_op (intersect) (all) (bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except, except_all) */ "a except b except all c" to "(bag_op (except) (all) (bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except, union) */ "a except b union c" to "(bag_op (union) (distinct) (bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except, union_all) */ "a except b union all c" to "(bag_op (union) (all) (bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except, and) */ "a except b and c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, or) */ "a except b or c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, =) */ "a except b = c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, <>) */ "a except b <> c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, in) */ "a except b in c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, not_in) */ "a except b not in c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (except, <) */ "a except b < c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, <=) */ "a except b <= c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, >) */ "a except b > c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, >=) */ "a except b >= c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, between) */ "a except b between w and c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, not_between) */ "a except b not between y and c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (except, like) */ "a except b like c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (except, not_like) */ "a except b not like c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (except, +) */ "a except b + c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, -) */ "a except b - c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, ||) */ "a except b || c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, *) */ "a except b * c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, /) */ "a except b / c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, %) */ "a except b % c" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except, is) */ "a except b is boolean" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (except, is_not) */ "a except b is not boolean" to "(bag_op (except) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (except, intersect) */ "a except b intersect c" to "(bag_op (outer_intersect) (distinct) (bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except, intersect_all) */ "a except b intersect all c" to "(bag_op (outer_intersect) (all) (bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except, except_all) */ "a except b except all c" to "(bag_op (outer_except) (all) (bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except, union) */ "a except b union c" to "(bag_op (outer_union) (distinct) (bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except, union_all) */ "a except b union all c" to "(bag_op (outer_union) (all) (bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except, and) */ "a except b and c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, or) */ "a except b or c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, =) */ "a except b = c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, <>) */ "a except b <> c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, in) */ "a except b in c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, not_in) */ "a except b not in c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (except, <) */ "a except b < c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, <=) */ "a except b <= c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, >) */ "a except b > c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, >=) */ "a except b >= c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, between) */ "a except b between w and c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, not_between) */ "a except b not between y and c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (except, like) */ "a except b like c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (except, not_like) */ "a except b not like c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (except, +) */ "a except b + c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, -) */ "a except b - c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, ||) */ "a except b || c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, *) */ "a except b * c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, /) */ "a except b / c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, %) */ "a except b % c" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except, is) */ "a except b is boolean" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (except, is_not) */ "a except b is not boolean" to "(bag_op (outer_except) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -132,33 +132,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun exceptAllPrecedence(pair: Pair) = runTest(pair) fun parametersForExceptAllPrecedence() = listOf( - /* (except_all, intersect) */ "a except all b intersect c" to "(bag_op (intersect) (distinct) (bag_op (except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except_all, intersect_all) */ "a except all b intersect all c" to "(bag_op (intersect) (all) (bag_op (except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except_all, except) */ "a except all b except c" to "(bag_op (except) (distinct) (bag_op (except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except_all, union) */ "a except all b union c" to "(bag_op (union) (distinct) (bag_op (except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except_all, union_all) */ "a except all b union all c" to "(bag_op (union) (all) (bag_op (except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (except_all, and) */ "a except all b and c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, or) */ "a except all b or c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, =) */ "a except all b = c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, <>) */ "a except all b <> c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, in) */ "a except all b in c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, not_in) */ "a except all b not in c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (except_all, <) */ "a except all b < c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, <=) */ "a except all b <= c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, >) */ "a except all b > c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, >=) */ "a except all b >= c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, between) */ "a except all b between w and c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, not_between) */ "a except all b not between y and c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (except_all, like) */ "a except all b like c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (except_all, not_like) */ "a except all b not like c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (except_all, +) */ "a except all b + c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, -) */ "a except all b - c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, ||) */ "a except all b || c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, *) */ "a except all b * c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, /) */ "a except all b / c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, %) */ "a except all b % c" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (except_all, is) */ "a except all b is boolean" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (except_all, is_not) */ "a except all b is not boolean" to "(bag_op (except) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (except_all, intersect) */ "a except all b intersect c" to "(bag_op (outer_intersect) (distinct) (bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except_all, intersect_all) */ "a except all b intersect all c" to "(bag_op (outer_intersect) (all) (bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except_all, except) */ "a except all b except c" to "(bag_op (outer_except) (distinct) (bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except_all, union) */ "a except all b union c" to "(bag_op (outer_union) (distinct) (bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except_all, union_all) */ "a except all b union all c" to "(bag_op (outer_union) (all) (bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (except_all, and) */ "a except all b and c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, or) */ "a except all b or c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, =) */ "a except all b = c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, <>) */ "a except all b <> c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, in) */ "a except all b in c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, not_in) */ "a except all b not in c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (except_all, <) */ "a except all b < c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, <=) */ "a except all b <= c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, >) */ "a except all b > c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, >=) */ "a except all b >= c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, between) */ "a except all b between w and c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, not_between) */ "a except all b not between y and c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (except_all, like) */ "a except all b like c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (except_all, not_like) */ "a except all b not like c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (except_all, +) */ "a except all b + c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, -) */ "a except all b - c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, ||) */ "a except all b || c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, *) */ "a except all b * c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, /) */ "a except all b / c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, %) */ "a except all b % c" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (except_all, is) */ "a except all b is boolean" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (except_all, is_not) */ "a except all b is not boolean" to "(bag_op (outer_except) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -166,33 +166,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun unionPrecedence(pair: Pair) = runTest(pair) fun parametersForUnionPrecedence() = listOf( - /* (union, intersect) */ "a union b intersect c" to "(bag_op (intersect) (distinct) (bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union, intersect_all) */ "a union b intersect all c" to "(bag_op (intersect) (all) (bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union, except) */ "a union b except c" to "(bag_op (except) (distinct) (bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union, except_all) */ "a union b except all c" to "(bag_op (except) (all) (bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union, union_all) */ "a union b union all c" to "(bag_op (union) (all) (bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union, and) */ "a union b and c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, or) */ "a union b or c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, =) */ "a union b = c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, <>) */ "a union b <> c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, in) */ "a union b in c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, not_in) */ "a union b not in c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (union, <) */ "a union b < c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, <=) */ "a union b <= c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, >) */ "a union b > c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, >=) */ "a union b >= c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, between) */ "a union b between w and c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, not_between) */ "a union b not between y and c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (union, like) */ "a union b like c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (union, not_like) */ "a union b not like c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (union, +) */ "a union b + c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, -) */ "a union b - c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, ||) */ "a union b || c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, *) */ "a union b * c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, /) */ "a union b / c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, %) */ "a union b % c" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union, is) */ "a union b is boolean" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (union, is_not) */ "a union b is not boolean" to "(bag_op (union) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (union, intersect) */ "a union b intersect c" to "(bag_op (outer_intersect) (distinct) (bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union, intersect_all) */ "a union b intersect all c" to "(bag_op (outer_intersect) (all) (bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union, except) */ "a union b except c" to "(bag_op (outer_except) (distinct) (bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union, except_all) */ "a union b except all c" to "(bag_op (outer_except) (all) (bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union, union_all) */ "a union b union all c" to "(bag_op (outer_union) (all) (bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union, and) */ "a union b and c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, or) */ "a union b or c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, =) */ "a union b = c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, <>) */ "a union b <> c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, in) */ "a union b in c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, not_in) */ "a union b not in c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (union, <) */ "a union b < c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, <=) */ "a union b <= c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, >) */ "a union b > c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, >=) */ "a union b >= c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, between) */ "a union b between w and c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, not_between) */ "a union b not between y and c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (union, like) */ "a union b like c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (union, not_like) */ "a union b not like c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (union, +) */ "a union b + c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, -) */ "a union b - c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, ||) */ "a union b || c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, *) */ "a union b * c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, /) */ "a union b / c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, %) */ "a union b % c" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union, is) */ "a union b is boolean" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (union, is_not) */ "a union b is not boolean" to "(bag_op (outer_union) (distinct) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -200,33 +200,33 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun unionAllPrecedence(pair: Pair) = runTest(pair) fun parametersForUnionAllPrecedence() = listOf( - /* (union_all, intersect) */ "a union all b intersect c" to "(bag_op (intersect) (distinct) (bag_op (union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union_all, intersect_all) */ "a union all b intersect all c" to "(bag_op (intersect) (all) (bag_op (union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union_all, except) */ "a union all b except c" to "(bag_op (except) (distinct) (bag_op (union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union_all, except_all) */ "a union all b except all c" to "(bag_op (except) (all) (bag_op (union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union_all, union) */ "a union all b union c" to "(bag_op (union) (distinct) (bag_op (union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (union_all, and) */ "a union all b and c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, or) */ "a union all b or c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, =) */ "a union all b = c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, <>) */ "a union all b <> c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, in) */ "a union all b in c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, not_in) */ "a union all b not in c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (union_all, <) */ "a union all b < c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, <=) */ "a union all b <= c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, >) */ "a union all b > c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, >=) */ "a union all b >= c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, between) */ "a union all b between w and c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, not_between) */ "a union all b not between y and c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", - /* (union_all, like) */ "a union all b like c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", - /* (union_all, not_like) */ "a union all b not like c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", - /* (union_all, +) */ "a union all b + c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, -) */ "a union all b - c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, ||) */ "a union all b || c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, *) */ "a union all b * c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, /) */ "a union all b / c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, %) */ "a union all b % c" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", - /* (union_all, is) */ "a union all b is boolean" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", - /* (union_all, is_not) */ "a union all b is not boolean" to "(bag_op (union) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" + /* (union_all, intersect) */ "a union all b intersect c" to "(bag_op (outer_intersect) (distinct) (bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union_all, intersect_all) */ "a union all b intersect all c" to "(bag_op (outer_intersect) (all) (bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union_all, except) */ "a union all b except c" to "(bag_op (outer_except) (distinct) (bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union_all, except_all) */ "a union all b except all c" to "(bag_op (outer_except) (all) (bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union_all, union) */ "a union all b union c" to "(bag_op (outer_union) (distinct) (bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (union_all, and) */ "a union all b and c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, or) */ "a union all b or c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (or (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, =) */ "a union all b = c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, <>) */ "a union all b <> c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, in) */ "a union all b in c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, not_in) */ "a union all b not in c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (not (in_collection (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (union_all, <) */ "a union all b < c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (lt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, <=) */ "a union all b <= c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (lte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, >) */ "a union all b > c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (gt (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, >=) */ "a union all b >= c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (gte (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, between) */ "a union all b between w and c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (between (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, not_between) */ "a union all b not between y and c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (not (between (id b (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)))))", + /* (union_all, like) */ "a union all b like c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null))", + /* (union_all, not_like) */ "a union all b not like c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (not (like (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified)) null)))", + /* (union_all, +) */ "a union all b + c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (plus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, -) */ "a union all b - c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (minus (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, ||) */ "a union all b || c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (concat (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, *) */ "a union all b * c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (times (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, /) */ "a union all b / c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (divide (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, %) */ "a union all b % c" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (modulo (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", + /* (union_all, is) */ "a union all b is boolean" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (is_type (id b (case_insensitive) (unqualified)) (boolean_type)))", + /* (union_all, is_not) */ "a union all b is not boolean" to "(bag_op (outer_union) (all) (id a (case_insensitive) (unqualified)) (not (is_type (id b (case_insensitive) (unqualified)) (boolean_type))))" ) @Test @@ -234,12 +234,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun andPrecedence(pair: Pair) = runTest(pair) fun parametersForAndPrecedence() = listOf( - /* (and, intersect) */ "a and b intersect c" to "(bag_op (intersect) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (and, intersect_all) */ "a and b intersect all c" to "(bag_op (intersect) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (and, except) */ "a and b except c" to "(bag_op (except) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (and, except_all) */ "a and b except all c" to "(bag_op (except) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (and, union) */ "a and b union c" to "(bag_op (union) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (and, union_all) */ "a and b union all c" to "(bag_op (union) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, intersect) */ "a and b intersect c" to "(bag_op (outer_intersect) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, intersect_all) */ "a and b intersect all c" to "(bag_op (outer_intersect) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, except) */ "a and b except c" to "(bag_op (outer_except) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, except_all) */ "a and b except all c" to "(bag_op (outer_except) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, union) */ "a and b union c" to "(bag_op (outer_union) (distinct) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (and, union_all) */ "a and b union all c" to "(bag_op (outer_union) (all) (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (and, or) */ "a and b or c" to "(or (and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (and, =) */ "a and b = c" to "(and (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", /* (and, <>) */ "a and b <> c" to "(and (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", @@ -268,12 +268,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun orPrecedence(pair: Pair) = runTest(pair) fun parametersForOrPrecedence() = listOf( - /* (or, intersect) */ "a or b intersect c" to "(bag_op (intersect) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (or, intersect_all) */ "a or b intersect all c " to "(bag_op (intersect) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (or, except) */ "a or b except c" to "(bag_op (except) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (or, except_all) */ "a or b except all c " to "(bag_op (except) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (or, union) */ "a or b union c" to "(bag_op (union) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (or, union_all) */ "a or b union all c " to "(bag_op (union) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, intersect) */ "a or b intersect c" to "(bag_op (outer_intersect) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, intersect_all) */ "a or b intersect all c " to "(bag_op (outer_intersect) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, except) */ "a or b except c" to "(bag_op (outer_except) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, except_all) */ "a or b except all c " to "(bag_op (outer_except) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, union) */ "a or b union c" to "(bag_op (outer_union) (distinct) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (or, union_all) */ "a or b union all c " to "(bag_op (outer_union) (all) (or (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (or, and) */ "a or b and c" to "(or (id a (case_insensitive) (unqualified)) (and (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", /* (or, =) */ "a or b = c" to "(or (id a (case_insensitive) (unqualified)) (eq (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", /* (or, <>) */ "a or b <> c" to "(or (id a (case_insensitive) (unqualified)) (ne (id b (case_insensitive) (unqualified)) (id c (case_insensitive) (unqualified))))", @@ -302,12 +302,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun equalsPrecedence(pair: Pair) = runTest(pair) fun parametersForEqualsPrecedence() = listOf( - /* (=, intersect) */ "a = b intersect c" to "(bag_op (intersect) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (=, intersect_all) */ "a = b intersect all c " to "(bag_op (intersect) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (=, except) */ "a = b except c" to "(bag_op (except) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (=, except_all) */ "a = b except all c " to "(bag_op (except) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (=, union) */ "a = b union c" to "(bag_op (union) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (=, union_all) */ "a = b union all c " to "(bag_op (union) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, intersect) */ "a = b intersect c" to "(bag_op (outer_intersect) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, intersect_all) */ "a = b intersect all c " to "(bag_op (outer_intersect) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, except) */ "a = b except c" to "(bag_op (outer_except) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, except_all) */ "a = b except all c " to "(bag_op (outer_except) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, union) */ "a = b union c" to "(bag_op (outer_union) (distinct) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (=, union_all) */ "a = b union all c " to "(bag_op (outer_union) (all) (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (=, or) */ "a = b or c" to "(or (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (=, and) */ "a = b and c" to "(and (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (=, <>) */ "a = b <> c" to "(ne (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -328,12 +328,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun notEqualPrecedence(pair: Pair) = runTest(pair) fun parametersForNotEqualPrecedence() = listOf( - /* (<>, intersect) */ "a <> b intersect c" to "(bag_op (intersect) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<>, intersect_all) */ "a <> b intersect all c" to "(bag_op (intersect) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<>, except) */ "a <> b except c" to "(bag_op (except) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<>, except_all) */ "a <> b except all c" to "(bag_op (except) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<>, union) */ "a <> b union c" to "(bag_op (union) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<>, union_all) */ "a <> b union all c" to "(bag_op (union) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, intersect) */ "a <> b intersect c" to "(bag_op (outer_intersect) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, intersect_all) */ "a <> b intersect all c" to "(bag_op (outer_intersect) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, except) */ "a <> b except c" to "(bag_op (outer_except) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, except_all) */ "a <> b except all c" to "(bag_op (outer_except) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, union) */ "a <> b union c" to "(bag_op (outer_union) (distinct) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<>, union_all) */ "a <> b union all c" to "(bag_op (outer_union) (all) (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<>, or) */ "a <> b or c" to "(or (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<>, and) */ "a <> b and c" to "(and (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<>, =) */ "a <> b = c" to "(eq (ne (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -354,12 +354,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun isPrecedence(pair: Pair) = runTest(pair) fun parametersForIsPrecedence() = listOf( - /* (is, intersect) */ "a is boolean intersect c" to "(bag_op (intersect) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", - /* (is, intersect_all) */ "a is boolean intersect all c" to "(bag_op (intersect) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", - /* (is, except) */ "a is boolean except c" to "(bag_op (except) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", - /* (is, except_all) */ "a is boolean except all c" to "(bag_op (except) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", - /* (is, union) */ "a is boolean union c" to "(bag_op (union) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", - /* (is, union_all) */ "a is boolean union all c" to "(bag_op (union) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, intersect) */ "a is boolean intersect c" to "(bag_op (outer_intersect) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, intersect_all) */ "a is boolean intersect all c" to "(bag_op (outer_intersect) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, except) */ "a is boolean except c" to "(bag_op (outer_except) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, except_all) */ "a is boolean except all c" to "(bag_op (outer_except) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, union) */ "a is boolean union c" to "(bag_op (outer_union) (distinct) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", + /* (is, union_all) */ "a is boolean union all c" to "(bag_op (outer_union) (all) (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", /* (is, or) */ "a is boolean or c" to "(or (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", /* (is, and) */ "a is boolean and c" to "(and (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", /* (is, =) */ "a is boolean = c" to "(eq (is_type (id a (case_insensitive) (unqualified)) (boolean_type)) (id c (case_insensitive) (unqualified)))", @@ -381,11 +381,11 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun isNotPrecedence(pair: Pair) = runTest(pair) fun parametersForIsNotPrecedence() = listOf( - /* (not (is, intersect) */ "a is not boolean intersect c" to "(bag_op (intersect) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", - /* (not (is, intersect_all) */ "a is not boolean intersect all c" to "(bag_op (intersect) (all) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", - /* (not (is, except) */ "a is not boolean except c" to "(bag_op (except) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", - /* (not (is, union) */ "a is not boolean union c" to "(bag_op (union) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", - /* (not (is, union_all) */ "a is not boolean union all c" to "(bag_op (union) (all) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", + /* (not (is, intersect) */ "a is not boolean intersect c" to "(bag_op (outer_intersect) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", + /* (not (is, intersect_all) */ "a is not boolean intersect all c" to "(bag_op (outer_intersect) (all) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", + /* (not (is, except) */ "a is not boolean except c" to "(bag_op (outer_except) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", + /* (not (is, union) */ "a is not boolean union c" to "(bag_op (outer_union) (distinct) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", + /* (not (is, union_all) */ "a is not boolean union all c" to "(bag_op (outer_union) (all) (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", /* (not (is, or) */ "a is not boolean or c" to "(or (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", /* (not (is, and) */ "a is not boolean and c" to "(and (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", /* (not (is, =) */ "a is not boolean = c" to "(eq (not (is_type (id a (case_insensitive) (unqualified)) (boolean_type))) (id c (case_insensitive) (unqualified)))", @@ -408,12 +408,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun inPrecedence(pair: Pair) = runTest(pair) fun parametersForInPrecedence() = listOf( - /* (in, intersect) */ "a in b intersect c" to "(bag_op (intersect) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (in, intersect_all) */ "a in b intersect all c" to "(bag_op (intersect) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (in, except) */ "a in b except c" to "(bag_op (except) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (in, except_all) */ "a in b except all c" to "(bag_op (except) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (in, union) */ "a in b union c" to "(bag_op (union) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (in, union_all) */ "a in b union all c" to "(bag_op (union) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, intersect) */ "a in b intersect c" to "(bag_op (outer_intersect) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, intersect_all) */ "a in b intersect all c" to "(bag_op (outer_intersect) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, except) */ "a in b except c" to "(bag_op (outer_except) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, except_all) */ "a in b except all c" to "(bag_op (outer_except) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, union) */ "a in b union c" to "(bag_op (outer_union) (distinct) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (in, union_all) */ "a in b union all c" to "(bag_op (outer_union) (all) (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (in, or) */ "a in b or c" to "(or (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (in, and) */ "a in b and c" to "(and (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (in, =) */ "a in b = c" to "(eq (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -434,12 +434,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun notInPrecedence(pair: Pair) = runTest(pair) fun parametersForNotInPrecedence() = listOf( - /* (not (in, intersect) */ "a not in b intersect c" to "(bag_op (intersect) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (in, intersect_all) */ "a not in b intersect all c" to "(bag_op (intersect) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (in, except) */ "a not in b except c" to "(bag_op (except) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (in, except_all) */ "a not in b except all c" to "(bag_op (except) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (in, union) */ "a not in b union c" to "(bag_op (union) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (in, union_all) */ "a not in b union all c" to "(bag_op (union) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, intersect) */ "a not in b intersect c" to "(bag_op (outer_intersect) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, intersect_all) */ "a not in b intersect all c" to "(bag_op (outer_intersect) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, except) */ "a not in b except c" to "(bag_op (outer_except) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, except_all) */ "a not in b except all c" to "(bag_op (outer_except) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, union) */ "a not in b union c" to "(bag_op (outer_union) (distinct) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (in, union_all) */ "a not in b union all c" to "(bag_op (outer_union) (all) (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (in, or) */ "a not in b or c" to "(or (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (in, and) */ "a not in b and c" to "(and (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (in, =) */ "a not in b = c" to "(eq (not (in_collection (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", @@ -460,12 +460,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun ltPrecedence(pair: Pair) = runTest(pair) fun parametersForLtPrecedence() = listOf( - /* (<, intersect) */ "a < b intersect c" to "(bag_op (intersect) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<, intersect_all) */ "a < b intersect all c" to "(bag_op (intersect) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<, except) */ "a < b except c" to "(bag_op (except) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<, except_all) */ "a < b except all c" to "(bag_op (except) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<, union) */ "a < b union c" to "(bag_op (union) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<, union_all) */ "a < b union all c" to "(bag_op (union) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, intersect) */ "a < b intersect c" to "(bag_op (outer_intersect) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, intersect_all) */ "a < b intersect all c" to "(bag_op (outer_intersect) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, except) */ "a < b except c" to "(bag_op (outer_except) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, except_all) */ "a < b except all c" to "(bag_op (outer_except) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, union) */ "a < b union c" to "(bag_op (outer_union) (distinct) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<, union_all) */ "a < b union all c" to "(bag_op (outer_union) (all) (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<, or) */ "a < b or c" to "(or (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<, and) */ "a < b and c" to "(and (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<, =) */ "a < b = c" to "(eq (lt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -494,12 +494,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun ltePrecedence(pair: Pair) = runTest(pair) fun parametersForLtePrecedence() = listOf( - /* (<=, intersect) */ "a <= b intersect c" to "(bag_op (intersect) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<=, intersect_all) */ "a <= b intersect all c" to "(bag_op (intersect) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<=, except) */ "a <= b except c" to "(bag_op (except) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<=, except_all) */ "a <= b except all c" to "(bag_op (except) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<=, union) */ "a <= b union c" to "(bag_op (union) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (<=, union_all) */ "a <= b union all c" to "(bag_op (union) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, intersect) */ "a <= b intersect c" to "(bag_op (outer_intersect) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, intersect_all) */ "a <= b intersect all c" to "(bag_op (outer_intersect) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, except) */ "a <= b except c" to "(bag_op (outer_except) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, except_all) */ "a <= b except all c" to "(bag_op (outer_except) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, union) */ "a <= b union c" to "(bag_op (outer_union) (distinct) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (<=, union_all) */ "a <= b union all c" to "(bag_op (outer_union) (all) (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<=, or) */ "a <= b or c" to "(or (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<=, and) */ "a <= b and c" to "(and (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (<=, =) */ "a <= b = c" to "(eq (lte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -528,12 +528,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun gtPrecedence(pair: Pair) = runTest(pair) fun parametersForGtPrecedence() = listOf( - /* (>, intersect) */ "a > b intersect c" to "(bag_op (intersect) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>, intersect_all) */ "a > b intersect all c" to "(bag_op (intersect) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>, except) */ "a > b except c" to "(bag_op (except) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>, except_all) */ "a > b except all c" to "(bag_op (except) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>, union) */ "a > b union c" to "(bag_op (union) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>, union_all) */ "a > b union all c" to "(bag_op (union) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, intersect) */ "a > b intersect c" to "(bag_op (outer_intersect) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, intersect_all) */ "a > b intersect all c" to "(bag_op (outer_intersect) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, except) */ "a > b except c" to "(bag_op (outer_except) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, except_all) */ "a > b except all c" to "(bag_op (outer_except) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, union) */ "a > b union c" to "(bag_op (outer_union) (distinct) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>, union_all) */ "a > b union all c" to "(bag_op (outer_union) (all) (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>, or) */ "a > b or c" to "(or (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>, and) */ "a > b and c" to "(and (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>, =) */ "a > b = c" to "(eq (gt (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -562,12 +562,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun gtePrecedence(pair: Pair) = runTest(pair) fun parametersForGtePrecedence() = listOf( - /* (>=, intersect) */ "a >= b intersect c" to "(bag_op (intersect) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>=, intersect_all) */ "a >= b intersect all c" to "(bag_op (intersect) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>=, except) */ "a >= b except c" to "(bag_op (except) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>=, except_all) */ "a >= b except all c" to "(bag_op (except) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>=, union) */ "a >= b union c" to "(bag_op (union) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (>=, union_all) */ "a >= b union all c" to "(bag_op (union) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, intersect) */ "a >= b intersect c" to "(bag_op (outer_intersect) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, intersect_all) */ "a >= b intersect all c" to "(bag_op (outer_intersect) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, except) */ "a >= b except c" to "(bag_op (outer_except) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, except_all) */ "a >= b except all c" to "(bag_op (outer_except) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, union) */ "a >= b union c" to "(bag_op (outer_union) (distinct) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (>=, union_all) */ "a >= b union all c" to "(bag_op (outer_union) (all) (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>=, or) */ "a >= b or c" to "(or (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>=, and) */ "a >= b and c" to "(and (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (>=, =) */ "a >= b = c" to "(eq (gte (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -596,12 +596,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun betweenPrecedence(pair: Pair) = runTest(pair) fun parametersForBetweenPrecedence() = listOf( - /* (between, intersect) */ "a between b and w intersect c" to "(bag_op (intersect) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (between, intersect_all) */ "a between b and w intersect all c" to "(bag_op (intersect) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (between, except) */ "a between b and w except c" to "(bag_op (except) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (between, except_all) */ "a between b and w except all c" to "(bag_op (except) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (between, union) */ "a between b and w union c" to "(bag_op (union) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (between, union_all) */ "a between b and w union all c" to "(bag_op (union) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, intersect) */ "a between b and w intersect c" to "(bag_op (outer_intersect) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, intersect_all) */ "a between b and w intersect all c" to "(bag_op (outer_intersect) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, except) */ "a between b and w except c" to "(bag_op (outer_except) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, except_all) */ "a between b and w except all c" to "(bag_op (outer_except) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, union) */ "a between b and w union c" to "(bag_op (outer_union) (distinct) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (between, union_all) */ "a between b and w union all c" to "(bag_op (outer_union) (all) (between (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (between, or) */ "a between w and b or c" to "(or (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (between, and) */ "a between w and b and c" to "(and (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (between, =) */ "a between w and b = c" to "(eq (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -630,12 +630,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun notBetweenPrecedence(pair: Pair) = runTest(pair) fun parametersForNotBetweenPrecedence() = listOf( - /* (not (between, intersect) */ "a not between w and b intersect c" to "(bag_op (intersect) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (between, intersect_all) */ "a not between w and b intersect all c" to "(bag_op (intersect) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (between, except) */ "a not between w and b except c" to "(bag_op (except) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (between, except_all) */ "a not between w and b except all c" to "(bag_op (except) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (between, union) */ "a not between w and b union c" to "(bag_op (union) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", - /* (not (between, union_all) */ "a not between w and b union all c" to "(bag_op (union) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, intersect) */ "a not between w and b intersect c" to "(bag_op (outer_intersect) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, intersect_all) */ "a not between w and b intersect all c" to "(bag_op (outer_intersect) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, except) */ "a not between w and b except c" to "(bag_op (outer_except) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, except_all) */ "a not between w and b except all c" to "(bag_op (outer_except) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, union) */ "a not between w and b union c" to "(bag_op (outer_union) (distinct) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", + /* (not (between, union_all) */ "a not between w and b union all c" to "(bag_op (outer_union) (all) (not (between (id a (case_insensitive) (unqualified)) (id w (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (between, or) */ "a not between y and b or c" to "(or (not (between (id a (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (between, and) */ "a not between y and b and c" to "(and (not (between (id a (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", /* (not (between, =) */ "a not between y and b = c" to "(eq (not (between (id a (case_insensitive) (unqualified)) (id y (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)))) (id c (case_insensitive) (unqualified)))", @@ -664,12 +664,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun likePrecedence(pair: Pair) = runTest(pair) fun parametersForLikePrecedence() = listOf( - /* (like, intersect) */ "a like b intersect c" to "(bag_op (intersect) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", - /* (like, intersect_all) */ "a like b intersect all c" to "(bag_op (intersect) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", - /* (like, except) */ "a like b except c" to "(bag_op (except) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", - /* (like, except_all) */ "a like b except all c" to "(bag_op (except) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", - /* (like, union) */ "a like b union c" to "(bag_op (union) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", - /* (like, union_all) */ "a like b union all c" to "(bag_op (union) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, intersect) */ "a like b intersect c" to "(bag_op (outer_intersect) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, intersect_all) */ "a like b intersect all c" to "(bag_op (outer_intersect) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, except) */ "a like b except c" to "(bag_op (outer_except) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, except_all) */ "a like b except all c" to "(bag_op (outer_except) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, union) */ "a like b union c" to "(bag_op (outer_union) (distinct) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", + /* (like, union_all) */ "a like b union all c" to "(bag_op (outer_union) (all) (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", /* (like, or) */ "a like b or c" to "(or (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", /* (like, and) */ "a like b and c" to "(and (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", /* (like, =) */ "a like b = c" to "(eq (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null) (id c (case_insensitive) (unqualified)))", @@ -698,12 +698,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun notLikePrecedence(pair: Pair) = runTest(pair) fun parametersForNotLikePrecedence() = listOf( - /* (not (like, intersect) */ "a not like b intersect c" to "(bag_op (intersect) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", - /* (not (like, intersect_all) */ "a not like b intersect all c" to "(bag_op (intersect) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", - /* (not (like, except) */ "a not like b except c" to "(bag_op (except) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", - /* (not (like, except_all) */ "a not like b except all c" to "(bag_op (except) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", - /* (not (like, union) */ "a not like b union c" to "(bag_op (union) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", - /* (not (like, union_all) */ "a not like b union all c" to "(bag_op (union) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, intersect) */ "a not like b intersect c" to "(bag_op (outer_intersect) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, intersect_all) */ "a not like b intersect all c" to "(bag_op (outer_intersect) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, except) */ "a not like b except c" to "(bag_op (outer_except) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, except_all) */ "a not like b except all c" to "(bag_op (outer_except) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, union) */ "a not like b union c" to "(bag_op (outer_union) (distinct) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", + /* (not (like, union_all) */ "a not like b union all c" to "(bag_op (outer_union) (all) (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", /* (not (like, or) */ "a not like b or c" to "(or (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", /* (not (like, and) */ "a not like b and c" to "(and (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", /* (not (like, =) */ "a not like b = c" to "(eq (not (like (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified)) null)) (id c (case_insensitive) (unqualified)))", @@ -732,12 +732,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun subtractPrecedence(pair: Pair) = runTest(pair) fun parametersForSubtractPrecedence() = listOf( - /* (+, intersect) */ "a + b intersect c" to "(bag_op (intersect) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (+, intersect_all) */ "a + b intersect all c" to "(bag_op (intersect) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (+, except) */ "a + b except c" to "(bag_op (except) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (+, except_all) */ "a + b except all c" to "(bag_op (except) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (+, union) */ "a + b union c" to "(bag_op (union) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (+, union_all) */ "a + b union all c" to "(bag_op (union) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, intersect) */ "a + b intersect c" to "(bag_op (outer_intersect) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, intersect_all) */ "a + b intersect all c" to "(bag_op (outer_intersect) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, except) */ "a + b except c" to "(bag_op (outer_except) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, except_all) */ "a + b except all c" to "(bag_op (outer_except) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, union) */ "a + b union c" to "(bag_op (outer_union) (distinct) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (+, union_all) */ "a + b union all c" to "(bag_op (outer_union) (all) (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (+, or) */ "a + b or c" to "(or (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (+, and) */ "a + b and c" to "(and (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (+, =) */ "a + b = c" to "(eq (plus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -766,12 +766,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun minusPrecedence(pair: Pair) = runTest(pair) fun parametersForMinusPrecedence() = listOf( - /* (-, intersect) */ "a - b intersect c" to "(bag_op (intersect) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (-, intersect_all) */ "a - b intersect all c" to "(bag_op (intersect) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (-, except) */ "a - b except c" to "(bag_op (except) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (-, except_all) */ "a - b except all c" to "(bag_op (except) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (-, union) */ "a - b union c" to "(bag_op (union) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (-, union_all) */ "a - b union all c" to "(bag_op (union) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, intersect) */ "a - b intersect c" to "(bag_op (outer_intersect) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, intersect_all) */ "a - b intersect all c" to "(bag_op (outer_intersect) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, except) */ "a - b except c" to "(bag_op (outer_except) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, except_all) */ "a - b except all c" to "(bag_op (outer_except) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, union) */ "a - b union c" to "(bag_op (outer_union) (distinct) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (-, union_all) */ "a - b union all c" to "(bag_op (outer_union) (all) (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (-, or) */ "a - b or c" to "(or (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (-, and) */ "a - b and c" to "(and (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (-, =) */ "a - b = c" to "(eq (minus (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -800,12 +800,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun concatPrecedence(pair: Pair) = runTest(pair) fun parametersForConcatPrecedence() = listOf( - /* (||, intersect) */ "a || b intersect c" to "(bag_op (intersect) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (||, intersect_all) */ "a || b intersect all c" to "(bag_op (intersect) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (||, except) */ "a || b except c" to "(bag_op (except) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (||, except_all) */ "a || b except all c" to "(bag_op (except) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (||, union) */ "a || b union c" to "(bag_op (union) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (||, union_all) */ "a || b union all c" to "(bag_op (union) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, intersect) */ "a || b intersect c" to "(bag_op (outer_intersect) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, intersect_all) */ "a || b intersect all c" to "(bag_op (outer_intersect) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, except) */ "a || b except c" to "(bag_op (outer_except) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, except_all) */ "a || b except all c" to "(bag_op (outer_except) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, union) */ "a || b union c" to "(bag_op (outer_union) (distinct) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (||, union_all) */ "a || b union all c" to "(bag_op (outer_union) (all) (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (||, or) */ "a || b or c" to "(or (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (||, and) */ "a || b and c" to "(and (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (||, =) */ "a || b = c" to "(eq (concat (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -832,12 +832,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun mulPrecedence(pair: Pair) = runTest(pair) fun parametersForMulPrecedence() = listOf( - /* (*, intersect) */ "a * b intersect c" to "(bag_op (intersect) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (*, intersect_all) */ "a * b intersect all c" to "(bag_op (intersect) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (*, except) */ "a * b except c" to "(bag_op (except) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (*, except_all) */ "a * b except all c" to "(bag_op (except) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (*, union) */ "a * b union c" to "(bag_op (union) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (*, union_all) */ "a * b union all c" to "(bag_op (union) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, intersect) */ "a * b intersect c" to "(bag_op (outer_intersect) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, intersect_all) */ "a * b intersect all c" to "(bag_op (outer_intersect) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, except) */ "a * b except c" to "(bag_op (outer_except) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, except_all) */ "a * b except all c" to "(bag_op (outer_except) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, union) */ "a * b union c" to "(bag_op (outer_union) (distinct) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (*, union_all) */ "a * b union all c" to "(bag_op (outer_union) (all) (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (*, or) */ "a * b or c" to "(or (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (*, and) */ "a * b and c" to "(and (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (*, =) */ "a * b = c" to "(eq (times (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -866,12 +866,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun divPrecedence(pair: Pair) = runTest(pair) fun parametersForDivPrecedence() = listOf( - /* (/, intersect) */ "a / b intersect c" to "(bag_op (intersect) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (/, intersect_all) */ "a / b intersect all c" to "(bag_op (intersect) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (/, except) */ "a / b except c" to "(bag_op (except) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (/, except_all) */ "a / b except all c" to "(bag_op (except) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (/, union) */ "a / b union c" to "(bag_op (union) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (/, union_all) */ "a / b union all c" to "(bag_op (union) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, intersect) */ "a / b intersect c" to "(bag_op (outer_intersect) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, intersect_all) */ "a / b intersect all c" to "(bag_op (outer_intersect) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, except) */ "a / b except c" to "(bag_op (outer_except) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, except_all) */ "a / b except all c" to "(bag_op (outer_except) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, union) */ "a / b union c" to "(bag_op (outer_union) (distinct) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (/, union_all) */ "a / b union all c" to "(bag_op (outer_union) (all) (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (/, or) */ "a / b or c" to "(or (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (/, and) */ "a / b and c" to "(and (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (/, =) */ "a / b = c" to "(eq (divide (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -900,12 +900,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun modPrecedence(pair: Pair) = runTest(pair) fun parametersForModPrecedence() = listOf( - /* (%, intersect) */ "a % b intersect c" to "(bag_op (intersect) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (%, intersect_all) */ "a % b intersect all c" to "(bag_op (intersect) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (%, except) */ "a % b except c" to "(bag_op (except) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (%, except_all) */ "a % b except all c" to "(bag_op (except) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (%, union) */ "a % b union c" to "(bag_op (union) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (%, union_all) */ "a % b union all c" to "(bag_op (union) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, intersect) */ "a % b intersect c" to "(bag_op (outer_intersect) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, intersect_all) */ "a % b intersect all c" to "(bag_op (outer_intersect) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, except) */ "a % b except c" to "(bag_op (outer_except) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, except_all) */ "a % b except all c" to "(bag_op (outer_except) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, union) */ "a % b union c" to "(bag_op (outer_union) (distinct) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (%, union_all) */ "a % b union all c" to "(bag_op (outer_union) (all) (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (%, or) */ "a % b or c" to "(or (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (%, and) */ "a % b and c" to "(and (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (%, =) */ "a % b = c" to "(eq (modulo (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -934,12 +934,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun bitwiseAndPrecedence(pair: Pair) = runTest(pair) fun parametersForBitwiseAndPrecedence() = listOf( - /* (&, intersect) */ "a & b intersect c" to "(bag_op (intersect) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (&, intersect_all) */ "a & b intersect all c" to "(bag_op (intersect) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (&, except) */ "a & b except c" to "(bag_op (except) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (&, except_all) */ "a & b except all c" to "(bag_op (except) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (&, union) */ "a & b union c" to "(bag_op (union) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", - /* (&, union_all) */ "a & b union all c" to "(bag_op (union) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, intersect) */ "a & b intersect c" to "(bag_op (outer_intersect) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, intersect_all) */ "a & b intersect all c" to "(bag_op (outer_intersect) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, except) */ "a & b except c" to "(bag_op (outer_except) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, except_all) */ "a & b except all c" to "(bag_op (outer_except) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, union) */ "a & b union c" to "(bag_op (outer_union) (distinct) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", + /* (&, union_all) */ "a & b union all c" to "(bag_op (outer_union) (all) (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (&, or) */ "a & b or c" to "(or (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (&, and) */ "a & b and c" to "(and (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", /* (&, =) */ "a & b = c" to "(eq (bitwise_and (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))) (id c (case_insensitive) (unqualified)))", @@ -990,12 +990,12 @@ class PartiQLParserPrecedenceTest : PartiQLParserTestBase() { @TestCaseName("{0}") fun notUnaryPrecedence(pair: Pair) = runTest(pair) fun parametersForNotUnaryPrecedence() = listOf( - /* (not, intersect) */ "not a intersect b" to "(bag_op (intersect) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", - /* (not, intersect_all) */ "not a intersect all b" to "(bag_op (intersect) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", - /* (not, except) */ "not a except b" to "(bag_op (except) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", - /* (not, except_all) */ "not a except all b" to "(bag_op (except) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", - /* (not, union) */ "not a union b" to "(bag_op (union) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", - /* (not, union_all) */ "not a union all b" to "(bag_op (union) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, intersect) */ "not a intersect b" to "(bag_op (outer_intersect) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, intersect_all) */ "not a intersect all b" to "(bag_op (outer_intersect) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, except) */ "not a except b" to "(bag_op (outer_except) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, except_all) */ "not a except all b" to "(bag_op (outer_except) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, union) */ "not a union b" to "(bag_op (outer_union) (distinct) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", + /* (not, union_all) */ "not a union all b" to "(bag_op (outer_union) (all) (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", /* (not, or) */ "not a or b" to "(or (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", /* (not, and) */ "not a and b" to "(and (not (id a (case_insensitive) (unqualified))) (id b (case_insensitive) (unqualified)))", /* (not, =) */ "not a = b" to "(not (eq (id a (case_insensitive) (unqualified)) (id b (case_insensitive) (unqualified))))", diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt index a72fefa81f..78482f90d2 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt @@ -4102,7 +4102,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a UNION b" ) { bagOp( - op = PartiqlAst.BagOpType.Union(), + op = PartiqlAst.BagOpType.OuterUnion(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4116,7 +4116,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a UNION DISTINCT b" ) { bagOp( - op = PartiqlAst.BagOpType.Union(), + op = PartiqlAst.BagOpType.OuterUnion(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4130,7 +4130,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a UNION ALL b" ) { bagOp( - op = PartiqlAst.BagOpType.Union(), + op = PartiqlAst.BagOpType.OuterUnion(), quantifier = PartiqlAst.SetQuantifier.All(), operands = listOf( id("a"), @@ -4144,7 +4144,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a INTERSECT b" ) { bagOp( - op = PartiqlAst.BagOpType.Intersect(), + op = PartiqlAst.BagOpType.OuterIntersect(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4158,7 +4158,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a INTERSECT DISTINCT b" ) { bagOp( - op = PartiqlAst.BagOpType.Intersect(), + op = PartiqlAst.BagOpType.OuterIntersect(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4172,7 +4172,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a INTERSECT ALL b" ) { bagOp( - op = PartiqlAst.BagOpType.Intersect(), + op = PartiqlAst.BagOpType.OuterIntersect(), quantifier = PartiqlAst.SetQuantifier.All(), operands = listOf( id("a"), @@ -4186,7 +4186,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a EXCEPT b" ) { bagOp( - op = PartiqlAst.BagOpType.Except(), + op = PartiqlAst.BagOpType.OuterExcept(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4200,7 +4200,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a EXCEPT DISTINCT b" ) { bagOp( - op = PartiqlAst.BagOpType.Except(), + op = PartiqlAst.BagOpType.OuterExcept(), quantifier = PartiqlAst.SetQuantifier.Distinct(), operands = listOf( id("a"), @@ -4214,7 +4214,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { "a EXCEPT ALL b" ) { bagOp( - op = PartiqlAst.BagOpType.Except(), + op = PartiqlAst.BagOpType.OuterExcept(), quantifier = PartiqlAst.SetQuantifier.All(), operands = listOf( id("a"), diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index ac30689f17..d5c02c4710 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -1113,6 +1113,17 @@ internal class PartiQLParserDefault : PartiQLParser { * */ + /** + * Verifies if all of the [args] are + * 1. [Expr.SFW] or + * 2. [Expr.BagOp] and is a SQL Set op (i.e. [Expr.BagOp.outer] != true) + */ + private fun argsAreSFW(args: List): Boolean { + return args.all { arg -> + arg is Expr.SFW || (arg is Expr.BagOp && arg.outer != true) + } + } + override fun visitIntersect(ctx: GeneratedParser.IntersectContext) = translate(ctx) { val setq = when { ctx.ALL() != null -> SetQuantifier.ALL @@ -1123,7 +1134,7 @@ internal class PartiQLParserDefault : PartiQLParser { val lhs = visitAs(ctx.lhs) val rhs = visitAs(ctx.rhs) val outer = ctx.OUTER() != null - exprBagOp(op, lhs, rhs, outer) + exprBagOp(op, lhs, rhs, outer || !argsAreSFW(listOf(lhs, rhs))) } override fun visitExcept(ctx: GeneratedParser.ExceptContext) = translate(ctx) { @@ -1136,7 +1147,7 @@ internal class PartiQLParserDefault : PartiQLParser { val lhs = visitAs(ctx.lhs) val rhs = visitAs(ctx.rhs) val outer = ctx.OUTER() != null - exprBagOp(op, lhs, rhs, outer) + exprBagOp(op, lhs, rhs, outer || !argsAreSFW(listOf(lhs, rhs))) } override fun visitUnion(ctx: GeneratedParser.UnionContext) = translate(ctx) { @@ -1149,7 +1160,7 @@ internal class PartiQLParserDefault : PartiQLParser { val lhs = visitAs(ctx.lhs) val rhs = visitAs(ctx.rhs) val outer = ctx.OUTER() != null - exprBagOp(op, lhs, rhs, outer) + exprBagOp(op, lhs, rhs, outer || !argsAreSFW(listOf(lhs, rhs))) } /** diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt new file mode 100644 index 0000000000..ccf3742c42 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt @@ -0,0 +1,510 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.api.Test +import org.partiql.ast.AstNode +import org.partiql.ast.Expr +import org.partiql.ast.From +import org.partiql.ast.SetOp +import org.partiql.ast.SetQuantifier +import org.partiql.ast.exprBagOp +import org.partiql.ast.exprCollection +import org.partiql.ast.exprLit +import org.partiql.ast.exprSFW +import org.partiql.ast.exprStruct +import org.partiql.ast.exprStructField +import org.partiql.ast.fromValue +import org.partiql.ast.selectStar +import org.partiql.ast.setOp +import org.partiql.ast.statementQuery +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.int32Value +import org.partiql.value.stringValue +import kotlin.test.assertEquals + +class PartiQLParserBagOpTests { + + private val parser = PartiQLParserDefault() + + private inline fun query(body: () -> Expr) = statementQuery(body()) + + @OptIn(PartiQLValueExperimental::class) + private fun createSFW(i: Int): Expr.SFW = + exprSFW( + select = selectStar(null), + from = fromValue( + expr = exprCollection( + type = Expr.Collection.Type.BAG, + values = listOf( + exprStruct( + listOf( + exprStructField( + exprLit(stringValue("a")), + exprLit(int32Value(i)), + ) + ) + ) + ) + ), + type = From.Value.Type.SCAN, + asAlias = null, + atAlias = null, + byAlias = null, + ), + exclude = null, + let = null, + `where` = null, + groupBy = null, + having = null, + setOp = null, + orderBy = null, + limit = null, + offset = null + ) + + @OptIn(PartiQLValueExperimental::class) + private fun createLit(i: Int) = exprLit(int32Value(i)) + + // SQL Union + @Test + fun sqlUnion() = assertExpression( + "SELECT * FROM <<{'a': 1}>> UNION SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ) + } + ) + + @Test + fun sqlUnionMultiple() = assertExpression( + "SELECT * FROM <<{'a': 1}>> UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createSFW(3), + outer = false + ) + } + ) + + @Test + fun sqlUnionMultipleRight() = assertExpression( + "SELECT * FROM <<{'a': 1}>> UNION ALL (SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>)", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.DISTINCT + ), + lhs = createSFW(2), + rhs = createSFW(3), + outer = false + ), + outer = false + ) + } + ) + + // Outer Union + @Test + fun outerUnion() = assertExpression( + "SELECT * FROM <<{'a': 1}>> OUTER UNION SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = true + ) + } + ) + + @Test + fun outerUnionNonSpecified() = assertExpression( + "SELECT * FROM <<{'a': 1}>> UNION 2", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + null + ), + lhs = createSFW(1), + rhs = createLit(2), + outer = true + ) + } + ) + + @Test + fun sqlUnionAndOuterUnion() = assertExpression( + "SELECT * FROM <<{'a': 1}>> UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT 3", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createLit(3), + outer = true // outer + ) + } + ) + + @Test + fun outerUnionAndSQLUnion() = assertExpression( + "1 UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.UNION, + SetQuantifier.ALL + ), + lhs = createLit(1), + rhs = createSFW(2), + outer = true // outer + ), + rhs = createSFW(3), + outer = true // also outer + ) + } + ) + + // SQL Except + @Test + fun sqlExcept() = assertExpression( + "SELECT * FROM <<{'a': 1}>> EXCEPT SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ) + } + ) + + @Test + fun sqlExceptMultiple() = assertExpression( + "SELECT * FROM <<{'a': 1}>> EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createSFW(3), + outer = false + ) + } + ) + + @Test + fun sqlExceptMultipleRight() = assertExpression( + "SELECT * FROM <<{'a': 1}>> EXCEPT ALL (SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>)", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.DISTINCT + ), + lhs = createSFW(2), + rhs = createSFW(3), + outer = false + ), + outer = false + ) + } + ) + + // Outer Except + @Test + fun outerExcept() = assertExpression( + "SELECT * FROM <<{'a': 1}>> OUTER EXCEPT SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = true + ) + } + ) + + @Test + fun outerExceptNonSpecified() = assertExpression( + "SELECT * FROM <<{'a': 1}>> EXCEPT 2", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + null + ), + lhs = createSFW(1), + rhs = createLit(2), + outer = true + ) + } + ) + + @Test + fun sqlExceptAndOuterExcept() = assertExpression( + "SELECT * FROM <<{'a': 1}>> EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT 3", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createLit(3), + outer = true // outer + ) + } + ) + + @Test + fun outerExceptAndSQLExcept() = assertExpression( + "1 EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.EXCEPT, + SetQuantifier.ALL + ), + lhs = createLit(1), + rhs = createSFW(2), + outer = true // outer + ), + rhs = createSFW(3), + outer = true // also outer + ) + } + ) + + // SQL Intersect + @Test + fun sqlIntersect() = assertExpression( + "SELECT * FROM <<{'a': 1}>> INTERSECT SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ) + } + ) + + @Test + fun sqlIntersectMultiple() = assertExpression( + "SELECT * FROM <<{'a': 1}>> INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createSFW(3), + outer = false + ) + } + ) + + @Test + fun sqlIntersectMultipleRight() = assertExpression( + "SELECT * FROM <<{'a': 1}>> INTERSECT ALL (SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>)", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.DISTINCT + ), + lhs = createSFW(2), + rhs = createSFW(3), + outer = false + ), + outer = false + ) + } + ) + + // Outer Intersect + @Test + fun outerIntersect() = assertExpression( + "SELECT * FROM <<{'a': 1}>> OUTER INTERSECT SELECT * FROM <<{'a': 2}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + null + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = true + ) + } + ) + + @Test + fun outerIntersectNonSpecified() = assertExpression( + "SELECT * FROM <<{'a': 1}>> INTERSECT 2", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + null + ), + lhs = createSFW(1), + rhs = createLit(2), + outer = true + ) + } + ) + + @Test + fun sqlIntersectAndOuterIntersect() = assertExpression( + "SELECT * FROM <<{'a': 1}>> INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT 3", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.ALL + ), + lhs = createSFW(1), + rhs = createSFW(2), + outer = false + ), + rhs = createLit(3), + outer = true // outer + ) + } + ) + + @Test + fun outerIntersectAndSQLIntersect() = assertExpression( + "1 INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>", + query { + exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.DISTINCT + ), + lhs = exprBagOp( + type = setOp( + SetOp.Type.INTERSECT, + SetQuantifier.ALL + ), + lhs = createLit(1), + rhs = createSFW(2), + outer = true // outer + ), + rhs = createSFW(3), + outer = true // also outer + ) + } + ) + + private fun assertExpression(input: String, expected: AstNode) { + val result = parser.parse(input) + val actual = result.root + assertEquals(expected, actual) + } +} diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 2dc6afe791..0498a775b2 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -193,6 +193,24 @@ rex::{ args: list::[rex], }, + union::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + + intersect::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + + except::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + err::{ message: string, }, 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 1fc96d44fd..b99411608c 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 @@ -54,7 +54,9 @@ import org.partiql.planner.internal.ir.builder.RexOpCaseBuilder import org.partiql.planner.internal.ir.builder.RexOpCoalesceBuilder import org.partiql.planner.internal.ir.builder.RexOpCollectionBuilder import org.partiql.planner.internal.ir.builder.RexOpErrBuilder +import org.partiql.planner.internal.ir.builder.RexOpExceptBuilder import org.partiql.planner.internal.ir.builder.RexOpGlobalBuilder +import org.partiql.planner.internal.ir.builder.RexOpIntersectBuilder import org.partiql.planner.internal.ir.builder.RexOpLitBuilder import org.partiql.planner.internal.ir.builder.RexOpNullifBuilder import org.partiql.planner.internal.ir.builder.RexOpPathIndexBuilder @@ -66,6 +68,7 @@ import org.partiql.planner.internal.ir.builder.RexOpStructBuilder import org.partiql.planner.internal.ir.builder.RexOpStructFieldBuilder import org.partiql.planner.internal.ir.builder.RexOpSubqueryBuilder import org.partiql.planner.internal.ir.builder.RexOpTupleUnionBuilder +import org.partiql.planner.internal.ir.builder.RexOpUnionBuilder import org.partiql.planner.internal.ir.builder.RexOpVarResolvedBuilder import org.partiql.planner.internal.ir.builder.RexOpVarUnresolvedBuilder import org.partiql.planner.internal.ir.builder.StatementQueryBuilder @@ -322,6 +325,9 @@ internal data class Rex( is Subquery -> visitor.visitRexOpSubquery(this, ctx) is Select -> visitor.visitRexOpSelect(this, ctx) is TupleUnion -> visitor.visitRexOpTupleUnion(this, ctx) + is Union -> visitor.visitRexOpUnion(this, ctx) + is Intersect -> visitor.visitRexOpIntersect(this, ctx) + is Except -> visitor.visitRexOpExcept(this, ctx) is Err -> visitor.visitRexOpErr(this, ctx) } @@ -744,6 +750,81 @@ internal data class Rex( internal fun builder(): RexOpTupleUnionBuilder = RexOpTupleUnionBuilder() } } + + internal data class Union( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rex, + @JvmField + internal val rhs: Rex, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpUnion(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpUnionBuilder = RexOpUnionBuilder() + } + } + + internal data class Intersect( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rex, + @JvmField + internal val rhs: Rex, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpIntersect(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpIntersectBuilder = RexOpIntersectBuilder() + } + } + + internal data class Except( + @JvmField + internal val setq: SetQuantifier, + @JvmField + internal val lhs: Rex, + @JvmField + internal val rhs: Rex, + ) : Op() { + internal override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() + } + + + internal override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpExcept(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpExceptBuilder = RexOpExceptBuilder() + } + } internal data class Err( @JvmField internal val message: String, @@ -958,8 +1039,6 @@ internal data class Rel( internal val lhs: Rel, @JvmField internal val rhs: Rel, - @JvmField - internal val isOuter: Boolean, ) : Op() { internal override val children: List by lazy { val kids = mutableListOf() @@ -985,8 +1064,6 @@ internal data class Rel( internal val lhs: Rel, @JvmField internal val rhs: Rel, - @JvmField - internal val isOuter: Boolean, ) : Op() { internal override val children: List by lazy { val kids = mutableListOf() @@ -1012,8 +1089,6 @@ internal data class Rel( internal val lhs: Rel, @JvmField internal val rhs: Rel, - @JvmField - internal val isOuter: Boolean, ) : Op() { internal override val children: List by lazy { val kids = mutableListOf() 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 0f622081f6..01940d40c4 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 @@ -237,6 +237,24 @@ internal object PlanTransform : PlanBaseVisitor() { override fun visitRexOpTupleUnion(node: Rex.Op.TupleUnion, ctx: ProblemCallback) = org.partiql.plan.Rex.Op.TupleUnion(args = node.args.map { visitRex(it, ctx) }) + override fun visitRexOpExcept(node: Rex.Op.Except, ctx: ProblemCallback) = org.partiql.plan.Rex.Op.Except( + lhs = visitRex(node.lhs, ctx), + rhs = visitRex(node.rhs, ctx), + setq = visitSetQuantifier(node.setq) + ) + + override fun visitRexOpIntersect(node: Rex.Op.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rex.Op.Intersect( + lhs = visitRex(node.lhs, ctx), + rhs = visitRex(node.rhs, ctx), + setq = visitSetQuantifier(node.setq) + ) + + override fun visitRexOpUnion(node: Rex.Op.Union, ctx: ProblemCallback) = org.partiql.plan.Rex.Op.Union( + lhs = visitRex(node.lhs, ctx), + rhs = visitRex(node.rhs, ctx), + setq = visitSetQuantifier(node.setq) + ) + override fun visitRexOpErr(node: Rex.Op.Err, ctx: ProblemCallback) = org.partiql.plan.Rex.Op.Err(node.message) // RELATION OPERATORS @@ -300,22 +318,22 @@ internal object PlanTransform : PlanBaseVisitor() { override fun visitRelOpExcept(node: Rel.Op.Except, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Except( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - setq = visitRelOpSetQuantifier(node.setq) + setq = visitSetQuantifier(node.setq) ) override fun visitRelOpIntersect(node: Rel.Op.Intersect, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Intersect( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - setq = visitRelOpSetQuantifier(node.setq) + setq = visitSetQuantifier(node.setq) ) override fun visitRelOpUnion(node: Rel.Op.Union, ctx: ProblemCallback) = org.partiql.plan.Rel.Op.Union( lhs = visitRel(node.lhs, ctx), rhs = visitRel(node.rhs, ctx), - setq = visitRelOpSetQuantifier(node.setq) + setq = visitSetQuantifier(node.setq) ) - private fun visitRelOpSetQuantifier(node: SetQuantifier) = when (node) { + private fun visitSetQuantifier(node: SetQuantifier) = when (node) { SetQuantifier.ALL -> org.partiql.plan.SetQuantifier.ALL SetQuantifier.DISTINCT -> org.partiql.plan.SetQuantifier.DISTINCT } 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 bb15cd7d4c..c5e2d10e42 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 @@ -72,7 +72,7 @@ import org.partiql.value.boolValue internal object RelConverter { // IGNORE — so we don't have to non-null assert on operator inputs - private val nil = rel(relType(emptyList(), emptySet()), relOpErr("nil")) + internal val nil = rel(relType(emptyList(), emptySet()), relOpErr("nil")) /** * Here we convert an SFW to composed [Rel]s, then apply the appropriate relation-value projection to get a [Rex]. @@ -120,7 +120,7 @@ internal object RelConverter { private fun Expr.toRex(env: Env): Rex = RexConverter.apply(this, env) @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName") - private class ToRel(private val env: Env) : AstBaseVisitor() { + internal class ToRel(private val env: Env) : AstBaseVisitor() { override fun defaultReturn(node: AstNode, input: Rel): Rel = throw IllegalArgumentException("unsupported rel $node") @@ -160,6 +160,29 @@ internal object RelConverter { return rel } + // Create a SQL set op + override fun visitExprBagOp(node: Expr.BagOp, ctx: Rel): Rel { + // Assumes parser correctly only allows Expr.SFW or other Expr.BagOps with Expr.SFW arguments when + // converting to the SQL set op + assert(node.lhs is Expr.SFW || node.lhs is Expr.BagOp) + assert(node.rhs is Expr.SFW || node.rhs is Expr.BagOp) + val setq = when (node.type.setq) { + SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL + null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT + } + val lhsRel = visitExpr(node.lhs, ctx) + val rhsRel = visitExpr(node.rhs, ctx) + val op = when (node.type.type) { + SetOp.Type.UNION -> Rel.Op.Union(setq, lhsRel, rhsRel) + SetOp.Type.EXCEPT -> Rel.Op.Except(setq, lhsRel, rhsRel) + SetOp.Type.INTERSECT -> Rel.Op.Intersect(setq, lhsRel, rhsRel) + } + return Rel( + type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), + op = op + ) + } + /** * Given a non-null [setQuantifier], this will return a [Rel] of [Rel.Op.Distinct] wrapping the [input]. * If [setQuantifier] is null or ALL, this will return the [input]. @@ -417,9 +440,9 @@ internal object RelConverter { null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT } val op = when (setOp.type.type) { - SetOp.Type.UNION -> Rel.Op.Union(quantifier, lhs, rhs, false) - SetOp.Type.EXCEPT -> Rel.Op.Except(quantifier, lhs, rhs, false) - SetOp.Type.INTERSECT -> Rel.Op.Intersect(quantifier, lhs, rhs, false) + SetOp.Type.UNION -> Rel.Op.Union(quantifier, lhs, rhs) + SetOp.Type.EXCEPT -> Rel.Op.Except(quantifier, lhs, rhs) + SetOp.Type.INTERSECT -> Rel.Op.Intersect(quantifier, lhs, rhs) } return rel(type, 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 e212182c0d..3f054a518a 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 @@ -25,7 +25,6 @@ import org.partiql.ast.Type import org.partiql.ast.visitor.AstBaseVisitor import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Identifier -import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.builder.plan import org.partiql.planner.internal.ir.fnUnresolved @@ -44,7 +43,9 @@ 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.rexOpVarResolved import org.partiql.planner.internal.ir.rexOpVarUnresolved +import org.partiql.planner.internal.transforms.RelConverter.nil import org.partiql.planner.internal.typer.toNonNullStaticType import org.partiql.planner.internal.typer.toStaticType import org.partiql.types.StaticType @@ -635,38 +636,37 @@ internal object RexConverter { override fun visitExprSFW(node: Expr.SFW, context: Env): Rex = RelConverter.apply(node, context) override fun visitExprBagOp(node: Expr.BagOp, ctx: Env): Rex { - val lhs = Rel( - type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), - op = Rel.Op.Scan(visitExpr(node.lhs, ctx)) - ) - val rhs = Rel( - type = Rel.Type(listOf(Rel.Binding("_1", StaticType.ANY)), props = emptySet()), - op = Rel.Op.Scan(visitExpr(node.rhs, ctx)) - ) - val quantifier = when (node.type.setq) { - SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL - null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT - } - val isOuter = node.outer == true - val op = when (node.type.type) { - SetOp.Type.UNION -> Rel.Op.Union(quantifier, lhs, rhs, isOuter) - SetOp.Type.EXCEPT -> Rel.Op.Except(quantifier, lhs, rhs, isOuter) - SetOp.Type.INTERSECT -> Rel.Op.Intersect(quantifier, lhs, rhs, isOuter) - } - val rel = Rel( - type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), - op = op - ) - return Rex( - type = StaticType.ANY, - op = Rex.Op.Select( - constructor = Rex( - StaticType.ANY, - Rex.Op.Var.Unresolved(Identifier.Symbol("_0", Identifier.CaseSensitivity.SENSITIVE), Rex.Op.Var.Scope.LOCAL) - ), - rel = rel + if (node.outer == true) { + // PartiQL bag op; create bag op rex + val lhs = visitExpr(node.lhs, ctx) + val rhs = visitExpr(node.rhs, ctx) + val setq = when (node.type.setq) { + SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL + null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT + } + val op = when (node.type.type) { + SetOp.Type.UNION -> Rex.Op.Union(setq, lhs, rhs) + SetOp.Type.EXCEPT -> Rex.Op.Except(setq, lhs, rhs) + SetOp.Type.INTERSECT -> Rex.Op.Intersect(setq, lhs, rhs) + } + return Rex( + type = StaticType.ANY, + op = op + ) + } else { + // SQL set op; create set op rel + val rel = node.accept(RelConverter.ToRel(ctx), nil) + return Rex( + type = StaticType.ANY, + op = Rex.Op.Select( + constructor = Rex( + StaticType.ANY, + rexOpVarResolved(0) + ), + rel = rel + ) ) - ) + } } // Helpers 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 3250f82509..55c023e6b4 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 @@ -249,7 +249,7 @@ internal class PlanTyper( if (!setOpSchemaSizesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchSizes() } - if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + if (!setOpSchemaTypesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchTypes() } // Compute Schema @@ -264,7 +264,7 @@ internal class PlanTyper( if (!setOpSchemaSizesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchSizes() } - if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + if (!setOpSchemaTypesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchTypes() } // Compute Schema @@ -279,7 +279,7 @@ internal class PlanTyper( if (!setOpSchemaSizesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchSizes() } - if (!node.isOuter && !setOpSchemaTypesMatch(lhs, rhs)) { + if (!setOpSchemaTypesMatch(lhs, rhs)) { return createRelErrForSetOpMismatchTypes() } // Compute Schema @@ -291,7 +291,7 @@ internal class PlanTyper( true -> lhsBinding.name false -> "_$it" } - Rel.Binding(bindingName, unionOf(lhsBinding.type, rhsBinding.type)) + Rel.Binding(bindingName, unionOf(lhsBinding.type, rhsBinding.type).flatten()) } val type = Rel.Type(schema, props = emptySet()) return Rel(type, node.copy(lhs = lhs, rhs = rhs)) @@ -508,6 +508,30 @@ internal class PlanTyper( override fun visitRex(node: Rex, ctx: StaticType?): Rex = visitRexOp(node.op, node.type) as Rex + override fun visitRexOpUnion(node: Rex.Op.Union, ctx: StaticType?): Rex { + val lhs = visitRex(node.lhs, node.lhs.type) + val rhs = visitRex(node.rhs, node.rhs.type) + // Compute Schema + val type = unionOf(lhs.type, rhs.type).flatten() + return Rex(type, node.copy(lhs = lhs, rhs = rhs)) + } + + override fun visitRexOpExcept(node: Rex.Op.Except, ctx: StaticType?): Rex { + val lhs = visitRex(node.lhs, node.lhs.type) + val rhs = visitRex(node.rhs, node.rhs.type) + // Compute Schema + val type = unionOf(lhs.type, rhs.type).flatten() + return Rex(type, node.copy(lhs = lhs, rhs = rhs)) + } + + override fun visitRexOpIntersect(node: Rex.Op.Intersect, ctx: StaticType?): Rex { + val lhs = visitRex(node.lhs, node.lhs.type) + val rhs = visitRex(node.rhs, node.rhs.type) + // Compute Schema + val type = unionOf(lhs.type, rhs.type).flatten() + return Rex(type, node.copy(lhs = lhs, rhs = rhs)) + } + override fun visitRexOpLit(node: Rex.Op.Lit, ctx: StaticType?): Rex { // type comes from RexConverter return rex(ctx!!, node) diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 7fc8a247ca..ad5b89c83a 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -216,6 +216,24 @@ rex::{ args: list::[rex], }, + union::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + + intersect::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + + except::{ + setq: set_quantifier, + lhs: rex, + rhs: rex, + }, + err::{ message: string, }, @@ -279,21 +297,18 @@ rel::{ setq: set_quantifier, lhs: rel, rhs: rel, - is_outer: bool, }, intersect::{ setq: set_quantifier, lhs: rel, rhs: rel, - is_outer: bool, }, except::{ setq: set_quantifier, lhs: rel, rhs: rel, - is_outer: bool, }, limit::{ From 2f7ccc7758161a51d0df08d7809571c3ad3e5621 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Fri, 12 Jul 2024 11:13:40 -0700 Subject: [PATCH 07/10] Fix SqlDialect (and InternalSD) parens for set ops --- CHANGELOG.md | 1 + .../kotlin/org/partiql/ast/sql/SqlDialect.kt | 7 ++ .../ast/sql/internal/InternalSqlDialect.kt | 7 ++ .../org/partiql/ast/sql/SqlDialectTest.kt | 114 ++++++++++++++++++ 4 files changed, 129 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e2f9d53fb..3a13971730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ org.partiql.parser.PartiQLParser, not the org.partiql.lang.syntax.PartiQLParser. - partiql-plan: adds a dedicated Rex node for PartiQL bag operators `UNION`, `INTERSECT`, and `EXCEPT` - partiql-planner: Adds typing support for set operators - partiql-parser: parses non-SFW expressions to be PartiQL `OUTER` bag operators +- partiql-ast: fixes missing parens from `bag_op` when printing using `SqlDialect` ### Deprecated diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt index 6a589a0556..c4ad9217fc 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt @@ -64,6 +64,13 @@ public abstract class SqlDialect : AstBaseVisitor() { h = h concat ")" h } + is Expr.BagOp -> { + var h = head + h = h concat "(" + h = visitExprBagOp(node, h) + h = h concat ")" + h + } else -> visitExpr(node, head) } diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt index 4ecfb9d569..4612feefeb 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt @@ -82,6 +82,13 @@ internal abstract class InternalSqlDialect : AstBaseVisitor { + var t = tail + t = t concat "(" + t = visitExprBagOp(node, t) + t = t concat ")" + t + } else -> visitExpr(node, tail) } diff --git a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt index 6276adb43d..9520d83519 100644 --- a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt +++ b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt @@ -986,6 +986,120 @@ class SqlDialectTest { rhs = v("y") } }, + expect("(x UNION y) UNION z") { + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = null + } + outer = false + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = null + } + outer = false + lhs = v("x") + rhs = v("y") + } + rhs = v("z") + } + }, + expect("x UNION (y UNION z)") { + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = null + } + outer = false + lhs = v("x") + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = null + } + outer = false + lhs = v("y") + rhs = v("z") + } + } + }, + expect("(x EXCEPT y) EXCEPT z") { + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = null + } + outer = false + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = null + } + outer = false + lhs = v("x") + rhs = v("y") + } + rhs = v("z") + } + }, + expect("x EXCEPT (y EXCEPT z)") { + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = null + } + outer = false + lhs = v("x") + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = null + } + outer = false + lhs = v("y") + rhs = v("z") + } + } + }, + expect("(x INTERSECT y) INTERSECT z") { + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = null + } + outer = false + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = null + } + outer = false + lhs = v("x") + rhs = v("y") + } + rhs = v("z") + } + }, + expect("x INTERSECT (y INTERSECT z)") { + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = null + } + outer = false + lhs = v("x") + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = null + } + outer = false + lhs = v("y") + rhs = v("z") + } + } + }, ) @JvmStatic From 0dd43ea6ef9d7f874a6ee729be77255bae309672 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Fri, 12 Jul 2024 11:13:56 -0700 Subject: [PATCH 08/10] Update parse tests to use AST builder --- .../internal/PartiQLParserBagOpTests.kt | 559 +++++++++--------- 1 file changed, 266 insertions(+), 293 deletions(-) diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt index ccf3742c42..8a556e6c89 100644 --- a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserBagOpTests.kt @@ -6,16 +6,8 @@ import org.partiql.ast.Expr import org.partiql.ast.From import org.partiql.ast.SetOp import org.partiql.ast.SetQuantifier -import org.partiql.ast.exprBagOp -import org.partiql.ast.exprCollection -import org.partiql.ast.exprLit -import org.partiql.ast.exprSFW -import org.partiql.ast.exprStruct -import org.partiql.ast.exprStructField -import org.partiql.ast.fromValue -import org.partiql.ast.selectStar -import org.partiql.ast.setOp -import org.partiql.ast.statementQuery +import org.partiql.ast.builder.AstBuilder +import org.partiql.ast.builder.ast import org.partiql.value.PartiQLValueExperimental import org.partiql.value.int32Value import org.partiql.value.stringValue @@ -25,59 +17,48 @@ class PartiQLParserBagOpTests { private val parser = PartiQLParserDefault() - private inline fun query(body: () -> Expr) = statementQuery(body()) + private fun query(block: AstBuilder.() -> Expr) = ast { statementQuery { expr = block() } } @OptIn(PartiQLValueExperimental::class) private fun createSFW(i: Int): Expr.SFW = - exprSFW( - select = selectStar(null), - from = fromValue( - expr = exprCollection( - type = Expr.Collection.Type.BAG, - values = listOf( - exprStruct( - listOf( - exprStructField( - exprLit(stringValue("a")), - exprLit(int32Value(i)), + ast { + exprSFW { + select = selectStar() + from = fromValue { + expr = exprCollection { + type = Expr.Collection.Type.BAG + values = mutableListOf( + exprStruct { + fields = mutableListOf( + exprStructField { + name = exprLit { value = stringValue("a") } + value = exprLit { value = int32Value(i) } + } ) - ) + } ) - ) - ), - type = From.Value.Type.SCAN, - asAlias = null, - atAlias = null, - byAlias = null, - ), - exclude = null, - let = null, - `where` = null, - groupBy = null, - having = null, - setOp = null, - orderBy = null, - limit = null, - offset = null - ) + } + type = From.Value.Type.SCAN + } + } + } @OptIn(PartiQLValueExperimental::class) - private fun createLit(i: Int) = exprLit(int32Value(i)) + private fun createLit(i: Int) = ast { exprLit { value = int32Value(i) } } // SQL Union @Test fun sqlUnion() = assertExpression( "SELECT * FROM <<{'a': 1}>> UNION SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ) + } } ) @@ -85,23 +66,23 @@ class PartiQLParserBagOpTests { fun sqlUnionMultiple() = assertExpression( "SELECT * FROM <<{'a': 1}>> UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = false - ) + } } ) @@ -109,23 +90,23 @@ class PartiQLParserBagOpTests { fun sqlUnionMultipleRight() = assertExpression( "SELECT * FROM <<{'a': 1}>> UNION ALL (SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>)", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.DISTINCT - ), - lhs = createSFW(2), - rhs = createSFW(3), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.DISTINCT + } + lhs = createSFW(2) + rhs = createSFW(3) outer = false - ), + } outer = false - ) + } } ) @@ -134,15 +115,14 @@ class PartiQLParserBagOpTests { fun outerUnion() = assertExpression( "SELECT * FROM <<{'a': 1}>> OUTER UNION SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + } + lhs = createSFW(1) + rhs = createSFW(2) outer = true - ) + } } ) @@ -150,15 +130,14 @@ class PartiQLParserBagOpTests { fun outerUnionNonSpecified() = assertExpression( "SELECT * FROM <<{'a': 1}>> UNION 2", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - null - ), - lhs = createSFW(1), - rhs = createLit(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + } + lhs = createSFW(1) + rhs = createLit(2) outer = true - ) + } } ) @@ -166,23 +145,23 @@ class PartiQLParserBagOpTests { fun sqlUnionAndOuterUnion() = assertExpression( "SELECT * FROM <<{'a': 1}>> UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT 3", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createLit(3), + } + rhs = createLit(3) outer = true // outer - ) + } } ) @@ -190,23 +169,23 @@ class PartiQLParserBagOpTests { fun outerUnionAndSQLUnion() = assertExpression( "1 UNION ALL SELECT * FROM <<{'a': 2}>> UNION DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.UNION, - SetQuantifier.ALL - ), - lhs = createLit(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.UNION + setq = SetQuantifier.ALL + } + lhs = createLit(1) + rhs = createSFW(2) outer = true // outer - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = true // also outer - ) + } } ) @@ -215,15 +194,14 @@ class PartiQLParserBagOpTests { fun sqlExcept() = assertExpression( "SELECT * FROM <<{'a': 1}>> EXCEPT SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ) + } } ) @@ -231,23 +209,23 @@ class PartiQLParserBagOpTests { fun sqlExceptMultiple() = assertExpression( "SELECT * FROM <<{'a': 1}>> EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = false - ) + } } ) @@ -255,23 +233,23 @@ class PartiQLParserBagOpTests { fun sqlExceptMultipleRight() = assertExpression( "SELECT * FROM <<{'a': 1}>> EXCEPT ALL (SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>)", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.DISTINCT - ), - lhs = createSFW(2), - rhs = createSFW(3), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.DISTINCT + } + lhs = createSFW(2) + rhs = createSFW(3) outer = false - ), + } outer = false - ) + } } ) @@ -280,15 +258,14 @@ class PartiQLParserBagOpTests { fun outerExcept() = assertExpression( "SELECT * FROM <<{'a': 1}>> OUTER EXCEPT SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + } + lhs = createSFW(1) + rhs = createSFW(2) outer = true - ) + } } ) @@ -296,15 +273,14 @@ class PartiQLParserBagOpTests { fun outerExceptNonSpecified() = assertExpression( "SELECT * FROM <<{'a': 1}>> EXCEPT 2", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - null - ), - lhs = createSFW(1), - rhs = createLit(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + } + lhs = createSFW(1) + rhs = createLit(2) outer = true - ) + } } ) @@ -312,23 +288,23 @@ class PartiQLParserBagOpTests { fun sqlExceptAndOuterExcept() = assertExpression( "SELECT * FROM <<{'a': 1}>> EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT 3", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createLit(3), + } + rhs = createLit(3) outer = true // outer - ) + } } ) @@ -336,23 +312,23 @@ class PartiQLParserBagOpTests { fun outerExceptAndSQLExcept() = assertExpression( "1 EXCEPT ALL SELECT * FROM <<{'a': 2}>> EXCEPT DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.EXCEPT, - SetQuantifier.ALL - ), - lhs = createLit(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.EXCEPT + setq = SetQuantifier.ALL + } + lhs = createLit(1) + rhs = createSFW(2) outer = true // outer - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = true // also outer - ) + } } ) @@ -361,15 +337,14 @@ class PartiQLParserBagOpTests { fun sqlIntersect() = assertExpression( "SELECT * FROM <<{'a': 1}>> INTERSECT SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ) + } } ) @@ -377,23 +352,23 @@ class PartiQLParserBagOpTests { fun sqlIntersectMultiple() = assertExpression( "SELECT * FROM <<{'a': 1}>> INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = false - ) + } } ) @@ -401,23 +376,23 @@ class PartiQLParserBagOpTests { fun sqlIntersectMultipleRight() = assertExpression( "SELECT * FROM <<{'a': 1}>> INTERSECT ALL (SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>)", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.DISTINCT - ), - lhs = createSFW(2), - rhs = createSFW(3), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.DISTINCT + } + lhs = createSFW(2) + rhs = createSFW(3) outer = false - ), + } outer = false - ) + } } ) @@ -426,15 +401,14 @@ class PartiQLParserBagOpTests { fun outerIntersect() = assertExpression( "SELECT * FROM <<{'a': 1}>> OUTER INTERSECT SELECT * FROM <<{'a': 2}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - null - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + } + lhs = createSFW(1) + rhs = createSFW(2) outer = true - ) + } } ) @@ -442,15 +416,14 @@ class PartiQLParserBagOpTests { fun outerIntersectNonSpecified() = assertExpression( "SELECT * FROM <<{'a': 1}>> INTERSECT 2", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - null - ), - lhs = createSFW(1), - rhs = createLit(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + } + lhs = createSFW(1) + rhs = createLit(2) outer = true - ) + } } ) @@ -458,23 +431,23 @@ class PartiQLParserBagOpTests { fun sqlIntersectAndOuterIntersect() = assertExpression( "SELECT * FROM <<{'a': 1}>> INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT 3", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.ALL - ), - lhs = createSFW(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.ALL + } + lhs = createSFW(1) + rhs = createSFW(2) outer = false - ), - rhs = createLit(3), + } + rhs = createLit(3) outer = true // outer - ) + } } ) @@ -482,23 +455,23 @@ class PartiQLParserBagOpTests { fun outerIntersectAndSQLIntersect() = assertExpression( "1 INTERSECT ALL SELECT * FROM <<{'a': 2}>> INTERSECT DISTINCT SELECT * FROM <<{'a': 3}>>", query { - exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.DISTINCT - ), - lhs = exprBagOp( - type = setOp( - SetOp.Type.INTERSECT, - SetQuantifier.ALL - ), - lhs = createLit(1), - rhs = createSFW(2), + exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.DISTINCT + } + lhs = exprBagOp { + type = setOp { + type = SetOp.Type.INTERSECT + setq = SetQuantifier.ALL + } + lhs = createLit(1) + rhs = createSFW(2) outer = true // outer - ), - rhs = createSFW(3), + } + rhs = createSFW(3) outer = true // also outer - ) + } } ) From b7b62163c1073d6d7e69e3da37ed0b612a869686 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Fri, 12 Jul 2024 15:47:34 -0700 Subject: [PATCH 09/10] Update outdated plan comments --- partiql-plan/src/main/resources/partiql_plan.ion | 2 ++ .../src/main/resources/partiql_plan_internal.ion | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 0498a775b2..29804e2b6b 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -193,6 +193,7 @@ rex::{ args: list::[rex], }, + // PartiQL bag ops union::{ setq: set_quantifier, lhs: rex, @@ -264,6 +265,7 @@ rel::{ ], }, + // SQL set ops union::{ setq: set_quantifier, lhs: rel, diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index ad5b89c83a..75779e5a3f 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -216,6 +216,7 @@ rex::{ args: list::[rex], }, + // PartiQL bag ops union::{ setq: set_quantifier, lhs: rex, @@ -287,12 +288,7 @@ rel::{ ], }, - - // In each variant, is_outer is an internal-only field. It is specifically used to aid in typing the plan and throwing potential errors. - // For example, if a user were to write: `<< { 'a': 1 } >>` UNION << { 'b': 'hello' } >>, then this would FAIL - // due to [RFC-0007](https://github.com/partiql/partiql-lang/blob/main/RFCs/0007-rfc-bag-operators.md). However, - // if a user were to use OUTER UNION, then it would work. Under the hood at execution, the operator is the same -- - // however, at planning time, with static type analysis, we can fail queries prior to their execution. + // SQL set ops union::{ setq: set_quantifier, lhs: rel, From abdeaecb15b12cc2a388ca6cf0656b8f5b6b6bb0 Mon Sep 17 00:00:00 2001 From: Alan Cai Date: Mon, 15 Jul 2024 11:11:23 -0700 Subject: [PATCH 10/10] Add err messages; use nil --- .../planner/internal/transforms/RelConverter.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 c5e2d10e42..9d6fe42a22 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 @@ -164,8 +164,14 @@ internal object RelConverter { override fun visitExprBagOp(node: Expr.BagOp, ctx: Rel): Rel { // Assumes parser correctly only allows Expr.SFW or other Expr.BagOps with Expr.SFW arguments when // converting to the SQL set op - assert(node.lhs is Expr.SFW || node.lhs is Expr.BagOp) - assert(node.rhs is Expr.SFW || node.rhs is Expr.BagOp) + assert(node.lhs is Expr.SFW || node.lhs is Expr.BagOp) { + "Expect LHS of bag op to be a Expr.SFW or a Expr.BagOp. " + + "However, it is ${node.lhs}." + } + assert(node.rhs is Expr.SFW || node.rhs is Expr.BagOp) { + "Expect RHS of bag op to be a Expr.SFW or a Expr.BagOp. " + + "However, it is ${node.lhs}." + } val setq = when (node.type.setq) { SetQuantifier.ALL -> org.partiql.planner.internal.ir.SetQuantifier.ALL null, SetQuantifier.DISTINCT -> org.partiql.planner.internal.ir.SetQuantifier.DISTINCT @@ -178,7 +184,7 @@ internal object RelConverter { SetOp.Type.INTERSECT -> Rel.Op.Intersect(setq, lhsRel, rhsRel) } return Rel( - type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()), + type = nil.type, op = op ) }