Skip to content

Commit

Permalink
Adds enforcement of non-OUTER typing
Browse files Browse the repository at this point in the history
  • Loading branch information
johnedquinn committed Apr 16, 2024
1 parent 75c85e5 commit b5a7955
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ internal data class Rel(
@JvmField internal val lhs: Rel,
@JvmField internal val rhs: Rel,
@JvmField internal val type: Type,
@JvmField internal val isOuter: Boolean
) : Op() {
public override val children: List<PlanNode> by lazy {
val kids = mutableListOf<PlanNode?>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ internal object RelConverter {
null, SetQuantifier.DISTINCT -> Rel.Op.Set.Type.INTERSECT_DISTINCT
}
}
val op = Rel.Op.Set(lhs, rhs, setType)
val op = Rel.Op.Set(lhs, rhs, setType, isOuter = false)
return rel(type, op)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ internal object RexConverter {
null, SetQuantifier.DISTINCT -> Rel.Op.Set.Type.INTERSECT_DISTINCT
}
}
val op = Rel.Op.Set(lhs, rhs, type)
val op = Rel.Op.Set(lhs, rhs, type, isOuter = node.outer == true)
val rel = Rel(
type = Rel.Type(listOf(Rel.Binding("_0", StaticType.ANY)), props = emptySet()),
op = op
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,23 +233,46 @@ internal class PlanTyper(
return rel(type, op)
}

override fun visitRelOpSet(node: Rel.Op.Set, ctx: Rel.Type?): PlanNode {
override fun visitRelOpSet(node: Rel.Op.Set, ctx: Rel.Type?): Rel {
val lhs = visitRel(node.lhs, node.lhs.type)
val rhs = visitRel(node.rhs, node.rhs.type)
val set = node.copy(lhs = lhs, rhs = rhs)

// Compute Schema
// Check that types are comparable
if (!node.isOuter) {
if (lhs.type.schema.size != rhs.type.schema.size) {
return Rel(Rel.Type(emptyList(), emptySet()), Rel.Op.Err("LHS and RHS of SET OP do not have the same number of bindings."))
}
for (i in 0..lhs.type.schema.lastIndex) {
val lhsBindingType = lhs.type.schema[i].type
val rhsBindingType = rhs.type.schema[i].type
// 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.
if (lhsBindingType != rhsBindingType) {
return Rel(Rel.Type(emptyList(), emptySet()), Rel.Op.Err("LHS and RHS of SET OP do not have the same type."))
}
}
}

// Compute Output 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"
val type = when (node.type) {
Rel.Op.Set.Type.EXCEPT_DISTINCT, Rel.Op.Set.Type.EXCEPT_ALL -> lhs.type
Rel.Op.Set.Type.INTERSECT_ALL, Rel.Op.Set.Type.INTERSECT_DISTINCT,
Rel.Op.Set.Type.UNION_ALL, Rel.Op.Set.Type.UNION_DISTINCT -> {
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))
}
Rel.Type(schema, props = emptySet())
}
Rel.Binding(bindingName, unionOf(lhsBinding.type, rhsBinding.type))
}
val type = Rel.Type(schema, props = emptySet())
return Rel(type, set)
}

Expand Down
8 changes: 7 additions & 1 deletion partiql-planner/src/main/resources/partiql_plan_internal.ion
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,13 @@ rel::{
INTERSECT_DISTINCT,
EXCEPT_ALL,
EXCEPT_DISTINCT
]
],
// This 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.
is_outer: bool
},

limit::{
Expand Down

0 comments on commit b5a7955

Please sign in to comment.