Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializes PartiQL's Engine #1283

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions partiql-eval/src/main/kotlin/org/partiql/eval/PartiQLEngine.kt
Original file line number Diff line number Diff line change
@@ -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<Plugin> = emptyList()
private var implementation: Implementation = Implementation.DEFAULT

public fun withPlugins(plugins: List<Plugin>): 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()
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
16 changes: 16 additions & 0 deletions partiql-eval/src/main/kotlin/org/partiql/eval/impl/PhysicalNode.kt
Original file line number Diff line number Diff line change
@@ -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<Record>
}
}
Original file line number Diff line number Diff line change
@@ -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<PhysicalNode, Unit>() {
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)
}
}
}
8 changes: 8 additions & 0 deletions partiql-eval/src/main/kotlin/org/partiql/eval/impl/Record.kt
Original file line number Diff line number Diff line change
@@ -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<PartiQLValue>
)
Original file line number Diff line number Diff line change
@@ -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>
) : PhysicalNode.Expression {
@PartiQLValueExperimental
override fun evaluate(record: Record): PartiQLValue {
return bagValue(
values.map { it.evaluate(record) }.asSequence()
)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<PartiQLValue>()
input.evaluate().forEach { record ->
val element = constructor.evaluate(record)
elements.add(element)
}
return bagValue(elements.asSequence())
}
}
Original file line number Diff line number Diff line change
@@ -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<Field>) : 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)
}
Original file line number Diff line number Diff line change
@@ -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]
}
}
Original file line number Diff line number Diff line change
@@ -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.Expression>
) : PhysicalNode.Relation {
@OptIn(PartiQLValueExperimental::class)
override fun evaluate(): Iterator<Record> {

val inputIter = input.evaluate()

return object : Iterator<Record> {
override fun hasNext(): Boolean {
return inputIter.hasNext()
}

override fun next(): Record {
val inputRecord = inputIter.next()
return Record(
projections.map { it.evaluate(inputRecord) }
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Record> {
return when (val value = expr.evaluate(Record(emptyList()))) {
is CollectionValue<*> -> value.elements!!.map { Record(listOf(it)) }.iterator()
else -> iterator { yield(Record(listOf(value))) }
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}