Skip to content

Commit 162a313

Browse files
authored
Merge 04f15b1 into 23961f3
2 parents 23961f3 + 04f15b1 commit 162a313

File tree

14 files changed

+221
-63
lines changed

14 files changed

+221
-63
lines changed

partiql-eval/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
testImplementation(project(":partiql-parser"))
3232
testImplementation(project(":plugins:partiql-local"))
3333
testImplementation(project(":plugins:partiql-memory"))
34+
testImplementation(project(":plugins:partiql-plugin"))
3435
testImplementation(testFixtures(project(":partiql-planner")))
3536
testImplementation(testFixtures(project(":partiql-lang")))
3637
testImplementation(Deps.junit4)

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

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import org.partiql.eval.internal.operator.rel.RelJoinRight
1111
import org.partiql.eval.internal.operator.rel.RelProject
1212
import org.partiql.eval.internal.operator.rel.RelScan
1313
import org.partiql.eval.internal.operator.rel.RelScanIndexed
14-
import org.partiql.eval.internal.operator.rex.ExprCall
14+
import org.partiql.eval.internal.operator.rex.ExprCallDynamic
15+
import org.partiql.eval.internal.operator.rex.ExprCallStatic
1516
import org.partiql.eval.internal.operator.rex.ExprCase
1617
import org.partiql.eval.internal.operator.rex.ExprCollection
1718
import org.partiql.eval.internal.operator.rex.ExprGlobal
@@ -34,10 +35,11 @@ import org.partiql.plan.visitor.PlanBaseVisitor
3435
import org.partiql.spi.connector.ConnectorObjectPath
3536
import org.partiql.spi.function.PartiQLFunction
3637
import org.partiql.spi.function.PartiQLFunctionExperimental
38+
import org.partiql.types.function.FunctionSignature
3739
import org.partiql.value.PartiQLValueExperimental
3840
import java.lang.IllegalStateException
3941

40-
internal class Compiler(
42+
internal class Compiler @OptIn(PartiQLFunctionExperimental::class) constructor(
4143
private val plan: PartiQLPlan,
4244
private val session: PartiQLEngine.Session
4345
) : PlanBaseVisitor<Operator, Unit>() {
@@ -133,23 +135,38 @@ internal class Compiler(
133135

134136
@OptIn(PartiQLFunctionExperimental::class)
135137
override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: Unit): Operator {
136-
val fn = node.fn.signature
137-
val args = node.args.map { visitRex(it, ctx) }
138+
val function = getFunction(node.fn.signature)
139+
val args = node.args.map { visitRex(it, ctx) }.toTypedArray()
140+
return ExprCallStatic(function, args)
141+
}
142+
143+
@OptIn(PartiQLFunctionExperimental::class, PartiQLValueExperimental::class)
144+
override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: Unit): Operator {
145+
val args = node.args.map { visitRex(it, ctx) }.toTypedArray()
146+
val candidates = node.candidates.map { candidate ->
147+
val fn = getFunction(candidate.fn.signature)
148+
val coercions = candidate.coercions.map { it?.signature?.let { sig -> getFunction(sig) } }
149+
ExprCallDynamic.Candidate(candidate.parameters.toTypedArray(), fn, coercions)
150+
}
151+
return ExprCallDynamic(candidates, args)
152+
}
153+
154+
@OptIn(PartiQLFunctionExperimental::class)
155+
private fun getFunction(signature: FunctionSignature): PartiQLFunction.Scalar {
156+
// TODO: .flatMap is a HACK. Once functions in the plan reference functions in a catalog, we will need to
157+
// query that connector. This should be a somewhat simple change.
138158
val matches = session.functions
139159
.flatMap { it.value }
140160
.filterIsInstance<PartiQLFunction.Scalar>()
141-
.filter { it.signature == fn }
161+
.filter { it.signature == signature }
162+
142163
return when (matches.size) {
143-
0 -> error("no match")
144-
1 -> ExprCall(matches.first(), args.toTypedArray())
145-
else -> error("multiple math")
164+
0 -> error("No matches encountered for $signature")
165+
1 -> matches.first()
166+
else -> error("Multiple matches encountered for $signature")
146167
}
147168
}
148169

149-
override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: Unit): Operator {
150-
error("call dynamic not yet implemented")
151-
}
152-
153170
// REL
154171
override fun visitRel(node: Rel, ctx: Unit): Operator.Relation {
155172
return super.visitRelOp(node.op, ctx) as Operator.Relation
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.partiql.eval.internal.operator.rex
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.spi.function.PartiQLFunction
7+
import org.partiql.spi.function.PartiQLFunctionExperimental
8+
import org.partiql.value.PartiQLValue
9+
import org.partiql.value.PartiQLValueExperimental
10+
import org.partiql.value.PartiQLValueType
11+
12+
/**
13+
* This represents Dynamic Dispatch.
14+
*
15+
* For the purposes of efficiency, this implementation aims to reduce any re-execution of compiled arguments. It
16+
* does this by avoiding the compilation of [Candidate.fn] and [Candidate.coercions] into
17+
* [ExprCallStatic]'s. By doing this, this implementation can evaluate ([eval]) the input [Record], execute and gather the
18+
* arguments, and pass the [PartiQLValue]s directly to the [Candidate.eval].
19+
*/
20+
internal class ExprCallDynamic(
21+
private val candidates: List<Candidate>,
22+
private val args: Array<Operator.Expr>
23+
) : Operator.Expr {
24+
25+
@OptIn(PartiQLValueExperimental::class)
26+
override fun eval(record: Record): PartiQLValue {
27+
val actualArgs = args.map { it.eval(record) }.toTypedArray()
28+
candidates.forEach { candidate ->
29+
if (candidate.matches(actualArgs)) {
30+
return candidate.eval(actualArgs)
31+
}
32+
}
33+
throw TypeCheckException()
34+
}
35+
36+
/**
37+
* This represents a single candidate for dynamic dispatch.
38+
*
39+
* This implementation assumes that the [eval] input [Record] contains the original arguments for the desired [fn].
40+
* It performs the coercions (if necessary) before computing the result.
41+
*
42+
* @see ExprCallDynamic
43+
*/
44+
internal class Candidate @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) constructor(
45+
val types: Array<PartiQLValueType>,
46+
val fn: PartiQLFunction.Scalar,
47+
val coercions: List<PartiQLFunction.Scalar?>
48+
) {
49+
50+
@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class)
51+
fun eval(originalArgs: Array<PartiQLValue>): PartiQLValue {
52+
val args = coercions.mapIndexed { index, coercion ->
53+
coercion?.invoke(arrayOf(originalArgs[index])) ?: originalArgs[index]
54+
}.toTypedArray()
55+
return fn.invoke(args)
56+
}
57+
58+
@OptIn(PartiQLValueExperimental::class)
59+
internal fun matches(args: Array<PartiQLValue>): Boolean {
60+
for (i in args.indices) {
61+
if (args[i].type != types[i]) {
62+
return false
63+
}
64+
}
65+
return true
66+
}
67+
}
68+
}

partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCall.kt renamed to partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCallStatic.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import org.partiql.value.PartiQLValueExperimental
1111
import org.partiql.value.missingValue
1212

1313
@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class)
14-
internal class ExprCall(
14+
internal class ExprCallStatic(
1515
private val fn: PartiQLFunction.Scalar,
1616
private val inputs: Array<Operator.Expr>,
1717
) : Operator.Expr {

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

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import org.partiql.eval.PartiQLResult
1111
import org.partiql.parser.PartiQLParser
1212
import org.partiql.planner.PartiQLPlanner
1313
import org.partiql.planner.PartiQLPlannerBuilder
14+
import org.partiql.plugin.PartiQLPlugin
15+
import org.partiql.spi.function.PartiQLFunctionExperimental
1416
import org.partiql.value.PartiQLValue
1517
import org.partiql.value.PartiQLValueExperimental
1618
import org.partiql.value.bagValue
@@ -212,6 +214,18 @@ class PartiQLEngineDefaultTest {
212214
input = "SELECT DISTINCT VALUE t FROM <<true, false, true, false, false, false>> AS t;",
213215
expected = bagValue(boolValue(true), boolValue(false))
214216
),
217+
SuccessTestCase(
218+
input = "SELECT DISTINCT VALUE t FROM <<true, false, true, false, false, false>> AS t WHERE t = TRUE;",
219+
expected = bagValue(boolValue(true))
220+
),
221+
SuccessTestCase(
222+
input = "100 + 50;",
223+
expected = int32Value(150)
224+
),
225+
SuccessTestCase(
226+
input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2, 3>> AS t;",
227+
expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300))
228+
),
215229
SuccessTestCase(
216230
input = """
217231
PIVOT x.v AT x.k FROM <<
@@ -226,23 +240,56 @@ class PartiQLEngineDefaultTest {
226240
"c" to stringValue("z"),
227241
)
228242
),
243+
SuccessTestCase(
244+
input = """
245+
CASE (1)
246+
WHEN NULL THEN 'isNull'
247+
WHEN MISSING THEN 'isMissing'
248+
WHEN 2 THEN 'isTwo'
249+
END
250+
;
251+
""".trimIndent(),
252+
expected = nullValue()
253+
),
254+
SuccessTestCase(
255+
input = """
256+
CASE (1)
257+
WHEN NULL THEN 'isNull'
258+
WHEN MISSING THEN 'isMissing'
259+
WHEN 2 THEN 'isTwo'
260+
WHEN 1 THEN 'isOne'
261+
END
262+
;
263+
""".trimIndent(),
264+
expected = stringValue("isOne")
265+
),
266+
SuccessTestCase(
267+
input = """
268+
`null.bool` IS NULL
269+
""".trimIndent(),
270+
expected = boolValue(true)
271+
)
229272
)
230273
}
231274
public class SuccessTestCase @OptIn(PartiQLValueExperimental::class) constructor(
232275
val input: String,
233276
val expected: PartiQLValue
234277
) {
235278

236-
private val engine = PartiQLEngine.default()
279+
@OptIn(PartiQLFunctionExperimental::class)
280+
private val engine = PartiQLEngine.builder().build()
237281
private val planner = PartiQLPlannerBuilder().build()
238282
private val parser = PartiQLParser.default()
239283

240-
@OptIn(PartiQLValueExperimental::class)
284+
@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class)
241285
internal fun assert() {
242286
val statement = parser.parse(input).root
243287
val session = PartiQLPlanner.Session("q", "u")
244288
val plan = planner.plan(statement, session)
245-
val prepared = engine.prepare(plan.plan, PartiQLEngine.Session())
289+
val functions = mapOf(
290+
"partiql" to PartiQLPlugin.functions
291+
)
292+
val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(functions = functions))
246293
val result = engine.execute(prepared) as PartiQLResult.Value
247294
val output = result.value
248295
assertEquals(expected, output, comparisonString(expected, output))
@@ -266,32 +313,17 @@ class PartiQLEngineDefaultTest {
266313
}
267314
}
268315

269-
@Disabled("This is disabled because FN EQUALS is not yet implemented.")
270316
@Test
271-
fun testCaseLiteral02() = SuccessTestCase(
272-
input = """
273-
CASE (1)
274-
WHEN NULL THEN 'isNull'
275-
WHEN MISSING THEN 'isMissing'
276-
WHEN 2 THEN 'isTwo'
277-
WHEN 1 THEN 'isOne'
278-
END
279-
;
280-
""".trimIndent(),
281-
expected = stringValue("isOne")
317+
@Disabled("CASTS have not yet been implemented.")
318+
fun testCast1() = SuccessTestCase(
319+
input = "1 + 2.0",
320+
expected = int32Value(3),
282321
).assert()
283322

284-
@Disabled("This is disabled because FN EQUALS is not yet implemented.")
285323
@Test
286-
fun testCaseLiteral03() = SuccessTestCase(
287-
input = """
288-
CASE (1)
289-
WHEN NULL THEN 'isNull'
290-
WHEN MISSING THEN 'isMissing'
291-
WHEN 2 THEN 'isTwo'
292-
END
293-
;
294-
""".trimIndent(),
295-
expected = nullValue()
324+
@Disabled("CASTS have not yet been implemented.")
325+
fun testCasts() = SuccessTestCase(
326+
input = "SELECT DISTINCT VALUE t * 100 FROM <<0, 1, 2.0, 3.0>> AS t;",
327+
expected = bagValue(int32Value(0), int32Value(100), int32Value(200), int32Value(300))
296328
).assert()
297329
}

partiql-plan/src/main/resources/partiql_plan.ion

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
imports::{
22
kotlin: [
33
partiql_value::'org.partiql.value.PartiQLValue',
4+
partiql_value_type::'org.partiql.value.PartiQLValueType',
45
static_type::'org.partiql.types.StaticType',
56
scalar_signature::'org.partiql.types.function.FunctionSignature$Scalar',
67
aggregation_signature::'org.partiql.types.function.FunctionSignature$Aggregation',
@@ -116,8 +117,16 @@ rex::{
116117
args: list::[rex],
117118
candidates: list::[candidate],
118119
_: [
120+
// Represents a potential candidate for dynamic dispatch. AKA `SELECT abs(a) FROM << 1, 2.0 >> AS a` can invoke
121+
// ABS(INT32) -> INT32 or ABS(DEC) -> DEC. In this scenario, we maintain the two potential candidates.
122+
//
123+
// @param fn - represents the function to invoke (ex: ABS(INT32) -> INT32)
124+
// @param parameters - represents the input type(s) to match. (ex: INT32)
125+
// @param coercions - represents the optional coercion to use on the argument(s). It will be NULL if no coercion
126+
// is necessary.
119127
candidate::{
120128
fn: fn,
129+
parameters: list::[partiql_value_type],
121130
coercions: list::[optional::fn]
122131
}
123132
]

partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import org.partiql.types.StaticType
6565
import org.partiql.types.function.FunctionSignature
6666
import org.partiql.value.PartiQLValue
6767
import org.partiql.value.PartiQLValueExperimental
68+
import org.partiql.value.PartiQLValueType
6869
import kotlin.random.Random
6970

7071
internal abstract class PlanNode {
@@ -550,9 +551,11 @@ internal data class Rex(
550551

551552
internal data class Candidate(
552553
@JvmField
553-
internal val fn: Fn.Resolved,
554+
internal val fn: Fn,
554555
@JvmField
555-
internal val coercions: List<Fn.Resolved?>,
556+
internal val parameters: List<PartiQLValueType>,
557+
@JvmField
558+
internal val coercions: List<Fn?>,
556559
) : PlanNode() {
557560
internal override val children: List<PlanNode> by lazy {
558561
val kids = mutableListOf<PlanNode?>()

partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Plan.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.partiql.types.StaticType
77
import org.partiql.types.function.FunctionSignature
88
import org.partiql.value.PartiQLValue
99
import org.partiql.value.PartiQLValueExperimental
10+
import org.partiql.value.PartiQLValueType
1011

1112
internal fun partiQLPlan(
1213
version: PartiQLVersion,
@@ -69,8 +70,11 @@ internal fun rexOpCallStatic(fn: Fn, args: List<Rex>): Rex.Op.Call.Static = Rex.
6970
internal fun rexOpCallDynamic(args: List<Rex>, candidates: List<Rex.Op.Call.Dynamic.Candidate>):
7071
Rex.Op.Call.Dynamic = Rex.Op.Call.Dynamic(args, candidates)
7172

72-
internal fun rexOpCallDynamicCandidate(fn: Fn.Resolved, coercions: List<Fn.Resolved?>):
73-
Rex.Op.Call.Dynamic.Candidate = Rex.Op.Call.Dynamic.Candidate(fn, coercions)
73+
internal fun rexOpCallDynamicCandidate(
74+
fn: Fn,
75+
parameters: List<PartiQLValueType>,
76+
coercions: List<Fn?>,
77+
): Rex.Op.Call.Dynamic.Candidate = Rex.Op.Call.Dynamic.Candidate(fn, parameters, coercions)
7478

7579
internal fun rexOpCase(branches: List<Rex.Op.Case.Branch>, default: Rex): Rex.Op.Case =
7680
Rex.Op.Case(branches, default)

partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/builder/PlanBuilder.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.partiql.types.StaticType
1616
import org.partiql.types.function.FunctionSignature
1717
import org.partiql.value.PartiQLValue
1818
import org.partiql.value.PartiQLValueExperimental
19+
import org.partiql.value.PartiQLValueType
1920

2021
internal fun <T : PlanNode> plan(block: PlanBuilder.() -> T) = PlanBuilder().block()
2122

@@ -219,12 +220,14 @@ internal class PlanBuilder {
219220
return builder.build()
220221
}
221222

223+
@OptIn(PartiQLValueExperimental::class)
222224
internal fun rexOpCallDynamicCandidate(
223-
fn: Fn.Resolved? = null,
224-
coercions: MutableList<Fn.Resolved?> = mutableListOf(),
225+
fn: Fn? = null,
226+
parameters: MutableList<PartiQLValueType> = mutableListOf(),
227+
coercions: MutableList<Fn?> = mutableListOf(),
225228
block: RexOpCallDynamicCandidateBuilder.() -> Unit = {},
226229
): Rex.Op.Call.Dynamic.Candidate {
227-
val builder = RexOpCallDynamicCandidateBuilder(fn, coercions)
230+
val builder = RexOpCallDynamicCandidateBuilder(fn, parameters, coercions)
228231
builder.block()
229232
return builder.build()
230233
}

0 commit comments

Comments
 (0)