Skip to content

Commit

Permalink
PartiQL Eval - Planner Mode (#1385)
Browse files Browse the repository at this point in the history
  • Loading branch information
yliuuuu authored Apr 9, 2024
1 parent 3db0221 commit 00611ba
Show file tree
Hide file tree
Showing 22 changed files with 1,731 additions and 1,158 deletions.
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/partiql.conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ java {
tasks.test {
useJUnitPlatform() // Enable JUnit5
jvmArgs.addAll(listOf("-Duser.language=en", "-Duser.country=US"))
jvmArgs.add("-Djunit.jupiter.execution.timeout.mode=disabled_on_debug")
maxHeapSize = "4g"
testLogging {
events.add(TestLogEvent.FAILED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal abstract class RelJoinNestedLoop : RelPeeking() {
toReturn = join(result.isTrue(), lhsRecord!!, rhsRecord)
}
// Move the pointer to the next row for the RHS
if (toReturn == null) rhsRecord = rhs.next()
if (toReturn == null) rhsRecord = if (rhs.hasNext()) rhs.next() else null
}
while (toReturn == null)
return toReturn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ internal class RelOffset(

override fun hasNext(): Boolean {
if (!init) {
for (record in input) {
while (input.hasNext()) {
if (_seen >= _offset) {
break
}
_seen = _seen.add(BigInteger.ONE)
input.next()
}
init = true
}
Expand Down
6 changes: 6 additions & 0 deletions partiql-plan/src/main/resources/partiql_plan.ion
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ rex::{

err::{
message: string,
causes: list::['.rex.op']
},

missing::{
message: string,
causes: list::['.rex.op']
},
],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public interface PartiQLPlanner {
public val catalogs: Map<String, ConnectorMetadata> = emptyMap(),
public val instant: Instant = Instant.now(),
)

public companion object {

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
package org.partiql.planner

import org.partiql.planner.internal.PartiQLPlannerDefault
import org.partiql.planner.internal.PlannerFlag
import org.partiql.spi.connector.ConnectorMetadata

/**
* PartiQLPlannerBuilder is used to programmatically construct a [PartiQLPlanner] implementation.
*
* Usage:
* PartiQLPlanner.builder()
* .signalMode()
* .addPass(myPass)
* .build()
*/
public class PartiQLPlannerBuilder {

private val flags: MutableSet<PlannerFlag> = mutableSetOf()

private val passes: MutableList<PartiQLPlannerPass> = mutableListOf()

/**
* Build the builder, return an implementation of a [PartiQLPlanner].
*
* @return
*/
public fun build(): PartiQLPlanner = PartiQLPlannerDefault(passes)
public fun build(): PartiQLPlanner = PartiQLPlannerDefault(passes, flags)

/**
* Java style method for adding a planner pass to this planner builder.
Expand All @@ -41,6 +46,13 @@ public class PartiQLPlannerBuilder {
this.passes.addAll(passes)
}

/**
* Java style method for setting the planner to signal mode
*/
public fun signalMode(): PartiQLPlannerBuilder = this.apply {
this.flags.add(PlannerFlag.SIGNAL_MODE)
}

/**
* Java style method for assigning a Catalog name to [ConnectorMetadata].
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,22 @@ internal class Env(private val session: PartiQLPlanner.Session) {
// Invoke FnResolver to determine if we made a match
val variants = item.handle.entity.getVariants()
val match = FnResolver.resolve(variants, args.map { it.type })
// If Type mismatch, then we return a missingOp whose trace is all possible candidates.
if (match == null) {
// unable to make a match, consider returning helpful error messages given the item.variants.
return null
val candidates = variants.map { fnSignature ->
rexOpCallDynamicCandidate(
fn = refFn(
item.catalog,
path = item.handle.path.steps,
signature = fnSignature
),
coercions = emptyList()
)
}
return ProblemGenerator.missingRex(
rexOpCallDynamic(args, candidates, false),
ProblemGenerator.incompatibleTypesForOp(args.map { it.type }, path.normalized.joinToString("."))
)
}
return when (match) {
is FnMatch.Dynamic -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.partiql.planner
package org.partiql.planner.internal

import org.partiql.ast.Statement
import org.partiql.ast.normalize.normalize
import org.partiql.errors.ProblemCallback
import org.partiql.planner.internal.Env
import org.partiql.planner.PartiQLPlanner
import org.partiql.planner.PartiQLPlannerPass
import org.partiql.planner.internal.transforms.AstToPlan
import org.partiql.planner.internal.transforms.PlanTransform
import org.partiql.planner.internal.typer.PlanTyper
Expand All @@ -13,6 +14,7 @@ import org.partiql.planner.internal.typer.PlanTyper
*/
internal class PartiQLPlannerDefault(
private val passes: List<PartiQLPlannerPass>,
private val flags: Set<PlannerFlag>
) : PartiQLPlanner {

override fun plan(
Expand All @@ -31,12 +33,12 @@ internal class PartiQLPlannerDefault(
val root = AstToPlan.apply(ast, env)

// 3. Resolve variables
val typer = PlanTyper(env, onProblem)
val typer = PlanTyper(env)
val typed = typer.resolve(root)
val internal = org.partiql.planner.internal.ir.PartiQLPlan(typed)

// 4. Assert plan has been resolved — translating to public API
var plan = PlanTransform.transform(internal, onProblem)
var plan = PlanTransform(flags).transform(internal, onProblem)

// 5. Apply all passes
for (pass in passes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.partiql.planner.internal

internal enum class PlannerFlag {
/**
* Determine the planner behavior upon encounter an operation that always returns MISSING.
*
* If this flag is included:
*
* The problematic operation will be tracked in problem callback as a error.
*
* The result plan will turn the problematic operation into an error node.
*
* Otherwise:
*
* The problematic operation will be tracked in problem callback as a missing.
*
* The result plan will turn the problematic operation into a missing node.
*/
SIGNAL_MODE
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.partiql.planner
package org.partiql.planner.internal

import org.partiql.errors.ProblemDetails
import org.partiql.errors.ProblemSeverity
Expand All @@ -13,21 +13,45 @@ import org.partiql.types.StaticType
* This information can be used to generate end-user readable error messages and is also easy to assert
* equivalence in unit tests.
*/
public sealed class PlanningProblemDetails(
internal open class PlanningProblemDetails(
override val severity: ProblemSeverity,
public val messageFormatter: () -> String,
val messageFormatter: () -> String,
) : ProblemDetails {

companion object {
private fun quotationHint(caseSensitive: Boolean) =
if (caseSensitive) {
// Individuals that are new to SQL often try to use double quotes for string literals.
// Let's help them out a bit.
" Hint: did you intend to use single-quotes (') here? Remember that double-quotes (\") denote " +
"quoted identifiers and single-quotes denote strings."
} else {
""
}

private fun Identifier.sql(): String = when (this) {
is Identifier.Qualified -> this.sql()
is Identifier.Symbol -> this.sql()
}

private fun Identifier.Qualified.sql(): String = root.sql() + "." + steps.joinToString(".") { it.sql() }

private fun Identifier.Symbol.sql(): String = when (caseSensitivity) {
Identifier.CaseSensitivity.SENSITIVE -> "\"$symbol\""
Identifier.CaseSensitivity.INSENSITIVE -> symbol
}
}

override fun toString(): String = message
override val message: String get() = messageFormatter()

public data class ParseError(val parseErrorMessage: String) :
data class ParseError(val parseErrorMessage: String) :
PlanningProblemDetails(ProblemSeverity.ERROR, { parseErrorMessage })

public data class CompileError(val errorMessage: String) :
data class CompileError(val errorMessage: String) :
PlanningProblemDetails(ProblemSeverity.ERROR, { errorMessage })

public data class UndefinedVariable(val id: BindingPath) :
data class UndefinedVariable(val id: BindingPath) :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{
Expand All @@ -37,7 +61,7 @@ public sealed class PlanningProblemDetails(
}
)

public data class UndefinedDmlTarget(val variableName: String, val caseSensitive: Boolean) :
data class UndefinedDmlTarget(val variableName: String, val caseSensitive: Boolean) :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{
Expand All @@ -47,25 +71,25 @@ public sealed class PlanningProblemDetails(
}
)

public data class VariablePreviouslyDefined(val variableName: String) :
data class VariablePreviouslyDefined(val variableName: String) :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{ "The variable '$variableName' was previously defined." }
)

public data class UnimplementedFeature(val featureName: String) :
data class UnimplementedFeature(val featureName: String) :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{ "The syntax at this location is valid but utilizes unimplemented PartiQL feature '$featureName'" }
)

public object InvalidDmlTarget :
object InvalidDmlTarget :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{ "Expression is not a valid DML target. Hint: only table names are allowed here." }
)

public object InsertValueDisallowed :
object InsertValueDisallowed :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{
Expand All @@ -74,7 +98,7 @@ public sealed class PlanningProblemDetails(
}
)

public object InsertValuesDisallowed :
object InsertValuesDisallowed :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{
Expand All @@ -83,27 +107,32 @@ public sealed class PlanningProblemDetails(
}
)

public data class UnexpectedType(
data class UnexpectedType(
val actualType: StaticType,
val expectedTypes: Set<StaticType>,
) : PlanningProblemDetails(ProblemSeverity.ERROR, {
"Unexpected type $actualType, expected one of ${expectedTypes.joinToString()}"
})

public data class UnknownFunction(
data class UnknownFunction(
val identifier: String,
val args: List<StaticType>,
) : PlanningProblemDetails(ProblemSeverity.ERROR, {
val types = args.joinToString { "<${it.toString().lowercase()}>" }
"Unknown function `$identifier($types)"
})

public object ExpressionAlwaysReturnsNullOrMissing : PlanningProblemDetails(
data class ExpressionAlwaysReturnsMissing(val reason: String? = null) : PlanningProblemDetails(
severity = ProblemSeverity.ERROR,
messageFormatter = { "Expression always returns null or missing: caused by $reason" }
)

object ExpressionAlwaysReturnsNullOrMissing : PlanningProblemDetails(
severity = ProblemSeverity.ERROR,
messageFormatter = { "Expression always returns null or missing." }
)

public data class InvalidArgumentTypeForFunction(
data class InvalidArgumentTypeForFunction(
val functionName: String,
val expectedType: StaticType,
val actualType: StaticType,
Expand All @@ -113,7 +142,7 @@ public sealed class PlanningProblemDetails(
messageFormatter = { "Invalid argument type for $functionName. Expected $expectedType but got $actualType" }
)

public data class IncompatibleTypesForOp(
data class IncompatibleTypesForOp(
val actualTypes: List<StaticType>,
val operator: String,
) :
Expand All @@ -122,31 +151,9 @@ public sealed class PlanningProblemDetails(
messageFormatter = { "${actualTypes.joinToString()} is/are incompatible data types for the '$operator' operator." }
)

public data class UnresolvedExcludeExprRoot(val root: String) :
data class UnresolvedExcludeExprRoot(val root: String) :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{ "Exclude expression given an unresolvable root '$root'" }
)
}

private fun quotationHint(caseSensitive: Boolean) =
if (caseSensitive) {
// Individuals that are new to SQL often try to use double quotes for string literals.
// Let's help them out a bit.
" Hint: did you intend to use single-quotes (') here? Remember that double-quotes (\") denote " +
"quoted identifiers and single-quotes denote strings."
} else {
""
}

private fun Identifier.sql(): String = when (this) {
is Identifier.Qualified -> this.sql()
is Identifier.Symbol -> this.sql()
}

private fun Identifier.Qualified.sql(): String = root.sql() + "." + steps.joinToString(".") { it.sql() }

private fun Identifier.Symbol.sql(): String = when (caseSensitivity) {
Identifier.CaseSensitivity.SENSITIVE -> "\"$symbol\""
Identifier.CaseSensitivity.INSENSITIVE -> symbol
}
Loading

0 comments on commit 00611ba

Please sign in to comment.