From 8faaf06537d1e0ec2ca44a9ef1067c736824a71e Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 24 Jun 2024 09:49:57 -0700 Subject: [PATCH 01/15] Reset partiql-spi closer to main branch --- .../main/kotlin/org/partiql/planner/catalog/Name.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 596b97c4a..432b2dd56 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,4 +1,17 @@ package org.partiql.planner.catalog +/* + * 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. + */ /** * A reference to a named object in a catalog; case-preserved. From 807c12918cd1ad06672f6775b02971a0bc3eb765 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 27 Jun 2024 10:31:44 -0700 Subject: [PATCH 02/15] Adds v1 connector interfaces over bindings and metadata --- .../org/partiql/planner/metadata/Metadata.kt | 12 +++ .../org/partiql/planner/metadata/Name.kt | 26 ++++++ .../org/partiql/planner/metadata/Namespace.kt | 61 +++++++++++++ .../org/partiql/planner/metadata/Routine.kt | 91 +++++++++++++++++++ .../org/partiql/planner/metadata/Table.kt | 40 ++++++++ 5 files changed, 230 insertions(+) create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt new file mode 100644 index 000000000..b525c2487 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt @@ -0,0 +1,12 @@ +package org.partiql.planner.metadata + +/** + * Top-level metadata interface for access to object descriptors. + */ +public interface Metadata { + + /** + * The root namespace. + */ + public fun getNamespace(): Namespace +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt new file mode 100644 index 000000000..9cf27077a --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt @@ -0,0 +1,26 @@ +package org.partiql.planner.metadata + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * Thin wrapper over a list of strings. + * + * @property steps + */ +public data class Name(public val steps: List) : Iterable { + + public companion object { + + @JvmStatic + public fun of(vararg steps: String): Name = Name(steps.toList()) + } + + public operator fun get(index: Int): String = steps[index] + + override fun forEach(action: Consumer?): Unit = steps.forEach(action) + + override fun iterator(): Iterator = steps.iterator() + + override fun spliterator(): Spliterator = steps.spliterator() +} \ No newline at end of file diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt new file mode 100644 index 000000000..750c43eba --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt @@ -0,0 +1,61 @@ +package org.partiql.planner.metadata + +/** + * A [Namespace] is a namespace for the following objects; top-level namespaces are called "Catalogs". + * + * . Tables + * . Base + * . View + * . Index + * . Routines + * . Functions + * . Procedures + * . Namespaces + * . Types + * + * See, https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java + */ +public interface Namespace { + + /** + * The [Namespace] name. + */ + public fun getName(): String + + /** + * Get a table by name. + * + * @param name The case-sensitive [Table] name. + * @return The [Table] or null if not found. + */ + public fun getTable(name: String): Table? = null + + /** + * Get a function'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: String): Collection = DEFAULT_FUNCTIONS + + /** + * Get a sub-namespace by name. + * + * @param name + * @return + */ + public fun getNamespace(name: String): Namespace? = null + + /** + * Get all sub-namespaces. + */ + public fun getNamespaces(): Collection = DEFAULT_SCOPES + + /** + * Memoized defaults. + */ + private companion object { + val DEFAULT_FUNCTIONS = emptyList() + val DEFAULT_SCOPES = emptyList() + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt new file mode 100644 index 000000000..a58453aa9 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt @@ -0,0 +1,91 @@ +package org.partiql.planner.metadata + +import org.partiql.types.PType + +/** + * A [Routine] is a PartiQL-routine callable from an expression context. + */ +public sealed interface Routine { + + /** + * The function name. Required. + */ + public fun getName(): String + + /** + * The formal argument definitions. Optional. + */ + public fun getParameters(): Array = DEFAULT_PARAMETERS + + /** + * The function return type. Required. + */ + public fun getReturnType(): PType.Kind + + /** + * Represents an SQL row-value expression call. + */ + public interface Operator : Routine { + public fun getSymbol(): String + public fun getLHS(): PType.Kind? + public fun getRHS(): PType.Kind + } + + /** + * Represents an SQL row-value expression call. + */ + public interface Scalar : Routine { + + /** + * Additional function properties useful for planning. Optional. + */ + public fun getProperties(): Properties = DEFAULT_PROPERTIES + } + + /** + * Represents an SQL table-value expression call. + */ + public interface Aggregation : Routine + + /** + * [Parameter] is a formal argument's definition. + * + * @property name + * @property type + */ + public data class Parameter( + @JvmField public val name: String, + @JvmField public val type: PType.Kind, + ) + + /** + * PartiQL-function properties. + * + * @property isNullCall + */ + public data class Properties( + @JvmField public val isNullCall: Boolean = false, + ) + + /** + * Memoized defaults. + */ + public companion object { + + private val DEFAULT_PARAMETERS = emptyArray() + private val DEFAULT_PROPERTIES = Properties(isNullCall = true) + + @JvmOverloads + public fun scalar( + name: String, + parameters: Collection, + returnType: PType.Kind, + properties: Properties = DEFAULT_PROPERTIES, + ): Scalar = object : Scalar { + override fun getName(): String = name + override fun getParameters(): Array = parameters.toTypedArray() + override fun getReturnType(): PType.Kind = returnType + override fun getProperties(): Properties = properties + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt new file mode 100644 index 000000000..8022f014d --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt @@ -0,0 +1,40 @@ +package org.partiql.planner.metadata + +import org.partiql.types.PType + +/** + * In PartiQL, a [Table] can take on any shape and is not necessarily rows+columns. + * + * From Calcite, + * + * ''' + * Note that a table does not know its name. It is in fact possible for + * a table to be used more than once, perhaps under multiple names or under + * multiple schemas. (Compare with the i-node concept + * in the UNIX filesystem.) + * ''' + * + * See, https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Table.java + */ +public interface Table { + + /** + * The table's kind. + */ + public fun getKind(): Kind = Kind.TABLE + + /** + * The table's schema. + */ + public fun getSchema(): PType = PType.typeDynamic() + + /** + * A [Table] can be one of several [Kind]s. + */ + public enum class Kind { + TABLE, + VIEW, + INDEX, + OTHER, + } +} \ No newline at end of file From 2d27658da84eb6bb8d98fa6ed1946ed9df0188a9 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 27 Jun 2024 11:57:24 -0700 Subject: [PATCH 03/15] Updates memory plugin to v1 interfaces --- .../org/partiql/spi/connector/Connector.kt | 6 +- .../partiql/plugins/memory/MemoryCatalog.kt | 84 +++++++++++++++++++ .../partiql/plugins/memory/MemoryConnector.kt | 64 ++------------ .../partiql/plugins/memory/MemoryNamespace.kt | 39 +++++++++ .../org/partiql/plugins/memory/MemoryTable.kt | 17 ++-- 5 files changed, 141 insertions(+), 69 deletions(-) create mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt create mode 100644 plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt 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 10b5a72f4..4fc51c7b8 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 @@ -17,7 +17,7 @@ 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 +import org.partiql.planner.metadata.Metadata /** * A mechanism by which PartiQL can access bindings and catalog metadata. @@ -30,9 +30,9 @@ public interface Connector { public fun getBindings(): Bindings /** - * Returns a [Catalog] which the planner uses to load catalog metadata. + * Returns the root namespace of this catalog. */ - public fun getCatalog(): Catalog + public fun getMetadata(): org.partiql.planner.catalog.Catalog /** * A Plugin leverages a [Factory] to produce a [Connector] which is used for binding and metadata access. 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 000000000..3f7a2c247 --- /dev/null +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt @@ -0,0 +1,84 @@ +/* + * 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.eval.bindings.Binding +import org.partiql.eval.bindings.Bindings +import org.partiql.planner.metadata.Metadata +import org.partiql.planner.metadata.Namespace + +/** + * A basic connector implementation used in testing. + * + * TODO incorporate the nested namespaces possible in the current v1 memory plugin. + * I'm keeping this simple for now before APIs stabilize. + * + * https://github.com/partiql/partiql-lang-kotlin/commit/f8cbeb83a8d4ba8f5218b9db016e0661d778441e + */ +public class MemoryCatalog private constructor( + private val name: String, + private val tables: Map, +) { + + // these could be anonymous, but I thought it was cleaner to separate. + private val bindings = MBindings() + private val metadata = MMetadata() + + public fun getBindings(): Bindings = bindings + public fun getMetadata(): Metadata = metadata + + 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 defineTable(name: String, table: MemoryTable): Builder = apply { tables[name] = table } + + public fun build(): MemoryCatalog = MemoryCatalog(name!!, tables) + } + + public companion object { + + @JvmStatic + public fun builder(): Builder = Builder() + } + + private inner class MBindings : Bindings { + override fun getBindings(name: String): Bindings? = null + override fun getBinding(name: String): Binding? = tables[name] + } + + private inner class MMetadata : Metadata { + + /** + * Build a root namespace. + */ + private val root = MemoryNamespace.builder() + .name(name) + .apply { + for (table in tables) { + val n = table.key + val t = table.value + defineTable(n, t) + } + } + .build() + + override fun getNamespace(): Namespace = root + } +} 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 48276a5f6..abb90646f 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,24 +16,23 @@ 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.planner.metadata.Metadata import org.partiql.spi.connector.Connector /** * This is a plugin used for testing and is not a versioned API per semver. */ -public class MemoryConnector private constructor( - private val name: String, - private val tables: Map, -) : Connector { +public class MemoryConnector private constructor(private val catalog: MemoryCatalog) : Connector { - override fun getBindings(): Bindings = bindings + public companion object { + + @JvmStatic + public fun from(catalog: MemoryCatalog): MemoryConnector = MemoryConnector(catalog) + } - override fun getCatalog(): Catalog = catalog + override fun getBindings(): Bindings = catalog.getBindings() + override fun getMetadata(): Metadata = catalog.getMetadata() /** * For use with ServiceLoader to instantiate a connector from an Ion config. @@ -46,49 +45,4 @@ public class MemoryConnector private constructor( TODO("Instantiation of a MemoryConnector via the factory is currently not supported") } } - - public companion object { - - @JvmStatic - public fun builder(): Builder = Builder() - - public class Builder internal constructor() { - - private var name: String? = null - private var tables: MutableMap = mutableMapOf() - - public fun name(name: String): Builder = apply { this.name = name } - - 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/MemoryNamespace.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt new file mode 100644 index 000000000..b1aeb5ef6 --- /dev/null +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt @@ -0,0 +1,39 @@ +package org.partiql.plugins.memory + +import org.partiql.planner.metadata.Namespace +import org.partiql.planner.metadata.Table + +/** + * Namespace implementation. + */ +public class MemoryNamespace private constructor( + private val name: String, + private val tables: Map, + private val namespaces: Map, +) : Namespace { + override fun getName(): String = name + override fun getTable(name: String): Table? = tables[name] + override fun getNamespace(name: String): Namespace? = namespaces[name] + override fun getNamespaces(): Collection = namespaces.values + + public class Builder internal constructor() { + + private var name: String? = null + private var tables: MutableMap = mutableMapOf() + private var namespaces: MutableMap = mutableMapOf() + + public fun name(name: String): Builder = apply { this.name = name } + + public fun defineTable(name: String, table: MemoryTable): Builder = apply { tables[name] = table } + + public fun defineNamespace(namespace: MemoryNamespace): Builder = apply { namespaces[namespace.getName()] = namespace } + + public fun build(): MemoryNamespace = MemoryNamespace(name!!, tables, namespaces) + } + + public companion object { + + @JvmStatic + public fun builder(): Builder = Builder() + } +} 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 index 6872cc3c5..a3a2f6c7e 100644 --- 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 @@ -2,16 +2,15 @@ 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.planner.metadata.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 getKind(): Table.Kind = Table.Kind.TABLE override fun getSchema(): PType = type override fun getDatum(): Datum = datum @@ -21,8 +20,7 @@ public class MemoryTable private constructor( * Create an empty table with dynamic schema. */ @JvmStatic - public fun empty(name: String): MemoryTable = MemoryTable( - name = name, + public fun empty(): MemoryTable = MemoryTable( type = PType.typeDynamic(), datum = Datum.nullValue(), ) @@ -31,8 +29,7 @@ public class MemoryTable private constructor( * Create an empty table with known schema. */ @JvmStatic - public fun empty(name: String, schema: PType): MemoryTable = MemoryTable( - name = name, + public fun empty(schema: PType): MemoryTable = MemoryTable( type = schema, datum = Datum.nullValue(), ) @@ -41,8 +38,7 @@ public class MemoryTable private constructor( * Create a table from a Datum with dynamic schema. */ @JvmStatic - public fun of(name: String, value: Datum): MemoryTable = MemoryTable( - name = name, + public fun of(value: Datum): MemoryTable = MemoryTable( type = PType.typeDynamic(), datum = value, ) @@ -51,8 +47,7 @@ public class MemoryTable private constructor( * Create a table from a Datum with known schema. */ @JvmStatic - public fun of(name: String, value: Datum, schema: PType): MemoryTable = MemoryTable( - name = name, + public fun of(value: Datum, schema: PType): MemoryTable = MemoryTable( type = schema, datum = value, ) From 245c6da254007eaf16ada8bdf28fdafbc14bb6d6 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 27 Jun 2024 14:29:23 -0700 Subject: [PATCH 04/15] Updates local plugin --- .../org/partiql/plugins/local/LocalCatalog.kt | 75 ------------------- .../partiql/plugins/local/LocalConnector.kt | 34 ++------- .../partiql/plugins/local/LocalNamespace.kt | 42 +++++++++++ .../org/partiql/plugins/local/LocalTable.kt | 16 +--- 4 files changed, 52 insertions(+), 115 deletions(-) delete mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt create mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt 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 deleted file mode 100644 index 4345398ac..000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.partiql.plugins.local - -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 -import kotlin.io.path.isDirectory -import kotlin.io.path.notExists - -/** - * Implementation of [Catalog] where dirs are namespaces and files are table metadata. - */ -internal class LocalCatalog( - 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" } - } - - override fun getName(): String { - return name - } - - 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) - } - - override fun listTables(namespace: Namespace): Collection { - val path = toPath(namespace) - if (path.notExists()) { - // throw exception? - return emptyList() - } - return super.listTables(namespace) - } - - 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()) } - } - - override fun getRoutines(name: Name): Collection = emptyList() - - private fun toPath(namespace: Namespace): Path { - var curr = root - for (level in namespace) { - curr = curr.resolve(level) - } - return curr - } - - 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 302355fde..9f2556427 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 @@ -16,7 +16,8 @@ package org.partiql.plugins.local import com.amazon.ionelement.api.StructElement import org.partiql.eval.bindings.Bindings -import org.partiql.planner.catalog.Catalog +import org.partiql.planner.metadata.Metadata +import org.partiql.planner.metadata.Namespace import org.partiql.spi.connector.Connector import java.nio.file.Path import java.nio.file.Paths @@ -35,39 +36,18 @@ import kotlin.io.path.notExists * } * ``` */ -public class LocalConnector private constructor( - private val name: String, - private val root: Path, -) : Connector { - - private val catalog = LocalCatalog(name, root) +public class LocalConnector(private val root: Path) : Connector { 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() { - - 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(): LocalConnector = LocalConnector(name!!, root!!) - } } override fun getBindings(): Bindings = LocalBindings - override fun getCatalog(): Catalog = catalog + override fun getMetadata(): Metadata = object : Metadata { + override fun getNamespace(): Namespace = LocalNamespace(root) + } internal class Factory : Connector.Factory { @@ -81,7 +61,7 @@ public class LocalConnector private constructor( if (root.notExists() || !root.isDirectory()) { error("Invalid catalog `$root`") } - return LocalConnector("default", root) + return LocalConnector(root) } } } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt new file mode 100644 index 000000000..edc66811d --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt @@ -0,0 +1,42 @@ +package org.partiql.plugins.local + +import org.partiql.planner.metadata.Namespace +import org.partiql.planner.metadata.Table +import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.isRegularFile +import kotlin.io.path.nameWithoutExtension + +/** + * Thin wrapper over a directory. + * - subdir -> namespace + * - file -> table + */ +public class LocalNamespace(private val path: Path) : Namespace { + + init { + assert(path.isDirectory()) { "LocalNamespace must be a directory" } + } + + override fun getName(): String = path.nameWithoutExtension + + override fun getNamespaces(): Collection { + return path.toFile().listFiles { f, _ -> f.isDirectory }!!.map { LocalNamespace(it.toPath()) } + } + + override fun getNamespace(name: String): Namespace? { + val p = path.resolve(name) + return when (p.isDirectory()) { + true -> LocalNamespace(p) + else -> null + } + } + + override fun getTable(name: String): Table? { + val p = path.resolve(name) + return when (p.isRegularFile()) { + true -> LocalTable(p) + else -> null + } + } +} 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 index fa589265c..a63284215 100644 --- 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 @@ -18,28 +18,18 @@ 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.planner.metadata.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 { +internal class LocalTable(private val path: Path) : Table, Binding { - init { - assert(!path.isDirectory()) { "LocalTable path must be a file." } - } - - override fun getName(): String { - return name - } + override fun getKind(): Table.Kind = Table.Kind.TABLE override fun getSchema(): PType { val reader = IonReaderBuilder.standard().build(path.reader()) From ab878b2221d2579448f3abd2540b1cf991e8dc2a Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 8 Jul 2024 13:49:05 -0700 Subject: [PATCH 05/15] Make Catalog the primary interface --- .../org/partiql/planner/catalog/Name.kt | 92 +++---------------- .../org/partiql/planner/metadata/Metadata.kt | 12 --- .../org/partiql/planner/metadata/Name.kt | 26 ------ .../org/partiql/planner/metadata/Namespace.kt | 61 ------------ .../org/partiql/planner/metadata/Routine.kt | 91 ------------------ .../org/partiql/planner/metadata/Table.kt | 40 -------- 6 files changed, 12 insertions(+), 310 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt 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 432b2dd56..747ee8761 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,92 +1,24 @@ package org.partiql.planner.catalog -/* - * 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. - */ + +import java.util.Spliterator +import java.util.function.Consumer /** - * A reference to a named object in a catalog; case-preserved. + * Thin wrapper over a list of strings. */ -public class Name( - private val namespace: Namespace, - private val name: String, -) { - - /** - * Returns the unqualified name part. - */ - public fun getName(): String = name - - /** - * Returns the name's namespace. - */ - public fun getNamespace(): Namespace = namespace +public data class Name(public val steps: List) : Iterable { - /** - * Returns true if the namespace is non-empty. - */ - public fun hasNamespace(): Boolean = !namespace.isEmpty() - - /** - * Compares two names including their namespaces and symbols. - */ - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null || javaClass != other.javaClass) { - return false - } - other as Name - return (this.name == other.name) && (this.namespace == other.namespace) - } + public companion object { - /** - * The hashCode() is case-sensitive. - */ - override fun hashCode(): Int { - var result = 1 - result = 31 * result + namespace.hashCode() - result = 31 * result + name.hashCode() - return result + @JvmStatic + public fun of(vararg steps: String): Name = Name(steps.toList()) } - /** - * Return the SQL name representation of this name — all parts delimited. - */ - override fun toString(): String { - val parts = mutableListOf() - parts.addAll(namespace.getLevels()) - parts.add(name) - return Identifier.of(parts).toString() - } + public operator fun get(index: Int): String = steps[index] - public companion object { + override fun forEach(action: Consumer?): Unit = steps.forEach(action) - /** - * Construct a name from a string. - */ - @JvmStatic - public fun of(vararg names: String): Name = of(names.toList()) + override fun iterator(): Iterator = steps.iterator() - /** - * Construct a name from a collection of strings. - */ - @JvmStatic - public fun of(names: Collection): Name { - assert(names.size > 1) { "Cannot create an empty name" } - val namespace = Namespace.of(names.drop(1)) - val name = names.last() - return Name(namespace, name) - } - } + override fun spliterator(): Spliterator = steps.spliterator() } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt deleted file mode 100644 index b525c2487..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Metadata.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.partiql.planner.metadata - -/** - * Top-level metadata interface for access to object descriptors. - */ -public interface Metadata { - - /** - * The root namespace. - */ - public fun getNamespace(): Namespace -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt deleted file mode 100644 index 9cf27077a..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Name.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.partiql.planner.metadata - -import java.util.Spliterator -import java.util.function.Consumer - -/** - * Thin wrapper over a list of strings. - * - * @property steps - */ -public data class Name(public val steps: List) : Iterable { - - public companion object { - - @JvmStatic - public fun of(vararg steps: String): Name = Name(steps.toList()) - } - - public operator fun get(index: Int): String = steps[index] - - override fun forEach(action: Consumer?): Unit = steps.forEach(action) - - override fun iterator(): Iterator = steps.iterator() - - override fun spliterator(): Spliterator = steps.spliterator() -} \ No newline at end of file diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt deleted file mode 100644 index 750c43eba..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Namespace.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.partiql.planner.metadata - -/** - * A [Namespace] is a namespace for the following objects; top-level namespaces are called "Catalogs". - * - * . Tables - * . Base - * . View - * . Index - * . Routines - * . Functions - * . Procedures - * . Namespaces - * . Types - * - * See, https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java - */ -public interface Namespace { - - /** - * The [Namespace] name. - */ - public fun getName(): String - - /** - * Get a table by name. - * - * @param name The case-sensitive [Table] name. - * @return The [Table] or null if not found. - */ - public fun getTable(name: String): Table? = null - - /** - * Get a function'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: String): Collection = DEFAULT_FUNCTIONS - - /** - * Get a sub-namespace by name. - * - * @param name - * @return - */ - public fun getNamespace(name: String): Namespace? = null - - /** - * Get all sub-namespaces. - */ - public fun getNamespaces(): Collection = DEFAULT_SCOPES - - /** - * Memoized defaults. - */ - private companion object { - val DEFAULT_FUNCTIONS = emptyList() - val DEFAULT_SCOPES = emptyList() - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt deleted file mode 100644 index a58453aa9..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Routine.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.partiql.planner.metadata - -import org.partiql.types.PType - -/** - * A [Routine] is a PartiQL-routine callable from an expression context. - */ -public sealed interface Routine { - - /** - * The function name. Required. - */ - public fun getName(): String - - /** - * The formal argument definitions. Optional. - */ - public fun getParameters(): Array = DEFAULT_PARAMETERS - - /** - * The function return type. Required. - */ - public fun getReturnType(): PType.Kind - - /** - * Represents an SQL row-value expression call. - */ - public interface Operator : Routine { - public fun getSymbol(): String - public fun getLHS(): PType.Kind? - public fun getRHS(): PType.Kind - } - - /** - * Represents an SQL row-value expression call. - */ - public interface Scalar : Routine { - - /** - * Additional function properties useful for planning. Optional. - */ - public fun getProperties(): Properties = DEFAULT_PROPERTIES - } - - /** - * Represents an SQL table-value expression call. - */ - public interface Aggregation : Routine - - /** - * [Parameter] is a formal argument's definition. - * - * @property name - * @property type - */ - public data class Parameter( - @JvmField public val name: String, - @JvmField public val type: PType.Kind, - ) - - /** - * PartiQL-function properties. - * - * @property isNullCall - */ - public data class Properties( - @JvmField public val isNullCall: Boolean = false, - ) - - /** - * Memoized defaults. - */ - public companion object { - - private val DEFAULT_PARAMETERS = emptyArray() - private val DEFAULT_PROPERTIES = Properties(isNullCall = true) - - @JvmOverloads - public fun scalar( - name: String, - parameters: Collection, - returnType: PType.Kind, - properties: Properties = DEFAULT_PROPERTIES, - ): Scalar = object : Scalar { - override fun getName(): String = name - override fun getParameters(): Array = parameters.toTypedArray() - override fun getReturnType(): PType.Kind = returnType - override fun getProperties(): Properties = properties - } - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt deleted file mode 100644 index 8022f014d..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/metadata/Table.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.partiql.planner.metadata - -import org.partiql.types.PType - -/** - * In PartiQL, a [Table] can take on any shape and is not necessarily rows+columns. - * - * From Calcite, - * - * ''' - * Note that a table does not know its name. It is in fact possible for - * a table to be used more than once, perhaps under multiple names or under - * multiple schemas. (Compare with the i-node concept - * in the UNIX filesystem.) - * ''' - * - * See, https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Table.java - */ -public interface Table { - - /** - * The table's kind. - */ - public fun getKind(): Kind = Kind.TABLE - - /** - * The table's schema. - */ - public fun getSchema(): PType = PType.typeDynamic() - - /** - * A [Table] can be one of several [Kind]s. - */ - public enum class Kind { - TABLE, - VIEW, - INDEX, - OTHER, - } -} \ No newline at end of file From 1a6d5daddba1e585ea75593f6b2fbb7c18e52b3b Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 8 Jul 2024 14:48:26 -0700 Subject: [PATCH 06/15] Updates MemoryConnector --- .../org/partiql/planner/catalog/Catalog.kt | 20 ++--- .../org/partiql/planner/catalog/Name.kt | 2 + .../partiql/plugins/memory/MemoryCatalog.kt | 84 ------------------- .../partiql/plugins/memory/MemoryConnector.kt | 64 ++++++++++++-- .../partiql/plugins/memory/MemoryNamespace.kt | 39 --------- .../org/partiql/plugins/memory/MemoryTable.kt | 17 ++-- 6 files changed, 77 insertions(+), 149 deletions(-) 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/MemoryNamespace.kt 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 bf98dbcde..cff4f3e27 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 @@ -16,37 +16,35 @@ public interface Catalog { /** * Get a table by name. + * + * @param name The case-sensitive [Table] name. + * @return The [Table] or null if not found. */ - public fun getTable(session: Session, name: Name): Table? = null - - /** - * Get a table by identifier; note that identifiers may be case-insensitive. - */ - public fun getTable(session: Session, identifier: Identifier): Table? = null + public fun getTable(name: Name): Table? = null /** * List top-level tables. */ - public fun listTables(session: Session): Collection = listTables(session, Namespace.root()) + public fun listTables(): Collection = emptyList() /** * List all tables under this namespace. * * @param namespace */ - public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() + public fun listTables(namespace: Namespace): Collection = emptyList() /** * List top-level namespaces from the catalog. */ - public fun listNamespaces(session: Session): Collection = listNamespaces(session, Namespace.root()) + public fun listNamespaces(): Collection = emptyList() /** * List all child namespaces from the namespace. * * @param namespace */ - public fun listNamespaces(session: Session, namespace: Namespace): Collection = emptyList() + public fun listNamespaces(namespace: Namespace): Collection = emptyList() /** * Get a routine's variants by name. @@ -54,5 +52,5 @@ public interface Catalog { * @param name The case-sensitive [Routine] name. * @return A collection of all [Routine]s in the current namespace with this name. */ - public fun getRoutines(session: Session, 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..30235da7e 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 @@ -14,6 +14,8 @@ public data class Name(public val steps: List) : Iterable { public fun of(vararg steps: String): Name = Name(steps.toList()) } + public fun getLength(): Int = steps.size + public operator fun get(index: Int): String = steps[index] override fun forEach(action: Consumer?): Unit = steps.forEach(action) 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 3f7a2c247..000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryCatalog.kt +++ /dev/null @@ -1,84 +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.eval.bindings.Binding -import org.partiql.eval.bindings.Bindings -import org.partiql.planner.metadata.Metadata -import org.partiql.planner.metadata.Namespace - -/** - * A basic connector implementation used in testing. - * - * TODO incorporate the nested namespaces possible in the current v1 memory plugin. - * I'm keeping this simple for now before APIs stabilize. - * - * https://github.com/partiql/partiql-lang-kotlin/commit/f8cbeb83a8d4ba8f5218b9db016e0661d778441e - */ -public class MemoryCatalog private constructor( - private val name: String, - private val tables: Map, -) { - - // these could be anonymous, but I thought it was cleaner to separate. - private val bindings = MBindings() - private val metadata = MMetadata() - - public fun getBindings(): Bindings = bindings - public fun getMetadata(): Metadata = metadata - - 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 defineTable(name: String, table: MemoryTable): Builder = apply { tables[name] = table } - - public fun build(): MemoryCatalog = MemoryCatalog(name!!, tables) - } - - public companion object { - - @JvmStatic - public fun builder(): Builder = Builder() - } - - private inner class MBindings : Bindings { - override fun getBindings(name: String): Bindings? = null - override fun getBinding(name: String): Binding? = tables[name] - } - - private inner class MMetadata : Metadata { - - /** - * Build a root namespace. - */ - private val root = MemoryNamespace.builder() - .name(name) - .apply { - for (table in tables) { - val n = table.key - val t = table.value - defineTable(n, t) - } - } - .build() - - override fun getNamespace(): Namespace = root - } -} 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 abb90646f..48e76c995 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,23 +16,24 @@ 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.metadata.Metadata +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Table import org.partiql.spi.connector.Connector /** * This is a plugin used for testing and is not a versioned API per semver. */ -public class MemoryConnector private constructor(private val catalog: MemoryCatalog) : Connector { +public class MemoryConnector private constructor( + private val name: String, + private val tables: Map, +) : Connector { - public companion object { - - @JvmStatic - public fun from(catalog: MemoryCatalog): MemoryConnector = MemoryConnector(catalog) - } + override fun getBindings(): Bindings = bindings - override fun getBindings(): Bindings = catalog.getBindings() - override fun getMetadata(): Metadata = catalog.getMetadata() + override fun getCatalog(): Catalog = catalog /** * For use with ServiceLoader to instantiate a connector from an Ion config. @@ -45,4 +46,49 @@ public class MemoryConnector private constructor(private val catalog: MemoryCata TODO("Instantiation of a MemoryConnector via the factory is currently not supported") } } + + public companion object { + + @JvmStatic + public fun builder(): Builder = Builder() + + public class Builder internal constructor() { + + private var name: String? = null + private var tables: MutableMap = mutableMapOf() + + public fun name(name: String): Builder = apply { this.name = name } + + 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.getLength() > 1) { + error("MemoryCatalog does not support namespaces") + } + return tables[name[0]] + } + + override fun listTables(): Collection { + return tables.keys.map { Name.of(it) } + } + } } diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt deleted file mode 100644 index b1aeb5ef6..000000000 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryNamespace.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.partiql.plugins.memory - -import org.partiql.planner.metadata.Namespace -import org.partiql.planner.metadata.Table - -/** - * Namespace implementation. - */ -public class MemoryNamespace private constructor( - private val name: String, - private val tables: Map, - private val namespaces: Map, -) : Namespace { - override fun getName(): String = name - override fun getTable(name: String): Table? = tables[name] - override fun getNamespace(name: String): Namespace? = namespaces[name] - override fun getNamespaces(): Collection = namespaces.values - - public class Builder internal constructor() { - - private var name: String? = null - private var tables: MutableMap = mutableMapOf() - private var namespaces: MutableMap = mutableMapOf() - - public fun name(name: String): Builder = apply { this.name = name } - - public fun defineTable(name: String, table: MemoryTable): Builder = apply { tables[name] = table } - - public fun defineNamespace(namespace: MemoryNamespace): Builder = apply { namespaces[namespace.getName()] = namespace } - - public fun build(): MemoryNamespace = MemoryNamespace(name!!, tables, namespaces) - } - - public companion object { - - @JvmStatic - public fun builder(): Builder = Builder() - } -} 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 index a3a2f6c7e..6872cc3c5 100644 --- 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 @@ -2,15 +2,16 @@ package org.partiql.plugins.memory import org.partiql.eval.bindings.Binding import org.partiql.eval.value.Datum -import org.partiql.planner.metadata.Table +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 getKind(): Table.Kind = Table.Kind.TABLE + override fun getName(): String = name override fun getSchema(): PType = type override fun getDatum(): Datum = datum @@ -20,7 +21,8 @@ public class MemoryTable private constructor( * Create an empty table with dynamic schema. */ @JvmStatic - public fun empty(): MemoryTable = MemoryTable( + public fun empty(name: String): MemoryTable = MemoryTable( + name = name, type = PType.typeDynamic(), datum = Datum.nullValue(), ) @@ -29,7 +31,8 @@ public class MemoryTable private constructor( * Create an empty table with known schema. */ @JvmStatic - public fun empty(schema: PType): MemoryTable = MemoryTable( + public fun empty(name: String, schema: PType): MemoryTable = MemoryTable( + name = name, type = schema, datum = Datum.nullValue(), ) @@ -38,7 +41,8 @@ public class MemoryTable private constructor( * Create a table from a Datum with dynamic schema. */ @JvmStatic - public fun of(value: Datum): MemoryTable = MemoryTable( + public fun of(name: String, value: Datum): MemoryTable = MemoryTable( + name = name, type = PType.typeDynamic(), datum = value, ) @@ -47,7 +51,8 @@ public class MemoryTable private constructor( * Create a table from a Datum with known schema. */ @JvmStatic - public fun of(value: Datum, schema: PType): MemoryTable = MemoryTable( + public fun of(name: String, value: Datum, schema: PType): MemoryTable = MemoryTable( + name = name, type = schema, datum = value, ) From 66a7889d809afb7b78001ca75642ef2d0f5e08de Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 8 Jul 2024 16:14:20 -0700 Subject: [PATCH 07/15] Updates partiql-local plugin --- .../org/partiql/planner/catalog/Catalog.kt | 4 +- .../org/partiql/planner/catalog/Name.kt | 32 ++++---- .../org/partiql/planner/catalog/Namespace.kt | 21 ++---- .../org/partiql/plugins/local/LocalCatalog.kt | 75 +++++++++++++++++++ .../partiql/plugins/local/LocalConnector.kt | 34 +++++++-- .../partiql/plugins/local/LocalNamespace.kt | 42 ----------- .../org/partiql/plugins/local/LocalTable.kt | 16 +++- .../partiql/plugins/memory/MemoryConnector.kt | 4 +- 8 files changed, 144 insertions(+), 84 deletions(-) create mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt delete mode 100644 plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt 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 cff4f3e27..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,7 +25,7 @@ public interface Catalog { /** * List top-level tables. */ - public fun listTables(): Collection = emptyList() + public fun listTables(): Collection = listTables(Namespace.empty()) /** * List all tables under this namespace. @@ -37,7 +37,7 @@ public interface Catalog { /** * 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. 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 30235da7e..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,26 +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 fun getLength(): Int = steps.size + public fun hasNamespace(): Boolean = !namespace.isEmpty() - public operator fun get(index: Int): String = steps[index] + public fun getName(): String = name - override fun forEach(action: Consumer?): Unit = steps.forEach(action) - - 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 932f4e99c..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 @@ -4,7 +4,7 @@ import java.util.Spliterator import java.util.function.Consumer /** - * A reference to a namespace within a catalog; case-preserved. + * A reference to a namespace within a catalog. * * Related * - Iceberg — https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/catalog/Namespace.java @@ -49,33 +49,28 @@ public class Namespace private constructor( if (other == null || javaClass != other.javaClass) { return false } - return levels.contentEquals((other as Namespace).levels) + val namespace = other as Namespace + return levels.contentEquals(namespace.levels) } - /** - * The hashCode() is case-sensitive — java.util.Arrays.hashCode - */ public override fun hashCode(): Int { return levels.contentHashCode() } - /** - * Return the SQL identifier representation of this namespace. - */ public override fun toString(): String { - return Identifier.of(*levels).toString() + return levels.joinToString(".") } public companion object { - private val ROOT = Namespace(emptyArray()) + private val EMPTY = Namespace(emptyArray()) - public fun root(): Namespace = ROOT + public fun empty(): Namespace = EMPTY @JvmStatic public fun of(vararg levels: String): Namespace { if (levels.isEmpty()) { - return root() + return empty() } return Namespace(arrayOf(*levels)) } @@ -83,7 +78,7 @@ public class Namespace private constructor( @JvmStatic public fun of(levels: Collection): Namespace { if (levels.isEmpty()) { - return root() + return empty() } return Namespace(levels.toTypedArray()) } 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 new file mode 100644 index 000000000..4345398ac --- /dev/null +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalCatalog.kt @@ -0,0 +1,75 @@ +package org.partiql.plugins.local + +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 +import kotlin.io.path.isDirectory +import kotlin.io.path.notExists + +/** + * Implementation of [Catalog] where dirs are namespaces and files are table metadata. + */ +internal class LocalCatalog( + 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" } + } + + override fun getName(): String { + return name + } + + 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) + } + + override fun listTables(namespace: Namespace): Collection { + val path = toPath(namespace) + if (path.notExists()) { + // throw exception? + return emptyList() + } + return super.listTables(namespace) + } + + 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()) } + } + + override fun getRoutines(name: Name): Collection = emptyList() + + private fun toPath(namespace: Namespace): Path { + var curr = root + for (level in namespace) { + curr = curr.resolve(level) + } + return curr + } + + 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 9f2556427..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 @@ -16,8 +16,7 @@ package org.partiql.plugins.local import com.amazon.ionelement.api.StructElement import org.partiql.eval.bindings.Bindings -import org.partiql.planner.metadata.Metadata -import org.partiql.planner.metadata.Namespace +import org.partiql.planner.catalog.Catalog import org.partiql.spi.connector.Connector import java.nio.file.Path import java.nio.file.Paths @@ -36,18 +35,39 @@ import kotlin.io.path.notExists * } * ``` */ -public class LocalConnector(private val root: Path) : Connector { +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() { + + 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(): LocalConnector = LocalConnector(name!!, root!!) + } } override fun getBindings(): Bindings = LocalBindings - override fun getMetadata(): Metadata = object : Metadata { - override fun getNamespace(): Namespace = LocalNamespace(root) - } + override fun getCatalog(): Catalog = catalog internal class Factory : Connector.Factory { @@ -61,7 +81,7 @@ public class LocalConnector(private val root: Path) : Connector { if (root.notExists() || !root.isDirectory()) { error("Invalid catalog `$root`") } - return LocalConnector(root) + return LocalConnector("default", root) } } } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt deleted file mode 100644 index edc66811d..000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalNamespace.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.partiql.plugins.local - -import org.partiql.planner.metadata.Namespace -import org.partiql.planner.metadata.Table -import java.nio.file.Path -import kotlin.io.path.isDirectory -import kotlin.io.path.isRegularFile -import kotlin.io.path.nameWithoutExtension - -/** - * Thin wrapper over a directory. - * - subdir -> namespace - * - file -> table - */ -public class LocalNamespace(private val path: Path) : Namespace { - - init { - assert(path.isDirectory()) { "LocalNamespace must be a directory" } - } - - override fun getName(): String = path.nameWithoutExtension - - override fun getNamespaces(): Collection { - return path.toFile().listFiles { f, _ -> f.isDirectory }!!.map { LocalNamespace(it.toPath()) } - } - - override fun getNamespace(name: String): Namespace? { - val p = path.resolve(name) - return when (p.isDirectory()) { - true -> LocalNamespace(p) - else -> null - } - } - - override fun getTable(name: String): Table? { - val p = path.resolve(name) - return when (p.isRegularFile()) { - true -> LocalTable(p) - else -> null - } - } -} 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 index a63284215..fa589265c 100644 --- 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 @@ -18,18 +18,28 @@ 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.metadata.Table +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 path: Path) : Table, Binding { +internal class LocalTable( + private val name: String, + private val path: Path, +) : Table, Binding { - override fun getKind(): Table.Kind = Table.Kind.TABLE + 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()) 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 48e76c995..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 @@ -81,10 +81,10 @@ public class MemoryConnector private constructor( override fun getName(): String = name override fun getTable(name: Name): Table? { - if (name.getLength() > 1) { + if (name.hasNamespace()) { error("MemoryCatalog does not support namespaces") } - return tables[name[0]] + return tables[name.getName()] } override fun listTables(): Collection { From 7635ee98889054b8624b9a1d2a7144c6289634eb Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 9 Jul 2024 09:33:28 -0700 Subject: [PATCH 08/15] Fix Connector.kt --- .../src/main/kotlin/org/partiql/spi/connector/Connector.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 4fc51c7b8..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 @@ -17,7 +17,7 @@ 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.metadata.Metadata +import org.partiql.planner.catalog.Catalog /** * A mechanism by which PartiQL can access bindings and catalog metadata. @@ -30,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. From c293964082db3d0d468cb26cafc6d63d6506ac67 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 9 Jul 2024 13:24:45 -0700 Subject: [PATCH 09/15] Defines Path and Session --- .../org/partiql/planner/PartiQLPlanner.kt | 21 +------------------ .../org/partiql/planner/catalog/Catalog.kt | 4 ++-- .../org/partiql/planner/catalog/Namespace.kt | 8 +++---- .../org/partiql/planner/catalog/Path.kt | 6 ++++-- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt index 9e5b72930..8305686dc 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt @@ -4,8 +4,7 @@ import org.partiql.ast.Statement import org.partiql.errors.Problem import org.partiql.errors.ProblemCallback import org.partiql.plan.PartiQLPlan -import org.partiql.spi.connector.ConnectorMetadata -import java.time.Instant +import org.partiql.planner.catalog.Session /** * PartiQLPlanner is responsible for transforming an AST into PartiQL's logical query plan. @@ -32,24 +31,6 @@ public interface PartiQLPlanner { public val problems: List, ) - /** - * From [org.partiql.lang.planner.transforms] - * - * @property queryId - * @property userId - * @property currentCatalog - * @property currentDirectory - * @property catalogs - * @property instant - */ - public class Session( - public val queryId: String, - public val userId: String, - public val currentCatalog: String, - public val currentDirectory: List = emptyList(), - public val catalogs: Map = emptyMap(), - public val instant: Instant = Instant.now(), - ) public companion object { @JvmStatic 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 8d2c3033a..b097a3ab3 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,7 +25,7 @@ public interface Catalog { /** * List top-level tables. */ - public fun listTables(): Collection = listTables(Namespace.empty()) + public fun listTables(): Collection = listTables(Namespace.root()) /** * List all tables under this namespace. @@ -37,7 +37,7 @@ public interface Catalog { /** * List top-level namespaces from the catalog. */ - public fun listNamespaces(): Collection = listNamespaces(Namespace.empty()) + public fun listNamespaces(): Collection = listNamespaces(Namespace.root()) /** * List all child namespaces from the namespace. 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 fbbb603d0..f366bf252 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 @@ -63,14 +63,14 @@ public class Namespace private constructor( public companion object { - private val EMPTY = Namespace(emptyArray()) + private val ROOT = Namespace(emptyArray()) - public fun empty(): Namespace = EMPTY + public fun root(): Namespace = ROOT @JvmStatic public fun of(vararg levels: String): Namespace { if (levels.isEmpty()) { - return empty() + return root() } return Namespace(arrayOf(*levels)) } @@ -78,7 +78,7 @@ public class Namespace private constructor( @JvmStatic public fun of(levels: Collection): Namespace { if (levels.isEmpty()) { - return empty() + return root() } return Namespace(levels.toTypedArray()) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt index 4dcd05ec1..960054e67 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt @@ -6,7 +6,9 @@ import java.util.function.Consumer /** * The routine resolution path, accessible via PATH. */ -public class Path private constructor(private val namespaces: List) : Iterable { +public class Path private constructor( + private val namespaces: List, +) : Iterable { public companion object { @@ -38,5 +40,5 @@ public class Path private constructor(private val namespaces: List) : return namespaces.spliterator() } - override fun toString(): String = "PATH = (${namespaces.joinToString()})" + override fun toString(): String = "PATH: (${namespaces.joinToString()})" } From e4b3cc62784ec776d95df888cd19028f1aa8659b Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Wed, 10 Jul 2024 12:08:54 -0700 Subject: [PATCH 10/15] Adds optional casing to names/namespaces --- .../org/partiql/planner/catalog/Name.kt | 107 ++++++++++++++++-- .../org/partiql/planner/catalog/Namespace.kt | 105 ++++++++++++++--- 2 files changed, 187 insertions(+), 25 deletions(-) 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 347801804..f7bcaaae6 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,28 +1,119 @@ package org.partiql.planner.catalog +import org.partiql.ast.Identifier +import org.partiql.ast.sql.sql + /** - * Thin wrapper over a list of strings. + * A reference to a named object in a catalog. */ -public data class Name( +public class Name( private val namespace: Namespace, - private val name: String, + private val name: Identifier.Symbol, ) { public fun getNamespace(): Namespace = namespace public fun hasNamespace(): Boolean = !namespace.isEmpty() - public fun getName(): String = name + public fun getName(): String = name.symbol + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + return matches(other as Name, ignoreCase = false) + } + + /** + * The hashCode() is case-sensitive. + */ + override fun hashCode(): Int { + var result = 1 + result = 31 * result + namespace.hashCode() + result = 31 * result + name.symbol.hashCode() + return result + } + + /** + * Return the SQL identifier representation of this name. + */ + override fun toString(): String { + return if (namespace.isEmpty()) { + name.sql() + } else { + "${namespace}.${name.sql()}" + } + } + + /** + * Compares one name to another, possibly ignoring case. + * + * @param other The other name to match against. + * @param ignoreCase If false, the compare all levels case-sensitively (exact-case match). + * @return + */ + public fun matches(other: Name, ignoreCase: Boolean = false): Boolean { + if (ignoreCase && !matches(name, other.name)) { + return false + } else if (name.symbol != other.name.symbol) { + return false + } + return this.namespace.matches(other.namespace, ignoreCase) + } + + // TODO de-duplicate or define on Identifier class + private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean { + val ignoreCase = ( + lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE || + rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE + ) + return lhs.symbol.equals(rhs.symbol, ignoreCase) + } public companion object { @JvmStatic - public fun of(vararg names: String): Name { - assert(names.size > 1) { "Cannot create an empty" } + public fun of(vararg names: String): Name = of(names.toList()) + + @JvmStatic + public fun of(names: Collection): Name { + assert(names.size > 1) { "Cannot create an empty name" } + val namespace = Namespace.of(names.drop(1)) + val name = Identifier.Symbol(names.last(), Identifier.CaseSensitivity.SENSITIVE) + return Name(namespace, name) + } + + @JvmStatic + public fun of(identifier: Identifier): Name = when (identifier) { + is Identifier.Qualified -> of(identifier) + is Identifier.Symbol -> of(identifier) + } + + @JvmStatic + public fun of(identifier: Identifier.Symbol): Name { return Name( - namespace = Namespace.of(*names.drop(1).toTypedArray()), - name = names.last(), + namespace = Namespace.root(), + name = identifier ) } + + @JvmStatic + public fun of(identifier: Identifier.Qualified): Name { + val identifiers = mutableListOf() + identifiers.add(identifier.root) + identifiers.addAll(identifier.steps) + return of(identifiers) + } + + @JvmStatic + public fun of(identifiers: Collection): Name { + assert(identifiers.size > 1) { "Cannot create an empty name" } + val namespace = Namespace.of(identifiers.drop(1)) + val name = identifiers.last() + return Name(namespace, name) + } } } 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 f366bf252..7b70d0f11 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 @@ -1,5 +1,7 @@ package org.partiql.planner.catalog +import org.partiql.ast.Identifier +import org.partiql.ast.sql.sql import java.util.Spliterator import java.util.function.Consumer @@ -11,10 +13,10 @@ import java.util.function.Consumer * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java */ public class Namespace private constructor( - private val levels: Array, -) : Iterable { + private val levels: Array, +) : Iterable { - public fun getLevels(): Array { + public fun getLevels(): Array { return levels } @@ -26,19 +28,19 @@ public class Namespace private constructor( return levels.isEmpty() } - public operator fun get(index: Int): String { + public operator fun get(index: Int): Identifier.Symbol { return levels[index] } - override fun forEach(action: Consumer?) { + override fun forEach(action: Consumer?) { levels.toList().forEach(action) } - override fun iterator(): Iterator { + override fun iterator(): Iterator { return levels.iterator() } - override fun spliterator(): Spliterator { + override fun spliterator(): Spliterator { return levels.toList().spliterator() } @@ -49,16 +51,62 @@ public class Namespace private constructor( if (other == null || javaClass != other.javaClass) { return false } - val namespace = other as Namespace - return levels.contentEquals(namespace.levels) + return matches(other as Namespace, ignoreCase = false) } + /** + * The hashCode() is case-sensitive — java.util.Arrays.hashCode + */ public override fun hashCode(): Int { - return levels.contentHashCode() + var result = 1 + for (element in levels) result = 31 * result + element.symbol.hashCode() + return result } + /** + * Return the SQL identifier representation of this namespace. + */ public override fun toString(): String { - return levels.joinToString(".") + return levels.joinToString(".") { it.sql() } + } + + /** + * Compares one namespace to another, possibly ignoring case. + * + * Note that ignoring case pushes the case-sensitivity check down to the identifier matching. + * + * For example, + * "x" and "X" are NOT equal regardless of ignoreCase because they are delimited. + * x and X are NOT equal with ignoreCase=false but are matching identifiers. + * + * @param other The other namespace to match against. + * @param ignoreCase If false, then compare all levels case-sensitively (exact-case match). + * @return + */ + public fun matches(other: Namespace, ignoreCase: Boolean = false): Boolean { + val n = getLength() + if (n != other.getLength()) { + return false + } + for (i in 0 until n) { + val lhs = levels[i] + val rhs = other[i] + if (ignoreCase && !matches(lhs, rhs)) { + return false + } else if (lhs.symbol != rhs.symbol) { + return false + } + } + return true + } + + // TODO de-duplicate or define on Identifier class + private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean { + val ignoreCase = ( + lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE || + rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE + ) + return lhs.symbol.equals(rhs.symbol, ignoreCase) } public companion object { @@ -68,19 +116,42 @@ public class Namespace private constructor( public fun root(): Namespace = ROOT @JvmStatic - public fun of(vararg levels: String): Namespace { - if (levels.isEmpty()) { - return root() - } - return Namespace(arrayOf(*levels)) - } + public fun of(vararg levels: String): Namespace = of(levels.toList()) @JvmStatic public fun of(levels: Collection): Namespace { if (levels.isEmpty()) { return root() } + return Namespace( + levels + .map { Identifier.Symbol(it, Identifier.CaseSensitivity.SENSITIVE) } + .toTypedArray() + ) + } + + @JvmStatic + public fun of(identifier: Identifier): Namespace = when (identifier) { + is Identifier.Qualified -> of(identifier) + is Identifier.Symbol -> of(identifier) + } + + @JvmStatic + public fun of(identifier: Identifier.Symbol): Namespace { + return Namespace(arrayOf(identifier)) + } + + @JvmStatic + public fun of(identifier: Identifier.Qualified): Namespace { + val levels = mutableListOf() + levels.add(identifier.root) + levels.addAll(identifier.steps) return Namespace(levels.toTypedArray()) } + + @JvmStatic + public fun of(identifiers: Collection): Namespace { + return Namespace(identifiers.toTypedArray()) + } } } From a5e37ebd52d87c06b5e002346dbf3ba6caa6b799 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 11 Jul 2024 14:25:09 -0700 Subject: [PATCH 11/15] Backing up before git tomfoolery --- partiql-planner/build.gradle.kts | 19 +- .../org/partiql/planner/PartiQLPlanner.kt | 23 +- .../partiql/planner/PartiQLPlannerBuilder.kt | 80 - .../org/partiql/planner/PartiQLPlannerPass.kt | 9 - .../org/partiql/planner/catalog/Catalog.kt | 12 +- .../org/partiql/planner/catalog/Catalogs.kt | 4 +- .../org/partiql/planner/catalog/Identifier.kt | 154 +- .../org/partiql/planner/catalog/Name.kt | 65 +- .../org/partiql/planner/catalog/Namespace.kt | 58 +- .../org/partiql/planner/internal/Env.kt | 384 ++- .../planner/internal/PartiQLPlannerDefault.kt | 16 +- .../org/partiql/planner/internal/ir/Nodes.kt | 2637 +++++++++-------- .../org/partiql/planner/modes/CasingMode.kt | 11 + .../main/resources/partiql_plan_internal.ion | 54 +- .../org/partiql/plugins/local/LocalCatalog.kt | 15 +- .../partiql/plugins/memory/MemoryConnector.kt | 7 +- 16 files changed, 1690 insertions(+), 1858 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerBuilder.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerPass.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt diff --git a/partiql-planner/build.gradle.kts b/partiql-planner/build.gradle.kts index d15642f6d..d7b2a5ffe 100644 --- a/partiql-planner/build.gradle.kts +++ b/partiql-planner/build.gradle.kts @@ -24,9 +24,9 @@ plugins { } dependencies { + api(project(":partiql-ast")) api(project(":partiql-plan")) api(project(":partiql-types")) - implementation(project(":partiql-ast")) implementation(Deps.dotlin) implementation(Deps.ionElement) // Test @@ -93,7 +93,6 @@ tasks.register("codegen") { "--poems", "builder", "--poems", "util", "--opt-in", "org.partiql.value.PartiQLValueExperimental", - "--opt-in", "org.partiql.spi.fn.FnExperimental", "./src/main/resources/partiql_plan_internal.ion" ) } @@ -112,14 +111,14 @@ tasks.register("copyUtils") { // // !! IMPORTANT !! — only run manually, as this will overwrite the existing ir/Nodes.kt. // -// tasks.register("copyNodes") { -// includeEmptyDirs = false -// dependsOn("codegen") -// filter { it.replace(Regex("public (?!(override|(fun visit)))"), "internal ") } -// from("$buildDir/tmp") -// include("**/Nodes.kt") -// into("src/main/kotlin") -// } +tasks.register("copyNodes") { + includeEmptyDirs = false + dependsOn("codegen") + filter { it.replace(Regex("public (?!(override|(fun visit)))"), "internal ") } + from("$buildDir/tmp") + include("**/Nodes.kt") + into("src/main/kotlin") +} tasks.register("generate") { dependsOn("codegen", "copyUtils") diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt index 8305686dc..680aceee8 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt @@ -5,6 +5,8 @@ import org.partiql.errors.Problem import org.partiql.errors.ProblemCallback import org.partiql.plan.PartiQLPlan import org.partiql.planner.catalog.Session +import org.partiql.planner.internal.PartiQLPlannerDefault +import org.partiql.planner.internal.PlannerFlag /** * PartiQLPlanner is responsible for transforming an AST into PartiQL's logical query plan. @@ -37,6 +39,25 @@ public interface PartiQLPlanner { public fun builder(): PartiQLPlannerBuilder = PartiQLPlannerBuilder() @JvmStatic - public fun default(): PartiQLPlanner = PartiQLPlannerBuilder().build() + public fun default(): PartiQLPlanner = Builder().build() + } + + public class Builder { + + private val flags: MutableSet = mutableSetOf() + + /** + * Build the builder, return an implementation of a [PartiQLPlanner]. + * + * @return + */ + public fun build(): PartiQLPlanner = PartiQLPlannerDefault(flags) + + /** + * Java style method for setting the planner to signal mode + */ + public fun signalMode(): Builder = this.apply { + this.flags.add(PlannerFlag.SIGNAL_MODE) + } } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerBuilder.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerBuilder.kt deleted file mode 100644 index 7af3c275f..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerBuilder.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.partiql.planner - -import org.partiql.planner.internal.PartiQLPlannerDefault -import org.partiql.planner.internal.PlannerFlag -import org.partiql.spi.connector.ConnectorMetadata - -/** - * PartiQLPlannerBuilder is used to programmatically construct a [PartiQLPlanner] implementation. - * - * Usage: - * PartiQLPlanner.builder() - * .signalMode() - * .addPass(myPass) - * .build() - */ -public class PartiQLPlannerBuilder { - - private val flags: MutableSet = mutableSetOf() - - private val passes: MutableList = mutableListOf() - - /** - * Build the builder, return an implementation of a [PartiQLPlanner]. - * - * @return - */ - public fun build(): PartiQLPlanner = PartiQLPlannerDefault(passes, flags) - - /** - * Java style method for adding a planner pass to this planner builder. - * - * @param pass - * @return - */ - public fun addPass(pass: PartiQLPlannerPass): PartiQLPlannerBuilder = this.apply { - this.passes.add(pass) - } - - /** - * Kotlin style method for adding a list of planner passes to this planner builder. - * - * @param passes - * @return - */ - public fun addPasses(vararg passes: PartiQLPlannerPass): PartiQLPlannerBuilder = this.apply { - this.passes.addAll(passes) - } - - /** - * Java style method for setting the planner to signal mode - */ - public fun signalMode(): PartiQLPlannerBuilder = this.apply { - this.flags.add(PlannerFlag.SIGNAL_MODE) - } - - /** - * Java style method for assigning a Catalog name to [ConnectorMetadata]. - * - * @param catalog - * @param metadata - * @return - */ - @Deprecated("This will be removed in version 1.0", ReplaceWith("Please use org.partiql.planner.PartiQLPlanner.Session")) - public fun addCatalog(catalog: String, metadata: ConnectorMetadata): PartiQLPlannerBuilder = this - - /** - * Kotlin style method for assigning Catalog names to [ConnectorMetadata]. - * - * @param catalogs - * @return - */ - @Deprecated("This will be removed in v0.15.0+.", ReplaceWith("Please use org.partiql.planner.PartiQLPlanner.Session")) - public fun catalogs(vararg catalogs: Pair): PartiQLPlannerBuilder = this - - @Deprecated("This will be removed in v0.15.0+.", ReplaceWith("addPasses")) - public fun passes(passes: List): PartiQLPlannerBuilder = this.apply { - this.passes.clear() - this.passes.addAll(passes) - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerPass.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerPass.kt deleted file mode 100644 index e02249b01..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlannerPass.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.partiql.planner - -import org.partiql.errors.ProblemCallback -import org.partiql.plan.PartiQLPlan - -public interface PartiQLPlannerPass { - - public fun apply(plan: PartiQLPlan, onProblem: ProblemCallback): PartiQLPlan -} 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 b097a3ab3..71c5eafe7 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 @@ -20,31 +20,31 @@ public interface Catalog { * @param name The case-sensitive [Table] name. * @return The [Table] or null if not found. */ - public fun getTable(name: Name): Table? = null + public fun getTable(session: Session, name: Name): Table? = null /** * List top-level tables. */ - public fun listTables(): Collection = listTables(Namespace.root()) + public fun listTables(session: Session): Collection = listTables(session, Namespace.root()) /** * List all tables under this namespace. * * @param namespace */ - public fun listTables(namespace: Namespace): Collection = emptyList() + public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() /** * List top-level namespaces from the catalog. */ - public fun listNamespaces(): Collection = listNamespaces(Namespace.root()) + public fun listNamespaces(session: Session): Collection = listNamespaces(session, Namespace.root()) /** * List all child namespaces from the namespace. * * @param namespace */ - public fun listNamespaces(namespace: Namespace): Collection = emptyList() + public fun listNamespaces(session: Session, namespace: Namespace): Collection = emptyList() /** * Get a routine's variants by name. @@ -52,5 +52,5 @@ public interface Catalog { * @param name The case-sensitive [Routine] name. * @return A collection of all [Routine]s in the current namespace with this name. */ - public fun getRoutines(name: Name): Collection = emptyList() + public fun getRoutines(session: Session, name: Name): Collection = emptyList() } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt index 537998e85..369851707 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt @@ -13,9 +13,9 @@ public interface Catalogs { /** * Returns a catalog by name (single identifier). */ - public fun get(name: String, ignoreCase: Boolean = false): Catalog? { + public fun get(identifier: Identifier): Catalog? { val default = default() - return if (name.equals(default.getName(), ignoreCase)) { + return if (identifier.matches(default.getName())) { default } else { null diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt index ade423191..7f2bd9503 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -1,173 +1,69 @@ package org.partiql.planner.catalog /** - * Represents an SQL identifier (possibly qualified). + * Represents an SQL identifier () which is regular (unquoted) or delimited (double-quoted). * - * @property qualifier If - * @property identifier + * @property text The identifier body. + * @property regular True if the identifier should be treated as an SQL regular identifier. */ public class Identifier private constructor( - private val qualifier: Array, - private val identifier: Part, + private val text: String, + private val regular: Boolean, ) { /** - * Returns the unqualified name part. + * Returns the identifier's case-preserved text. */ - public fun getIdentifier(): Part = identifier + public fun getText(): String = text /** - * Returns the name's namespace. + * Compares this identifier to a string. */ - public fun getQualifier(): Array = qualifier - - /** - * Returns true if the namespace is non-empty. - */ - public fun hasQualifier(): Boolean = qualifier.isNotEmpty() + public fun matches(other: String): Boolean { + return this.text.equals(other, ignoreCase = this.regular) + } /** - * Compares one identifier to another, possibly ignoring case. + * Compares two identifiers, ignoring case iff at least one identifier is non-delimited. */ - public fun matches(other: Identifier, ignoreCase: Boolean = false): Boolean { - // - if (this.qualifier.size != other.qualifier.size) { - return false - } - // Compare identifier - if (ignoreCase && !(this.identifier.matches(other.identifier))) { - return false - } else if (this.identifier != other.identifier) { - return false - } - for (i in this.qualifier.indices) { - val lhs = this.qualifier[i] - val rhs = other.qualifier[i] - if (ignoreCase && !lhs.matches(rhs)) { - return false - } else if (lhs != rhs) { - return false - } - } - return true + public fun matches(other: Identifier): Boolean { + return this.text.equals(other.text, ignoreCase = (this.regular || other.regular)) } /** * Compares the case-preserved text of two identifiers — that is case-sensitive equality. */ - public override fun equals(other: Any?): Boolean { + override fun equals(other: Any?): Boolean { if (this === other) { return true } if (other == null || javaClass != other.javaClass) { return false } - other as Identifier - return (this.identifier == other.identifier && this.qualifier.contentEquals(other.qualifier)) + return this.text == (other as Identifier).text } /** - * The hashCode() is case-sensitive — java.util.Arrays.hashCode + * Returns the hashcode of the identifier's case-preserved text. */ - public override fun hashCode(): Int { - var result = 1 - result = 31 * result + qualifier.hashCode() - result = 31 * result + identifier.hashCode() - return result + override fun hashCode(): Int { + return this.text.hashCode() } /** - * Return the SQL representation of this identifier. + * Return the identifier as a SQL string. */ - public override fun toString(): String = buildString { - if (qualifier.isNotEmpty()) { - append(qualifier.joinToString(".")) - append(".") - } - append(identifier) - } - - /** - * Represents an SQL identifier part which is either regular (unquoted) or delimited (double-quoted). - * - * @property text The case-preserved identifier text. - * @property regular True if the identifier should be treated as an SQL regular identifier. - */ - public class Part private constructor( - private val text: String, - private val regular: Boolean, - ) { - - /** - * Compares two identifiers, ignoring case iff at least one identifier is non-delimited. - */ - public fun matches(other: Part): Boolean { - return this.text.equals(other.text, ignoreCase = (this.regular || other.regular)) - } - - /** - * Compares the case-preserved text of two identifiers — that is case-sensitive equality. - */ - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null || javaClass != other.javaClass) { - return false - } - return this.text == (other as Part).text - } - - /** - * Returns the hashcode of the identifier's case-preserved text. - */ - override fun hashCode(): Int { - return this.text.hashCode() - } - - /** - * Return the identifier as a SQL string. - */ - override fun toString(): String = when (regular) { - true -> "\"${text}\"" - false -> text - } - - public companion object { - - @JvmStatic - public fun regular(text: String): Part = Part(text, true) - - @JvmStatic - public fun delimited(text: String): Part = Part(text, false) - } + override fun toString(): String = when (regular) { + true -> "\"${text}\"" + false -> text } public companion object { @JvmStatic - public fun regular(text: String): Identifier = Identifier(emptyArray(), Part.regular(text)) + public fun regular(text: String): Identifier = Identifier(text, true) @JvmStatic - public fun delimited(text: String): Identifier = Identifier(emptyArray(), Part.delimited(text)) - - @JvmStatic - public fun of(part: Part): Identifier = Identifier(emptyArray(), part) - - @JvmStatic - public fun of(vararg parts: Part): Identifier = TODO() - - @JvmStatic - public fun of(parts: Collection): Identifier = TODO() - - @JvmStatic - public fun of(vararg parts: String): Identifier { - TODO() - } - - @JvmStatic - public fun of(parts: Collection): Identifier { - TODO() - } + public fun delimited(text: String): Identifier = Identifier(text, false) } } 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 f7bcaaae6..78521341b 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,22 +1,22 @@ package org.partiql.planner.catalog -import org.partiql.ast.Identifier -import org.partiql.ast.sql.sql - /** * A reference to a named object in a catalog. */ public class Name( private val namespace: Namespace, - private val name: Identifier.Symbol, + private val name: Identifier, ) { public fun getNamespace(): Namespace = namespace public fun hasNamespace(): Boolean = !namespace.isEmpty() - public fun getName(): String = name.symbol + public fun getName(): Identifier = name + /** + * Compares two names including their namespaces and symbols. + */ override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -33,18 +33,18 @@ public class Name( override fun hashCode(): Int { var result = 1 result = 31 * result + namespace.hashCode() - result = 31 * result + name.symbol.hashCode() + result = 31 * result + name.hashCode() return result } /** - * Return the SQL identifier representation of this name. + * Return the SQL name representation of this name. */ override fun toString(): String { return if (namespace.isEmpty()) { - name.sql() + name.toString() } else { - "${namespace}.${name.sql()}" + "$namespace.$name" } } @@ -56,63 +56,46 @@ public class Name( * @return */ public fun matches(other: Name, ignoreCase: Boolean = false): Boolean { - if (ignoreCase && !matches(name, other.name)) { + if (ignoreCase && !(this.name.matches(other.name))) { return false - } else if (name.symbol != other.name.symbol) { + } else if (name != other.name) { return false } return this.namespace.matches(other.namespace, ignoreCase) } - // TODO de-duplicate or define on Identifier class - private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean { - val ignoreCase = ( - lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE || - rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE - ) - return lhs.symbol.equals(rhs.symbol, ignoreCase) - } - public companion object { + /** + * Construct a name from a string. + */ @JvmStatic public fun of(vararg names: String): Name = of(names.toList()) + /** + * Construct a name from a collection of strings. + */ @JvmStatic public fun of(names: Collection): Name { assert(names.size > 1) { "Cannot create an empty name" } val namespace = Namespace.of(names.drop(1)) - val name = Identifier.Symbol(names.last(), Identifier.CaseSensitivity.SENSITIVE) + val name = Identifier.delimited(names.last()) return Name(namespace, name) } @JvmStatic - public fun of(identifier: Identifier): Name = when (identifier) { - is Identifier.Qualified -> of(identifier) - is Identifier.Symbol -> of(identifier) - } - - @JvmStatic - public fun of(identifier: Identifier.Symbol): Name { + public fun of(name: Identifier): Name { return Name( namespace = Namespace.root(), - name = identifier + name = name ) } @JvmStatic - public fun of(identifier: Identifier.Qualified): Name { - val identifiers = mutableListOf() - identifiers.add(identifier.root) - identifiers.addAll(identifier.steps) - return of(identifiers) - } - - @JvmStatic - public fun of(identifiers: Collection): Name { - assert(identifiers.size > 1) { "Cannot create an empty name" } - val namespace = Namespace.of(identifiers.drop(1)) - val name = identifiers.last() + public fun of(names: Collection): Name { + assert(names.size > 1) { "Cannot create an empty name" } + val namespace = Namespace.of(names.drop(1)) + val name = names.last() return Name(namespace, name) } } 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 7b70d0f11..f4e0cb56f 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 @@ -1,7 +1,5 @@ package org.partiql.planner.catalog -import org.partiql.ast.Identifier -import org.partiql.ast.sql.sql import java.util.Spliterator import java.util.function.Consumer @@ -13,10 +11,10 @@ import java.util.function.Consumer * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java */ public class Namespace private constructor( - private val levels: Array, -) : Iterable { + private val levels: Array, +) : Iterable { - public fun getLevels(): Array { + public fun getLevels(): Array { return levels } @@ -28,19 +26,19 @@ public class Namespace private constructor( return levels.isEmpty() } - public operator fun get(index: Int): Identifier.Symbol { + public operator fun get(index: Int): Identifier { return levels[index] } - override fun forEach(action: Consumer?) { + override fun forEach(action: Consumer?) { levels.toList().forEach(action) } - override fun iterator(): Iterator { + override fun iterator(): Iterator { return levels.iterator() } - override fun spliterator(): Spliterator { + override fun spliterator(): Spliterator { return levels.toList().spliterator() } @@ -59,7 +57,9 @@ public class Namespace private constructor( */ public override fun hashCode(): Int { var result = 1 - for (element in levels) result = 31 * result + element.symbol.hashCode() + for (level in levels) { + result = 31 * result + level.hashCode() + } return result } @@ -67,7 +67,7 @@ public class Namespace private constructor( * Return the SQL identifier representation of this namespace. */ public override fun toString(): String { - return levels.joinToString(".") { it.sql() } + return levels.joinToString(".") } /** @@ -91,24 +91,15 @@ public class Namespace private constructor( for (i in 0 until n) { val lhs = levels[i] val rhs = other[i] - if (ignoreCase && !matches(lhs, rhs)) { + if (ignoreCase && !lhs.matches(rhs)) { return false - } else if (lhs.symbol != rhs.symbol) { + } else if (lhs != rhs) { return false } } return true } - // TODO de-duplicate or define on Identifier class - private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean { - val ignoreCase = ( - lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE || - rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE - ) - return lhs.symbol.equals(rhs.symbol, ignoreCase) - } - public companion object { private val ROOT = Namespace(emptyArray()) @@ -123,34 +114,17 @@ public class Namespace private constructor( if (levels.isEmpty()) { return root() } - return Namespace( - levels - .map { Identifier.Symbol(it, Identifier.CaseSensitivity.SENSITIVE) } - .toTypedArray() + return Namespace(levels.map { Identifier.delimited(it) }.toTypedArray() ) } @JvmStatic - public fun of(identifier: Identifier): Namespace = when (identifier) { - is Identifier.Qualified -> of(identifier) - is Identifier.Symbol -> of(identifier) - } - - @JvmStatic - public fun of(identifier: Identifier.Symbol): Namespace { + public fun of(identifier: Identifier): Namespace { return Namespace(arrayOf(identifier)) } @JvmStatic - public fun of(identifier: Identifier.Qualified): Namespace { - val levels = mutableListOf() - levels.add(identifier.root) - levels.addAll(identifier.steps) - return Namespace(levels.toTypedArray()) - } - - @JvmStatic - public fun of(identifiers: Collection): Namespace { + public fun of(identifiers: Collection): Namespace { return Namespace(identifiers.toTypedArray()) } } 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 aa7d22491..79e7a3a0f 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -1,31 +1,14 @@ package org.partiql.planner.internal -import org.partiql.planner.PartiQLPlanner +import org.partiql.ast.Identifier +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.casts.CastTable -import org.partiql.planner.internal.casts.Coercions -import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex -import org.partiql.planner.internal.ir.refAgg -import org.partiql.planner.internal.ir.refFn -import org.partiql.planner.internal.ir.refObj -import org.partiql.planner.internal.ir.relOpAggregateCallResolved -import org.partiql.planner.internal.ir.rex -import org.partiql.planner.internal.ir.rexOpCallDynamic -import org.partiql.planner.internal.ir.rexOpCallDynamicCandidate import org.partiql.planner.internal.ir.rexOpCastResolved -import org.partiql.planner.internal.ir.rexOpVarGlobal import org.partiql.planner.internal.typer.CompilerType -import org.partiql.planner.internal.typer.TypeEnv.Companion.toPath -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.fn.AggSignature -import org.partiql.spi.fn.FnExperimental -import org.partiql.types.PType -import org.partiql.types.PType.Kind -import org.partiql.value.PartiQLValueExperimental /** * [Env] is similar to the database type environment from the PartiQL Specification. This includes resolution of @@ -37,131 +20,147 @@ import org.partiql.value.PartiQLValueExperimental * * @property session */ -internal class Env(private val session: PartiQLPlanner.Session) { +internal class Env( + private val catalog: Catalog, + private val session: Session, +) { /** - * Current catalog [ConnectorMetadata]. Error if missing from the session. - */ - private val catalog: ConnectorMetadata = session.catalogs[session.currentCatalog] - ?: error("Session is missing ConnectorMetadata for current catalog ${session.currentCatalog}") - - /** - * A [PathResolver] for looking up objects given both unqualified and qualified names. + * + * TODO handle missing table error. + * + * Convert any remaining binding names (tail) to a path expression. */ - private val objects: PathResolverObj = PathResolverObj(catalog, session) + fun getTable(identifier: Identifier): Rex? = when (identifier) { + is Identifier.Qualified -> getTable(identifier) + is Identifier.Symbol -> getTable(identifier) + } /** - * A [PathResolver] for looking up functions given both unqualified and qualified names. + * + * + * + * @param identifier + * @return */ - private val fns: PathResolverFn = PathResolverFn(catalog, session) + fun getTable(identifier: Identifier.Symbol): Rex? { + val name = Name.of(identifier) + val table = catalog.getTable(session, name) ?: return null + return null + } /** - * A [PathResolver] for aggregation function lookup. + * TODO + * + * Convert any remaining binding names (tail) to a path expression. */ - private val aggs: PathResolverAgg = PathResolverAgg(catalog, session) + fun getTable(identifier: Identifier.Qualified): Rex? { + return null + } /** - * This function looks up a global [BindingPath], returning a global reference expression. + * This function looks up a global [Identitifier], returning a global reference expression. * * Convert any remaining binding names (tail) to a path expression. * * @param path * @return */ - fun resolveObj(path: BindingPath): Rex? { - val item = objects.lookup(path) ?: return null - // Create an internal typed reference - val ref = refObj( - catalog = item.catalog, - path = item.handle.path.steps, - type = CompilerType(item.handle.entity.getPType()), - ) - // Rewrite as a path expression. - val root = rex(ref.type, rexOpVarGlobal(ref)) - val depth = calculateMatched(path, item.input, ref.path) - val tail = path.steps.drop(depth) - return if (tail.isEmpty()) root else root.toPath(tail) - } + // fun resolveObj(path: Identifier): Rex? { + // + // val item = objects.lookup(path) ?: return null + // // Create an internal typed reference + // val ref = refObj( + // catalog = item.catalog, + // path = item.handle.path.steps, + // type = CompilerType(item.handle.entity.getPType()), + // ) + // // Rewrite as a path expression. + // val root = rex(ref.type, rexOpVarGlobal(ref)) + // val depth = calculateMatched(path, item.input, ref.path) + // val tail = path.steps.drop(depth) + // return if (tail.isEmpty()) root else root.toPath(tail) + // } - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) - fun resolveFn(path: BindingPath, args: List): Rex? { - val item = fns.lookup(path) ?: return null - // Invoke FnResolver to determine if we made a match - val variants = item.handle.entity.getVariants() - val match = FnResolver.resolve(variants, args.map { it.type }) - // If Type mismatch, then we return a missingOp whose trace is all possible candidates. - if (match == null) { - val candidates = variants.map { fnSignature -> - rexOpCallDynamicCandidate( - fn = refFn( - item.catalog, - path = item.handle.path.steps, - signature = fnSignature - ), - coercions = emptyList() - ) - } - return ProblemGenerator.missingRex( - rexOpCallDynamic(args, candidates), - ProblemGenerator.incompatibleTypesForOp(path.normalized.joinToString("."), args.map { it.type }) - ) - } - return when (match) { - is FnMatch.Dynamic -> { - val candidates = match.candidates.map { - // Create an internal typed reference for every candidate - rexOpCallDynamicCandidate( - fn = refFn( - catalog = item.catalog, - path = item.handle.path.steps, - signature = it.signature, - ), - coercions = it.mapping.toList(), - ) - } - // Rewrite as a dynamic call to be typed by PlanTyper - Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Dynamic(args, candidates)) - } - is FnMatch.Static -> { - // Create an internal typed reference - val ref = refFn( - catalog = item.catalog, - path = item.handle.path.steps, - signature = match.signature, - ) - // Apply the coercions as explicit casts - val coercions: List = args.mapIndexed { i, arg -> - when (val cast = match.mapping[i]) { - null -> arg - else -> Rex(CompilerType(PType.typeDynamic()), Rex.Op.Cast.Resolved(cast, arg)) - } - } - // Rewrite as a static call to be typed by PlanTyper - Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Static(ref, coercions)) - } - } + fun resolveFn(path: Identifier, args: List): Rex? { + // val item = fns.lookup(path) ?: return null + // // Invoke FnResolver to determine if we made a match + // val variants = item.handle.entity.getVariants() + // val match = FnResolver.resolve(variants, args.map { it.type }) + // // If Type mismatch, then we return a missingOp whose trace is all possible candidates. + // if (match == null) { + // val candidates = variants.map { fnSignature -> + // rexOpCallDynamicCandidate( + // fn = refFn( + // item.catalog, + // path = item.handle.path.steps, + // signature = fnSignature + // ), + // coercions = emptyList() + // ) + // } + // return ProblemGenerator.missingRex( + // rexOpCallDynamic(args, candidates), + // ProblemGenerator.incompatibleTypesForOp(path.normalized.joinToString("."), args.map { it.type }) + // ) + // } + // return when (match) { + // is FnMatch.Dynamic -> { + // val candidates = match.candidates.map { + // // Create an internal typed reference for every candidate + // rexOpCallDynamicCandidate( + // fn = refFn( + // catalog = item.catalog, + // path = item.handle.path.steps, + // signature = it.signature, + // ), + // coercions = it.mapping.toList(), + // ) + // } + // // Rewrite as a dynamic call to be typed by PlanTyper + // Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Dynamic(args, candidates)) + // } + // is FnMatch.Static -> { + // // Create an internal typed reference + // val ref = refFn( + // catalog = item.catalog, + // path = item.handle.path.steps, + // signature = match.signature, + // ) + // // Apply the coercions as explicit casts + // val coercions: List = args.mapIndexed { i, arg -> + // when (val cast = match.mapping[i]) { + // null -> arg + // else -> Rex(CompilerType(PType.typeDynamic()), Rex.Op.Cast.Resolved(cast, arg)) + // } + // } + // // Rewrite as a static call to be typed by PlanTyper + // Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Static(ref, coercions)) + // } + // } + TODO("resolveFn") } - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) fun resolveAgg(name: String, setQuantifier: Rel.Op.Aggregate.SetQuantifier, args: List): Rel.Op.Aggregate.Call.Resolved? { // TODO: Eventually, do we want to support sensitive lookup? With a path? - val path = BindingPath(listOf(BindingName(name, BindingCase.INSENSITIVE))) - val item = aggs.lookup(path) ?: return null - val candidates = item.handle.entity.getVariants() - val parameters = args.mapIndexed { i, arg -> arg.type } - val match = match(candidates, parameters) ?: return null - val agg = match.first - val mapping = match.second - // Create an internal typed reference - val ref = refAgg(item.catalog, item.handle.path.steps, agg) - // Apply the coercions as explicit casts - val coercions: List = args.mapIndexed { i, arg -> - when (val cast = mapping[i]) { - null -> arg - else -> rex(cast.target, rexOpCastResolved(cast, arg)) - } - } - return relOpAggregateCallResolved(ref, setQuantifier, coercions) + TODO("resolveAgg") + // val path = Identifier(listOf(BindingName(name, BindingCase.INSENSITIVE))) + // val item = aggs.lookup(path) ?: return null + // val candidates = item.handle.entity.getVariants() + // val parameters = args.mapIndexed { i, arg -> arg.type } + // val match = match(candidates, parameters) ?: return null + // val agg = match.first + // val mapping = match.second + // // Create an internal typed reference + // val ref = refAgg(item.catalog, item.handle.path.steps, agg) + // // Apply the coercions as explicit casts + // val coercions: List = args.mapIndexed { i, arg -> + // when (val cast = mapping[i]) { + // null -> arg + // else -> rex(cast.target, rexOpCastResolved(cast, arg)) + // } + // } + // return relOpAggregateCallResolved(ref, setQuantifier, coercions) } fun resolveCast(input: Rex, target: CompilerType): Rex.Op.Cast.Resolved? { @@ -207,74 +206,71 @@ internal class Env(private val session: PartiQLPlanner.Session) { * Therefore, in this example we have determined that from the original input (`T.A1.A2`) `T` is the value matched in the * database environment. */ - private fun calculateMatched( - userInputPath: BindingPath, - pathSentToConnector: BindingPath, - actualAbsolutePath: List, - ): Int { - return userInputPath.steps.size + actualAbsolutePath.size - pathSentToConnector.steps.size - } - - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) - private fun match(candidates: List, args: List): Pair>? { - // 1. Check for an exact match - for (candidate in candidates) { - if (candidate.matches(args)) { - return candidate to arrayOfNulls(args.size) - } - } - // 2. Look for best match. - var match: Pair>? = null - for (candidate in candidates) { - val m = candidate.match(args) ?: continue - // TODO AggMatch comparison - // if (match != null && m.exact < match.exact) { - // // already had a better match. - // continue - // } - match = m - } - // 3. Return best match or null - return match - } - - /** - * Check if this function accepts the exact input argument types. Assume same arity. - */ - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) - private fun AggSignature.matches(args: List): Boolean { - for (i in args.indices) { - val a = args[i] - val p = parameters[i] - if (p.type.kind != Kind.DYNAMIC && a != p.type) return false - } - return true - } - - /** - * Attempt to match arguments to the parameters; return the implicit casts if necessary. - * - * @param args - * @return - */ - @OptIn(FnExperimental::class, PartiQLValueExperimental::class) - private fun AggSignature.match(args: List): Pair>? { - val mapping = arrayOfNulls(args.size) - for (i in args.indices) { - val arg = args[i] - val p = parameters[i] - when { - // 1. Exact match - arg == p.type -> continue - // 2. Match ANY, no coercion needed - p.type.kind == Kind.DYNAMIC -> continue - // 3. Check for a coercion - else -> when (val coercion = Coercions.get(arg, p.type)) { - null -> return null // short-circuit - else -> mapping[i] = coercion - } - } - } - return this to mapping - } + // private fun calculateMatched( + // userInputPath: Identitifier, + // pathSentToConnector: Identitifier, + // actualAbsolutePath: List, + // ): Int { + // return userInputPath.steps.size + actualAbsolutePath.size - pathSentToConnector.steps.size + // } + // + // private fun match(candidates: List, args: List): Pair>? { + // // 1. Check for an exact match + // for (candidate in candidates) { + // if (candidate.matches(args)) { + // return candidate to arrayOfNulls(args.size) + // } + // } + // // 2. Look for best match. + // var match: Pair>? = null + // for (candidate in candidates) { + // val m = candidate.match(args) ?: continue + // // TODO AggMatch comparison + // // if (match != null && m.exact < match.exact) { + // // // already had a better match. + // // continue + // // } + // match = m + // } + // // 3. Return best match or null + // return match + // } + // + // /** + // * Check if this function accepts the exact input argument types. Assume same arity. + // */ + // private fun AggSignature.matches(args: List): Boolean { + // for (i in args.indices) { + // val a = args[i] + // val p = parameters[i] + // if (p.type.kind != Kind.DYNAMIC && a != p.type) return false + // } + // return true + // } + // + // /** + // * Attempt to match arguments to the parameters; return the implicit casts if necessary. + // * + // * @param args + // * @return + // */ + // private fun AggSignature.match(args: List): Pair>? { + // val mapping = arrayOfNulls(args.size) + // for (i in args.indices) { + // val arg = args[i] + // val p = parameters[i] + // when { + // // 1. Exact match + // arg == p.type -> continue + // // 2. Match ANY, no coercion needed + // p.type.kind == Kind.DYNAMIC -> continue + // // 3. Check for a coercion + // else -> when (val coercion = Coercions.get(arg, p.type)) { + // null -> return null // short-circuit + // else -> mapping[i] = coercion + // } + // } + // } + // return this to mapping + // } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt index a9815a8bd..c9064a8ea 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt @@ -4,7 +4,8 @@ import org.partiql.ast.Statement import org.partiql.ast.normalize.normalize import org.partiql.errors.ProblemCallback import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.PartiQLPlannerPass +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.transforms.AstToPlan import org.partiql.planner.internal.transforms.PlanTransform import org.partiql.planner.internal.typer.PlanTyper @@ -13,18 +14,18 @@ import org.partiql.planner.internal.typer.PlanTyper * Default PartiQL logical query planner. */ internal class PartiQLPlannerDefault( - private val passes: List, + private val catalog: Catalog, private val flags: Set ) : PartiQLPlanner { override fun plan( statement: Statement, - session: PartiQLPlanner.Session, + session: Session, onProblem: ProblemCallback, ): PartiQLPlanner.Result { // 0. Initialize the planning environment - val env = Env(session) + val env = Env(catalog, session) // 1. Normalize val ast = statement.normalize() @@ -38,12 +39,7 @@ internal class PartiQLPlannerDefault( val internal = org.partiql.planner.internal.ir.PartiQLPlan(typed) // 4. Assert plan has been resolved — translating to public API - var plan = PlanTransform(flags).transform(internal, onProblem) - - // 5. Apply all passes - for (pass in passes) { - plan = pass.apply(plan, onProblem) - } + val plan = PlanTransform(flags).transform(internal, onProblem) return PartiQLPlanner.Result(plan, emptyList()) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 9860a132f..44a7af3dd 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -1,81 +1,7 @@ -@file:OptIn( - PartiQLValueExperimental::class, - FnExperimental::class, -) +@file:OptIn(PartiQLValueExperimental::class) package org.partiql.planner.`internal`.ir -import org.partiql.errors.Problem -import org.partiql.planner.internal.ir.builder.IdentifierQualifiedBuilder -import org.partiql.planner.internal.ir.builder.IdentifierSymbolBuilder -import org.partiql.planner.internal.ir.builder.PartiQlPlanBuilder -import org.partiql.planner.internal.ir.builder.RefAggBuilder -import org.partiql.planner.internal.ir.builder.RefCastBuilder -import org.partiql.planner.internal.ir.builder.RefFnBuilder -import org.partiql.planner.internal.ir.builder.RefObjBuilder -import org.partiql.planner.internal.ir.builder.RelBindingBuilder -import org.partiql.planner.internal.ir.builder.RelBuilder -import org.partiql.planner.internal.ir.builder.RelOpAggregateBuilder -import org.partiql.planner.internal.ir.builder.RelOpAggregateCallResolvedBuilder -import org.partiql.planner.internal.ir.builder.RelOpAggregateCallUnresolvedBuilder -import org.partiql.planner.internal.ir.builder.RelOpDistinctBuilder -import org.partiql.planner.internal.ir.builder.RelOpErrBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludePathBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeStepBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeTypeCollIndexBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeTypeCollWildcardBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeTypeStructKeyBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeTypeStructSymbolBuilder -import org.partiql.planner.internal.ir.builder.RelOpExcludeTypeStructWildcardBuilder -import org.partiql.planner.internal.ir.builder.RelOpFilterBuilder -import org.partiql.planner.internal.ir.builder.RelOpJoinBuilder -import org.partiql.planner.internal.ir.builder.RelOpLimitBuilder -import org.partiql.planner.internal.ir.builder.RelOpOffsetBuilder -import org.partiql.planner.internal.ir.builder.RelOpProjectBuilder -import org.partiql.planner.internal.ir.builder.RelOpScanBuilder -import org.partiql.planner.internal.ir.builder.RelOpScanIndexedBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetExceptBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetIntersectBuilder -import org.partiql.planner.internal.ir.builder.RelOpSetUnionBuilder -import org.partiql.planner.internal.ir.builder.RelOpSortBuilder -import org.partiql.planner.internal.ir.builder.RelOpSortSpecBuilder -import org.partiql.planner.internal.ir.builder.RelOpUnpivotBuilder -import org.partiql.planner.internal.ir.builder.RelTypeBuilder -import org.partiql.planner.internal.ir.builder.RexBuilder -import org.partiql.planner.internal.ir.builder.RexOpCallDynamicBuilder -import org.partiql.planner.internal.ir.builder.RexOpCallDynamicCandidateBuilder -import org.partiql.planner.internal.ir.builder.RexOpCallStaticBuilder -import org.partiql.planner.internal.ir.builder.RexOpCallUnresolvedBuilder -import org.partiql.planner.internal.ir.builder.RexOpCaseBranchBuilder -import org.partiql.planner.internal.ir.builder.RexOpCaseBuilder -import org.partiql.planner.internal.ir.builder.RexOpCastResolvedBuilder -import org.partiql.planner.internal.ir.builder.RexOpCastUnresolvedBuilder -import org.partiql.planner.internal.ir.builder.RexOpCoalesceBuilder -import org.partiql.planner.internal.ir.builder.RexOpCollectionBuilder -import org.partiql.planner.internal.ir.builder.RexOpErrBuilder -import org.partiql.planner.internal.ir.builder.RexOpLitBuilder -import org.partiql.planner.internal.ir.builder.RexOpNullifBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathIndexBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathKeyBuilder -import org.partiql.planner.internal.ir.builder.RexOpPathSymbolBuilder -import org.partiql.planner.internal.ir.builder.RexOpPivotBuilder -import org.partiql.planner.internal.ir.builder.RexOpSelectBuilder -import org.partiql.planner.internal.ir.builder.RexOpStructBuilder -import org.partiql.planner.internal.ir.builder.RexOpStructFieldBuilder -import org.partiql.planner.internal.ir.builder.RexOpSubqueryBuilder -import org.partiql.planner.internal.ir.builder.RexOpTupleUnionBuilder -import org.partiql.planner.internal.ir.builder.RexOpVarGlobalBuilder -import org.partiql.planner.internal.ir.builder.RexOpVarLocalBuilder -import org.partiql.planner.internal.ir.builder.RexOpVarUnresolvedBuilder -import org.partiql.planner.internal.ir.builder.StatementQueryBuilder -import org.partiql.planner.internal.ir.visitor.PlanVisitor -import org.partiql.planner.internal.typer.CompilerType -import org.partiql.spi.fn.AggSignature -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.FnSignature -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental import kotlin.Boolean import kotlin.Char import kotlin.Int @@ -86,1371 +12,1522 @@ import kotlin.collections.Set import kotlin.jvm.JvmField import kotlin.jvm.JvmStatic import kotlin.random.Random +import org.partiql.`value`.PartiQLValue +import org.partiql.`value`.PartiQLValueExperimental +import org.partiql.ast.Identifier +import org.partiql.errors.Problem +import org.partiql.planner.`internal`.ir.builder.PartiQlPlanBuilder +import org.partiql.planner.`internal`.ir.builder.RefBuilder +import org.partiql.planner.`internal`.ir.builder.RefCastBuilder +import org.partiql.planner.`internal`.ir.builder.RelBindingBuilder +import org.partiql.planner.`internal`.ir.builder.RelBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpAggregateBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpAggregateCallResolvedBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpAggregateCallUnresolvedBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpDistinctBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpErrBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludePathBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeStepBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeTypeCollIndexBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeTypeCollWildcardBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeTypeStructKeyBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeTypeStructSymbolBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpExcludeTypeStructWildcardBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpFilterBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpJoinBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpLimitBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpOffsetBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpProjectBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpScanBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpScanIndexedBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpSetExceptBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpSetIntersectBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpSetUnionBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpSortBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpSortSpecBuilder +import org.partiql.planner.`internal`.ir.builder.RelOpUnpivotBuilder +import org.partiql.planner.`internal`.ir.builder.RelTypeBuilder +import org.partiql.planner.`internal`.ir.builder.RexBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCallDynamicBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCallDynamicCandidateBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCallStaticBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCallUnresolvedBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCaseBranchBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCaseBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCastResolvedBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCastUnresolvedBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCoalesceBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpCollectionBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpErrBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpLitBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpMissingBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpNullifBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpPathIndexBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpPathKeyBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpPathSymbolBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpPivotBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpSelectBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpStructBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpStructFieldBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpSubqueryBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpTupleUnionBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpVarGlobalBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpVarLocalBuilder +import org.partiql.planner.`internal`.ir.builder.RexOpVarUnresolvedBuilder +import org.partiql.planner.`internal`.ir.builder.StatementQueryBuilder +import org.partiql.planner.`internal`.ir.visitor.PlanVisitor +import org.partiql.planner.`internal`.typer.CompilerType internal abstract class PlanNode { - @JvmField - internal var tag: String = "Plan-${"%06x".format(Random.nextInt())}" + @JvmField + internal var tag: String = "Plan-${"%06x".format(Random.nextInt())}" - internal abstract val children: List + internal abstract val children: List - internal abstract fun accept(visitor: PlanVisitor, ctx: C): R + internal abstract fun accept(visitor: PlanVisitor, ctx: C): R } internal data class PartiQLPlan( - @JvmField internal val statement: Statement, + @JvmField + internal val statement: Statement, ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(statement) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitPartiQLPlan(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): PartiQlPlanBuilder = PartiQlPlanBuilder() + } +} + +internal data class Ref( + @JvmField + internal val name: String, +) : PlanNode() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRef(this, + ctx) + + internal data class Cast( + @JvmField + internal val input: CompilerType, + @JvmField + internal val target: CompilerType, + @JvmField + internal val safety: Safety, + @JvmField + internal val isNullable: Boolean, + ) : PlanNode() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRefCast(this, ctx) + + internal enum class Safety { + COERCION, + EXPLICIT, + UNSAFE, + } + + internal companion object { + @JvmStatic + internal fun builder(): RefCastBuilder = RefCastBuilder() + } + } + + internal companion object { + @JvmStatic + internal fun builder(): RefBuilder = RefBuilder() + } +} + +internal sealed class Statement : PlanNode() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Query -> visitor.visitStatementQuery(this, ctx) + } + + internal data class Query( + @JvmField + internal val root: Rex, + ) : Statement() { public override val children: List by lazy { - val kids = mutableListOf() - kids.add(statement) - kids.filterNotNull() + val kids = mutableListOf() + kids.add(root) + kids.filterNotNull() } - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitPartiQLPlan(this, ctx) + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitStatementQuery(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): PartiQlPlanBuilder = PartiQlPlanBuilder() + @JvmStatic + internal fun builder(): StatementQueryBuilder = StatementQueryBuilder() } + } } -internal sealed class Ref : PlanNode() { +internal data class Rex( + @JvmField + internal val type: CompilerType, + @JvmField + internal val op: Op, +) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(op) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRex(this, + ctx) + + internal sealed class Op : PlanNode() { public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Obj -> visitor.visitRefObj(this, ctx) - is Fn -> visitor.visitRefFn(this, ctx) - is Agg -> visitor.visitRefAgg(this, ctx) + is Lit -> visitor.visitRexOpLit(this, ctx) + is Var -> visitor.visitRexOpVar(this, ctx) + is Path -> visitor.visitRexOpPath(this, ctx) + is Cast -> visitor.visitRexOpCast(this, ctx) + is Call -> visitor.visitRexOpCall(this, ctx) + is Case -> visitor.visitRexOpCase(this, ctx) + is Nullif -> visitor.visitRexOpNullif(this, ctx) + is Coalesce -> visitor.visitRexOpCoalesce(this, ctx) + is Collection -> visitor.visitRexOpCollection(this, ctx) + is Struct -> visitor.visitRexOpStruct(this, ctx) + is Pivot -> visitor.visitRexOpPivot(this, ctx) + is Subquery -> visitor.visitRexOpSubquery(this, ctx) + is Select -> visitor.visitRexOpSelect(this, ctx) + is TupleUnion -> visitor.visitRexOpTupleUnion(this, ctx) + is Err -> visitor.visitRexOpErr(this, ctx) + is Missing -> visitor.visitRexOpMissing(this, ctx) + } + + internal data class Lit( + @JvmField + internal val `value`: PartiQLValue, + ) : Op() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpLit(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpLitBuilder = RexOpLitBuilder() + } } - internal data class Obj( - @JvmField internal val catalog: String, - @JvmField internal val path: List, - @JvmField internal val type: CompilerType, - ) : Ref() { + internal sealed class Var : Op() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Local -> visitor.visitRexOpVarLocal(this, ctx) + is Global -> visitor.visitRexOpVarGlobal(this, ctx) + is Unresolved -> visitor.visitRexOpVarUnresolved(this, ctx) + } + + internal enum class Scope { + DEFAULT, + LOCAL, + } + + internal data class Local( + @JvmField + internal val depth: Int, + @JvmField + internal val ref: Int, + ) : Var() { public override val children: List = emptyList() - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRefObj(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpVarLocal(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): RefObjBuilder = RefObjBuilder() + @JvmStatic + internal fun builder(): RexOpVarLocalBuilder = RexOpVarLocalBuilder() } - } + } + + internal data class Global( + @JvmField + internal val ref: Ref, + ) : Var() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(ref) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpVarGlobal(this, ctx) - internal data class Fn( - @JvmField internal val catalog: String, - @JvmField internal val path: List, - @JvmField internal val signature: FnSignature, - ) : Ref() { + internal companion object { + @JvmStatic + internal fun builder(): RexOpVarGlobalBuilder = RexOpVarGlobalBuilder() + } + } + + internal data class Unresolved( + @JvmField + internal val identifier: Identifier, + @JvmField + internal val scope: Scope, + ) : Var() { public override val children: List = emptyList() - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRefFn(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpVarUnresolved(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): RefFnBuilder = RefFnBuilder() + @JvmStatic + internal fun builder(): RexOpVarUnresolvedBuilder = RexOpVarUnresolvedBuilder() } + } } - internal data class Agg( - @JvmField internal val catalog: String, - @JvmField internal val path: List, - @JvmField internal val signature: AggSignature, - ) : Ref() { - public override val children: List = emptyList() + internal sealed class Path : Op() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Index -> visitor.visitRexOpPathIndex(this, ctx) + is Key -> visitor.visitRexOpPathKey(this, ctx) + is Symbol -> visitor.visitRexOpPathSymbol(this, ctx) + } + + internal data class Index( + @JvmField + internal val root: Rex, + @JvmField + internal val key: Rex, + ) : Path() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.add(key) + kids.filterNotNull() + } + - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRefAgg(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathIndex(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): RefAggBuilder = RefAggBuilder() + @JvmStatic + internal fun builder(): RexOpPathIndexBuilder = RexOpPathIndexBuilder() + } + } + + internal data class Key( + @JvmField + internal val root: Rex, + @JvmField + internal val key: Rex, + ) : Path() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.add(key) + kids.filterNotNull() } - } - internal data class Cast( - @JvmField internal val input: CompilerType, - @JvmField internal val target: CompilerType, - @JvmField internal val safety: Safety, - @JvmField internal val isNullable: Boolean, - ) : PlanNode() { - public override val children: List = emptyList() - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRefCast(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathKey(this, ctx) - internal enum class Safety { - COERCION, EXPLICIT, UNSAFE, + internal companion object { + @JvmStatic + internal fun builder(): RexOpPathKeyBuilder = RexOpPathKeyBuilder() + } + } + + internal data class Symbol( + @JvmField + internal val root: Rex, + @JvmField + internal val key: String, + ) : Path() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.filterNotNull() } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPathSymbol(this, ctx) + internal companion object { - @JvmStatic - internal fun builder(): RefCastBuilder = RefCastBuilder() + @JvmStatic + internal fun builder(): RexOpPathSymbolBuilder = RexOpPathSymbolBuilder() } + } } -} -internal sealed class Statement : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Query -> visitor.visitStatementQuery(this, ctx) - } + internal sealed class Cast : Op() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Unresolved -> visitor.visitRexOpCastUnresolved(this, ctx) + is Resolved -> visitor.visitRexOpCastResolved(this, ctx) + } + + internal data class Unresolved( + @JvmField + internal val target: CompilerType, + @JvmField + internal val arg: Rex, + ) : Cast() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(arg) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCastUnresolved(this, ctx) - internal data class Query( - @JvmField internal val root: Rex, - ) : Statement() { + internal companion object { + @JvmStatic + internal fun builder(): RexOpCastUnresolvedBuilder = RexOpCastUnresolvedBuilder() + } + } + + internal data class Resolved( + @JvmField + internal val cast: Ref.Cast, + @JvmField + internal val arg: Rex, + ) : Cast() { public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.filterNotNull() + val kids = mutableListOf() + kids.add(cast) + kids.add(arg) + kids.filterNotNull() } + public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitStatementQuery(this, ctx) + visitor.visitRexOpCastResolved(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): StatementQueryBuilder = StatementQueryBuilder() + @JvmStatic + internal fun builder(): RexOpCastResolvedBuilder = RexOpCastResolvedBuilder() } + } } -} -internal sealed class Identifier : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Symbol -> visitor.visitIdentifierSymbol(this, ctx) - is Qualified -> visitor.visitIdentifierQualified(this, ctx) - } + internal sealed class Call : Op() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Unresolved -> visitor.visitRexOpCallUnresolved(this, ctx) + is Static -> visitor.visitRexOpCallStatic(this, ctx) + is Dynamic -> visitor.visitRexOpCallDynamic(this, ctx) + } + + internal data class Unresolved( + @JvmField + internal val identifier: Identifier, + @JvmField + internal val args: List, + ) : Call() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(args) + kids.filterNotNull() + } - internal enum class CaseSensitivity { - SENSITIVE, INSENSITIVE, - } - internal data class Symbol( - @JvmField internal val symbol: String, - @JvmField internal val caseSensitivity: CaseSensitivity, - ) : Identifier() { - public override val children: List = emptyList() + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCallUnresolved(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpCallUnresolvedBuilder = RexOpCallUnresolvedBuilder() + } + } + + internal data class Static( + @JvmField + internal val fn: Ref, + @JvmField + internal val args: List, + ) : Call() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(fn) + kids.addAll(args) + kids.filterNotNull() + } + public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitIdentifierSymbol(this, ctx) + visitor.visitRexOpCallStatic(this, ctx) internal companion object { + @JvmStatic + internal fun builder(): RexOpCallStaticBuilder = RexOpCallStaticBuilder() + } + } + + internal data class Dynamic( + @JvmField + internal val args: List, + @JvmField + internal val candidates: List, + ) : Call() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(args) + kids.addAll(candidates) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCallDynamic(this, ctx) + + internal data class Candidate( + @JvmField + internal val fn: Ref, + @JvmField + internal val coercions: List, + ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(fn) + kids.addAll(coercions) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCallDynamicCandidate(this, ctx) + + internal companion object { @JvmStatic - internal fun builder(): IdentifierSymbolBuilder = IdentifierSymbolBuilder() + internal fun builder(): RexOpCallDynamicCandidateBuilder = + RexOpCallDynamicCandidateBuilder() + } + } + + internal companion object { + @JvmStatic + internal fun builder(): RexOpCallDynamicBuilder = RexOpCallDynamicBuilder() } + } } - internal data class Qualified( - @JvmField internal val root: Symbol, - @JvmField internal val steps: List, - ) : Identifier() { + internal data class Case( + @JvmField + internal val branches: List, + @JvmField + internal val default: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(branches) + kids.add(default) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCase(this, ctx) + + internal data class Branch( + @JvmField + internal val condition: Rex, + @JvmField + internal val rex: Rex, + ) : PlanNode() { public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.addAll(steps) - kids.filterNotNull() + val kids = mutableListOf() + kids.add(condition) + kids.add(rex) + kids.filterNotNull() } + public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitIdentifierQualified(this, ctx) + visitor.visitRexOpCaseBranch(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): IdentifierQualifiedBuilder = IdentifierQualifiedBuilder() + @JvmStatic + internal fun builder(): RexOpCaseBranchBuilder = RexOpCaseBranchBuilder() } + } + + internal companion object { + @JvmStatic + internal fun builder(): RexOpCaseBuilder = RexOpCaseBuilder() + } } -} -internal data class Rex( - @JvmField internal val type: CompilerType, - @JvmField internal val op: Op, -) : PlanNode() { - public override val children: List by lazy { + internal data class Nullif( + @JvmField + internal val `value`: Rex, + @JvmField + internal val nullifier: Rex, + ) : Op() { + public override val children: List by lazy { val kids = mutableListOf() - kids.add(op) + kids.add(value) + kids.add(nullifier) kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpNullif(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpNullifBuilder = RexOpNullifBuilder() + } } - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRex( - this, ctx - ) + internal data class Coalesce( + @JvmField + internal val args: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(args) + kids.filterNotNull() + } - internal sealed class Op : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Lit -> visitor.visitRexOpLit(this, ctx) - is Var -> visitor.visitRexOpVar(this, ctx) - is Path -> visitor.visitRexOpPath(this, ctx) - is Cast -> visitor.visitRexOpCast(this, ctx) - is Call -> visitor.visitRexOpCall(this, ctx) - is Case -> visitor.visitRexOpCase(this, ctx) - is Nullif -> visitor.visitRexOpNullif(this, ctx) - is Coalesce -> visitor.visitRexOpCoalesce(this, ctx) - is Collection -> visitor.visitRexOpCollection(this, ctx) - is Struct -> visitor.visitRexOpStruct(this, ctx) - is Pivot -> visitor.visitRexOpPivot(this, ctx) - is Subquery -> visitor.visitRexOpSubquery(this, ctx) - is Select -> visitor.visitRexOpSelect(this, ctx) - is TupleUnion -> visitor.visitRexOpTupleUnion(this, ctx) - is Err -> visitor.visitRexOpErr(this, ctx) - is Missing -> visitor.visitRexOpMissing(this, ctx) - } - internal data class Lit( - @JvmField internal val `value`: PartiQLValue, - ) : Op() { - public override val children: List = emptyList() + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCoalesce(this, ctx) - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRexOpLit(this, ctx) + internal companion object { + @JvmStatic + internal fun builder(): RexOpCoalesceBuilder = RexOpCoalesceBuilder() + } + } - internal companion object { - @JvmStatic - internal fun builder(): RexOpLitBuilder = RexOpLitBuilder() - } - } + internal data class Collection( + @JvmField + internal val values: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(values) + kids.filterNotNull() + } - internal sealed class Var : Op() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Local -> visitor.visitRexOpVarLocal(this, ctx) - is Global -> visitor.visitRexOpVarGlobal(this, ctx) - is Unresolved -> visitor.visitRexOpVarUnresolved(this, ctx) - } - - internal enum class Scope { - DEFAULT, LOCAL, - } - - internal data class Local( - @JvmField internal val depth: Int, - @JvmField internal val ref: Int, - ) : Var() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpVarLocal(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpVarLocalBuilder = RexOpVarLocalBuilder() - } - } - - internal data class Global( - @JvmField internal val ref: Ref.Obj, - ) : Var() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(ref) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpVarGlobal(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpVarGlobalBuilder = RexOpVarGlobalBuilder() - } - } - - internal data class Unresolved( - @JvmField internal val identifier: Identifier, - @JvmField internal val scope: Scope, - ) : Var() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(identifier) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpVarUnresolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpVarUnresolvedBuilder = RexOpVarUnresolvedBuilder() - } - } - } - internal sealed class Path : Op() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Index -> visitor.visitRexOpPathIndex(this, ctx) - is Key -> visitor.visitRexOpPathKey(this, ctx) - is Symbol -> visitor.visitRexOpPathSymbol(this, ctx) - } - - internal data class Index( - @JvmField internal val root: Rex, - @JvmField internal val key: Rex, - ) : Path() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.add(key) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathIndex(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathIndexBuilder = RexOpPathIndexBuilder() - } - } - - internal data class Key( - @JvmField internal val root: Rex, - @JvmField internal val key: Rex, - ) : Path() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.add(key) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathKey(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathKeyBuilder = RexOpPathKeyBuilder() - } - } - - internal data class Symbol( - @JvmField internal val root: Rex, - @JvmField internal val key: String, - ) : Path() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPathSymbol(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPathSymbolBuilder = RexOpPathSymbolBuilder() - } - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpCollection(this, ctx) - internal sealed class Cast : Op() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Unresolved -> visitor.visitRexOpCastUnresolved(this, ctx) - is Resolved -> visitor.visitRexOpCastResolved(this, ctx) - } - - internal data class Unresolved( - @JvmField internal val target: CompilerType, - @JvmField internal val arg: Rex, - ) : Cast() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(arg) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCastUnresolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCastUnresolvedBuilder = RexOpCastUnresolvedBuilder() - } - } - - internal data class Resolved( - @JvmField internal val cast: Ref.Cast, - @JvmField internal val arg: Rex, - ) : Cast() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(cast) - kids.add(arg) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCastResolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCastResolvedBuilder = RexOpCastResolvedBuilder() - } - } - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpCollectionBuilder = RexOpCollectionBuilder() + } + } - internal sealed class Call : Op() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Unresolved -> visitor.visitRexOpCallUnresolved(this, ctx) - is Static -> visitor.visitRexOpCallStatic(this, ctx) - is Dynamic -> visitor.visitRexOpCallDynamic(this, ctx) - } - - internal data class Unresolved( - @JvmField internal val identifier: Identifier, - @JvmField internal val args: List, - ) : Call() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(identifier) - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCallUnresolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCallUnresolvedBuilder = RexOpCallUnresolvedBuilder() - } - } - - internal data class Static( - @JvmField internal val fn: Ref.Fn, - @JvmField internal val args: List, - ) : Call() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(fn) - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCallStatic(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCallStaticBuilder = RexOpCallStaticBuilder() - } - } - - internal data class Dynamic( - @JvmField internal val args: List, - @JvmField internal val candidates: List, - ) : Call() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(args) - kids.addAll(candidates) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCallDynamic(this, ctx) - - internal data class Candidate( - @JvmField internal val fn: Ref.Fn, - @JvmField internal val coercions: List, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(fn) - kids.addAll(coercions) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCallDynamicCandidate(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCallDynamicCandidateBuilder = RexOpCallDynamicCandidateBuilder() - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCallDynamicBuilder = RexOpCallDynamicBuilder() - } - } - } + internal data class Struct( + @JvmField + internal val fields: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(fields) + kids.filterNotNull() + } - internal data class Case( - @JvmField internal val branches: List, - @JvmField internal val default: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(branches) - kids.add(default) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRexOpCase(this, ctx) - - internal data class Branch( - @JvmField internal val condition: Rex, - @JvmField internal val rex: Rex, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(condition) - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCaseBranch(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCaseBranchBuilder = RexOpCaseBranchBuilder() - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCaseBuilder = RexOpCaseBuilder() - } - } - internal data class Nullif( - @JvmField internal val `value`: Rex, - @JvmField internal val nullifier: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(value) - kids.add(nullifier) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpNullif(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpNullifBuilder = RexOpNullifBuilder() - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpStruct(this, ctx) - internal data class Coalesce( - @JvmField internal val args: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCoalesce(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCoalesceBuilder = RexOpCoalesceBuilder() - } + internal data class Field( + @JvmField + internal val k: Rex, + @JvmField + internal val v: Rex, + ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(k) + kids.add(v) + kids.filterNotNull() } - internal data class Collection( - @JvmField internal val values: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(values) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpCollection(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpCollectionBuilder = RexOpCollectionBuilder() - } - } - internal data class Struct( - @JvmField internal val fields: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(fields) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpStruct(this, ctx) - - internal data class Field( - @JvmField internal val k: Rex, - @JvmField internal val v: Rex, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(k) - kids.add(v) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpStructField(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpStructFieldBuilder = RexOpStructFieldBuilder() - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RexOpStructBuilder = RexOpStructBuilder() - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpStructField(this, ctx) - internal data class Pivot( - @JvmField internal val key: Rex, - @JvmField internal val `value`: Rex, - @JvmField internal val rel: Rel, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(key) - kids.add(value) - kids.add(rel) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpPivot(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpPivotBuilder = RexOpPivotBuilder() - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpStructFieldBuilder = RexOpStructFieldBuilder() } + } - internal data class Subquery( - @JvmField internal val `constructor`: Rex, - @JvmField internal val rel: Rel, - @JvmField internal val coercion: Coercion, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(constructor) - kids.add(rel) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpSubquery(this, ctx) - - internal enum class Coercion { - SCALAR, ROW, - } - - internal companion object { - @JvmStatic - internal fun builder(): RexOpSubqueryBuilder = RexOpSubqueryBuilder() - } - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpStructBuilder = RexOpStructBuilder() + } + } - internal data class Select( - @JvmField internal val `constructor`: Rex, - @JvmField internal val rel: Rel, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(constructor) - kids.add(rel) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpSelect(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpSelectBuilder = RexOpSelectBuilder() - } - } + internal data class Pivot( + @JvmField + internal val key: Rex, + @JvmField + internal val `value`: Rex, + @JvmField + internal val rel: Rel, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(key) + kids.add(value) + kids.add(rel) + kids.filterNotNull() + } - internal data class TupleUnion( - @JvmField internal val args: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRexOpTupleUnion(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpTupleUnionBuilder = RexOpTupleUnionBuilder() - } - } - internal data class Err( - @JvmField internal val problem: Problem, - @JvmField internal val causes: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(causes) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRexOpErr(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpErrBuilder = RexOpErrBuilder() - } - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpPivot(this, ctx) - internal data class Missing( - @JvmField internal val problem: Problem, - @JvmField internal val causes: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(causes) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRexOpMissing(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RexOpErrBuilder = RexOpErrBuilder() - } - } + internal companion object { + @JvmStatic + internal fun builder(): RexOpPivotBuilder = RexOpPivotBuilder() + } } - internal companion object { + internal data class Subquery( + @JvmField + internal val `constructor`: Rex, + @JvmField + internal val rel: Rel, + @JvmField + internal val coercion: Coercion, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(constructor) + kids.add(rel) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpSubquery(this, ctx) + + internal enum class Coercion { + SCALAR, + ROW, + } + + internal companion object { + @JvmStatic + internal fun builder(): RexOpSubqueryBuilder = RexOpSubqueryBuilder() + } + } + + internal data class Select( + @JvmField + internal val `constructor`: Rex, + @JvmField + internal val rel: Rel, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(constructor) + kids.add(rel) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpSelect(this, ctx) + + internal companion object { @JvmStatic - internal fun builder(): RexBuilder = RexBuilder() + internal fun builder(): RexOpSelectBuilder = RexOpSelectBuilder() + } } + + internal data class TupleUnion( + @JvmField + internal val args: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(args) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpTupleUnion(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpTupleUnionBuilder = RexOpTupleUnionBuilder() + } + } + + internal data class Err( + @JvmField + internal val problem: Problem, + @JvmField + internal val causes: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(causes) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpErr(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpErrBuilder = RexOpErrBuilder() + } + } + + internal data class Missing( + @JvmField + internal val problem: Problem, + @JvmField + internal val causes: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(causes) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRexOpMissing(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RexOpMissingBuilder = RexOpMissingBuilder() + } + } + } + + internal companion object { + @JvmStatic + internal fun builder(): RexBuilder = RexBuilder() + } } internal data class Rel( - @JvmField internal val type: Type, - @JvmField internal val op: Op, + @JvmField + internal val type: Type, + @JvmField + internal val op: Op, ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(type) + kids.add(op) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRel(this, + ctx) + + internal enum class Prop { + ORDERED, + } + + internal data class Type( + @JvmField + internal val schema: List, + @JvmField + internal val props: Set, + ) : PlanNode() { public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(schema) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelType(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelTypeBuilder = RelTypeBuilder() + } + } + + internal sealed class Op : PlanNode() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Scan -> visitor.visitRelOpScan(this, ctx) + is ScanIndexed -> visitor.visitRelOpScanIndexed(this, ctx) + is Unpivot -> visitor.visitRelOpUnpivot(this, ctx) + is Distinct -> visitor.visitRelOpDistinct(this, ctx) + is Filter -> visitor.visitRelOpFilter(this, ctx) + is Sort -> visitor.visitRelOpSort(this, ctx) + is Set -> visitor.visitRelOpSet(this, ctx) + is Limit -> visitor.visitRelOpLimit(this, ctx) + is Offset -> visitor.visitRelOpOffset(this, ctx) + is Project -> visitor.visitRelOpProject(this, ctx) + is Join -> visitor.visitRelOpJoin(this, ctx) + is Aggregate -> visitor.visitRelOpAggregate(this, ctx) + is Exclude -> visitor.visitRelOpExclude(this, ctx) + is Err -> visitor.visitRelOpErr(this, ctx) + } + + internal data class Scan( + @JvmField + internal val rex: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(rex) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpScan(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpScanBuilder = RelOpScanBuilder() + } + } + + internal data class ScanIndexed( + @JvmField + internal val rex: Rex, + ) : Op() { + public override val children: List by lazy { val kids = mutableListOf() - kids.add(type) - kids.add(op) + kids.add(rex) kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpScanIndexed(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpScanIndexedBuilder = RelOpScanIndexedBuilder() + } } - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRel( - this, ctx - ) + internal data class Unpivot( + @JvmField + internal val rex: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(rex) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpUnpivot(this, ctx) - internal enum class Prop { - ORDERED, + internal companion object { + @JvmStatic + internal fun builder(): RelOpUnpivotBuilder = RelOpUnpivotBuilder() + } } - internal data class Type( - @JvmField internal val schema: List, - @JvmField internal val props: Set, - ) : PlanNode() { + internal data class Distinct( + @JvmField + internal val input: Rel, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpDistinct(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpDistinctBuilder = RelOpDistinctBuilder() + } + } + + internal data class Filter( + @JvmField + internal val input: Rel, + @JvmField + internal val predicate: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.add(predicate) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpFilter(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpFilterBuilder = RelOpFilterBuilder() + } + } + + internal data class Sort( + @JvmField + internal val input: Rel, + @JvmField + internal val specs: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.addAll(specs) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSort(this, ctx) + + internal enum class Order { + ASC_NULLS_LAST, + ASC_NULLS_FIRST, + DESC_NULLS_LAST, + DESC_NULLS_FIRST, + } + + internal data class Spec( + @JvmField + internal val rex: Rex, + @JvmField + internal val order: Order, + ) : PlanNode() { public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(schema) - kids.filterNotNull() + val kids = mutableListOf() + kids.add(rex) + kids.filterNotNull() } - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelType(this, ctx) + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSortSpec(this, ctx) internal companion object { - @JvmStatic - internal fun builder(): RelTypeBuilder = RelTypeBuilder() + @JvmStatic + internal fun builder(): RelOpSortSpecBuilder = RelOpSortSpecBuilder() } + } + + internal companion object { + @JvmStatic + internal fun builder(): RelOpSortBuilder = RelOpSortBuilder() + } } - internal sealed class Op : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Scan -> visitor.visitRelOpScan(this, ctx) - is ScanIndexed -> visitor.visitRelOpScanIndexed(this, ctx) - is Unpivot -> visitor.visitRelOpUnpivot(this, ctx) - is Distinct -> visitor.visitRelOpDistinct(this, ctx) - is Filter -> visitor.visitRelOpFilter(this, ctx) - is Sort -> visitor.visitRelOpSort(this, ctx) - is Set -> visitor.visitRelOpSet(this, ctx) - is Limit -> visitor.visitRelOpLimit(this, ctx) - is Offset -> visitor.visitRelOpOffset(this, ctx) - is Project -> visitor.visitRelOpProject(this, ctx) - is Join -> visitor.visitRelOpJoin(this, ctx) - is Aggregate -> visitor.visitRelOpAggregate(this, ctx) - is Exclude -> visitor.visitRelOpExclude(this, ctx) - is Err -> visitor.visitRelOpErr(this, ctx) + internal sealed class Set : Op() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Union -> visitor.visitRelOpSetUnion(this, ctx) + is Intersect -> visitor.visitRelOpSetIntersect(this, ctx) + is Except -> visitor.visitRelOpSetExcept(this, ctx) + } + + internal enum class Quantifier { + ALL, + DISTINCT, + } + + internal data class Union( + @JvmField + internal val quantifier: Quantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() } - internal data class Scan( - @JvmField internal val rex: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpScan(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpScanBuilder = RelOpScanBuilder() - } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetUnion(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetUnionBuilder = RelOpSetUnionBuilder() + } + } + + internal data class Intersect( + @JvmField + internal val quantifier: Quantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() } - internal data class ScanIndexed( - @JvmField internal val rex: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpScanIndexed(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpScanIndexedBuilder = RelOpScanIndexedBuilder() - } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetIntersect(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetIntersectBuilder = RelOpSetIntersectBuilder() + } + } + + internal data class Except( + @JvmField + internal val quantifier: Quantifier, + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val isOuter: Boolean, + ) : Set() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.filterNotNull() } - internal data class Unpivot( - @JvmField internal val rex: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpUnpivot(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpUnpivotBuilder = RelOpUnpivotBuilder() - } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpSetExcept(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpSetExceptBuilder = RelOpSetExceptBuilder() } + } + } + + internal data class Limit( + @JvmField + internal val input: Rel, + @JvmField + internal val limit: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.add(limit) + kids.filterNotNull() + } - internal data class Distinct( - @JvmField internal val input: Rel, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpDistinct(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpDistinctBuilder = RelOpDistinctBuilder() - } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpLimit(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpLimitBuilder = RelOpLimitBuilder() + } + } + + internal data class Offset( + @JvmField + internal val input: Rel, + @JvmField + internal val offset: Rex, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.add(offset) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpOffset(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpOffsetBuilder = RelOpOffsetBuilder() + } + } + + internal data class Project( + @JvmField + internal val input: Rel, + @JvmField + internal val projections: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.addAll(projections) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpProject(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpProjectBuilder = RelOpProjectBuilder() + } + } + + internal data class Join( + @JvmField + internal val lhs: Rel, + @JvmField + internal val rhs: Rel, + @JvmField + internal val rex: Rex, + @JvmField + internal val type: Type, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(lhs) + kids.add(rhs) + kids.add(rex) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpJoin(this, ctx) + + internal enum class Type { + INNER, + LEFT, + RIGHT, + FULL, + } + + internal companion object { + @JvmStatic + internal fun builder(): RelOpJoinBuilder = RelOpJoinBuilder() + } + } + + internal data class Aggregate( + @JvmField + internal val input: Rel, + @JvmField + internal val strategy: Strategy, + @JvmField + internal val calls: List, + @JvmField + internal val groups: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.addAll(calls) + kids.addAll(groups) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpAggregate(this, ctx) + + internal enum class Strategy { + FULL, + PARTIAL, + } + + internal enum class SetQuantifier { + ALL, + DISTINCT, + } + + internal sealed class Call : PlanNode() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is Unresolved -> visitor.visitRelOpAggregateCallUnresolved(this, ctx) + is Resolved -> visitor.visitRelOpAggregateCallResolved(this, ctx) } - internal data class Filter( - @JvmField internal val input: Rel, - @JvmField internal val predicate: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.add(predicate) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpFilter(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpFilterBuilder = RelOpFilterBuilder() - } + internal data class Unresolved( + @JvmField + internal val name: String, + @JvmField + internal val setQuantifier: SetQuantifier, + @JvmField + internal val args: List, + ) : Call() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.addAll(args) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpAggregateCallUnresolved(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpAggregateCallUnresolvedBuilder = + RelOpAggregateCallUnresolvedBuilder() + } } - internal data class Sort( - @JvmField internal val input: Rel, - @JvmField internal val specs: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.addAll(specs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpSort(this, ctx) - - internal enum class Order { - ASC_NULLS_LAST, ASC_NULLS_FIRST, DESC_NULLS_LAST, DESC_NULLS_FIRST, - } - - internal data class Spec( - @JvmField internal val rex: Rex, - @JvmField internal val order: Order, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSortSpec(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSortSpecBuilder = RelOpSortSpecBuilder() - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSortBuilder = RelOpSortBuilder() - } + internal data class Resolved( + @JvmField + internal val agg: Ref, + @JvmField + internal val setQuantifier: SetQuantifier, + @JvmField + internal val args: List, + ) : Call() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(agg) + kids.addAll(args) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpAggregateCallResolved(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpAggregateCallResolvedBuilder = + RelOpAggregateCallResolvedBuilder() + } } - internal sealed class Set : Op() { - - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Union -> visitor.visitRelOpSetUnion(this, ctx) - is Intersect -> visitor.visitRelOpSetIntersect(this, ctx) - is Except -> visitor.visitRelOpSetExcept(this, ctx) - } - - internal data class Union( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetUnion(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetUnionBuilder = RelOpSetUnionBuilder() - } - } - - internal data class Intersect( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetIntersect(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetIntersectBuilder = RelOpSetIntersectBuilder() - } - } - - internal data class Except( - @JvmField internal val quantifier: Quantifier, - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val isOuter: Boolean, - ) : Set() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpSetExcept(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpSetExceptBuilder = RelOpSetExceptBuilder() - } - } - - internal enum class Quantifier { - ALL, DISTINCT - } + } + + internal companion object { + @JvmStatic + internal fun builder(): RelOpAggregateBuilder = RelOpAggregateBuilder() + } + } + + internal data class Exclude( + @JvmField + internal val input: Rel, + @JvmField + internal val paths: List, + ) : Op() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(input) + kids.addAll(paths) + kids.filterNotNull() + } + + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExclude(this, ctx) + + internal data class Path( + @JvmField + internal val root: Ref, + @JvmField + internal val steps: List, + ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(root) + kids.addAll(steps) + kids.filterNotNull() } - internal data class Limit( - @JvmField internal val input: Rel, - @JvmField internal val limit: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.add(limit) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpLimit(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpLimitBuilder = RelOpLimitBuilder() - } + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludePath(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludePathBuilder = RelOpExcludePathBuilder() } + } + + internal data class Step( + @JvmField + internal val type: Type, + @JvmField + internal val substeps: List, + ) : PlanNode() { + public override val children: List by lazy { + val kids = mutableListOf() + kids.add(type) + kids.addAll(substeps) + kids.filterNotNull() + } + - internal data class Offset( - @JvmField internal val input: Rel, - @JvmField internal val offset: Rex, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.add(offset) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpOffset(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpOffsetBuilder = RelOpOffsetBuilder() - } + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeStep(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeStepBuilder = RelOpExcludeStepBuilder() } + } - internal data class Project( - @JvmField internal val input: Rel, - @JvmField internal val projections: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.addAll(projections) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpProject(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpProjectBuilder = RelOpProjectBuilder() - } + internal sealed class Type : PlanNode() { + public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { + is StructSymbol -> visitor.visitRelOpExcludeTypeStructSymbol(this, ctx) + is StructKey -> visitor.visitRelOpExcludeTypeStructKey(this, ctx) + is CollIndex -> visitor.visitRelOpExcludeTypeCollIndex(this, ctx) + is StructWildcard -> visitor.visitRelOpExcludeTypeStructWildcard(this, ctx) + is CollWildcard -> visitor.visitRelOpExcludeTypeCollWildcard(this, ctx) } - internal data class Join( - @JvmField internal val lhs: Rel, - @JvmField internal val rhs: Rel, - @JvmField internal val rex: Rex, - @JvmField internal val type: Type, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(lhs) - kids.add(rhs) - kids.add(rex) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpJoin(this, ctx) - - internal enum class Type { - INNER, LEFT, RIGHT, FULL, - } - - internal companion object { - @JvmStatic - internal fun builder(): RelOpJoinBuilder = RelOpJoinBuilder() - } + internal data class StructSymbol( + @JvmField + internal val symbol: String, + ) : Type() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeTypeStructSymbol(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeTypeStructSymbolBuilder = + RelOpExcludeTypeStructSymbolBuilder() + } } - internal data class Aggregate( - @JvmField internal val input: Rel, - @JvmField internal val strategy: Strategy, - @JvmField internal val calls: List, - @JvmField internal val groups: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.addAll(calls) - kids.addAll(groups) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpAggregate(this, ctx) - - internal enum class Strategy { - FULL, PARTIAL, - } - - internal enum class SetQuantifier { - ALL, DISTINCT, - } - - internal sealed class Call : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is Unresolved -> visitor.visitRelOpAggregateCallUnresolved(this, ctx) - is Resolved -> visitor.visitRelOpAggregateCallResolved(this, ctx) - } - - internal data class Unresolved( - @JvmField internal val name: String, - @JvmField internal val setQuantifier: SetQuantifier, - @JvmField internal val args: List, - ) : Call() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpAggregateCallUnresolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpAggregateCallUnresolvedBuilder = - RelOpAggregateCallUnresolvedBuilder() - } - } - - internal data class Resolved( - @JvmField internal val agg: Ref.Agg, - @JvmField internal val setQuantifier: SetQuantifier, - @JvmField internal val args: List, - ) : Call() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(agg) - kids.addAll(args) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpAggregateCallResolved(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpAggregateCallResolvedBuilder = RelOpAggregateCallResolvedBuilder() - } - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RelOpAggregateBuilder = RelOpAggregateBuilder() - } + internal data class StructKey( + @JvmField + internal val key: String, + ) : Type() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeTypeStructKey(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeTypeStructKeyBuilder = + RelOpExcludeTypeStructKeyBuilder() + } } - internal data class Exclude( - @JvmField internal val input: Rel, - @JvmField internal val paths: List, - ) : Op() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(input) - kids.addAll(paths) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExclude(this, ctx) - - internal data class Path( - @JvmField internal val root: Rex.Op, - @JvmField internal val steps: List, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(root) - kids.addAll(steps) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludePath(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludePathBuilder = RelOpExcludePathBuilder() - } - } - - internal data class Step( - @JvmField internal val type: Type, - @JvmField internal val substeps: List, - ) : PlanNode() { - public override val children: List by lazy { - val kids = mutableListOf() - kids.add(type) - kids.addAll(substeps) - kids.filterNotNull() - } - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeStep(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeStepBuilder = RelOpExcludeStepBuilder() - } - } - - internal sealed class Type : PlanNode() { - public override fun accept(visitor: PlanVisitor, ctx: C): R = when (this) { - is StructSymbol -> visitor.visitRelOpExcludeTypeStructSymbol(this, ctx) - is StructKey -> visitor.visitRelOpExcludeTypeStructKey(this, ctx) - is CollIndex -> visitor.visitRelOpExcludeTypeCollIndex(this, ctx) - is StructWildcard -> visitor.visitRelOpExcludeTypeStructWildcard(this, ctx) - is CollWildcard -> visitor.visitRelOpExcludeTypeCollWildcard(this, ctx) - } - - internal data class StructSymbol( - @JvmField internal val symbol: String, - ) : Type() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeTypeStructSymbol(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeTypeStructSymbolBuilder = - RelOpExcludeTypeStructSymbolBuilder() - } - - // Explicitly override `equals` and `hashcode` for case-insensitivity - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as StructSymbol - - if (!symbol.equals(other.symbol, ignoreCase = true)) return false - if (children != other.children) return false - - return true - } - - override fun hashCode(): Int { - return symbol.lowercase().hashCode() - } - } - - internal data class StructKey( - @JvmField internal val key: String, - ) : Type() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeTypeStructKey(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeTypeStructKeyBuilder = RelOpExcludeTypeStructKeyBuilder() - } - } - - internal data class CollIndex( - @JvmField internal val index: Int, - ) : Type() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeTypeCollIndex(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeTypeCollIndexBuilder = RelOpExcludeTypeCollIndexBuilder() - } - } - - internal data class StructWildcard( - @JvmField internal val ` `: Char = ' ', - ) : Type() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeTypeStructWildcard(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeTypeStructWildcardBuilder = - RelOpExcludeTypeStructWildcardBuilder() - } - } - - internal data class CollWildcard( - @JvmField internal val ` `: Char = ' ', - ) : Type() { - public override val children: List = emptyList() - - public override fun accept(visitor: PlanVisitor, ctx: C): R = - visitor.visitRelOpExcludeTypeCollWildcard(this, ctx) - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeTypeCollWildcardBuilder = - RelOpExcludeTypeCollWildcardBuilder() - } - } - } - - internal companion object { - @JvmStatic - internal fun builder(): RelOpExcludeBuilder = RelOpExcludeBuilder() - } + internal data class CollIndex( + @JvmField + internal val index: Int, + ) : Type() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeTypeCollIndex(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeTypeCollIndexBuilder = + RelOpExcludeTypeCollIndexBuilder() + } } - internal data class Err( - @JvmField internal val message: String, - ) : Op() { - public override val children: List = emptyList() + internal data class StructWildcard( + @JvmField + internal val ` `: Char = ' ', + ) : Type() { + public override val children: List = emptyList() - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelOpErr(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeTypeStructWildcard(this, ctx) - internal companion object { - @JvmStatic - internal fun builder(): RelOpErrBuilder = RelOpErrBuilder() - } + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeTypeStructWildcardBuilder = + RelOpExcludeTypeStructWildcardBuilder() + } } - } - internal data class Binding( - @JvmField internal val name: String, - @JvmField internal val type: CompilerType, - ) : PlanNode() { - public override val children: List = emptyList() + internal data class CollWildcard( + @JvmField + internal val ` `: Char = ' ', + ) : Type() { + public override val children: List = emptyList() - public override fun accept(visitor: PlanVisitor, ctx: C): R = visitor.visitRelBinding(this, ctx) + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpExcludeTypeCollWildcard(this, ctx) - internal companion object { + internal companion object { @JvmStatic - internal fun builder(): RelBindingBuilder = RelBindingBuilder() + internal fun builder(): RelOpExcludeTypeCollWildcardBuilder = + RelOpExcludeTypeCollWildcardBuilder() + } } + } + + internal companion object { + @JvmStatic + internal fun builder(): RelOpExcludeBuilder = RelOpExcludeBuilder() + } } - internal companion object { + internal data class Err( + @JvmField + internal val message: String, + ) : Op() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelOpErr(this, ctx) + + internal companion object { @JvmStatic - internal fun builder(): RelBuilder = RelBuilder() + internal fun builder(): RelOpErrBuilder = RelOpErrBuilder() + } + } + } + + internal data class Binding( + @JvmField + internal val name: String, + @JvmField + internal val type: CompilerType, + ) : PlanNode() { + public override val children: List = emptyList() + + public override fun accept(visitor: PlanVisitor, ctx: C): R = + visitor.visitRelBinding(this, ctx) + + internal companion object { + @JvmStatic + internal fun builder(): RelBindingBuilder = RelBindingBuilder() } + } + + internal companion object { + @JvmStatic + internal fun builder(): RelBuilder = RelBuilder() + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt new file mode 100644 index 000000000..f52d3d490 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt @@ -0,0 +1,11 @@ +package org.partiql.planner.modes + +/** + * Identifier lookup case handling. + */ +public enum class CasingMode { + NORMALIZE_LOWER, + NORMALIZE_UPPER, + INSENSITIVE, + SENSITIVE, +} diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 0c1cfdde1..aac995305 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -1,10 +1,9 @@ imports::{ kotlin: [ + identifier::'org.partiql.planner.catalog.Identifier', partiql_value::'org.partiql.value.PartiQLValue', partiql_value_type::'org.partiql.planner.internal.typer.CompilerType', static_type::'org.partiql.planner.internal.typer.CompilerType', - fn_signature::'org.partiql.spi.fn.FnSignature', - agg_signature::'org.partiql.spi.fn.AggSignature', problem::'org.partiql.errors.Problem' ], } @@ -18,23 +17,9 @@ parti_q_l_plan::{ // When transforming to the public plan we produce a symbol table, // replacing each internal ref with a pointer ref to the public plan's catalog list. -ref::[ - obj::{ - catalog: string, - path: list::[string], - type: static_type, - }, - fn::{ - catalog: string, - path: list::[string], - signature: fn_signature, - }, - agg::{ - catalog: string, - path: list::[string], - signature: agg_signature, - }, - _::[ +ref::{ + name: string, // TEMPORARY + _: [ cast::{ input: partiql_value_type, target: partiql_value_type, @@ -46,7 +31,7 @@ ref::[ isNullable: bool } ] -] +} // Statements @@ -56,25 +41,6 @@ statement::[ }, ] -// Identifiers - -identifier::[ - symbol::{ - symbol: string, - case_sensitivity: case_sensitivity, - }, - qualified::{ - root: symbol, - steps: list::[symbol], - }, - _::[ - case_sensitivity::[ - SENSITIVE, - INSENSITIVE, - ], - ], -] - // Rex rex::{ type: static_type, @@ -92,7 +58,7 @@ rex::{ }, // Refers to a value in the database environment. global::{ - ref: '.ref.obj', + ref: ref, }, unresolved::{ identifier: identifier, @@ -136,7 +102,7 @@ rex::{ }, static::{ - fn: '.ref.fn', + fn: ref, args: list::[rex], }, @@ -150,7 +116,7 @@ rex::{ candidates: list::[candidate], _: [ candidate::{ - fn: '.ref.fn', + fn: ref, coercions: list::[optional::'.ref.cast'], } ] @@ -368,7 +334,7 @@ rel::{ args: list::[rex], }, resolved::{ - agg: '.ref.agg', + agg: ref, set_quantifier: set_quantifier, args: list::[rex], }, @@ -382,7 +348,7 @@ rel::{ paths: list::[path], _: [ path::{ - root: '.rex.op', + root: ref, steps: list::[step], }, step::{ 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 4345398ac..13f64491e 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 @@ -4,6 +4,7 @@ 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.Session import org.partiql.planner.catalog.Table import java.nio.file.Path import kotlin.io.path.isDirectory @@ -29,24 +30,24 @@ internal class LocalCatalog( return name } - override fun getTable(name: Name): Table? { - val path = toPath(name.getNamespace()).resolve(name.getName() + EXT) + override fun getTable(session: Session, name: Name): Table? { + val path = toPath(name.getNamespace()).resolve(name.getText() + EXT) if (path.notExists() || !path.isDirectory()) { return null } - return LocalTable(name.getName(), path) + return LocalTable(name.getText(), path) } - override fun listTables(namespace: Namespace): Collection { + override fun listTables(session: Session, namespace: Namespace): Collection { val path = toPath(namespace) if (path.notExists()) { // throw exception? return emptyList() } - return super.listTables(namespace) + return super.listTables(session, namespace) } - override fun listNamespaces(namespace: Namespace): Collection { + override fun listNamespaces(session: Session, namespace: Namespace): Collection { val path = toPath(namespace) if (path.notExists() || path.isDirectory()) { // throw exception? @@ -59,7 +60,7 @@ internal class LocalCatalog( .map { toNamespace(it.toPath()) } } - override fun getRoutines(name: Name): Collection = emptyList() + override fun getRoutines(session: Session, name: Name): Collection = emptyList() private fun toPath(namespace: Namespace): Path { var curr = root 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 48276a5f6..78d47789b 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 @@ -20,6 +20,7 @@ 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.Session import org.partiql.planner.catalog.Table import org.partiql.spi.connector.Connector @@ -80,14 +81,14 @@ public class MemoryConnector private constructor( override fun getName(): String = name - override fun getTable(name: Name): Table? { + override fun getTable(session: Session, name: Name): Table? { if (name.hasNamespace()) { error("MemoryCatalog does not support namespaces") } - return tables[name.getName()] + return tables[name.getText()] } - override fun listTables(): Collection { + override fun listTables(session: Session): Collection { return tables.keys.map { Name.of(it) } } } From 484064e51c79bc50705ceee2e36913245151c4e9 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 15 Jul 2024 09:56:55 -0700 Subject: [PATCH 12/15] Fix plugins --- .../main/kotlin/org/partiql/plugins/local/LocalCatalog.kt | 4 ++-- .../main/kotlin/org/partiql/plugins/local/LocalTable.kt | 7 +++---- .../kotlin/org/partiql/plugins/memory/MemoryConnector.kt | 2 +- .../main/kotlin/org/partiql/plugins/memory/MemoryTable.kt | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) 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 13f64491e..ff412fb55 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 @@ -31,11 +31,11 @@ internal class LocalCatalog( } override fun getTable(session: Session, name: Name): Table? { - val path = toPath(name.getNamespace()).resolve(name.getText() + EXT) + val path = toPath(name.getNamespace()).resolve(name.getName() + EXT) if (path.notExists() || !path.isDirectory()) { return null } - return LocalTable(name.getText(), path) + return LocalTable(name, path) } override fun listTables(session: Session, namespace: Namespace): Collection { 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 index fa589265c..06f5ad68c 100644 --- 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 @@ -18,6 +18,7 @@ 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.Name import org.partiql.planner.catalog.Table import org.partiql.types.PType import org.partiql.types.StaticType @@ -29,7 +30,7 @@ import kotlin.io.path.reader * Associate a resolved path with a [StaticType] */ internal class LocalTable( - private val name: String, + private val name: Name, private val path: Path, ) : Table, Binding { @@ -37,9 +38,7 @@ internal class LocalTable( assert(!path.isDirectory()) { "LocalTable path must be a file." } } - override fun getName(): String { - return name - } + override fun getName(): Name = name override fun getSchema(): PType { val reader = IonReaderBuilder.standard().build(path.reader()) 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 78d47789b..571026dad 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 @@ -85,7 +85,7 @@ public class MemoryConnector private constructor( if (name.hasNamespace()) { error("MemoryCatalog does not support namespaces") } - return tables[name.getText()] + return tables[name.getName()] } override fun listTables(session: Session): Collection { 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 index 6872cc3c5..278a582e0 100644 --- 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 @@ -2,6 +2,7 @@ package org.partiql.plugins.memory import org.partiql.eval.bindings.Binding import org.partiql.eval.value.Datum +import org.partiql.planner.catalog.Name import org.partiql.planner.catalog.Table import org.partiql.types.PType @@ -11,7 +12,7 @@ public class MemoryTable private constructor( private val datum: Datum, ) : Table, Binding { - override fun getName(): String = name + override fun getName(): Name = Name.of(name) override fun getSchema(): PType = type override fun getDatum(): Datum = datum From e556effff7fd713c307838095b172a4059f65e1d Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 15 Jul 2024 17:09:06 -0700 Subject: [PATCH 13/15] Temporary updates for planner.internal to catalog APIs --- .../org/partiql/planner/catalog/Identifier.kt | 226 ++++++++++++++-- .../org/partiql/planner/internal/Env.kt | 241 +----------------- .../partiql/planner/internal/FnComparator.kt | 32 ++- .../org/partiql/planner/internal/FnMatch.kt | 6 +- .../partiql/planner/internal/FnResolver.kt | 28 +- .../org/partiql/planner/internal/PathItem.kt | 18 -- .../partiql/planner/internal/PathResolver.kt | 118 --------- .../planner/internal/PathResolverAgg.kt | 19 -- .../planner/internal/PathResolverFn.kt | 30 --- .../planner/internal/PathResolverObj.kt | 21 -- .../planner/internal/ProblemGenerator.kt | 18 +- .../planner/internal/casts/Coercions.kt | 7 + .../planner/internal/fn/FnValidator.kt | 18 ++ .../org/partiql/planner/internal/ir/Nodes.kt | 31 ++- .../planner/internal/transforms/AstToPlan.kt | 29 +-- .../internal/transforms/PlanTransform.kt | 38 +-- .../internal/transforms/RelConverter.kt | 8 +- .../internal/transforms/RexConverter.kt | 85 +++--- .../planner/internal/transforms/Symbols.kt | 23 +- .../planner/internal/typer/CompilerType.kt | 53 ++++ .../planner/internal/typer/PlanTyper.kt | 117 ++------- .../partiql/planner/internal/typer/TypeEnv.kt | 33 ++- .../planner/internal/utils/PlanUtils.kt | 39 ++- .../main/resources/partiql_plan_internal.ion | 9 +- 24 files changed, 464 insertions(+), 783 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverAgg.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverFn.kt delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt create mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt index 7f2bd9503..18f8a12c0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -1,69 +1,245 @@ package org.partiql.planner.catalog +import java.util.Spliterator +import java.util.function.Consumer + /** - * Represents an SQL identifier () which is regular (unquoted) or delimited (double-quoted). + * Represents an SQL identifier (possibly qualified). * - * @property text The identifier body. - * @property regular True if the identifier should be treated as an SQL regular identifier. + * @property qualifier If + * @property identifier */ public class Identifier private constructor( - private val text: String, - private val regular: Boolean, -) { + private val qualifier: Array, + private val identifier: Part, +) : Iterable { /** - * Returns the identifier's case-preserved text. + * Returns the unqualified name part. */ - public fun getText(): String = text + public fun getIdentifier(): Part = identifier /** - * Compares this identifier to a string. + * Returns the name's namespace. */ - public fun matches(other: String): Boolean { - return this.text.equals(other, ignoreCase = this.regular) + public fun getQualifier(): Array = qualifier + + /** + * Returns true if the namespace is non-empty. + */ + public fun hasQualifier(): Boolean = qualifier.isNotEmpty() + + /** + * Returns a collection of the identifier parts. + */ + public fun getParts(): List { + return listOf(*qualifier) + identifier } /** - * Compares two identifiers, ignoring case iff at least one identifier is non-delimited. + * Iterable forEach(action). */ - public fun matches(other: Identifier): Boolean { - return this.text.equals(other.text, ignoreCase = (this.regular || other.regular)) + override fun forEach(action: Consumer?) { + getParts().forEach(action) + } + + /** + * Iterable iterator(). + */ + override fun iterator(): Iterator { + return getParts().iterator() + } + + /** + * Iterable spliterator(). + */ + override fun spliterator(): Spliterator { + return getParts().spliterator() + } + + /** + * Compares this identifier to string, possibly ignoring case. + */ + public fun matches(other: String, ignoreCase: Boolean = false): Boolean { + if (this.hasQualifier()) { + return false + } + return if (ignoreCase) { + this.identifier.matches(other) + } else { + other == this.identifier.getText() + } + } + + /** + * Compares one identifier to another, possibly ignoring case. + */ + public fun matches(other: Identifier, ignoreCase: Boolean = false): Boolean { + // + if (this.qualifier.size != other.qualifier.size) { + return false + } + // Compare identifier + if (ignoreCase && !(this.identifier.matches(other.identifier))) { + return false + } else if (this.identifier != other.identifier) { + return false + } + for (i in this.qualifier.indices) { + val lhs = this.qualifier[i] + val rhs = other.qualifier[i] + if (ignoreCase && !lhs.matches(rhs)) { + return false + } else if (lhs != rhs) { + return false + } + } + return true } /** * Compares the case-preserved text of two identifiers — that is case-sensitive equality. */ - override fun equals(other: Any?): Boolean { + public override fun equals(other: Any?): Boolean { if (this === other) { return true } if (other == null || javaClass != other.javaClass) { return false } - return this.text == (other as Identifier).text + other as Identifier + return (this.identifier == other.identifier && this.qualifier.contentEquals(other.qualifier)) + } + + /** + * The hashCode() is case-sensitive — java.util.Arrays.hashCode + */ + public override fun hashCode(): Int { + var result = 1 + result = 31 * result + qualifier.hashCode() + result = 31 * result + identifier.hashCode() + return result } /** - * Returns the hashcode of the identifier's case-preserved text. + * Return the SQL representation of this identifier. */ - override fun hashCode(): Int { - return this.text.hashCode() + public override fun toString(): String = buildString { + if (qualifier.isNotEmpty()) { + append(qualifier.joinToString(".")) + append(".") + } + append(identifier) } /** - * Return the identifier as a SQL string. + * Represents an SQL identifier part which is either regular (unquoted) or delimited (double-quoted). + * + * @property text The case-preserved identifier text. + * @property regular True if the identifier should be treated as an SQL regular identifier. */ - override fun toString(): String = when (regular) { - true -> "\"${text}\"" - false -> text + public class Part private constructor( + private val text: String, + private val regular: Boolean, + ) { + + /** + * Returns the identifier text. + */ + public fun getText(): String = text + + /** + * Returns true iff this is a regular identifier. + */ + public fun isRegular(): Boolean = regular + + /** + * Compares this identifier part to a string. + */ + public fun matches(other: String): Boolean { + return this.text.equals(other, ignoreCase = this.regular) + } + + /** + * Compares two identifiers, ignoring case iff at least one identifier is non-delimited. + */ + public fun matches(other: Part): Boolean { + return this.text.equals(other.text, ignoreCase = (this.regular || other.regular)) + } + + /** + * Compares the case-preserved text of two identifiers — that is case-sensitive equality. + */ + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + return this.text == (other as Part).text + } + + /** + * Returns the hashcode of the identifier's case-preserved text. + */ + override fun hashCode(): Int { + return this.text.hashCode() + } + + /** + * Return the identifier as a SQL string. + */ + override fun toString(): String = when (regular) { + true -> "\"${text}\"" + false -> text + } + + public companion object { + + @JvmStatic + public fun regular(text: String): Part = Part(text, true) + + @JvmStatic + public fun delimited(text: String): Part = Part(text, false) + } } public companion object { @JvmStatic - public fun regular(text: String): Identifier = Identifier(text, true) + public fun regular(text: String): Identifier = Identifier(emptyArray(), Part.regular(text)) + + @JvmStatic + public fun delimited(text: String): Identifier = Identifier(emptyArray(), Part.delimited(text)) + + @JvmStatic + public fun of(part: Part): Identifier = Identifier(emptyArray(), part) @JvmStatic - public fun delimited(text: String): Identifier = Identifier(text, false) + public fun of(vararg parts: Part): Identifier = of(parts.toList()) + + @JvmStatic + public fun of(parts: Collection): Identifier { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.drop(1).toTypedArray() + val identifier = parts.last() + return Identifier(qualifier, identifier) + } + + @JvmStatic + public fun of(vararg parts: String): Identifier = of(parts.toList()) + + @JvmStatic + public fun of(parts: Collection): Identifier { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.drop(1).map { Part.delimited(it) }.toTypedArray() + val identifier = Part.delimited(parts.last()) + return Identifier(qualifier, identifier) + } } } 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 79e7a3a0f..f63f06f98 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -1,11 +1,9 @@ package org.partiql.planner.internal -import org.partiql.ast.Identifier import org.partiql.planner.catalog.Catalog -import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Identifier import org.partiql.planner.catalog.Session import org.partiql.planner.internal.casts.CastTable -import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.rexOpCastResolved import org.partiql.planner.internal.typer.CompilerType @@ -31,136 +29,12 @@ internal class Env( * * Convert any remaining binding names (tail) to a path expression. */ - fun getTable(identifier: Identifier): Rex? = when (identifier) { - is Identifier.Qualified -> getTable(identifier) - is Identifier.Symbol -> getTable(identifier) + fun getTable(identifier: Identifier): Rex? { + TODO("Env.getTable not implemented") } - /** - * - * - * - * @param identifier - * @return - */ - fun getTable(identifier: Identifier.Symbol): Rex? { - val name = Name.of(identifier) - val table = catalog.getTable(session, name) ?: return null - return null - } - - /** - * TODO - * - * Convert any remaining binding names (tail) to a path expression. - */ - fun getTable(identifier: Identifier.Qualified): Rex? { - return null - } - - /** - * This function looks up a global [Identitifier], returning a global reference expression. - * - * Convert any remaining binding names (tail) to a path expression. - * - * @param path - * @return - */ - // fun resolveObj(path: Identifier): Rex? { - // - // val item = objects.lookup(path) ?: return null - // // Create an internal typed reference - // val ref = refObj( - // catalog = item.catalog, - // path = item.handle.path.steps, - // type = CompilerType(item.handle.entity.getPType()), - // ) - // // Rewrite as a path expression. - // val root = rex(ref.type, rexOpVarGlobal(ref)) - // val depth = calculateMatched(path, item.input, ref.path) - // val tail = path.steps.drop(depth) - // return if (tail.isEmpty()) root else root.toPath(tail) - // } - - fun resolveFn(path: Identifier, args: List): Rex? { - // val item = fns.lookup(path) ?: return null - // // Invoke FnResolver to determine if we made a match - // val variants = item.handle.entity.getVariants() - // val match = FnResolver.resolve(variants, args.map { it.type }) - // // If Type mismatch, then we return a missingOp whose trace is all possible candidates. - // if (match == null) { - // val candidates = variants.map { fnSignature -> - // rexOpCallDynamicCandidate( - // fn = refFn( - // item.catalog, - // path = item.handle.path.steps, - // signature = fnSignature - // ), - // coercions = emptyList() - // ) - // } - // return ProblemGenerator.missingRex( - // rexOpCallDynamic(args, candidates), - // ProblemGenerator.incompatibleTypesForOp(path.normalized.joinToString("."), args.map { it.type }) - // ) - // } - // return when (match) { - // is FnMatch.Dynamic -> { - // val candidates = match.candidates.map { - // // Create an internal typed reference for every candidate - // rexOpCallDynamicCandidate( - // fn = refFn( - // catalog = item.catalog, - // path = item.handle.path.steps, - // signature = it.signature, - // ), - // coercions = it.mapping.toList(), - // ) - // } - // // Rewrite as a dynamic call to be typed by PlanTyper - // Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Dynamic(args, candidates)) - // } - // is FnMatch.Static -> { - // // Create an internal typed reference - // val ref = refFn( - // catalog = item.catalog, - // path = item.handle.path.steps, - // signature = match.signature, - // ) - // // Apply the coercions as explicit casts - // val coercions: List = args.mapIndexed { i, arg -> - // when (val cast = match.mapping[i]) { - // null -> arg - // else -> Rex(CompilerType(PType.typeDynamic()), Rex.Op.Cast.Resolved(cast, arg)) - // } - // } - // // Rewrite as a static call to be typed by PlanTyper - // Rex(CompilerType(PType.typeDynamic()), Rex.Op.Call.Static(ref, coercions)) - // } - // } - TODO("resolveFn") - } - - fun resolveAgg(name: String, setQuantifier: Rel.Op.Aggregate.SetQuantifier, args: List): Rel.Op.Aggregate.Call.Resolved? { - // TODO: Eventually, do we want to support sensitive lookup? With a path? - TODO("resolveAgg") - // val path = Identifier(listOf(BindingName(name, BindingCase.INSENSITIVE))) - // val item = aggs.lookup(path) ?: return null - // val candidates = item.handle.entity.getVariants() - // val parameters = args.mapIndexed { i, arg -> arg.type } - // val match = match(candidates, parameters) ?: return null - // val agg = match.first - // val mapping = match.second - // // Create an internal typed reference - // val ref = refAgg(item.catalog, item.handle.path.steps, agg) - // // Apply the coercions as explicit casts - // val coercions: List = args.mapIndexed { i, arg -> - // when (val cast = mapping[i]) { - // null -> arg - // else -> rex(cast.target, rexOpCastResolved(cast, arg)) - // } - // } - // return relOpAggregateCallResolved(ref, setQuantifier, coercions) + fun getRoutine(identifier: Identifier, args: List): Rex? { + TODO("Env.getRoutine not implemented") } fun resolveCast(input: Rex, target: CompilerType): Rex.Op.Cast.Resolved? { @@ -168,109 +42,4 @@ internal class Env( val cast = CastTable.partiql.get(operand, target) ?: return null return rexOpCastResolved(cast, input) } - - // ----------------------- - // Helpers - // ----------------------- - - /** - * Logic for determining how many BindingNames were “matched” by the ConnectorMetadata - * - * Assume: - * - steps_matched = user_input_path_size - path_steps_not_found_size - * - path_steps_not_found_size = catalog_path_sent_to_spi_size - actual_catalog_absolute_path_size - * - * Therefore, we present the equation to [calculateMatched]: - * - steps_matched = user_input_path_size - (catalog_path_sent_to_spi_size - actual_catalog_absolute_path_size) - * = user_input_path_size + actual_catalog_absolute_path_size - catalog_path_sent_to_spi_size - * - * For example: - * - * Assume we are in some catalog, C, in some schema, S. There is a tuple, T, with attribute, A1. Assume A1 is of type - * tuple with an attribute A2. - * If our query references `T.A1.A2`, we will eventually ask SPI (connector C) for `S.T.A1.A2`. In this scenario: - * - The original user input was `T.A1.A2` (length 3) - * - The absolute path returned from SPI will be `S.T` (length 2) - * - The path we eventually sent to SPI to resolve was `S.T.A1.A2` (length 4) - * - * So, we can now use [calculateMatched] to determine how many were actually matched from the user input. Using the - * equation from above: - * - * - steps_matched = len(user input) + len(absolute catalog path) - len(path sent to SPI) - * = len([userInputPath]) + len([actualAbsolutePath]) - len([pathSentToConnector]) - * = 3 + 2 - 4 - * = 5 - 4 - * = 1 - * - * - * Therefore, in this example we have determined that from the original input (`T.A1.A2`) `T` is the value matched in the - * database environment. - */ - // private fun calculateMatched( - // userInputPath: Identitifier, - // pathSentToConnector: Identitifier, - // actualAbsolutePath: List, - // ): Int { - // return userInputPath.steps.size + actualAbsolutePath.size - pathSentToConnector.steps.size - // } - // - // private fun match(candidates: List, args: List): Pair>? { - // // 1. Check for an exact match - // for (candidate in candidates) { - // if (candidate.matches(args)) { - // return candidate to arrayOfNulls(args.size) - // } - // } - // // 2. Look for best match. - // var match: Pair>? = null - // for (candidate in candidates) { - // val m = candidate.match(args) ?: continue - // // TODO AggMatch comparison - // // if (match != null && m.exact < match.exact) { - // // // already had a better match. - // // continue - // // } - // match = m - // } - // // 3. Return best match or null - // return match - // } - // - // /** - // * Check if this function accepts the exact input argument types. Assume same arity. - // */ - // private fun AggSignature.matches(args: List): Boolean { - // for (i in args.indices) { - // val a = args[i] - // val p = parameters[i] - // if (p.type.kind != Kind.DYNAMIC && a != p.type) return false - // } - // return true - // } - // - // /** - // * Attempt to match arguments to the parameters; return the implicit casts if necessary. - // * - // * @param args - // * @return - // */ - // private fun AggSignature.match(args: List): Pair>? { - // val mapping = arrayOfNulls(args.size) - // for (i in args.indices) { - // val arg = args[i] - // val p = parameters[i] - // when { - // // 1. Exact match - // arg == p.type -> continue - // // 2. Match ANY, no coercion needed - // p.type.kind == Kind.DYNAMIC -> continue - // // 3. Check for a coercion - // else -> when (val coercion = Coercions.get(arg, p.type)) { - // null -> return null // short-circuit - // else -> mapping[i] = coercion - // } - // } - // } - // return this to mapping - // } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnComparator.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnComparator.kt index f94f52c10..351393141 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnComparator.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnComparator.kt @@ -1,11 +1,8 @@ package org.partiql.planner.internal -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.FnParameter -import org.partiql.spi.fn.FnSignature +import org.partiql.planner.catalog.Routine import org.partiql.types.PType import org.partiql.types.PType.Kind -import org.partiql.value.PartiQLValueExperimental /** * Function precedence comparator; this is not formally specified. @@ -13,32 +10,33 @@ import org.partiql.value.PartiQLValueExperimental * 1. Fewest args first * 2. Parameters are compared left-to-right */ -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object FnComparator : Comparator { +internal object FnComparator : Comparator { - override fun compare(fn1: FnSignature, fn2: FnSignature): Int { + override fun compare(fn1: Routine, fn2: Routine): Int { // Compare number of arguments - if (fn1.parameters.size != fn2.parameters.size) { - return fn1.parameters.size - fn2.parameters.size + val p1 = fn1.getParameters() + val p2 = fn2.getParameters() + if (p1.size != p2.size) { + return p1.size - p2.size } // Compare operand type precedence - for (i in fn1.parameters.indices) { - val p1 = fn1.parameters[i] - val p2 = fn2.parameters[i] - val comparison = p1.compareTo(p2) + for (i in p2.indices) { + val arg1 = p1[i] + val arg2 = p2[i] + val comparison = arg1.compareTo(arg2) if (comparison != 0) return comparison } // unreachable? return 0 } - private fun FnParameter.compareTo(other: FnParameter): Int = + private fun Routine.Parameter.compareTo(other: Routine.Parameter): Int = comparePrecedence(this.type, other.type) - private fun comparePrecedence(t1: PType, t2: PType): Int { + private fun comparePrecedence(t1: PType.Kind, t2: PType.Kind): Int { if (t1 == t2) return 0 - val p1 = precedence[t1.kind]!! - val p2 = precedence[t2.kind]!! + val p1 = precedence[t1]!! + val p2 = precedence[t2]!! return p1 - p2 } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt index 8fb4197af..67cd4f3a0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt @@ -1,13 +1,11 @@ package org.partiql.planner.internal +import org.partiql.planner.catalog.Routine import org.partiql.planner.internal.ir.Ref -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.FnSignature /** * Result of matching an unresolved function. */ -@OptIn(FnExperimental::class) internal sealed class FnMatch { /** @@ -17,7 +15,7 @@ internal sealed class FnMatch { * @property mapping */ data class Static( - val signature: FnSignature, + val signature: Routine, val mapping: Array, ) : FnMatch() { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt index ceea6f1c5..783d66256 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt @@ -1,10 +1,9 @@ package org.partiql.planner.internal +import org.partiql.planner.catalog.Routine import org.partiql.planner.internal.casts.Coercions import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.typer.CompilerType -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.FnSignature import org.partiql.types.PType.Kind /** @@ -22,7 +21,6 @@ import org.partiql.types.PType.Kind * * Reference https://www.postgresql.org/docs/current/typeconv-func.html */ -@OptIn(FnExperimental::class) internal object FnResolver { /** @@ -34,9 +32,9 @@ internal object FnResolver { * @param args * @return */ - fun resolve(variants: List, args: List): FnMatch? { + fun resolve(variants: List, args: List): FnMatch? { val candidates = variants - .filter { it.parameters.size == args.size } + .filter { it.getParameters().size == args.size } .ifEmpty { return null } // 1. Look for exact match @@ -64,9 +62,9 @@ internal object FnResolver { // 5. If there are DYNAMIC nodes, return all candidates var isDynamic = false for (match in matches) { - val params = match.match.signature.parameters + val params = match.match.signature.getParameters() for (index in params.indices) { - if ((args[index].kind == Kind.DYNAMIC) && params[index].type.kind != Kind.DYNAMIC) { + if ((args[index].kind == Kind.DYNAMIC) && params[index].type != Kind.DYNAMIC) { isDynamic = true } } @@ -87,7 +85,7 @@ internal object FnResolver { * @param args * @return */ - private fun match(candidates: List, args: List): List { + private fun match(candidates: List, args: List): List { val matches = mutableSetOf() for (candidate in candidates) { val m = candidate.match(args) ?: continue @@ -117,9 +115,10 @@ internal object FnResolver { /** * Check if this function accepts the exact input argument types. Assume same arity. */ - private fun FnSignature.matchesExactly(args: List): Boolean { + private fun Routine.matchesExactly(args: List): Boolean { + val parameters = getParameters() for (i in args.indices) { - val a = args[i] + val a = args[i].kind val p = parameters[i] if (a != p.type) return false } @@ -132,23 +131,24 @@ internal object FnResolver { * @param args * @return */ - private fun FnSignature.match(args: List): MatchResult? { + private fun Routine.match(args: List): MatchResult? { val mapping = arrayOfNulls(args.size) + val parameters = getParameters() var exactInputTypes: Int = 0 for (i in args.indices) { val arg = args[i] val p = parameters[i] when { // 1. Exact match - arg == p.type -> { + arg.kind == p.type -> { exactInputTypes++ continue } // 2. Match ANY, no coercion needed // TODO: Rewrite args in this scenario - arg.kind == Kind.UNKNOWN || p.type.kind == Kind.DYNAMIC || arg.kind == Kind.DYNAMIC -> continue + arg.kind == Kind.UNKNOWN || p.type == Kind.DYNAMIC || arg.kind == Kind.DYNAMIC -> continue // 3. Check for a coercion - else -> when (val coercion = Coercions.get(arg, p.type)) { + else -> when (val coercion = Coercions.get(arg.kind, p.type)) { null -> return null // short-circuit else -> mapping[i] = coercion } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt deleted file mode 100644 index 08a7ebca5..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathItem.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle - -/** - * A simple catalog to metadata pair. - * - * @param T - * @property catalog The resolved entity's catalog name. - * @property input The input binding path (sent to SPI) that resulted in this item match. - * @property handle The resolved entity's catalog path and type information. - */ -internal data class PathItem( - @JvmField val catalog: String, - @JvmField val input: BindingPath, - @JvmField val handle: ConnectorHandle, -) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt deleted file mode 100644 index bb89e28c6..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolver.kt +++ /dev/null @@ -1,118 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.PartiQLPlanner -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata - -/** - * This is the base behavior for name resolution. - * - * Let N be the number of steps in a given path. - * - * 1. If N = 1 - * (a) Lookup at .. (relative in catalog) - * 2. If N = 2 - * (a) Lookup at .. (relative in catalog) - * (b) Lookup at . (absolute in catalog) - * 3. If N > 2 - * (a) Lookup at .. (relative in catalog) - * (b) Lookup at . (absolute in catalog) - * (c) Lookup as an absolute where the first step is the catalog. (absolute in system) - * - * @param T - * @property catalog - * @property session - */ -internal abstract class PathResolver( - private val catalog: ConnectorMetadata, - private val session: PartiQLPlanner.Session, -) { - - /** - * The session's current directory represented as [BindingName] steps. - */ - open val schema = session.currentDirectory.map { it.toBindingName() } - - /** - * A [PathResolver] should override this one method for which [ConnectorMetadata] API to call. - * - * @param metadata ConnectorMetadata to resolve this catalog entity in. - * @param path The catalog absolute path. - * @return - */ - abstract fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle? - - /** - * Lookup a `path` following the scoping rules in the class javadoc. - * - * Returns a pair of the - * - * @param path This represents the exact path - * @return - */ - internal fun lookup(path: BindingPath): PathItem? { - val n = path.steps.size - val m = schema.size - return if (m > 0) { - val absPath = BindingPath(schema + path.steps) - when (n) { - 0 -> return null - 1 -> return get(absPath) - 2 -> return get(absPath) ?: get(path) - else -> return get(absPath) ?: get(path) ?: search(path) - } - } else { - // no need to prepend path as it's empty - when (n) { - 0 -> null - 1 -> get(path) - 2 -> get(path) - else -> get(path) ?: search(path) - } - } - } - - /** - * This gets the path in the current catalog. - * - * @param path Catalog absolute path. - * @return - */ - private fun get(path: BindingPath): PathItem? { - val handle = get(catalog, path) ?: return null - return PathItem(session.currentCatalog, path, handle) - } - - /** - * This searches with a system absolute path, using the session to lookup catalogs. - * - * @param path System absolute path. - */ - private fun search(path: BindingPath): PathItem? { - var match: Map.Entry? = null - val first: BindingName = path.steps.first() - for (catalog in session.catalogs) { - if (first.matches(catalog.key)) { - if (match != null) { - // TODO root was already matched, emit ambiguous error - return null - } - match = catalog - } - } - if (match == null) { - return null - } - // Lookup in the unambiguously matched catalog, calculating the depth matched. - val absPath = BindingPath(path.steps.drop(1)) - val catalog = match.key - val metadata = match.value - val handle = get(metadata, absPath) ?: return null - return PathItem(catalog, absPath, handle) - } - - private fun String.toBindingName() = BindingName(this, BindingCase.SENSITIVE) -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverAgg.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverAgg.kt deleted file mode 100644 index 27d3e3dde..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverAgg.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.PartiQLPlanner -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorAgg -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.fn.FnExperimental - -@OptIn(FnExperimental::class) -internal class PathResolverAgg( - catalog: ConnectorMetadata, - session: PartiQLPlanner.Session, -) : PathResolver(catalog, session) { - - override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle.Agg? { - return metadata.getAggregation(path) - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverFn.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverFn.kt deleted file mode 100644 index b87ec6092..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverFn.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.PartiQLPlanner -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorFn -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.fn.FnExperimental - -/** - * PathResolver which calls out to get matching function names. - - * - * @param catalog - * @param session - */ -@OptIn(FnExperimental::class) -internal class PathResolverFn( - catalog: ConnectorMetadata, - session: PartiQLPlanner.Session, -) : PathResolver(catalog, session) { - - /** - * Default INFORMATION_SCHEMA.ROUTINES. Keep empty for now for top-level lookup. - */ - override val schema: List = emptyList() - - override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle.Fn? = metadata.getFunction(path) -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt deleted file mode 100644 index f976c1072..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PathResolverObj.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.partiql.planner.internal - -import org.partiql.planner.PartiQLPlanner -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorHandle -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorObject - -/** - * PathResolver implementation which calls out to get database objects. - * - * @param catalog - * @param session - */ -internal class PathResolverObj( - catalog: ConnectorMetadata, - session: PartiQLPlanner.Session, -) : PathResolver(catalog, session) { - - override fun get(metadata: ConnectorMetadata, path: BindingPath): ConnectorHandle.Obj? = metadata.getObject(path) -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ProblemGenerator.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ProblemGenerator.kt index 10d2f93d2..2cd84b7b3 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ProblemGenerator.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ProblemGenerator.kt @@ -13,7 +13,7 @@ import org.partiql.planner.internal.ir.rexOpMissing import org.partiql.planner.internal.typer.CompilerType import org.partiql.types.PType import org.partiql.types.StaticType -import org.partiql.planner.internal.ir.Identifier as InternalIdentifier +import org.partiql.planner.catalog.Identifier as InternalIdentifier /** * Used to report problems during planning phase. @@ -53,23 +53,15 @@ internal object ProblemGenerator { fun errorRex(trace: Rex.Op, problem: Problem): Rex = rex(CompilerType(PType.typeDynamic(), isMissingValue = true), rexOpErr(problem, listOf(trace))) - private fun InternalIdentifier.debug(): String = when (this) { - is InternalIdentifier.Qualified -> (listOf(root.debug()) + steps.map { it.debug() }).joinToString(".") - is InternalIdentifier.Symbol -> when (caseSensitivity) { - InternalIdentifier.CaseSensitivity.SENSITIVE -> "\"$symbol\"" - InternalIdentifier.CaseSensitivity.INSENSITIVE -> symbol - } - } - fun undefinedFunction(identifier: InternalIdentifier, args: List, location: ProblemLocation = UNKNOWN_PROBLEM_LOCATION): Problem = - problem(location, PlanningProblemDetails.UnknownFunction(identifier.debug(), args.map { PType.fromStaticType(it) })) + problem(location, PlanningProblemDetails.UnknownFunction(identifier.toString(), args.map { PType.fromStaticType(it) })) fun undefinedFunction( args: List, - identifier: org.partiql.planner.internal.ir.Identifier, + identifier: InternalIdentifier, location: ProblemLocation = UNKNOWN_PROBLEM_LOCATION ): Problem = - problem(location, PlanningProblemDetails.UnknownFunction(identifier.debug(), args)) + problem(location, PlanningProblemDetails.UnknownFunction(identifier.toString(), args)) fun undefinedFunction(identifier: String, args: List, location: ProblemLocation = UNKNOWN_PROBLEM_LOCATION): Problem = problem(location, PlanningProblemDetails.UnknownFunction(identifier, args.map { PType.fromStaticType(it) })) @@ -91,7 +83,7 @@ internal object ProblemGenerator { problem(location, PlanningProblemDetails.IncompatibleTypesForOp(actualTypes, operator)) fun unresolvedExcludedExprRoot(root: InternalIdentifier, location: ProblemLocation = UNKNOWN_PROBLEM_LOCATION): Problem = - problem(location, PlanningProblemDetails.UnresolvedExcludeExprRoot(root.debug())) + problem(location, PlanningProblemDetails.UnresolvedExcludeExprRoot(root.toString())) fun unresolvedExcludedExprRoot(root: String, location: ProblemLocation = UNKNOWN_PROBLEM_LOCATION): Problem = problem(location, PlanningProblemDetails.UnresolvedExcludeExprRoot(root)) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt index 5c8a944e0..cc94eaec3 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt @@ -13,6 +13,13 @@ import org.partiql.types.PType.Kind */ internal object Coercions { + /** + * TODO all coercion APIs are based on kind, we don't need a full PType here. + */ + fun get(input: PType.Kind, target: PType.Kind): Ref.Cast? { + TODO("coercion from kind") + } + fun get(input: PType, target: PType): Ref.Cast? { return getCoercion(input, target) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt new file mode 100644 index 000000000..49dd64eb1 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt @@ -0,0 +1,18 @@ +package org.partiql.planner.internal.fn + +import org.partiql.planner.catalog.Routine +import org.partiql.types.PType + +/** + * Class for function validation to compute return types. + */ +internal object FnValidator { + + /** + * This computes a PType for a given function. + */ + @JvmStatic + fun validate(routine: Routine, args: List): PType { + return PType.typeDynamic() + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 44a7af3dd..573cbeca0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -14,7 +14,6 @@ import kotlin.jvm.JvmStatic import kotlin.random.Random import org.partiql.`value`.PartiQLValue import org.partiql.`value`.PartiQLValueExperimental -import org.partiql.ast.Identifier import org.partiql.errors.Problem import org.partiql.planner.`internal`.ir.builder.PartiQlPlanBuilder import org.partiql.planner.`internal`.ir.builder.RefBuilder @@ -78,6 +77,8 @@ import org.partiql.planner.`internal`.ir.builder.RexOpVarUnresolvedBuilder import org.partiql.planner.`internal`.ir.builder.StatementQueryBuilder import org.partiql.planner.`internal`.ir.visitor.PlanVisitor import org.partiql.planner.`internal`.typer.CompilerType +import org.partiql.planner.catalog.Identifier +import org.partiql.planner.catalog.Routine internal abstract class PlanNode { @JvmField @@ -453,13 +454,12 @@ internal data class Rex( internal data class Static( @JvmField - internal val fn: Ref, + internal val fn: Routine, @JvmField internal val args: List, ) : Call() { public override val children: List by lazy { val kids = mutableListOf() - kids.add(fn) kids.addAll(args) kids.filterNotNull() } @@ -493,13 +493,12 @@ internal data class Rex( internal data class Candidate( @JvmField - internal val fn: Ref, + internal val fn: Routine, @JvmField internal val coercions: List, ) : PlanNode() { public override val children: List by lazy { val kids = mutableListOf() - kids.add(fn) kids.addAll(coercions) kids.filterNotNull() } @@ -1303,7 +1302,7 @@ internal data class Rel( internal data class Resolved( @JvmField - internal val agg: Ref, + internal val agg: Routine, @JvmField internal val setQuantifier: SetQuantifier, @JvmField @@ -1311,7 +1310,6 @@ internal data class Rel( ) : Call() { public override val children: List by lazy { val kids = mutableListOf() - kids.add(agg) kids.addAll(args) kids.filterNotNull() } @@ -1353,7 +1351,7 @@ internal data class Rel( internal data class Path( @JvmField - internal val root: Ref, + internal val root: Rex.Op, @JvmField internal val steps: List, ) : PlanNode() { @@ -1420,6 +1418,23 @@ internal data class Rel( internal fun builder(): RelOpExcludeTypeStructSymbolBuilder = RelOpExcludeTypeStructSymbolBuilder() } + + // Explicitly override `equals` and `hashcode` for case-insensitivity + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as StructSymbol + + if (!symbol.equals(other.symbol, ignoreCase = true)) return false + if (children != other.children) return false + + return true + } + + override fun hashCode(): Int { + return symbol.lowercase().hashCode() + } } internal data class StructKey( diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt index 050e1bb84..963ecb52d 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt @@ -19,13 +19,11 @@ package org.partiql.planner.internal.transforms import org.partiql.ast.AstNode import org.partiql.ast.Expr import org.partiql.ast.visitor.AstBaseVisitor +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env -import org.partiql.planner.internal.ir.identifierQualified -import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.statementQuery import org.partiql.ast.Identifier as AstIdentifier import org.partiql.ast.Statement as AstStatement -import org.partiql.planner.internal.ir.Identifier as PlanIdentifier import org.partiql.planner.internal.ir.Statement as PlanStatement /** @@ -53,23 +51,24 @@ internal object AstToPlan { // --- Helpers -------------------- - fun convert(identifier: AstIdentifier): PlanIdentifier = when (identifier) { + fun convert(identifier: AstIdentifier): Identifier = when (identifier) { is AstIdentifier.Qualified -> convert(identifier) is AstIdentifier.Symbol -> convert(identifier) } - fun convert(identifier: AstIdentifier.Qualified): PlanIdentifier.Qualified { - val root = convert(identifier.root) - val steps = identifier.steps.map { convert(it) } - return identifierQualified(root, steps) + fun convert(identifier: AstIdentifier.Qualified): Identifier { + val parts = mutableListOf() + parts.add(part(identifier.root)) + parts.addAll(identifier.steps.map { part(it) }) + return Identifier.of(parts) } - fun convert(identifier: AstIdentifier.Symbol): PlanIdentifier.Symbol { - val symbol = identifier.symbol - val case = when (identifier.caseSensitivity) { - AstIdentifier.CaseSensitivity.SENSITIVE -> PlanIdentifier.CaseSensitivity.SENSITIVE - AstIdentifier.CaseSensitivity.INSENSITIVE -> PlanIdentifier.CaseSensitivity.INSENSITIVE - } - return identifierSymbol(symbol, case) + fun convert(identifier: AstIdentifier.Symbol): Identifier { + return Identifier.of(part(identifier)) + } + + fun part(identifier: AstIdentifier.Symbol): Identifier.Part = when (identifier.caseSensitivity) { + AstIdentifier.CaseSensitivity.SENSITIVE -> Identifier.Part.delimited(identifier.symbol) + AstIdentifier.CaseSensitivity.INSENSITIVE -> Identifier.Part.regular(identifier.symbol) } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt index 0d0c7de20..cf7a74b66 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt @@ -7,7 +7,6 @@ import org.partiql.plan.rexOpCast import org.partiql.plan.rexOpErr import org.partiql.planner.internal.PlannerFlag import org.partiql.planner.internal.ProblemGenerator -import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.PartiQLPlan import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.ir.Rel @@ -61,24 +60,10 @@ internal class PlanTransform( error("Not implemented") } - override fun visitRef(node: Ref, ctx: Unit) = super.visitRef(node, ctx) as org.partiql.plan.Ref - - /** - * Insert into symbol table, returning the public reference. - */ - override fun visitRefObj(node: Ref.Obj, ctx: Unit) = symbols.insert(node) - - /** - * Insert into symbol table, returning the public reference. - */ - override fun visitRefFn(node: Ref.Fn, ctx: Unit) = symbols.insert(node) - - /** - * Insert into symbol table, returning the public reference. - */ - override fun visitRefAgg(node: Ref.Agg, ctx: Unit) = symbols.insert(node) + override fun visitRef(node: Ref, ctx: Unit): org.partiql.plan.Ref { + TODO("Catalog ref not implemented") + } - @OptIn(PartiQLValueExperimental::class) override fun visitRefCast(node: Ref.Cast, ctx: Unit) = org.partiql.plan.refCast(node.input, node.target, node.isNullable) @@ -90,23 +75,6 @@ internal class PlanTransform( return org.partiql.plan.Statement.Query(root) } - override fun visitIdentifier(node: Identifier, ctx: Unit) = - super.visitIdentifier(node, ctx) as org.partiql.plan.Identifier - - override fun visitIdentifierSymbol(node: Identifier.Symbol, ctx: Unit) = org.partiql.plan.Identifier.Symbol( - symbol = node.symbol, - caseSensitivity = when (node.caseSensitivity) { - Identifier.CaseSensitivity.SENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.SENSITIVE - Identifier.CaseSensitivity.INSENSITIVE -> org.partiql.plan.Identifier.CaseSensitivity.INSENSITIVE - } - ) - - override fun visitIdentifierQualified(node: Identifier.Qualified, ctx: Unit) = - org.partiql.plan.Identifier.Qualified( - root = visitIdentifierSymbol(node.root, ctx), - steps = node.steps.map { visitIdentifierSymbol(it, ctx) } - ) - // EXPRESSIONS override fun visitRex(node: Rex, ctx: Unit): org.partiql.plan.Rex { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt index c1c62bb09..4804d5cc9 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt @@ -75,7 +75,6 @@ import org.partiql.value.PartiQLValueExperimental import org.partiql.value.boolValue import org.partiql.value.int32Value import org.partiql.value.stringValue -import org.partiql.planner.internal.ir.Identifier as InternalId /** * Lexically scoped state for use in translating an individual SELECT statement. @@ -366,10 +365,11 @@ internal object RelConverter { schema.add(binding) val args = expr.args.map { arg -> arg.toRex(env) } val id = AstToPlan.convert(expr.function) - val name = when (id) { - is InternalId.Qualified -> error("Qualified aggregation calls are not supported.") - is InternalId.Symbol -> id.symbol.lowercase() + if (id.hasQualifier()) { + error("Qualified aggregation calls are not supported.") } + // lowercase normalize all calls + val name = id.getIdentifier().getText().lowercase() if (name == "count" && expr.args.isEmpty()) { relOpAggregateCallUnresolved( name, diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 330397672..ae05abb59 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -24,13 +24,11 @@ import org.partiql.ast.SetOp import org.partiql.ast.SetQuantifier import org.partiql.ast.Type import org.partiql.ast.visitor.AstBaseVisitor +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env -import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.builder.plan -import org.partiql.planner.internal.ir.identifierQualified -import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.rel import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.ir.relOpJoin @@ -153,7 +151,7 @@ internal object RexConverter { val arg = visitExprCoerce(node.expr, context) val args = listOf(arg) // Fn - val id = identifierSymbol(node.op.name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val id = Identifier.delimited(node.op.name.lowercase()) val op = rexOpCallUnresolved(id, args) return rex(type, op) } @@ -199,7 +197,7 @@ internal object RexConverter { rex(type, op) } else -> { - val id = identifierSymbol(node.op.name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val id = Identifier.delimited(node.op.name.lowercase()) val op = rexOpCallUnresolved(id, args) rex(type, op) } @@ -213,23 +211,6 @@ internal object RexConverter { node.select is Select.Project || node.select is Select.Star ) - private fun mergeIdentifiers(root: Identifier, steps: List): Identifier { - if (steps.isEmpty()) { - return root - } - val (newRoot, firstSteps) = when (root) { - is Identifier.Symbol -> root to emptyList() - is Identifier.Qualified -> root.root to root.steps - } - val followingSteps = steps.flatMap { step -> - when (step) { - is Identifier.Symbol -> listOf(step) - is Identifier.Qualified -> listOf(step.root) + step.steps - } - } - return identifierQualified(newRoot, firstSteps + followingSteps) - } - override fun visitExprPath(node: Expr.Path, context: Env): Rex { // Args val root = visitExprCoerce(node.root, context) @@ -237,26 +218,22 @@ internal object RexConverter { // Attempt to create qualified identifier val (newRoot, newSteps) = when (val op = root.op) { is Rex.Op.Var.Unresolved -> { - val identifierSteps = mutableListOf() - run { - node.steps.forEach { step -> - if (step !is Expr.Path.Step.Symbol) { - return@run - } - identifierSteps.add(AstToPlan.convert(step.symbol)) - } - } - when (identifierSteps.size) { - 0 -> root to node.steps - else -> { - val newRoot = rex( - ANY, - rexOpVarUnresolved(mergeIdentifiers(op.identifier, identifierSteps), op.scope) - ) - val newSteps = node.steps.subList(identifierSteps.size, node.steps.size) - newRoot to newSteps + + + // convert consecutive symbol path steps to the root identifier + var i = 0 + val parts = mutableListOf() + parts.addAll(op.identifier.getParts()) + for (step in node.steps) { + if (step !is Expr.Path.Step.Symbol) { + break } + parts.add(AstToPlan.part(step.symbol)) + i += 1 } + val newRoot = rex(ANY, rexOpVarUnresolved(Identifier.of(parts), op.scope)) + val newSteps = node.steps.subList(i, node.steps.size) + newRoot to newSteps } else -> root to node.steps } @@ -290,16 +267,16 @@ internal object RexConverter { } is Expr.Path.Step.Symbol -> { - val identifier = AstToPlan.convert(step.symbol) - val op = when (identifier.caseSensitivity) { - Identifier.CaseSensitivity.SENSITIVE -> rexOpPathKey( - current, - rexString(identifier.symbol) - ) - - Identifier.CaseSensitivity.INSENSITIVE -> rexOpPathSymbol(current, identifier.symbol) + when (step.symbol.caseSensitivity) { + org.partiql.ast.Identifier.CaseSensitivity.SENSITIVE -> { + // case-sensitive path step becomes a key lookup + rexOpPathKey(current, rexString(step.symbol.symbol)) + } + org.partiql.ast.Identifier.CaseSensitivity.INSENSITIVE -> { + // case-insensitive path step becomes a symbol lookup + rexOpPathSymbol(current, step.symbol.symbol) + } } - op } // Unpivot and Wildcard steps trigger the rewrite @@ -415,7 +392,7 @@ internal object RexConverter { val type = (ANY) // Fn val id = AstToPlan.convert(node.function) - if (id is Identifier.Symbol && id.symbol.equals("TUPLEUNION", ignoreCase = true)) { + if (id.matches("TUPLEUNION")) { return visitExprCallTupleUnion(node, context) } // Args @@ -440,7 +417,7 @@ internal object RexConverter { } // Converts AST CASE (x) WHEN y THEN z --> Plan CASE WHEN x = y THEN z - val id = identifierSymbol(Expr.Binary.Op.EQ.name.lowercase(), Identifier.CaseSensitivity.SENSITIVE) + val id = Identifier.delimited(Expr.Binary.Op.EQ.name.lowercase()) val createBranch: (Rex, Rex) -> Rex.Op.Case.Branch = { condition: Rex, result: Rex -> val updatedCondition = when (rex) { null -> condition @@ -869,7 +846,7 @@ internal object RexConverter { op = Rex.Op.Select( constructor = Rex( ANY, - Rex.Op.Var.Unresolved(Identifier.Symbol("_0", Identifier.CaseSensitivity.SENSITIVE), Rex.Op.Var.Scope.LOCAL) + Rex.Op.Var.Unresolved(Identifier.delimited("_0"), Rex.Op.Var.Scope.LOCAL) ), rel = rel ) @@ -880,7 +857,7 @@ internal object RexConverter { private fun negate(call: Rex.Op.Call): Rex.Op.Call { val name = Expr.Unary.Op.NOT.name - val id = identifierSymbol(name.lowercase(), Identifier.CaseSensitivity.SENSITIVE) + val id = Identifier.delimited(name.lowercase()) // wrap val arg = rex(BOOL, call) // rewrite call @@ -892,7 +869,7 @@ internal object RexConverter { * The purpose of having such hidden function is to prevent usage of generated function name in query text. */ private fun call(name: String, vararg args: Rex): Rex.Op.Call { - val id = identifierSymbol(name, Identifier.CaseSensitivity.INSENSITIVE) + val id = Identifier.regular(name) return rexOpCallUnresolved(id, args.toList()) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/Symbols.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/Symbols.kt index 9aca5cbf0..2e4314820 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/Symbols.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/Symbols.kt @@ -2,11 +2,7 @@ package org.partiql.planner.internal.transforms import org.partiql.plan.Catalog import org.partiql.plan.builder.CatalogBuilder -import org.partiql.plan.catalogItemAgg -import org.partiql.plan.catalogItemFn -import org.partiql.plan.catalogItemValue import org.partiql.planner.internal.ir.Ref -import org.partiql.spi.fn.FnExperimental import org.partiql.plan.Ref as CatalogRef /** @@ -26,22 +22,9 @@ internal class Symbols private constructor() { return catalogs.map { it.build() } } - fun insert(ref: Ref.Obj): CatalogRef = insert( - catalog = ref.catalog, - item = catalogItemValue(ref.path, ref.type), - ) - - @OptIn(FnExperimental::class) - fun insert(ref: Ref.Fn): CatalogRef = insert( - catalog = ref.catalog, - item = catalogItemFn(ref.path, ref.signature.specific), - ) - - @OptIn(FnExperimental::class) - fun insert(ref: Ref.Agg): CatalogRef = insert( - catalog = ref.catalog, - item = catalogItemAgg(ref.path, ref.signature.specific), - ) + fun insert(ref: Ref): CatalogRef { + TODO("Catalog not implemented") + } private fun insert(catalog: String, item: Catalog.Item): CatalogRef { val i = upsert(catalog) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/CompilerType.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/CompilerType.kt index 722db8d4e..ce233a33a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/CompilerType.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/CompilerType.kt @@ -1,5 +1,6 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.catalog.Identifier import org.partiql.types.PType import org.partiql.types.PType.Kind @@ -74,4 +75,56 @@ internal class CompilerType( return result } } + + internal fun isNumeric(): Boolean { + return this.kind in setOf( + Kind.INT, + Kind.INT_ARBITRARY, + Kind.BIGINT, + Kind.TINYINT, + Kind.SMALLINT, + Kind.REAL, + Kind.DOUBLE_PRECISION, + Kind.DECIMAL, + Kind.DECIMAL_ARBITRARY + ) + } + + /** + * Assumes that the type is either a struct or row. + * + * @return null when the field definitely does not exist; dynamic when the type cannot be determined + */ + internal fun getSymbol(field: String): Pair? { + if (this.kind == Kind.STRUCT) { + return Identifier.Part.regular(field) to CompilerType(PType.typeDynamic()) + } + val fields = this.fields.mapNotNull { + when (it.name.equals(field, true)) { + true -> it.name to it.type + false -> null + } + }.ifEmpty { return null } + val type = anyOf(fields.map { it.second }) + val ids = fields.map { it.first }.toSet() + return when (ids.size > 1) { + true -> Identifier.Part.regular(field) to type + false -> Identifier.Part.delimited(ids.first()) to type + } + } + + internal companion object { + + @JvmStatic + internal fun anyOf(types: Collection): CompilerType { + if (types.isEmpty()) { + return CompilerType(PType.typeDynamic()) + } + val unique = types.toSet() + return when (unique.size) { + 1 -> unique.first() + else -> CompilerType(PType.typeDynamic()) + } + } + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 33383eeef..1405eaae3 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -16,10 +16,10 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env import org.partiql.planner.internal.ProblemGenerator import org.partiql.planner.internal.exclude.ExcludeRepr -import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.PlanNode import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex @@ -52,10 +52,6 @@ import org.partiql.planner.internal.ir.rexOpSubquery import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.ir.util.PlanRewriter import org.partiql.planner.internal.utils.PlanUtils -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.fn.FnExperimental import org.partiql.types.Field import org.partiql.types.PType import org.partiql.types.PType.Kind @@ -86,6 +82,7 @@ internal class PlanTyper(private val env: Env) { return statementQuery(root) } internal companion object { + fun PType.static(): CompilerType = CompilerType(this) fun anyOf(types: Collection): PType? { @@ -166,20 +163,6 @@ internal class PlanTyper(private val env: Env) { fun PType.toCType(): CompilerType = CompilerType(this) fun List.toCType(): List = this.map { it.toCType() } - - fun CompilerType.isNumeric(): Boolean { - return this.kind in setOf( - Kind.INT, - Kind.INT_ARBITRARY, - Kind.BIGINT, - Kind.TINYINT, - Kind.SMALLINT, - Kind.REAL, - Kind.DOUBLE_PRECISION, - Kind.DECIMAL, - Kind.DECIMAL_ARBITRARY - ) - } } /** @@ -498,8 +481,7 @@ internal class PlanTyper(private val env: Env) { is Rex.Op.Var.Unresolved -> { // resolve `root` to local binding val locals = TypeEnv(input.type.schema, outer) - val path = root.identifier.toBindingPath() - val resolved = locals.resolve(path) + val resolved = locals.resolve(root.identifier) if (resolved == null) { ProblemGenerator.missingRex( emptyList(), @@ -601,14 +583,14 @@ internal class PlanTyper(private val env: Env) { } override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: CompilerType?): Rex { - val path = node.identifier.toBindingPath() + val path = node.identifier val scope = when (node.scope) { Rex.Op.Var.Scope.DEFAULT -> strategy Rex.Op.Var.Scope.LOCAL -> Scope.LOCAL } val resolvedVar = when (scope) { - Scope.LOCAL -> locals.resolve(path) ?: env.resolveObj(path) - Scope.GLOBAL -> env.resolveObj(path) ?: locals.resolve(path) + Scope.LOCAL -> locals.resolve(path) ?: env.getTable(path) + Scope.GLOBAL -> env.getTable(path) ?: locals.resolve(path) } if (resolvedVar == null) { val id = PlanUtils.externalize(node.identifier) @@ -622,7 +604,10 @@ internal class PlanTyper(private val env: Env) { return visitRex(resolvedVar, null) } - override fun visitRexOpVarGlobal(node: Rex.Op.Var.Global, ctx: CompilerType?): Rex = rex(node.ref.type, node) + override fun visitRexOpVarGlobal(node: Rex.Op.Var.Global, ctx: CompilerType?): Rex { + TODO("typing of RexOpVarGlobal") + // rex(node.ref.type, node) + } /** * TODO: Create a function signature for the Rex.Op.Path.Index to get automatic coercions. @@ -741,9 +726,9 @@ internal class PlanTyper(private val env: Env) { ) ) } - return when (field.first.caseSensitivity) { - Identifier.CaseSensitivity.INSENSITIVE -> Rex(field.second, Rex.Op.Path.Symbol(root, node.key)) - Identifier.CaseSensitivity.SENSITIVE -> Rex(field.second, Rex.Op.Path.Key(root, rexString(field.first.symbol))) + return when (field.first.isRegular()) { + true -> Rex(field.second, Rex.Op.Path.Symbol(root, node.key)) + else -> Rex(field.second, Rex.Op.Path.Key(root, rexString(field.first.getText()))) } } @@ -765,28 +750,6 @@ internal class PlanTyper(private val env: Env) { private fun rexString(str: String) = rex(CompilerType(PType.typeString()), Rex.Op.Lit(stringValue(str))) - /** - * Assumes that the type is either a struct or row. - * @return null when the field definitely does not exist; dynamic when the type cannot be determined - */ - private fun CompilerType.getSymbol(field: String): Pair? { - if (this.kind == Kind.STRUCT) { - return Identifier.Symbol(field, Identifier.CaseSensitivity.INSENSITIVE) to CompilerType(PType.typeDynamic()) - } - val fields = this.fields!!.mapNotNull { - when (it.name.equals(field, true)) { - true -> it.name to it.type - false -> null - } - }.ifEmpty { return null } - val type = anyOf(fields.map { it.second }) ?: PType.typeDynamic() - val ids = fields.map { it.first }.toSet() - return when (ids.size > 1) { - true -> Identifier.Symbol(field, Identifier.CaseSensitivity.INSENSITIVE) to type.toCType() - false -> Identifier.Symbol(ids.first(), Identifier.CaseSensitivity.SENSITIVE) to type.toCType() - } - } - override fun visitRexOpCastUnresolved(node: Rex.Op.Cast.Unresolved, ctx: CompilerType?): Rex { val arg = visitRex(node.arg, null) val cast = env.resolveCast(arg, node.target) ?: return ProblemGenerator.errorRex( @@ -804,8 +767,7 @@ internal class PlanTyper(private val env: Env) { // Type the arguments val args = node.args.map { visitRex(it, null) } // Attempt to resolve in the environment - val path = node.identifier.toBindingPath() - val rex = env.resolveFn(path, args) + val rex = env.getRoutine(node.identifier, args) if (rex == null) { return ProblemGenerator.errorRex( causes = args.map { it.op }, @@ -823,7 +785,6 @@ internal class PlanTyper(private val env: Env) { * @param ctx * @return */ - @OptIn(FnExperimental::class) override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: CompilerType?): Rex { // Apply the coercions as explicit casts val args: List = node.args.map { @@ -855,11 +816,12 @@ internal class PlanTyper(private val env: Env) { * @param ctx * @return */ - @OptIn(FnExperimental::class) override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: CompilerType?): Rex { - val types = node.candidates.map { candidate -> candidate.fn.signature.returns }.toMutableSet() + val types = node.candidates.map { candidate -> + val kind = candidate.fn.getReturnType() + }.toMutableSet() // TODO: Should this always be DYNAMIC? - return Rex(type = CompilerType(anyOf(types) ?: PType.typeDynamic()), op = node) + return Rex(type = CompilerType.anyOf(types), op = node) } override fun visitRexOpCase(node: Rex.Op.Case, ctx: CompilerType?): Rex { @@ -1184,7 +1146,6 @@ internal class PlanTyper(private val env: Env) { return Rex(type.toCType(), Rex.Op.Struct(fields)) } - @OptIn(FnExperimental::class) private fun replaceGeneratedTupleUnionArg(node: Rex): Rex? { if (node.op is Rex.Op.Struct && node.type.kind == Kind.ROW) { return node @@ -1275,15 +1236,15 @@ internal class PlanTyper(private val env: Env) { * Let TX be the single-column table that is the result of applying the * to each row of T and eliminating null values <--- all NULL values are eliminated as inputs */ - @OptIn(FnExperimental::class) fun resolveAgg(node: Rel.Op.Aggregate.Call.Unresolved): Pair { // Type the arguments val args = node.args.map { visitRex(it, null) } val argsResolved = Rel.Op.Aggregate.Call.Unresolved(node.name, node.setQuantifier, args) - // Resolve the function - val call = env.resolveAgg(node.name, node.setQuantifier, args) ?: return argsResolved to CompilerType(PType.typeDynamic()) - return call to CompilerType(call.agg.signature.returns) + TODO("resolveAgg not implemented") + // val path = Identifier.delimited(node.name.lowercase()) + // val call = env.getRoutine(path, node.setQuantifier, args) ?: return argsResolved to CompilerType(PType.typeDynamic()) + // return call to CompilerType(call.agg.signature.returns) } } @@ -1322,29 +1283,8 @@ internal class PlanTyper(private val env: Env) { return this.copy(schema = schema.mapIndexed { i, binding -> binding.copy(type = types[i]) }) } - private fun Identifier.toBindingPath() = when (this) { - is Identifier.Qualified -> this.toBindingPath() - is Identifier.Symbol -> BindingPath(listOf(this.toBindingName())) - } - - private fun Identifier.Qualified.toBindingPath() = - BindingPath(steps = listOf(this.root.toBindingName()) + steps.map { it.toBindingName() }) - - private fun Identifier.Symbol.toBindingName() = BindingName( - name = symbol, - case = when (caseSensitivity) { - Identifier.CaseSensitivity.SENSITIVE -> BindingCase.SENSITIVE - Identifier.CaseSensitivity.INSENSITIVE -> BindingCase.INSENSITIVE - } - ) - private fun Rel.isOrdered(): Boolean = type.props.contains(Rel.Prop.ORDERED) - /** - * Produce a union type from all the - */ - private fun List.toUnionType(): PType = anyOf(map { it.type }.toSet()) ?: PType.typeDynamic() - private fun getElementTypeForFromSource(fromSourceType: CompilerType): CompilerType = when (fromSourceType.kind) { Kind.DYNAMIC -> CompilerType(PType.typeDynamic()) Kind.BAG, Kind.LIST, Kind.SEXP -> fromSourceType.typeParameter @@ -1356,9 +1296,10 @@ internal class PlanTyper(private val env: Env) { val output = input.map { when (val root = item.root) { is Rex.Op.Var.Unresolved -> { - when (val id = root.identifier) { - is Identifier.Symbol -> { - if (id.isEquivalentTo(it.name)) { + when (root.identifier.hasQualifier()) { + true -> it + else -> { + if (root.identifier.matches(it.name)) { // recompute the PType of this binding after applying the exclusions val type = it.type.exclude(item.steps, lastStepOptional = false) it.copy(type = type) @@ -1366,7 +1307,6 @@ internal class PlanTyper(private val env: Env) { it } } - is Identifier.Qualified -> it } } is Rex.Op.Var.Local, is Rex.Op.Var.Global -> it @@ -1375,9 +1315,4 @@ internal class PlanTyper(private val env: Env) { } return output } - - private fun Identifier.Symbol.isEquivalentTo(other: String): Boolean = when (caseSensitivity) { - Identifier.CaseSensitivity.SENSITIVE -> symbol.equals(other) - Identifier.CaseSensitivity.INSENSITIVE -> symbol.equals(other, ignoreCase = true) - } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt index 17a7acb3c..0eb42ddfb 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt @@ -1,5 +1,6 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.rex @@ -7,9 +8,6 @@ import org.partiql.planner.internal.ir.rexOpLit import org.partiql.planner.internal.ir.rexOpPathKey import org.partiql.planner.internal.ir.rexOpPathSymbol import org.partiql.planner.internal.ir.rexOpVarLocal -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath import org.partiql.types.PType import org.partiql.types.PType.Kind import org.partiql.types.StaticType @@ -44,13 +42,14 @@ internal data class TypeEnv( * @param path * @return */ - fun resolve(path: BindingPath): Rex? { - val head: BindingName = path.steps[0] - var tail: List = path.steps.drop(1) + fun resolve(path: Identifier): Rex? { + val parts = path.getParts() + val head: Identifier.Part = parts[0] + var tail: List = parts.drop(1) var r = matchRoot(head) if (r == null) { r = matchStruct(head) ?: return null - tail = path.steps + tail = parts } // Convert any remaining binding names (tail) to an untyped path expression. return if (tail.isEmpty()) r else r.toPath(tail) @@ -69,7 +68,7 @@ internal data class TypeEnv( * @param name * @return */ - private fun matchRoot(name: BindingName, depth: Int = 0): Rex? { + private fun matchRoot(name: Identifier.Part, depth: Int = 0): Rex? { var r: Rex? = null for (i in schema.indices) { val local = schema[i] @@ -94,7 +93,7 @@ internal data class TypeEnv( * @param name * @return */ - private fun matchStruct(name: BindingName, depth: Int = 0): Rex? { + private fun matchStruct(name: Identifier.Part, depth: Int = 0): Rex? { var c: Rex? = null var known = false for (i in schema.indices) { @@ -131,7 +130,7 @@ internal data class TypeEnv( } /** - * Searches for the [BindingName] within the given [StaticType]. + * Searches for the [Identifier.Part] within the given [StaticType]. * * Returns * - true iff known to contain key @@ -141,7 +140,7 @@ internal data class TypeEnv( * @param name * @return */ - private fun CompilerType.containsKey(name: BindingName): Boolean? { + private fun CompilerType.containsKey(name: Identifier.Part): Boolean? { return when (this.kind) { Kind.ROW -> this.fields!!.any { name.matches(it.name) } Kind.STRUCT -> null @@ -153,20 +152,20 @@ internal data class TypeEnv( companion object { /** - * Converts a list of [BindingName] to a path expression. + * Converts a list of [Identifier.Part] to a path expression. * * 1) Case SENSITIVE identifiers become string literal key lookups. * 2) Case INSENSITIVE identifiers become symbol lookups. * - * @param steps + * @param parts * @return */ @JvmStatic @OptIn(PartiQLValueExperimental::class) - internal fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> - val op = when (step.case) { - BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(CompilerType(PType.typeString()), rexOpLit(stringValue(step.name)))) - BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + internal fun Rex.toPath(parts: List): Rex = parts.fold(this) { curr, part -> + val op = when (part.isRegular()) { + true -> rexOpPathSymbol(curr, part.getText()) + else -> rexOpPathKey(curr, rex(CompilerType(PType.typeString()), rexOpLit(stringValue(part.getText())))) } rex(CompilerType(PType.typeDynamic()), op) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/utils/PlanUtils.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/utils/PlanUtils.kt index 4a0e1b519..1b890b4ef 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/utils/PlanUtils.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/utils/PlanUtils.kt @@ -1,6 +1,8 @@ package org.partiql.planner.internal.utils import org.partiql.plan.Identifier +import org.partiql.plan.identifierQualified +import org.partiql.plan.identifierSymbol internal object PlanUtils { @@ -24,27 +26,24 @@ internal object PlanUtils { Identifier.CaseSensitivity.INSENSITIVE -> node.symbol } - fun externalize(node: org.partiql.planner.internal.ir.Identifier): Identifier = when (node) { - is org.partiql.planner.internal.ir.Identifier.Symbol -> externalize(node) - is org.partiql.planner.internal.ir.Identifier.Qualified -> externalize(node) - } - - private fun externalize(node: org.partiql.planner.internal.ir.Identifier.Symbol): Identifier.Symbol { - val symbol = node.symbol - val case = externalize(node.caseSensitivity) - return Identifier.Symbol(symbol, case) - } - - private fun externalize(node: org.partiql.planner.internal.ir.Identifier.Qualified): Identifier.Qualified { - val root = externalize(node.root) - val steps = node.steps.map { externalize(it) } - return Identifier.Qualified(root, steps) + fun externalize(identifier: org.partiql.planner.catalog.Identifier): Identifier { + if (identifier.hasQualifier()) { + val symbols = identifier.getParts().map { externalize(it) } + identifierQualified( + root = symbols.first(), + steps = symbols.subList(1, symbols.size) + ) + } + return externalize(identifier.getIdentifier()) } - private fun externalize(node: org.partiql.planner.internal.ir.Identifier.CaseSensitivity): Identifier.CaseSensitivity { - return when (node) { - org.partiql.planner.internal.ir.Identifier.CaseSensitivity.SENSITIVE -> Identifier.CaseSensitivity.SENSITIVE - org.partiql.planner.internal.ir.Identifier.CaseSensitivity.INSENSITIVE -> Identifier.CaseSensitivity.INSENSITIVE - } + private fun externalize(part: org.partiql.planner.catalog.Identifier.Part): Identifier.Symbol { + return identifierSymbol( + symbol = part.getText(), + caseSensitivity = when (part.isRegular()) { + true -> Identifier.CaseSensitivity.INSENSITIVE + false -> Identifier.CaseSensitivity.SENSITIVE + } + ) } } diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index aac995305..a6ce9a474 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -1,6 +1,7 @@ imports::{ kotlin: [ identifier::'org.partiql.planner.catalog.Identifier', + routine::'org.partiql.planner.catalog.Routine', partiql_value::'org.partiql.value.PartiQLValue', partiql_value_type::'org.partiql.planner.internal.typer.CompilerType', static_type::'org.partiql.planner.internal.typer.CompilerType', @@ -102,7 +103,7 @@ rex::{ }, static::{ - fn: ref, + fn: routine, args: list::[rex], }, @@ -116,7 +117,7 @@ rex::{ candidates: list::[candidate], _: [ candidate::{ - fn: ref, + fn: routine, coercions: list::[optional::'.ref.cast'], } ] @@ -334,7 +335,7 @@ rel::{ args: list::[rex], }, resolved::{ - agg: ref, + agg: routine, set_quantifier: set_quantifier, args: list::[rex], }, @@ -348,7 +349,7 @@ rel::{ paths: list::[path], _: [ path::{ - root: ref, + root: '.rex.op', steps: list::[step], }, step::{ From f7e6ba899dda7f2c67b9ed580aadde7e97600c0e Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 16 Jul 2024 07:32:35 -0700 Subject: [PATCH 14/15] Fix assemble --- .../org/partiql/planner/PartiQLPlanner.kt | 14 +++- .../org/partiql/planner/catalog/Identifier.kt | 4 +- .../org/partiql/planner/catalog/Name.kt | 62 +++++---------- .../org/partiql/planner/catalog/Namespace.kt | 76 +++++-------------- .../org/partiql/planner/catalog/Routine.kt | 6 +- .../org/partiql/planner/internal/Env.kt | 3 +- .../planner/internal/PartiQLPlannerDefault.kt | 6 +- .../planner/internal/fn/FnValidator.kt | 18 ----- .../org/partiql/planner/internal/ir/Nodes.kt | 17 ----- .../internal/transforms/PlanTransform.kt | 10 ++- .../planner/internal/typer/PlanTyper.kt | 15 ++-- 11 files changed, 72 insertions(+), 159 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt index 680aceee8..ed808ce63 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt @@ -4,6 +4,7 @@ import org.partiql.ast.Statement import org.partiql.errors.Problem import org.partiql.errors.ProblemCallback import org.partiql.plan.PartiQLPlan +import org.partiql.planner.catalog.Catalogs import org.partiql.planner.catalog.Session import org.partiql.planner.internal.PartiQLPlannerDefault import org.partiql.planner.internal.PlannerFlag @@ -36,7 +37,7 @@ public interface PartiQLPlanner { public companion object { @JvmStatic - public fun builder(): PartiQLPlannerBuilder = PartiQLPlannerBuilder() + public fun builder(): Builder = Builder() @JvmStatic public fun default(): PartiQLPlanner = Builder().build() @@ -45,13 +46,22 @@ public interface PartiQLPlanner { public class Builder { private val flags: MutableSet = mutableSetOf() + private var catalogs: Catalogs? = null /** * Build the builder, return an implementation of a [PartiQLPlanner]. * * @return */ - public fun build(): PartiQLPlanner = PartiQLPlannerDefault(flags) + public fun build(): PartiQLPlanner { + assert(catalogs != null) { "The `catalogs` field cannot be null, set with .catalgos(...)"} + return PartiQLPlannerDefault(catalogs!!, flags) + } + + /** + * Adds a catalog provider to this planner builder. + */ + public fun catalogs(catalogs: Catalogs): Builder = this.apply { this.catalogs = catalogs } /** * Java style method for setting the planner to signal mode diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt index 18f8a12c0..efbb8a998 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -230,10 +230,10 @@ public class Identifier private constructor( } @JvmStatic - public fun of(vararg parts: String): Identifier = of(parts.toList()) + public fun delimited(vararg parts: String): Identifier = delimited(parts.toList()) @JvmStatic - public fun of(parts: Collection): Identifier { + public fun delimited(parts: Collection): Identifier { if (parts.isEmpty()) { error("Cannot create an identifier with no parts") } 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 78521341b..2716301bc 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 @@ -5,15 +5,23 @@ package org.partiql.planner.catalog */ public class Name( private val namespace: Namespace, - private val name: Identifier, + private val name: String, ) { + /** + * Returns the unqualified name part. + */ + public fun getName(): String = name + + /** + * Returns the name's namespace. + */ public fun getNamespace(): Namespace = namespace + /** + * Returns true if the namespace is non-empty. + */ public fun hasNamespace(): Boolean = !namespace.isEmpty() - - public fun getName(): Identifier = name - /** * Compares two names including their namespaces and symbols. */ @@ -24,7 +32,8 @@ public class Name( if (other == null || javaClass != other.javaClass) { return false } - return matches(other as Name, ignoreCase = false) + other as Name + return (this.name == other.name) && (this.namespace == other.namespace) } /** @@ -38,30 +47,13 @@ public class Name( } /** - * Return the SQL name representation of this name. + * Return the SQL name representation of this name — all parts delimited. */ override fun toString(): String { - return if (namespace.isEmpty()) { - name.toString() - } else { - "$namespace.$name" - } - } - - /** - * Compares one name to another, possibly ignoring case. - * - * @param other The other name to match against. - * @param ignoreCase If false, the compare all levels case-sensitively (exact-case match). - * @return - */ - public fun matches(other: Name, ignoreCase: Boolean = false): Boolean { - if (ignoreCase && !(this.name.matches(other.name))) { - return false - } else if (name != other.name) { - return false - } - return this.namespace.matches(other.namespace, ignoreCase) + val parts = mutableListOf() + parts.addAll(namespace.getLevels()) + parts.add(name) + return Identifier.delimited(parts).toString() } public companion object { @@ -77,22 +69,6 @@ public class Name( */ @JvmStatic public fun of(names: Collection): Name { - assert(names.size > 1) { "Cannot create an empty name" } - val namespace = Namespace.of(names.drop(1)) - val name = Identifier.delimited(names.last()) - return Name(namespace, name) - } - - @JvmStatic - public fun of(name: Identifier): Name { - return Name( - namespace = Namespace.root(), - name = name - ) - } - - @JvmStatic - public fun of(names: Collection): Name { assert(names.size > 1) { "Cannot create an empty name" } val namespace = Namespace.of(names.drop(1)) val 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 f4e0cb56f..2ab7679c5 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 @@ -4,17 +4,17 @@ import java.util.Spliterator import java.util.function.Consumer /** - * A reference to a namespace within a catalog. + * A reference to a namespace within a catalog; case-preserved. * * Related * - Iceberg — https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/catalog/Namespace.java * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java */ public class Namespace private constructor( - private val levels: Array, -) : Iterable { + private val levels: Array, +) : Iterable { - public fun getLevels(): Array { + public fun getLevels(): Array { return levels } @@ -26,19 +26,19 @@ public class Namespace private constructor( return levels.isEmpty() } - public operator fun get(index: Int): Identifier { + public operator fun get(index: Int): String { return levels[index] } - override fun forEach(action: Consumer?) { + override fun forEach(action: Consumer?) { levels.toList().forEach(action) } - override fun iterator(): Iterator { + override fun iterator(): Iterator { return levels.iterator() } - override fun spliterator(): Spliterator { + override fun spliterator(): Spliterator { return levels.toList().spliterator() } @@ -49,55 +49,21 @@ public class Namespace private constructor( if (other == null || javaClass != other.javaClass) { return false } - return matches(other as Namespace, ignoreCase = false) + return levels.contentEquals((other as Namespace).levels) } /** * The hashCode() is case-sensitive — java.util.Arrays.hashCode */ public override fun hashCode(): Int { - var result = 1 - for (level in levels) { - result = 31 * result + level.hashCode() - } - return result + return levels.contentHashCode() } /** * Return the SQL identifier representation of this namespace. */ public override fun toString(): String { - return levels.joinToString(".") - } - - /** - * Compares one namespace to another, possibly ignoring case. - * - * Note that ignoring case pushes the case-sensitivity check down to the identifier matching. - * - * For example, - * "x" and "X" are NOT equal regardless of ignoreCase because they are delimited. - * x and X are NOT equal with ignoreCase=false but are matching identifiers. - * - * @param other The other namespace to match against. - * @param ignoreCase If false, then compare all levels case-sensitively (exact-case match). - * @return - */ - public fun matches(other: Namespace, ignoreCase: Boolean = false): Boolean { - val n = getLength() - if (n != other.getLength()) { - return false - } - for (i in 0 until n) { - val lhs = levels[i] - val rhs = other[i] - if (ignoreCase && !lhs.matches(rhs)) { - return false - } else if (lhs != rhs) { - return false - } - } - return true + return Identifier.delimited(*levels).toString() } public companion object { @@ -107,25 +73,19 @@ public class Namespace private constructor( public fun root(): Namespace = ROOT @JvmStatic - public fun of(vararg levels: String): Namespace = of(levels.toList()) - - @JvmStatic - public fun of(levels: Collection): Namespace { + public fun of(vararg levels: String): Namespace { if (levels.isEmpty()) { return root() } - return Namespace(levels.map { Identifier.delimited(it) }.toTypedArray() - ) - } - - @JvmStatic - public fun of(identifier: Identifier): Namespace { - return Namespace(arrayOf(identifier)) + return Namespace(arrayOf(*levels)) } @JvmStatic - public fun of(identifiers: Collection): Namespace { - return Namespace(identifiers.toTypedArray()) + public fun of(levels: Collection): Namespace { + if (levels.isEmpty()) { + return root() + } + return Namespace(levels.toTypedArray()) } } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Routine.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Routine.kt index 52b5d9731..28b8fc415 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Routine.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Routine.kt @@ -20,7 +20,7 @@ public sealed interface Routine { /** * The function return type. Required. */ - public fun getReturnType(): PType.Kind + public fun getReturnType(): PType /** * Represents an SQL row-value expression call. @@ -79,12 +79,12 @@ public sealed interface Routine { public fun scalar( name: String, parameters: Collection, - returnType: PType.Kind, + returnType: PType, properties: Properties = DEFAULT_PROPERTIES, ): Scalar = object : Scalar { override fun getName(): String = name override fun getParameters(): Array = parameters.toTypedArray() - override fun getReturnType(): PType.Kind = returnType + override fun getReturnType(): PType = returnType override fun getProperties(): Properties = properties } } 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 f63f06f98..24568e912 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -1,6 +1,7 @@ package org.partiql.planner.internal import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs import org.partiql.planner.catalog.Identifier import org.partiql.planner.catalog.Session import org.partiql.planner.internal.casts.CastTable @@ -19,7 +20,7 @@ import org.partiql.planner.internal.typer.CompilerType * @property session */ internal class Env( - private val catalog: Catalog, + private val catalogs: Catalogs, private val session: Session, ) { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt index c9064a8ea..915d1b57d 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/PartiQLPlannerDefault.kt @@ -4,7 +4,7 @@ import org.partiql.ast.Statement import org.partiql.ast.normalize.normalize import org.partiql.errors.ProblemCallback import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs import org.partiql.planner.catalog.Session import org.partiql.planner.internal.transforms.AstToPlan import org.partiql.planner.internal.transforms.PlanTransform @@ -14,7 +14,7 @@ import org.partiql.planner.internal.typer.PlanTyper * Default PartiQL logical query planner. */ internal class PartiQLPlannerDefault( - private val catalog: Catalog, + private val catalogs: Catalogs, private val flags: Set ) : PartiQLPlanner { @@ -25,7 +25,7 @@ internal class PartiQLPlannerDefault( ): PartiQLPlanner.Result { // 0. Initialize the planning environment - val env = Env(catalog, session) + val env = Env(catalogs, session) // 1. Normalize val ast = statement.normalize() diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt deleted file mode 100644 index 49dd64eb1..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/fn/FnValidator.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.partiql.planner.internal.fn - -import org.partiql.planner.catalog.Routine -import org.partiql.types.PType - -/** - * Class for function validation to compute return types. - */ -internal object FnValidator { - - /** - * This computes a PType for a given function. - */ - @JvmStatic - fun validate(routine: Routine, args: List): PType { - return PType.typeDynamic() - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 573cbeca0..2a1797d62 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -1418,23 +1418,6 @@ internal data class Rel( internal fun builder(): RelOpExcludeTypeStructSymbolBuilder = RelOpExcludeTypeStructSymbolBuilder() } - - // Explicitly override `equals` and `hashcode` for case-insensitivity - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as StructSymbol - - if (!symbol.equals(other.symbol, ignoreCase = true)) return false - if (children != other.children) return false - - return true - } - - override fun hashCode(): Int { - return symbol.lowercase().hashCode() - } } internal data class StructKey( diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt index cf7a74b66..8472cc61a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt @@ -12,6 +12,7 @@ import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.ir.Rel import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.Statement +import org.partiql.planner.internal.ir.ref import org.partiql.planner.internal.ir.visitor.PlanBaseVisitor import org.partiql.value.PartiQLValueExperimental @@ -143,7 +144,8 @@ internal class PlanTransform( } override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: Unit): org.partiql.plan.Rex.Op { - val fn = visitRef(node.fn, ctx) + val ref = ref(node.fn.getName()) + val fn = visitRef(ref, ctx) val args = node.args.map { visitRex(it, ctx) } return org.partiql.plan.rexOpCallStatic(fn, args) } @@ -161,7 +163,8 @@ internal class PlanTransform( } override fun visitRexOpCallDynamicCandidate(node: Rex.Op.Call.Dynamic.Candidate, ctx: Unit): PlanNode { - val fn = visitRef(node.fn, ctx) + val ref = ref(node.fn.getName()) + val fn = visitRef(ref, ctx) val coercions = node.coercions.map { it?.let { visitRefCast(it, ctx) } } return org.partiql.plan.Rex.Op.Call.Dynamic.Candidate(fn, coercions) } @@ -364,7 +367,8 @@ internal class PlanTransform( } override fun visitRelOpAggregateCallResolved(node: Rel.Op.Aggregate.Call.Resolved, ctx: Unit): PlanNode { - val agg = visitRef(node.agg, ctx) + val ref = ref(node.agg.getName()) + val agg = visitRef(ref, ctx) val args = node.args.map { visitRex(it, ctx) } val setQuantifier = when (node.setQuantifier) { Rel.Op.Aggregate.SetQuantifier.ALL -> org.partiql.plan.Rel.Op.Aggregate.Call.SetQuantifier.ALL diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 1405eaae3..f01bfda27 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -16,7 +16,6 @@ package org.partiql.planner.internal.typer -import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.Env import org.partiql.planner.internal.ProblemGenerator import org.partiql.planner.internal.exclude.ExcludeRepr @@ -796,17 +795,17 @@ internal class PlanTyper(private val env: Env) { } // Check if any arg is always missing - val argIsAlwaysMissing = args.any { it.type.isMissingValue } - if (node.fn.signature.isMissingCall && argIsAlwaysMissing) { + val hasMissingArg = args.any { it.type.isMissingValue } + if (hasMissingArg) { return ProblemGenerator.missingRex( node, ProblemGenerator.expressionAlwaysReturnsMissing("Static function always receives MISSING arguments."), - CompilerType(node.fn.signature.returns, isMissingValue = true) + CompilerType(node.fn.getReturnType(), isMissingValue = true) ) } // Infer fn return type - return rex(CompilerType(node.fn.signature.returns), Rex.Op.Call.Static(node.fn, args)) + return rex(CompilerType(node.fn.getReturnType()), Rex.Op.Call.Static(node.fn, args)) } /** @@ -817,9 +816,7 @@ internal class PlanTyper(private val env: Env) { * @return */ override fun visitRexOpCallDynamic(node: Rex.Op.Call.Dynamic, ctx: CompilerType?): Rex { - val types = node.candidates.map { candidate -> - val kind = candidate.fn.getReturnType() - }.toMutableSet() + val types = node.candidates.map { candidate -> candidate.fn.getReturnType().toCType() }.toMutableSet() // TODO: Should this always be DYNAMIC? return Rex(type = CompilerType.anyOf(types), op = node) } @@ -1159,7 +1156,7 @@ internal class PlanTyper(private val env: Env) { if (firstBranchCondition !is Rex.Op.Call.Static) { return null } - if (!firstBranchCondition.fn.signature.name.equals("is_struct", ignoreCase = true)) { + if (!firstBranchCondition.fn.getName().equals("is_struct", ignoreCase = true)) { return null } val firstBranchResultType = firstBranch.rex.type From ad93e3c18066f09d63a6bd6ddc1cad6ce52f3d3d Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 16 Jul 2024 13:28:16 -0700 Subject: [PATCH 15/15] Fixes assemble for partiql-planner --- partiql-planner/build.gradle.kts | 4 - .../org/partiql/planner/PartiQLPlanner.kt | 18 +- .../org/partiql/planner/catalog/Catalog.kt | 97 +++++++- .../org/partiql/planner/catalog/Catalogs.kt | 79 ++++++ .../org/partiql/planner/catalog/Identifier.kt | 2 +- .../org/partiql/planner/catalog/Session.kt | 46 ++-- .../org/partiql/planner/catalog/Table.kt | 52 +++- .../org/partiql/planner/internal/Env.kt | 1 - .../internal/transforms/RexConverter.kt | 1 - .../org/partiql/planner/modes/CasingMode.kt | 11 - .../kotlin/org/partiql/planner/PlanTest.kt | 62 ++--- .../planner/PlannerErrorReportingTests.kt | 47 ++-- .../internal/exclude/SubsumptionTest.kt | 26 +- .../planner/internal/typer/FnResolverTest.kt | 27 +- .../internal/typer/PartiQLTyperTestBase.kt | 78 +++--- .../planner/internal/typer/PlanTyperTest.kt | 38 +-- .../internal/typer/PlanTyperTestsPorted.kt | 150 +++++------ .../planner/internal/typer/TypeEnvTest.kt | 12 +- .../planner/plugins/local/LocalCatalog.kt | 76 ++++++ .../planner/plugins/local/LocalSchema.kt | 235 ++++++++++++++++++ .../planner/plugins/local/LocalTable.kt | 46 ++++ .../planner/test/PartiQLTestProvider.kt | 6 - .../partiql/plugins/memory/MemoryConnector.kt | 10 +- .../org/partiql/plugins/memory/MemoryTable.kt | 12 +- 24 files changed, 823 insertions(+), 313 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt create mode 100644 partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalCatalog.kt create mode 100644 partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalSchema.kt create mode 100644 partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalTable.kt diff --git a/partiql-planner/build.gradle.kts b/partiql-planner/build.gradle.kts index d7b2a5ffe..54268a47c 100644 --- a/partiql-planner/build.gradle.kts +++ b/partiql-planner/build.gradle.kts @@ -31,10 +31,6 @@ dependencies { implementation(Deps.ionElement) // Test testImplementation(project(":partiql-parser")) - testImplementation(project(":plugins:partiql-local")) - testImplementation(project(":plugins:partiql-memory")) - // Test Fixtures - testFixturesImplementation(project(":partiql-spi")) } tasks.register("generateResourcePath") { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt index ed808ce63..5944921eb 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/PartiQLPlanner.kt @@ -38,9 +38,6 @@ public interface PartiQLPlanner { @JvmStatic public fun builder(): Builder = Builder() - - @JvmStatic - public fun default(): PartiQLPlanner = Builder().build() } public class Builder { @@ -54,20 +51,27 @@ public interface PartiQLPlanner { * @return */ public fun build(): PartiQLPlanner { - assert(catalogs != null) { "The `catalogs` field cannot be null, set with .catalgos(...)"} + assert(catalogs != null) { "The `catalogs` field cannot be null, set with .catalogs(...)" } return PartiQLPlannerDefault(catalogs!!, flags) } /** * Adds a catalog provider to this planner builder. */ - public fun catalogs(catalogs: Catalogs): Builder = this.apply { this.catalogs = catalogs } + public fun catalogs(catalogs: Catalogs): Builder { + this.catalogs = catalogs + return this + } /** * Java style method for setting the planner to signal mode */ - public fun signalMode(): Builder = this.apply { - this.flags.add(PlannerFlag.SIGNAL_MODE) + public fun signal(signal: Boolean = true): Builder = this.apply { + if (signal) { + this.flags.add(PlannerFlag.SIGNAL_MODE) + } else { + this.flags.remove(PlannerFlag.SIGNAL_MODE) + } } } } 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 71c5eafe7..716110a4c 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 @@ -1,5 +1,7 @@ package org.partiql.planner.catalog +import org.partiql.types.PType + /** * Catalog interface for access to tables and routines. * @@ -29,8 +31,6 @@ public interface Catalog { /** * List all tables under this namespace. - * - * @param namespace */ public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() @@ -53,4 +53,97 @@ public interface Catalog { * @return A collection of all [Routine]s in the current namespace with this name. */ public fun getRoutines(session: Session, name: Name): Collection = emptyList() + + /** + * Factory methods and builder. + */ + public companion object { + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Catalog] implementation. + * + */ + public class Builder { + + private var name: String? = null + private var tables = mutableMapOf() + + public fun name(name: String): Builder { + this.name = name + return this + } + + public fun createTable(name: String, schema: PType): Builder { + this.tables[name] = Table.of(name, schema) + return this + } + + public fun createTable(name: Name, schema: PType): Builder { + if (name.hasNamespace()) { + error("Table name must not have a namespace: $name") + } + this.tables[name.getName()] = Table.of(name.getName(), schema) + return this + } + + public fun build(): Catalog { + + val name = this.name ?: throw IllegalArgumentException("Catalog name must be provided") + + return object : Catalog { + + override fun getName(): String = name + + override fun getTable(session: Session, name: Name): Table? { + if (name.hasNamespace()) { + return null + } + return tables[name.getName()] + } + + override fun getTable(session: Session, identifier: Identifier): Table? { + if (identifier.hasQualifier()) { + return null + } + var match: Table? = null + val id = identifier.getIdentifier() + for (table in tables.values) { + if (id.matches(table.getName())) { + if (match == null) { + match = table + } else { + error("Ambiguous table name: $name") + } + } + } + return match + } + + override fun listTables(session: Session): Collection { + return tables.values.map { Name.of(it.getName()) } + } + + override fun listTables(session: Session, namespace: Namespace): Collection { + if (!namespace.isEmpty()) { + return emptyList() + } + return tables.values.map { Name.of(it.getName()) } + } + + override fun listNamespaces(session: Session): Collection { + return emptyList() + } + + override fun listNamespaces(session: Session, namespace: Namespace): Collection { + return emptyList() + } + + override fun getRoutines(session: Session, name: Name): Collection = emptyList() + } + } + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt index 369851707..303f6e833 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt @@ -26,4 +26,83 @@ public interface Catalogs { * Returns a list of all available catalogs. */ public fun list(): Collection = listOf(default()) + + /** + * Factory methods and builder. + */ + public companion object { + + @JvmStatic + public fun of(vararg catalogs: Catalog): Catalogs = of(catalogs.toList()) + + @JvmStatic + public fun of(catalogs: Collection): Catalogs { + if (catalogs.isEmpty()) { + error("Cannot create `Catalogs` with empty catalogs list.") + } + return builder().apply { catalogs.forEach { add(it) } }.build() + } + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Catalogs] implementation. + */ + public class Builder { + + private var default: Catalog? = null + private val catalogs = mutableMapOf() + + /** + * Sets the default catalog. + */ + public fun default(default: Catalog): Builder = this.apply { + this.default = default + catalogs[default.getName()] = default + } + + /** + * Adds this catalog, overwriting any existing one with the same name. + */ + public fun add(catalog: Catalog): Builder = this.apply { + if (default == null) { + this.default = catalog + } + catalogs[catalog.getName()] = catalog + } + + public fun build(): Catalogs { + + val default = default ?: error("Default catalog is required") + + return object : Catalogs { + + override fun default(): Catalog = default + + override fun get(name: String, ignoreCase: Boolean): Catalog? { + if (ignoreCase) { + // search + var match: Catalog? = null + for (catalog in list()) { + if (catalog.getName().equals(name, ignoreCase = true)) { + if (match != null) { + // TODO exceptions for ambiguous catalog name lookup + error("Catalog name is ambiguous, found more than one match.") + } else { + match = catalog + } + } + } + return match + } + // lookup + return catalogs[name] + } + + override fun list(): Collection = catalogs.values + } + } + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt index efbb8a998..b46a248f9 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -153,7 +153,7 @@ public class Identifier private constructor( */ public fun isRegular(): Boolean = regular - /** + /** * Compares this identifier part to a string. */ public fun matches(other: String): Boolean { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt index bc79f2ef8..0f6344863 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt @@ -5,20 +5,6 @@ package org.partiql.planner.catalog */ public interface Session { - public companion object { - - private val EMPTY = object : Session { - override fun getIdentity(): String = "unknown" - override fun getNamespace(): Namespace = Namespace.root() - } - - @JvmStatic - public fun empty(): Session = EMPTY - - @JvmStatic - public fun builder(): Builder = Builder() - } - /** * Returns the caller identity as a string; accessible via CURRENT_USER. */ @@ -42,7 +28,22 @@ public interface Session { public fun getProperties(): Map = emptyMap() /** - * Java-style session builder. + * Factory methods and builder. + */ + public companion object { + + @JvmStatic + public fun empty(): Session = object : Session { + override fun getIdentity(): String = "unknown" + override fun getNamespace(): Namespace = Namespace.root() + } + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Session] implementation. */ public class Builder { @@ -50,11 +51,20 @@ public interface Session { private var namespace: Namespace = Namespace.root() private var properties: MutableMap = mutableMapOf() - public fun identity(identity: String): Builder = this.apply { this.identity = identity } + public fun identity(identity: String): Builder { + this.identity = identity + return this + } - public fun namespace(namespace: Namespace): Builder = this.apply { this.namespace = namespace } + public fun namespace(namespace: Namespace): Builder { + this.namespace = namespace + return this + } - public fun property(name: String, value: String): Builder = this.apply { this.properties[name] = value } + public fun property(name: String, value: String): Builder { + this.properties[name] = value + return this + } public fun build(): Session = object : Session { override fun getIdentity(): String = identity diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt index 909fde62f..3a21daa5b 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt @@ -10,10 +10,60 @@ public interface Table { /** * The table's name. */ - public fun getName(): Name + public fun getName(): String /** * The table's schema. */ public fun getSchema(): PType = PType.typeDynamic() + + /** + * Factory methods and builder. + */ + public companion object { + + /** + * Create a simple table with a name and schema. + */ + @JvmStatic + public fun of(name: String, schema: PType = PType.typeDynamic()): Table = object : Table { + override fun getName(): String = name + override fun getSchema(): PType = schema + } + + /** + * Returns the Java-style builder. + */ + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default Table implementation. + */ + public class Builder { + + private var name: String? = null + private var schema: PType = PType.typeDynamic() + + public fun name(name: String): Builder { + this.name = name + return this + } + + public fun schema(schema: PType): Builder { + this.schema = schema + return this + } + + public fun build(): Table { + // Validate builder parameters + val name = this.name ?: throw IllegalStateException("Table name cannot be null") + // Default implementation + return object : Table { + override fun getName(): String = name + override fun getSchema(): PType = schema + } + } + } } 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 24568e912..0fd0a357d 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -1,6 +1,5 @@ package org.partiql.planner.internal -import org.partiql.planner.catalog.Catalog import org.partiql.planner.catalog.Catalogs import org.partiql.planner.catalog.Identifier import org.partiql.planner.catalog.Session diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index ae05abb59..f7b81d9f5 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -219,7 +219,6 @@ internal object RexConverter { val (newRoot, newSteps) = when (val op = root.op) { is Rex.Op.Var.Unresolved -> { - // convert consecutive symbol path steps to the root identifier var i = 0 val parts = mutableListOf() diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt deleted file mode 100644 index f52d3d490..000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/modes/CasingMode.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.partiql.planner.modes - -/** - * Identifier lookup case handling. - */ -public enum class CasingMode { - NORMALIZE_LOWER, - NORMALIZE_UPPER, - INSENSITIVE, - SENSITIVE, -} 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 7c8d0bdc8..ae39dfb4d 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlanTest.kt @@ -9,25 +9,21 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.PartiQLPlan import org.partiql.plan.PlanNode import org.partiql.plan.debug.PlanPrinter +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.PlanNodeEquivalentVisitor import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.BagType +import org.partiql.types.PType import org.partiql.types.StaticType import org.partiql.types.StructType import org.partiql.types.TupleConstraint import java.io.File import java.nio.file.Path -import java.time.Instant import java.util.stream.Stream import kotlin.io.path.toPath @@ -40,9 +36,9 @@ import kotlin.io.path.toPath // the produced plan will be identical as the normalized query: // `SELECT "T"['a'] AS "a", "T"['b'] AS "b", "T"['c'] AS "c" FROM "default"."T" AS "T";` class PlanTest { - val root: Path = this::class.java.getResource("/outputs")!!.toURI().toPath() - val input = PartiQLTestProvider().apply { load() } + private val root: Path = this::class.java.getResource("/outputs")!!.toURI().toPath() + private val input = PartiQLTestProvider().apply { load() } val type = BagType( StructType( @@ -71,41 +67,25 @@ class PlanTest { ) ) - val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - - val pipeline: (PartiQLTest, Boolean) -> PartiQLPlanner.Result = { test, isSignalMode -> - val session = PartiQLPlanner.Session( - queryId = test.key.toString(), - userId = "user_id", - currentCatalog = "default", - currentDirectory = listOf("SCHEMA"), - catalogs = mapOf("default" to buildMetadata("default")), - instant = Instant.now() - ) + private val pipeline: (PartiQLTest, Boolean) -> PartiQLPlanner.Result = { test, isSignalMode -> + val session = Session.builder() + .identity("user_id") + .namespace(Namespace.of("default", "SCHEMA")) + .build() val problemCollector = ProblemCollector() val ast = PartiQLParser.default().parse(test.statement).root - val planner = when (isSignalMode) { - true -> PartiQLPlanner.builder().signalMode().build() - else -> PartiQLPlanner.builder().build() - } + val planner = PartiQLPlanner.builder() + .catalogs(Catalogs.of(buildCatalog("default"))) + .signal(isSignalMode) + .build() planner.plan(ast, session, problemCollector) } - fun buildMetadata(catalogName: String): ConnectorMetadata { - val catalog = MemoryCatalog.PartiQL().name(catalogName).build() - // Insert binding - val name = BindingPath( - listOf( - BindingName("SCHEMA", BindingCase.INSENSITIVE), - BindingName("T", BindingCase.INSENSITIVE), - ) - ) - val obj = MemoryObject(type) - catalog.insert(name, obj) - return MemoryConnector(catalog).getMetadata(connectorSession) + private fun buildCatalog(catalogName: String): Catalog { + return Catalog.builder() + .name(catalogName) + .createTable("T", PType.fromStaticType(type)) + .build() } @TestFactory diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index 4d8f33a87..a35a60288 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -7,12 +7,13 @@ import org.partiql.errors.Problem import org.partiql.errors.ProblemSeverity import org.partiql.parser.PartiQLParserBuilder import org.partiql.plan.debug.PlanPrinter +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.typer.CompilerType import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.spi.connector.ConnectorSession +import org.partiql.types.Field import org.partiql.types.BagType import org.partiql.types.PType import org.partiql.types.StaticType @@ -21,39 +22,25 @@ import org.partiql.types.TupleConstraint import kotlin.test.assertEquals internal class PlannerErrorReportingTests { + val catalogName = "mode_test" val userId = "test-user" val queryId = "query" - val catalog = MemoryCatalog - .PartiQL() + val catalog = Catalog + .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( - "struct_with_missing", - closedStruct( - StructType.Field("f1", StaticType.INT2), - ) - ) + .createTable("missing_binding", PType.typeDynamic()) + .createTable("atomic", PType.typeSmallInt()) + .createTable("collection_no_missing_atomic", PType.typeBag(PType.typeSmallInt())) + .createTable("collection_contain_missing_atomic",PType.typeBag(PType.typeSmallInt())) + .createTable("struct_no_missing", PType.typeRow(listOf(Field.of("f1", PType.typeSmallInt())))) + .createTable("struct_with_missing", PType.typeRow(listOf(Field.of("f1", PType.typeSmallInt())))) .build() - val metadata = MemoryConnector(catalog).getMetadata( - object : ConnectorSession { - override fun getQueryId(): String = "q" - override fun getUserId(): String = "s" - } - ) - - val session = PartiQLPlanner.Session( - queryId = queryId, - userId = userId, - currentCatalog = catalogName, - catalogs = mapOf(catalogName to metadata), - ) + val session = Session.builder() + .namespace(Namespace.of(catalogName)) + .build() val parser = PartiQLParserBuilder().build() @@ -398,7 +385,7 @@ internal class PlannerErrorReportingTests { private fun runTestCase(tc: TestCase) { val planner = when (tc.isSignal) { - true -> PartiQLPlanner.builder().signalMode().build() + true -> PartiQLPlanner.builder().signal().build() else -> PartiQLPlanner.builder().build() } val pc = ProblemCollector() 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 9b0519f7d..54b1c6703 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,8 +20,10 @@ 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.spi.connector.ConnectorSession +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import java.util.stream.Stream import kotlin.test.assertEquals @@ -29,13 +31,12 @@ class SubsumptionTest { companion object { - private val planner = PartiQLPlanner.default() private val parser = PartiQLParser.default() - private val session = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - private val connector = MemoryConnector.partiQL() + private val catalog = Catalog.builder().name("default").build() + private val planner = PartiQLPlanner + .builder() + .catalogs(Catalogs.of(catalog)) + .build() } private fun getExcludeClause(statement: Statement): Rel.Op.Exclude { @@ -47,12 +48,9 @@ class SubsumptionTest { private fun testExcludeExprSubsumption(tc: SubsumptionTC) { val text = "SELECT * EXCLUDE ${tc.excludeExprStr} FROM <<>> AS s, <<>> AS t;" val statement = parser.parse(text).root - val session = PartiQLPlanner.Session( - queryId = "query-id", userId = "user-id", currentCatalog = "default", - catalogs = mapOf( - "default" to connector.getMetadata(session), - ) - ) + val session = Session.builder() + .namespace(Namespace.of("default")) + .build() val plan = planner.plan(statement, session).plan val excludeClause = getExcludeClause(plan.statement).paths assertEquals(tc.expectedExcludeExprs, excludeClause) diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/FnResolverTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/FnResolverTest.kt index e27142930..5c3c9cf0c 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/FnResolverTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/FnResolverTest.kt @@ -2,35 +2,30 @@ package org.partiql.planner.internal.typer import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail +import org.partiql.planner.catalog.Routine import org.partiql.planner.internal.FnMatch import org.partiql.planner.internal.FnResolver import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType -import org.partiql.spi.fn.FnExperimental -import org.partiql.spi.fn.FnParameter -import org.partiql.spi.fn.FnSignature import org.partiql.types.PType -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType /** * As far as testing is concerned, we can stub out all value related things. * We may be able to pretty-print with string equals to also simplify things. * Only the "types" of expressions matter, we ignore the underlying ops. */ -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) class FnResolverTest { @Test fun sanity() { // 1 + 1.0 -> 2.0 val variants = listOf( - FnSignature( + Routine.scalar( name = "plus", - returns = PartiQLValueType.FLOAT64, parameters = listOf( - FnParameter("arg-0", PartiQLValueType.FLOAT64), - FnParameter("arg-1", PartiQLValueType.FLOAT64), + Routine.Parameter("arg-0", PType.Kind.DOUBLE_PRECISION), + Routine.Parameter("arg-1", PType.Kind.DOUBLE_PRECISION), ), + returnType = PType.typeDoublePrecision(), ) ) val args = listOf(PType.typeInt().toCType(), PType.typeDoublePrecision().toCType()) @@ -42,14 +37,14 @@ class FnResolverTest { @Test fun split() { val variants = listOf( - FnSignature( + Routine.scalar( name = "split", - returns = PartiQLValueType.LIST, parameters = listOf( - FnParameter("value", PartiQLValueType.STRING), - FnParameter("delimiter", PartiQLValueType.STRING), + Routine.Parameter("value", PType.Kind.STRING), + Routine.Parameter("delimiter", PType.Kind.STRING), ), - isNullable = false, + returnType = PType.typeList(), + // isNullable = false, ) ) val args = listOf(PType.typeString().toCType(), PType.typeString().toCType()) @@ -63,7 +58,7 @@ class FnResolverTest { abstract fun assert() class Success( - private val variants: List, + private val variants: List, private val inputs: List, private val expectedImplicitCast: List, ) : Case() { 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 d8cff4fc0..3e19deb75 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt @@ -7,23 +7,17 @@ import org.partiql.parser.PartiQLParser import org.partiql.plan.Statement import org.partiql.plan.debug.PlanPrinter import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.PlanningProblemDetails import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.PType import org.partiql.types.PType.Kind import org.partiql.types.StaticType -import org.partiql.value.PartiQLValueExperimental -import java.util.Random import java.util.stream.Stream abstract class PartiQLTyperTestBase { @@ -40,50 +34,36 @@ abstract class PartiQLTyperTestBase { } } - companion object { - - public val parser = PartiQLParser.default() - public val planner = PartiQLPlanner.default() - - internal val session: ((String, ConnectorMetadata) -> PartiQLPlanner.Session) = { catalog, metadata -> - PartiQLPlanner.Session( - queryId = Random().nextInt().toString(), - userId = "test-user", - currentCatalog = catalog, - catalogs = mapOf( - catalog to metadata - ), - ) - } - - internal val connectorSession = object : ConnectorSession { - override fun getQueryId(): String = "test" - override fun getUserId(): String = "test" - } - } - val inputs = PartiQLTestProvider().apply { load() } - val testingPipeline: ((String, String, ConnectorMetadata, ProblemCallback) -> PartiQLPlanner.Result) = { query, catalog, metadata, collector -> + val testingPipeline: ((String, Catalog, ProblemCallback) -> PartiQLPlanner.Result) = { query, catalog, collector -> + val parser = PartiQLParser.default() val ast = parser.parse(query).root - planner.plan(ast, session(catalog, metadata), collector) + val planner = PartiQLPlanner.builder() + .catalogs(Catalogs.of(catalog)) + .build() + val session = Session.builder() + .namespace(Namespace.of(catalog.getName())) + .build() + planner.plan(ast, session, collector) } /** * Build a ConnectorMetadata instance from the list of types. */ - @OptIn(PartiQLValueExperimental::class) - private fun buildMetadata(catalog: String, types: List): ConnectorMetadata { - val cat = MemoryCatalog.PartiQL().name(catalog).build() - val connector = MemoryConnector(cat) - - // define all bindings - types.forEachIndexed { i, t -> - val binding = BindingPath(listOf(BindingName("t${i + 1}", BindingCase.SENSITIVE))) - val obj = MemoryObject(t) - cat.insert(binding, obj) - } - return connector.getMetadata(connectorSession) + private fun buildCatalog(catalog: String, types: List): Catalog { + return Catalog.builder() + .name(catalog) + .apply { + // define all bindings + types.forEachIndexed { i, t -> + createTable( + name = "t${i + 1}", + schema = PType.fromStaticType(t) + ) + } + } + .build() } fun testGen( @@ -97,14 +77,14 @@ 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 catalog = buildCatalog(testName, types) val displayName = "$group | $testName | $types" val statement = test.statement // Assert DynamicTest.dynamicTest(displayName) { val pc = ProblemCollector() if (key is TestResult.Success) { - val result = testingPipeline(statement, testName, metadata, pc) + val result = testingPipeline(statement, catalog, pc) val root = (result.plan.statement as Statement.Query).root val actualType = root.type assert(actualType == key.expectedType) { @@ -129,7 +109,7 @@ abstract class PartiQLTyperTestBase { } } } else { - val result = testingPipeline(statement, testName, metadata, pc) + val result = testingPipeline(statement, catalog, pc) val root = (result.plan.statement as Statement.Query).root val actualType = root.type assert(actualType.kind == Kind.DYNAMIC) { 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 2fe976d56..3ea837c13 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTest.kt @@ -2,13 +2,14 @@ package org.partiql.planner.internal.typer import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Identifier +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.Env -import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.Statement -import org.partiql.planner.internal.ir.identifierSymbol -import org.partiql.planner.internal.ir.refObj +import org.partiql.planner.internal.ir.ref import org.partiql.planner.internal.ir.rex import org.partiql.planner.internal.ir.rexOpLit import org.partiql.planner.internal.ir.rexOpPathKey @@ -19,13 +20,12 @@ import org.partiql.planner.internal.ir.rexOpVarGlobal 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.plugins.local.LocalCatalog import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.local.LocalConnector import org.partiql.types.PType import org.partiql.value.PartiQLValueExperimental import org.partiql.value.int32Value import org.partiql.value.stringValue -import java.util.Random import kotlin.io.path.toPath class PlanTyperTest { @@ -118,17 +118,12 @@ class PlanTyperTest { private fun getTyper(): PlanTyperWrapper { ProblemCollector() - val env = Env( - PartiQLPlanner.Session( - queryId = Random().nextInt().toString(), - userId = "test-user", - currentCatalog = "pql", - currentDirectory = listOf("main"), - catalogs = mapOf( - "pql" to LocalConnector.Metadata(root) - ), - ) - ) + val catalog = LocalCatalog("pql", root) + val catalogs = Catalogs.of(catalog) + val session = Session.builder() + .namespace(Namespace.of("pql", "main")) + .build() + val env = Env(catalogs, session) return PlanTyperWrapper(PlanTyper(env)) } } @@ -317,16 +312,21 @@ class PlanTyperTest { return rex( type, rexOpVarUnresolved( - identifierSymbol(name, Identifier.CaseSensitivity.SENSITIVE), + Identifier.delimited(name), Rex.Op.Var.Scope.DEFAULT ) ) } private fun global(type: CompilerType, path: List): Rex { + // return rex( + // type, + // rexOpVarGlobal(refObj(catalog = "pql", path = path, type)) + // ) + // TODO!! return rex( type, - rexOpVarGlobal(refObj(catalog = "pql", path = path, type)) + rexOpVarGlobal(ref("pql.${path.joinToString(".")}")) ) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt index 481a819ca..86f666283 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt @@ -19,39 +19,32 @@ import org.partiql.plan.PartiQLPlan import org.partiql.plan.Statement import org.partiql.plan.debug.PlanPrinter import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Catalogs +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Namespace +import org.partiql.planner.catalog.Session import org.partiql.planner.internal.ProblemGenerator import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.ErrorTestCase import org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.SuccessTestCase import org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.ThrowingExceptionTestCase +import org.partiql.planner.plugins.local.toStaticType import org.partiql.planner.test.PartiQLTest import org.partiql.planner.test.PartiQLTestProvider import org.partiql.planner.util.ProblemCollector -import org.partiql.plugins.local.toStaticType -import org.partiql.plugins.memory.MemoryCatalog -import org.partiql.plugins.memory.MemoryConnector -import org.partiql.plugins.memory.MemoryObject -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath -import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.BagType import org.partiql.types.ListType import org.partiql.types.PType import org.partiql.types.SexpType import org.partiql.types.StaticType import org.partiql.types.StaticType.Companion.ANY -import org.partiql.types.StaticType.Companion.DECIMAL -import org.partiql.types.StaticType.Companion.INT import org.partiql.types.StaticType.Companion.INT4 import org.partiql.types.StaticType.Companion.INT8 -import org.partiql.types.StaticType.Companion.STRING import org.partiql.types.StaticType.Companion.STRUCT import org.partiql.types.StaticType.Companion.unionOf import org.partiql.types.StructType import org.partiql.types.TupleConstraint -import org.partiql.value.PartiQLValueExperimental import java.util.stream.Stream import kotlin.reflect.KClass import kotlin.test.assertEquals @@ -108,7 +101,16 @@ internal class PlanTyperTestsPorted { note: String? = null, expected: StaticType? = null, problemHandler: ProblemHandler? = null, - ) : this(name, key, query, catalog, catalogPath, note, expected?.let { PType.fromStaticType(it).toCType() }, problemHandler) + ) : this( + name, + key, + query, + catalog, + catalogPath, + note, + expected?.let { PType.fromStaticType(it).toCType() }, + problemHandler + ) override fun toString(): String = "$name : ${query ?: key}" } @@ -130,7 +132,10 @@ internal class PlanTyperTestsPorted { companion object { private val parser = PartiQLParser.default() - private val planner = PartiQLPlanner.builder().signalMode().build() + private val planner = PartiQLPlanner.builder() + .catalogs(loadCatalogs()) + .signal() + .build() private fun assertProblemExists(problem: Problem) = ProblemHandler { problems, ignoreSourceLocation -> val message = buildString { @@ -148,11 +153,6 @@ internal class PlanTyperTestsPorted { } } - val session = object : ConnectorSession { - override fun getQueryId(): String = "query-id" - override fun getUserId(): String = "user-id" - } - private fun id(vararg parts: Identifier.Symbol): Identifier { return when (parts.size) { 0 -> error("Identifier requires more than one part.") @@ -170,10 +170,10 @@ internal class PlanTyperTestsPorted { /** * MemoryConnector.Factory from reading the resources in /resource_path.txt for Github CI/CD. */ - @OptIn(PartiQLValueExperimental::class) - val catalogs: List> by lazy { + private fun loadCatalogs(): Catalogs { + // Make a map from catalog name to tables. val inputStream = this::class.java.getResourceAsStream("/resource_path.txt")!! - val map = mutableMapOf>>() + val map = mutableMapOf>>() inputStream.reader().readLines().forEach { path -> if (path.startsWith("catalogs/default")) { val schema = this::class.java.getResourceAsStream("/$path")!! @@ -181,31 +181,33 @@ internal class PlanTyperTestsPorted { val staticType = ion.toStaticType() val steps = path.substring(0, path.length - 4).split('/').drop(2) // drop the catalogs/default val catalogName = steps.first() - val bindingSteps = steps - .drop(1) - .map { BindingName(it, BindingCase.INSENSITIVE) } - val bindingPath = BindingPath(bindingSteps) + // args + val name = Name.of(steps.drop(1)) + val ptype = PType.fromStaticType(staticType) if (map.containsKey(catalogName)) { - map[catalogName]!!.add(bindingPath to staticType) + map[catalogName]!!.add(name to ptype) } else { - map[catalogName] = mutableListOf(bindingPath to staticType) + map[catalogName] = mutableListOf(name to ptype) } } } - map.entries.map { (catalogName, bindings) -> - val catalog = MemoryCatalog.PartiQL().name(catalogName).build() - val connector = MemoryConnector(catalog) - for (binding in bindings) { - val path = binding.first - val obj = MemoryObject(binding.second) - catalog.insert(path, obj) - } - catalogName to connector.getMetadata(session) + // Make a catalogs map + val catalogs = Catalogs.builder() + for ((catalogName, tables) in map) { + val catalog = Catalog.builder() + .name(catalogName) + .apply { + for ((name, schema) in tables) { + createTable(name, schema) + } + } + .build() + catalogs.add(catalog) } + // finalize + return catalogs.build() } - private const val USER_ID = "TEST_USER" - private fun key(name: String) = PartiQLTest.Key("schema_inferencer", name) // @@ -291,14 +293,6 @@ internal class PlanTyperTestsPorted { ) ) - private fun insensitiveId(symbol: String) = BindingPath(listOf(BindingName(symbol, BindingCase.INSENSITIVE))) - - private fun sensitiveId(symbol: String) = BindingPath(listOf(BindingName(symbol, BindingCase.SENSITIVE))) - - private fun idQualified(vararg symbol: Pair) = symbol.map { - BindingName(it.first, it.second) - }.let { BindingPath(it) } - // // Parameterized Test Source // @@ -2619,7 +2613,15 @@ internal class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-28"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING, StaticType.CLOB), + expected = unionOf( + StaticType.INT2, + StaticType.INT4, + StaticType.INT8, + StaticType.INT, + StaticType.DECIMAL, + StaticType.STRING, + StaticType.CLOB + ), ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-29"), @@ -2734,7 +2736,13 @@ internal class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-16"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL) + expected = unionOf( + StaticType.INT2, + StaticType.INT4, + StaticType.INT8, + StaticType.INT, + StaticType.DECIMAL + ) ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-17"), @@ -2841,7 +2849,14 @@ internal class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-15"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING) + expected = unionOf( + StaticType.INT2, + StaticType.INT4, + StaticType.INT8, + StaticType.INT, + StaticType.DECIMAL, + StaticType.STRING + ) ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-16"), @@ -3702,7 +3717,7 @@ internal class PlanTyperTestsPorted { // private fun infer( query: String, - session: PartiQLPlanner.Session, + session: Session, problemCollector: ProblemCollector, ): PartiQLPlan { val ast = parser.parse(query).root @@ -3716,14 +3731,9 @@ internal class PlanTyperTestsPorted { } private fun runTest(tc: SuccessTestCase) { - val session = PartiQLPlanner.Session( - tc.query.hashCode().toString(), - USER_ID, - tc.catalog, - tc.catalogPath, - catalogs = mapOf(*catalogs.toTypedArray()), - ) - + val session = Session.builder() + .namespace(Namespace.of(listOf(tc.catalog) + tc.catalogPath)) + .build() val hasQuery = tc.query != null val hasKey = tc.key != null if (hasQuery == hasKey) { @@ -3757,13 +3767,9 @@ internal class PlanTyperTestsPorted { } private fun runTest(tc: ErrorTestCase) { - val session = PartiQLPlanner.Session( - tc.query.hashCode().toString(), - USER_ID, - tc.catalog, - tc.catalogPath, - catalogs = mapOf(*catalogs.toTypedArray()), - ) + val session = Session.builder() + .namespace(Namespace.of(listOf(tc.catalog) + tc.catalogPath)) + .build() val collector = ProblemCollector() val hasQuery = tc.query != null @@ -3803,13 +3809,9 @@ internal class PlanTyperTestsPorted { } private fun runTest(tc: ThrowingExceptionTestCase) { - val session = PartiQLPlanner.Session( - tc.query.hashCode().toString(), - USER_ID, - tc.catalog, - tc.catalogPath, - catalogs = mapOf(*catalogs.toTypedArray()), - ) + val session = Session.builder() + .namespace(Namespace.of(listOf(tc.catalog) + tc.catalogPath)) + .build() val collector = ProblemCollector() val exception = assertThrows { infer(tc.query, session, collector) diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt index a5274f20d..8671d8200 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt @@ -4,12 +4,10 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource +import org.partiql.planner.catalog.Identifier import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName -import org.partiql.spi.BindingPath import org.partiql.types.PType import kotlin.test.assertEquals import kotlin.test.fail @@ -101,13 +99,13 @@ internal class TypeEnvTest { assertEquals(expected, root.ref) } - private fun String.path(): BindingPath { + private fun String.path(): Identifier { val steps = trim().split(".").map { when (it.startsWith("\"")) { - true -> BindingName(it.drop(1).dropLast(1), BindingCase.SENSITIVE) - else -> BindingName(it, BindingCase.INSENSITIVE) + true -> Identifier.Part.delimited(it.drop(1).dropLast(1)) // strip " + else -> Identifier.Part.regular(it) } } - return BindingPath(steps) + return Identifier.of(steps) } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalCatalog.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalCatalog.kt new file mode 100644 index 000000000..6195401f3 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalCatalog.kt @@ -0,0 +1,76 @@ +package org.partiql.planner.plugins.local + +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.Session +import org.partiql.planner.catalog.Table +import java.nio.file.Path +import kotlin.io.path.isDirectory +import kotlin.io.path.notExists + +/** + * Implementation of [Catalog] where dirs are namespaces and files are table metadata. + */ +internal class LocalCatalog( + 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" } + } + + override fun getName(): String { + return name + } + + override fun getTable(session: Session, name: Name): Table? { + val path = toPath(name.getNamespace()).resolve(name.getName() + EXT) + if (path.notExists() || !path.isDirectory()) { + return null + } + return LocalTable(name.getName(), 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) + } + + 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()) } + } + + override fun getRoutines(session: Session, name: Name): Collection = emptyList() + + private fun toPath(namespace: Namespace): Path { + var curr = root + for (level in namespace) { + curr = curr.resolve(level) + } + return curr + } + + private fun toNamespace(path: Path): Namespace { + return Namespace.of(path.relativize(root).map { it.toString() }) + } +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalSchema.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalSchema.kt new file mode 100644 index 000000000..be24a8521 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalSchema.kt @@ -0,0 +1,235 @@ +/* + * 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.planner.plugins.local + +import com.amazon.ionelement.api.IonElement +import com.amazon.ionelement.api.ListElement +import com.amazon.ionelement.api.StringElement +import com.amazon.ionelement.api.StructElement +import com.amazon.ionelement.api.SymbolElement +import com.amazon.ionelement.api.ionListOf +import com.amazon.ionelement.api.ionString +import com.amazon.ionelement.api.ionStructOf +import com.amazon.ionelement.api.ionSymbol +import org.partiql.types.AnyOfType +import org.partiql.types.AnyType +import org.partiql.types.BagType +import org.partiql.types.BlobType +import org.partiql.types.BoolType +import org.partiql.types.ClobType +import org.partiql.types.DateType +import org.partiql.types.DecimalType +import org.partiql.types.FloatType +import org.partiql.types.GraphType +import org.partiql.types.IntType +import org.partiql.types.ListType +import org.partiql.types.MissingType +import org.partiql.types.NullType +import org.partiql.types.SexpType +import org.partiql.types.StaticType +import org.partiql.types.StringType +import org.partiql.types.StructType +import org.partiql.types.SymbolType +import org.partiql.types.TimeType +import org.partiql.types.TimestampType +import org.partiql.types.TupleConstraint + +// Use some generated serde eventually + +public inline fun StructElement.getAngry(name: String): T { + val f = getOptional(name) ?: error("Expected field `$name`") + if (f !is T) { + error("Expected field `name` to be of type ${T::class.simpleName}") + } + return f +} + +/** + * Parses an IonElement to a StaticType. + * + * The format used is effectively Avro JSON, but with PartiQL type names. + */ +public fun IonElement.toStaticType(): StaticType { + return when (this) { + is StringElement -> this.toStaticType() + is ListElement -> this.toStaticType() + is StructElement -> this.toStaticType() + else -> error("Invalid element, expected string, list, or struct") + } +} + +// Atomic type +public fun StringElement.toStaticType(): StaticType = when (textValue) { + "any" -> StaticType.ANY + "bool" -> StaticType.BOOL + "int8" -> error("`int8` is currently not supported") + "int16" -> StaticType.INT2 + "int32" -> StaticType.INT4 + "int64" -> StaticType.INT8 + "int" -> StaticType.INT + "decimal" -> StaticType.DECIMAL + "float32" -> StaticType.FLOAT + "float64" -> StaticType.FLOAT + "string" -> StaticType.STRING + "symbol" -> StaticType.SYMBOL + "binary" -> error("`binary` is currently not supported") + "byte" -> error("`byte` is currently not supported") + "blob" -> StaticType.BLOB + "clob" -> StaticType.CLOB + "date" -> StaticType.DATE + "time" -> StaticType.TIME + "timestamp" -> StaticType.TIMESTAMP + "interval" -> error("`interval` is currently not supported") + "bag" -> error("`bag` is not an atomic type") + "list" -> error("`list` is not an atomic type") + "sexp" -> error("`sexp` is not an atomic type") + "struct" -> error("`struct` is not an atomic type") + "null", "missing" -> error("Absent values ($textValue) do not have a corresponding type.") + else -> error("Invalid type `$textValue`") +} + +// Union type +public fun ListElement.toStaticType(): StaticType { + val types = values.map { it.toStaticType() }.toSet() + return StaticType.unionOf(types) +} + +// Complex type +public fun StructElement.toStaticType(): StaticType { + val type = getAngry("type").textValue + return when (type) { + "bag" -> toBagType() + "list" -> toListType() + "sexp" -> toSexpType() + "struct" -> toStructType() + else -> error("Unknown complex type $type") + } +} + +public fun StructElement.toBagType(): StaticType { + val items = getAngry("items").toStaticType() + return BagType(items) +} + +public fun StructElement.toListType(): StaticType { + val items = getAngry("items").toStaticType() + return ListType(items) +} + +public fun StructElement.toSexpType(): StaticType { + val items = getAngry("items").toStaticType() + return SexpType(items) +} + +public fun StructElement.toStructType(): StaticType { + // Constraints + var contentClosed = false + val constraintsE = getOptional("constraints") ?: ionListOf() + val constraints = (constraintsE as ListElement).values.map { + assert(it is SymbolElement) + it as SymbolElement + when (it.textValue) { + "ordered" -> TupleConstraint.Ordered + "unique" -> TupleConstraint.UniqueAttrs(true) + "closed" -> { + contentClosed = true + TupleConstraint.Open(false) + } + else -> error("unknown tuple constraint `${it.textValue}`") + } + }.toSet() + // Fields + val fieldsE = getAngry("fields") + val fields = fieldsE.values.map { + assert(it is StructElement) { "field definition must be as struct" } + it as StructElement + val name = it.getAngry("name").textValue + val type = it.getAngry("type").toStaticType() + StructType.Field(name, type) + } + return StructType(fields, contentClosed, constraints = constraints) +} + +public fun StaticType.toIon(): IonElement = when (this) { + is AnyOfType -> this.toIon() + is AnyType -> ionString("any") + is BlobType -> ionString("blob") + is BoolType -> ionString("bool") + is ClobType -> ionString("clob") + is BagType -> this.toIon() + is ListType -> this.toIon() + is SexpType -> this.toIon() + is DateType -> ionString("date") + is DecimalType -> ionString("decimal") + is FloatType -> ionString("float64") + is GraphType -> ionString("graph") + is IntType -> when (this.rangeConstraint) { + IntType.IntRangeConstraint.SHORT -> ionString("int16") + IntType.IntRangeConstraint.INT4 -> ionString("int32") + IntType.IntRangeConstraint.LONG -> ionString("int64") + IntType.IntRangeConstraint.UNCONSTRAINED -> ionString("int") + } + is StringType -> ionString("string") // TODO char + is StructType -> this.toIon() + is SymbolType -> ionString("symbol") + is TimeType -> ionString("time") + is TimestampType -> ionString("timestamp") + is MissingType, is NullType -> error("Cannot output absent type ($this) to Ion.") +} + +private fun AnyOfType.toIon(): IonElement { + // create some predictable ordering + val sorted = this.types.sortedWith { t1, t2 -> t1::class.java.simpleName.compareTo(t2::class.java.simpleName) } + val elements = sorted.map { it.toIon() } + return ionListOf(elements) +} + +private fun BagType.toIon(): IonElement = ionStructOf( + "type" to ionString("bag"), + "items" to elementType.toIon() +) + +private fun ListType.toIon(): IonElement = ionStructOf( + "type" to ionString("list"), + "items" to elementType.toIon() +) + +private fun SexpType.toIon(): IonElement = ionStructOf( + "type" to ionString("sexp"), + "items" to elementType.toIon() +) + +private fun StructType.toIon(): IonElement { + val constraintSymbols = mutableListOf() + for (constraint in constraints) { + val c = when (constraint) { + is TupleConstraint.Open -> if (constraint.value) null else ionSymbol("closed") + TupleConstraint.Ordered -> ionSymbol("ordered") + is TupleConstraint.UniqueAttrs -> ionSymbol("unique") + } + if (c != null) constraintSymbols.add(c) + } + val fieldTypes = this.fields.map { + ionStructOf( + "name" to ionString(it.key), + "type" to it.value.toIon(), + ) + } + return ionStructOf( + "type" to ionString("struct"), + "fields" to ionListOf(fieldTypes), + "constraints" to ionListOf(constraintSymbols), + ) +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalTable.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalTable.kt new file mode 100644 index 000000000..4ff85e3c0 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/plugins/local/LocalTable.kt @@ -0,0 +1,46 @@ +/* + * 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.planner.plugins.local + +import com.amazon.ion.system.IonReaderBuilder +import com.amazon.ionelement.api.loadSingleElement +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 { + + init { + assert(!path.isDirectory()) { "LocalTable path must be a file." } + } + + override fun getName(): String = name + + override fun getSchema(): PType { + val reader = IonReaderBuilder.standard().build(path.reader()) + val element = loadSingleElement(reader) + val staticType = element.toStaticType() + return PType.fromStaticType(staticType) + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt index 086571fe3..c2c56ea14 100644 --- a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt @@ -17,7 +17,6 @@ package org.partiql.planner.test import java.io.File import java.io.InputStream import java.nio.file.Path -import kotlin.io.path.toPath /** * The PartiQLTestProvider is a simple utility for indexing SQL statements within files for re-use across library tests. @@ -29,11 +28,6 @@ class PartiQLTestProvider { */ private val map: MutableMap = mutableMapOf() - /** - * Default database of test inputs. - */ - private val default = this::class.java.getResource("/inputs")!!.toURI().toPath() - /** * Load test groups from a directory. */ 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 571026dad..677a63c42 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 @@ -29,7 +29,7 @@ import org.partiql.spi.connector.Connector */ public class MemoryConnector private constructor( private val name: String, - private val tables: Map, + private val tables: Map, ) : Connector { override fun getBindings(): Bindings = bindings @@ -56,7 +56,7 @@ public class MemoryConnector private constructor( public class Builder internal constructor() { private var name: String? = null - private var tables: MutableMap = mutableMapOf() + private var tables: MutableMap = mutableMapOf() public fun name(name: String): Builder = apply { this.name = name } @@ -71,7 +71,7 @@ public class MemoryConnector private constructor( */ private val bindings = object : Bindings { override fun getBindings(name: String): Bindings? = null - override fun getBinding(name: String): Binding? = tables[name] + override fun getBinding(name: String): Binding? = tables[Name.of(name)] } /** @@ -85,11 +85,11 @@ public class MemoryConnector private constructor( if (name.hasNamespace()) { error("MemoryCatalog does not support namespaces") } - return tables[name.getName()] + return tables[name] } override fun listTables(session: Session): Collection { - return tables.keys.map { Name.of(it) } + return tables.keys.map { it } } } } 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 index 278a582e0..001bfc734 100644 --- 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 @@ -7,12 +7,12 @@ import org.partiql.planner.catalog.Table import org.partiql.types.PType public class MemoryTable private constructor( - private val name: String, + private val name: Name, private val type: PType, private val datum: Datum, ) : Table, Binding { - override fun getName(): Name = Name.of(name) + override fun getName(): Name = name override fun getSchema(): PType = type override fun getDatum(): Datum = datum @@ -22,7 +22,7 @@ public class MemoryTable private constructor( * Create an empty table with dynamic schema. */ @JvmStatic - public fun empty(name: String): MemoryTable = MemoryTable( + public fun empty(name: Name): MemoryTable = MemoryTable( name = name, type = PType.typeDynamic(), datum = Datum.nullValue(), @@ -32,7 +32,7 @@ public class MemoryTable private constructor( * Create an empty table with known schema. */ @JvmStatic - public fun empty(name: String, schema: PType): MemoryTable = MemoryTable( + public fun empty(name: Name, schema: PType): MemoryTable = MemoryTable( name = name, type = schema, datum = Datum.nullValue(), @@ -42,7 +42,7 @@ public class MemoryTable private constructor( * Create a table from a Datum with dynamic schema. */ @JvmStatic - public fun of(name: String, value: Datum): MemoryTable = MemoryTable( + public fun of(name: Name, value: Datum): MemoryTable = MemoryTable( name = name, type = PType.typeDynamic(), datum = value, @@ -52,7 +52,7 @@ public class MemoryTable private constructor( * Create a table from a Datum with known schema. */ @JvmStatic - public fun of(name: String, value: Datum, schema: PType): MemoryTable = MemoryTable( + public fun of(name: Name, value: Datum, schema: PType): MemoryTable = MemoryTable( name = name, type = schema, datum = value,