diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngine.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngine.kt new file mode 100644 index 0000000000..2f7b88be3a --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngine.kt @@ -0,0 +1,59 @@ +package org.partiql.eval + +import org.partiql.eval.impl.PartiQLEngineDefault +import org.partiql.plan.PartiQLPlan +import org.partiql.spi.Plugin +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental + +/** + * PartiQL's Experimental Engine. + * + * It represents the execution of queries and does NOT represent the + * maintenance of an individual's session. For example, by the time the engine is invoked, all functions + * should be resolved via the SQL Path (which takes into consideration the user's current catalog/schema). + * + * This is in contrast to an actual application of PartiQL. Applications of PartiQL should instantiate a + * [org.partiql.planner.PartiQLPlanner] and should pass in a user's session. This engine has no idea what the session is. + * It assumes that the [org.partiql.plan.PartiQLPlan] has been resolved to accommodate session specifics. + * + * This engine also internalizes the mechanics of the engine itself. Internally, it creates a physical plan to operate on, + * and it executes directly on that plan. The limited number of APIs exposed in this library is intentional to allow for + * under-the-hood experimentation by the PartiQL Community. + */ +public interface PartiQLEngine { + + public fun execute(plan: PartiQLPlan): Result + + public enum class Implementation { + DEFAULT + } + + public sealed interface Result { + public data class Success @OptIn(PartiQLValueExperimental::class) constructor( + val output: PartiQLValue + ) : Result + + public data class Error @OptIn(PartiQLValueExperimental::class) constructor( + val output: PartiQLValue + ) : Result + } + + public class Builder { + + private var plugins: List = emptyList() + private var implementation: Implementation = Implementation.DEFAULT + + public fun withPlugins(plugins: List): Builder = this.apply { + this.plugins = plugins + } + + public fun withImplementation(impl: Implementation): Builder = this.apply { + this.implementation = impl + } + + public fun build(): PartiQLEngine = when (this.implementation) { + Implementation.DEFAULT -> PartiQLEngineDefault() + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PartiQLEngineDefault.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PartiQLEngineDefault.kt new file mode 100644 index 0000000000..a64f137ff2 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PartiQLEngineDefault.kt @@ -0,0 +1,14 @@ +package org.partiql.eval.impl + +import org.partiql.eval.PartiQLEngine +import org.partiql.plan.PartiQLPlan +import org.partiql.value.PartiQLValueExperimental + +internal class PartiQLEngineDefault : PartiQLEngine { + @OptIn(PartiQLValueExperimental::class) + override fun execute(plan: PartiQLPlan): PartiQLEngine.Result { + val expression = PlanToPhysical.convert(plan) + val value = expression.evaluate(Record(emptyList())) + return PartiQLEngine.Result.Success(value) + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PhysicalNode.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PhysicalNode.kt new file mode 100644 index 0000000000..4f8f2e7a53 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PhysicalNode.kt @@ -0,0 +1,16 @@ +package org.partiql.eval.impl + +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental + +internal interface PhysicalNode { + + interface Expression : PhysicalNode { + @OptIn(PartiQLValueExperimental::class) + fun evaluate(record: Record): PartiQLValue + } + + interface Relation : PhysicalNode { + fun evaluate(): Iterator + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PlanToPhysical.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PlanToPhysical.kt new file mode 100644 index 0000000000..3424a7bb14 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/PlanToPhysical.kt @@ -0,0 +1,85 @@ +package org.partiql.eval.impl + +import org.partiql.eval.impl.expression.Collection +import org.partiql.eval.impl.expression.Literal +import org.partiql.eval.impl.expression.Select +import org.partiql.eval.impl.expression.Struct +import org.partiql.eval.impl.expression.Variable +import org.partiql.eval.impl.relation.Projection +import org.partiql.eval.impl.relation.Scan +import org.partiql.plan.PartiQLPlan +import org.partiql.plan.PlanNode +import org.partiql.plan.Rel +import org.partiql.plan.Rex +import org.partiql.plan.Statement +import org.partiql.plan.visitor.PlanBaseVisitor +import org.partiql.value.PartiQLValueExperimental + +internal object PlanToPhysical { + + fun convert(plan: PartiQLPlan): PhysicalNode.Expression { + return PlanToCodeTransformer.visitPartiQLPlan(plan, Unit) + } + + private object PlanToCodeTransformer : PlanBaseVisitor() { + override fun defaultReturn(node: PlanNode, ctx: Unit): PhysicalNode { + TODO("Not yet implemented") + } + + override fun visitRexOpStruct(node: Rex.Op.Struct, ctx: Unit): PhysicalNode { + val fields = node.fields.map { + Struct.Field(visitRex(it.k, ctx), visitRex(it.v, ctx)) + } + return Struct(fields) + } + + override fun visitRexOpVar(node: Rex.Op.Var, ctx: Unit): PhysicalNode { + return Variable(node.ref) + } + + override fun visitRexOpCollection(node: Rex.Op.Collection, ctx: Unit): PhysicalNode { + val values = node.values.map { visitRex(it, ctx) } + return Collection(values) + } + + override fun visitRelOpProject(node: Rel.Op.Project, ctx: Unit): PhysicalNode { + val input = visitRel(node.input, ctx) + val projections = node.projections.map { visitRex(it, ctx) } + return Projection(input, projections) + } + + override fun visitRexOpSelect(node: Rex.Op.Select, ctx: Unit): PhysicalNode { + val rel = visitRel(node.rel, ctx) + val constructor = visitRex(node.constructor, ctx) + return Select(rel, constructor) + } + + override fun visitRelOpScan(node: Rel.Op.Scan, ctx: Unit): PhysicalNode { + val rex = visitRex(node.rex, ctx) + return Scan(rex) + } + + @OptIn(PartiQLValueExperimental::class) + override fun visitRexOpLit(node: Rex.Op.Lit, ctx: Unit): PhysicalNode { + return Literal(node.value) + } + + override fun visitRel(node: Rel, ctx: Unit): PhysicalNode.Relation { + return super.visitRelOp(node.op, ctx) as PhysicalNode.Relation + } + + override fun visitRex(node: Rex, ctx: Unit): PhysicalNode.Expression { + return super.visitRexOp(node.op, ctx) as PhysicalNode.Expression + } + + // TODO: Re-look at + override fun visitPartiQLPlan(node: PartiQLPlan, ctx: Unit): PhysicalNode.Expression { + return visitStatement(node.statement, ctx) as PhysicalNode.Expression + } + + // TODO: Re-look at + override fun visitStatementQuery(node: Statement.Query, ctx: Unit): PhysicalNode.Expression { + return visitRex(node.root, ctx) + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/Record.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/Record.kt new file mode 100644 index 0000000000..ae68936e73 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/Record.kt @@ -0,0 +1,8 @@ +package org.partiql.eval.impl + +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental + +internal class Record @OptIn(PartiQLValueExperimental::class) constructor( + val values: List +) diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Collection.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Collection.kt new file mode 100644 index 0000000000..2ea18ee947 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Collection.kt @@ -0,0 +1,18 @@ +package org.partiql.eval.impl.expression + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.bagValue + +internal class Collection( + private val values: List +) : PhysicalNode.Expression { + @PartiQLValueExperimental + override fun evaluate(record: Record): PartiQLValue { + return bagValue( + values.map { it.evaluate(record) }.asSequence() + ) + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Literal.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Literal.kt new file mode 100644 index 0000000000..c9eec9a69b --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Literal.kt @@ -0,0 +1,13 @@ +package org.partiql.eval.impl.expression + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental + +internal class Literal @OptIn(PartiQLValueExperimental::class) constructor(private val value: PartiQLValue) : PhysicalNode.Expression { + @PartiQLValueExperimental + override fun evaluate(record: Record): PartiQLValue { + return value + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Select.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Select.kt new file mode 100644 index 0000000000..c768eb14a1 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Select.kt @@ -0,0 +1,22 @@ +package org.partiql.eval.impl.expression + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.bagValue + +internal class Select( + val input: PhysicalNode.Relation, + val constructor: PhysicalNode.Expression +) : PhysicalNode.Expression { + @PartiQLValueExperimental + override fun evaluate(record: Record): PartiQLValue { + val elements = mutableListOf() + input.evaluate().forEach { record -> + val element = constructor.evaluate(record) + elements.add(element) + } + return bagValue(elements.asSequence()) + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Struct.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Struct.kt new file mode 100644 index 0000000000..e8a03f8219 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Struct.kt @@ -0,0 +1,22 @@ +package org.partiql.eval.impl.expression + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.StringValue +import org.partiql.value.structValue + +internal class Struct(val fields: List) : PhysicalNode.Expression { + @OptIn(PartiQLValueExperimental::class) + override fun evaluate(record: Record): PartiQLValue { + val fields = fields.map { + val key = it.key.evaluate(record) as? StringValue ?: error("Expected struct key to be a STRING.") + val value = it.key.evaluate(record) + key.value!! to value + } + return structValue(fields.asSequence()) + } + + internal class Field(val key: PhysicalNode.Expression, val value: PhysicalNode.Expression) +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Variable.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Variable.kt new file mode 100644 index 0000000000..efff05ae31 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/expression/Variable.kt @@ -0,0 +1,13 @@ +package org.partiql.eval.impl.expression + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValue +import org.partiql.value.PartiQLValueExperimental + +internal class Variable(private val index: Int) : PhysicalNode.Expression { + @PartiQLValueExperimental + override fun evaluate(record: Record): PartiQLValue { + return record.values[index] + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Projection.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Projection.kt new file mode 100644 index 0000000000..7b4360c029 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Projection.kt @@ -0,0 +1,29 @@ +package org.partiql.eval.impl.relation + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.PartiQLValueExperimental + +internal class Projection( + private val input: PhysicalNode.Relation, + val projections: List +) : PhysicalNode.Relation { + @OptIn(PartiQLValueExperimental::class) + override fun evaluate(): Iterator { + + val inputIter = input.evaluate() + + return object : Iterator { + override fun hasNext(): Boolean { + return inputIter.hasNext() + } + + override fun next(): Record { + val inputRecord = inputIter.next() + return Record( + projections.map { it.evaluate(inputRecord) } + ) + } + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Scan.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Scan.kt new file mode 100644 index 0000000000..f786401268 --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/impl/relation/Scan.kt @@ -0,0 +1,19 @@ +package org.partiql.eval.impl.relation + +import org.partiql.eval.impl.PhysicalNode +import org.partiql.eval.impl.Record +import org.partiql.value.CollectionValue +import org.partiql.value.PartiQLValueExperimental + +internal class Scan( + private val expr: PhysicalNode.Expression +) : PhysicalNode.Relation { + + @OptIn(PartiQLValueExperimental::class) + override fun evaluate(): Iterator { + return when (val value = expr.evaluate(Record(emptyList()))) { + is CollectionValue<*> -> value.elements!!.map { Record(listOf(it)) }.iterator() + else -> iterator { yield(Record(listOf(value))) } + } + } +} diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/impl/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/impl/PartiQLEngineDefaultTest.kt new file mode 100644 index 0000000000..c6caf813de --- /dev/null +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/impl/PartiQLEngineDefaultTest.kt @@ -0,0 +1,43 @@ +package org.partiql.eval.impl + +import org.junit.jupiter.api.Test +import org.partiql.eval.PartiQLEngine +import org.partiql.parser.PartiQLParserBuilder +import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.PartiQLPlannerBuilder +import org.partiql.value.BagValue +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.bagValue +import org.partiql.value.int32Value +import kotlin.test.assertEquals + +class PartiQLEngineDefaultTest { + + private val engine = PartiQLEngineDefault() + private val planner = PartiQLPlannerBuilder().build() + private val parser = PartiQLParserBuilder.standard().build() + + @OptIn(PartiQLValueExperimental::class) + @Test + fun testLiterals() { + val statement = parser.parse("SELECT VALUE 1 FROM <<0, 1>>;").root + val session = PartiQLPlanner.Session("q", "u") + val plan = planner.plan(statement, session) + val result = engine.execute(plan.plan) as PartiQLEngine.Result.Success + val output = result.output as BagValue<*> + val expected = bagValue(sequenceOf(int32Value(1), int32Value(1))) + assertEquals(expected, output) + } + + @OptIn(PartiQLValueExperimental::class) + @Test + fun testReference() { + val statement = parser.parse("SELECT VALUE t FROM <<10, 20, 30>> AS t;").root + val session = PartiQLPlanner.Session("q", "u") + val plan = planner.plan(statement, session) + val result = engine.execute(plan.plan) as PartiQLEngine.Result.Success + val output = result.output as BagValue<*> + val expected = bagValue(sequenceOf(int32Value(10), int32Value(20), int32Value(30))) + assertEquals(expected, output) + } +}