From d8ef10bbbffe270fd07c85785759c10588a29cb7 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Wed, 10 Jul 2024 10:26:55 -0700 Subject: [PATCH] Updates plugins to use the Catalog and Bindings interfaces (#1502) --- .../plugins/local/LocalConnectorTest.kt | 11 + .../org/partiql/planner/catalog/Catalog.kt | 12 +- .../org/partiql/planner/catalog/Name.kt | 30 +-- .../org/partiql/planner/catalog/Namespace.kt | 9 + .../org/partiql/spi/connector/Connector.kt | 10 +- .../partiql/plugins/local/LocalBindings.kt | 11 + .../org/partiql/plugins/local/LocalCatalog.kt | 140 +++++------ .../partiql/plugins/local/LocalConnector.kt | 102 +++----- .../org/partiql/plugins/local/LocalObject.kt | 31 --- .../org/partiql/plugins/local/LocalTable.kt | 53 +++++ .../local/LocalConnectorMetadataTests.kt | 150 ------------ .../resources/catalogs/local/data/records.ion | 17 -- .../resources/catalogs/local/data/struct.ion | 31 --- plugins/partiql-memory/README.md | 45 ++-- .../partiql/plugins/memory/MemoryBindings.kt | 15 -- .../partiql/plugins/memory/MemoryCatalog.kt | 221 ------------------ .../plugins/memory/MemoryCatalogBuilder.kt | 81 ------- .../partiql/plugins/memory/MemoryConnector.kt | 80 +++++-- .../partiql/plugins/memory/MemoryMetadata.kt | 24 -- .../partiql/plugins/memory/MemoryObject.kt | 32 --- .../partiql/plugins/memory/MemoryPlugin.kt | 5 +- .../org/partiql/plugins/memory/MemoryTable.kt | 60 +++++ .../plugins/memory/MemoryCatalogTest.kt | 111 --------- .../plugins/memory/MemoryConnectorTest.kt | 19 ++ .../internal/fn/scalar/FnDateAddYear.kt | 188 --------------- .../plugin/internal/fn/scalar/FnIsChar.kt | 69 ------ 26 files changed, 349 insertions(+), 1208 deletions(-) create mode 100644 partiql-cli/src/test/kotlin/org/partiql/plugins/local/LocalConnectorTest.kt create mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalBindings.kt delete mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalObject.kt create mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalTable.kt delete mode 100644 plugins/partiql-local/src/test/kotlin/org/partiql/plugins/local/LocalConnectorMetadataTests.kt delete mode 100644 plugins/partiql-local/src/test/resources/catalogs/local/data/records.ion delete mode 100644 plugins/partiql-local/src/test/resources/catalogs/local/data/struct.ion delete mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryBindings.kt delete 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/MemoryCatalogBuilder.kt delete mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryMetadata.kt delete mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryObject.kt create mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryTable.kt delete mode 100644 plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryCatalogTest.kt create mode 100644 plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryConnectorTest.kt delete mode 100644 plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnDateAddYear.kt delete mode 100644 plugins/partiql-plugin/src/main/kotlin/org/partiql/plugin/internal/fn/scalar/FnIsChar.kt diff --git a/partiql-cli/src/test/kotlin/org/partiql/plugins/local/LocalConnectorTest.kt b/partiql-cli/src/test/kotlin/org/partiql/plugins/local/LocalConnectorTest.kt new file mode 100644 index 000000000..c18c1abfc --- /dev/null +++ b/partiql-cli/src/test/kotlin/org/partiql/plugins/local/LocalConnectorTest.kt @@ -0,0 +1,11 @@ +package org.partiql.plugins.local + +import org.junit.jupiter.api.Test + +class LocalConnectorTest { + + @Test + fun sanity() { + TODO("Not yet implemented") + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt index d14999cab..8d2c3033a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt @@ -25,32 +25,32 @@ public interface Catalog { /** * List top-level tables. */ - public fun listTables(): Name? = null + public fun listTables(): Collection = listTables(Namespace.empty()) /** * List all tables under this namespace. * * @param namespace */ - public fun listTables(namespace: Namespace): List = emptyList() + public fun listTables(namespace: Namespace): Collection = emptyList() /** * List top-level namespaces from the catalog. */ - public fun listNamespaces(): Collection = emptyList() + public fun listNamespaces(): Collection = listNamespaces(Namespace.empty()) /** * List all child namespaces from the namespace. * * @param namespace */ - public fun listNamespaces(namespace: Namespace) + public fun listNamespaces(namespace: Namespace): Collection = emptyList() /** - * Get a function's variants by name. + * Get a routine's variants by name. * * @param name The case-sensitive [Routine] name. * @return A collection of all [Routine]s in the current namespace with this name. */ - public fun getFunctions(name: Name): Collection = emptyList() + public fun getRoutines(name: Name): Collection = emptyList() } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt index 747ee8761..347801804 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt @@ -1,24 +1,28 @@ package org.partiql.planner.catalog -import java.util.Spliterator -import java.util.function.Consumer - /** * Thin wrapper over a list of strings. */ -public data class Name(public val steps: List) : Iterable { - - public companion object { +public data class Name( + private val namespace: Namespace, + private val name: String, +) { - @JvmStatic - public fun of(vararg steps: String): Name = Name(steps.toList()) - } + public fun getNamespace(): Namespace = namespace - public operator fun get(index: Int): String = steps[index] + public fun hasNamespace(): Boolean = !namespace.isEmpty() - override fun forEach(action: Consumer?): Unit = steps.forEach(action) + public fun getName(): String = name - override fun iterator(): Iterator = steps.iterator() + public companion object { - override fun spliterator(): Spliterator = steps.spliterator() + @JvmStatic + public fun of(vararg names: String): Name { + assert(names.size > 1) { "Cannot create an empty" } + return Name( + namespace = Namespace.of(*names.drop(1).toTypedArray()), + name = names.last(), + ) + } + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt index b77e3aac3..fbbb603d0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt @@ -67,11 +67,20 @@ public class Namespace private constructor( public fun empty(): Namespace = EMPTY + @JvmStatic public fun of(vararg levels: String): Namespace { if (levels.isEmpty()) { return empty() } return Namespace(arrayOf(*levels)) } + + @JvmStatic + public fun of(levels: Collection): Namespace { + if (levels.isEmpty()) { + return empty() + } + return Namespace(levels.toTypedArray()) + } } } 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 a7b0ab6ba..10b5a72f4 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 @@ -15,10 +15,12 @@ package org.partiql.spi.connector import com.amazon.ionelement.api.StructElement +import com.amazon.ionelement.api.emptyIonStruct import org.partiql.eval.bindings.Bindings +import org.partiql.planner.catalog.Catalog /** - * A mechanism by which PartiQL can access bindings and metadata for a namespace (catalog). + * A mechanism by which PartiQL can access bindings and catalog metadata. */ public interface Connector { @@ -28,9 +30,9 @@ public interface Connector { public fun getBindings(): Bindings /** - * Returns the root namespace of this catalog. + * Returns a [Catalog] which the planner uses to load catalog metadata. */ - public fun getMetadata(): org.partiql.planner.catalog.Catalog + public fun getCatalog(): Catalog /** * A Plugin leverages a [Factory] to produce a [Connector] which is used for binding and metadata access. @@ -48,6 +50,6 @@ public interface Connector { * @param config * @return */ - public fun create(config: StructElement? = null): Connector + public fun create(config: StructElement = emptyIonStruct()): Connector } } 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 000000000..9d8c6d883 --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalBindings.kt @@ -0,0 +1,11 @@ +package org.partiql.plugins.local + +import org.partiql.eval.bindings.Binding +import org.partiql.eval.bindings.Bindings + +internal object LocalBindings : Bindings { + + override fun getBindings(name: String): Bindings? = null + + override fun getBinding(name: String): Binding? = 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 207b5b9a2..4345398ac 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,75 @@ -/* - * 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.Name +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Routine +import org.partiql.planner.catalog.Table import java.nio.file.Path - -private sealed class FsTree(val name: String) { - - // "Directory" node - class D(name: String, val children: List) : FsTree(name) - - // Type node - class T(name: String, val type: StaticType) : FsTree(name) -} +import kotlin.io.path.isDirectory +import kotlin.io.path.notExists /** - * Build a memoized catalog tree from local schema definitions. + * Implementation of [Catalog] where dirs are namespaces and files are table metadata. */ -internal class LocalCatalog private constructor(private val root: FsTree.D) { +internal class LocalCatalog( + private val name: String, + private val root: Path, +) : Catalog { - /** - * Search the tree for the type. - */ - 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) - } - } - } - // All steps matched and we're at a leaf - if (curr is FsTree.T) { - return LocalObject(match, curr.type) - } - return null + private companion object { + private const val EXT = ".ion" + } + + init { + assert(root.isDirectory()) { "LocalNamespace must be a directory" } } - /** - * Provide a list of all objects in this catalog. - */ - public fun listObjects(): List = sequence { search(emptyList(), root) }.toList() + override fun getName(): String { + return name + } - private suspend fun SequenceScope.search(acc: List, node: FsTree) = - when (node) { - is FsTree.D -> search(acc, node) - is FsTree.T -> search(acc, node) + override fun getTable(name: Name): Table? { + val path = toPath(name.getNamespace()).resolve(name.getName() + EXT) + if (path.notExists() || !path.isDirectory()) { + return null } + return LocalTable(name.getName(), path) + } - 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(namespace: Namespace): Collection { + val path = toPath(namespace) + if (path.notExists()) { + // throw exception? + return emptyList() } + return super.listTables(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(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) + override fun getRoutines(name: Name): Collection = emptyList() - private fun File.tree(): FsTree = when (this.isDirectory) { - true -> d() - else -> t() - } - - 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 9072c659c..302355fde 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,18 +15,12 @@ package org.partiql.plugins.local import com.amazon.ionelement.api.StructElement -import org.partiql.spi.BindingPath +import org.partiql.eval.bindings.Bindings +import org.partiql.planner.catalog.Catalog import org.partiql.spi.connector.Connector -import org.partiql.spi.connector.ConnectorAggProvider -import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorFnProvider -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 org.partiql.spi.fn.FnExperimental import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.isDirectory import kotlin.io.path.notExists /** @@ -40,88 +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 CONNECTOR_NAME: String = "local" - // not yet defined in SPI - public fun listObjects(): List = metadata.listObjects() + public const val ROOT_KEY: String = "root" - override fun getMetadata(session: ConnectorSession): ConnectorMetadata = metadata + @JvmStatic + public fun builder(): Builder = Builder() - override fun getBindings(): ConnectorBindings { - TODO("Not yet implemented") - } + public class Builder internal constructor() { - @FnExperimental - override fun getFunctions(): ConnectorFnProvider { - TODO("Not yet implemented") - } + private var name: String? = null - @FnExperimental - override fun getAggregations(): ConnectorAggProvider { - TODO("Not yet implemented") - } + private var root: Path? = null - internal class Factory : Connector.Factory { + public fun name(name: String): Builder = apply { this.name = name } - private val default: Path = Paths.get(System.getProperty("user.home")).resolve(".partiql/local") + public fun root(root: Path): Builder = apply { this.root = root } - override val name: String = CONNECTOR_NAME - - 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(): Bindings = 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 - @FnExperimental - override fun getFunction(path: BindingPath): ConnectorHandle.Fn? { - TODO("Not yet implemented") + 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) } - - @FnExperimental - override fun getAggregation(path: BindingPath): ConnectorHandle.Agg? = null - - internal fun listObjects(): List = catalog.listObjects() } } 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 dda603302..000000000 --- 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 000000000..fa589265c --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalTable.kt @@ -0,0 +1,53 @@ +/* + * 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.bindings.Binding +import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Table +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: String, + private val path: Path, +) : Table, Binding { + + init { + assert(!path.isDirectory()) { "LocalTable path must be a file." } + } + + override fun getName(): String { + return 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 11397f334..000000000 --- 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 884915fa5..000000000 --- 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 cba96f5e2..000000000 --- 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 e26a74f27..c3633cf4f 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.typeFloat32(), + 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 24cec718c..000000000 --- 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 426b65d49..000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt +++ /dev/null @@ -1,221 +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.ConnectorFnProvider -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorPath -import org.partiql.spi.connector.sql.SqlFnProvider -import org.partiql.spi.connector.sql.info.InfoSchema -import org.partiql.spi.fn.FnExperimental - -/** - * 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, public val infoSchema: InfoSchema) { - - @OptIn(FnExperimental::class) - public fun getFunctions(): ConnectorFnProvider = SqlFnProvider(infoSchema.functions) - - 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() - - @JvmStatic - public fun SQL(): MemoryCatalogBuilder = MemoryCatalogBuilder().info(InfoSchema.default()) - - @JvmStatic - public fun PartiQL(): MemoryCatalogBuilder = MemoryCatalogBuilder().info(InfoSchema.ext()) - } - - 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 15d6e06c5..000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalogBuilder.kt +++ /dev/null @@ -1,81 +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.spi.connector.ConnectorPath -import org.partiql.spi.connector.sql.info.InfoSchema -import org.partiql.spi.fn.Agg -import org.partiql.spi.fn.Fn -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.Index -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 _info: InfoSchema? = null - private var _items: MutableList> = mutableListOf() - - public fun name(name: String): MemoryCatalogBuilder = this.apply { this._name = name } - - public fun info(info: InfoSchema): MemoryCatalogBuilder = this.apply { this._info = info } - - /** - * 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) - } - - @OptIn(FnExperimental::class) - public fun build(): MemoryCatalog { - val name = _name ?: error("MemoryCatalog must have a name") - val info = _info ?: InfoSchema( - object : Index { - override fun get(path: List): List = emptyList() - override fun get(path: ConnectorPath, specific: String): Fn? = null - }, - object : Index { - override fun get(path: List): List = emptyList() - override fun get(path: ConnectorPath, specific: String): Agg? = null - } - ) - val catalog = MemoryCatalog(name, info) - 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 ea984ae2e..48276a5f6 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,45 +16,79 @@ package org.partiql.plugins.memory import com.amazon.ionelement.api.StructElement +import org.partiql.eval.bindings.Binding +import org.partiql.eval.bindings.Bindings +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Table import org.partiql.spi.connector.Connector -import org.partiql.spi.connector.ConnectorBindings -import org.partiql.spi.connector.ConnectorFnProvider -import org.partiql.spi.connector.ConnectorSession -import org.partiql.spi.connector.sql.SqlConnector -import org.partiql.spi.connector.sql.SqlMetadata -import org.partiql.spi.fn.FnExperimental /** * This is a plugin used for testing and is not a versioned API per semver. */ -public class MemoryConnector(private val catalog: MemoryCatalog) : SqlConnector() { +public class MemoryConnector private constructor( + private val name: String, + private val tables: Map, +) : Connector { - private val bindings = MemoryBindings(catalog) + override fun getBindings(): Bindings = bindings - override fun getBindings(): ConnectorBindings = bindings + override fun getCatalog(): Catalog = catalog - override fun getMetadata(session: ConnectorSession): SqlMetadata = MemoryMetadata(catalog, session, catalog.infoSchema) - - @OptIn(FnExperimental::class) - override fun getFunctions(): ConnectorFnProvider = catalog.getFunctions() - - 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 { - /** - * A connector whose catalogs holds no binding and all SQL-92 function and PartiQL-Builtin - */ @JvmStatic - public fun partiQL(): MemoryConnector = MemoryConnector(MemoryCatalog.PartiQL().name("default").build()) + 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 } + + public fun createTable(table: MemoryTable): Builder = apply { tables[table.getName()] = table } + + public fun build(): MemoryConnector = MemoryConnector(name!!, tables) + } + } + + /** + * Implement [Bindings] over the tables map. + */ + private val bindings = object : Bindings { + override fun getBindings(name: String): Bindings? = null + override fun getBinding(name: String): Binding? = tables[name] + } + + /** + * Implement [Catalog] over the tables map. + */ + private val catalog = object : Catalog { + + override fun getName(): String = name + + override fun getTable(name: Name): Table? { + if (name.hasNamespace()) { + error("MemoryCatalog does not support namespaces") + } + return tables[name.getName()] + } + + override fun listTables(): Collection { + return tables.keys.map { Name.of(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 2818d8580..000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryMetadata.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.partiql.plugins.memory - -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorSession -import org.partiql.spi.connector.sql.SqlMetadata -import org.partiql.spi.connector.sql.info.InfoSchema -import org.partiql.spi.fn.FnExperimental - -internal class MemoryMetadata( - private val catalog: MemoryCatalog, - session: ConnectorSession, - info: InfoSchema, -) : SqlMetadata(session, info) { - - override fun getObject(path: BindingPath): ConnectorHandle.Obj? { - return super.getObject(path) ?: catalog.find(path) - } - - @FnExperimental - override fun getFunction(path: BindingPath): ConnectorHandle.Fn? { - return super.getFunction(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 881ec291a..000000000 --- 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 d282dd1be..21d4cba48 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 000000000..6872cc3c5 --- /dev/null +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryTable.kt @@ -0,0 +1,60 @@ +package org.partiql.plugins.memory + +import org.partiql.eval.bindings.Binding +import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Table +import org.partiql.types.PType + +public class MemoryTable private constructor( + private val name: String, + private val type: PType, + private val datum: Datum, +) : Table, Binding { + + override fun getName(): String = name + override fun getSchema(): PType = type + override fun getDatum(): Datum = datum + + public companion object { + + /** + * Create an empty table with dynamic schema. + */ + @JvmStatic + public fun empty(name: String): MemoryTable = MemoryTable( + name = name, + type = PType.typeDynamic(), + datum = Datum.nullValue(), + ) + + /** + * Create an empty table with known schema. + */ + @JvmStatic + public fun empty(name: String, schema: PType): MemoryTable = MemoryTable( + name = name, + type = schema, + datum = Datum.nullValue(), + ) + + /** + * Create a table from a Datum with dynamic schema. + */ + @JvmStatic + public fun of(name: String, value: Datum): MemoryTable = MemoryTable( + name = name, + type = PType.typeDynamic(), + datum = value, + ) + + /** + * Create a table from a Datum with known schema. + */ + @JvmStatic + public fun of(name: String, value: Datum, schema: PType): MemoryTable = MemoryTable( + name = name, + type = schema, + datum = value, + ) + } +} 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 210b7779a..000000000 --- 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 000000000..00e4b86d3 --- /dev/null +++ b/plugins/partiql-memory/src/test/kotlin/org/partiql/plugins/memory/MemoryConnectorTest.kt @@ -0,0 +1,19 @@ +package org.partiql.plugins.memory + +import org.junit.jupiter.api.Test + +class MemoryConnectorTest { + + @Test + fun sanity() { + val connector = MemoryConnector.builder() + .name("default") + .createTable(MemoryTable.empty("a")) + .createTable(MemoryTable.empty("b")) + .createTable(MemoryTable.empty("c")) + .build() + val catalog = connector.getCatalog() + assert(catalog.listTables().size == 3) + assert(catalog.listNamespaces().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 c8b736c23..000000000 --- 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 ae010524e..000000000 --- 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) - } - } -}