Skip to content

Commit be3daf6

Browse files
authored
Merge 060bf1d into c8f9060
2 parents c8f9060 + 060bf1d commit be3daf6

File tree

5 files changed

+155
-27
lines changed

5 files changed

+155
-27
lines changed

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

+22
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import org.partiql.eval.internal.operator.rel.RelJoinInner
99
import org.partiql.eval.internal.operator.rel.RelJoinLeft
1010
import org.partiql.eval.internal.operator.rel.RelJoinOuterFull
1111
import org.partiql.eval.internal.operator.rel.RelJoinRight
12+
import org.partiql.eval.internal.operator.rel.RelLimit
1213
import org.partiql.eval.internal.operator.rel.RelProject
1314
import org.partiql.eval.internal.operator.rel.RelScan
1415
import org.partiql.eval.internal.operator.rel.RelScanIndexed
1516
import org.partiql.eval.internal.operator.rel.RelScanIndexedPermissive
1617
import org.partiql.eval.internal.operator.rel.RelScanPermissive
1718
import org.partiql.eval.internal.operator.rel.RelSort
19+
import org.partiql.eval.internal.operator.rel.RelUnpivot
1820
import org.partiql.eval.internal.operator.rex.ExprCallDynamic
1921
import org.partiql.eval.internal.operator.rex.ExprCallStatic
2022
import org.partiql.eval.internal.operator.rex.ExprCase
@@ -197,6 +199,26 @@ internal class Compiler(
197199
}
198200
}
199201

202+
override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: StaticType?): Operator {
203+
val expr = visitRex(node.rex, ctx)
204+
return when (session.mode) {
205+
PartiQLEngine.Mode.PERMISSIVE -> RelUnpivot.Permissive(expr)
206+
PartiQLEngine.Mode.STRICT -> RelUnpivot.Strict(expr)
207+
}
208+
}
209+
210+
override fun visitRelOpLimit(node: Rel.Op.Limit, ctx: StaticType?): Operator {
211+
val input = visitRel(node.input, ctx)
212+
val limit = visitRex(node.limit, ctx)
213+
return RelLimit(input, limit)
214+
}
215+
216+
override fun visitRelOpOffset(node: Rel.Op.Offset, ctx: StaticType?): Operator {
217+
val input = visitRel(node.input, ctx)
218+
val offset = visitRex(node.offset, ctx)
219+
return RelLimit(input, offset)
220+
}
221+
200222
override fun visitRexOpTupleUnion(node: Rex.Op.TupleUnion, ctx: StaticType?): Operator {
201223
val args = node.args.map { visitRex(it, ctx) }.toTypedArray()
202224
return ExprTupleUnion(args)

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

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
package org.partiql.eval.internal.operator.rel
22

3+
import org.partiql.errors.TypeCheckException
34
import org.partiql.eval.internal.Record
45
import org.partiql.eval.internal.operator.Operator
6+
import org.partiql.value.NumericValue
7+
import org.partiql.value.PartiQLValueExperimental
58

9+
@OptIn(PartiQLValueExperimental::class)
610
internal class RelLimit(
711
private val input: Operator.Relation,
8-
private val limit: Long,
12+
private val limit: Operator.Expr,
913
) : Operator.Relation {
1014

11-
private var seen = 0
15+
private var _seen: Long = 0
16+
private var _limit: Long = 0
1217

1318
override fun open() {
1419
input.open()
15-
seen = 0
20+
_seen = 0
21+
22+
// TODO pass outer scope to limit expression
23+
val l = limit.eval(Record.empty)
24+
if (l is NumericValue<*>) {
25+
_limit = l.toInt64().value ?: 0L
26+
} else {
27+
throw TypeCheckException()
28+
}
1629
}
1730

1831
override fun next(): Record? {
19-
if (seen < limit) {
32+
if (_seen < _limit) {
2033
val row = input.next() ?: return null
21-
seen += 1
34+
_seen += 1
2235
return row
2336
}
2437
return null

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

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
package org.partiql.eval.internal.operator.rel
22

3+
import org.partiql.errors.TypeCheckException
34
import org.partiql.eval.internal.Record
45
import org.partiql.eval.internal.operator.Operator
6+
import org.partiql.value.NumericValue
7+
import org.partiql.value.PartiQLValueExperimental
58

9+
@OptIn(PartiQLValueExperimental::class)
610
internal class RelOffset(
711
private val input: Operator.Relation,
8-
private val offset: Long,
12+
private val offset: Operator.Expr,
913
) : Operator.Relation {
1014

1115
private var init = false
12-
private var seen = 0
16+
private var _seen: Long = 0
17+
private var _offset: Long = 0
1318

1419
override fun open() {
1520
input.open()
1621
init = false
17-
seen = 0
22+
_seen = 0
23+
24+
// TODO pass outer scope to offset expression
25+
val o = offset.eval(Record.empty)
26+
if (o is NumericValue<*>) {
27+
_offset = o.toInt64().value ?: 0L
28+
} else {
29+
throw TypeCheckException()
30+
}
1831
}
1932

2033
override fun next(): Record? {
2134
if (!init) {
22-
while (seen < offset) {
35+
while (_seen < _offset) {
2336
input.next() ?: return null
24-
seen += 1
37+
_seen += 1
2538
}
2639
init = true
2740
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.partiql.eval.internal.operator.rel
2+
3+
import org.partiql.errors.TypeCheckException
4+
import org.partiql.eval.internal.Record
5+
import org.partiql.eval.internal.operator.Operator
6+
import org.partiql.value.MissingValue
7+
import org.partiql.value.PartiQLValue
8+
import org.partiql.value.PartiQLValueExperimental
9+
import org.partiql.value.StructValue
10+
import org.partiql.value.stringValue
11+
import org.partiql.value.structValue
12+
13+
/**
14+
* The unpivot operator produces a bag of records from a struct.
15+
*
16+
* Input: { k_0: v_0, ..., k_i: v_i }
17+
* Output: [ k_0, v_0 ] ... [ k_i, v_i ]
18+
*/
19+
@OptIn(PartiQLValueExperimental::class)
20+
internal sealed class RelUnpivot : Operator.Relation {
21+
22+
/**
23+
* Iterator of the struct fields.
24+
*/
25+
private lateinit var _iterator: Iterator<Pair<String, PartiQLValue>>
26+
27+
/**
28+
* Each mode overrides.
29+
*/
30+
abstract fun struct(): StructValue<*>
31+
32+
/**
33+
* Initialize the _iterator from the concrete implementation's struct()
34+
*/
35+
override fun open() {
36+
_iterator = struct().entries.iterator()
37+
}
38+
39+
override fun next(): Record? {
40+
if (!_iterator.hasNext()) {
41+
return null
42+
}
43+
val f = _iterator.next()
44+
val k = stringValue(f.first)
45+
val v = f.second
46+
return Record.of(k, v)
47+
}
48+
49+
override fun close() {}
50+
51+
/**
52+
* In strict mode, the UNPIVOT operator raises an error on mistyped input.
53+
*
54+
* @property expr
55+
*/
56+
class Strict(private val expr: Operator.Expr) : RelUnpivot() {
57+
58+
override fun struct(): StructValue<*> {
59+
val v = expr.eval(Record.empty)
60+
if (v !is StructValue<*>) {
61+
throw TypeCheckException()
62+
}
63+
return v
64+
}
65+
}
66+
67+
/**
68+
* In permissive mode, the UNPIVOT operator coerces the input (v) to a struct.
69+
*
70+
* 1. If v is a struct, return it.
71+
* 2. If v is MISSING, return { }.
72+
* 3. Else, return { '_1': v }.
73+
*
74+
* @property expr
75+
*/
76+
class Permissive(private val expr: Operator.Expr) : RelUnpivot() {
77+
78+
override fun struct(): StructValue<*> = when (val v = expr.eval(Record.empty)) {
79+
is StructValue<*> -> v
80+
is MissingValue -> structValue<PartiQLValue>()
81+
else -> structValue("_1" to v)
82+
}
83+
}
84+
}

partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt

+13-17
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import org.partiql.planner.internal.ir.relBinding
3434
import org.partiql.planner.internal.ir.relOpAggregate
3535
import org.partiql.planner.internal.ir.relOpAggregateCallUnresolved
3636
import org.partiql.planner.internal.ir.relOpDistinct
37-
import org.partiql.planner.internal.ir.relOpErr
3837
import org.partiql.planner.internal.ir.relOpExclude
3938
import org.partiql.planner.internal.ir.relOpExcludePath
4039
import org.partiql.planner.internal.ir.relOpFilter
@@ -173,26 +172,23 @@ internal class PlanTyper(
173172
// descend, with GLOBAL resolution strategy
174173
val rex = node.rex.type(outer, Scope.GLOBAL)
175174

176-
// only UNPIVOT a struct
177-
if (rex.type !is StructType) {
178-
handleUnexpectedType(rex.type, expected = setOf(StaticType.STRUCT))
179-
return rel(ctx!!, relOpErr("UNPIVOT on non-STRUCT type ${rex.type}"))
180-
}
175+
// key type, always a string.
176+
val kType = STRING
181177

182-
// compute element type
183-
val t = rex.type
184-
val e = if (t.contentClosed) {
185-
unionOf(t.fields.map { it.value }.toSet()).flatten()
186-
} else {
187-
ANY
178+
// value type, possibly coerced.
179+
val vType = when (val t = rex.type) {
180+
is StructType -> {
181+
if (t.contentClosed || t.constraints.contains(TupleConstraint.Open(false))) {
182+
unionOf(t.fields.map { it.value }.toSet()).flatten()
183+
} else {
184+
ANY
185+
}
186+
}
187+
else -> t
188188
}
189189

190-
// compute rel type
191-
val kType = STRING
192-
val vType = e
193-
val type = ctx!!.copyWithSchema(listOf(kType, vType))
194-
195190
// rewrite
191+
val type = ctx!!.copyWithSchema(listOf(kType, vType))
196192
val op = relOpUnpivot(rex)
197193
return rel(type, op)
198194
}

0 commit comments

Comments
 (0)