diff --git a/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt b/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt index b089dfe90a..d2b496c106 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt @@ -24,12 +24,14 @@ import org.partiql.cli.pipeline.Pipeline import org.partiql.cli.shell.Shell import org.partiql.eval.PartiQLEngine import org.partiql.eval.PartiQLResult -import org.partiql.plugins.memory.MemoryCatalog +import org.partiql.planner.catalog.Name import org.partiql.plugins.memory.MemoryConnector +import org.partiql.plugins.memory.MemoryTable import org.partiql.spi.connector.Connector -import org.partiql.types.StaticType +import org.partiql.types.PType import org.partiql.value.PartiQLValueExperimental import org.partiql.value.io.PartiQLValueTextWriter +import org.partiql.value.ion.IonDatum import picocli.CommandLine import java.io.File import java.io.InputStream @@ -223,16 +225,18 @@ internal class MainCommand : Runnable { } else { ionNull() } - val catalog = MemoryCatalog.builder() + val connector = MemoryConnector.builder() .name("default") .define( - name = "stdin", - type = StaticType.ANY, - value = value, + MemoryTable.of( + name = Name.of("stdin"), + schema = PType.dynamic(), + datum = IonDatum.of(value.asAnyElement()) + ) ) .build() return mapOf( - "default" to MemoryConnector(catalog) + "default" to connector ) } diff --git a/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/Pipeline.kt b/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/Pipeline.kt index 24407aa29f..1cf4c2f6c5 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/Pipeline.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/pipeline/Pipeline.kt @@ -10,7 +10,6 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.PartiQLPlan import org.partiql.planner.PartiQLPlanner import org.partiql.spi.connector.Connector -import org.partiql.spi.connector.ConnectorSession import java.time.Instant import org.partiql.planner.catalog.Session as PlannerSession @@ -34,15 +33,13 @@ internal class Pipeline private constructor( @JvmField val mode: PartiQLEngine.Mode, ) { - val connector = object : ConnectorSession { - override fun getQueryId(): String = queryId - override fun getUserId(): String = userId - } + private val catalogs = connectors.values.map { it.getCatalog() } fun planner() = PlannerSession.builder() .identity(userId) + .namespace(currentDirectory) .catalog(currentCatalog) - .catalogs(*connectors.map { it.key to it.value.getMetadata(connector) }.toTypedArray()) + .catalogs(*catalogs.toTypedArray()) .build() fun engine() = PartiQLEngine.Session( diff --git a/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt b/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt index 242c0265d3..c79db45fcd 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt @@ -31,8 +31,6 @@ import org.jline.utils.InfoCmp import org.joda.time.Duration import org.partiql.cli.pipeline.Pipeline import org.partiql.eval.PartiQLResult -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName import org.partiql.value.PartiQLValueExperimental import org.partiql.value.io.PartiQLValueTextWriter import java.io.Closeable @@ -250,16 +248,7 @@ internal class Shell( out.error("No connector for catalog ${session.currentCatalog}.") continue } - // Create a path from the arg - val arg1 = args.getOrNull(1) - val path = if (arg1 == null) { - emptyList() - } else { - arg1.split(".").map { BindingName(it, BindingCase.INSENSITIVE) } - } - // Query connector metadata - TODO("Connectors do not support listing metadata") - out.println() + out.error("Connectors do not support listing metadata") } "session" -> { // Print session information diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Symbols.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Symbols.kt index 8175e74064..815ebedeec 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Symbols.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Symbols.kt @@ -6,8 +6,8 @@ import org.partiql.eval.internal.operator.rex.ExprVarGlobal import org.partiql.plan.Catalog import org.partiql.plan.PartiQLPlan import org.partiql.plan.Ref +import org.partiql.planner.catalog.Name import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorPath import org.partiql.spi.fn.Agg import org.partiql.spi.fn.Fn import org.partiql.spi.fn.SqlFnProvider @@ -27,8 +27,8 @@ internal class Symbols private constructor(private val catalogs: Array) { ) { // TEMPORARY FOR DEPENDENCY REASONS - fun getFn(path: ConnectorPath, specific: String): Fn? = SqlFnProvider.getFn(specific) - fun getAgg(path: ConnectorPath, specific: String): Agg? = SqlFnProvider.getAgg(specific) + fun getFn(name: Name, specific: String): Fn? = SqlFnProvider.getFn(specific) + fun getAgg(name: Name, specific: String): Agg? = SqlFnProvider.getAgg(specific) override fun toString(): String = name } @@ -39,8 +39,8 @@ internal class Symbols private constructor(private val catalogs: Array) { if (item == null || item !is Catalog.Item.Value) { error("Invalid reference $ref; missing value entry for catalog `$catalog`.") } - val path = ConnectorPath(item.path) - return ExprVarGlobal(path, catalog.bindings) + val name = Name.of(item.path) + return ExprVarGlobal(name, catalog.bindings) } fun getFn(ref: Ref): Fn { @@ -50,8 +50,8 @@ internal class Symbols private constructor(private val catalogs: Array) { error("Invalid reference $ref; missing function entry for catalog `$catalog`.") } // Lookup in connector - val path = ConnectorPath(item.path) - return catalog.getFn(path, item.specific) + val name = Name.of(item.path) + return catalog.getFn(name, item.specific) ?: error("Catalog `$catalog` has no entry for function $item") } @@ -62,8 +62,8 @@ internal class Symbols private constructor(private val catalogs: Array) { error("Invalid reference $ref; missing aggregation entry for catalog `$catalog`.") } // Lookup in connector - val path = ConnectorPath(item.path) - return catalog.getAgg(path, item.specific) + val name = Name.of(item.path) + return catalog.getAgg(name, item.specific) ?: error("Catalog `$catalog` has no entry for aggregation function $item") } diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprVarGlobal.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprVarGlobal.kt index 6edd15cec2..4ab4a7bc4f 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprVarGlobal.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprVarGlobal.kt @@ -3,16 +3,16 @@ package org.partiql.eval.internal.operator.rex import org.partiql.eval.internal.Environment import org.partiql.eval.internal.operator.Operator import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Name import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorPath -import org.partiql.value.PartiQLValueExperimental -@OptIn(PartiQLValueExperimental::class) internal class ExprVarGlobal( - private val path: ConnectorPath, + private val name: Name, private val bindings: ConnectorBindings, ) : Operator.Expr { // TODO: Potentially make ConnectorBindings return PQLValue - override fun eval(env: Environment): Datum = Datum.of(bindings.getValue(path)) + override fun eval(env: Environment): Datum { + return bindings.getBinding(name)!!.getDatum() + } } diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt index 957b38d840..6069fb9c0d 100644 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -1,6 +1,7 @@ package org.partiql.eval.internal import com.amazon.ionelement.api.createIonElementLoader +import com.amazon.ionelement.api.loadSingleElement import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.parallel.Execution @@ -13,10 +14,11 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.PartiQLPlan import org.partiql.plan.debug.PlanPrinter import org.partiql.planner.builder.PartiQLPlannerBuilder +import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Session -import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryConnector -import org.partiql.spi.connector.ConnectorSession +import org.partiql.plugins.memory.MemoryTable +import org.partiql.types.PType import org.partiql.types.StaticType import org.partiql.value.CollectionValue import org.partiql.value.PartiQLValue @@ -28,6 +30,7 @@ import org.partiql.value.int32Value import org.partiql.value.int64Value import org.partiql.value.intValue import org.partiql.value.io.PartiQLValueIonWriterBuilder +import org.partiql.value.ion.IonDatum import org.partiql.value.listValue import org.partiql.value.missingValue import org.partiql.value.nullValue @@ -1258,19 +1261,22 @@ class PartiQLEngineDefaultTest { internal fun assert() { val statement = parser.parse(input).root - val catalogBuilder = MemoryCatalog.builder().name("memory") - globals.forEach { global -> - catalogBuilder.define(global.name, global.type, loader.loadSingleElement(global.value)) - } - val catalog = catalogBuilder.build() - val connector = MemoryConnector(catalog) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "u" - } + val connector = MemoryConnector.builder() + .name("memory") + .apply { + globals.forEach { + val table = MemoryTable.of( + name = Name.of(it.name), + schema = PType.fromStaticType(it.type), + datum = IonDatum.of(loadSingleElement(it.value)) + ) + define(table) + } + } + .build() val session = Session.builder() .catalog("memory") - .catalogs("memory" to connector.getMetadata(connectorSession)) + .catalogs(connector.getCatalog()) .build() val plan = planner.plan(statement, session) val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) @@ -1344,15 +1350,10 @@ class PartiQLEngineDefaultTest { private fun run(mode: PartiQLEngine.Mode): Pair { val statement = parser.parse(input).root - val catalog = MemoryCatalog.builder().name("memory").build() - val connector = MemoryConnector(catalog) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "u" - } + val connector = MemoryConnector.builder().name("memory").build() val session = Session.builder() .catalog("memory") - .catalogs("memory" to connector.getMetadata(connectorSession)) + .catalogs(connector.getCatalog()) .build() val plan = planner.plan(statement, session) val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) diff --git a/partiql-lang/api/partiql-lang.api b/partiql-lang/api/partiql-lang.api index dfffc5b192..0d139d511a 100644 --- a/partiql-lang/api/partiql-lang.api +++ b/partiql-lang/api/partiql-lang.api @@ -2253,17 +2253,6 @@ public final class org/partiql/lang/planner/transforms/AstNormalizeKt { public static final fun normalize (Lorg/partiql/lang/domains/PartiqlAst$Statement;)Lorg/partiql/lang/domains/PartiqlAst$Statement; } -public final class org/partiql/lang/planner/transforms/PlannerSession { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/time/Instant;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/time/Instant;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCatalogConfig ()Ljava/util/Map; - public final fun getCurrentCatalog ()Ljava/lang/String; - public final fun getCurrentDirectory ()Ljava/util/List; - public final fun getInstant ()Ljava/time/Instant; - public final fun getQueryId ()Ljava/lang/String; - public final fun getUserId ()Ljava/lang/String; -} - public final class org/partiql/lang/planner/transforms/UtilKt { public static final field PLAN_VERSION_NUMBER Ljava/lang/String; public static final fun isLitTrue (Lorg/partiql/lang/domains/PartiqlPhysical$Expr;)Z diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/PlannerSession.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/PlannerSession.kt deleted file mode 100644 index eee17a7912..0000000000 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/PlannerSession.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 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.planner.transforms - -import com.amazon.ionelement.api.StructElement -import org.partiql.spi.connector.ConnectorSession -import java.time.Instant - -/** - * Contains session information for the purposes of planning. - * - * @param queryId a unique identifier for a query. This will be passed to [ConnectorSession] during planning. - * @param userId a unique identifier for a user. This will be passed to [ConnectorSession] during planning. - * @param currentCatalog the current catalog of the session - * @param currentDirectory the current "namespace" within the Catalog. This will aid in building - * [org.partiql.spi.connector.ConnectorObjectPath]'s for unresolved variables. - * @param catalogConfig a map where each key represents a catalog's name, and each value represents the configuration - * ([StructElement]) for the corresponding [org.partiql.spi.connector.Connector]. The [StructElement] has a single mandatory - * key-value pair, with the key being [org.partiql.spi.connector.Constants.CONFIG_KEY_CONNECTOR_NAME] and the value - * being the [com.amazon.ionelement.api.StringElement] representing the name of the corresponding [org.partiql.spi.connector.Connector]. - * The [StructElement] *may* include other key-value pairs that are required/optional by the [org.partiql.spi.connector.Connector]. - * This [StructElement] will be passed to [org.partiql.spi.connector.Connector.Factory.create] to create the - * [org.partiql.spi.connector.Connector]. - * @param instant the instant evaluation begins - */ -public class PlannerSession( - public val queryId: String, - public val userId: String, - public val currentCatalog: String? = null, - public val currentDirectory: List = emptyList(), - public val catalogConfig: Map = emptyMap(), - public val instant: Instant = Instant.now() -) { - internal fun toConnectorSession(): ConnectorSession = object : ConnectorSession { - override fun getQueryId(): String = queryId - override fun getUserId(): String = userId - } -} 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 451bd27c5e..ef5445cd3b 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 @@ -1,5 +1,8 @@ package org.partiql.planner.internal +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Identifier import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Session import org.partiql.planner.internal.casts.CastTable @@ -9,7 +12,6 @@ import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.refAgg import org.partiql.planner.internal.ir.refFn -import org.partiql.planner.internal.ir.refObj import org.partiql.planner.internal.ir.relOpAggregateCallResolved import org.partiql.planner.internal.ir.rex import org.partiql.planner.internal.ir.rexOpCallDynamic @@ -18,8 +20,6 @@ import org.partiql.planner.internal.ir.rexOpCastResolved import org.partiql.planner.internal.ir.rexOpVarGlobal import org.partiql.planner.internal.typer.CompilerType import org.partiql.planner.internal.typer.Scope.Companion.toPath -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata import org.partiql.spi.fn.AggSignature import org.partiql.spi.fn.SqlFnProvider import org.partiql.types.PType @@ -37,18 +37,12 @@ import org.partiql.types.PType.Kind */ internal class Env(private val session: Session) { - private val catalogs = session.getCatalogs() + private val catalogs: Catalogs = session.getCatalogs() /** - * Current catalog [ConnectorMetadata]. Error if missing from the session. + * Current [Catalog] implementation; error if missing from the [Catalogs] provider. */ - private val catalog: ConnectorMetadata = catalogs[session.getCatalog()] - ?: error("Session is missing ConnectorMetadata for current catalog ${session.getCatalog()}") - - /** - * A [PathResolver] for looking up objects given both unqualified and qualified names. - */ - private val objects: PathResolverObj = PathResolverObj(catalog, catalogs, session) + private val default: Catalog = catalogs.getCatalog(session.getCatalog()) ?: error("Default catalog does not exist") /** * A [SqlFnProvider] for looking up built-in functions. @@ -56,35 +50,60 @@ internal class Env(private val session: Session) { private val fns: SqlFnProvider = SqlFnProvider /** - * This function looks up a global [BindingPath], returning a global reference expression. + * Catalog lookup needs to search (3x) to handle schema-qualified and catalog-qualified use-cases. * - * Convert any remaining binding names (tail) to a path expression. + * 1. Lookup in current catalog and namespace. + * 2. Lookup as a schema-qualified identifier. + * 3. Lookup as a catalog-qualified identifier. * - * @param path - * @return */ - fun resolveObj(path: BindingPath): Rex? { - val item = objects.lookup(path) ?: return null - // Create an internal typed reference - val ref = refObj( - catalog = item.catalog, - name = Name.of(item.handle.path.steps), - type = CompilerType(item.handle.entity.getPType()), - ) - // Rewrite as a path expression. - val root = rex(ref.type, rexOpVarGlobal(ref)) - val depth = calculateMatched(path, item.input, ref.name.toList()) - val tail = path.steps.drop(depth) + fun resolveTable(identifier: Identifier): Rex? { + + // 1. Search in current catalog and namespace + var catalog = default + var path = resolve(identifier) + var handle = catalog.getTableHandle(session, path) + + // 2. Lookup as a schema-qualified identifier. + if (handle == null && identifier.hasQualifier()) { + path = identifier + handle = catalog.getTableHandle(session, path) + } + + // 3. Lookup as a catalog-qualified identifier + if (handle == null && identifier.hasQualifier()) { + val parts = identifier.getParts() + val head = parts.first() + val tail = parts.drop(1) + catalog = catalogs.getCatalog(head.getText(), ignoreCase = head.isRegular()) ?: return null + path = Identifier.of(tail) + handle = catalog.getTableHandle(session, path) + } + + // !! NOT FOUND !! + if (handle == null) { + return null + } + + // Make a reference and return a global variable expression. + val refCatalog = catalog.getName() + val refName = handle.name + val refType = CompilerType(handle.table.getSchema()) + val ref = Ref.Obj(refCatalog, refName, refType) + + // Convert any remaining identifier parts to a path expression + val root = Rex(ref.type, rexOpVarGlobal(ref)) + val tail = calculateMatched(path, handle.name) return if (tail.isEmpty()) root else root.toPath(tail) } - fun resolveFn(path: BindingPath, args: List): Rex? { + fun resolveFn(identifier: Identifier, args: List): Rex? { // Assume all functions are defined in the current catalog and reject qualified routine names. - if (path.steps.size > 1) { + if (identifier.hasQualifier()) { error("Qualified functions are not supported.") } val catalog = session.getCatalog() - val name = path.steps.last().name.lowercase() + val name = identifier.getIdentifier().getText().lowercase() // Invoke existing function resolution logic val variants = fns.lookupFn(name) ?: return null val match = FnResolver.resolve(variants, args.map { it.type }) @@ -100,9 +119,10 @@ internal class Env(private val session: Session) { coercions = emptyList() ) } + // TODO consistency for error messages? return ProblemGenerator.missingRex( rexOpCallDynamic(args, candidates), - ProblemGenerator.incompatibleTypesForOp(path.normalized.joinToString("."), args.map { it.type }) + ProblemGenerator.incompatibleTypesForOp(name.uppercase(), args.map { it.type }) ) } return when (match) { @@ -173,45 +193,29 @@ internal class Env(private val session: Session) { // Helpers // ----------------------- + // Helpers + /** - * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata - * - * Assume: - * - steps_matched = user_input_path_size - path_steps_not_found_size - * - path_steps_not_found_size = catalog_path_sent_to_spi_size - actual_catalog_absolute_path_size - * - * Therefore, we present the equation to [calculateMatched]: - * - steps_matched = user_input_path_size - (catalog_path_sent_to_spi_size - actual_catalog_absolute_path_size) - * = user_input_path_size + actual_catalog_absolute_path_size - catalog_path_sent_to_spi_size - * - * For example: - * - * Assume we are in some catalog, C, in some schema, S. There is a tuple, T, with attribute, A1. Assume A1 is of type - * tuple with an attribute A2. - * If our query references `T.A1.A2`, we will eventually ask SPI (connector C) for `S.T.A1.A2`. In this scenario: - * - The original user input was `T.A1.A2` (length 3) - * - The absolute path returned from SPI will be `S.T` (length 2) - * - The path we eventually sent to SPI to resolve was `S.T.A1.A2` (length 4) - * - * So, we can now use [calculateMatched] to determine how many were actually matched from the user input. Using the - * equation from above: - * - * - steps_matched = len(user input) + len(absolute catalog path) - len(path sent to SPI) - * = len([userInputPath]) + len([actualAbsolutePath]) - len([pathSentToConnector]) - * = 3 + 2 - 4 - * = 5 - 4 - * = 1 - * - * - * Therefore, in this example we have determined that from the original input (`T.A1.A2`) `T` is the value matched in the - * database environment. + * Prepends the current session namespace to the identifier; named like Path.resolve() from java io. + */ + private fun resolve(identifier: Identifier): Identifier { + val namespace = session.getNamespace() + return if (namespace.isEmpty()) { + // no need to create another object + identifier + } else { + // prepend the namespace + namespace.asIdentifier().append(identifier) + } + } + + /** + * Returns a list of the unmatched parts of the identifier given the matched name. */ - private fun calculateMatched( - userInputPath: BindingPath, - pathSentToConnector: BindingPath, - actualAbsolutePath: List, - ): Int { - return userInputPath.steps.size + actualAbsolutePath.size - pathSentToConnector.steps.size + private fun calculateMatched(path: Identifier, name: Name): List { + val lhs = name.toList() + val rhs = path.toList() + return rhs.takeLast(rhs.size - lhs.size) } private fun match(candidates: List, args: List): Pair>? { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt deleted file mode 100644 index 08a7ebca5a..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle - -/** - * A simple catalog to metadata pair. - * - * @param T - * @property catalog The resolved entity's catalog name. - * @property input The input binding path (sent to SPI) that resulted in this item match. - * @property handle The resolved entity's catalog path and type information. - */ -internal data class PathItem( - @JvmField val catalog: String, - @JvmField val input: BindingPath, - @JvmField val handle: ConnectorHandle, -) 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 deleted file mode 100644 index accba43a32..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt +++ /dev/null @@ -1,123 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.catalog.Session -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata - -/** - * TODO PATH RESOLVER WITH BE REPLACED BY THE org.partiql.planner.catalog HOWEVER THIS FACTORING IS TEMPORARY - * TO MAKE THE TRANSITION OF THE SESSION A SMALLER AND MORE INCREMENTAL CHANGE. - * - * - * This is the base behavior for name resolution. - * - * Let N be the number of steps in a given path. - * - * 1. If N = 1 - * (a) Lookup at .. (relative in catalog) - * 2. If N = 2 - * (a) Lookup at .. (relative in catalog) - * (b) Lookup at . (absolute in catalog) - * 3. If N > 2 - * (a) Lookup at .. (relative in catalog) - * (b) Lookup at . (absolute in catalog) - * (c) Lookup as an absolute where the first step is the catalog. (absolute in system) - * - * @param T - * @property catalog - * @property session - */ -internal abstract class PathResolver( - private val catalog: ConnectorMetadata, - private val catalogs: Map, - private val session: Session, -) { - - /** - * The session's current directory represented as [BindingName] steps. - */ - open val schema = session.getNamespace().map { it.toBindingName() } - - /** - * A [PathResolver] should override this one method for which [ConnectorMetadata] API to call. - * - * @param metadata ConnectorMetadata to resolve this catalog entity in. - * @param path The catalog absolute path. - * @return - */ - abstract fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle? - - /** - * 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 lookup(path: BindingPath): PathItem? { - val n = path.steps.size - val m = schema.size - return if (m > 0) { - val absPath = BindingPath(schema + path.steps) - when (n) { - 0 -> return null - 1 -> return get(absPath) - 2 -> return get(absPath) ?: get(path) - else -> return get(absPath) ?: get(path) ?: search(path) - } - } else { - // no need to prepend path as it's empty - when (n) { - 0 -> null - 1 -> get(path) - 2 -> get(path) - else -> get(path) ?: search(path) - } - } - } - - /** - * This gets the path in the current catalog. - * - * @param path Catalog absolute path. - * @return - */ - private fun get(path: BindingPath): PathItem? { - val handle = get(catalog, path) ?: return null - return PathItem(session.getCatalog(), path, handle) - } - - /** - * This searches with a system absolute path, using the session to lookup catalogs. - * - * @param path System absolute path. - */ - private fun search(path: BindingPath): PathItem? { - var match: Map.Entry? = null - val first: BindingName = path.steps.first() - for (catalog in catalogs) { - if (first.matches(catalog.key)) { - if (match != null) { - // TODO root was already matched, emit ambiguous error - return null - } - match = catalog - } - } - 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 - return PathItem(catalog, absPath, handle) - } - - private fun String.toBindingName() = BindingName(this, BindingCase.SENSITIVE) -} 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 deleted file mode 100644 index 42e64acb9e..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.catalog.Session -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorObject - -/** - * PathResolver implementation which calls out to get database objects. - * - * @param catalog - * @param catalogs - * @param session - */ -internal class PathResolverObj( - catalog: ConnectorMetadata, - catalogs: Map, - session: Session, -) : PathResolver(catalog, catalogs, session) { - - override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle.Obj? = metadata.getObject(path) -} 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 1dc37eb3fd..3463001107 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 @@ -16,7 +16,6 @@ package org.partiql.planner.internal.typer -import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env import org.partiql.planner.internal.ProblemGenerator import org.partiql.planner.internal.exclude.ExcludeRepr @@ -52,9 +51,6 @@ import org.partiql.planner.internal.ir.rexOpSubquery import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.ir.util.PlanRewriter import org.partiql.planner.internal.utils.PlanUtils -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath import org.partiql.types.Field import org.partiql.types.PType import org.partiql.types.PType.Kind @@ -168,20 +164,6 @@ internal class PlanTyper(private val env: Env) { } fun List.toCType(): List = this.map { it.toCType() } - - fun CompilerType.isNumeric(): Boolean { - return this.kind in setOf( - Kind.INTEGER, - Kind.NUMERIC, - Kind.BIGINT, - Kind.TINYINT, - Kind.SMALLINT, - Kind.REAL, - Kind.DOUBLE, - Kind.DECIMAL, - Kind.DECIMAL_ARBITRARY - ) - } } /** @@ -505,8 +487,7 @@ internal class PlanTyper(private val env: Env) { // resolve `root` to local binding val locals = Scope(input.type.schema, outer) val typeEnv = TypeEnv(env, locals) - val path = root.identifier.toBindingPath() - val resolved = typeEnv.resolve(path) + val resolved = typeEnv.resolve(root.identifier) if (resolved == null) { ProblemGenerator.missingRex( emptyList(), @@ -609,12 +590,11 @@ internal class PlanTyper(private val env: Env) { } override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: CompilerType?): Rex { - val path = node.identifier.toBindingPath() val strategy = when (node.scope) { Rex.Op.Var.Scope.DEFAULT -> strategy Rex.Op.Var.Scope.LOCAL -> Strategy.LOCAL } - val resolvedVar = typeEnv.resolve(path, strategy) + val resolvedVar = typeEnv.resolve(node.identifier, strategy) if (resolvedVar == null) { val id = PlanUtils.externalize(node.identifier) val inScopeVariables = typeEnv.locals.schema.map { it.name }.toSet() @@ -787,8 +767,7 @@ internal class PlanTyper(private val env: Env) { // Type the arguments val args = node.args.map { visitRex(it, null) } // Attempt to resolve in the environment - val path = node.identifier.toBindingPath() - val rex = env.resolveFn(path, args) + val rex = env.resolveFn(node.identifier, args) if (rex == null) { return ProblemGenerator.errorRex( causes = args.map { it.op }, @@ -1310,28 +1289,8 @@ internal class PlanTyper(private val env: Env) { return this.copy(schema = schema.mapIndexed { i, binding -> binding.copy(type = types[i]) }) } - /** - * Convert an identifier into a binding path. - */ - private fun Identifier.toBindingPath() = BindingPath( - map { - BindingName( - name = it.getText(), - case = when (it.isRegular()) { - true -> BindingCase.INSENSITIVE - else -> BindingCase.SENSITIVE - } - ) - } - ) - private fun Rel.isOrdered(): Boolean = type.props.contains(Rel.Prop.ORDERED) - /** - * Produce a union type from all the - */ - private fun List.toUnionType(): PType = anyOf(map { it.type }.toSet()) ?: PType.dynamic() - private fun getElementTypeForFromSource(fromSourceType: CompilerType): CompilerType = when (fromSourceType.kind) { Kind.DYNAMIC -> CompilerType(PType.dynamic()) Kind.BAG, Kind.ARRAY, Kind.SEXP -> fromSourceType.typeParameter diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt index 964a8a3e97..78d1b4c37c 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt @@ -1,5 +1,6 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.rex @@ -7,9 +8,7 @@ import org.partiql.planner.internal.ir.rexOpLit import org.partiql.planner.internal.ir.rexOpPathKey import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.ir.rexOpVarLocal -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath +import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.types.PType import org.partiql.types.PType.Kind import org.partiql.types.StaticType @@ -36,9 +35,10 @@ internal data class Scope( /** * Attempts to resolve using just the local binding name. */ - fun resolveName(path: BindingPath): Rex? { - val head: BindingName = path.steps[0] - val tail: List = path.steps.drop(1) + fun resolveName(identifier: Identifier): Rex? { + val path = identifier.toList() + val head = path.first() + val tail = path.drop(1) val r = matchRoot(head) ?: return null // Convert any remaining binding names (tail) to an untyped path expression. return if (tail.isEmpty()) r else r.toPath(tail) @@ -47,14 +47,12 @@ internal data class Scope( /** * Check if the path root unambiguously matches a local binding struct value field. * Convert any remaining binding names (tail) to a path expression. - * - * @param path - * @return */ - fun resolveField(path: BindingPath): Rex? { - val head: BindingName = path.steps[0] + fun resolveField(identifier: Identifier): Rex? { + val head = identifier.first() + val tail = identifier.toList() + // Match first identifier to a struct field val r = matchStruct(head) ?: return null - val tail = path.steps // Convert any remaining binding names (tail) to an untyped path expression. return if (tail.isEmpty()) r else r.toPath(tail) } @@ -72,7 +70,7 @@ internal data class Scope( * @param name * @return */ - private fun matchRoot(name: BindingName, depth: Int = 0): Rex? { + private fun matchRoot(name: Identifier.Part, depth: Int = 0): Rex? { var r: Rex? = null for (i in schema.indices) { val local = schema[i] @@ -97,7 +95,7 @@ internal data class Scope( * @param name * @return */ - private fun matchStruct(name: BindingName, depth: Int = 0): Rex? { + private fun matchStruct(name: Identifier.Part, depth: Int = 0): Rex? { var c: Rex? = null var known = false for (i in schema.indices) { @@ -134,7 +132,7 @@ internal data class Scope( } /** - * Searches for the [BindingName] within the given [StaticType]. + * Searches for the [Identifier.Part] within the given [StaticType]. * * Returns * - true iff known to contain key @@ -144,9 +142,9 @@ internal data class Scope( * @param name * @return */ - private fun CompilerType.containsKey(name: BindingName): Boolean? { + private fun CompilerType.containsKey(name: Identifier.Part): Boolean? { return when (this.kind) { - Kind.ROW -> this.fields!!.any { name.matches(it.name) } + Kind.ROW -> this.fields.any { name.matches(it.name) } Kind.STRUCT -> null Kind.DYNAMIC -> null else -> false @@ -156,22 +154,26 @@ internal data class Scope( companion object { /** - * Converts a list of [BindingName] to a path expression. + * Converts a list of [Identifier.Part] to a path expression. * * 1) Case SENSITIVE identifiers become string literal key lookups. * 2) Case INSENSITIVE identifiers become symbol lookups. * - * @param steps + * @param parts * @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(CompilerType(PType.string()), rexOpLit(stringValue(step.name)))) - BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + internal fun Rex.toPath(parts: List): Rex = parts.fold(this) { curr, part -> + val type = PType.dynamic().toCType() + val text = part.getText() + val op = when (part.isRegular()) { + true -> rexOpPathSymbol(curr, text) + else -> rexOpPathKey(curr, string(text)) } - rex(CompilerType(PType.dynamic()), op) + rex(type, op) } + + @OptIn(PartiQLValueExperimental::class) + private fun string(text: String) = rex(CompilerType(PType.string()), rexOpLit(stringValue(text))) } } 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 137e513916..7e73317d43 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 @@ -1,8 +1,8 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rex -import org.partiql.spi.BindingPath /** * TypeEnv represents the variables type environment (holds references to both locals and globals). @@ -26,10 +26,18 @@ internal class TypeEnv( * 2. Match Nested Field * - Match Locals */ - fun resolve(path: BindingPath, strategy: Strategy = Strategy.LOCAL): Rex? { + fun resolve(identifier: Identifier, strategy: Strategy = Strategy.LOCAL): Rex? { return when (strategy) { - Strategy.LOCAL -> locals.resolveName(path) ?: globals.resolveObj(path) ?: locals.resolveField(path) - Strategy.GLOBAL -> globals.resolveObj(path) ?: locals.resolveName(path) ?: locals.resolveField(path) + Strategy.LOCAL -> { + locals.resolveName(identifier) + ?: globals.resolveTable(identifier) + ?: locals.resolveField(identifier) + } + Strategy.GLOBAL -> { + globals.resolveTable(identifier) + ?: locals.resolveName(identifier) + ?: locals.resolveField(identifier) + } } } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt index 3ab8095dec..2186d260c1 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt @@ -9,20 +9,16 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.PartiQLPlan import org.partiql.plan.PlanNode import org.partiql.plan.debug.PlanPrinter +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Session +import org.partiql.planner.internal.TestCatalog import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.PlanNodeEquivalentVisitor import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.BagType +import org.partiql.types.PType import org.partiql.types.StaticType import org.partiql.types.StructType import org.partiql.types.TupleConstraint @@ -71,15 +67,10 @@ class PlanTest { ) ) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - val pipeline: (PartiQLTest, Boolean) -> PartiQLPlanner.Result = { test, isSignalMode -> val session = Session.builder() .catalog("default") - .catalogs("default" to buildMetadata("default")) + .catalogs(buildCatalog("default")) .namespace("SCHEMA") .build() val problemCollector = ProblemCollector() @@ -88,18 +79,11 @@ class PlanTest { planner.plan(ast, session, problemCollector) } - fun buildMetadata(catalogName: String): ConnectorMetadata { - val catalog = MemoryCatalog.builder().name(catalogName).build() - // Insert binding - val name = BindingPath( - listOf( - BindingName("SCHEMA", BindingCase.INSENSITIVE), - BindingName("T", BindingCase.INSENSITIVE), - ) - ) - val obj = MemoryObject(type) - catalog.insert(name, obj) - return MemoryConnector(catalog).getMetadata(connectorSession) + private fun buildCatalog(catalogName: String): Catalog { + return TestCatalog.builder() + .name(catalogName) + .createTable(Name.of("SCHEMA", "T"), PType.fromStaticType(type)) + .build() } @TestFactory diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index ef8c588596..dd22b40c3d 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -11,9 +11,7 @@ import org.partiql.planner.catalog.Session import org.partiql.planner.internal.typer.CompilerType import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryConnector -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.BagType import org.partiql.types.PType import org.partiql.types.StaticType @@ -27,7 +25,7 @@ internal class PlannerErrorReportingTests { val userId = "test-user" val queryId = "query" - val catalog = MemoryCatalog + val catalog = MemoryConnector .builder() .name(catalogName) .define("missing_binding", StaticType.ANY) @@ -41,18 +39,11 @@ internal class PlannerErrorReportingTests { StructType.Field("f1", StaticType.INT2), ) ) - .build() - - val metadata = MemoryConnector(catalog).getMetadata( - object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "s" - } - ) + .build().getCatalog() val session = Session.builder() .catalog(catalogName) - .catalogs(catalogName to metadata) + .catalogs(catalog) .build() val parser = PartiQLParserBuilder().build() diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/TestCatalog.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/TestCatalog.kt new file mode 100644 index 0000000000..61c180b1bd --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/TestCatalog.kt @@ -0,0 +1,129 @@ +package org.partiql.planner.internal + +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Identifier +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session +import org.partiql.planner.catalog.Table +import org.partiql.types.PType + +/** + * Basic catalog implementation used for testing; consider merging with MemoryConnector? + */ +public class TestCatalog private constructor( + private val name: String, + private val root: Tree, +) : Catalog { + + override fun getName(): String = name + + override fun getTable(session: Session, name: Name): Table? { + return null + } + + override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? { + val matched = mutableListOf() + var curr: Tree = root + for (part in identifier) { + curr = curr.get(part) ?: break + matched.add(curr.name) + } + if (curr.table == null) { + return null + } + return Table.Handle( + name = Name.of(matched), + table = curr.table!! + ) + } + + // TODO + override fun listTables(session: Session, namespace: Namespace): Collection { + return emptyList() + } + + // TODO + override fun listNamespaces(session: Session, namespace: Namespace): Collection { + return emptyList() + } + + private class Tree( + @JvmField val name: String, + @JvmField var table: Table?, + @JvmField val children: MutableMap, + ) { + + /** + * TODO ambiguous binding error? + */ + fun get(part: Identifier.Part): Tree? { + // regular, search insensitively + if (part.isRegular()) { + for (child in children.values) { + if (part.matches(child.name)) { + return child + } + } + } + // delimited, search exact + return children[part.getText()] + } + + fun getOrPut(name: String): Tree = children.getOrPut(name) { Tree(name, null, mutableMapOf()) } + } + + override fun toString(): String = buildString { + for (child in root.children.values) { + append(toString(child)) + } + } + + private fun toString(tree: Tree, prefix: String? = null): String = buildString { + val pre = if (prefix != null) prefix + "." + tree.name else tree.name + appendLine(pre) + for (child in tree.children.values) { + append(toString(child, pre)) + } + } + + companion object { + + @JvmStatic + fun empty(name: String): TestCatalog = TestCatalog(name, Tree(name, null, mutableMapOf())) + + @JvmStatic + fun builder(): Builder = Builder() + } + + /** + * Perhaps this will be moved to Catalog. + */ + class Builder { + + private var name: String? = null + private val root = Tree(".", null, mutableMapOf()) + + fun name(name: String): Builder { + this.name = name + return this + } + + fun createTable(name: Name, schema: PType): Builder { + var curr = root + for (part in name) { + // upsert namespaces + curr = curr.getOrPut(part) + } + curr.table = Table.of(name.getName(), schema) + return this + } + + fun build(): Catalog { + if (name == null) { + error("Catalog must have a name") + } + return TestCatalog(name!!, root) + } + } +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/exclude/SubsumptionTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/exclude/SubsumptionTest.kt index 58042d2f85..023b63ebb1 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/exclude/SubsumptionTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/exclude/SubsumptionTest.kt @@ -21,9 +21,7 @@ import org.partiql.plan.relOpExcludeTypeStructWildcard import org.partiql.plan.rexOpVar import org.partiql.planner.PartiQLPlanner import org.partiql.planner.catalog.Session -import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryConnector -import org.partiql.spi.connector.ConnectorSession import java.util.stream.Stream import kotlin.test.assertEquals @@ -33,11 +31,7 @@ class SubsumptionTest { private val planner = PartiQLPlanner.standard() private val parser = PartiQLParser.default() - private val session = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - private val connector = MemoryConnector(MemoryCatalog("default")) + private val catalog = MemoryConnector.builder().name("default").build().getCatalog() } private fun getExcludeClause(statement: Statement): Rel.Op.Exclude { @@ -51,9 +45,7 @@ class SubsumptionTest { val statement = parser.parse(text).root val session = Session.builder() .catalog("default") - .catalogs( - "default" to connector.getMetadata(session), - ) + .catalogs(catalog) .build() val plan = planner.plan(statement, session).plan val excludeClause = getExcludeClause(plan.statement).paths diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt index 32289a30ca..9f5f3d58b3 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt @@ -7,23 +7,18 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.Statement import org.partiql.plan.debug.PlanPrinter import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Session import org.partiql.planner.internal.PlanningProblemDetails import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession +import org.partiql.plugins.memory.MemoryTable import org.partiql.types.PType import org.partiql.types.PType.Kind import org.partiql.types.StaticType -import org.partiql.value.PartiQLValueExperimental import java.util.stream.Stream abstract class PartiQLTyperTestBase { @@ -45,22 +40,17 @@ abstract class PartiQLTyperTestBase { public val parser = PartiQLParser.default() public val planner = PartiQLPlanner.standard() - internal val session: ((String, ConnectorMetadata) -> Session) = { catalog, metadata -> + internal val session: ((String, Catalog) -> Session) = { catalog, metadata -> Session.builder() .catalog(catalog) - .catalogs(catalog to metadata) + .catalogs(metadata) .build() } - - internal val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "test" - override fun getUserId(): String = "test" - } } val inputs = PartiQLTestProvider().apply { load() } - val testingPipeline: ((String, String, ConnectorMetadata, ProblemCallback) -> PartiQLPlanner.Result) = { query, catalog, metadata, collector -> + val testingPipeline: ((String, String, Catalog, ProblemCallback) -> PartiQLPlanner.Result) = { query, catalog, metadata, collector -> val ast = parser.parse(query).root planner.plan(ast, session(catalog, metadata), collector) } @@ -68,18 +58,16 @@ abstract class PartiQLTyperTestBase { /** * Build a ConnectorMetadata instance from the list of types. */ - @OptIn(PartiQLValueExperimental::class) - private fun buildMetadata(catalog: String, types: List): ConnectorMetadata { - val cat = MemoryCatalog.builder().name(catalog).build() - val connector = MemoryConnector(cat) - + private fun buildMetadata(catalog: String, types: List): Catalog { + val connector = MemoryConnector.builder().name(catalog) // define all bindings types.forEachIndexed { i, t -> - val binding = BindingPath(listOf(BindingName("t${i + 1}", BindingCase.SENSITIVE))) - val obj = MemoryObject(t) - cat.insert(binding, obj) + val name = Name.of("t${i + 1}") + val schema = PType.fromStaticType(t) + val table = MemoryTable.empty(name, schema) + connector.define(table) } - return connector.getMetadata(connectorSession) + return connector.build().getCatalog() } fun testGen( 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 f6d74dfab8..5254f40f12 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 @@ -121,7 +121,12 @@ class PlanTyperTest { Session.builder() .catalog("pql") .namespace("main") - .catalogs("pql" to LocalConnector.Metadata(root)) + .catalogs( + LocalConnector.builder() + .name("pql") + .root(root) + .build().getCatalog() + ) .build() ) return PlanTyperWrapper(PlanTyper(env)) diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt index f7097f222b..a5b737638e 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt @@ -19,8 +19,11 @@ import org.partiql.plan.PartiQLPlan import org.partiql.plan.Statement import org.partiql.plan.debug.PlanPrinter import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Session import org.partiql.planner.internal.ProblemGenerator +import org.partiql.planner.internal.TestCatalog import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.ErrorTestCase import org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.SuccessTestCase @@ -29,14 +32,6 @@ import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.ProblemCollector import org.partiql.plugins.local.toStaticType -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.BagType import org.partiql.types.ListType import org.partiql.types.PType @@ -49,7 +44,6 @@ import org.partiql.types.StaticType.Companion.STRUCT import org.partiql.types.StaticType.Companion.unionOf import org.partiql.types.StructType import org.partiql.types.TupleConstraint -import org.partiql.value.PartiQLValueExperimental import java.util.stream.Stream import kotlin.reflect.KClass import kotlin.test.assertEquals @@ -146,11 +140,6 @@ internal class PlanTyperTestsPorted { } } - val session = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - private fun id(vararg parts: Identifier.Symbol): Identifier { return when (parts.size) { 0 -> error("Identifier requires more than one part.") @@ -168,10 +157,10 @@ internal class PlanTyperTestsPorted { /** * MemoryConnector.Factory from reading the resources in /resource_path.txt for Github CI/CD. */ - @OptIn(PartiQLValueExperimental::class) - val catalogs: List> by lazy { + private val catalogs: List by lazy { + // Make a map from catalog name to tables. val inputStream = this::class.java.getResourceAsStream("/resource_path.txt")!! - val map = mutableMapOf>>() + val map = mutableMapOf>>() inputStream.reader().readLines().forEach { path -> if (path.startsWith("catalogs/default")) { val schema = this::class.java.getResourceAsStream("/$path")!! @@ -179,26 +168,26 @@ internal class PlanTyperTestsPorted { val staticType = ion.toStaticType() val steps = path.substring(0, path.length - 4).split('/').drop(2) // drop the catalogs/default val catalogName = steps.first() - val bindingSteps = steps - .drop(1) - .map { BindingName(it, BindingCase.INSENSITIVE) } - val bindingPath = BindingPath(bindingSteps) + // args + val name = Name.of(steps.drop(1)) + val ptype = PType.fromStaticType(staticType) if (map.containsKey(catalogName)) { - map[catalogName]!!.add(bindingPath to staticType) + map[catalogName]!!.add(name to ptype) } else { - map[catalogName] = mutableListOf(bindingPath to staticType) + map[catalogName] = mutableListOf(name to ptype) } } } - map.entries.map { (catalogName, bindings) -> - val catalog = MemoryCatalog.builder().name(catalogName).build() - val connector = MemoryConnector(catalog) - for (binding in bindings) { - val path = binding.first - val obj = MemoryObject(binding.second) - catalog.insert(path, obj) - } - catalogName to connector.getMetadata(session) + // Make a catalogs list + map.map { (catalog, tables) -> + TestCatalog.builder() + .name(catalog) + .apply { + for ((name, schema) in tables) { + createTable(name, schema) + } + } + .build() } } @@ -287,14 +276,6 @@ internal class PlanTyperTestsPorted { ) ) - private fun insensitiveId(symbol: String) = BindingPath(listOf(BindingName(symbol, BindingCase.INSENSITIVE))) - - private fun sensitiveId(symbol: String) = BindingPath(listOf(BindingName(symbol, BindingCase.SENSITIVE))) - - private fun idQualified(vararg symbol: Pair) = symbol.map { - BindingName(it.first, it.second) - }.let { BindingPath(it) } - // // Parameterized Test Source // diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt index 49292455c8..d57480d76a 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt @@ -4,16 +4,13 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Identifier import org.partiql.planner.catalog.Session import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata import org.partiql.types.PType import kotlin.test.assertEquals import kotlin.test.fail @@ -22,6 +19,10 @@ internal class ScopeTest { companion object { + private val catalog = object : Catalog { + override fun getName(): String = "currentCatalog" + } + /** * < * A : { B: } }, @@ -37,13 +38,7 @@ internal class ScopeTest { Env( Session.builder() .catalog("currentCatalog") - .catalogs( - "currentCatalog" to object : ConnectorMetadata { - override fun getObject(path: BindingPath): ConnectorHandle.Obj? { - return null - } - } - ) + .catalogs(catalog) .build() ), Scope( @@ -119,13 +114,13 @@ internal class ScopeTest { assertEquals(expected, root.ref) } - private fun String.path(): BindingPath { - val steps = trim().split(".").map { + private fun String.path(): Identifier { + val parts = trim().split(".").map { when (it.startsWith("\"")) { - true -> BindingName(it.drop(1).dropLast(1), BindingCase.SENSITIVE) - else -> BindingName(it, BindingCase.INSENSITIVE) + true -> Identifier.Part.delimited(it.drop(1).dropLast(1)) + else -> Identifier.Part.regular(it) } } - return BindingPath(steps) + return Identifier.Companion.of(parts) } } diff --git a/partiql-spi/api/partiql-spi.api b/partiql-spi/api/partiql-spi.api index 2215dffd5b..5f4868d57f 100644 --- a/partiql-spi/api/partiql-spi.api +++ b/partiql-spi/api/partiql-spi.api @@ -355,9 +355,8 @@ public final class org/partiql/planner/catalog/Catalog$DefaultImpls { public abstract interface class org/partiql/planner/catalog/Catalogs { public static final field Companion Lorg/partiql/planner/catalog/Catalogs$Companion; public static fun builder ()Lorg/partiql/planner/catalog/Catalogs$Builder; - public abstract fun default ()Lorg/partiql/planner/catalog/Catalog; - public abstract fun get (Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; - public abstract fun list ()Ljava/util/Collection; + public abstract fun getCatalog (Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; + public abstract fun listCatalogs ()Ljava/util/Collection; public static fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Catalogs; public static fun of ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs; } @@ -366,7 +365,6 @@ public final class org/partiql/planner/catalog/Catalogs$Builder { public fun ()V public final fun add (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; public final fun build ()Lorg/partiql/planner/catalog/Catalogs; - public final fun default (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; } public final class org/partiql/planner/catalog/Catalogs$Companion { @@ -376,9 +374,8 @@ public final class org/partiql/planner/catalog/Catalogs$Companion { } public final class org/partiql/planner/catalog/Catalogs$DefaultImpls { - public static fun get (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; - public static synthetic fun get$default (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/planner/catalog/Catalog; - public static fun list (Lorg/partiql/planner/catalog/Catalogs;)Ljava/util/Collection; + public static synthetic fun getCatalog$default (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/planner/catalog/Catalog; + public static fun listCatalogs (Lorg/partiql/planner/catalog/Catalogs;)Ljava/util/Collection; } public abstract interface class org/partiql/planner/catalog/Function { @@ -559,7 +556,7 @@ public abstract interface class org/partiql/planner/catalog/Session { public static fun builder ()Lorg/partiql/planner/catalog/Session$Builder; public static fun empty (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session; public abstract fun getCatalog ()Ljava/lang/String; - public abstract fun getCatalogs ()Ljava/util/Map; + public abstract fun getCatalogs ()Lorg/partiql/planner/catalog/Catalogs; public abstract fun getIdentity ()Ljava/lang/String; public abstract fun getNamespace ()Lorg/partiql/planner/catalog/Namespace; public abstract fun getPath ()Lorg/partiql/planner/catalog/Path; @@ -570,7 +567,7 @@ public final class org/partiql/planner/catalog/Session$Builder { public fun ()V public final fun build ()Lorg/partiql/planner/catalog/Session; public final fun catalog (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; - public final fun catalogs ([Lkotlin/Pair;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun catalogs ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Session$Builder; public final fun identity (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; public final fun namespace (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Session$Builder; public final fun namespace (Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Session$Builder; @@ -591,7 +588,7 @@ public final class org/partiql/planner/catalog/Session$DefaultImpls { public abstract interface class org/partiql/planner/catalog/Table { public static final field Companion Lorg/partiql/planner/catalog/Table$Companion; public static fun builder ()Lorg/partiql/planner/catalog/Table$Builder; - public abstract fun getName ()Ljava/lang/String; + public abstract fun getName ()Lorg/partiql/planner/catalog/Name; public abstract fun getSchema ()Lorg/partiql/types/PType; public static fun of (Ljava/lang/String;Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table; } @@ -619,121 +616,30 @@ public final class org/partiql/planner/catalog/Table$Handle { public fun (Lorg/partiql/planner/catalog/Name;Lorg/partiql/planner/catalog/Table;)V } -public final class org/partiql/spi/BindingCase : java/lang/Enum { - public static final field INSENSITIVE Lorg/partiql/spi/BindingCase; - public static final field SENSITIVE Lorg/partiql/spi/BindingCase; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/spi/BindingCase; - public static fun values ()[Lorg/partiql/spi/BindingCase; -} - -public final class org/partiql/spi/BindingName { - public fun (Ljava/lang/String;Lorg/partiql/spi/BindingCase;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lorg/partiql/spi/BindingCase; - public final fun copy (Ljava/lang/String;Lorg/partiql/spi/BindingCase;)Lorg/partiql/spi/BindingName; - public static synthetic fun copy$default (Lorg/partiql/spi/BindingName;Ljava/lang/String;Lorg/partiql/spi/BindingCase;ILjava/lang/Object;)Lorg/partiql/spi/BindingName; - public fun equals (Ljava/lang/Object;)Z - public final fun getCase ()Lorg/partiql/spi/BindingCase; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public final fun matches (Ljava/lang/String;)Z - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/spi/BindingPath { - public fun (Ljava/util/List;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getKey ()Ljava/lang/String; - public final fun getNormalized ()Ljava/util/List; - public final fun getSteps ()Ljava/util/List; - public fun hashCode ()I - public final fun matches (Lorg/partiql/spi/connector/ConnectorPath;)Z - public fun toString ()Ljava/lang/String; -} - public abstract interface class org/partiql/spi/Plugin { public abstract fun getFactory ()Lorg/partiql/spi/connector/Connector$Factory; } public abstract interface class org/partiql/spi/connector/Connector { public abstract fun getBindings ()Lorg/partiql/spi/connector/ConnectorBindings; - public abstract fun getMetadata (Lorg/partiql/spi/connector/ConnectorSession;)Lorg/partiql/spi/connector/ConnectorMetadata; + public abstract fun getCatalog ()Lorg/partiql/planner/catalog/Catalog; } public abstract interface class org/partiql/spi/connector/Connector$Factory { - public abstract fun create (Ljava/lang/String;Lcom/amazon/ionelement/api/StructElement;)Lorg/partiql/spi/connector/Connector; + public abstract fun create (Lcom/amazon/ionelement/api/StructElement;)Lorg/partiql/spi/connector/Connector; public abstract fun getName ()Ljava/lang/String; } public final class org/partiql/spi/connector/Connector$Factory$DefaultImpls { - public static synthetic fun create$default (Lorg/partiql/spi/connector/Connector$Factory;Ljava/lang/String;Lcom/amazon/ionelement/api/StructElement;ILjava/lang/Object;)Lorg/partiql/spi/connector/Connector; -} - -public abstract interface class org/partiql/spi/connector/ConnectorBindings { - public abstract fun getValue (Lorg/partiql/spi/connector/ConnectorPath;)Lorg/partiql/value/PartiQLValue; -} - -public abstract class org/partiql/spi/connector/ConnectorHandle { - public abstract fun getEntity ()Ljava/lang/Object; - public abstract fun getPath ()Lorg/partiql/spi/connector/ConnectorPath; -} - -public final class org/partiql/spi/connector/ConnectorHandle$Obj : org/partiql/spi/connector/ConnectorHandle { - public fun (Lorg/partiql/spi/connector/ConnectorPath;Lorg/partiql/spi/connector/ConnectorObject;)V - public final fun component1 ()Lorg/partiql/spi/connector/ConnectorPath; - public final fun component2 ()Lorg/partiql/spi/connector/ConnectorObject; - public final fun copy (Lorg/partiql/spi/connector/ConnectorPath;Lorg/partiql/spi/connector/ConnectorObject;)Lorg/partiql/spi/connector/ConnectorHandle$Obj; - public static synthetic fun copy$default (Lorg/partiql/spi/connector/ConnectorHandle$Obj;Lorg/partiql/spi/connector/ConnectorPath;Lorg/partiql/spi/connector/ConnectorObject;ILjava/lang/Object;)Lorg/partiql/spi/connector/ConnectorHandle$Obj; - public fun equals (Ljava/lang/Object;)Z - public synthetic fun getEntity ()Ljava/lang/Object; - public fun getEntity ()Lorg/partiql/spi/connector/ConnectorObject; - public fun getPath ()Lorg/partiql/spi/connector/ConnectorPath; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class org/partiql/spi/connector/ConnectorMetadata { - public abstract fun getObject (Lorg/partiql/spi/BindingPath;)Lorg/partiql/spi/connector/ConnectorHandle$Obj; -} - -public abstract interface class org/partiql/spi/connector/ConnectorObject { - public abstract fun getPType ()Lorg/partiql/types/PType; - public abstract fun getType ()Lorg/partiql/types/StaticType; -} - -public final class org/partiql/spi/connector/ConnectorObject$DefaultImpls { - public static fun getPType (Lorg/partiql/spi/connector/ConnectorObject;)Lorg/partiql/types/PType; -} - -public final class org/partiql/spi/connector/ConnectorPath : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Lorg/partiql/spi/connector/ConnectorPath$Companion; - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lorg/partiql/spi/connector/ConnectorPath; - public static synthetic fun copy$default (Lorg/partiql/spi/connector/ConnectorPath;Ljava/util/List;ILjava/lang/Object;)Lorg/partiql/spi/connector/ConnectorPath; - public fun equals (Ljava/lang/Object;)Z - public fun forEach (Ljava/util/function/Consumer;)V - public final fun get (I)Ljava/lang/String; - public final fun getSteps ()Ljava/util/List; - public fun hashCode ()I - public fun iterator ()Ljava/util/Iterator; - public static final fun of ([Ljava/lang/String;)Lorg/partiql/spi/connector/ConnectorPath; - public fun spliterator ()Ljava/util/Spliterator; - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/spi/connector/ConnectorPath$Companion { - public final fun of ([Ljava/lang/String;)Lorg/partiql/spi/connector/ConnectorPath; + public static synthetic fun create$default (Lorg/partiql/spi/connector/Connector$Factory;Lcom/amazon/ionelement/api/StructElement;ILjava/lang/Object;)Lorg/partiql/spi/connector/Connector; } -public abstract interface class org/partiql/spi/connector/ConnectorSession { - public abstract fun getQueryId ()Ljava/lang/String; - public abstract fun getUserId ()Ljava/lang/String; +public abstract interface class org/partiql/spi/connector/ConnectorBinding { + public abstract fun getDatum ()Lorg/partiql/eval/value/Datum; } -public final class org/partiql/spi/connector/Constants { - public static final field CONFIG_KEY_CONNECTOR_NAME Ljava/lang/String; - public static final field INSTANCE Lorg/partiql/spi/connector/Constants; +public abstract interface class org/partiql/spi/connector/ConnectorBindings { + public abstract fun getBinding (Lorg/partiql/planner/catalog/Name;)Lorg/partiql/spi/connector/ConnectorBinding; } public abstract interface class org/partiql/spi/fn/Agg { @@ -1596,6 +1502,31 @@ public final class org/partiql/value/io/PartiQLValueWriterBuilder$Companion { public final fun standard ()Lorg/partiql/value/io/PartiQLValueWriterBuilder; } +public final class org/partiql/value/ion/IonDatum : org/partiql/eval/value/Datum { + public static final field Companion Lorg/partiql/value/ion/IonDatum$Companion; + public synthetic fun (Lcom/amazon/ionelement/api/AnyElement;Lorg/partiql/types/PType;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun get (Ljava/lang/String;)Lorg/partiql/eval/value/Datum; + public fun getBigDecimal ()Ljava/math/BigDecimal; + public fun getBigInteger ()Ljava/math/BigInteger; + public fun getBoolean ()Z + public fun getDate ()Lorg/partiql/value/datetime/Date; + public fun getDouble ()D + public fun getFields ()Ljava/util/Iterator; + public fun getInsensitive (Ljava/lang/String;)Lorg/partiql/eval/value/Datum; + public fun getString ()Ljava/lang/String; + public fun getTime ()Lorg/partiql/value/datetime/Time; + public fun getTimestamp ()Lorg/partiql/value/datetime/Timestamp; + public fun getType ()Lorg/partiql/types/PType; + public fun isMissing ()Z + public fun isNull ()Z + public fun iterator ()Ljava/util/Iterator; + public static final fun of (Lcom/amazon/ionelement/api/AnyElement;)Lorg/partiql/eval/value/Datum; +} + +public final class org/partiql/value/ion/IonDatum$Companion { + public final fun of (Lcom/amazon/ionelement/api/AnyElement;)Lorg/partiql/eval/value/Datum; +} + public final class org/partiql/value/util/NumberExtensionsKt { public static final fun coerceNumbers (Ljava/lang/Number;Ljava/lang/Number;)Lkotlin/Pair; } diff --git a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt index cf53465190..17f89d5e79 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt @@ -5,27 +5,15 @@ package org.partiql.planner.catalog */ public interface Catalogs { - /** - * Returns the default catalog. Required. - */ - public fun default(): Catalog - /** * Returns a catalog by name (single identifier). */ - public fun get(name: String, ignoreCase: Boolean = false): Catalog? { - val default = default() - return if (name.equals(default.getName(), ignoreCase)) { - default - } else { - null - } - } + public fun getCatalog(name: String, ignoreCase: Boolean = false): Catalog? /** * Returns a list of all available catalogs. */ - public fun list(): Collection = listOf(default()) + public fun listCatalogs(): Collection = listOf() /** * Factory methods and builder. @@ -37,9 +25,6 @@ public interface Catalogs { @JvmStatic public fun of(catalogs: Collection): Catalogs { - if (catalogs.isEmpty()) { - error("Cannot create `Catalogs` with empty catalogs list.") - } return builder().apply { catalogs.forEach { add(it) } }.build() } @@ -52,40 +37,24 @@ public interface Catalogs { */ public class Builder { - private var default: Catalog? = null private val catalogs = mutableMapOf() - /** - * Sets the default catalog. - */ - public fun default(default: Catalog): Builder = this.apply { - this.default = default - catalogs[default.getName()] = default - } - /** * Adds this catalog, overwriting any existing one with the same name. */ public fun add(catalog: Catalog): Builder = this.apply { - if (default == null) { - this.default = catalog - } catalogs[catalog.getName()] = catalog } public fun build(): Catalogs { - val default = default ?: error("Default catalog is required") - return object : Catalogs { - override fun default(): Catalog = default - - override fun get(name: String, ignoreCase: Boolean): Catalog? { + override fun getCatalog(name: String, ignoreCase: Boolean): Catalog? { + // search if (ignoreCase) { - // search var match: Catalog? = null - for (catalog in list()) { + for (catalog in catalogs.values) { if (catalog.getName().equals(name, ignoreCase = true)) { if (match != null) { // TODO exceptions for ambiguous catalog name lookup @@ -101,7 +70,7 @@ public interface Catalogs { return catalogs[name] } - override fun list(): Collection = catalogs.values + override fun listCatalogs(): Collection = catalogs.values } } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Session.kt b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Session.kt index dc87fb3439..78bdd25d6a 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Session.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Session.kt @@ -1,7 +1,5 @@ package org.partiql.planner.catalog -import org.partiql.spi.connector.ConnectorMetadata - /** * Session is used for authorization and name resolution. */ @@ -19,10 +17,8 @@ public interface Session { /** * Returns the catalog provider for this session. - * - * TODO replace with org.partiql.planner.catalog.Catalogs */ - public fun getCatalogs(): Map + public fun getCatalogs(): Catalogs /** * Returns the current [Namespace]; accessible via the CURRENT_NAMESPACE session variable. @@ -53,7 +49,7 @@ public interface Session { public fun empty(catalog: String): Session = object : Session { override fun getIdentity(): String = "unknown" override fun getCatalog(): String = catalog - override fun getCatalogs(): Map = emptyMap() + override fun getCatalogs(): Catalogs = Catalogs.of() override fun getNamespace(): Namespace = Namespace.empty() } @@ -68,7 +64,7 @@ public interface Session { private var identity: String = "unknown" private var catalog: String? = null - private var catalogs: MutableMap = mutableMapOf() + private var catalogs: Catalogs.Builder = Catalogs.builder() private var namespace: Namespace = Namespace.empty() private var properties: MutableMap = mutableMapOf() @@ -107,20 +103,24 @@ public interface Session { * * TODO replace with org.partiql.planner.catalog.Catalog. */ - public fun catalogs(vararg catalogs: Pair): Builder { - for ((name, metadata) in catalogs) this.catalogs[name] = metadata + public fun catalogs(vararg catalogs: Catalog): Builder { + for (catalog in catalogs) { + this.catalogs.add(catalog) + } return this } public fun build(): Session = object : Session { + private val _catalogs = catalogs.build() + init { require(catalog != null) { "Session catalog must be set" } } override fun getIdentity(): String = identity override fun getCatalog(): String = catalog!! - override fun getCatalogs(): Map = catalogs + override fun getCatalogs(): Catalogs = _catalogs override fun getNamespace(): Namespace = namespace } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Table.kt b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Table.kt index cd48e2e542..f8def32b7f 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Table.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/planner/catalog/Table.kt @@ -20,7 +20,7 @@ public interface Table { /** * The table's name. */ - public fun getName(): String + public fun getName(): Name /** * The table's schema. @@ -37,7 +37,7 @@ public interface Table { */ @JvmStatic public fun of(name: String, schema: PType = PType.dynamic()): Table = object : Table { - override fun getName(): String = name + override fun getName(): Name = Name.of(name) override fun getSchema(): PType = schema } @@ -71,7 +71,7 @@ public interface Table { val name = this.name ?: throw IllegalStateException("Table name cannot be null") // Default implementation return object : Table { - override fun getName(): String = name + override fun getName(): Name = Name.of(name) override fun getSchema(): PType = schema } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingCase.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/BindingCase.kt deleted file mode 100644 index 1001fa36e3..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingCase.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 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.spi - -/** Indicates if the lookup of a particular binding should be case-sensitive or not. */ -public enum class BindingCase { - SENSITIVE, - INSENSITIVE; -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingName.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/BindingName.kt deleted file mode 100644 index 06906a61da..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingName.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 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.spi - -/** - * Encapsulates the data necessary to perform a binding lookup. - */ -public data class BindingName( - public val name: String, - public val case: BindingCase, -) { - - /** - * Compares [name] to [target] using the rules specified by [case]. - */ - public fun matches(target: String): Boolean = when (case) { - BindingCase.SENSITIVE -> target == name - BindingCase.INSENSITIVE -> target.equals(name, ignoreCase = true) - } -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingPath.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/BindingPath.kt deleted file mode 100644 index 53d32603c4..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/BindingPath.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 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.spi - -import org.partiql.spi.connector.ConnectorPath - -/** - * A [BindingPath] represents an SQL-qualified identifier which is composed of case-sensitive and case-insensitive steps. - * - * @property steps - */ -public class BindingPath(public val steps: List) { - - /** - * SQL-99 CNF — Case Normal Form. - */ - public val normalized: List = steps.map { - when (it.case) { - BindingCase.SENSITIVE -> it.name - BindingCase.INSENSITIVE -> it.name.uppercase() - } - } - - /** - * SQL-99 CNF as string. - */ - public val key: String = normalized.joinToString(".") - - /** - * Memoized hashCode for hashing data structures. - */ - private val hashCode = key.hashCode() - - override fun equals(other: Any?): Boolean = (other is BindingPath && other.key == key) - - override fun hashCode(): Int = hashCode - - override fun toString(): String = key - - public fun matches(path: ConnectorPath): Boolean { - if (path.steps.size != steps.size) { - return false - } - for (i in path.steps.indices) { - val t = path.steps[i] - val s = steps[i] - if (!s.matches(t)) { - return false - } - } - return true - } -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt index 29672293f2..865d5762d3 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt @@ -22,7 +22,7 @@ import org.partiql.spi.connector.Connector public interface Plugin { /** - * A [Connector.Factory] is used to instantiate a connector. + * A [Connector.Factory] is used to instantiate a catalog. */ public val factory: Connector.Factory } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt index ee1715bdfa..34bbd4ce57 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt @@ -1,45 +1,26 @@ -/* - * Copyright 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.spi.connector import com.amazon.ionelement.api.StructElement +import com.amazon.ionelement.api.emptyIonStruct +import org.partiql.planner.catalog.Catalog /** - * A [Connector] is used by the PartiQL compiler and engine to implement a catalog. + * A mechanism by which PartiQL can access bindings and catalog metadata. */ public interface Connector { /** - * Returns a [ConnectorMetadata] for the given [ConnectorSession]. - * - * The [ConnectorMetadata] is responsible for accessing catalog metadata. - * - * @param session - * @return + * Returns a [ConnectorBindings] which the engine uses to load values. */ - public fun getMetadata(session: ConnectorSession): ConnectorMetadata + public fun getBindings(): ConnectorBindings /** - * Returns a [ConnectorBindings] which the engine uses to load values. - * - * @return + * Returns a [Catalog] which the planner uses to load catalog metadata. */ - public fun getBindings(): ConnectorBindings + public fun getCatalog(): Catalog /** - * A Plugin leverages a [Factory] to produce a [Connector] which is used for catalog metadata and data access. + * A Plugin leverages a [Factory] to produce a [Connector] which is used for binding and metadata access. */ public interface Factory { @@ -51,10 +32,9 @@ public interface Connector { /** * The connector factory method. * - * @param catalogName * @param config * @return */ - public fun create(catalogName: String, config: StructElement? = null): Connector + public fun create(config: StructElement = emptyIonStruct()): Connector } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBinding.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBinding.kt new file mode 100644 index 0000000000..27eb168aba --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBinding.kt @@ -0,0 +1,14 @@ +package org.partiql.spi.connector + +import org.partiql.eval.value.Datum + +/** + * TODO REMOVE ME IN FAVOR OF EXTENSIONS TO TABLE + */ +public interface ConnectorBinding { + + /** + * Return the datum for this binding. + */ + public fun getDatum(): Datum +} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBindings.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBindings.kt index 396763375a..b03085327f 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBindings.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorBindings.kt @@ -1,33 +1,16 @@ -/* - * Copyright 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.spi.connector -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental +import org.partiql.planner.catalog.Name /** - * [ConnectorBindings] is responsible for managing value bindings in a catalog. + * TODO REMOVE ME IN FAVOR OF SCANNING FROM A CATALOG IMPLEMENTATION + * + * Top-level interface for loading data into the engine. */ public interface ConnectorBindings { /** - * Retrieves a value for the given path. - * - * @param path - * @return + * Get a binding for the given name (or null). */ - @OptIn(PartiQLValueExperimental::class) - public fun getValue(path: ConnectorPath): PartiQLValue + public fun getBinding(name: Name): ConnectorBinding? } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorHandle.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorHandle.kt deleted file mode 100644 index cac161491c..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorHandle.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 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.spi.connector - -/** - * A handle holds a reference to an entity within a catalog. - */ -public sealed class ConnectorHandle { - - /** - * The case-normal-form path to an entity in a catalog. - */ - public abstract val path: ConnectorPath - - /** - * The catalog entity's metadata. - */ - public abstract val entity: T - - public data class Obj( - override val path: ConnectorPath, - override val entity: ConnectorObject, - ) : ConnectorHandle() -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorMetadata.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorMetadata.kt deleted file mode 100644 index 95263166ea..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorMetadata.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 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.spi.connector - -import org.partiql.spi.BindingPath - -/** - * Aids in retrieving relevant Catalog metadata for the purpose of planning and execution. - */ -public interface ConnectorMetadata { - - /** - * Given a [BindingPath], returns a [ConnectorHandle] that corresponds to the longest-available requested path. - * For example, given an object named "Object" located within Catalog "AWS" and Namespace "a".b"."c", a user could - * call [getObject] with the [path] of "a"."b"."c"."Object". The returned [ConnectorHandle] will contain - * the object representation and the matching path: "a"."b"."c"."Object" - * - * As another example, consider an object within a Namespace that may be a Struct with nested attributes. A user could - * call [getObject] with the [path] of "a"."b"."c"."Object"."x". In the Namespace, only object "Object" exists. - * Therefore, this method will return a [ConnectorHandle] with the "Object" representation and the matching - * path: "a"."b"."c"."Object". The returned [ConnectorHandle.path] must be correct for correct - * evaluation. - * - * If the [path] does not correspond to an existing [ConnectorObject], implementers should return null. - */ - public fun getObject(path: BindingPath): ConnectorHandle.Obj? -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorObject.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorObject.kt deleted file mode 100644 index e267d41d93..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorObject.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 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.spi.connector - -import org.partiql.types.PType -import org.partiql.types.StaticType - -/** - * An object's representation within a Catalog. This is used by plugin implementers to store logic in relation to the - * [ConnectorMetadata]. An example implementation of [ConnectorObject] could represent an object of a Catalog that holds - * the serialized [org.partiql.types.StaticType]. - */ -public interface ConnectorObject { - - /** - * Returns the type descriptor of an object in a catalog. - * - * If the handle is unable to produce a [StaticType], implementers should return null. - * - * @return - */ - @Deprecated("This is subject to removal in a future release.") - public fun getType(): StaticType - - /** - * @return the type of the object in a catalog. - */ - public fun getPType(): PType { - // TODO: Remove the default prior to release - return PType.fromStaticType(getType()) - } -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorPath.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorPath.kt deleted file mode 100644 index 45ddb5d620..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorPath.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.partiql.spi.connector - -import java.util.Spliterator -import java.util.function.Consumer - -/** - * Thin wrapper over a list of strings. - * - * @property steps - */ -public data class ConnectorPath(public val steps: List) : Iterable { - - public companion object { - - @JvmStatic - public fun of(vararg steps: String): ConnectorPath = ConnectorPath(steps.toList()) - } - - public operator fun get(index: Int): String = steps[index] - - override fun forEach(action: Consumer?): Unit = steps.forEach(action) - - override fun iterator(): Iterator = steps.iterator() - - override fun spliterator(): Spliterator = steps.spliterator() -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorSession.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorSession.kt deleted file mode 100644 index f3a58db550..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/ConnectorSession.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 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.spi.connector - -/** - * Session details that are exposed to plugin implementers. - */ -public interface ConnectorSession { - public fun getQueryId(): String - public fun getUserId(): String -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Constants.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Constants.kt deleted file mode 100644 index dfc2eab088..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Constants.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.spi.connector - -public object Constants { - public const val CONFIG_KEY_CONNECTOR_NAME: String = "connector_name" -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/value/ion/IonDatum.kt b/partiql-spi/src/main/kotlin/org/partiql/value/ion/IonDatum.kt new file mode 100644 index 0000000000..bc382db92c --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/value/ion/IonDatum.kt @@ -0,0 +1,232 @@ +package org.partiql.value.ion + +import com.amazon.ionelement.api.AnyElement +import com.amazon.ionelement.api.ElementType.BLOB +import com.amazon.ionelement.api.ElementType.BOOL +import com.amazon.ionelement.api.ElementType.CLOB +import com.amazon.ionelement.api.ElementType.DECIMAL +import com.amazon.ionelement.api.ElementType.FLOAT +import com.amazon.ionelement.api.ElementType.INT +import com.amazon.ionelement.api.ElementType.LIST +import com.amazon.ionelement.api.ElementType.NULL +import com.amazon.ionelement.api.ElementType.SEXP +import com.amazon.ionelement.api.ElementType.STRING +import com.amazon.ionelement.api.ElementType.STRUCT +import com.amazon.ionelement.api.ElementType.SYMBOL +import com.amazon.ionelement.api.ElementType.TIMESTAMP +import org.partiql.eval.value.Datum +import org.partiql.eval.value.Field +import org.partiql.types.PType +import org.partiql.value.datetime.Date +import org.partiql.value.datetime.Time +import org.partiql.value.datetime.Timestamp +import java.math.BigDecimal +import java.math.BigInteger + +/** + * A [Datum] implemented over Ion's [AnyElement]. + */ +public class IonDatum private constructor(value: AnyElement, type: PType) : Datum { + + // DO NOT USE FINAL + private var _value = value + private var _type = type + private var _kind = value.type + + /** + * Some encoding of PartiQL values as Ion. + */ + private enum class Annotation(val symbol: String) { + MISSING("\$missing"), + BAG("\$bag"), + DATE("\$date"), + TIME("\$time"), + TIMESTAMP("\$timestamp"), + GRAPH("\$graph"); + + override fun toString(): String = symbol + + companion object { + + @JvmStatic + fun of(value: AnyElement): Annotation? = value.annotations.lastOrNull()?.let { + Annotation.values().find { a -> a.symbol == it } + } + } + } + + public companion object { + + /** + * TODO reader/writer ?? or check annotations + * + * @param value + * @return + */ + @JvmStatic + public fun of(value: AnyElement): Datum { + val tag = Annotation.of(value) + val type = when (value.type) { + NULL -> return when (tag) { + Annotation.MISSING -> Datum.missing() + Annotation.BAG -> Datum.nullValue(PType.bag()) + Annotation.DATE -> Datum.nullValue(PType.date()) + Annotation.TIME -> Datum.nullValue(PType.time(6)) + Annotation.TIMESTAMP -> Datum.nullValue(PType.time(6)) + Annotation.GRAPH -> error("Datum does not support GRAPH type.") + null -> Datum.nullValue() + } + BOOL -> when (tag) { + null -> PType.bool() + else -> error("Unexpected type annotation for Ion BOOL: $tag") + } + INT -> when (tag) { + null -> PType.numeric() + else -> error("Unexpected type annotation for Ion INT: $tag") + } + FLOAT -> when (tag) { + null -> PType.doublePrecision() + else -> error("Unexpected type annotation for Ion FLOAT: $tag") + } + DECIMAL -> when (tag) { + null -> PType.decimal() + else -> error("Unexpected type annotation for Ion DECIMAL: $tag") + } + STRING -> when (tag) { + null -> PType.string() + else -> error("Unexpected type annotation for Ion STRING: $tag") + } + CLOB -> when (tag) { + null -> PType.clob(Int.MAX_VALUE) + else -> error("Unexpected type annotation for Ion CLOB: $tag") + } + BLOB -> when (tag) { + null -> PType.blob(Int.MAX_VALUE) + else -> error("Unexpected type annotation for Ion BLOB: $tag") + } + LIST -> when (tag) { + Annotation.BAG -> PType.bag() + null -> PType.array() + else -> error("Unexpected type annotation for Ion LIST: $tag") + } + STRUCT -> when (tag) { + null -> PType.struct() + Annotation.DATE -> TODO("IonDatum for DATE not supported") + Annotation.TIME -> TODO("IonDatum for TIME not supported") + Annotation.TIMESTAMP -> TODO("IonDatum for TIMESTAMP not supported") + else -> error("Unexpected type annotation for Ion STRUCT: $tag") + } + SEXP -> when (tag) { + null -> PType.sexp() + else -> error("Unexpected type annotation for Ion SEXP: $tag") + } + SYMBOL -> when (tag) { + null -> PType.symbol() + else -> error("Unexpected type annotation for Ion SYMBOL: $tag") + } + TIMESTAMP -> when (tag) { + null -> PType.timestamp(6) + else -> error("Unexpected type annotation for Ion TIMESTAMP: $tag") + } + } + return IonDatum(value, type) + } + } + + override fun getType(): PType = _type + + override fun isNull(): Boolean = _value.isNull + + override fun isMissing(): Boolean = false + + override fun getString(): String = when (_kind) { + SYMBOL -> _value.stringValue + STRING -> _value.stringValue + else -> super.getString() + } + + override fun getBoolean(): Boolean = when (_kind) { + BOOL -> _value.booleanValue + else -> super.getBoolean() + } + + // override fun getBytes(): ByteArray = when (_kind) { + // CLOB -> _value.clobValue.copyOfBytes() + // BLOB -> _value.blobValue.copyOfBytes() + // else -> super.getBytes() + // } + // + // override fun getByte(): Byte { + // return super.getByte() + // } + + override fun getDate(): Date { + TODO("IonDatum does not support DATE") + } + + override fun getTime(): Time { + TODO("IonDatum does not support TIME") + } + + override fun getTimestamp(): Timestamp { + TODO("IonDatum does not support TIMESTAMP") + } + + override fun getBigInteger(): BigInteger = when (_kind) { + INT -> _value.bigIntegerValue + else -> super.getBigInteger() + } + + override fun getDouble(): Double = when (_kind) { + FLOAT -> _value.doubleValue + else -> super.getDouble() + } + + override fun getBigDecimal(): BigDecimal = when (_kind) { + DECIMAL -> _value.decimalValue.bigDecimalValue() + else -> super.getBigDecimal() + } + + override fun iterator(): MutableIterator = when (_kind) { + LIST -> _value.listValues.map { of(it) }.toMutableList().iterator() + SEXP -> _value.sexpValues.map { of(it) }.toMutableList().iterator() + else -> super.iterator() + } + + override fun getFields(): MutableIterator { + if (_kind != STRUCT) { + return super.getFields() + } + return _value.structFields + .map { Field.of(it.name, of(it.value)) } + .toMutableList() + .iterator() + } + + override fun get(name: String): Datum { + if (_kind != STRUCT) { + return super.get(name) + } + // TODO handle multiple/ambiguous field names? + val v = _value.asStruct().getOptional(name) + return if (v == null) { + Datum.missing() + } else { + of(v) + } + } + + override fun getInsensitive(name: String): Datum { + if (_kind != STRUCT) { + return super.get(name) + } + // TODO handle multiple/ambiguous field names? + val struct = _value.asStruct() + for (field in struct.fields) { + if (field.name.equals(name, ignoreCase = true)) { + return of(field.value) + } + } + return Datum.missing() + } +} diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalBindings.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalBindings.kt new file mode 100644 index 0000000000..9a4efbad21 --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalBindings.kt @@ -0,0 +1,10 @@ +package org.partiql.plugins.local + +import org.partiql.planner.catalog.Name +import org.partiql.spi.connector.ConnectorBinding +import org.partiql.spi.connector.ConnectorBindings + +internal object LocalBindings : ConnectorBindings { + + override fun getBinding(name: Name): ConnectorBinding? = null +} diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt index 207b5b9a25..7264c1693c 100644 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt @@ -1,109 +1,109 @@ -/* - * Copyright 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.plugins.local -import com.amazon.ionelement.api.loadSingleElement -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.types.StaticType -import java.io.File +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Function +import org.partiql.planner.catalog.Identifier +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session +import org.partiql.planner.catalog.Table import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.notExists -private sealed class FsTree(val name: String) { +/** + * Implementation of [Catalog] where dirs are namespaces and files are table metadata. + */ +internal class LocalCatalog( + private val name: String, + private val root: Path, +) : Catalog { - // "Directory" node - class D(name: String, val children: List) : FsTree(name) + private companion object { + private const val EXT = ".ion" + } - // Type node - class T(name: String, val type: StaticType) : FsTree(name) -} + init { + assert(root.isDirectory()) { "LocalNamespace must be a directory" } + } -/** - * Build a memoized catalog tree from local schema definitions. - */ -internal class LocalCatalog private constructor(private val root: FsTree.D) { + override fun getName(): String { + return name + } + + override fun getTable(session: Session, name: Name): Table? { + val path = toPath(name.getNamespace()).resolve(name.getName() + EXT) + if (path.notExists() || path.isDirectory()) { + return null + } + return LocalTable(name, path) + } /** - * Search the tree for the type. + * TODO this doesn't handle ambiguous binding errors or back-tracking for longest prefix searching. */ - public fun lookup(path: BindingPath): LocalObject? { - val match = mutableListOf() - var curr: FsTree? = root - for (step in path.steps) { - if (curr == null) return null - when (curr) { - is FsTree.T -> break - is FsTree.D -> { - curr = curr.children.firstOrNull { step.matches(it.name) } - if (curr != null) match.add(curr.name) + override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? { + val matched = mutableListOf() + var curr = root + for (part in identifier) { + var next: Path? = null + for (child in curr.toFile().listFiles()!!) { + // TODO ambiguous bindings errors + if (part.matches(child.nameWithoutExtension)) { + next = child.toPath() + break } } + if (next == null) { + break + } + curr = next + matched.add(curr.nameWithoutExtension) } - // All steps matched and we're at a leaf - if (curr is FsTree.T) { - return LocalObject(match, curr.type) + // Does this table exist? + val path = curr + if (path.notExists() || path.isDirectory()) { + return null } - return null + // Remove the extension + val name = Name.of(matched) + return Table.Handle(name, LocalTable(name, path)) } - /** - * Provide a list of all objects in this catalog. - */ - public fun listObjects(): List = sequence { search(emptyList(), root) }.toList() - - private suspend fun SequenceScope.search(acc: List, node: FsTree) = - when (node) { - is FsTree.D -> search(acc, node) - is FsTree.T -> search(acc, node) - } - - private suspend fun SequenceScope.search(acc: List, node: FsTree.D) { - val steps = acc + BindingName(node.name, BindingCase.INSENSITIVE) - for (child in node.children) { - search(steps, child) + override fun listTables(session: Session, namespace: Namespace): Collection { + val path = toPath(namespace) + if (path.notExists()) { + // throw exception? + return emptyList() } + return super.listTables(session, namespace) } - private suspend fun SequenceScope.search(acc: List, node: FsTree.T) { - val steps = acc + BindingName(node.name, BindingCase.INSENSITIVE) - this.yield(BindingPath(steps)) + override fun listNamespaces(session: Session, namespace: Namespace): Collection { + val path = toPath(namespace) + if (path.notExists() || !path.isDirectory()) { + // throw exception? + return emptyList() + } + // List all child directories + return path.toFile() + .listFiles()!! + .filter { it.isDirectory } + .map { toNamespace(it.toPath()) } } - companion object { - - /** - * Builds a FsTree from the given root. - */ - public fun load(root: Path): LocalCatalog = LocalCatalog(root.toFile().tree() as FsTree.D) - - private fun File.tree(): FsTree = when (this.isDirectory) { - true -> d() - else -> t() - } + override fun getFunctions(session: Session, name: Name): Collection = emptyList() - private fun File.d(): FsTree.D { - val children = listFiles()!!.map { it.tree() } - return FsTree.D(name, children) + private fun toPath(namespace: Namespace): Path { + var curr = root + for (level in namespace) { + curr = curr.resolve(level) } + return curr + } - private fun File.t(): FsTree.T { - val text = readText() - val ion = loadSingleElement(text) - val type = ion.toStaticType() - return FsTree.T(nameWithoutExtension, type) - } + private fun toNamespace(path: Path): Namespace { + return Namespace.of(path.relativize(root).map { it.toString() }) } } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt index 9a08b03c7d..17ee7c3292 100644 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt @@ -15,15 +15,12 @@ package org.partiql.plugins.local import com.amazon.ionelement.api.StructElement -import org.partiql.spi.BindingPath +import org.partiql.planner.catalog.Catalog import org.partiql.spi.connector.Connector import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorPath -import org.partiql.spi.connector.ConnectorSession import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.isDirectory import kotlin.io.path.notExists /** @@ -37,70 +34,54 @@ import kotlin.io.path.notExists * root: "/Users/me/some/root/directory" * } * ``` - * - * @property catalogRoot Catalog root path - * @property catalogName Catalog name - * @property config Catalog configuration */ -public class LocalConnector( - private val catalogRoot: Path, - private val catalogName: String, - private val config: StructElement, +public class LocalConnector private constructor( + private val name: String, + private val root: Path, ) : Connector { + private val catalog = LocalCatalog(name, root) + public companion object { + public const val CONNECTOR_NAME: String = "local" - public const val ROOT_KEY: String = "root" - } - private val metadata = Metadata(catalogRoot) + public const val ROOT_KEY: String = "root" - // not yet defined in SPI - public fun listObjects(): List = metadata.listObjects() + @JvmStatic + public fun builder(): Builder = Builder() - override fun getMetadata(session: ConnectorSession): ConnectorMetadata = metadata + public class Builder internal constructor() { - override fun getBindings(): ConnectorBindings { - TODO("Not yet implemented") - } + private var name: String? = null - internal class Factory : Connector.Factory { + private var root: Path? = null - private val default: Path = Paths.get(System.getProperty("user.home")).resolve(".partiql/local") + public fun name(name: String): Builder = apply { this.name = name } - override val name: String = CONNECTOR_NAME + public fun root(root: Path): Builder = apply { this.root = root } - override fun create(catalogName: String, config: StructElement?): Connector { - assert(config != null) { "Local plugin requires non-null config" } - val root = config!!.getOptional(ROOT_KEY)?.stringValueOrNull?.let { Paths.get(it) } - val catalogRoot = root ?: default - if (catalogRoot.notExists()) { - error("Invalid catalog `$catalogRoot`") - } - return LocalConnector(catalogRoot, catalogName, config) + public fun build(): LocalConnector = LocalConnector(name!!, root!!) } } - public class Metadata(root: Path) : ConnectorMetadata { + override fun getBindings(): ConnectorBindings = LocalBindings - /** - * TODO watch root for changes and rebuild catalog if needed. - */ - // private val watcher = FileSystems.getDefault().newWatchService() + override fun getCatalog(): Catalog = catalog - /** - * Cached catalog - */ - private var catalog = LocalCatalog.load(root) + internal class Factory : Connector.Factory { - override fun getObject(path: BindingPath): ConnectorHandle.Obj? { - val value = catalog.lookup(path) ?: return null - return ConnectorHandle.Obj( - path = ConnectorPath(value.path), - entity = value, - ) - } + override val name: String = CONNECTOR_NAME - internal fun listObjects(): List = catalog.listObjects() + override fun create(config: StructElement): Connector { + val root = config.getOptional(ROOT_KEY)?.stringValueOrNull?.let { Paths.get(it) } + if (root == null) { + error("Root cannot be null") + } + if (root.notExists() || !root.isDirectory()) { + error("Invalid catalog `$root`") + } + return LocalConnector("default", root) + } } } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalObject.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalObject.kt deleted file mode 100644 index dda6033022..0000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalObject.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 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.plugins.local - -import org.partiql.spi.connector.ConnectorObject -import org.partiql.types.StaticType - -/** - * Associate a resolved path with a [StaticType] - * - * @property path - * @property type - */ -internal class LocalObject( - val path: List, - private val type: StaticType, -) : ConnectorObject { - override fun getType(): StaticType = type -} diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalTable.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalTable.kt new file mode 100644 index 0000000000..70931c66dc --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalTable.kt @@ -0,0 +1,52 @@ +/* + * Copyright 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.plugins.local + +import com.amazon.ion.system.IonReaderBuilder +import com.amazon.ionelement.api.loadSingleElement +import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Table +import org.partiql.spi.connector.ConnectorBinding +import org.partiql.types.PType +import org.partiql.types.StaticType +import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.reader + +/** + * Associate a resolved path with a [StaticType] + */ +internal class LocalTable( + private val name: Name, + private val path: Path, +) : Table, ConnectorBinding { + + init { + assert(!path.isDirectory()) { "LocalTable path must be a file." } + } + + override fun getName(): Name = name + + override fun getSchema(): PType { + val reader = IonReaderBuilder.standard().build(path.reader()) + val element = loadSingleElement(reader) + val staticType = element.toStaticType() + return PType.fromStaticType(staticType) + } + + // TODO for now files are `type` only. + override fun getDatum(): Datum = Datum.nullValue() +} diff --git a/plugins/partiql-local/src/test/kotlin/org/partiql/plugins/local/LocalConnectorMetadataTests.kt b/plugins/partiql-local/src/test/kotlin/org/partiql/plugins/local/LocalConnectorMetadataTests.kt deleted file mode 100644 index 11397f3348..0000000000 --- a/plugins/partiql-local/src/test/kotlin/org/partiql/plugins/local/LocalConnectorMetadataTests.kt +++ /dev/null @@ -1,150 +0,0 @@ -package org.partiql.plugins.local - -import org.junit.jupiter.api.Test -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorPath -import org.partiql.types.BagType -import org.partiql.types.IntType -import org.partiql.types.StaticType -import org.partiql.types.StructType -import org.partiql.types.TupleConstraint -import java.nio.file.Paths -import kotlin.test.assertEquals - -class LocalConnectorMetadataTests { - - private val catalogUrl = - LocalConnectorMetadataTests::class.java.classLoader.getResource("catalogs/local") ?: error("Couldn't be found") - - private val metadata = LocalConnector.Metadata(Paths.get(catalogUrl.path)) - - @Test - fun getTable() { - // Prepare - val requested = BindingPath( - listOf( - BindingName("data", BindingCase.INSENSITIVE), - BindingName("records", BindingCase.INSENSITIVE), - ) - ) - val expected = BagType( - StructType( - fields = mapOf( - "id" to StaticType.INT, - "path" to StaticType.STRING - ), - contentClosed = true, - constraints = setOf(TupleConstraint.Ordered, TupleConstraint.Open(false)) - ) - ) - - // Act - val handle = metadata.getObject(requested)!! - val descriptor = handle.entity.getType() - - // Assert - assert(requested.matches(handle.path)) - assert(expected == descriptor) { - buildString { - appendLine("Expected: $expected") - appendLine("Actual: $descriptor") - } - } - } - - @Test - fun getStruct() { - // Prepare - val requested = BindingPath( - listOf( - BindingName("data", BindingCase.INSENSITIVE), - BindingName("struct", BindingCase.INSENSITIVE), - BindingName("nested", BindingCase.INSENSITIVE), - ) - ) - val expectedPath = ConnectorPath.of("data", "struct") - val expected = - StructType( - contentClosed = true, - fields = mapOf( - "id" to IntType(), - "nested" to StructType( - contentClosed = true, - fields = mapOf( - "nested_id" to IntType() - ), - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered - ) - ) - ), - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered - ) - ) - - // Act - val handle = metadata.getObject(requested)!! - val descriptor = handle.entity.getType() - - // Assert - assertEquals(expectedPath, handle.path) - assert(expected == descriptor) { - buildString { - appendLine("Expected: $expected") - appendLine("Actual: $descriptor") - } - } - } - - @Test - fun failToFindObject() { - // Prepare - val requested = BindingPath( - listOf( - BindingName("data", BindingCase.INSENSITIVE), - BindingName("unknown", BindingCase.INSENSITIVE), - ) - ) - - // Act - val handle = metadata.getObject(requested) - assertEquals(null, handle) - } - - @Test - fun failToFindSchema() { - // Prepare - val requested = BindingPath( - listOf( - BindingName("unknown", BindingCase.INSENSITIVE), - BindingName("records", BindingCase.INSENSITIVE), - ) - ) - - // Act - val handle = metadata.getObject(requested) - assertEquals(null, handle) - } - - @Test - fun failToFindCaseSensitiveObject() { - // Prepare - val requested = BindingPath( - listOf( - BindingName("data", BindingCase.INSENSITIVE), - BindingName("RECORDS", BindingCase.SENSITIVE), - ) - ) - - // Act - val handle = metadata.getObject(requested) - assertEquals(null, handle) - } -} diff --git a/plugins/partiql-local/src/test/resources/catalogs/local/data/records.ion b/plugins/partiql-local/src/test/resources/catalogs/local/data/records.ion deleted file mode 100644 index 884915fa5d..0000000000 --- a/plugins/partiql-local/src/test/resources/catalogs/local/data/records.ion +++ /dev/null @@ -1,17 +0,0 @@ -{ - type: "bag", - items: { - type: "struct", - constraints: [ ordered, closed ], - fields: [ - { - name: "id", - type: "int", - }, - { - name: "path", - type: "string", - }, - ], - }, -} diff --git a/plugins/partiql-local/src/test/resources/catalogs/local/data/struct.ion b/plugins/partiql-local/src/test/resources/catalogs/local/data/struct.ion deleted file mode 100644 index cba96f5e2d..0000000000 --- a/plugins/partiql-local/src/test/resources/catalogs/local/data/struct.ion +++ /dev/null @@ -1,31 +0,0 @@ -{ - type: "struct", - constraints: [ - closed, - unique, - ordered - ], - fields: [ - { - name: "id", - type: "int", - }, - { - name: "nested", - type: { - type: "struct", - constraints: [ - closed, - unique, - ordered - ], - fields: [ - { - name: "nested_id", - type: "int", - }, - ], - }, - }, - ], -} diff --git a/plugins/partiql-memory/README.md b/plugins/partiql-memory/README.md index e26a74f27a..84dbca8b7e 100644 --- a/plugins/partiql-memory/README.md +++ b/plugins/partiql-memory/README.md @@ -1,36 +1,19 @@ -# PartiQL In-Memory Plugin +# PartiQL Memory Plugin -This is a PartiQL plugin for in-memory DB. The primary purpose of this plugin is for testing. +This is a plugin for defining namespaces and tables in memory. -## Provider - -The plugin is backed by a catalog provider. This enables use to easily modify a catalog for testing. - -```kotlin -val provider = MemoryCatalog.Provider() -provider[catalogName] = MemoryCatalog.of( - t1 to StaticType.INT2, - ... -) -``` - -## Catalog path - -The in-memory connector can handle arbitrary depth catalog path: +## Usage ```kotlin -val provider = MemoryCatalog.Provider() -provider[catalogName] = MemoryCatalog.of( - "schema.tbl" to StaticType.INT2, -) -``` - -The full path is `catalogName.schema.tbl` - -The lookup logic is identical to localPlugin. - +// define the data and types. +val catalog = MemoryCatalog.builder() + .name("hello") + .defineTable("pi", MemoryTable( + type = PType.real(), + data = ionFloat(3.14), + )) + .build() + +// create a connector to be used in a session +val connector = MemoryConnector.from(catalog) ``` -|_ catalogName - |_ schema - |_ tbl.ion -``` \ No newline at end of file diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryBindings.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryBindings.kt deleted file mode 100644 index 24cec718cd..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryBindings.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.partiql.plugins.memory - -import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorPath -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.missingValue - -@OptIn(PartiQLValueExperimental::class) -public class MemoryBindings(private val catalog: MemoryCatalog) : ConnectorBindings { - - override fun getValue(path: ConnectorPath): PartiQLValue { - return catalog.get(path)?.getValue() ?: missingValue() - } -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt deleted file mode 100644 index f138165cb1..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 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://www.apache.org/licenses/LICENSE-2.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.plugins.memory - -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorPath - -/** - * A basic catalog implementation used in testing. - * - * Note, this treats both delimited (quoted) and simple (unquoted) identifiers as case-sensitive. - * - * @property name - */ -public class MemoryCatalog(public val name: String) { - - private val root: Tree.Dir = Tree.Dir(name) - - /** - * Inserts the `obj` at the given path, creating any non-existent intermediate directories as necessary. - * - * @param path Catalog absolute path. - * @param obj Object to insert. - */ - public fun insert(path: BindingPath, obj: MemoryObject) { - val dir = path.steps.dropLast(1) - val binding = path.steps.last() - var curr: Tree.Dir = root - // create any non-existent intermediate directories - dir.forEach { - curr = curr.mkdir(it.name) - } - // insert entity in current dir - curr.insert( - binding.name, - ConnectorHandle.Obj( - path = ConnectorPath(path.steps.map { it.name }), - entity = obj, - ) - ) - } - - /** - * Finds a [MemoryObject] in the catalog, returning `null` if it does not exist. - * - * 1) If multiple paths are found, return the longest match. - * 2) If the path is ambiguous, this will throw an error. - * - * This follows the scoping rules in section 10 of the PartiQL Specification. - * - * @param path - * @return - */ - public fun find(path: BindingPath): ConnectorHandle.Obj? { - var currItems = listOf() - var currDirs = listOf(root) - for (name in path.steps) { - val nextItems = mutableListOf() - val nextDirs = mutableListOf() - currDirs.flatMap { it.find(name) }.forEach { - when (it) { - is Tree.Dir -> nextDirs.add(it) - is Tree.Item -> nextItems.add(it) - } - } - currItems = if (nextItems.isEmpty()) currItems else nextItems - currDirs = if (nextDirs.isEmpty()) break else nextDirs - } - return when (currItems.size) { - 0 -> null - 1 -> currItems.first().obj - else -> error("Ambiguous binding $path, found multiple matching bindings") - } - } - - /** - * Gets a [MemoryObject] in the catalog, returning `null` if it does not exist. - * - * @param path - * @return - */ - public fun get(path: ConnectorPath): MemoryObject? { - var curr: Tree.Dir = root - for (i in path.steps.indices) { - val next = curr.get(path.steps[i]) ?: break - when (next) { - is Tree.Dir -> curr = next - is Tree.Item -> { - if (i == path.steps.size - 1) { - return next.obj.entity as? MemoryObject - } - break - } - } - } - return null - } - - public companion object { - - @JvmStatic - public fun builder(): MemoryCatalogBuilder = MemoryCatalogBuilder() - } - - private sealed interface Tree { - - /** - * The catalog entry's case-sensitive binding name. - */ - val name: String - - /** - * Dir is similar to an SQL Schema as well as a Unix directory. - * - * @property name - */ - class Dir(override val name: String) : Tree { - - private val children: MutableMap = mutableMapOf() - - /** - * Creates a directory, returning the new directory. - * - * 1) If a subdirectory with this name already exists, no action. - * 2) If an entity with this name already exists, error. - * - * @param name - * @return - */ - fun mkdir(name: String): Dir { - var child = children[name] - if (child is Item) { - error("File exists: `$name`") - } - if (child == null) { - child = Dir(name) - children[name] = child - } - return child as Dir - } - - /** - * Inserts an entity in this directory, return the new entity. - * - * 1) If an entity with this name already exists, overwrite. - * 2) If a subdirectory with this name already exists, error. - * - * @param name - * @param obj - * @return - */ - fun insert(name: String, obj: ConnectorHandle.Obj): Item { - if (children[name] is Dir) { - error("Directory exists: `$name`") - } - val child = Item(name, obj) - children[name] = child - return child - } - - /** - * List directory contents. - * - * @return - */ - fun ls(): Collection = children.values - - /** - * Find all directory entries by binding naming. - * - * @param name - * @return - */ - fun find(name: BindingName): List = ls().filter { name.matches(it.name) } - - /** - * Get all directory entries by name. - * - * @param name - * @return - */ - fun get(name: String): Tree? = children[name] - } - - /** - * Item represents a type-annotated global binding in a catalog. - * - * @property name - * @property obj - */ - class Item(override val name: String, val obj: ConnectorHandle.Obj) : Tree - } -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalogBuilder.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalogBuilder.kt deleted file mode 100644 index e7ba9c799c..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalogBuilder.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 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://www.apache.org/licenses/LICENSE-2.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.plugins.memory - -import com.amazon.ionelement.api.IonElement -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.types.StaticType -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.io.PartiQLValueIonReaderBuilder - -/** - * Utility class for creating a MemoryCatalog. - */ -public class MemoryCatalogBuilder { - - private var _name: String? = null - private var _items: MutableList> = mutableListOf() - - public fun name(name: String): MemoryCatalogBuilder = this.apply { this._name = name } - - /** - * This is a simple `dot` delimited utility for adding type definitions. - * - * At some point, this will support adding values as well as paths. - * - * @param name - * @param type - */ - @OptIn(PartiQLValueExperimental::class) - @JvmOverloads - public fun define(name: String, type: StaticType = StaticType.ANY, value: IonElement? = null): MemoryCatalogBuilder = this.apply { - val path = BindingPath(name.split(".").map { BindingName(it, BindingCase.SENSITIVE) }) - val pValue = value?.let { elt -> - PartiQLValueIonReaderBuilder.standard().build(elt).read() - } - val obj = MemoryObject(type, value = pValue) - _items.add(path to obj) - } - - public fun build(): MemoryCatalog { - val name = _name ?: error("MemoryCatalog must have a name") - val catalog = MemoryCatalog(name) - for (item in _items) { catalog.insert(item.first, item.second) } - return catalog - } -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt index 9ad57afc16..735c5067f1 100644 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt @@ -16,30 +16,105 @@ package org.partiql.plugins.memory import com.amazon.ionelement.api.StructElement +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Identifier +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Session +import org.partiql.planner.catalog.Table import org.partiql.spi.connector.Connector +import org.partiql.spi.connector.ConnectorBinding import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession +import org.partiql.types.PType +import org.partiql.types.StaticType /** * This is a plugin used for testing and is not a versioned API per semver. */ -public class MemoryConnector(private val catalog: MemoryCatalog) : Connector { - - private val bindings = MemoryBindings(catalog) +public class MemoryConnector private constructor( + private val name: String, + private val tables: Map, +) : Connector { override fun getBindings(): ConnectorBindings = bindings - override fun getMetadata(session: ConnectorSession): ConnectorMetadata = MemoryMetadata(catalog) + override fun getCatalog(): Catalog = catalog - internal class Factory(private val catalogs: List) : Connector.Factory { + /** + * For use with ServiceLoader to instantiate a connector from an Ion config. + */ + internal class Factory : Connector.Factory { override val name: String = "memory" - override fun create(catalogName: String, config: StructElement?): MemoryConnector { - val catalog = catalogs.firstOrNull { it.name == catalogName } - ?: error("Catalog $catalogName is not registered in the MemoryPlugin") - return MemoryConnector(catalog) + override fun create(config: StructElement): Connector { + TODO("Instantiation of a MemoryConnector via the factory is currently not supported") + } + } + + public companion object { + + @JvmStatic + public fun builder(): Builder = Builder() + } + + public class Builder internal constructor() { + + private var name: String? = null + private var tables: MutableMap = mutableMapOf() + + public fun name(name: String): Builder = apply { this.name = name } + + // TODO REMOVE AFTER CREATE TABLE IS ADDED TO CATALOG + public fun define(name: String, type: StaticType): Builder { + val table = MemoryTable.empty(name, PType.fromStaticType(type)) + return define(table) + } + + // TODO REMOVE AFTER CREATE TABLE IS ADDED TO CATALOG + public fun define(table: MemoryTable): Builder = apply { tables[table.getName()] = table } + + public fun build(): MemoryConnector = MemoryConnector(name!!, tables) + } + + /** + * Implement [ConnectorBindings] over the tables map. + */ + private val bindings = object : ConnectorBindings { + override fun getBinding(name: Name): ConnectorBinding? = tables[name] + } + + /** + * Implement [Catalog] over the tables map. + */ + private val catalog = object : Catalog { + + override fun getName(): String = name + + override fun getTable(session: Session, name: Name): Table? { + if (name.hasNamespace()) { + error("MemoryCatalog does not support namespaces") + } + return tables[name] + } + + /** + * TODO implement "longest match" on identifier searching. + */ + override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? { + // TODO memory connector does not handle qualified identifiers and longest match + val first = identifier.first() + for ((name, table) in tables) { + val str = name.getName() // only use single identifiers for now + if (first.matches(str)) { + // TODO emit errors on ambiguous table names + return Table.Handle(name, table) + } + } + return super.getTableHandle(session, identifier) + } + + override fun listTables(session: Session): Collection { + return tables.keys.map { it } } } } diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryMetadata.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryMetadata.kt deleted file mode 100644 index 9e1182fca9..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryMetadata.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.partiql.plugins.memory - -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata - -internal class MemoryMetadata(private val catalog: MemoryCatalog) : ConnectorMetadata { - - override fun getObject(path: BindingPath): ConnectorHandle.Obj? { - return catalog.find(path) - } -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryObject.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryObject.kt deleted file mode 100644 index 881ec291aa..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryObject.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 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://www.apache.org/licenses/LICENSE-2.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.plugins.memory - -import org.partiql.spi.connector.ConnectorObject -import org.partiql.types.StaticType -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental - -@OptIn(PartiQLValueExperimental::class) -public class MemoryObject( - private val type: StaticType, - private val value: PartiQLValue? = null, -) : ConnectorObject { - - public fun getValue(): PartiQLValue? = value - - override fun getType(): StaticType = type -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt index d282dd1be8..21d4cba488 100644 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt @@ -18,7 +18,6 @@ package org.partiql.plugins.memory import org.partiql.spi.Plugin import org.partiql.spi.connector.Connector -public class MemoryPlugin(catalogs: List) : Plugin { - - override val factory: Connector.Factory = MemoryConnector.Factory(catalogs) +public object MemoryPlugin : Plugin { + override val factory: Connector.Factory = MemoryConnector.Factory() } diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryTable.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryTable.kt new file mode 100644 index 0000000000..ea14d28833 --- /dev/null +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryTable.kt @@ -0,0 +1,75 @@ +package org.partiql.plugins.memory + +import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Table +import org.partiql.spi.connector.ConnectorBinding +import org.partiql.types.PType + +public class MemoryTable private constructor( + private val name: Name, + private val schema: PType, + private val datum: Datum, +) : Table, ConnectorBinding { + + override fun getName(): Name = name + override fun getSchema(): PType = schema + override fun getDatum(): Datum = datum + + public companion object { + + @JvmStatic + public fun empty(name: String): MemoryTable = MemoryTable( + name = Name.of(name), + schema = PType.dynamic(), + datum = Datum.nullValue(), + ) + + /** + * Create an empty table with dynamic schema. + */ + @JvmStatic + public fun empty(name: Name): MemoryTable = MemoryTable( + name = name, + schema = PType.dynamic(), + datum = Datum.nullValue(), + ) + + @JvmStatic + public fun empty(name: String, schema: PType): MemoryTable = MemoryTable( + name = Name.of(name), + schema = schema, + datum = Datum.nullValue(), + ) + + /** + * Create an empty table with known schema. + */ + @JvmStatic + public fun empty(name: Name, schema: PType): MemoryTable = MemoryTable( + name = name, + schema = schema, + datum = Datum.nullValue(), + ) + + /** + * Create a table from a Datum with dynamic schema. + */ + @JvmStatic + public fun of(name: Name, datum: Datum): MemoryTable = MemoryTable( + name = name, + schema = PType.dynamic(), + datum = datum, + ) + + /** + * Create a table from a Datum with known schema. + */ + @JvmStatic + public fun of(name: Name, datum: Datum, schema: PType): MemoryTable = MemoryTable( + name = name, + schema = schema, + datum = datum, + ) + } +} diff --git a/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryCatalogTest.kt b/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryCatalogTest.kt deleted file mode 100644 index 210b7779ac..0000000000 --- a/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryCatalogTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -package org.partiql.plugins.memory - -import org.junit.jupiter.api.Test -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorPath -import org.partiql.types.BagType -import org.partiql.types.StaticType -import org.partiql.types.StructType - -class MemoryCatalogTest { - - companion object { - - private val catalog = MemoryCatalog.builder() - .name("test") - .define("a", StaticType.INT2) - .define( - "struct", - StructType( - fields = listOf(StructType.Field("a", StaticType.INT2)) - ) - ) - .define( - "schema.tbl", - BagType( - StructType( - fields = listOf(StructType.Field("a", StaticType.INT2)) - ) - ) - ) - .build() - } - - @Test - fun getValue() { - val requested = BindingPath( - listOf( - BindingName("a", BindingCase.INSENSITIVE) - ) - ) - val expected = StaticType.INT2 - val handle = catalog.find(requested) - val descriptor = handle!!.entity.getType() - assert(requested.matches(handle.path)) - assert(expected == descriptor) - } - - @Test - fun getCaseSensitiveValueShouldFail() { - val requested = BindingPath( - listOf( - BindingName("A", BindingCase.SENSITIVE) - ) - ) - val handle = catalog.find(requested) - assert(null == handle) - } - - @Test - fun accessStruct() { - val requested = BindingPath( - listOf( - BindingName("struct", BindingCase.INSENSITIVE), - BindingName("a", BindingCase.INSENSITIVE) - ) - ) - val handle = catalog.find(requested) - val descriptor = handle!!.entity.getType() - val expectConnectorPath = ConnectorPath.of("struct") - val expectedObjectType = StructType(fields = listOf(StructType.Field("a", StaticType.INT2))) - - assert(expectConnectorPath == handle.path) - assert(expectedObjectType == descriptor) - } - - @Test - fun pathNavigationSuccess() { - val requested = BindingPath( - listOf( - BindingName("schema", BindingCase.INSENSITIVE), - BindingName("tbl", BindingCase.INSENSITIVE) - ) - ) - val handle = catalog.find(requested) - val descriptor = handle!!.entity.getType() - val expectedObjectType = BagType(StructType(fields = listOf(StructType.Field("a", StaticType.INT2)))) - - assert(requested.matches(handle.path)) - assert(expectedObjectType == descriptor) - } - - @Test - fun pathNavigationSuccess2() { - val requested = BindingPath( - listOf( - BindingName("schema", BindingCase.INSENSITIVE), - BindingName("tbl", BindingCase.INSENSITIVE), - BindingName("a", BindingCase.INSENSITIVE) - ) - ) - val handle = catalog.find(requested) - val descriptor = handle!!.entity.getType() - val expectedObjectType = BagType(StructType(fields = listOf(StructType.Field("a", StaticType.INT2)))) - val expectConnectorPath = ConnectorPath.of("schema", "tbl") - - assert(expectConnectorPath == handle.path) - assert(expectedObjectType == descriptor) - } -} diff --git a/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryConnectorTest.kt b/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryConnectorTest.kt new file mode 100644 index 0000000000..6ee2e4f64c --- /dev/null +++ b/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryConnectorTest.kt @@ -0,0 +1,21 @@ +package org.partiql.plugins.memory + +import org.junit.jupiter.api.Test +import org.partiql.planner.catalog.Session + +class MemoryConnectorTest { + + @Test + fun sanity() { + val session = Session.empty("") + val connector = MemoryConnector.builder() + .name("default") + .define(MemoryTable.empty("a")) + .define(MemoryTable.empty("b")) + .define(MemoryTable.empty("c")) + .build() + val catalog = connector.getCatalog() + assert(catalog.listTables(session).size == 3) + assert(catalog.listNamespaces(session).size == 0) + } +} diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt deleted file mode 100644 index c8b736c236..0000000000 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt +++ /dev/null @@ -1,188 +0,0 @@ -// ktlint-disable filename -@file:Suppress("ClassName") - -package org.partiql.plugin.internal.fn.scalar - -import org.partiql.errors.DataException -import org.partiql.errors.TypeCheckException -import org.partiql.spi.function.PartiQLFunction -import org.partiql.spi.function.PartiQLFunctionExperimental -import org.partiql.types.function.FunctionParameter -import org.partiql.types.function.FunctionSignature -import org.partiql.value.DateValue -import org.partiql.value.Int32Value -import org.partiql.value.Int64Value -import org.partiql.value.IntValue -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.DATE -import org.partiql.value.PartiQLValueType.INT -import org.partiql.value.PartiQLValueType.INT32 -import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.TIMESTAMP -import org.partiql.value.TimestampValue -import org.partiql.value.check -import org.partiql.value.dateValue -import org.partiql.value.timestampValue - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT32_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - dateValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = interval.toInt64().value!! - dateValue(datetimeValue.plusYears(intervalValue)) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT64_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - dateValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = interval.value!! - dateValue(datetimeValue.plusYears(intervalValue)) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT_DATE__DATE : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = DATE, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", DATE), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - dateValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = try { interval.toInt64().value!! } catch (e: DataException) { throw TypeCheckException() } - dateValue(datetimeValue.plusYears(intervalValue)) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT32_TIMESTAMP__TIMESTAMP : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIMESTAMP, - parameters = listOf( - FunctionParameter("interval", INT32), - FunctionParameter("datetime", TIMESTAMP), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - timestampValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = interval.toInt64().value!! - timestampValue(datetimeValue.plusYears(intervalValue)) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT64_TIMESTAMP__TIMESTAMP : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIMESTAMP, - parameters = listOf( - FunctionParameter("interval", INT64), - FunctionParameter("datetime", TIMESTAMP), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - timestampValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = interval.value!! - timestampValue(datetimeValue.plusYears(intervalValue)) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_DATE_ADD_YEAR__INT_TIMESTAMP__TIMESTAMP : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "date_add_year", - returns = TIMESTAMP, - parameters = listOf( - FunctionParameter("interval", INT), - FunctionParameter("datetime", TIMESTAMP), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val interval = args[0].check() - val datetime = args[1].check() - return if (datetime.value == null || interval.value == null) { - timestampValue(null) - } else { - val datetimeValue = datetime.value!! - val intervalValue = try { interval.toInt64().value!! } catch (e: DataException) { throw TypeCheckException() } - timestampValue(datetimeValue.plusYears(intervalValue)) - } - } -} diff --git a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnIsChar.kt b/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnIsChar.kt deleted file mode 100644 index ae010524e2..0000000000 --- a/plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnIsChar.kt +++ /dev/null @@ -1,69 +0,0 @@ -// ktlint-disable filename -@file:Suppress("ClassName") - -package org.partiql.plugin.internal.fn.scalar - -import org.partiql.errors.TypeCheckException -import org.partiql.spi.function.PartiQLFunction -import org.partiql.spi.function.PartiQLFunctionExperimental -import org.partiql.types.function.FunctionParameter -import org.partiql.types.function.FunctionSignature -import org.partiql.value.CharValue -import org.partiql.value.Int32Value -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType.ANY -import org.partiql.value.PartiQLValueType.BOOL -import org.partiql.value.PartiQLValueType.INT32 -import org.partiql.value.StringValue -import org.partiql.value.boolValue -import org.partiql.value.check - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_IS_CHAR__ANY__BOOL : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "is_char", - returns = BOOL, - parameters = listOf(FunctionParameter("value", ANY)), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val arg = args[0] - return if (arg.isNull) { - boolValue(null) - } else { - boolValue(arg is CharValue) - } - } -} - -@OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) -internal object Fn_IS_CHAR__INT32_ANY__BOOL : PartiQLFunction.Scalar { - - override val signature = FunctionSignature.Scalar( - name = "is_char", - returns = BOOL, - parameters = listOf( - FunctionParameter("length", INT32), - FunctionParameter("value", ANY), - ), - isNullCall = true, - isNullable = false, - ) - - override fun invoke(args: Array): PartiQLValue { - val length = args[0].check().value - if (length == null || length < 0) { - throw TypeCheckException() - } - val v = args[1] - return when { - v.isNull -> boolValue(null) - v !is StringValue -> boolValue(false) - else -> boolValue(v.value!!.length == length) - } - } -} diff --git a/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt b/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt index 25194982eb..cb3bee2a0d 100644 --- a/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt +++ b/test/partiql-tests-runner/src/test/kotlin/org/partiql/runner/executor/EvalExecutor.kt @@ -10,23 +10,19 @@ import com.amazon.ionelement.api.toIonValue import org.partiql.eval.PartiQLEngine import org.partiql.eval.PartiQLResult import org.partiql.eval.PartiQLStatement +import org.partiql.eval.value.Datum import org.partiql.lang.eval.CompileOptions import org.partiql.lang.eval.TypingMode import org.partiql.parser.PartiQLParser import org.partiql.plan.Statement import org.partiql.planner.PartiQLPlanner -import org.partiql.plugins.memory.MemoryCatalog +import org.partiql.planner.catalog.Name import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject +import org.partiql.plugins.memory.MemoryTable import org.partiql.runner.ION import org.partiql.runner.test.TestExecutor -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath import org.partiql.spi.connector.Connector -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.PType -import org.partiql.types.StaticType import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.io.PartiQLValueIonReaderBuilder @@ -36,7 +32,7 @@ import org.partiql.planner.catalog.Session as PlannerSession @OptIn(PartiQLValueExperimental::class) class EvalExecutor( private val plannerSession: PlannerSession, - private val evalSession: PartiQLEngine.Session + private val evalSession: PartiQLEngine.Session, ) : TestExecutor, PartiQLResult> { override fun prepare(statement: String): PartiQLStatement<*> { @@ -112,6 +108,7 @@ class EvalExecutor( } return false } + companion object { val parser = PartiQLParser.default() val planner = PartiQLPlanner.standard() @@ -129,12 +126,7 @@ class EvalExecutor( val session = PlannerSession.builder() .catalog(catalog) - .catalogs( - "default" to connector.getMetadata(object : ConnectorSession { - override fun getQueryId(): String = "query" - override fun getUserId(): String = "user" - }) - ) + .catalogs(connector.getCatalog()) .build() val mode = when (options.typingMode) { @@ -162,22 +154,20 @@ class EvalExecutor( env.fields.forEach { map[it.name] = inferEnv(it.value) } - val catalog = MemoryCatalog.builder().name("default").build() - catalog.load(env) - return MemoryConnector(catalog) + return MemoryConnector.builder() + .name("default") + .apply { load(env) } + .build() } + /** + * Uses the planner to infer the type of the environment. + */ private fun inferEnv(env: AnyElement): PType { - val catalog = MemoryCatalog.builder().name("conformance_test").build() - val connector = MemoryConnector(catalog) + val catalog = MemoryConnector.builder().name("default").build().getCatalog() val session = PlannerSession.builder() .catalog("default") - .catalogs( - "default" to connector.getMetadata(object : ConnectorSession { - override fun getQueryId(): String = "query" - override fun getUserId(): String = "user" - }) - ) + .catalogs(catalog) .build() val stmt = parser.parse("`$env`").root val plan = planner.plan(stmt, session) @@ -189,17 +179,23 @@ class EvalExecutor( * * TODO until this point, PartiQL Kotlin has only done top-level bindings. */ - private fun MemoryCatalog.load(env: StructElement) { + private fun MemoryConnector.Builder.load(env: StructElement) { for (f in env.fields) { - val k = f.name - val v = f.value - // convert to binding - val path = BindingPath(steps = listOf(BindingName(k, BindingCase.SENSITIVE))) - val item = MemoryObject( - type = StaticType.ANY, - value = PartiQLValueIonReaderBuilder.standard().build(v).read(), + val name = Name.of(f.name) + + // WITH SHIM (233 failures) + val value = PartiQLValueIonReaderBuilder.standard().build(f.value).read() + val datum = Datum.of(value) + + // NO SHIM (343 failures) + // val datum = IonDatum.of(f.value) + + val table = MemoryTable.of( + name = name, + schema = PType.dynamic(), + datum = datum, ) - this.insert(path, item) + define(table) } } }