Skip to content

Commit 8728496

Browse files
authored
Merge ed21f96 into ff60c72
2 parents ff60c72 + ed21f96 commit 8728496

File tree

11 files changed

+275
-103
lines changed

11 files changed

+275
-103
lines changed

partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import org.partiql.eval.internal.operator.Operator
44
import org.partiql.eval.internal.operator.rel.RelFilter
55
import org.partiql.eval.internal.operator.rel.RelJoinInner
66
import org.partiql.eval.internal.operator.rel.RelJoinLeft
7+
import org.partiql.eval.internal.operator.rel.RelJoinOuterFull
8+
import org.partiql.eval.internal.operator.rel.RelJoinRight
79
import org.partiql.eval.internal.operator.rel.RelProject
810
import org.partiql.eval.internal.operator.rel.RelScan
911
import org.partiql.eval.internal.operator.rex.ExprCollection
@@ -108,8 +110,8 @@ internal object Compiler {
108110
return when (node.type) {
109111
Rel.Op.Join.Type.INNER -> RelJoinInner(lhs, rhs, condition)
110112
Rel.Op.Join.Type.LEFT -> RelJoinLeft(lhs, rhs, condition)
111-
Rel.Op.Join.Type.RIGHT -> TODO()
112-
Rel.Op.Join.Type.FULL -> TODO()
113+
Rel.Op.Join.Type.RIGHT -> RelJoinRight(lhs, rhs, condition)
114+
Rel.Op.Join.Type.FULL -> RelJoinOuterFull(lhs, rhs, condition)
113115
}
114116
}
115117

partiql-eval/src/main/kotlin/org/partiql/eval/internal/Record.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ internal class Record(val values: Array<PartiQLValue>) {
2525
public operator fun plus(rhs: Record): Record {
2626
return Record(this.values + rhs.values)
2727
}
28+
29+
public fun copy(): Record {
30+
return Record(this.values.copyOf())
31+
}
2832
}

partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoin.kt

Lines changed: 0 additions & 65 deletions
This file was deleted.

partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinInner.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ internal class RelJoinInner(
77
override val lhs: Operator.Relation,
88
override val rhs: Operator.Relation,
99
override val condition: Operator.Expr
10-
) : RelJoin() {
11-
override fun getOutputRecord(result: Boolean, lhs: Record, rhs: Record): Record {
12-
return lhs + rhs
10+
) : RelJoinNestedLoop() {
11+
override fun join(condition: Boolean, lhs: Record, rhs: Record): Record? {
12+
return when (condition) {
13+
true -> lhs + rhs
14+
false -> null
15+
}
1316
}
1417
}

partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinLeft.kt

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,17 @@ package org.partiql.eval.internal.operator.rel
22

33
import org.partiql.eval.internal.Record
44
import org.partiql.eval.internal.operator.Operator
5-
import org.partiql.value.PartiQLValue
6-
import org.partiql.value.PartiQLValueExperimental
7-
import org.partiql.value.StructValue
8-
import org.partiql.value.nullValue
9-
import org.partiql.value.structValue
105

116
internal class RelJoinLeft(
127
override val lhs: Operator.Relation,
138
override val rhs: Operator.Relation,
149
override val condition: Operator.Expr
15-
) : RelJoin() {
10+
) : RelJoinNestedLoop() {
1611

17-
override fun getOutputRecord(result: Boolean, lhs: Record, rhs: Record): Record {
18-
if (result.not()) {
12+
override fun join(condition: Boolean, lhs: Record, rhs: Record): Record {
13+
if (condition.not()) {
1914
rhs.padNull()
2015
}
2116
return lhs + rhs
2217
}
23-
24-
@OptIn(PartiQLValueExperimental::class)
25-
private fun Record.padNull() {
26-
this.values.indices.forEach { index ->
27-
this.values[index] = values[index].padNull()
28-
}
29-
}
30-
31-
@OptIn(PartiQLValueExperimental::class)
32-
private fun PartiQLValue.padNull(): PartiQLValue {
33-
return when (this) {
34-
is StructValue<*> -> {
35-
val newFields = this.fields?.map { it.first to nullValue() }
36-
structValue(newFields)
37-
}
38-
else -> nullValue()
39-
}
40-
}
4118
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.partiql.eval.internal.operator.rel
2+
3+
import org.partiql.eval.internal.Record
4+
import org.partiql.eval.internal.operator.Operator
5+
import org.partiql.value.BoolValue
6+
import org.partiql.value.PartiQLValue
7+
import org.partiql.value.PartiQLValueExperimental
8+
import org.partiql.value.StructValue
9+
import org.partiql.value.nullValue
10+
import org.partiql.value.structValue
11+
12+
internal abstract class RelJoinNestedLoop : Operator.Relation {
13+
14+
abstract val lhs: Operator.Relation
15+
abstract val rhs: Operator.Relation
16+
abstract val condition: Operator.Expr
17+
18+
private var rhsRecord: Record? = null
19+
20+
override fun open() {
21+
lhs.open()
22+
rhs.open()
23+
rhsRecord = rhs.next()
24+
}
25+
26+
abstract fun join(condition: Boolean, lhs: Record, rhs: Record): Record?
27+
28+
@OptIn(PartiQLValueExperimental::class)
29+
override fun next(): Record? {
30+
var lhsRecord = lhs.next()
31+
var toReturn: Record? = null
32+
do {
33+
// Acquire LHS and RHS Records
34+
if (lhsRecord == null) {
35+
lhs.close()
36+
rhsRecord = rhs.next() ?: return null
37+
lhs.open()
38+
lhsRecord = lhs.next()
39+
}
40+
// Return Joined Record
41+
if (lhsRecord != null && rhsRecord != null) {
42+
val input = lhsRecord + rhsRecord!!
43+
val result = condition.eval(input)
44+
toReturn = join(result.isTrue(), lhsRecord, rhsRecord!!)
45+
}
46+
}
47+
while (toReturn == null)
48+
return toReturn
49+
}
50+
51+
override fun close() {
52+
lhs.close()
53+
rhs.close()
54+
}
55+
56+
@OptIn(PartiQLValueExperimental::class)
57+
private fun PartiQLValue.isTrue(): Boolean {
58+
return this is BoolValue && this.value == true
59+
}
60+
61+
@OptIn(PartiQLValueExperimental::class)
62+
internal fun Record.padNull() {
63+
this.values.indices.forEach { index ->
64+
this.values[index] = values[index].padNull()
65+
}
66+
}
67+
68+
@OptIn(PartiQLValueExperimental::class)
69+
private fun PartiQLValue.padNull(): PartiQLValue {
70+
return when (this) {
71+
is StructValue<*> -> {
72+
val newFields = this.fields?.map { it.first to nullValue() }
73+
structValue(newFields)
74+
}
75+
else -> nullValue()
76+
}
77+
}
78+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.partiql.eval.internal.operator.rel
2+
3+
import org.partiql.eval.internal.Record
4+
import org.partiql.eval.internal.operator.Operator
5+
6+
/**
7+
* Here's a simple implementation of FULL OUTER JOIN. The idea is fairly straightforward:
8+
* Iterate through LHS. For each iteration of the LHS, iterate through RHS. Now, check the condition.
9+
* - If the condition passes, return the merged record (equivalent to result of INNER JOIN)
10+
* - If the condition does not pass, we need a way to return two records (one where the LHS is padded with nulls, and
11+
* one where the RHS is padded with nulls). How we do this:
12+
* - We maintain the [previousLhs] and [previousRhs]. If they are null, we then compute the next LHS and RHS. We
13+
* store their values in-memory. Then we return a merged Record where the LHS is padded and the RHS is not (equivalent
14+
* to result of RIGHT OUTER JOIN).
15+
* - If they aren't null, then we pad the RHS with NULLS (we assume we've already padded the LHS) and return (equivalent
16+
* to result of LEFT OUTER JOIN). We also make sure [previousLhs] and [previousRhs] are now null.
17+
*
18+
* Performance Analysis: Assume that [lhs] has size M and [rhs] has size N.
19+
* - Time: O(M * N)
20+
* - Space: O(1)
21+
*/
22+
internal class RelJoinOuterFull(
23+
override val lhs: Operator.Relation,
24+
override val rhs: Operator.Relation,
25+
override val condition: Operator.Expr
26+
) : RelJoinNestedLoop() {
27+
28+
private var previousLhs: Record? = null
29+
private var previousRhs: Record? = null
30+
31+
override fun next(): Record? {
32+
if (previousLhs != null && previousRhs != null) {
33+
previousRhs!!.padNull()
34+
val newRecord = previousLhs!! + previousRhs!!
35+
previousLhs = null
36+
previousRhs = null
37+
return newRecord
38+
}
39+
return super.next()
40+
}
41+
42+
/**
43+
* Specifically, for FULL OUTER JOIN, when the JOIN Condition ([condition]) is TRUE, we need to return the
44+
* rows merged (without modification). When the JOIN Condition ([condition]) is FALSE, we need to return
45+
* the LHS padded (and merged with RHS not padded) and the RHS padded (merged with the LHS not padded).
46+
*/
47+
override fun join(condition: Boolean, lhs: Record, rhs: Record): Record {
48+
when (condition) {
49+
true -> {
50+
previousLhs = null
51+
previousRhs = null
52+
}
53+
false -> {
54+
previousLhs = lhs.copy()
55+
previousRhs = rhs.copy()
56+
lhs.padNull()
57+
}
58+
}
59+
return lhs + rhs
60+
}
61+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.partiql.eval.internal.operator.rel
2+
3+
import org.partiql.eval.internal.Record
4+
import org.partiql.eval.internal.operator.Operator
5+
6+
internal class RelJoinRight(
7+
lhs: Operator.Relation,
8+
rhs: Operator.Relation,
9+
override val condition: Operator.Expr
10+
) : RelJoinNestedLoop() {
11+
12+
override val lhs: Operator.Relation = rhs
13+
override val rhs: Operator.Relation = lhs
14+
15+
override fun join(condition: Boolean, lhs: Record, rhs: Record): Record {
16+
if (condition.not()) {
17+
lhs.padNull()
18+
}
19+
return lhs + rhs
20+
}
21+
}

0 commit comments

Comments
 (0)