From 77760dc68d81377e3a68b856b749b5e3df19be28 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Wed, 24 Jan 2024 16:28:23 -0800 Subject: [PATCH] [1/2] Catalog obj resolution --- .../partiql/planner/PartiQLPlannerDefault.kt | 1 - .../org/partiql/planner/internal/Env.kt | 160 ++------ .../internal/{PathEntry.kt => PathItem.kt} | 2 +- .../org/partiql/planner/internal/PathMatch.kt | 13 + .../partiql/planner/internal/PathResolver.kt | 72 +++- .../planner/internal/PathResolverObj.kt | 117 +----- .../partiql/planner/internal/ResolvedVar.kt | 49 --- .../org/partiql/planner/internal/Symbols.kt | 35 ++ .../internal/catalog/CatalogResolver.kt | 23 -- .../planner/internal/transforms/AstToPlan.kt | 10 +- .../internal/transforms/RelConverter.kt | 8 +- .../internal/transforms/RexConverter.kt | 68 ++-- .../planner/internal/typer/PlanTyper.kt | 38 +- .../{ResolutionStrategy.kt => typer/Scope.kt} | 4 +- .../partiql/planner/internal/typer/TypeEnv.kt | 41 +- .../planner/internal/typer/TypeLattice.kt | 375 ------------------ .../org/partiql/planner/internal/EnvTest.kt | 16 +- .../planner/internal/typer/PlanTyperTest.kt | 4 +- 18 files changed, 232 insertions(+), 804 deletions(-) rename partiql-planner/src/main/kotlin/org/partiql/planner/internal/{PathEntry.kt => PathItem.kt} (91%) create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathMatch.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolvedVar.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/Symbols.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/catalog/CatalogResolver.kt rename partiql-planner/src/main/kotlin/org/partiql/planner/internal/{ResolutionStrategy.kt => typer/Scope.kt} (82%) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeLattice.kt diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerDefault.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerDefault.kt index 67333482d1..a8e1759832 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerDefault.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerDefault.kt @@ -4,7 +4,6 @@ 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.internal.ir.PartiQLVersion import org.partiql.planner.internal.transforms.AstToPlan import org.partiql.planner.internal.transforms.PlanTransform import org.partiql.planner.internal.typer.PlanTyper diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt index 2fdbf7bde3..67de00eca2 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -2,162 +2,60 @@ package org.partiql.planner.internal import org.partiql.planner.PartiQLPlanner import org.partiql.planner.internal.ir.Catalog +import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.rex +import org.partiql.planner.internal.ir.rexOpGlobal +import org.partiql.planner.internal.typer.TypeEnv.Companion.toPath import org.partiql.spi.BindingPath +import org.partiql.spi.connector.ConnectorMetadata +import org.partiql.types.StaticType /** - * [Env] represents the combination of the database type environment (db env) and some local env (type env). + * [Env] is similar to the database type environment from the PartiQL Specification. This includes resolution of + * database binding values and scoped functions. + * + * See [TypeEnv] for the variables type environment. * * @property session */ internal class Env(private val session: PartiQLPlanner.Session) { /** - * Collect the list of all referenced globals during planning. - */ - public val catalogs = mutableListOf() - - /** - * Catalog Metadata for this query session. + * Maintain a list of all resolved catalog symbols (objects and functions). */ - private val connectors = session.catalogs + private val symbols: Symbols = Symbols.empty() /** - * Leverages a [FnResolver] to find a matching function defined in the [Header] scalar function catalog. + * Convert the symbols structure into a list of [Catalog] for shipping in the plan. */ - internal fun resolveFn(fn: Fn.Unresolved, args: List) = fnResolver.resolveFn(fn, args) + internal fun catalogs(): List = symbols.build() /** - * Leverages a [FnResolver] to find a matching function defined in the [Header] aggregation function catalog. + * Current catalog [ConnectorMetadata]. Error if missing from the session. */ - internal fun resolveAgg(agg: Agg.Unresolved, args: List) = fnResolver.resolveAgg(agg, args) + private val catalog: ConnectorMetadata = session.catalogs[session.currentCatalog] + ?: error("Session is missing ConnectorMetadata for current catalog ${session.currentCatalog}") /** - * Fetch global object metadata from the given [BindingPath]. - * - * @param catalog Current catalog - * @param path Global identifier path - * @return + * A [PathResolver] for database objects. */ - fun catalogs(): List = catalogs.map { it.build() } + private val objects: PathResolverObj = PathResolverObj(catalog, session) /** + * This function looks up a global [BindingPath], returning a global reference expression. * + * Convert any remaining binding names (tail) to a path expression. * * @param path - * @param locals - * @param strategy * @return */ - public fun resolve(path: BindingPath, locals: TypeEnv, strategy: ResolutionStrategy): Rex? = when (strategy) { - ResolutionStrategy.LOCAL -> locals.resolve(path) ?: catalog.lookup - ResolutionStrategy.GLOBAL -> global(path) ?: locals.resolve(path) - } - - - /** - * TODO optimization, check known globals before calling out to connector again - * - * @param catalog - * @param originalPath - * @param catalogPath - * @return - */ - private fun getGlobalType( - catalog: BindingName?, - originalPath: BindingPath, - catalogPath: BindingPath, - ): ResolvedVar? { - return catalog?.let { cat -> - getObjectHandle(cat, catalogPath)?.let { handle -> - getObjectDescriptor(handle).let { type -> - val depth = calculateMatched(originalPath, catalogPath, handle.second.absolutePath) - val (catalogIndex, valueIndex) = getOrAddCatalogValue( - handle.first, - handle.second.absolutePath.steps, - type - ) - // Return resolution metadata - ResolvedVar(type, catalogIndex, depth, valueIndex) - } - } - } - } - - - // /** - // * TODO - // * - // * @param fn - // * @return - // */ - // @OptIn(FnExperimental::class) - // public fun resolve(fn: Fn.Unresolved, args: List): Rex? { - // TODO() - // } - - /** - * Attempt to resolve a [BindingPath] in the global + local type environments. - */ - fun resolve(path: BindingPath, locals: TypeEnv, strategy: ResolutionStrategy): Rex? { - return when (strategy) { - ResolutionStrategy.LOCAL -> locals.resolve(path) ?: resolveGlobalBind(path) - ResolutionStrategy.GLOBAL -> resolveGlobalBind(path) ?: locals.resolve(path) - } - } - - /** - * Logic is as follows: - * 1. If Current Catalog and Schema are set, create a Path to the object and attempt to grab handle and schema. - * a. If not found, just try to find the object in the catalog. - * 2. If Current Catalog is not set: - * a. Loop through all catalogs and try to find the object. - * - * TODO: Add global bindings - * TODO: Replace paths with global variable references if found - */ - private fun resolveGlobalBind(path: BindingPath): Rex? { - val currentCatalog = session.currentCatalog?.let { BindingName(it, BindingCase.SENSITIVE) } - val currentCatalogPath = BindingPath(session.currentDirectory.map { BindingName(it, BindingCase.SENSITIVE) }) - val absoluteCatalogPath = BindingPath(currentCatalogPath.steps + path.steps) - val resolvedVar = when (path.steps.size) { - 0 -> null - 1 -> getGlobalType(currentCatalog, path, absoluteCatalogPath) - 2 -> getGlobalType(currentCatalog, path, path) ?: getGlobalType(currentCatalog, path, absoluteCatalogPath) - else -> { - val inferredCatalog = path.steps[0] - val newPath = BindingPath(path.steps.subList(1, path.steps.size)) - getGlobalType(inferredCatalog, path, newPath) - ?: getGlobalType(currentCatalog, path, path) - ?: getGlobalType(currentCatalog, path, absoluteCatalogPath) - } - } ?: return null - // rewrite as path expression for any remaining steps. - val root = rex(resolvedVar.type, rexOpGlobal(catalogSymbolRef(resolvedVar.ordinal, resolvedVar.position))) - val tail = path.steps.drop(resolvedVar.depth) + fun resolveObj(path: BindingPath): Rex? { + val match = objects.lookup(path) ?: return null + // Insert into symbols, producing a reference + val ref = symbols.insert(match.item) + // Rewrite as an untyped path expression. + val root = rex(StaticType.ANY, rexOpGlobal(ref)) + val tail = path.steps.drop(match.depth) return if (tail.isEmpty()) root else root.toPath(tail) } - - /** - * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata - * 1. Matched = RelativePath - Not Found - * 2. Not Found = Input CatalogPath - Output CatalogPath - * 3. Matched = RelativePath - (Input CatalogPath - Output CatalogPath) - * 4. Matched = RelativePath + Output CatalogPath - Input CatalogPath - */ - private fun calculateMatched( - originalPath: BindingPath, - inputCatalogPath: BindingPath, - outputCatalogPath: ConnectorObjectPath, - ): Int { - return originalPath.steps.size + outputCatalogPath.steps.size - inputCatalogPath.steps.size - } - - @OptIn(PartiQLValueExperimental::class) - private fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> - val op = when (step.bindingCase) { - BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(StaticType.STRING, rexOpLit(stringValue(step.name)))) - BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) - } - rex(StaticType.ANY, op) - } -} \ No newline at end of file +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathEntry.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt similarity index 91% rename from partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathEntry.kt rename to partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt index fd7a779bd2..e520fa5036 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathEntry.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt @@ -9,7 +9,7 @@ import org.partiql.spi.connector.ConnectorHandle * @property catalog The resolved entity's catalog name. * @property handle The resolved entity's catalog path and type information. */ -internal data class PathEntry( +internal data class PathItem( @JvmField val catalog: String, @JvmField val handle: ConnectorHandle, ) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathMatch.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathMatch.kt new file mode 100644 index 0000000000..fb18dbba9f --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathMatch.kt @@ -0,0 +1,13 @@ +package org.partiql.planner.internal + +/** + * Result of searching a path in a catalog. + * + * @param T + * @property depth The depth/level of the path match. + * @property item The metadata for the matched catalog item. + */ +internal data class PathMatch( + @JvmField val depth: Int, + @JvmField val item: PathItem, +) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt index fec5025438..fc1ffe861f 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt @@ -32,59 +32,93 @@ internal abstract class PathResolver( ) { /** - * + * The session's current directory represented as [BindingName] steps. */ private val schema = session.currentDirectory.map { it.toBindingName() } /** * A [PathResolver] should override this one method for which [ConnectorMetadata] API to call. * - * @param path The absolute path within a catalog. + * @param path The catalog absolute path. * @return */ abstract fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle? /** - * Resolution rules for a given name. + * Lookup a `path` following the scoping rules in the class javadoc. + * + * Returns a pair of the * * @param path This represents the exact path * @return */ - internal fun resolve(path: BindingPath): PathEntry? { + internal fun lookup(path: BindingPath): PathMatch? { val n = path.steps.size val absPath = BindingPath(schema + path.steps) return when (n) { 0 -> null - 1 -> get(absPath) - 2 -> get(absPath) ?: get(path) - else -> get(absPath) ?: get(path) ?: lookup(path) + 1 -> get(absPath, n) + 2 -> get(absPath, n) ?: get(path, n) + else -> get(absPath, n) ?: get(path, n) ?: search(path, n) } } /** * This gets the path in the current catalog. * - * @param path + * @param absPath Catalog absolute path. + * @param n Original path step size, used in depth calculation. * @return */ - private fun get(path: BindingPath): PathEntry? = - get(catalog, path)?.let { PathEntry(session.currentCatalog, it) } + private fun get(absPath: BindingPath, n: Int): PathMatch? { + val handle = get(catalog, absPath) ?: return null + val depth = depth(n, absPath.steps.size, handle.path.size) + val item = PathItem(session.currentCatalog, handle) + return PathMatch(depth, item) + } /** - * This looks for an absolute path in the current system, using the session to lookup catalogs. + * This searches with a system absolute path, using the session to lookup catalogs. * - * @param path + * @param path System absolute path. + * @param n Original path step size, used in depth calculation. */ - private fun lookup(path: BindingPath): PathEntry? { - val head = path.steps.first() - val tail = BindingPath(path.steps.drop(1)) - for ((catalog, metadata) in session.catalogs) { - if (head.matches(catalog)) { - return get(metadata, tail)?.let { PathEntry(catalog, it) } + private fun search(path: BindingPath, n: Int): PathMatch? { + var match: Map.Entry? = null + val first: BindingName = path.steps.first() + for (catalog in session.catalogs) { + if (first.matches(catalog.key)) { + if (match != null) { + // TODO root was already matched, emit ambiguous error + return null + } + match = catalog } } - return null + if (match == null) { + return null + } + // Lookup in the unambiguously matched catalog, calculating the depth matched. + val absPath = BindingPath(path.steps.drop(1)) + val catalog = match.key + val metadata = match.value + val handle = get(metadata, absPath) ?: return null + val depth = depth(n, absPath.steps.size, handle.path.size) + val item = PathItem(catalog, handle) + return PathMatch(depth, item) } private fun String.toBindingName() = BindingName(this, BindingCase.SENSITIVE) + + /** + * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata + * + * 1. Matched = RelativePath - Not Found + * 2. Not Found = Input CatalogPath - Output CatalogPath + * 3. Matched = RelativePath - (Input CatalogPath - Output CatalogPath) + * 4. Matched = RelativePath + Output CatalogPath - Input CatalogPath + * + * Ask johqunn@ as this is too clever for me. + */ + private fun depth(original: Int, input: Int, output: Int): Int = original + output - input } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt index 3ccf94c91f..9b3218e3d0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt @@ -1,8 +1,6 @@ package org.partiql.planner.internal import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.internal.ResolvedVar -import org.partiql.spi.BindingName import org.partiql.spi.BindingPath import org.partiql.spi.connector.ConnectorHandle import org.partiql.spi.connector.ConnectorMetadata @@ -13,51 +11,9 @@ internal class PathResolverObj( session: PartiQLPlanner.Session, ) : PathResolver(catalog, session) { - override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle? { - TODO("Not yet implemented") - } + override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle? = + metadata.getObject(path) - /** - * Lookup a global using a [BindingName] as the catalog name. - * - * @param catalog - * @param originalPath - * @param catalogPath - * @return - */ - private fun getGlobalType( - catalog: BindingName, - originalPath: BindingPath, - catalogPath: BindingPath, - ): ResolvedVar? { - val cat = catalogs.keys.firstOrNull { catalog.matches(it) } ?: return null - return getGlobalType(cat, originalPath, catalogPath) - } - // - // /** - // * TODO optimization, check known globals before calling out to connector again - // * - // * @param catalog - // * @param originalPath - // * @param catalogPath - // * @return - // */ - // private fun getGlobalType( - // catalog: String, - // originalPath: BindingPath, - // catalogPath: BindingPath, - // ): ResolvedVar? { - // return catalogs[catalog]?.let { metadata -> - // metadata.getObject(catalogPath)?.let { obj -> - // obj.entity.getType()?.let { type -> - // val depth = calculateMatched(originalPath, catalogPath, obj.path) - // val (catalogIndex, valueIndex) = getOrAddCatalogValue(catalog, obj.path, type) - // // Return resolution metadata - // ResolvedVar.Global(type, catalogIndex, depth, valueIndex) - // } - // } - // } - // } // // /** // * @return a [Pair] where [Pair.first] is the catalog index and [Pair.second] is the value index within that catalog @@ -99,74 +55,5 @@ internal class PathResolverObj( // } // } // } - // - // /** - // * Attempt to resolve a [BindingPath] in the global + local type environments. - // */ - // fun resolve(path: BindingPath, locals: TypeEnv, scope: Rex.Op.Var.Scope): ResolvedVar? { - // val strategy = when (scope) { - // Rex.Op.Var.Scope.DEFAULT -> locals.strategy - // Rex.Op.Var.Scope.LOCAL -> ResolutionStrategy.LOCAL - // } - // return when (strategy) { - // ResolutionStrategy.LOCAL -> { - // var type: ResolvedVar? = null - // type = type ?: resolveLocalBind(path, locals.schema) - // type = type ?: resolveGlobalBind(path) - // type - // } - // ResolutionStrategy.GLOBAL -> { - // var type: ResolvedVar? = null - // type = type ?: resolveGlobalBind(path) - // type = type ?: resolveLocalBind(path, locals.schema) - // type - // } - // } - // } - // - // /** - // * Logic is as follows: - // * 1. If Current Catalog and Schema are set, create a Path to the object and attempt to grab handle and schema. - // * a. If not found, just try to find the object in the catalog. - // * 2. If Current Catalog is not set: - // * a. Loop through all catalogs and try to find the object. - // * - // * TODO: Replace paths with global variable references if found - // */ - // private fun resolveGlobalBind(path: BindingPath): ResolvedVar? { - // val currentCatalog = session.currentCatalog - // val currentCatalogPath = BindingPath(session.currentDirectory.map { BindingName(it, BindingCase.SENSITIVE) }) - // val absoluteCatalogPath = BindingPath(currentCatalogPath.steps + path.steps) - // val resolvedVar = when (path.steps.size) { - // 0 -> null - // 1 -> getGlobalType(currentCatalog, path, absoluteCatalogPath) - // 2 -> getGlobalType(currentCatalog, path, path) ?: getGlobalType(currentCatalog, path, absoluteCatalogPath) - // else -> { - // val inferredCatalog = path.steps[0] - // val newPath = BindingPath(path.steps.subList(1, path.steps.size)) - // getGlobalType(inferredCatalog, path, newPath) - // ?: getGlobalType(currentCatalog, path, path) - // ?: getGlobalType(currentCatalog, path, absoluteCatalogPath) - // } - // } - // return resolvedVar - // } - // - // - // /** - // * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata - // * - // * 1. Matched = RelativePath - Not Found - // * 2. Not Found = Input CatalogPath - Output CatalogPath - // * 3. Matched = RelativePath - (Input CatalogPath - Output CatalogPath) - // * 4. Matched = RelativePath + Output CatalogPath - Input CatalogPath - // */ - // private fun calculateMatched( - // originalPath: BindingPath, - // inputCatalogPath: BindingPath, - // outputCatalogPath: List, - // ): Int { - // return originalPath.steps.size + outputCatalogPath.size - inputCatalogPath.steps.size - // } } \ No newline at end of file diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolvedVar.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolvedVar.kt deleted file mode 100644 index 9dee5ed92a..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolvedVar.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.spi.BindingName -import org.partiql.types.StaticType - -/** - * Metadata regarding a resolved variable. - * - * @property depth The depth/level of the path match. - */ -internal sealed interface ResolvedVar { - - public val type: StaticType - public val ordinal: Int - public val depth: Int - - /** - * Metadata for a resolved local variable. - * - * @property type Resolved StaticType - * @property ordinal Index offset in [TypeEnvLocal] - * @property resolvedSteps The fully resolved path steps.s - */ - class Local( - override val type: StaticType, - override val ordinal: Int, - val rootType: StaticType, - val resolvedSteps: List, - ) : ResolvedVar { - // the depth are always going to be 1 because this is local variable. - // the global path, however the path length maybe, going to be replaced by a binding name. - override val depth: Int = 1 - } - - /** - * Metadata for a resolved global variable - * - * @property type Resolved StaticType - * @property ordinal The relevant catalog's index offset in the [Env.symbols] list - * @property depth The depth/level of the path match. - * @property position The relevant value's index offset in the [Catalog.values] list - */ - class Global( - override val type: StaticType, - override val ordinal: Int, - override val depth: Int, - val position: Int, - ) : ResolvedVar -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Symbols.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Symbols.kt new file mode 100644 index 0000000000..ceae51d208 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Symbols.kt @@ -0,0 +1,35 @@ +package org.partiql.planner.internal + +import org.partiql.planner.internal.ir.Catalog +import org.partiql.planner.internal.ir.Ref +import org.partiql.planner.internal.ir.builder.CatalogBuilder +import org.partiql.spi.connector.ConnectorFn +import org.partiql.spi.connector.ConnectorObject + +/** + * Symbols is a helper class for maintaining resolved catalog symbols during planning. + */ +internal class Symbols private constructor() { + + private val catalogs = LinkedHashMap() + + fun build(): List { + return emptyList() + } + + fun insert(item: PathItem): Ref { + // no-op + TODO() + } + + fun insert(item: PathItem): Ref { + // no-op + TODO() + } + + companion object { + + @JvmStatic + fun empty() = Symbols() + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/catalog/CatalogResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/catalog/CatalogResolver.kt deleted file mode 100644 index e05d8059e3..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/catalog/CatalogResolver.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.partiql.planner.internal.catalog - -import org.partiql.planner.internal.ir.Catalog -import org.partiql.planner.internal.ir.builder.CatalogBuilder - -internal class CatalogResolver( - private val -) { - - /** - * Maintain all catalog items referenced during query planning. - */ - private val refs = mutableListOf() - - /** - * Builds and returns the current list of all referenced catalog items. - * - * @return - */ - fun catalogs(): List = refs.map { it.build() } - - -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt index 050e1bb841..8ee62c8daf 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt @@ -19,7 +19,7 @@ package org.partiql.planner.internal.transforms import org.partiql.ast.AstNode import org.partiql.ast.Expr import org.partiql.ast.visitor.AstBaseVisitor -import org.partiql.planner.internal.Env +import org.partiql.planner.internal.TypeEnvDb import org.partiql.planner.internal.ir.identifierQualified import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.statementQuery @@ -35,14 +35,14 @@ internal object AstToPlan { // statement.toPlan() @JvmStatic - fun apply(statement: AstStatement, env: Env): PlanStatement = statement.accept(ToPlanStatement, env) + fun apply(statement: AstStatement, env: TypeEnvDb): PlanStatement = statement.accept(ToPlanStatement, env) @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - private object ToPlanStatement : AstBaseVisitor() { + private object ToPlanStatement : AstBaseVisitor() { - override fun defaultReturn(node: AstNode, env: Env) = throw IllegalArgumentException("Unsupported statement") + override fun defaultReturn(node: AstNode, env: TypeEnvDb) = throw IllegalArgumentException("Unsupported statement") - override fun visitStatementQuery(node: AstStatement.Query, env: Env): PlanStatement { + override fun visitStatementQuery(node: AstStatement.Query, env: TypeEnvDb): PlanStatement { val rex = when (val expr = node.expr) { is Expr.SFW -> RelConverter.apply(expr, env) else -> RexConverter.apply(expr, env) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt index 78474f204a..e4921cc6b4 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt @@ -30,7 +30,7 @@ import org.partiql.ast.builder.ast import org.partiql.ast.helpers.toBinder import org.partiql.ast.util.AstRewriter import org.partiql.ast.visitor.AstBaseVisitor -import org.partiql.planner.internal.Env +import org.partiql.planner.internal.TypeEnvDb import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.fnUnresolved @@ -80,7 +80,7 @@ internal object RelConverter { /** * Here we convert an SFW to composed [Rel]s, then apply the appropriate relation-value projection to get a [Rex]. */ - internal fun apply(sfw: Expr.SFW, env: Env): Rex { + internal fun apply(sfw: Expr.SFW, env: TypeEnvDb): Rex { val rel = sfw.accept(ToRel(env), nil) val rex = when (val projection = sfw.select) { // PIVOT ... FROM @@ -120,10 +120,10 @@ internal object RelConverter { /** * Syntax sugar for converting an [Expr] tree to a [Rex] tree. */ - private fun Expr.toRex(env: Env): Rex = RexConverter.apply(this, env) + private fun Expr.toRex(env: TypeEnvDb): Rex = RexConverter.apply(this, env) @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName") - private class ToRel(private val env: Env) : AstBaseVisitor() { + private class ToRel(private val env: TypeEnvDb) : AstBaseVisitor() { override fun defaultReturn(node: AstNode, input: Rel): Rel = throw IllegalArgumentException("unsupported rel $node") diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 062a1a9257..ee3bcd20c7 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -21,7 +21,7 @@ import org.partiql.ast.DatetimeField import org.partiql.ast.Expr import org.partiql.ast.Type import org.partiql.ast.visitor.AstBaseVisitor -import org.partiql.planner.internal.Env +import org.partiql.planner.internal.TypeEnvDb import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.builder.plan @@ -57,16 +57,16 @@ import org.partiql.value.stringValue */ internal object RexConverter { - internal fun apply(expr: Expr, context: Env): Rex = expr.accept(ToRex, context) // expr.toRex() + internal fun apply(expr: Expr, context: TypeEnvDb): Rex = expr.accept(ToRex, context) // expr.toRex() @OptIn(PartiQLValueExperimental::class) @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - private object ToRex : AstBaseVisitor() { + private object ToRex : AstBaseVisitor() { - override fun defaultReturn(node: AstNode, context: Env): Rex = + override fun defaultReturn(node: AstNode, context: TypeEnvDb): Rex = throw IllegalArgumentException("unsupported rex $node") - override fun visitExprLit(node: Expr.Lit, context: Env): Rex { + override fun visitExprLit(node: Expr.Lit, context: TypeEnvDb): Rex { val type = when (node.value.isNull) { true -> node.value.type.toStaticType() else -> node.value.type.toNonNullStaticType() @@ -75,7 +75,7 @@ internal object RexConverter { return rex(type, op) } - override fun visitExprIon(node: Expr.Ion, ctx: Env): Rex { + override fun visitExprIon(node: Expr.Ion, ctx: TypeEnvDb): Rex { val value = PartiQLValueIonReaderBuilder .standard().build(node.value).read() @@ -103,7 +103,7 @@ internal object RexConverter { * @param ctx * @return */ - private fun visitExprCoerce(node: Expr, ctx: Env, coercion: Rex.Op.Subquery.Coercion = Rex.Op.Subquery.Coercion.SCALAR): Rex { + private fun visitExprCoerce(node: Expr, ctx: TypeEnvDb, coercion: Rex.Op.Subquery.Coercion = Rex.Op.Subquery.Coercion.SCALAR): Rex { val rex = super.visitExpr(node, ctx) return when (rex.op is Rex.Op.Select) { true -> rex(StaticType.ANY, rexOpSubquery(rex.op, coercion)) @@ -111,7 +111,7 @@ internal object RexConverter { } } - override fun visitExprVar(node: Expr.Var, context: Env): Rex { + override fun visitExprVar(node: Expr.Var, context: TypeEnvDb): Rex { val type = (StaticType.ANY) val identifier = AstToPlan.convert(node.identifier) val scope = when (node.scope) { @@ -122,7 +122,7 @@ internal object RexConverter { return rex(type, op) } - override fun visitExprUnary(node: Expr.Unary, context: Env): Rex { + override fun visitExprUnary(node: Expr.Unary, context: TypeEnvDb): Rex { val type = (StaticType.ANY) // Args val arg = visitExprCoerce(node.expr, context) @@ -134,7 +134,7 @@ internal object RexConverter { return rex(type, op) } - override fun visitExprBinary(node: Expr.Binary, context: Env): Rex { + override fun visitExprBinary(node: Expr.Binary, context: TypeEnvDb): Rex { val type = (StaticType.ANY) // Args val lhs = visitExprCoerce(node.lhs, context) @@ -173,7 +173,7 @@ internal object RexConverter { return identifierQualified(newRoot, firstSteps + followingSteps) } - override fun visitExprPath(node: Expr.Path, context: Env): Rex { + override fun visitExprPath(node: Expr.Path, context: TypeEnvDb): Rex { // Args val root = visitExprCoerce(node.root, context) @@ -237,7 +237,7 @@ internal object RexConverter { private fun rexString(str: String) = rex(StaticType.STRING, rexOpLit(stringValue(str))) - override fun visitExprCall(node: Expr.Call, context: Env): Rex { + override fun visitExprCall(node: Expr.Call, context: TypeEnvDb): Rex { val type = (StaticType.ANY) // Fn val id = AstToPlan.convert(node.function) @@ -252,14 +252,14 @@ internal object RexConverter { return rex(type, op) } - private fun visitExprCallTupleUnion(node: Expr.Call, context: Env): Rex { + private fun visitExprCallTupleUnion(node: Expr.Call, context: TypeEnvDb): Rex { val type = (StaticType.STRUCT) val args = node.args.map { visitExprCoerce(it, context) }.toMutableList() val op = rexOpTupleUnion(args) return rex(type, op) } - override fun visitExprCase(node: Expr.Case, context: Env) = plan { + override fun visitExprCase(node: Expr.Case, context: TypeEnvDb) = plan { val type = (StaticType.ANY) val rex = when (node.expr) { null -> null @@ -291,7 +291,7 @@ internal object RexConverter { rex(type, op) } - override fun visitExprCollection(node: Expr.Collection, context: Env): Rex { + override fun visitExprCollection(node: Expr.Collection, context: TypeEnvDb): Rex { val type = when (node.type) { Expr.Collection.Type.BAG -> StaticType.BAG Expr.Collection.Type.ARRAY -> StaticType.LIST @@ -304,7 +304,7 @@ internal object RexConverter { return rex(type, op) } - override fun visitExprStruct(node: Expr.Struct, context: Env): Rex { + override fun visitExprStruct(node: Expr.Struct, context: TypeEnvDb): Rex { val type = (StaticType.STRUCT) val fields = node.fields.map { val k = visitExprCoerce(it.name, context) @@ -320,7 +320,7 @@ internal object RexConverter { /** * NOT? LIKE ( ESCAPE )? */ - override fun visitExprLike(node: Expr.Like, ctx: Env): Rex { + override fun visitExprLike(node: Expr.Like, ctx: TypeEnvDb): Rex { val type = StaticType.BOOL // Args val arg0 = visitExprCoerce(node.value, ctx) @@ -341,7 +341,7 @@ internal object RexConverter { /** * NOT? BETWEEN AND */ - override fun visitExprBetween(node: Expr.Between, ctx: Env): Rex = plan { + override fun visitExprBetween(node: Expr.Between, ctx: TypeEnvDb): Rex = plan { val type = StaticType.BOOL // Args val arg0 = visitExprCoerce(node.value, ctx) @@ -368,7 +368,7 @@ internal object RexConverter { * Otherwise, T in C is unknown. * */ - override fun visitExprInCollection(node: Expr.InCollection, ctx: Env): Rex { + override fun visitExprInCollection(node: Expr.InCollection, ctx: TypeEnvDb): Rex { val type = StaticType.BOOL // Args val arg0 = visitExprCoerce(node.lhs, ctx) @@ -386,7 +386,7 @@ internal object RexConverter { /** * IS ? */ - override fun visitExprIsType(node: Expr.IsType, ctx: Env): Rex { + override fun visitExprIsType(node: Expr.IsType, ctx: TypeEnvDb): Rex { val type = StaticType.BOOL // arg val arg0 = visitExprCoerce(node.value, ctx) @@ -444,7 +444,7 @@ internal object RexConverter { // ... // WHEN exprn is NOT NULL THEN exprn // ELSE NULL END - override fun visitExprCoalesce(node: Expr.Coalesce, ctx: Env): Rex = plan { + override fun visitExprCoalesce(node: Expr.Coalesce, ctx: TypeEnvDb): Rex = plan { val type = StaticType.ANY val createBranch: (Rex) -> Rex.Op.Case.Branch = { expr: Rex -> val updatedCondition = rex(type, negate(call("is_null", expr))) @@ -464,7 +464,7 @@ internal object RexConverter { // CASE // WHEN expr1 = expr2 THEN NULL // ELSE expr1 END - override fun visitExprNullIf(node: Expr.NullIf, ctx: Env): Rex = plan { + override fun visitExprNullIf(node: Expr.NullIf, ctx: TypeEnvDb): Rex = plan { val type = StaticType.ANY val expr1 = visitExpr(node.value, ctx) val expr2 = visitExpr(node.nullifier, ctx) @@ -481,7 +481,7 @@ internal object RexConverter { /** * SUBSTRING( (FROM (FOR )?)? ) */ - override fun visitExprSubstring(node: Expr.Substring, ctx: Env): Rex { + override fun visitExprSubstring(node: Expr.Substring, ctx: TypeEnvDb): Rex { val type = StaticType.ANY // Args val arg0 = visitExprCoerce(node.value, ctx) @@ -498,7 +498,7 @@ internal object RexConverter { /** * POSITION( IN ) */ - override fun visitExprPosition(node: Expr.Position, ctx: Env): Rex { + override fun visitExprPosition(node: Expr.Position, ctx: TypeEnvDb): Rex { val type = StaticType.ANY // Args val arg0 = visitExprCoerce(node.lhs, ctx) @@ -511,7 +511,7 @@ internal object RexConverter { /** * TRIM([LEADING|TRAILING|BOTH]? ( FROM)? ) */ - override fun visitExprTrim(node: Expr.Trim, ctx: Env): Rex { + override fun visitExprTrim(node: Expr.Trim, ctx: TypeEnvDb): Rex { val type = StaticType.TEXT // Args val arg0 = visitExprCoerce(node.value, ctx) @@ -535,16 +535,16 @@ internal object RexConverter { return rex(type, call) } - override fun visitExprOverlay(node: Expr.Overlay, ctx: Env): Rex { + override fun visitExprOverlay(node: Expr.Overlay, ctx: TypeEnvDb): Rex { TODO("SQL Special Form OVERLAY") } - override fun visitExprExtract(node: Expr.Extract, ctx: Env): Rex { + override fun visitExprExtract(node: Expr.Extract, ctx: TypeEnvDb): Rex { TODO("SQL Special Form EXTRACT") } // TODO: Ignoring type parameter now - override fun visitExprCast(node: Expr.Cast, ctx: Env): Rex { + override fun visitExprCast(node: Expr.Cast, ctx: TypeEnvDb): Rex { val type = node.asType val arg0 = visitExprCoerce(node.value, ctx) return when (type) { @@ -586,15 +586,15 @@ internal object RexConverter { } } - override fun visitExprCanCast(node: Expr.CanCast, ctx: Env): Rex { + override fun visitExprCanCast(node: Expr.CanCast, ctx: TypeEnvDb): Rex { TODO("PartiQL Special Form CAN_CAST") } - override fun visitExprCanLosslessCast(node: Expr.CanLosslessCast, ctx: Env): Rex { + override fun visitExprCanLosslessCast(node: Expr.CanLosslessCast, ctx: TypeEnvDb): Rex { TODO("PartiQL Special Form CAN_LOSSLESS_CAST") } - override fun visitExprDateAdd(node: Expr.DateAdd, ctx: Env): Rex { + override fun visitExprDateAdd(node: Expr.DateAdd, ctx: TypeEnvDb): Rex { val type = StaticType.TIMESTAMP // Args val arg0 = visitExprCoerce(node.lhs, ctx) @@ -608,7 +608,7 @@ internal object RexConverter { return rex(type, call) } - override fun visitExprDateDiff(node: Expr.DateDiff, ctx: Env): Rex { + override fun visitExprDateDiff(node: Expr.DateDiff, ctx: TypeEnvDb): Rex { val type = StaticType.TIMESTAMP // Args val arg0 = visitExprCoerce(node.lhs, ctx) @@ -622,14 +622,14 @@ internal object RexConverter { return rex(type, call) } - override fun visitExprSessionAttribute(node: Expr.SessionAttribute, ctx: Env): Rex { + override fun visitExprSessionAttribute(node: Expr.SessionAttribute, ctx: TypeEnvDb): Rex { val type = StaticType.ANY val fn = node.attribute.name.lowercase() val call = call(fn) return rex(type, call) } - override fun visitExprSFW(node: Expr.SFW, context: Env): Rex = RelConverter.apply(node, context) + override fun visitExprSFW(node: Expr.SFW, context: TypeEnvDb): Rex = RelConverter.apply(node, context) // Helpers diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 2fdee03fd1..301b3e0514 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -21,7 +21,6 @@ import org.partiql.errors.ProblemCallback import org.partiql.errors.UNKNOWN_PROBLEM_LOCATION import org.partiql.planner.PlanningProblemDetails import org.partiql.planner.internal.Env -import org.partiql.planner.internal.ResolutionStrategy import org.partiql.planner.internal.ir.Fn import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.PlanNode @@ -119,7 +118,7 @@ internal class PlanTyper( } // root TypeEnv has no bindings val typeEnv = TypeEnv(schema = emptyList()) - val root = statement.root.type(typeEnv, ResolutionStrategy.GLOBAL) + val root = statement.root.type(typeEnv, Scope.GLOBAL) return statementQuery(root) } @@ -131,7 +130,7 @@ internal class PlanTyper( */ private inner class RelTyper( private val outer: TypeEnv, - private val strategy: ResolutionStrategy, + private val strategy: Scope, ) : PlanRewriter() { override fun visitRel(node: Rel, ctx: Rel.Type?) = visitRelOp(node.op, node.type) as Rel @@ -141,7 +140,7 @@ internal class PlanTyper( */ override fun visitRelOpScan(node: Rel.Op.Scan, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) + val rex = node.rex.type(outer, Scope.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val type = ctx!!.copyWithSchema(listOf(valueT)) @@ -160,7 +159,7 @@ internal class PlanTyper( */ override fun visitRelOpScanIndexed(node: Rel.Op.ScanIndexed, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) + val rex = node.rex.type(outer, Scope.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val indexT = StaticType.INT8 @@ -175,7 +174,7 @@ internal class PlanTyper( */ override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(outer, ResolutionStrategy.GLOBAL) + val rex = node.rex.type(outer, Scope.GLOBAL) // only UNPIVOT a struct if (rex.type !is StructType) { @@ -251,7 +250,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type limit expression using outer scope with global resolution - val limit = node.limit.type(outer, ResolutionStrategy.GLOBAL) + val limit = node.limit.type(outer, Scope.GLOBAL) // check types assertAsInt(limit.type) // compute output schema @@ -265,7 +264,7 @@ internal class PlanTyper( // compute input schema val input = visitRel(node.input, ctx) // type offset expression using outer scope with global resolution - val offset = node.offset.type(outer, ResolutionStrategy.GLOBAL) + val offset = node.offset.type(outer, Scope.GLOBAL) // check types assertAsInt(offset.type) // compute output schema @@ -386,7 +385,7 @@ internal class PlanTyper( val input = visitRel(node.input, ctx) // type the calls and groups - val typer = RexTyper(TypeEnv(input.type.schema), ResolutionStrategy.LOCAL) + val typer = RexTyper(TypeEnv(input.type.schema), Scope.LOCAL) // typing of aggregate calls is slightly more complicated because they are not expressions. val calls = node.calls.mapIndexed { i, call -> @@ -425,7 +424,7 @@ internal class PlanTyper( @OptIn(PartiQLValueExperimental::class) private inner class RexTyper( private val locals: TypeEnv, - private val strategy: ResolutionStrategy, + private val strategy: Scope, ) : PlanRewriter() { override fun visitRex(node: Rex, ctx: StaticType?): Rex = visitRexOp(node.op, node.type) as Rex @@ -436,18 +435,21 @@ internal class PlanTyper( } override fun visitRexOpVarResolved(node: Rex.Op.Var.Resolved, ctx: StaticType?): Rex { - assert(node.ref < locals.size) { "Invalid resolved variable (var ${node.ref}) for $locals" } - val type = locals[node.ref].type + assert(node.ref < locals.schema.size) { "Invalid resolved variable (var ${node.ref}) for $locals" } + val type = locals.schema[node.ref].type return rex(type, node) } override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: StaticType?): Rex { val path = node.identifier.toBindingPath() - val strategy = when (node.scope) { + val scope = when (node.scope) { Rex.Op.Var.Scope.DEFAULT -> strategy - Rex.Op.Var.Scope.LOCAL -> ResolutionStrategy.LOCAL + Rex.Op.Var.Scope.LOCAL -> Scope.LOCAL + } + val resolvedVar = when (scope) { + Scope.LOCAL -> locals.resolve(path) ?: env.resolveObj(path) + Scope.GLOBAL -> env.resolveObj(path) ?: locals.resolve(path) } - val resolvedVar = env.resolve(path, locals, strategy) if (resolvedVar == null) { handleUndefinedVariable(path.steps.last()) return rex(ANY, rexOpErr("Undefined variable ${node.identifier}")) @@ -874,7 +876,7 @@ internal class PlanTyper( override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: StaticType?): Rex { val rel = node.rel.type(locals) val typeEnv = TypeEnv(rel.type.schema) - val typer = RexTyper(typeEnv, ResolutionStrategy.LOCAL) + val typer = RexTyper(typeEnv, Scope.LOCAL) val key = typer.visitRex(node.key, null) val value = typer.visitRex(node.value, null) val type = StructType( @@ -1217,10 +1219,10 @@ internal class PlanTyper( // HELPERS - private fun Rel.type(locals: TypeEnv, strategy: ResolutionStrategy = ResolutionStrategy.LOCAL): Rel = + private fun Rel.type(locals: TypeEnv, strategy: Scope = Scope.LOCAL): Rel = RelTyper(locals, strategy).visitRel(this, null) - private fun Rex.type(locals: TypeEnv, strategy: ResolutionStrategy = ResolutionStrategy.LOCAL) = + private fun Rex.type(locals: TypeEnv, strategy: Scope = Scope.LOCAL) = RexTyper(locals, strategy).visitRex(this, this.type) private fun rexErr(message: String) = rex(MISSING, rexOpErr(message)) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolutionStrategy.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt similarity index 82% rename from partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolutionStrategy.kt rename to partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt index b5e5921007..42eda6d22d 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ResolutionStrategy.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt @@ -1,4 +1,4 @@ -package org.partiql.planner.internal +package org.partiql.planner.internal.typer /** * Variable resolution strategies — https://partiql.org/assets/PartiQL-Specification.pdf#page=35 @@ -8,7 +8,7 @@ package org.partiql.planner.internal * | LOCAL | local-first lookup | Rules 1, 2 | * | GLOBAL | global-first lookup | Rule 3 | */ -internal enum class ResolutionStrategy { +internal enum class Scope { LOCAL, GLOBAL, } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt index 577cbc75d2..961cd944cc 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt @@ -114,24 +114,6 @@ internal class TypeEnv(public val schema: List) { return c } - /** - * Converts a list of [BindingName] to a path expression. - * - * 1) Case SENSITIVE identifiers become string literal key lookups. - * 2) Case INSENSITIVE identifiers become symbol lookups. - * - * @param steps - * @return - */ - @OptIn(PartiQLValueExperimental::class) - private fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> - val op = when (step.case) { - BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(StaticType.STRING, rexOpLit(stringValue(step.name)))) - BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) - } - rex(StaticType.ANY, op) - } - /** * Searches for the [BindingName] within the given [StructType]. * @@ -152,4 +134,27 @@ internal class TypeEnv(public val schema: List) { val closed = constraints.contains(TupleConstraint.Open(false)) return if (closed) false else null } + + companion object { + + /** + * Converts a list of [BindingName] to a path expression. + * + * 1) Case SENSITIVE identifiers become string literal key lookups. + * 2) Case INSENSITIVE identifiers become symbol lookups. + * + * @param steps + * @return + */ + @JvmStatic + @OptIn(PartiQLValueExperimental::class) + internal fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> + val op = when (step.case) { + BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(StaticType.STRING, rexOpLit(stringValue(step.name)))) + BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + } + rex(StaticType.ANY, op) + } + + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeLattice.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeLattice.kt deleted file mode 100644 index 767dd91922..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeLattice.kt +++ /dev/null @@ -1,375 +0,0 @@ -package org.partiql.planner.internal.typer - -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType -import org.partiql.value.PartiQLValueType.ANY -import org.partiql.value.PartiQLValueType.BAG -import org.partiql.value.PartiQLValueType.BINARY -import org.partiql.value.PartiQLValueType.BLOB -import org.partiql.value.PartiQLValueType.BOOL -import org.partiql.value.PartiQLValueType.BYTE -import org.partiql.value.PartiQLValueType.CHAR -import org.partiql.value.PartiQLValueType.CLOB -import org.partiql.value.PartiQLValueType.DATE -import org.partiql.value.PartiQLValueType.DECIMAL -import org.partiql.value.PartiQLValueType.DECIMAL_ARBITRARY -import org.partiql.value.PartiQLValueType.FLOAT32 -import org.partiql.value.PartiQLValueType.FLOAT64 -import org.partiql.value.PartiQLValueType.INT -import org.partiql.value.PartiQLValueType.INT16 -import org.partiql.value.PartiQLValueType.INT32 -import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.INT8 -import org.partiql.value.PartiQLValueType.INTERVAL -import org.partiql.value.PartiQLValueType.LIST -import org.partiql.value.PartiQLValueType.MISSING -import org.partiql.value.PartiQLValueType.NULL -import org.partiql.value.PartiQLValueType.SEXP -import org.partiql.value.PartiQLValueType.STRING -import org.partiql.value.PartiQLValueType.STRUCT -import org.partiql.value.PartiQLValueType.SYMBOL -import org.partiql.value.PartiQLValueType.TIME -import org.partiql.value.PartiQLValueType.TIMESTAMP - -/** - * Going with a matrix here (using enum ordinals) as it's simple and avoids walking. - */ -internal typealias TypeGraph = Array> - -/** - * Each edge represents a type relationship - */ -internal data class TypeRelationship(val cast: CastType) - -/** - * An COERCION will be inserted by the compiler during function resolution, an EXPLICIT CAST will never be inserted. - * - * COERCION: Lossless CAST(V AS T) -> T - * EXPLICIT: Lossy CAST(V AS T) -> T - * UNSAFE: CAST(V AS T) -> T|MISSING - */ -internal enum class CastType { COERCION, EXPLICIT, UNSAFE } - -/** - * A place to model type relationships (for now this is to answer CAST inquiries). - * - * Is this indeed a lattice? It's a rather smart sounding word. - */ -@OptIn(PartiQLValueExperimental::class) -internal class TypeLattice private constructor( - public val types: Array, - public val graph: TypeGraph, -) { - - public fun canCoerce(operand: PartiQLValueType, target: PartiQLValueType): Boolean { - return graph[operand][target]?.cast == CastType.COERCION - } - - internal val all = PartiQLValueType.values() - - internal val nullable = listOf( - NULL, // null.null - MISSING, // missing - ) - - internal val integer = listOf( - INT8, - INT16, - INT32, - INT64, - INT, - ) - - internal val numeric = listOf( - INT8, - INT16, - INT32, - INT64, - INT, - DECIMAL_ARBITRARY, - FLOAT32, - FLOAT64, - ) - - internal val text = listOf( - STRING, - SYMBOL, - CLOB, - ) - - internal val collections = listOf( - BAG, - LIST, - SEXP, - ) - - internal val datetime = listOf( - DATE, - TIME, - TIMESTAMP, - ) - - /** - * Dump the graph as an Asciidoc table. - */ - override fun toString(): String = buildString { - appendLine("|===") - appendLine() - // Header - append("| | ").appendLine(types.joinToString("| ")) - // Body - for (t1 in types) { - append("| $t1 ") - for (t2 in types) { - val symbol = when (val r = graph[t1][t2]) { - null -> "X" - else -> when (r.cast) { - CastType.COERCION -> "⬤" - CastType.EXPLICIT -> "◯" - CastType.UNSAFE -> "△" - } - } - append("| $symbol ") - } - appendLine() - } - appendLine() - appendLine("|===") - } - - private operator fun Array.get(t: PartiQLValueType): T = get(t.ordinal) - - companion object { - - private val N = PartiQLValueType.values().size - - private fun relationships(vararg relationships: Pair): Array { - val arr = arrayOfNulls(N) - for (type in relationships) { - arr[type.first] = type.second - } - return arr - } - - private fun coercion(): TypeRelationship = TypeRelationship(CastType.COERCION) - - private fun explicit(): TypeRelationship = TypeRelationship(CastType.EXPLICIT) - - private fun unsafe(): TypeRelationship = TypeRelationship(CastType.UNSAFE) - - private operator fun Array.set(t: PartiQLValueType, value: T): Unit = this.set(t.ordinal, value) - - /** - * Build the PartiQL type lattice. - * - * TODO this is incomplete. - */ - public fun partiql(): TypeLattice { - val types = PartiQLValueType.values() - val graph = arrayOfNulls>(N) - for (type in types) { - // initialize all with empty relationships - graph[type] = arrayOfNulls(N) - } - graph[ANY] = relationships( - ANY to coercion() - ) - graph[NULL] = relationships( - NULL to coercion() - ) - graph[MISSING] = relationships( - MISSING to coercion() - ) - graph[BOOL] = relationships( - BOOL to coercion(), - INT8 to explicit(), - INT16 to explicit(), - INT32 to explicit(), - INT64 to explicit(), - INT to explicit(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to explicit(), - FLOAT32 to explicit(), - FLOAT64 to explicit(), - CHAR to explicit(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[INT8] = relationships( - BOOL to explicit(), - INT8 to coercion(), - INT16 to coercion(), - INT32 to coercion(), - INT64 to coercion(), - INT to coercion(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[INT16] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to coercion(), - INT32 to coercion(), - INT64 to coercion(), - INT to coercion(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[INT32] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to coercion(), - INT64 to coercion(), - INT to coercion(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[INT64] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to unsafe(), - INT64 to coercion(), - INT to coercion(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[INT] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to unsafe(), - INT64 to unsafe(), - INT to coercion(), - DECIMAL to explicit(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[DECIMAL] = relationships( - BOOL to explicit(), - INT8 to explicit(), - INT16 to explicit(), - INT32 to explicit(), - INT64 to explicit(), - INT to explicit(), - DECIMAL to coercion(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to explicit(), - FLOAT64 to explicit(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[DECIMAL_ARBITRARY] = relationships( - BOOL to explicit(), - INT8 to explicit(), - INT16 to explicit(), - INT32 to explicit(), - INT64 to explicit(), - INT to explicit(), - DECIMAL to coercion(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to explicit(), - FLOAT64 to explicit(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[FLOAT32] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to unsafe(), - INT64 to unsafe(), - INT to unsafe(), - DECIMAL to unsafe(), - DECIMAL_ARBITRARY to coercion(), - FLOAT32 to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[FLOAT64] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to unsafe(), - INT64 to unsafe(), - INT to unsafe(), - DECIMAL to unsafe(), - DECIMAL_ARBITRARY to coercion(), - FLOAT64 to coercion(), - STRING to explicit(), - SYMBOL to explicit(), - ) - graph[CHAR] = relationships( - BOOL to explicit(), - CHAR to coercion(), - STRING to coercion(), - SYMBOL to coercion(), - ) - graph[STRING] = relationships( - BOOL to explicit(), - INT8 to unsafe(), - INT16 to unsafe(), - INT32 to unsafe(), - INT64 to unsafe(), - INT to unsafe(), - STRING to coercion(), - SYMBOL to explicit(), - CLOB to coercion(), - ) - graph[SYMBOL] = relationships( - BOOL to explicit(), - STRING to coercion(), - SYMBOL to coercion(), - CLOB to coercion(), - ) - graph[CLOB] = relationships( - CLOB to coercion(), - ) - graph[BINARY] = arrayOfNulls(N) - graph[BYTE] = arrayOfNulls(N) - graph[BLOB] = arrayOfNulls(N) - graph[DATE] = arrayOfNulls(N) - graph[TIME] = arrayOfNulls(N) - graph[TIMESTAMP] = arrayOfNulls(N) - graph[INTERVAL] = arrayOfNulls(N) - graph[BAG] = relationships( - BAG to coercion(), - ) - graph[LIST] = relationships( - BAG to coercion(), - SEXP to coercion(), - LIST to coercion(), - ) - graph[SEXP] = relationships( - BAG to coercion(), - SEXP to coercion(), - LIST to coercion(), - ) - graph[STRUCT] = relationships( - STRUCT to coercion(), - ) - return TypeLattice(types, graph.requireNoNulls()) - } - } -} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt index 9e0cb2d249..4db5071491 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/EnvTest.kt @@ -5,11 +5,13 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.partiql.planner.PartiQLPlanner import org.partiql.planner.internal.ir.Catalog +import org.partiql.planner.internal.typer.Scope +import org.partiql.planner.internal.typer.TypeEnv + <<<<<<< HEAD import org.partiql.planner.internal.ir.Rex ======= >>>>>>> partiql-spi -import org.partiql.planner.internal.typer.TypeEnv import org.partiql.plugins.local.LocalConnector import org.partiql.spi.BindingCase import org.partiql.spi.BindingName @@ -36,11 +38,11 @@ class EnvTest { ) } - private lateinit var env: Env + private lateinit var env: TypeEnvDb @BeforeEach fun init() { - env = Env( + env = TypeEnvDb( PartiQLPlanner.Session( queryId = Random().nextInt().toString(), userId = "test-user", @@ -61,7 +63,7 @@ class EnvTest { assertEquals(1, env.symbols.size) assert(env.symbols.contains(GLOBAL_OS)) ======= - assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) + assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, Scope.GLOBAL)) assertEquals(1, env.catalogs.size) assert(env.catalogs.contains(GLOBAL_OS)) >>>>>>> partiql-spi @@ -75,7 +77,7 @@ class EnvTest { assertEquals(1, env.symbols.size) assert(env.symbols.contains(GLOBAL_OS)) ======= - assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) + assertNotNull(env.resolve(path, EMPTY_TYPE_ENV, Scope.GLOBAL)) assertEquals(1, env.catalogs.size) assert(env.catalogs.contains(GLOBAL_OS)) >>>>>>> partiql-spi @@ -88,7 +90,7 @@ class EnvTest { assertNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) assert(env.symbols.isEmpty()) ======= - assertNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) + assertNull(env.resolve(path, EMPTY_TYPE_ENV, Scope.GLOBAL)) assert(env.catalogs.isEmpty()) >>>>>>> partiql-spi } @@ -100,7 +102,7 @@ class EnvTest { assertNull(env.resolve(path, EMPTY_TYPE_ENV, Rex.Op.Var.Scope.DEFAULT)) assert(env.symbols.isEmpty()) ======= - assertNull(env.resolve(path, EMPTY_TYPE_ENV, ResolutionStrategy.GLOBAL)) + assertNull(env.resolve(path, EMPTY_TYPE_ENV, Scope.GLOBAL)) assert(env.catalogs.isEmpty()) >>>>>>> partiql-spi } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt index 3645e13fa3..ca8a62b3ef 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt @@ -2,7 +2,7 @@ package org.partiql.planner.internal.typer import org.junit.jupiter.api.Test import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.internal.Env +import org.partiql.planner.internal.TypeEnvDb import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.catalogSymbolRef @@ -172,7 +172,7 @@ class PlanTyperTest { private fun getTyper(): PlanTyperWrapper { val collector = ProblemCollector() - val env = Env( + val env = TypeEnvDb( PartiQLPlanner.Session( queryId = Random().nextInt().toString(), userId = "test-user",