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

Add Plan Evaluator #592

Merged
merged 24 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
48a003e
Add 3 query planner passes.
dlurton May 10, 2022
a5bd78b
Address build failures
dlurton May 10, 2022
befac52
Add FILTER_DISTINCT ExprFunction
dlurton May 11, 2022
10ebc0e
Add PlannerPipeline
dlurton Apr 29, 2022
45d5d13
Add Plan evaluator.
dlurton Apr 30, 2022
3b898db
Fix test failure and add DynamicLookupExprFunctionTest
dlurton May 11, 2022
a73fe2c
add DynamicLookupExprFunction tests
dlurton May 11, 2022
ebd36d3
Merge branch 'physical-plan-staging' into physical-plan-staging-plann…
dlurton May 19, 2022
f18b6bb
Fix post-merge build failures
dlurton May 19, 2022
77a4ec5
Make ktlint happy
dlurton May 19, 2022
8383b55
Renames UniqueIdResolver to MetadataResolver
dlurton May 23, 2022
4a780a2
Merge branch 'physical-plan-staging-metadata-resolver' into physical-…
dlurton May 23, 2022
7d84673
emptySchemaResolver -> emptyMetadataResolver
dlurton May 23, 2022
6f5d1ec
Merge branch 'physical-plan-staging-metadata-resolver' into physical-…
dlurton May 23, 2022
2dd95c7
Fix dependent code after merge
dlurton May 23, 2022
a2ced51
Account for name change
dlurton May 23, 2022
89931f8
Merge branch 'physical-plan-staging-planner-pipeline' into physical-p…
dlurton May 23, 2022
99697f8
Post merge fixes
dlurton May 24, 2022
dedb7c1
Merge branch 'physical-plan-staging' into physical-plan-staging-plan-…
dlurton May 25, 2022
9b5f1a5
Revert "Remove custom functions, procedures and types from Planner API"
dlurton May 25, 2022
168d2ce
Mark custom functions, procedures and types on PlannerPipeline as int…
dlurton May 25, 2022
d07eda7
Add case-sensitivity to global_id
dlurton May 25, 2022
c81ab2c
Minor changes to docs
dlurton May 25, 2022
962d134
Add some kdoc
dlurton May 26, 2022
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
2 changes: 1 addition & 1 deletion lang/resources/org/partiql/type-domains/partiql.ion
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ may then be further optimized by selecting better implementations of each operat
// The value of `uniqueId` is PartiQL integration defined and can be any symbol that uniquely
// identifies the global variable. Examples include database object ids or the alphabetical case
// respecting table name found after case-insensitive lookup.
(global_id name::symbol uniqueId::symbol)
(global_id uniqueId::symbol case::case_sensitivity)
)
)
)
Expand Down
9 changes: 7 additions & 2 deletions lang/src/org/partiql/lang/CompilerPipeline.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import org.partiql.lang.types.StaticType
import org.partiql.lang.util.interruptibleFold

/**
* Contains all of the information needed for processing steps.
* Contains all information needed for processing steps.
*/
data class StepContext(
/** The instance of [ExprValueFactory] that is used by the pipeline. */
Expand Down Expand Up @@ -102,6 +102,11 @@ interface CompilerPipeline {
*/
val procedures: @JvmSuppressWildcards Map<String, StoredProcedure>

/**
* The configured global type bindings.
*/
val globalTypeBindings: Bindings<StaticType>?

/** Compiles the specified PartiQL query using the configured parser. */
fun compile(query: String): Expression

Expand Down Expand Up @@ -244,7 +249,7 @@ internal class CompilerPipelineImpl(
override val customDataTypes: List<CustomType>,
override val procedures: Map<String, StoredProcedure>,
private val preProcessingSteps: List<ProcessingStep>,
private val globalTypeBindings: Bindings<StaticType>?
override val globalTypeBindings: Bindings<StaticType>?
) : CompilerPipeline {

private val compiler = EvaluatingCompiler(
Expand Down
2 changes: 1 addition & 1 deletion lang/src/org/partiql/lang/ast/passes/SemanticException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class SemanticException(
constructor(err: Problem, cause: Throwable? = null) :
this(
message = "",
errorCode = ErrorCode.SEMANTIC_INFERENCER_ERROR,
errorCode = ErrorCode.SEMANTIC_PROBLEM,
errorContext = propertyValueMapOf(
Property.LINE_NUMBER to err.sourceLocation.lineNum,
Property.COLUMN_NUMBER to err.sourceLocation.charOffset,
Expand Down
8 changes: 1 addition & 7 deletions lang/src/org/partiql/lang/errors/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ enum class ErrorCode(
"got: ${errorContext?.get(Property.ACTUAL_ARGUMENT_TYPES) ?: UNKNOWN}"
},

SEMANTIC_INFERENCER_ERROR(
SEMANTIC_PROBLEM(
ErrorCategory.SEMANTIC,
LOCATION + setOf(Property.MESSAGE),
""
Expand Down Expand Up @@ -980,12 +980,6 @@ enum class ErrorCode(
ErrorBehaviorInPermissiveMode.RETURN_MISSING
),

EVALUATOR_SQL_EXCEPTION(
ErrorCategory.EVALUATOR,
LOCATION,
"SQL exception"
),

EVALUATOR_COUNT_START_NOT_ALLOWED(
ErrorCategory.EVALUATOR,
LOCATION,
Expand Down
101 changes: 101 additions & 0 deletions lang/src/org/partiql/lang/eval/builtins/DynamicLookupExprFunction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.partiql.lang.eval.builtins

import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.eval.BindingCase
import org.partiql.lang.eval.BindingName
import org.partiql.lang.eval.EvaluationException
import org.partiql.lang.eval.EvaluationSession
import org.partiql.lang.eval.ExprFunction
import org.partiql.lang.eval.ExprValue
import org.partiql.lang.eval.ExprValueType
import org.partiql.lang.eval.physical.throwUndefinedVariableException
import org.partiql.lang.eval.stringValue
import org.partiql.lang.types.FunctionSignature
import org.partiql.lang.types.StaticType
import org.partiql.lang.types.VarargFormalParameter

/**
* Performs dynamic variable resolution. Query authors should never call this function directly (and indeed it is
* named to avoid collision with the names of custom functions)--instead, the query planner injects call sites
* to this function to perform dynamic variable resolution of undefined variables. This provides a migration path
* for legacy customers that depend on this behavior.
*
* Arguments:
*
* - variable name
* - case sensitivity
* - lookup strategy (globals then locals or locals then globals)
* - A variadic list of locations to be searched.
*
* The variadic arguments must be of type `any` because the planner doesn't yet have knowledge of static types
* and therefore cannot filter out local variables types that are not structs.
*/
class DynamicLookupExprFunction : ExprFunction {
override val signature: FunctionSignature
get() {
return FunctionSignature(
name = DYNAMIC_LOOKUP_FUNCTION_NAME,
// Required parameters are: variable name, case sensitivity and lookup strategy
requiredParameters = listOf(StaticType.SYMBOL, StaticType.SYMBOL, StaticType.SYMBOL),
variadicParameter = VarargFormalParameter(StaticType.ANY, 0..Int.MAX_VALUE),
returnType = StaticType.ANY
)
}

override fun callWithVariadic(
session: EvaluationSession,
required: List<ExprValue>,
variadic: List<ExprValue>
): ExprValue {
val variableName = required[0].stringValue()

val caseSensitivity = when (val caseSensitivityParameterValue = required[1].stringValue()) {
"case_sensitive" -> BindingCase.SENSITIVE
"case_insensitive" -> BindingCase.INSENSITIVE
else -> throw EvaluationException(
message = "Invalid case sensitivity: $caseSensitivityParameterValue",
errorCode = ErrorCode.INTERNAL_ERROR,
internal = true
)
}

val bindingName = BindingName(variableName, caseSensitivity)

val globalsFirst = when (val lookupStrategyParameterValue = required[2].stringValue()) {
"locals_then_globals" -> false
"globals_then_locals" -> true
else -> throw EvaluationException(
message = "Invalid lookup strategy: $lookupStrategyParameterValue",
errorCode = ErrorCode.INTERNAL_ERROR,
internal = true
)
}

val found = when {
globalsFirst -> {
session.globals[bindingName] ?: searchLocals(variadic, bindingName)
}
else -> {
searchLocals(variadic, bindingName) ?: session.globals[bindingName]
}
}

if (found == null) {
// We don't know the metas inside ExprFunction implementations. The ThunkFactory error handlers
// should add line & col info to the exception & rethrow anyway.
throwUndefinedVariableException(bindingName, metas = null)
} else {
return found
}
}

private fun searchLocals(possibleLocations: List<ExprValue>, bindingName: BindingName) =
possibleLocations.asSequence().map {
when (it.type) {
ExprValueType.STRUCT ->
it.bindings[bindingName]
else ->
null
}
}.firstOrNull { it != null }
}
35 changes: 35 additions & 0 deletions lang/src/org/partiql/lang/eval/physical/EvaluatorState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at:
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/

package org.partiql.lang.eval.physical

import org.partiql.lang.eval.EvaluationSession
import org.partiql.lang.eval.ExprValue

/**
* Contains state needed during query evaluation such as an instance of [EvaluationSession] and an array of [registers]
* for each local variable that is part of the query.
*
* Since the elements of [registers] are mutable, when/if we decide to make query execution multi-threaded, we'll have
* to take care to not share [EvaluatorState] instances among different threads.
*
* @param session The evaluation session.
* @param registers An array of registers containing [ExprValue]s needed during query execution. Generally, there is
* one register per local variable. This is an array (and not a [List]) because its semantics match exactly what we
* need: fixed length but mutable elements.
*/
internal class EvaluatorState(
val session: EvaluationSession,
val registers: Array<ExprValue>
)
106 changes: 106 additions & 0 deletions lang/src/org/partiql/lang/eval/physical/OffsetLimitHelpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.partiql.lang.eval.physical

import com.amazon.ion.IntegerSize
import com.amazon.ion.IonInt
import org.partiql.lang.ast.SourceLocationMeta
import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.errors.Property
import org.partiql.lang.eval.ExprValueType
import org.partiql.lang.eval.err
import org.partiql.lang.eval.errorContextFrom
import org.partiql.lang.eval.numberValue

// The functions in this file look very similar and so the temptation to DRY is quite strong....
// However, there are enough subtle differences between them that avoiding the duplication isn't worth it.

internal fun evalLimitRowCount(rowCountThunk: PhysicalPlanThunk, env: EvaluatorState, limitLocationMeta: SourceLocationMeta?): Long {
val limitExprValue = rowCountThunk(env)

if (limitExprValue.type != ExprValueType.INT) {
err(
"LIMIT value was not an integer",
ErrorCode.EVALUATOR_NON_INT_LIMIT_VALUE,
errorContextFrom(limitLocationMeta).also {
it[Property.ACTUAL_TYPE] = limitExprValue.type.toString()
},
internal = false
)
}

// `Number.toLong()` (used below) does *not* cause an overflow exception if the underlying [Number]
// implementation (i.e. Decimal or BigInteger) exceeds the range that can be represented by Longs.
// This can cause very confusing behavior if the user specifies a LIMIT value that exceeds
// Long.MAX_VALUE, because no results will be returned from their query. That no overflow exception
// is thrown is not a problem as long as PartiQL's restriction of integer values to +/- 2^63 remains.
// We throw an exception here if the value exceeds the supported range (say if we change that
// restriction or if a custom [ExprValue] is provided which exceeds that value).
val limitIonValue = limitExprValue.ionValue as IonInt
if (limitIonValue.integerSize == IntegerSize.BIG_INTEGER) {
err(
"IntegerSize.BIG_INTEGER not supported for LIMIT values",
ErrorCode.INTERNAL_ERROR,
errorContextFrom(limitLocationMeta),
internal = true
)
}

val limitValue = limitExprValue.numberValue().toLong()

if (limitValue < 0) {
err(
"negative LIMIT",
ErrorCode.EVALUATOR_NEGATIVE_LIMIT,
errorContextFrom(limitLocationMeta),
internal = false
)
}

// we can't use the Kotlin's Sequence<T>.take(n) for this since it accepts only an integer.
// this references [Sequence<T>.take(count: Long): Sequence<T>] defined in [org.partiql.util].
return limitValue
}

internal fun evalOffsetRowCount(rowCountThunk: PhysicalPlanThunk, env: EvaluatorState, offsetLocationMeta: SourceLocationMeta?): Long {
val offsetExprValue = rowCountThunk(env)

if (offsetExprValue.type != ExprValueType.INT) {
err(
"OFFSET value was not an integer",
ErrorCode.EVALUATOR_NON_INT_OFFSET_VALUE,
errorContextFrom(offsetLocationMeta).also {
it[Property.ACTUAL_TYPE] = offsetExprValue.type.toString()
},
internal = false
)
}

// `Number.toLong()` (used below) does *not* cause an overflow exception if the underlying [Number]
// implementation (i.e. Decimal or BigInteger) exceeds the range that can be represented by Longs.
// This can cause very confusing behavior if the user specifies a OFFSET value that exceeds
// Long.MAX_VALUE, because no results will be returned from their query. That no overflow exception
// is thrown is not a problem as long as PartiQL's restriction of integer values to +/- 2^63 remains.
// We throw an exception here if the value exceeds the supported range (say if we change that
// restriction or if a custom [ExprValue] is provided which exceeds that value).
val offsetIonValue = offsetExprValue.ionValue as IonInt
if (offsetIonValue.integerSize == IntegerSize.BIG_INTEGER) {
err(
"IntegerSize.BIG_INTEGER not supported for OFFSET values",
ErrorCode.INTERNAL_ERROR,
errorContextFrom(offsetLocationMeta),
internal = true
)
}

val offsetValue = offsetExprValue.numberValue().toLong()

if (offsetValue < 0) {
err(
"negative OFFSET",
ErrorCode.EVALUATOR_NEGATIVE_OFFSET,
errorContextFrom(offsetLocationMeta),
internal = false
)
}

return offsetValue
}
Loading