From 1f9701a4447263f9df66c0a1505615745fba5037 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 19 Sep 2024 16:45:18 -0700 Subject: [PATCH 1/2] Removes unused SPI APIs and makes connector instantiate catalogs --- .../src/main/kotlin/org/partiql/cli/Main.kt | 34 +++---- .../org/partiql/cli/pipeline/Pipeline.kt | 28 +----- .../kotlin/org/partiql/cli/shell/Shell.kt | 21 ++-- .../eval/internal/PartiQLEngineDefaultTest.kt | 10 +- .../org/partiql/planner/internal/Env.kt | 22 ++--- .../kotlin/org/partiql/planner/PlanTest.kt | 7 +- .../planner/PlannerErrorReportingTests.kt | 21 ++-- .../partiql/planner/internal/TestCatalog.kt | 27 +----- .../internal/exclude/SubsumptionTest.kt | 4 +- .../internal/typer/PartiQLTyperTestBase.kt | 27 +++--- .../planner/internal/typer/PlanTyperTest.kt | 6 +- partiql-spi/api/partiql-spi.api | 41 ++------ .../main/kotlin/org/partiql/spi/Connector.kt | 48 ++++++++++ .../src/main/kotlin/org/partiql/spi/Plugin.kt | 28 ------ .../kotlin/org/partiql/spi/catalog/Catalog.kt | 43 +++------ .../org/partiql/spi/catalog/Catalogs.kt | 7 -- .../kotlin/org/partiql/spi/catalog/Session.kt | 4 +- .../kotlin/org/partiql/spi/catalog/Table.kt | 12 --- .../org/partiql/spi/connector/Connector.kt | 35 ------- .../org/partiql/plugins/local/LocalCatalog.kt | 77 +++++++++------ .../partiql/plugins/local/LocalConnector.kt | 75 +++++---------- .../org/partiql/plugins/local/LocalPlugin.kt | 28 ------ .../META-INF/services/org.partiql.spi.Plugin | 1 - .../partiql/plugins/memory/MemoryCatalog.kt | 67 +++++++++++++ .../partiql/plugins/memory/MemoryConnector.kt | 95 ++++--------------- .../partiql/plugins/memory/MemoryPlugin.kt | 23 ----- .../plugins/memory/MemoryConnectorTest.kt | 9 +- .../partiql/runner/executor/EvalExecutor.kt | 20 ++-- 28 files changed, 316 insertions(+), 504 deletions(-) create mode 100644 partiql-spi/src/main/kotlin/org/partiql/spi/Connector.kt delete mode 100644 partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt delete mode 100644 partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt delete mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt delete mode 100644 plugins/partiql-local/src/main/resources/META-INF/services/org.partiql.spi.Plugin create mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt delete mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt 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 9deb32524d..2f1d7fdf89 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt @@ -22,12 +22,12 @@ import com.amazon.ionelement.api.loadAllElements import org.partiql.cli.io.Format 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.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryTable +import org.partiql.spi.catalog.Catalog import org.partiql.spi.catalog.Name -import org.partiql.spi.connector.Connector +import org.partiql.spi.catalog.Session import org.partiql.spi.value.ion.IonDatum import org.partiql.types.PType import org.partiql.value.PartiQLValueExperimental @@ -36,7 +36,6 @@ import picocli.CommandLine import java.io.File import java.io.InputStream import java.io.SequenceInputStream -import java.time.Instant import java.util.Collections import java.util.Properties import kotlin.system.exitProcess @@ -185,24 +184,17 @@ internal class MainCommand : Runnable { } } - private fun session() = Pipeline.Session( - queryId = "cli", - userId = System.getProperty("user.name"), - currentCatalog = "default", - currentDirectory = emptyList(), - connectors = connectors(), - instant = Instant.now(), - debug = false, - mode = when (strict) { - true -> PartiQLEngine.Mode.STRICT - else -> PartiQLEngine.Mode.PERMISSIVE - } - ) + private fun session() = Session.builder() + .identity(System.getProperty("user.name")) + .namespace(emptyList()) + .catalog("default") + .catalogs(*catalogs().toTypedArray()) + .build() /** * Produce the connector map for planning and execution. */ - private fun connectors(): Map { + private fun catalogs(): List { if (dir != null && files != null && files!!.isNotEmpty()) { error("Cannot specify both a database directory and a list of files.") } @@ -225,7 +217,7 @@ internal class MainCommand : Runnable { } else { ionNull() } - val connector = MemoryConnector.builder() + val catalog = MemoryCatalog.builder() .name("default") .define( MemoryTable.of( @@ -235,9 +227,7 @@ internal class MainCommand : Runnable { ) ) .build() - return mapOf( - "default" to connector - ) + return listOf(catalog) } /** 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 6c5027c64f..2692261c1e 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,8 +10,6 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.v1.PartiQLPlan import org.partiql.planner.PartiQLPlanner import org.partiql.spi.catalog.Session -import org.partiql.spi.connector.Connector -import java.time.Instant internal class Pipeline private constructor( private val parser: PartiQLParser, @@ -19,30 +17,6 @@ internal class Pipeline private constructor( private val engine: PartiQLEngine, ) { - /** - * Combined planner and engine session. - */ - internal data class Session( - @JvmField val queryId: String, - @JvmField val userId: String, - @JvmField val currentCatalog: String, - @JvmField val currentDirectory: List, - @JvmField val connectors: Map, - @JvmField val instant: Instant, - @JvmField val debug: Boolean, - @JvmField val mode: PartiQLEngine.Mode, - ) { - - private val catalogs = connectors.values.map { it.getCatalog() } - - fun planner() = org.partiql.spi.catalog.Session.builder() - .identity(userId) - .namespace(currentDirectory) - .catalog(currentCatalog) - .catalogs(*catalogs.toTypedArray()) - .build() - } - /** * TODO replace with the ResultSet equivalent? */ @@ -59,7 +33,7 @@ internal class Pipeline private constructor( private fun plan(statement: Statement, session: Session): PartiQLPlan { val callback = ProblemListener() - val result = planner.plan(statement, session.planner(), callback) + val result = planner.plan(statement, session, callback) val errors = callback.problems.filter { it.details.severity == ProblemSeverity.ERROR } if (errors.isNotEmpty()) { throw RuntimeException(errors.joinToString()) 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 c79db45fcd..5629a5008d 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,6 +31,7 @@ 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.catalog.Session import org.partiql.value.PartiQLValueExperimental import org.partiql.value.io.PartiQLValueTextWriter import java.io.Closeable @@ -115,7 +116,7 @@ val donePrinting = AtomicBoolean(true) */ internal class Shell( private val pipeline: Pipeline, - private val session: Pipeline.Session, + private val session: Session, private val debug: Boolean, ) { @@ -243,19 +244,17 @@ internal class Shell( } "info" -> { // Print catalog information - val connector = session.connectors[session.currentCatalog] - if (connector == null) { - out.error("No connector for catalog ${session.currentCatalog}.") - continue - } - out.error("Connectors do not support listing metadata") + out.error("Catalogs do not support listing metadata") } "session" -> { // Print session information - out.info("user: ${session.userId}") - out.info("mode: ${session.mode.name.lowercase()}") - out.info("catalog: ${session.currentCatalog}") - out.info("path: [${session.currentDirectory.joinToString(".")}]") + out.info("identity: ${session.getIdentity()}") + // TODO Session mode, `out.info("mode: ${session.getMode()")` + out.println() + out.info("ENVIRONMENT") + out.info("-----------") + out.info("CURRENT_CATALOG: ${session.getCatalog()}") + out.info("CURRENT_NAMESPACE: ${session.getNamespace()}") out.println() } "version" -> { 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 e65426650b..923f04cc1f 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 @@ -14,7 +14,7 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.v1.PartiQLPlan import org.partiql.planner.builder.PartiQLPlannerBuilder import org.partiql.planner.internal.SqlPlannerV1 -import org.partiql.plugins.memory.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryTable import org.partiql.spi.catalog.Name import org.partiql.spi.catalog.Session @@ -1261,7 +1261,7 @@ class PartiQLEngineDefaultTest { internal fun assert() { val statement = parser.parse(input).root - val connector = MemoryConnector.builder() + val catalog = MemoryCatalog.builder() .name("memory") .apply { globals.forEach { @@ -1276,7 +1276,7 @@ class PartiQLEngineDefaultTest { .build() val session = Session.builder() .catalog("memory") - .catalogs(connector.getCatalog()) + .catalogs(catalog) .build() val plan = SqlPlannerV1.plan(statement, session) val stmt = engine.prepare(plan, mode, session) @@ -1353,10 +1353,10 @@ class PartiQLEngineDefaultTest { private fun run(mode: PartiQLEngine.Mode): Pair { val statement = parser.parse(input).root - val connector = MemoryConnector.builder().name("memory").build() + val catalog = MemoryCatalog.builder().name("memory").build() val session = Session.builder() .catalog("memory") - .catalogs(connector.getCatalog()) + .catalogs(catalog) .build() val plan = SqlPlannerV1.plan(statement, session) val stmt = engine.prepare(plan, mode, session) 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 f072e5806f..2df652521e 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 @@ -51,7 +51,7 @@ internal class Env(private val session: Session) { private val fns: SqlFnProvider = SqlFnProvider /** - * Catalog lookup needs to search (3x) to handle schema-qualified and catalog-qualified use-cases. + * Catalog lookup needs to search (3x) to table schema-qualified and catalog-qualified use-cases. * * 1. Lookup in current catalog and namespace. * 2. Lookup as a schema-qualified identifier. @@ -62,38 +62,38 @@ internal class Env(private val session: Session) { // 1. Search in current catalog and namespace var catalog = default var path = resolve(identifier) - var handle = catalog.getTableHandle(session, path) + var table = catalog.getTable(session, path) // 2. Lookup as a schema-qualified identifier. - if (handle == null && identifier.hasQualifier()) { + if (table == null && identifier.hasQualifier()) { path = identifier - handle = catalog.getTableHandle(session, path) + table = catalog.getTable(session, path) } // 3. Lookup as a catalog-qualified identifier - if (handle == null && identifier.hasQualifier()) { + if (table == 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) + table = catalog.getTable(session, path) } // !! NOT FOUND !! - if (handle == null) { + if (table == 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, handle.table) + val refName = table.getName() + val refType = CompilerType(table.getSchema()) + val ref = Ref.Obj(refCatalog, refName, refType, table) // Convert any remaining identifier parts to a path expression val root = Rex(ref.type, rexOpVarGlobal(ref)) - val tail = calculateMatched(path, handle.name) + val tail = calculateMatched(path, refName) return if (tail.isEmpty()) root else root.toPath(tail) } 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 34eeb23acf..40171a68bc 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt @@ -118,10 +118,11 @@ class PlanTest { // Assert DynamicTest.dynamicTest(displayName) { val input = input[test.key] ?: error("no test cases") - + val originalQuery = input + val normalizedQuery = test listOf(true, false).forEach { isSignal -> - val inputPlan = pipeline.invoke(input, isSignal).plan - val outputPlan = pipeline.invoke(test, isSignal).plan + val inputPlan = pipeline.invoke(originalQuery, isSignal).plan + val outputPlan = pipeline.invoke(normalizedQuery, isSignal).plan assertPlanEqual(inputPlan, outputPlan) } } 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 fb17610bed..2d3ef5792a 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -10,7 +10,7 @@ import org.partiql.plan.debug.PlanPrinter 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.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.spi.catalog.Session import org.partiql.types.BagType import org.partiql.types.PType @@ -25,21 +25,22 @@ internal class PlannerErrorReportingTests { val userId = "test-user" val queryId = "query" - val catalog = MemoryConnector + // TODO REMOVE fromStaticType + val catalog = MemoryCatalog .builder() .name(catalogName) - .define("missing_binding", StaticType.ANY) - .define("atomic", StaticType.INT2) - .define("collection_no_missing_atomic", BagType(StaticType.INT2)) - .define("collection_contain_missing_atomic", BagType(StaticType.INT2)) - .define("struct_no_missing", closedStruct(StructType.Field("f1", StaticType.INT2))) + .define("missing_binding", PType.fromStaticType(StaticType.ANY)) + .define("atomic", PType.fromStaticType(StaticType.INT2)) + .define("collection_no_missing_atomic", PType.fromStaticType(BagType(StaticType.INT2))) + .define("collection_contain_missing_atomic", PType.fromStaticType(BagType(StaticType.INT2))) + .define("struct_no_missing", PType.fromStaticType(closedStruct(StructType.Field("f1", StaticType.INT2)))) .define( "struct_with_missing", - closedStruct( + PType.fromStaticType(closedStruct( StructType.Field("f1", StaticType.INT2), - ) + )) ) - .build().getCatalog() + .build() val session = Session.builder() .catalog(catalogName) 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 index 46587b888e..39075fc60b 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/TestCatalog.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/TestCatalog.kt @@ -3,13 +3,14 @@ package org.partiql.planner.internal import org.partiql.spi.catalog.Catalog import org.partiql.spi.catalog.Identifier import org.partiql.spi.catalog.Name -import org.partiql.spi.catalog.Namespace import org.partiql.spi.catalog.Session import org.partiql.spi.catalog.Table import org.partiql.types.PType /** * Basic catalog implementation used for testing; consider merging with MemoryConnector? + * + * TODO COMBINE WITH MemoryCatalog as the standard catalog implementation. */ public class TestCatalog private constructor( private val name: String, @@ -22,30 +23,12 @@ public class TestCatalog private constructor( return null } - override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? { - val matched = mutableListOf() + override fun getTable(session: Session, identifier: Identifier): Table? { 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() + return curr.table } private class Tree( @@ -115,7 +98,7 @@ public class TestCatalog private constructor( // upsert namespaces curr = curr.getOrPut(part) } - curr.table = Table.empty(name.getName(), schema) + curr.table = Table.empty(name, schema) return this } 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 bf67a0c445..ee58dd97b9 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 @@ -20,7 +20,7 @@ import org.partiql.plan.relOpExcludeTypeStructSymbol import org.partiql.plan.relOpExcludeTypeStructWildcard import org.partiql.plan.rexOpVar import org.partiql.planner.PartiQLPlanner -import org.partiql.plugins.memory.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.spi.catalog.Session import java.util.stream.Stream import kotlin.test.assertEquals @@ -31,7 +31,7 @@ class SubsumptionTest { private val planner = PartiQLPlanner.standard() private val parser = PartiQLParser.default() - private val catalog = MemoryConnector.builder().name("default").build().getCatalog() + private val catalog = MemoryCatalog.builder().name("default").build() } private fun getExcludeClause(statement: Statement): Rel.Op.Exclude { 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 a467729574..3e2880aa21 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 @@ -11,7 +11,7 @@ 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.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryTable import org.partiql.spi.catalog.Catalog import org.partiql.spi.catalog.Name @@ -50,24 +50,25 @@ abstract class PartiQLTyperTestBase { val inputs = PartiQLTestProvider().apply { load() } - 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) - } + 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) + } /** * Build a ConnectorMetadata instance from the list of types. */ - private fun buildMetadata(catalog: String, types: List): Catalog { - val connector = MemoryConnector.builder().name(catalog) + private fun buildCatalog(name: String, types: List): Catalog { + val catalog = MemoryCatalog.builder().name(name) // define all bindings types.forEachIndexed { i, t -> - val name = Name.of("t${i + 1}") - val schema = PType.fromStaticType(t) - val table = MemoryTable.empty(name, schema) - connector.define(table) + val tableName = Name.of("t${i + 1}") + val tableSchema = PType.fromStaticType(t) + val table = MemoryTable.empty(tableName, tableSchema) + catalog.define(table) } - return connector.build().getCatalog() + return catalog.build() } fun testGen( @@ -81,7 +82,7 @@ abstract class PartiQLTyperTestBase { val children = argsMap.flatMap { (key, value) -> value.mapIndexed { index: Int, types: List -> val testName = "${testCategory}_${key}_$index" - val metadata = buildMetadata(testName, types) + val metadata = buildCatalog(testName, types) val displayName = "$group | $testName | $types" val statement = test.statement // Assert 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 b9ac8534b7..b5e1e3e637 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 @@ -17,7 +17,7 @@ import org.partiql.planner.internal.ir.rexOpVarUnresolved import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.local.LocalConnector +import org.partiql.plugins.local.LocalCatalog import org.partiql.spi.catalog.Identifier import org.partiql.spi.catalog.Name import org.partiql.spi.catalog.Session @@ -123,10 +123,10 @@ class PlanTyperTest { .catalog("pql") .namespace("main") .catalogs( - LocalConnector.builder() + LocalCatalog.builder() .name("pql") .root(root) - .build().getCatalog() + .build() ) .build() ) diff --git a/partiql-spi/api/partiql-spi.api b/partiql-spi/api/partiql-spi.api index cfe2525daa..2420293d2b 100644 --- a/partiql-spi/api/partiql-spi.api +++ b/partiql-spi/api/partiql-spi.api @@ -277,38 +277,33 @@ public final class org/partiql/errors/TypeCheckException : java/lang/RuntimeExce public fun fillInStackTrace ()Ljava/lang/Throwable; } -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 { + public abstract fun getCatalog (Ljava/lang/String;)Lorg/partiql/spi/catalog/Catalog; + public abstract fun getCatalog (Ljava/lang/String;Lorg/partiql/spi/Connector$Context;)Lorg/partiql/spi/catalog/Catalog; +} + +public abstract interface class org/partiql/spi/Connector$Context { } public abstract interface class org/partiql/spi/catalog/Catalog { public abstract fun getAggregations (Lorg/partiql/spi/catalog/Session;Ljava/lang/String;)Ljava/util/Collection; public abstract fun getFunctions (Lorg/partiql/spi/catalog/Session;Ljava/lang/String;)Ljava/util/Collection; public abstract fun getName ()Ljava/lang/String; + public abstract fun getTable (Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Identifier;)Lorg/partiql/spi/catalog/Table; public abstract fun getTable (Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Name;)Lorg/partiql/spi/catalog/Table; - public abstract fun getTableHandle (Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Identifier;)Lorg/partiql/spi/catalog/Table$Handle; - public abstract fun listNamespaces (Lorg/partiql/spi/catalog/Session;)Ljava/util/Collection; - public abstract fun listNamespaces (Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Namespace;)Ljava/util/Collection; - public abstract fun listTables (Lorg/partiql/spi/catalog/Session;)Ljava/util/Collection; - public abstract fun listTables (Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Namespace;)Ljava/util/Collection; } public final class org/partiql/spi/catalog/Catalog$DefaultImpls { public static fun getAggregations (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Ljava/lang/String;)Ljava/util/Collection; public static fun getFunctions (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Ljava/lang/String;)Ljava/util/Collection; + public static fun getTable (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Identifier;)Lorg/partiql/spi/catalog/Table; public static fun getTable (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Name;)Lorg/partiql/spi/catalog/Table; - public static fun getTableHandle (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Identifier;)Lorg/partiql/spi/catalog/Table$Handle; - public static fun listNamespaces (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;)Ljava/util/Collection; - public static fun listNamespaces (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Namespace;)Ljava/util/Collection; - public static fun listTables (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;)Ljava/util/Collection; - public static fun listTables (Lorg/partiql/spi/catalog/Catalog;Lorg/partiql/spi/catalog/Session;Lorg/partiql/spi/catalog/Namespace;)Ljava/util/Collection; } public abstract interface class org/partiql/spi/catalog/Catalogs { public static final field Companion Lorg/partiql/spi/catalog/Catalogs$Companion; public static fun builder ()Lorg/partiql/spi/catalog/Catalogs$Builder; public abstract fun getCatalog (Ljava/lang/String;Z)Lorg/partiql/spi/catalog/Catalog; - public abstract fun listCatalogs ()Ljava/util/Collection; public static fun of (Ljava/util/Collection;)Lorg/partiql/spi/catalog/Catalogs; public static fun of ([Lorg/partiql/spi/catalog/Catalog;)Lorg/partiql/spi/catalog/Catalogs; } @@ -327,7 +322,6 @@ public final class org/partiql/spi/catalog/Catalogs$Companion { public final class org/partiql/spi/catalog/Catalogs$DefaultImpls { public static synthetic fun getCatalog$default (Lorg/partiql/spi/catalog/Catalogs;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/spi/catalog/Catalog; - public static fun listCatalogs (Lorg/partiql/spi/catalog/Catalogs;)Ljava/util/Collection; } public final class org/partiql/spi/catalog/Identifier : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { @@ -516,25 +510,6 @@ public final class org/partiql/spi/catalog/Table$DefaultImpls { public static fun getSchema (Lorg/partiql/spi/catalog/Table;)Lorg/partiql/types/PType; } -public final class org/partiql/spi/catalog/Table$Handle { - public final field name Lorg/partiql/spi/catalog/Name; - public final field table Lorg/partiql/spi/catalog/Table; - public fun (Lorg/partiql/spi/catalog/Name;Lorg/partiql/spi/catalog/Table;)V -} - -public abstract interface class org/partiql/spi/connector/Connector { - public abstract fun getCatalog ()Lorg/partiql/spi/catalog/Catalog; -} - -public abstract interface class org/partiql/spi/connector/Connector$Factory { - 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;Lcom/amazon/ionelement/api/StructElement;ILjava/lang/Object;)Lorg/partiql/spi/connector/Connector; -} - public final class org/partiql/spi/fn/AggSignature { public final field description Ljava/lang/String; public final field isDecomposable Z diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/Connector.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/Connector.kt new file mode 100644 index 0000000000..76ad3bfbd0 --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/Connector.kt @@ -0,0 +1,48 @@ +package org.partiql.spi + +import org.partiql.spi.catalog.Catalog + +/** + * A Connector is responsible for instantiating [Catalog] implementations. + * + * @see Catalog + */ +public interface Connector { + + /** + * Marker interface for an arbitrary context argument; marker over generics due to type erasure of generics. + */ + public interface Context + + /** + * Get (or instantiate) a [Catalog] with the given name. + * + * @param name Catalog name + * @return + */ + public fun getCatalog(name: String): Catalog + + /** + * Get (or instantiate) a [Catalog] with the given name and additional context. + * + * Example: + * + * ``` + * override fun getCatalog(context: Context): Catalog { + * if (context !is MyContext) { + * throw IllegalArgumentException("Unsupported context type: $context") + * } + * return getCatalog(context) + * } + * + * private fun getCatalog(context: MyContext): Catalog { + * // ... get catalog from context + * } + * ``` + * + * @param name Catalog name + * @param context Context is an arbitrary object + * @return + */ + public fun getCatalog(name: String, context: Context): Catalog +} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt deleted file mode 100644 index 865d5762d3..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt +++ /dev/null @@ -1,28 +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.Connector - -/** - * A singular unit of external logic. - */ -public interface Plugin { - - /** - * 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/catalog/Catalog.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalog.kt index 6dda8d27ee..468e17d0f9 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalog.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalog.kt @@ -24,44 +24,23 @@ public interface Catalog { public fun getTable(session: Session, name: Name): Table? = null /** - * Given an [Identifier], returns a [Table.Handle] that corresponds to the longest-available requested path. + * Get a table by identifier. * - * For example, given a table named "Table" located within Catalog "AWS" and Namespace "a".b"."c", a user could - * call [getTableHandle] with the identifier "a"."b"."c"."Table". The returned [Table.Handle] will contain the table - * representation and the matching path: "a"."b"."c"."Table" + * !! IMPORTANT !! * - * As another example, consider a table within a [Namespace] that may be a struct with nested attributes. - * A user could call [getTableHandle] with the identifier "a"."b"."c"."Table"."x". In the Namespace, only table - * "Table" exists. Therefore, this method will return a [Table.Handle] with the "Table" representation and the - * matching path: "a"."b"."c"."Table". + * The returned [Table] MUST be matched following the PartiQL name resolution rules, in short: * - * IMPORTANT: The returned [Table.Handle.name] must be correct for correct evaluation. + * 1. If the identifier has multiple matches, choose the LONGEST matching name. + * 2. If the identifier is ambiguous (multiple matches with same length), throw an ambiguous identifier error. + * 3. If there are NO matches, return null. * - * If the [Identifier] does not correspond to an existing [Table], implementers should return null. - */ - public fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? = null - - /** - * List top-level tables. - */ - public fun listTables(session: Session): Collection = listTables(session, Namespace.empty()) - - /** - * List all tables under this namespace. - */ - public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() - - /** - * List top-level namespaces from the catalog. - */ - public fun listNamespaces(session: Session): Collection = listNamespaces(session, Namespace.empty()) - - /** - * List all child namespaces from the namespace. + * Example, * - * @param namespace + * 1. Consider a table with name `a.b.c.Example` -> Namespace=["a","b","c"], Table="Example" + * 2. Invoke getTable("a"."b"."c"."Example"."x") + * 3. The implementation MUST match "a"."b"."c"."Example" to a.b.c.Example (note "x" does not match a table) */ - public fun listNamespaces(session: Session, namespace: Namespace): Collection = emptyList() + public fun getTable(session: Session, identifier: Identifier): Table? = null /** * Returns a collection of scalar functions in this catalog with the given name, or an empty list if none. diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalogs.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalogs.kt index 8b118f42c7..a4f7323050 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalogs.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalogs.kt @@ -10,11 +10,6 @@ public interface Catalogs { */ public fun getCatalog(name: String, ignoreCase: Boolean = false): Catalog? - /** - * Returns a list of all available catalogs. - */ - public fun listCatalogs(): Collection = listOf() - /** * Factory methods and builder. */ @@ -69,8 +64,6 @@ public interface Catalogs { // lookup return catalogs[name] } - - override fun listCatalogs(): Collection = catalogs.values } } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Session.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Session.kt index 75746debaf..ffc4fd2795 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Session.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Session.kt @@ -99,9 +99,7 @@ public interface Session { } /** - * Adds catalogs to this session like the old Map. - * - * TODO replace with org.partiql.spi.catalog.Catalog. + * Adds catalogs to this session. */ public fun catalogs(vararg catalogs: Catalog): Builder { for (catalog in catalogs) { diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Table.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Table.kt index bf14a0d024..c4e404ddb6 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Table.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Table.kt @@ -8,18 +8,6 @@ import org.partiql.types.PType */ public interface Table { - /** - * Handle holds both a table and its resolved name within its respective catalog. - * - * TODO DELETE ME AS A TABLE ALREADY CONTAINS ITS NAME. - * - * Note: This replaces ConnectorObjectHandle from versions < 1.0 - */ - public class Handle( - @JvmField public val name: Name, - @JvmField public val table: Table, - ) - /** * The table's name. */ 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 deleted file mode 100644 index f3aa5053de..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.partiql.spi.connector - -import com.amazon.ionelement.api.StructElement -import com.amazon.ionelement.api.emptyIonStruct -import org.partiql.spi.catalog.Catalog - -/** - * A mechanism by which PartiQL can access bindings and catalog metadata. - */ -public interface Connector { - - /** - * Returns a [Catalog] which the planner uses to load catalog metadata. - */ - public fun getCatalog(): Catalog - - /** - * A Plugin leverages a [Factory] to produce a [Connector] which is used for binding and metadata access. - */ - public interface Factory { - - /** - * The connector name used to register the factory. - */ - public val name: String - - /** - * The connector factory method. - * - * @param config - * @return - */ - public fun create(config: StructElement = emptyIonStruct()): Connector - } -} 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 050884c407..7a3a232e69 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 @@ -14,15 +14,11 @@ import kotlin.io.path.notExists /** * Implementation of [Catalog] where dirs are namespaces and files are table metadata. */ -internal class LocalCatalog( +public class LocalCatalog internal constructor( private val name: String, private val root: Path, ) : Catalog { - private companion object { - private const val EXT = ".ion" - } - init { assert(root.isDirectory()) { "LocalNamespace must be a directory" } } @@ -42,7 +38,7 @@ internal class LocalCatalog( /** * TODO this doesn't handle ambiguous binding errors or back-tracking for longest prefix searching. */ - override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? { + override fun getTable(session: Session, identifier: Identifier): Table? { val matched = mutableListOf() var curr = root for (part in identifier) { @@ -67,30 +63,32 @@ internal class LocalCatalog( } // Remove the extension val name = Name.of(matched) - return Table.Handle(name, LocalTable(name, path)) + return LocalTable(name, path) } - override fun listTables(session: Session, namespace: Namespace): Collection { - val path = toPath(namespace) - if (path.notExists()) { - // throw exception? - return emptyList() - } - return super.listTables(session, namespace) - } + // TODO preserving this logic if catalog regains the listing APIs. + // override fun listTables(session: Session, namespace: Namespace): Collection { + // val path = toPath(namespace) + // if (path.notExists()) { + // // throw exception? + // return emptyList() + // } + // return super.listTables(session, namespace) + // } - 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()) } - } + // TODO preserving this logic if catalog regains the listing APIs. + // 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()) } + // } private fun toPath(namespace: Namespace): Path { var curr = root @@ -100,7 +98,28 @@ internal class LocalCatalog( return curr } - private fun toNamespace(path: Path): Namespace { - return Namespace.of(path.relativize(root).map { it.toString() }) + // TODO preserving this logic if catalog regains the listing APIs. + // private fun toNamespace(path: Path): Namespace { + // return Namespace.of(path.relativize(root).map { it.toString() }) + // } + + public companion object { + + private const val EXT = ".ion" + + @JvmStatic + public fun builder(): Builder = Builder() + } + + public class Builder internal constructor() { + + private var name: String? = null + private var root: Path? = null + + public fun name(name: String): Builder = apply { this.name = name } + + public fun root(root: Path): Builder = apply { this.root = root } + + public fun build(): LocalCatalog = LocalCatalog(name!!, root!!) } } 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 0cd258f6e0..5206b02e1e 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 @@ -14,71 +14,40 @@ package org.partiql.plugins.local -import com.amazon.ionelement.api.StructElement +import org.partiql.spi.Connector import org.partiql.spi.catalog.Catalog -import org.partiql.spi.connector.Connector import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.isDirectory -import kotlin.io.path.notExists /** * An implementation of a PartiQL [Connector] backed by a catalog in a local directory. * - * Set to the "root" key to specify the root of the local database. - * - * ```ion - * { - * connector_name: "local", - * root: "/Users/me/some/root/directory" - * } + * Example: + * ``` + * val connector = LocalConnector() + * val context = LocalConnector.Context(Paths.get("/path/to/catalog")) + * val catalog = connector.getCatalog("my_catalog", context) + * // use catalog * ``` */ -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" - - @JvmStatic - public fun builder(): Builder = Builder() - - public class Builder internal constructor() { +public class LocalConnector : Connector { - private var name: String? = null + /** + * Context arguments for instantiating a [LocalConnector] catalog. + * + * @property root The catalog root directory. + */ + public class Context(@JvmField public val root: Path) : Connector.Context - private var root: Path? = null - - public fun name(name: String): Builder = apply { this.name = name } - - public fun root(root: Path): Builder = apply { this.root = root } - - public fun build(): LocalConnector = LocalConnector(name!!, root!!) - } + override fun getCatalog(name: String): Catalog { + throw IllegalArgumentException("LocalConnector cannot instantiate a catalog with no context") } - override fun getCatalog(): Catalog = catalog - - internal class Factory : Connector.Factory { - - override val name: String = CONNECTOR_NAME - - 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) + override fun getCatalog(name: String, context: Connector.Context): Catalog { + if (context !is Context) { + throw IllegalArgumentException("LocalConnector context must be of type ${Context::class.java}, found: ${context::class.java}") } + return getCatalog(name, context) } + + private fun getCatalog(name: String, context: Context): Catalog = LocalCatalog(name, context.root) } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt deleted file mode 100644 index bdf6c18e9d..0000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt +++ /dev/null @@ -1,28 +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.Plugin -import org.partiql.spi.connector.Connector - -/** - * LocalPlugin is a PartiQL plugin that provides schemas written in PartiQL Value Schema. - * - * Backed by a memoized catalog tree from the given root dir; global bindings are files. - */ -public class LocalPlugin : Plugin { - - override val factory: Connector.Factory = LocalConnector.Factory() -} diff --git a/plugins/partiql-local/src/main/resources/META-INF/services/org.partiql.spi.Plugin b/plugins/partiql-local/src/main/resources/META-INF/services/org.partiql.spi.Plugin deleted file mode 100644 index 81324a3af9..0000000000 --- a/plugins/partiql-local/src/main/resources/META-INF/services/org.partiql.spi.Plugin +++ /dev/null @@ -1 +0,0 @@ -org.partiql.plugins.local.LocalPlugin \ No newline at end of file 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 new file mode 100644 index 0000000000..bc06dc9d49 --- /dev/null +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt @@ -0,0 +1,67 @@ +package org.partiql.plugins.memory + +import org.partiql.spi.catalog.Catalog +import org.partiql.spi.catalog.Identifier +import org.partiql.spi.catalog.Name +import org.partiql.spi.catalog.Session +import org.partiql.spi.catalog.Table +import org.partiql.types.PType + +/** + * Implement [Catalog] over an in-memory tables map. + */ +public class MemoryCatalog( + private val name: String, + private val tables: Map, +) : 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 getTable(session: Session, identifier: Identifier): Table? { + // 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 + } + } + return null + } + + 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: PType): Builder { + val table = MemoryTable.empty(name, 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(): MemoryCatalog = MemoryCatalog(name!!, tables) + } +} 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 2edfeef1aa..d9706e9679 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 @@ -15,95 +15,36 @@ package org.partiql.plugins.memory -import com.amazon.ionelement.api.StructElement +import org.partiql.spi.Connector import org.partiql.spi.catalog.Catalog -import org.partiql.spi.catalog.Identifier import org.partiql.spi.catalog.Name -import org.partiql.spi.catalog.Session -import org.partiql.spi.catalog.Table -import org.partiql.spi.connector.Connector -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. + * This is the standard connector implementation. + * + * TODO move into partiql-spi package before 1.0! */ -public class MemoryConnector private constructor( - private val name: String, - private val tables: Map, -) : Connector { - - override fun getCatalog(): Catalog = catalog +public class MemoryConnector : Connector { /** - * For use with ServiceLoader to instantiate a connector from an Ion config. + * TODO + * + * @property tables */ - internal class Factory : Connector.Factory { - - override val name: String = "memory" - - 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 class Context( + @JvmField public val tables: Map, + ) : Connector.Context - public fun name(name: String): Builder = apply { this.name = name } + override fun getCatalog(name: String): Catalog = getCatalog(name, Context(emptyMap())) - // 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) + override fun getCatalog(name: String, context: Connector.Context): Catalog { + if (context !is Context) { + throw IllegalArgumentException("MemoryConnector context must be of type ${Context::class.java}, found: ${context::class.java}") } - - // 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) + return getCatalog(name, context) } - /** - * 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 } - } + private fun getCatalog(name: String, context: Context): Catalog { + return MemoryCatalog(name, context.tables) } } 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 deleted file mode 100644 index 21d4cba488..0000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.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://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.Plugin -import org.partiql.spi.connector.Connector - -public object MemoryPlugin : Plugin { - override val factory: Connector.Factory = MemoryConnector.Factory() -} 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 index b24af305ac..62be2af59f 100644 --- 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 @@ -1,6 +1,7 @@ package org.partiql.plugins.memory import org.junit.jupiter.api.Test +import org.partiql.spi.catalog.Name import org.partiql.spi.catalog.Session class MemoryConnectorTest { @@ -8,14 +9,14 @@ class MemoryConnectorTest { @Test fun sanity() { val session = Session.empty("") - val connector = MemoryConnector.builder() + val catalog = MemoryCatalog.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) + assert(catalog.getTable(session, Name.of("a")) != null) + assert(catalog.getTable(session, Name.of("b")) != null) + assert(catalog.getTable(session, Name.of("c")) != null) } } 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 d5cfb7b0aa..1dfda410c9 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 @@ -16,13 +16,13 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.Statement import org.partiql.planner.PartiQLPlanner import org.partiql.planner.internal.SqlPlannerV1 -import org.partiql.plugins.memory.MemoryConnector +import org.partiql.plugins.memory.MemoryCatalog import org.partiql.plugins.memory.MemoryTable import org.partiql.runner.ION import org.partiql.runner.test.TestExecutor +import org.partiql.spi.catalog.Catalog import org.partiql.spi.catalog.Name import org.partiql.spi.catalog.Session -import org.partiql.spi.connector.Connector import org.partiql.spi.value.Datum import org.partiql.types.PType import org.partiql.value.PartiQLValue @@ -115,6 +115,7 @@ class EvalExecutor( val parser = PartiQLParser.default() val planner = PartiQLPlanner.standard() val engine = PartiQLEngine.standard() + // TODO REPLACE WITH DATUM COMPARATOR val comparator = PartiQLValue.comparator() } @@ -122,11 +123,10 @@ class EvalExecutor( override fun create(env: IonStruct, options: CompileOptions): TestExecutor { // infer catalog from conformance test `env` - val catalog = "default" - val connector = infer(env.toIonElement() as StructElement) + val catalog = infer(env.toIonElement() as StructElement) val session = Session.builder() - .catalog(catalog) - .catalogs(connector.getCatalog()) + .catalog("default") + .catalogs(catalog) .build() val mode = when (options.typingMode) { TypingMode.PERMISSIVE -> PartiQLEngine.Mode.PERMISSIVE @@ -141,12 +141,12 @@ class EvalExecutor( * @param env * @return */ - private fun infer(env: StructElement): Connector { + private fun infer(env: StructElement): Catalog { val map = mutableMapOf() env.fields.forEach { map[it.name] = inferEnv(it.value) } - return MemoryConnector.builder() + return MemoryCatalog.builder() .name("default") .apply { load(env) } .build() @@ -156,7 +156,7 @@ class EvalExecutor( * Uses the planner to infer the type of the environment. */ private fun inferEnv(env: AnyElement): PType { - val catalog = MemoryConnector.builder().name("default").build().getCatalog() + val catalog = MemoryCatalog.builder().name("default").build() val session = Session.builder() .catalog("default") .catalogs(catalog) @@ -171,7 +171,7 @@ class EvalExecutor( * * TODO until this point, PartiQL Kotlin has only done top-level bindings. */ - private fun MemoryConnector.Builder.load(env: StructElement) { + private fun MemoryCatalog.Builder.load(env: StructElement) { for (f in env.fields) { val name = Name.of(f.name) From b644210ca8a43f81cabc3b73744cae75cb12bf9b Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 19 Sep 2024 16:54:41 -0700 Subject: [PATCH 2/2] ktlint --- .../org/partiql/planner/PlannerErrorReportingTests.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 2d3ef5792a..c88ba4db5b 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -36,9 +36,11 @@ internal class PlannerErrorReportingTests { .define("struct_no_missing", PType.fromStaticType(closedStruct(StructType.Field("f1", StaticType.INT2)))) .define( "struct_with_missing", - PType.fromStaticType(closedStruct( - StructType.Field("f1", StaticType.INT2), - )) + PType.fromStaticType( + closedStruct( + StructType.Field("f1", StaticType.INT2), + ) + ) ) .build()