Skip to content

Commit 23961f3

Browse files
authored
Enable Eval Test Suites (#1340)
1 parent 57496a9 commit 23961f3

File tree

10 files changed

+107
-48
lines changed

10 files changed

+107
-48
lines changed

partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngine.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.partiql.eval
22

33
import org.partiql.plan.PartiQLPlan
4+
import org.partiql.spi.connector.ConnectorBindings
5+
import org.partiql.spi.function.PartiQLFunction
6+
import org.partiql.spi.function.PartiQLFunctionExperimental
47

58
/**
69
* PartiQL's Experimental Engine.
@@ -19,8 +22,9 @@ import org.partiql.plan.PartiQLPlan
1922
*/
2023
public interface PartiQLEngine {
2124

22-
public fun prepare(plan: PartiQLPlan): PartiQLStatement<*>
25+
public fun prepare(plan: PartiQLPlan, session: Session): PartiQLStatement<*>
2326

27+
// TODO: Pass session variable during statement execution once we finalize data structure for session.
2428
public fun execute(statement: PartiQLStatement<*>): PartiQLResult
2529

2630
companion object {
@@ -31,4 +35,9 @@ public interface PartiQLEngine {
3135
@JvmStatic
3236
fun default() = PartiQLEngineBuilder().build()
3337
}
38+
39+
public class Session @OptIn(PartiQLFunctionExperimental::class) constructor(
40+
val bindings: Map<String, ConnectorBindings> = mapOf(),
41+
val functions: Map<String, List<PartiQLFunction>> = mapOf()
42+
)
3443
}
Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,11 @@
11
package org.partiql.eval
22

3-
import org.partiql.spi.connector.ConnectorBindings
4-
53
class PartiQLEngineBuilder {
64

7-
private var catalogs: MutableMap<String, ConnectorBindings> = mutableMapOf()
8-
95
/**
106
* Build the builder, return an implementation of a [PartiQLEngine]
117
*
128
* @return
139
*/
14-
public fun build(): PartiQLEngine = PartiQLEngineDefault(catalogs)
15-
16-
/**
17-
* Java style method for assigning a Catalog name to [ConnectorBindings].
18-
*
19-
* @param catalog
20-
* @param bindings
21-
* @return
22-
*/
23-
public fun addCatalog(catalog: String, bindings: ConnectorBindings): PartiQLEngineBuilder = this.apply {
24-
this.catalogs[catalog] = bindings
25-
}
26-
27-
/**
28-
* Kotlin style method for assigning Catalog names to [ConnectorBindings].
29-
*
30-
* @param catalogs
31-
* @return
32-
*/
33-
public fun catalogs(vararg catalogs: Pair<String, ConnectorBindings>): PartiQLEngineBuilder = this.apply {
34-
this.catalogs = mutableMapOf(*catalogs)
35-
}
10+
public fun build(): PartiQLEngine = PartiQLEngineDefault()
3611
}

partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngineDefault.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@ package org.partiql.eval
33
import org.partiql.eval.internal.Compiler
44
import org.partiql.eval.internal.Record
55
import org.partiql.plan.PartiQLPlan
6-
import org.partiql.spi.connector.ConnectorBindings
76
import org.partiql.value.PartiQLValue
87
import org.partiql.value.PartiQLValueExperimental
98

10-
internal class PartiQLEngineDefault(
11-
private val catalogs: Map<String, ConnectorBindings>,
12-
) : PartiQLEngine {
9+
internal class PartiQLEngineDefault : PartiQLEngine {
1310

1411
@OptIn(PartiQLValueExperimental::class)
15-
override fun prepare(plan: PartiQLPlan): PartiQLStatement<*> {
12+
override fun prepare(plan: PartiQLPlan, session: PartiQLEngine.Session): PartiQLStatement<*> {
1613
try {
17-
val compiler = Compiler(plan, catalogs)
14+
val compiler = Compiler(plan, session)
1815
val expression = compiler.compile()
1916
return object : PartiQLStatement.Query {
2017
override fun execute(): PartiQLValue {

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.partiql.eval.internal
22

3+
import org.partiql.eval.PartiQLEngine
34
import org.partiql.eval.internal.operator.Operator
45
import org.partiql.eval.internal.operator.rel.RelDistinct
56
import org.partiql.eval.internal.operator.rel.RelFilter
@@ -10,6 +11,7 @@ import org.partiql.eval.internal.operator.rel.RelJoinRight
1011
import org.partiql.eval.internal.operator.rel.RelProject
1112
import org.partiql.eval.internal.operator.rel.RelScan
1213
import org.partiql.eval.internal.operator.rel.RelScanIndexed
14+
import org.partiql.eval.internal.operator.rex.ExprCall
1315
import org.partiql.eval.internal.operator.rex.ExprCase
1416
import org.partiql.eval.internal.operator.rex.ExprCollection
1517
import org.partiql.eval.internal.operator.rex.ExprGlobal
@@ -27,15 +29,17 @@ import org.partiql.plan.PlanNode
2729
import org.partiql.plan.Rel
2830
import org.partiql.plan.Rex
2931
import org.partiql.plan.Statement
32+
import org.partiql.plan.debug.PlanPrinter
3033
import org.partiql.plan.visitor.PlanBaseVisitor
31-
import org.partiql.spi.connector.ConnectorBindings
3234
import org.partiql.spi.connector.ConnectorObjectPath
35+
import org.partiql.spi.function.PartiQLFunction
36+
import org.partiql.spi.function.PartiQLFunctionExperimental
3337
import org.partiql.value.PartiQLValueExperimental
3438
import java.lang.IllegalStateException
3539

3640
internal class Compiler(
3741
private val plan: PartiQLPlan,
38-
private val catalogs: Map<String, ConnectorBindings>,
42+
private val session: PartiQLEngine.Session
3943
) : PlanBaseVisitor<Operator, Unit>() {
4044

4145
fun compile(): Operator.Expr {
@@ -47,7 +51,11 @@ internal class Compiler(
4751
}
4852

4953
override fun visitRexOpErr(node: Rex.Op.Err, ctx: Unit): Operator {
50-
throw IllegalStateException(node.message)
54+
val message = buildString {
55+
this.appendLine(node.message)
56+
PlanPrinter.append(this, plan)
57+
}
58+
throw IllegalStateException(message)
5159
}
5260

5361
override fun visitRelOpErr(node: Rel.Op.Err, ctx: Unit): Operator {
@@ -101,7 +109,7 @@ internal class Compiler(
101109
val catalog = plan.catalogs[node.ref.catalog]
102110
val symbol = catalog.symbols[node.ref.symbol]
103111
val path = ConnectorObjectPath(symbol.path)
104-
val bindings = catalogs[catalog.name]!!
112+
val bindings = session.bindings[catalog.name]!!
105113
return ExprGlobal(path, bindings)
106114
}
107115

@@ -123,8 +131,26 @@ internal class Compiler(
123131
return ExprPathIndex(root, index)
124132
}
125133

126-
// REL
134+
@OptIn(PartiQLFunctionExperimental::class)
135+
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 matches = session.functions
139+
.flatMap { it.value }
140+
.filterIsInstance<PartiQLFunction.Scalar>()
141+
.filter { it.signature == fn }
142+
return when (matches.size) {
143+
0 -> error("no match")
144+
1 -> ExprCall(matches.first(), args.toTypedArray())
145+
else -> error("multiple math")
146+
}
147+
}
148+
149+
override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: Unit): Operator {
150+
error("call dynamic not yet implemented")
151+
}
127152

153+
// REL
128154
override fun visitRel(node: Rel, ctx: Unit): Operator.Relation {
129155
return super.visitRelOp(node.op, ctx) as Operator.Relation
130156
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ class PartiQLEngineDefaultTest {
242242
val statement = parser.parse(input).root
243243
val session = PartiQLPlanner.Session("q", "u")
244244
val plan = planner.plan(statement, session)
245-
val prepared = engine.prepare(plan.plan)
245+
val prepared = engine.prepare(plan.plan, PartiQLEngine.Session())
246246
val result = engine.execute(prepared) as PartiQLResult.Value
247247
val output = result.value
248248
assertEquals(expected, output, comparisonString(expected, output))

test/partiql-tests-runner/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
testImplementation(project(":partiql-lang"))
2929
testImplementation(project(":partiql-eval"))
3030
testImplementation(project(":plugins:partiql-memory"))
31+
testImplementation(project(":plugins:partiql-plugin"))
3132
}
3233

3334
val tests = System.getenv()["PARTIQL_TESTS_DATA"] ?: "../partiql-tests/partiql-tests-data"

test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/ConformanceTestBase.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.partiql.runner
22

3+
import org.junit.jupiter.api.Timeout
34
import org.junit.jupiter.params.ParameterizedTest
45
import org.junit.jupiter.params.provider.ArgumentsSource
56
import org.partiql.runner.schema.TestCase
@@ -10,6 +11,11 @@ abstract class ConformanceTestBase<T, V> {
1011
abstract val runner: TestRunner<T, V>
1112

1213
// Tests the eval tests with the Kotlin implementation
14+
// Unit is second.
15+
// This is not a performance test. This is for stop long-running tests during development process in eval engine.
16+
// This number can be smaller, but to account for the cold start time and fluctuation of GitHub runner,
17+
// I decided to make this number a bit larger than needed.
18+
@Timeout(value = 100, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
1319
@ParameterizedTest(name = "{arguments}")
1420
@ArgumentsSource(TestProvider.Eval::class)
1521
fun validatePartiQLEvalTestData(tc: TestCase) {
@@ -20,6 +26,7 @@ abstract class ConformanceTestBase<T, V> {
2026
}
2127

2228
// Tests the eval equivalence tests with the Kotlin implementation
29+
@Timeout(value = 100, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
2330
@ParameterizedTest(name = "{arguments}")
2431
@ArgumentsSource(TestProvider.Equiv::class)
2532
fun validatePartiQLEvalEquivTestData(tc: TestCase) {

test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.partiql.runner.executor
22

33
import com.amazon.ion.IonStruct
44
import com.amazon.ion.IonValue
5+
import com.amazon.ionelement.api.ElementType
56
import com.amazon.ionelement.api.IonElement
67
import com.amazon.ionelement.api.StructElement
78
import com.amazon.ionelement.api.toIonElement
@@ -12,12 +13,14 @@ import org.partiql.eval.PartiQLStatement
1213
import org.partiql.lang.eval.CompileOptions
1314
import org.partiql.parser.PartiQLParser
1415
import org.partiql.planner.PartiQLPlanner
16+
import org.partiql.plugin.PartiQLPlugin
1517
import org.partiql.plugins.memory.MemoryBindings
1618
import org.partiql.plugins.memory.MemoryConnector
1719
import org.partiql.runner.ION
1820
import org.partiql.runner.test.TestExecutor
1921
import org.partiql.spi.connector.Connector
2022
import org.partiql.spi.connector.ConnectorSession
23+
import org.partiql.spi.function.PartiQLFunctionExperimental
2124
import org.partiql.types.StaticType
2225
import org.partiql.value.PartiQLValue
2326
import org.partiql.value.PartiQLValueExperimental
@@ -26,13 +29,14 @@ import org.partiql.value.toIon
2629

2730
@OptIn(PartiQLValueExperimental::class)
2831
class EvalExecutor(
29-
private val session: PartiQLPlanner.Session,
32+
private val plannerSession: PartiQLPlanner.Session,
33+
private val evalSession: PartiQLEngine.Session
3034
) : TestExecutor<PartiQLStatement<*>, PartiQLResult> {
3135

3236
override fun prepare(statement: String): PartiQLStatement<*> {
3337
val stmt = parser.parse(statement).root
34-
val plan = planner.plan(stmt, session)
35-
return engine.prepare(plan.plan)
38+
val plan = planner.plan(stmt, plannerSession)
39+
return engine.prepare(plan.plan, evalSession)
3640
}
3741

3842
override fun execute(statement: PartiQLStatement<*>): PartiQLResult {
@@ -54,11 +58,41 @@ class EvalExecutor(
5458

5559
override fun compare(actual: PartiQLResult, expect: PartiQLResult): Boolean {
5660
if (actual is PartiQLResult.Value && expect is PartiQLResult.Value) {
57-
return actual.value == expect.value
61+
return valueComparison(actual.value, expect.value)
5862
}
5963
error("Cannot compare different types of PartiQLResult")
6064
}
6165

66+
// Value comparison of PartiQL Value that utilized Ion Hashcode.
67+
// in here, null.bool is considered equivalent to null
68+
// missing is considered different from null
69+
// annotation::1 is considered different from 1
70+
// 1 of type INT is considered the same as 1 of type INT32
71+
// we should probably consider adding our own hashcode implementation
72+
private fun valueComparison(v1: PartiQLValue, v2: PartiQLValue): Boolean {
73+
// Additional check to put on annotation
74+
// we want to have
75+
// annotation::null.int == annotation::null.bool <- True
76+
// annotation::null.int == other::null.int <- False
77+
if (v1.annotations != v2.annotations) {
78+
return false
79+
}
80+
if (v1.isNull && v2.isNull) {
81+
return true
82+
}
83+
if (v1 == v2) {
84+
return true
85+
}
86+
if (v1.toIon().hashCode() == v2.toIon().hashCode()) {
87+
return true
88+
}
89+
// Ion element hash code contains a bug
90+
// Hashcode of BigIntIntElementImpl(BigInteger.ONE) is not the same as that of LongIntElementImpl(1)
91+
if (v1.toIon().type == ElementType.INT && v2.toIon().type == ElementType.INT) {
92+
return v1.toIon().asAnyElement().bigIntegerValue == v2.toIon().asAnyElement().bigIntegerValue
93+
}
94+
return false
95+
}
6296
companion object {
6397
val parser = PartiQLParser.default()
6498
val planner = PartiQLPlanner.default()
@@ -67,6 +101,7 @@ class EvalExecutor(
67101

68102
object Factory : TestExecutor.Factory<PartiQLStatement<*>, PartiQLResult> {
69103

104+
@OptIn(PartiQLFunctionExperimental::class)
70105
override fun create(env: IonStruct, options: CompileOptions): TestExecutor<PartiQLStatement<*>, PartiQLResult> {
71106
val catalog = "default"
72107
val data = env.toIonElement() as StructElement
@@ -79,13 +114,22 @@ class EvalExecutor(
79114
userId = "user",
80115
currentCatalog = catalog,
81116
catalogs = mapOf(
82-
"test" to connector.getMetadata(object : ConnectorSession {
117+
"default" to connector.getMetadata(object : ConnectorSession {
83118
override fun getQueryId(): String = "query"
84119
override fun getUserId(): String = "user"
85120
})
86121
)
87122
)
88-
return EvalExecutor(session)
123+
124+
val evalSession = PartiQLEngine.Session(
125+
bindings = mutableMapOf(
126+
"default" to connector.getBindings()
127+
),
128+
functions = mutableMapOf(
129+
"partiql" to PartiQLPlugin.functions
130+
)
131+
)
132+
return EvalExecutor(session, evalSession)
89133
}
90134

91135
/**

test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/report/ReportGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ReportGenerator(val engine: String) : TestWatcher, AfterAllCallback {
2424
}
2525

2626
override fun afterAll(p0: ExtensionContext?) {
27-
val basePath = System.getenv("conformanceReportDir")
27+
val basePath = System.getenv("conformanceReportDir") ?: "."
2828
val dir = Files.createDirectory(Path("$basePath/$engine")).toFile()
2929
val file = File(dir, "conformance_test_results.ion")
3030
val outputStream = file.outputStream()

test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/test/TestProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import org.junit.jupiter.params.provider.Arguments
55
import org.junit.jupiter.params.provider.ArgumentsProvider
66
import java.util.stream.Stream
77

8-
private val PARTIQL_EVAL_TEST_DATA_DIR = System.getenv("PARTIQL_EVAL_TESTS_DATA")
9-
private val PARTIQL_EVAL_EQUIV_TEST_DATA_DIR = System.getenv("PARTIQL_EVAL_EQUIV_TESTS_DATA")
8+
private val PARTIQL_EVAL_TEST_DATA_DIR = System.getenv("PARTIQL_EVAL_TESTS_DATA") ?: "../partiql-tests/partiql-tests-data/eval"
9+
private val PARTIQL_EVAL_EQUIV_TEST_DATA_DIR = System.getenv("PARTIQL_EVAL_EQUIV_TESTS_DATA") ?: "../partiql-tests/partiql-tests-data/eval-equiv"
1010

1111
/**
1212
* Reduces some of the boilerplate associated with the style of parameterized testing frequently

0 commit comments

Comments
 (0)