Skip to content

Commit 823e6a3

Browse files
authored
Merge dd63f1d into ce8d9be
2 parents ce8d9be + dd63f1d commit 823e6a3

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.partiql.eval.internal.operator.rex.ExprCollection
1212
import org.partiql.eval.internal.operator.rex.ExprLiteral
1313
import org.partiql.eval.internal.operator.rex.ExprSelect
1414
import org.partiql.eval.internal.operator.rex.ExprStruct
15+
import org.partiql.eval.internal.operator.rex.ExprTupleUnion
1516
import org.partiql.eval.internal.operator.rex.ExprVar
1617
import org.partiql.plan.PartiQLPlan
1718
import org.partiql.plan.PlanNode
@@ -84,6 +85,11 @@ internal object Compiler {
8485
return super.visitRexOp(node.op, ctx) as Operator.Expr
8586
}
8687

88+
override fun visitRexOpTupleUnion(node: Rex.Op.TupleUnion, ctx: Unit): Operator {
89+
val args = node.args.map { visitRex(it, ctx) }.toTypedArray()
90+
return ExprTupleUnion(args)
91+
}
92+
8793
override fun visitRelOpJoin(node: Rel.Op.Join, ctx: Unit): Operator {
8894
val lhs = visitRel(node.lhs, ctx)
8995
val rhs = visitRel(node.rhs, ctx)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.partiql.eval.internal.operator.rex
2+
3+
import org.partiql.eval.internal.Record
4+
import org.partiql.eval.internal.operator.Operator
5+
import org.partiql.value.NullValue
6+
import org.partiql.value.PartiQLValue
7+
import org.partiql.value.PartiQLValueExperimental
8+
import org.partiql.value.StructValue
9+
import org.partiql.value.missingValue
10+
import org.partiql.value.structValue
11+
12+
internal class ExprTupleUnion(
13+
val args: Array<Operator.Expr>
14+
) : Operator.Expr {
15+
16+
@OptIn(PartiQLValueExperimental::class)
17+
override fun eval(record: Record): PartiQLValue {
18+
// Return MISSING on Mistyping Case
19+
val tuples = args.map {
20+
when (val arg = it.eval(record)) {
21+
is StructValue<*> -> arg
22+
is NullValue -> structValue(null)
23+
else -> when (arg.isNull) {
24+
true -> structValue<PartiQLValue>(null)
25+
false -> return missingValue()
26+
}
27+
}
28+
}
29+
30+
// Return NULL if any arguments are NULL
31+
tuples.forEach {
32+
if (it.isNull) {
33+
return structValue<PartiQLValue>(null)
34+
}
35+
}
36+
37+
return structValue(tuples.flatMap { it.entries })
38+
}
39+
}

partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import org.partiql.value.bagValue
1414
import org.partiql.value.boolValue
1515
import org.partiql.value.int32Value
1616
import org.partiql.value.io.PartiQLValueIonWriterBuilder
17+
import org.partiql.value.missingValue
1718
import org.partiql.value.nullValue
19+
import org.partiql.value.stringValue
1820
import org.partiql.value.structValue
1921
import java.io.ByteArrayOutputStream
2022
import kotlin.test.assertEquals
@@ -112,10 +114,12 @@ class PartiQLEngineDefaultTest {
112114
fun testJoinOuterFull() {
113115
val statement =
114116
parser.parse("SELECT a, b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON false;").root
117+
115118
val session = PartiQLPlanner.Session("q", "u")
116119
val plan = planner.plan(statement, session)
117120

118121
val prepared = engine.prepare(plan.plan)
122+
119123
val result = engine.execute(prepared)
120124
if (result is PartiQLResult.Error) {
121125
throw result.cause
@@ -136,15 +140,43 @@ class PartiQLEngineDefaultTest {
136140
assertEquals(expected, output, comparisonString(expected, output))
137141
}
138142

143+
@OptIn(PartiQLValueExperimental::class)
144+
@Test
145+
fun testTupleUnion() {
146+
val source = """
147+
TUPLEUNION(
148+
{ 'a': 1 },
149+
{ 'b': TRUE },
150+
{ 'c': 'hello' }
151+
);
152+
""".trimIndent()
153+
val statement = parser.parse(source).root
154+
val session = PartiQLPlanner.Session("q", "u")
155+
val plan = planner.plan(statement, session)
156+
157+
val prepared = engine.prepare(plan.plan)
158+
val result = engine.execute(prepared) as PartiQLResult.Value
159+
val output = result.value
160+
161+
val expected = structValue(
162+
"a" to int32Value(1),
163+
"b" to boolValue(true),
164+
"c" to stringValue("hello")
165+
)
166+
assertEquals(expected, output)
167+
}
168+
139169
@OptIn(PartiQLValueExperimental::class)
140170
@Test
141171
fun testJoinOuterFullOnTrue() {
142172
val statement =
143173
parser.parse("SELECT a, b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON TRUE;").root
174+
144175
val session = PartiQLPlanner.Session("q", "u")
145176
val plan = planner.plan(statement, session)
146177

147178
val prepared = engine.prepare(plan.plan)
179+
148180
val result = engine.execute(prepared)
149181
if (result is PartiQLResult.Error) {
150182
throw result.cause
@@ -161,6 +193,28 @@ class PartiQLEngineDefaultTest {
161193
assertEquals(expected, output, comparisonString(expected, output))
162194
}
163195

196+
@OptIn(PartiQLValueExperimental::class)
197+
@Test
198+
fun testTupleUnionNullInput() {
199+
val source = """
200+
TUPLEUNION(
201+
{ 'a': 1 },
202+
NULL,
203+
{ 'c': 'hello' }
204+
);
205+
""".trimIndent()
206+
val statement = parser.parse(source).root
207+
val session = PartiQLPlanner.Session("q", "u")
208+
val plan = planner.plan(statement, session)
209+
210+
val prepared = engine.prepare(plan.plan)
211+
val result = engine.execute(prepared) as PartiQLResult.Value
212+
val output = result.value
213+
214+
val expected = structValue<PartiQLValue>(null)
215+
assertEquals(expected, output)
216+
}
217+
164218
@OptIn(PartiQLValueExperimental::class)
165219
private fun comparisonString(expected: PartiQLValue, actual: PartiQLValue): String {
166220
val expectedBuffer = ByteArrayOutputStream()
@@ -173,4 +227,84 @@ class PartiQLEngineDefaultTest {
173227
appendLine("Actual : $expectedBuffer")
174228
}
175229
}
230+
231+
@OptIn(PartiQLValueExperimental::class)
232+
@Test
233+
fun testTupleUnionBadInput() {
234+
val source = """
235+
TUPLEUNION(
236+
{ 'a': 1 },
237+
5,
238+
{ 'c': 'hello' }
239+
);
240+
""".trimIndent()
241+
val statement = parser.parse(source).root
242+
val session = PartiQLPlanner.Session("q", "u")
243+
val plan = planner.plan(statement, session)
244+
245+
val prepared = engine.prepare(plan.plan)
246+
val result = engine.execute(prepared) as PartiQLResult.Value
247+
val output = result.value
248+
249+
val expected = missingValue()
250+
assertEquals(expected, output)
251+
}
252+
253+
@OptIn(PartiQLValueExperimental::class)
254+
@Test
255+
fun testTupleUnionDuplicates() {
256+
val source = """
257+
TUPLEUNION(
258+
{ 'a': 1, 'b': FALSE },
259+
{ 'b': TRUE },
260+
{ 'c': 'hello' }
261+
);
262+
""".trimIndent()
263+
val statement = parser.parse(source).root
264+
val session = PartiQLPlanner.Session("q", "u")
265+
val plan = planner.plan(statement, session)
266+
267+
val prepared = engine.prepare(plan.plan)
268+
val result = engine.execute(prepared) as PartiQLResult.Value
269+
val output = result.value
270+
271+
val expected = structValue(
272+
"a" to int32Value(1),
273+
"b" to boolValue(false),
274+
"b" to boolValue(true),
275+
"c" to stringValue("hello")
276+
)
277+
assertEquals(expected, output)
278+
}
279+
280+
@OptIn(PartiQLValueExperimental::class)
281+
@Test
282+
fun testSelectStarTupleUnion() {
283+
// As SELECT * gets converted to TUPLEUNION, this is a sanity check
284+
val source = """
285+
SELECT * FROM
286+
<<
287+
{ 'a': 1, 'b': FALSE }
288+
>> AS t,
289+
<<
290+
{ 'b': TRUE }
291+
>> AS s
292+
""".trimIndent()
293+
val statement = parser.parse(source).root
294+
val session = PartiQLPlanner.Session("q", "u")
295+
val plan = planner.plan(statement, session)
296+
297+
val prepared = engine.prepare(plan.plan)
298+
val result = engine.execute(prepared) as PartiQLResult.Value
299+
val output = result.value
300+
301+
val expected = bagValue(
302+
structValue(
303+
"a" to int32Value(1),
304+
"b" to boolValue(false),
305+
"b" to boolValue(true)
306+
)
307+
)
308+
assertEquals(expected, output)
309+
}
176310
}

partiql-types/src/main/kotlin/org/partiql/value/PartiQLValue.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ public abstract class MissingValue : PartiQLValue {
558558
public fun PartiQLValue.toIon(): IonElement = accept(ToIon, Unit)
559559

560560
@PartiQLValueExperimental
561+
@Throws(TypeCheckException::class)
561562
public inline fun <reified T : PartiQLValue> PartiQLValue.check(): T {
562563
if (this is T) return this else throw TypeCheckException()
563564
}

0 commit comments

Comments
 (0)