Skip to content

Commit

Permalink
Query planner passes: AST->logical, logical->resolved, resolved->phys…
Browse files Browse the repository at this point in the history
…ical

Not yet integrated with anything.
  • Loading branch information
dlurton committed Apr 29, 2022
1 parent 3f1d7c5 commit 41e6384
Show file tree
Hide file tree
Showing 17 changed files with 1,836 additions and 14 deletions.
38 changes: 26 additions & 12 deletions lang/src/org/partiql/lang/domains/util.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.partiql.lang.domains

import com.amazon.ionelement.api.IonElement
import com.amazon.ionelement.api.MetaContainer
import com.amazon.ionelement.api.emptyMetaContainer
import com.amazon.ionelement.api.metaContainerOf
Expand All @@ -14,6 +15,19 @@ import org.partiql.lang.eval.BindingCase
fun PartiqlAst.Builder.id(name: String) =
id(name, caseInsensitive(), unqualified())

// TODO: once https://github.com/partiql/partiql-ir-generator/issues/6 has been completed, we can delete this.
fun PartiqlLogical.Builder.id(name: String) =
id(name, caseInsensitive(), unqualified())

// TODO: once https://github.com/partiql/partiql-ir-generator/issues/6 has been completed, we can delete this.
fun PartiqlLogical.Builder.pathExpr(exp: PartiqlLogical.Expr) =
pathExpr(exp, caseInsensitive())

// Workaround for a bug in PIG that is fixed in its next release:
// https://github.com/partiql/partiql-ir-generator/issues/41
fun List<IonElement>.asAnyElement() =
this.map { it.asAnyElement() }

val MetaContainer.staticType: StaticTypeMeta? get() = this[StaticTypeMeta.TAG] as StaticTypeMeta?

/** Constructs a container with the specified metas. */
Expand Down Expand Up @@ -60,17 +74,17 @@ fun PartiqlAst.CaseSensitivity.toBindingCase(): BindingCase = when (this) {
}

/**
* Returns the [SourceLocationMeta] as an error context if the [SourceLocationMeta.TAG] exists in the passed
* [metaContainer]. Otherwise, returns an empty map.
* Converts a [PartiqlLogical.CaseSensitivity] to a [BindingCase].
*/
fun errorContextFrom(metaContainer: MetaContainer?): PropertyValueMap {
if (metaContainer == null) {
return PropertyValueMap()
}
val location = metaContainer[SourceLocationMeta.TAG] as? SourceLocationMeta
return if (location != null) {
org.partiql.lang.eval.errorContextFrom(location)
} else {
PropertyValueMap()
}
fun PartiqlLogical.CaseSensitivity.toBindingCase(): BindingCase = when (this) {
is PartiqlLogical.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE
is PartiqlLogical.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE
}

/**
* Converts a [PartiqlLogical.CaseSensitivity] to a [BindingCase].
*/
fun PartiqlPhysical.CaseSensitivity.toBindingCase(): BindingCase = when (this) {
is PartiqlPhysical.CaseSensitivity.CaseInsensitive -> BindingCase.INSENSITIVE
is PartiqlPhysical.CaseSensitivity.CaseSensitive -> BindingCase.SENSITIVE
}
2 changes: 1 addition & 1 deletion lang/src/org/partiql/lang/eval/EvaluatingCompiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3058,7 +3058,7 @@ private class SingleProjectionElement(val name: ExprValue, val thunk: ThunkEnv)
*/
private class MultipleProjectionElement(val thunks: List<ThunkEnv>) : ProjectionElement()

private val MetaContainer.sourceLocationMeta get() = this[SourceLocationMeta.TAG] as? SourceLocationMeta
internal val MetaContainer.sourceLocationMeta get() = this[SourceLocationMeta.TAG] as? SourceLocationMeta

private fun StaticType.getTypes() = when (val flattened = this.flatten()) {
is AnyOfType -> flattened.types
Expand Down
31 changes: 31 additions & 0 deletions lang/src/org/partiql/lang/eval/builtins/BuiltinFunctions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
package org.partiql.lang.eval.builtins

import com.amazon.ion.system.IonSystemBuilder
import org.partiql.lang.eval.DEFAULT_COMPARATOR
import org.partiql.lang.eval.EvaluationSession
import org.partiql.lang.eval.ExprFunction
import org.partiql.lang.eval.ExprValue
import org.partiql.lang.eval.ExprValueFactory
import org.partiql.lang.eval.stringValue
import org.partiql.lang.eval.unnamedValue
import org.partiql.lang.types.AnyOfType
import org.partiql.lang.types.FunctionSignature
import org.partiql.lang.types.StaticType
import org.partiql.lang.types.UnknownArguments
import java.util.TreeSet

internal const val DYNAMIC_LOOKUP_FUNCTION_NAME = "\$__dynamic_lookup__"

internal fun createBuiltinFunctionSignatures(): Map<String, FunctionSignature> =
// Creating a new IonSystem in this instance is not the problem it would normally be since we are
Expand All @@ -40,6 +45,7 @@ internal fun createBuiltinFunctions(valueFactory: ExprValueFactory) =
createCharacterLength("character_length", valueFactory),
createCharacterLength("char_length", valueFactory),
createUtcNow(valueFactory),
createFilterDistinct(valueFactory),
DateAddExprFunction(valueFactory),
DateDiffExprFunction(valueFactory),
ExtractExprFunction(valueFactory),
Expand All @@ -52,6 +58,7 @@ internal fun createBuiltinFunctions(valueFactory: ExprValueFactory) =
SizeExprFunction(valueFactory),
FromUnixTimeFunction(valueFactory),
UnixTimestampFunction(valueFactory)
// Note that we do not include DynamicLookupExprFunction here since it is only needed by the plan evaluator.
)

internal fun createExists(valueFactory: ExprValueFactory): ExprFunction = object : ExprFunction {
Expand All @@ -77,6 +84,30 @@ internal fun createUtcNow(valueFactory: ExprValueFactory): ExprFunction = object
valueFactory.newTimestamp(session.now)
}

internal fun createFilterDistinct(valueFactory: ExprValueFactory): ExprFunction = object : ExprFunction {
override val signature = FunctionSignature(
"filter_distinct",
listOf(StaticType.unionOf(StaticType.BAG, StaticType.LIST, StaticType.SEXP, StaticType.STRUCT)),
returnType = StaticType.BAG
)

override fun callWithRequired(session: EvaluationSession, required: List<ExprValue>): ExprValue {
val argument = required.first()
// We cannot use a [HashSet] here because [ExprValue] does not implement .equals() and .hashCode()
val encountered = TreeSet(DEFAULT_COMPARATOR)
return valueFactory.newBag(
sequence {
argument.asSequence().forEach {
if (!encountered.contains(it)) {
encountered.add(it.unnamedValue())
yield(it)
}
}
}
)
}
}

internal fun createCharacterLength(name: String, valueFactory: ExprValueFactory): ExprFunction =
object : ExprFunction {
override val signature: FunctionSignature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import org.partiql.lang.ast.IsCountStarMeta
import org.partiql.lang.ast.passes.SemanticException
import org.partiql.lang.domains.PartiqlAst
import org.partiql.lang.domains.addSourceLocation
import org.partiql.lang.domains.errorContextFrom
import org.partiql.lang.errors.ErrorCode
import org.partiql.lang.errors.Property
import org.partiql.lang.errors.PropertyValueMap
import org.partiql.lang.eval.CompileOptions
import org.partiql.lang.eval.EvaluationException
import org.partiql.lang.eval.TypedOpBehavior
import org.partiql.lang.eval.err
import org.partiql.lang.eval.errorContextFrom
import org.partiql.pig.runtime.LongPrimitive

/**
Expand Down
50 changes: 50 additions & 0 deletions lang/src/org/partiql/lang/planner/GlobalBindings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.partiql.lang.planner

import org.partiql.lang.eval.BindingCase
import org.partiql.lang.eval.BindingName

/** Indicates the result of an attempt to resolve a global binding. */
sealed class ResolutionResult {
/**
* A success case, indicates the [uniqueId] of the match to the [BindingName] in the global scope.
* Typically, this is defined by the storage layer.
*/
data class GlobalVariable(val uniqueId: String) : ResolutionResult()

/**
* A success case, indicates the [index] of the only possible match to the [BindingName] in a local lexical scope.
* This is `internal` because [index] is an implementation detail that shouldn't be accessible outside of this
* library.
*/
internal data class LocalVariable(val index: Int) : ResolutionResult()

/** A failure case, indicates that resolution did not match any variable. */
object Undefined : ResolutionResult()
}

fun interface GlobalBindings {
/**
* Implementations try to resolve a global variable which is typically a database table, as identified by a
* [bindingName]. The [bindingName] includes both the name as specified by the query author and a [BindingCase]
* which indicates if query author included double quotes (") which mean the lookup should be case-sensitive.
*
* Implementations of this function must return:
*
* - [ResolutionResult.GlobalVariable] if [bindingName] matches a global variable (typically a database table).
* - [ResolutionResult.Undefined] if no identifier matches [bindingName].
*
* When determining if a variable name matches a global variable, it is important to consider if the comparison
* should be case-sensitive or case-insensitive. @see [BindingName.bindingCase]. In the event that more than one
* variable matches a case-insensitive [BindingName], the implementation must still select one of them
* without providing an error. (This is consistent with Postres's behavior in this scenario.)
*
* Note that while [ResolutionResult.LocalVariable] exists, it is intentionally marked `internal` and cannot
* be used by outside of this project..
*/
fun resolve(bindingName: BindingName): ResolutionResult
}

private val EMPTY = GlobalBindings { ResolutionResult.Undefined }

/** Convenience function for obtaining an instance of [GlobalBindings] with no defined variables. */
fun emptyGlobalBindings(): GlobalBindings = EMPTY
15 changes: 15 additions & 0 deletions lang/src/org/partiql/lang/planner/PassResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.partiql.lang.planner
import org.partiql.lang.errors.Problem

sealed class PassResult<TResult> {
/**
* Indicates query planning was successful and includes a list of any warnings that were encountered along the way.
*/
data class Success<TResult>(val result: TResult, val warnings: List<Problem>) : PassResult<TResult>()

/**
* Indicates query planning was not successful and includes a list of errors and warnings that were encountered
* along the way.
*/
data class Error<TResult>(val errors: List<Problem>) : PassResult<TResult>()
}
25 changes: 25 additions & 0 deletions lang/src/org/partiql/lang/planner/transforms/AstNormalize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.partiql.lang.planner.transforms

import org.partiql.lang.domains.PartiqlAst
import org.partiql.lang.eval.visitors.FromSourceAliasVisitorTransform
import org.partiql.lang.eval.visitors.PipelinedVisitorTransform
import org.partiql.lang.eval.visitors.SelectListItemAliasVisitorTransform
import org.partiql.lang.eval.visitors.SelectStarVisitorTransform

/**
* Executes the [SelectListItemAliasVisitorTransform], [FromSourceAliasVisitorTransform] and
* [SelectStarVisitorTransform] passes on the receiver.
*/
fun PartiqlAst.Statement.normalize(): PartiqlAst.Statement {
// Since these passes all work on PartiqlAst, we can use a PipelinedVisitorTransform which executes each
// specified VisitorTransform in sequence.
val transforms = PipelinedVisitorTransform(
// Synthesizes unspecified `SELECT <expr> AS ...` aliases
SelectListItemAliasVisitorTransform(),
// Synthesizes unspecified `FROM <expr> AS ...` aliases
FromSourceAliasVisitorTransform(),
// Changes `SELECT * FROM a, b` to SELECT a.*, b.* FROM a, b`
SelectStarVisitorTransform()
)
return transforms.transformStatement(this)
}
Loading

0 comments on commit 41e6384

Please sign in to comment.