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

Apply "PartiQL Eval - Planner Mode (#1385)" #1429

Merged
merged 3 commits into from
Apr 15, 2024
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
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 @@ -204,6 +204,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 @@ -12,18 +12,42 @@ 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(
Expand Down Expand Up @@ -90,25 +114,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 @@ -117,7 +141,7 @@ public sealed class PlanningProblemDetails(
}
)

public object InsertValuesDisallowed :
object InsertValuesDisallowed :
PlanningProblemDetails(
ProblemSeverity.ERROR,
{
Expand All @@ -126,14 +150,14 @@ 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, {
Expand All @@ -154,7 +178,12 @@ public sealed class PlanningProblemDetails(
messageFormatter = { "Expression always returns null or missing." }
)

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

data class InvalidArgumentTypeForFunction(
val functionName: String,
val expectedType: StaticType,
val actualType: StaticType,
Expand All @@ -164,7 +193,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 @@ -173,31 +202,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
Loading