diff --git a/partiql-planner/api/partiql-planner.api b/partiql-planner/api/partiql-planner.api index 75589ccc4..91ef7183c 100644 --- a/partiql-planner/api/partiql-planner.api +++ b/partiql-planner/api/partiql-planner.api @@ -46,3 +46,287 @@ public abstract interface class org/partiql/planner/PartiQLPlannerPass { public abstract fun apply (Lorg/partiql/plan/PartiQLPlan;Lkotlin/jvm/functions/Function1;)Lorg/partiql/plan/PartiQLPlan; } +public abstract interface class org/partiql/planner/catalog/Catalog { + public abstract fun getFunctions (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Ljava/util/Collection; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getTable (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Lorg/partiql/planner/catalog/Table; + public abstract fun getTableHandle (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Table$Handle; + public abstract fun listNamespaces (Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public abstract fun listNamespaces (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; + public abstract fun listTables (Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public abstract fun listTables (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; +} + +public final class org/partiql/planner/catalog/Catalog$DefaultImpls { + public static fun getFunctions (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Ljava/util/Collection; + public static fun getTable (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Lorg/partiql/planner/catalog/Table; + public static fun getTableHandle (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Table$Handle; + public static fun listNamespaces (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public static fun listNamespaces (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; + public static fun listTables (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public static fun listTables (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; +} + +public abstract interface class org/partiql/planner/catalog/Catalogs { + public static final field Companion Lorg/partiql/planner/catalog/Catalogs$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Catalogs$Builder; + public abstract fun default ()Lorg/partiql/planner/catalog/Catalog; + public abstract fun get (Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; + public abstract fun list ()Ljava/util/Collection; + public static fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Catalogs; + public static fun of ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs; +} + +public final class org/partiql/planner/catalog/Catalogs$Builder { + public fun ()V + public final fun add (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; + public final fun build ()Lorg/partiql/planner/catalog/Catalogs; + public final fun default (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; +} + +public final class org/partiql/planner/catalog/Catalogs$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Catalogs$Builder; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Catalogs; + public final fun of ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs; +} + +public final class org/partiql/planner/catalog/Catalogs$DefaultImpls { + public static fun get (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; + public static synthetic fun get$default (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/planner/catalog/Catalog; + public static fun list (Lorg/partiql/planner/catalog/Catalogs;)Ljava/util/Collection; +} + +public abstract interface class org/partiql/planner/catalog/Function { + public static final field Companion Lorg/partiql/planner/catalog/Function$Companion; + public static fun aggregation (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Aggregation; + public abstract fun computeReturnType (Ljava/util/List;)Lorg/partiql/types/PType; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getParameters ()[Lorg/partiql/planner/catalog/Function$Parameter; + public abstract fun getReturnType ()Lorg/partiql/types/PType$Kind; + public abstract fun getSpecific ()Ljava/lang/String; + public static fun scalar (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Scalar; +} + +public abstract interface class org/partiql/planner/catalog/Function$Aggregation : org/partiql/planner/catalog/Function { + public abstract fun getSpecific ()Ljava/lang/String; + public abstract fun isDecomposable ()Z +} + +public final class org/partiql/planner/catalog/Function$Aggregation$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function$Aggregation;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function$Aggregation;)[Lorg/partiql/planner/catalog/Function$Parameter; + public static fun getSpecific (Lorg/partiql/planner/catalog/Function$Aggregation;)Ljava/lang/String; + public static fun isDecomposable (Lorg/partiql/planner/catalog/Function$Aggregation;)Z +} + +public final class org/partiql/planner/catalog/Function$Companion { + public final fun aggregation (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Aggregation; + public final fun scalar (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Scalar; +} + +public final class org/partiql/planner/catalog/Function$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function;)[Lorg/partiql/planner/catalog/Function$Parameter; +} + +public final class org/partiql/planner/catalog/Function$Parameter { + public final field name Ljava/lang/String; + public final field type Lorg/partiql/types/PType$Kind; + public fun (Ljava/lang/String;Lorg/partiql/types/PType$Kind;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lorg/partiql/types/PType$Kind; + public final fun copy (Ljava/lang/String;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Parameter; + public static synthetic fun copy$default (Lorg/partiql/planner/catalog/Function$Parameter;Ljava/lang/String;Lorg/partiql/types/PType$Kind;ILjava/lang/Object;)Lorg/partiql/planner/catalog/Function$Parameter; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/partiql/planner/catalog/Function$Scalar : org/partiql/planner/catalog/Function { + public abstract fun getSpecific ()Ljava/lang/String; + public abstract fun isNullCall ()Z +} + +public final class org/partiql/planner/catalog/Function$Scalar$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function$Scalar;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function$Scalar;)[Lorg/partiql/planner/catalog/Function$Parameter; + public static fun getSpecific (Lorg/partiql/planner/catalog/Function$Scalar;)Ljava/lang/String; + public static fun isNullCall (Lorg/partiql/planner/catalog/Function$Scalar;)Z +} + +public final class org/partiql/planner/catalog/Identifier : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Identifier$Companion; + public synthetic fun ([Lorg/partiql/planner/catalog/Identifier$Part;Lorg/partiql/planner/catalog/Identifier$Part;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun append (Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Identifier; + public final fun append ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun getIdentifier ()Lorg/partiql/planner/catalog/Identifier$Part; + public final fun getParts ()Ljava/util/List; + public final fun getQualifier ()[Lorg/partiql/planner/catalog/Identifier$Part; + public final fun hasQualifier ()Z + public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; + public final fun matches (Ljava/lang/String;Z)Z + public static synthetic fun matches$default (Lorg/partiql/planner/catalog/Identifier;Ljava/lang/String;ZILjava/lang/Object;)Z + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public static final fun of ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public static final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Identifier$Companion { + public final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public final fun delimited (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public final fun delimited ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public final fun of ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; +} + +public final class org/partiql/planner/catalog/Identifier$Part { + public static final field Companion Lorg/partiql/planner/catalog/Identifier$Part$Companion; + public synthetic fun (Ljava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public fun equals (Ljava/lang/Object;)Z + public final fun getText ()Ljava/lang/String; + public fun hashCode ()I + public final fun isRegular ()Z + public final fun matches (Ljava/lang/String;)Z + public static final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Identifier$Part$Companion { + public final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; +} + +public final class org/partiql/planner/catalog/Name : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Name$Companion; + public fun (Lorg/partiql/planner/catalog/Namespace;Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun getName ()Ljava/lang/String; + public final fun getNamespace ()Lorg/partiql/planner/catalog/Namespace; + public final fun hasNamespace ()Z + public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Name; + public static final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Name; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Name$Companion { + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Name; + public final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Name; +} + +public final class org/partiql/planner/catalog/Namespace : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Namespace$Companion; + public synthetic fun ([Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun append ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; + public final fun asIdentifier ()Lorg/partiql/planner/catalog/Identifier; + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun get (I)Ljava/lang/String; + public final fun getLength ()I + public final fun getLevels ()[Ljava/lang/String; + public fun hashCode ()I + public final fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Namespace; + public static final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Namespace$Companion { + public final fun empty ()Lorg/partiql/planner/catalog/Namespace; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Namespace; + public final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; +} + +public final class org/partiql/planner/catalog/Path : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Path$Companion; + public synthetic fun (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun forEach (Ljava/util/function/Consumer;)V + public final fun get (I)Lorg/partiql/planner/catalog/Namespace; + public final fun getLength ()I + public final fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public static final fun of ([Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Path; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Path$Companion { + public final fun of ([Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Path; +} + +public abstract interface class org/partiql/planner/catalog/Session { + public static final field Companion Lorg/partiql/planner/catalog/Session$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Session$Builder; + public static fun empty (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session; + public abstract fun getCatalog ()Ljava/lang/String; + public abstract fun getIdentity ()Ljava/lang/String; + public abstract fun getNamespace ()Lorg/partiql/planner/catalog/Namespace; + public abstract fun getPath ()Lorg/partiql/planner/catalog/Path; + public abstract fun getProperties ()Ljava/util/Map; +} + +public final class org/partiql/planner/catalog/Session$Builder { + public fun ()V + public final fun build ()Lorg/partiql/planner/catalog/Session; + public final fun catalog (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun identity (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun namespace (Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun property (Ljava/lang/String;Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; +} + +public final class org/partiql/planner/catalog/Session$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Session$Builder; + public final fun empty (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session; +} + +public final class org/partiql/planner/catalog/Session$DefaultImpls { + public static fun getPath (Lorg/partiql/planner/catalog/Session;)Lorg/partiql/planner/catalog/Path; + public static fun getProperties (Lorg/partiql/planner/catalog/Session;)Ljava/util/Map; +} + +public abstract interface class org/partiql/planner/catalog/Table { + public static final field Companion Lorg/partiql/planner/catalog/Table$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Table$Builder; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getSchema ()Lorg/partiql/types/PType; + public static fun of (Ljava/lang/String;Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table; +} + +public final class org/partiql/planner/catalog/Table$Builder { + public fun ()V + public final fun build ()Lorg/partiql/planner/catalog/Table; + public final fun name (Ljava/lang/String;)Lorg/partiql/planner/catalog/Table$Builder; + public final fun schema (Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table$Builder; +} + +public final class org/partiql/planner/catalog/Table$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Table$Builder; + public final fun of (Ljava/lang/String;Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table; + public static synthetic fun of$default (Lorg/partiql/planner/catalog/Table$Companion;Ljava/lang/String;Lorg/partiql/types/PType;ILjava/lang/Object;)Lorg/partiql/planner/catalog/Table; +} + +public final class org/partiql/planner/catalog/Table$DefaultImpls { + public static fun getSchema (Lorg/partiql/planner/catalog/Table;)Lorg/partiql/types/PType; +} + +public final class org/partiql/planner/catalog/Table$Handle { + public final field name Lorg/partiql/planner/catalog/Name; + public final field table Lorg/partiql/planner/catalog/Table; + public fun (Lorg/partiql/planner/catalog/Name;Lorg/partiql/planner/catalog/Table;)V +} + 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 new file mode 100644 index 000000000..a094835b9 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt @@ -0,0 +1,72 @@ +package org.partiql.planner.catalog + +/** + * Catalog interface for access to tables and routines. + * + * Related + * - Iceberg — https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/catalog/Catalog.java + * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java + */ +public interface Catalog { + + /** + * Returns the catalog name. + */ + public fun getName(): String + + /** + * Get a table by name. + */ + public fun getTable(session: Session, name: Name): Table? = null + + /** + * Given an [Identifier], returns a [Table.Handle] that corresponds to the longest-available requested path. + * + * For example, given a table named "Table" located within Catalog "AWS" and Namespace "a".b"."c", a user could + * call [getTableHandle] with the identifier "a"."b"."c"."Table". The returned [Table.Handle] will contain the table + * representation and the matching path: "a"."b"."c"."Table" + * + * As another example, consider a table within a [Namespace] that may be a struct with nested attributes. + * A user could call [getTableHandle] with the identifier "a"."b"."c"."Table"."x". In the Namespace, only table + * "Table" exists. Therefore, this method will return a [Table.Handle] with the "Table" representation and the + * matching path: "a"."b"."c"."Table". + * + * IMPORTANT: The returned [Table.Handle.name] must be correct for correct evaluation. + * + * If the [Identifier] does not correspond to an existing [Table], implementers should return null. + */ + public fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? = null + + /** + * List top-level tables. + */ + public fun listTables(session: Session): Collection = listTables(session, Namespace.empty()) + + /** + * List all tables under this namespace. + */ + public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() + + /** + * List top-level namespaces from the catalog. + */ + public fun listNamespaces(session: Session): Collection = listNamespaces(session, Namespace.empty()) + + /** + * List all child namespaces from the namespace. + * + * @param namespace + */ + public fun listNamespaces(session: Session, namespace: Namespace): Collection = emptyList() + + /** + * Get a routine's variants by name; the default implementation is backed by the SQL-99 builtins. + */ + public fun getFunctions(session: Session, name: Name): Collection { + // if (name.hasNamespace()) { + // error("The default catalog implementation does not support namespaced functions, found: $name") + // } + // return SqlFunctions.getFunctions(name.getName()) + error("Catalog functions are not implemented.") + } +} 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 new file mode 100644 index 000000000..cf5346519 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt @@ -0,0 +1,108 @@ +package org.partiql.planner.catalog + +/** + * Catalogs is used to provide the default catalog and possibly others by name. + */ +public interface Catalogs { + + /** + * Returns the default catalog. Required. + */ + public fun default(): Catalog + + /** + * Returns a catalog by name (single identifier). + */ + public fun get(name: String, ignoreCase: Boolean = false): Catalog? { + val default = default() + return if (name.equals(default.getName(), ignoreCase)) { + default + } else { + null + } + } + + /** + * 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/Function.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Function.kt new file mode 100644 index 000000000..87116312c --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Function.kt @@ -0,0 +1,115 @@ +package org.partiql.planner.catalog + +import org.partiql.planner.internal.SqlTypes +import org.partiql.types.PType + +/** + * A [Function] is a PartiQL-routine callable from an expression context. + */ +public sealed interface Function { + + /** + * The routine 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 + + /** + * Compute a [PType] from the given arguments. + */ + public fun computeReturnType(args: List): PType = SqlTypes.from(getReturnType()) + + /** + * !! DO NOT OVERRIDE !! + */ + public fun getSpecific(): String + + /** + * Represents an SQL row-value expression call. + */ + public interface Scalar : Function { + + /** + * SQL NULL CALL -> RETURNS NULL ON NULL INPUT + */ + public fun isNullCall(): Boolean = true + + /** + * !! DO NOT OVERRIDE !! + */ + public override fun getSpecific(): String { + val name = getName().uppercase() + val parameters = getParameters().joinToString("__") { it.type.name } + val returnType = getReturnType().name + return "FN_${name}___${parameters}___$returnType" + } + } + + /** + * Represents an SQL table-value expression call. + */ + public interface Aggregation : Function { + + public fun isDecomposable(): Boolean = true + + /** + * !! DO NOT OVERRIDE !! + */ + public override fun getSpecific(): String { + val name = getName() + val parameters = getParameters().joinToString("__") { it.type.name } + val returnType = getReturnType().name + return "AGG_${name}___${parameters}___$returnType" + } + } + + /** + * [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, + ) + + /** + * Memoized defaults. + */ + public companion object { + + private val DEFAULT_PARAMETERS = emptyArray() + + @JvmStatic + public fun scalar( + name: String, + parameters: Collection, + returnType: PType.Kind, + ): Scalar = object : Scalar { + override fun getName(): String = name + override fun getParameters(): Array = parameters.toTypedArray() + override fun getReturnType(): PType.Kind = returnType + } + + @JvmStatic + public fun aggregation( + name: String, + parameters: Collection, + returnType: PType.Kind, + ): Aggregation = object : Aggregation { + override fun getName(): String = name + override fun getParameters(): Array = parameters.toTypedArray() + override fun getReturnType(): PType.Kind = returnType + } + } +} 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 new file mode 100644 index 000000000..2077ffa02 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -0,0 +1,223 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * Represents an SQL identifier (possibly qualified). + * + * @property qualifier If + * @property identifier + */ +public class Identifier private constructor( + private val qualifier: Array, + private val identifier: Part, +) : Iterable { + + /** + * Returns the unqualified name part. + */ + public fun getIdentifier(): Part = identifier + + /** + * Returns the name's namespace. + */ + 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 + } + + /** + * Iterable forEach(action). + */ + 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() + } + + /** + * Returns a new identifier with the given parts appended. + */ + public fun append(other: Identifier): Identifier { + return of(this.toList() + other.toList()) + } + + /** + * Returns a new identifier with the given parts appended. + */ + public fun append(vararg parts: Part): Identifier { + return of(this.toList() + parts) + } + + /** + * 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 the case-preserved text of two identifiers — that is case-sensitive equality. + */ + public 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)) + } + + /** + * 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 + } + + /** + * Return the SQL representation of this identifier. + */ + 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, + ) { + + /** + * 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 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(emptyArray(), Part.regular(text)) + + @JvmStatic + public fun delimited(text: String): Identifier = Identifier(emptyArray(), Part.delimited(text)) + + @JvmStatic + 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.take(parts.size - 1).toTypedArray() + val identifier = parts.last() + return Identifier(qualifier, identifier) + } + + @JvmStatic + public fun delimited(vararg parts: String): Identifier = delimited(parts.toList()) + + @JvmStatic + public fun delimited(parts: Collection): Identifier { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.take(parts.size - 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/catalog/Name.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt new file mode 100644 index 000000000..d19d4469c --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt @@ -0,0 +1,101 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * A reference to a named object in a catalog. + */ +public class Name( + private val namespace: Namespace, + private val name: String, +) : Iterable { + + /** + * 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() + + /** + * Returns an iterator of strings for this name. + */ + private fun getParts(): List { + val parts = mutableListOf() + parts.addAll(namespace) + parts.add(name) + return parts + } + + override fun forEach(action: Consumer?) { + getParts().forEach(action) + } + + override fun iterator(): Iterator { + return getParts().iterator() + } + + override fun spliterator(): Spliterator { + return getParts().spliterator() + } + + /** + * 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) + } + + /** + * The hashCode() is case-sensitive. + */ + override fun hashCode(): Int { + var result = 1 + result = 31 * result + namespace.hashCode() + result = 31 * result + name.hashCode() + return result + } + + /** + * Return the SQL name representation of this name — all parts delimited. + */ + override fun toString(): String { + return Identifier.delimited(getParts()).toString() + } + + 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 > 0) { "Cannot create an empty name" } + val namespace = Namespace.of(names.take(names.size - 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 new file mode 100644 index 000000000..746fb6807 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt @@ -0,0 +1,102 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * 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 { + + public fun getLevels(): Array { + return levels + } + + public fun getLength(): Int { + return levels.size + } + + public fun isEmpty(): Boolean { + return levels.isEmpty() + } + + public fun asIdentifier(): Identifier { + return Identifier.delimited(*levels) + } + + public operator fun get(index: Int): String { + return levels[index] + } + + override fun forEach(action: Consumer?) { + levels.toList().forEach(action) + } + + override fun iterator(): Iterator { + return levels.iterator() + } + + override fun spliterator(): Spliterator { + return levels.toList().spliterator() + } + + public override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + return levels.contentEquals((other as Namespace).levels) + } + + public fun append(vararg levels: String): Namespace { + return Namespace(this.levels + 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 { + if (isEmpty()) { + return "" + } + return Identifier.delimited(*levels).toString() + } + + public companion object { + + private val EMPTY = Namespace(emptyArray()) + + public fun empty(): Namespace = EMPTY + + @JvmStatic + public fun of(vararg levels: String): Namespace { + if (levels.isEmpty()) { + return empty() + } + return Namespace(arrayOf(*levels)) + } + + @JvmStatic + public fun of(levels: Collection): Namespace { + if (levels.isEmpty()) { + return empty() + } + return Namespace(levels.toTypedArray()) + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt new file mode 100644 index 000000000..960054e67 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt @@ -0,0 +1,44 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * The routine resolution path, accessible via PATH. + */ +public class Path private constructor( + private val namespaces: List, +) : Iterable { + + public companion object { + + @JvmStatic + public fun of(vararg namespaces: Namespace): Path = Path(namespaces.toList()) + } + + public fun getLength(): Int { + return namespaces.size + } + + public fun isEmpty(): Boolean { + return namespaces.isEmpty() + } + + public operator fun get(index: Int): Namespace { + return namespaces[index] + } + + override fun forEach(action: Consumer?) { + namespaces.forEach(action) + } + + override fun iterator(): Iterator { + return namespaces.iterator() + } + + override fun spliterator(): Spliterator { + return namespaces.spliterator() + } + + override fun toString(): String = "PATH: (${namespaces.joinToString()})" +} 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 new file mode 100644 index 000000000..0fd8a7d4a --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt @@ -0,0 +1,95 @@ +package org.partiql.planner.catalog + +/** + * Session is used for authorization and name resolution. + */ +public interface Session { + + /** + * Returns the caller identity as a string; accessible via CURRENT_USER. + */ + public fun getIdentity(): String + + /** + * Returns the current [Catalog]; accessible via the CURRENT_CATALOG session variable. + */ + public fun getCatalog(): String + + /** + * Returns the current [Namespace]; accessible via the CURRENT_NAMESPACE session variable. + */ + public fun getNamespace(): Namespace + + /** + * Returns the current [Path]; accessible via the PATH and CURRENT_PATH session variables. + * + * Default implementation returns the current namespace. + */ + public fun getPath(): Path = Path.of(getNamespace()) + + /** + * Arbitrary session properties that may be used in planning or custom plan passes. + */ + public fun getProperties(): Map = emptyMap() + + /** + * Factory methods and builder. + */ + public companion object { + + /** + * Returns an empty [Session] with the provided [catalog]. + */ + @JvmStatic + public fun empty(catalog: String): Session = object : Session { + override fun getIdentity(): String = "unknown" + override fun getCatalog(): String = catalog + override fun getNamespace(): Namespace = Namespace.empty() + } + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Session] implementation. + */ + public class Builder { + + private var identity: String = "unknown" + private var catalog: String? = null + private var namespace: Namespace = Namespace.empty() + private var properties: MutableMap = mutableMapOf() + + public fun identity(identity: String): Builder { + this.identity = identity + return this + } + + public fun catalog(catalog: String?): Builder { + this.catalog = catalog + return this + } + + public fun namespace(namespace: Namespace): Builder { + this.namespace = namespace + return this + } + + public fun property(name: String, value: String): Builder { + this.properties[name] = value + return this + } + + public fun build(): Session = object : Session { + + init { + require(catalog != null) { "Session catalog must be set" } + } + + override fun getIdentity(): String = identity + override fun getCatalog(): String = catalog!! + override fun getNamespace(): Namespace = namespace + } + } +} 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 new file mode 100644 index 000000000..6e63eb01d --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt @@ -0,0 +1,79 @@ +package org.partiql.planner.catalog + +import org.partiql.types.PType + +/** + * In PartiQL, a [Table] can take on any type and is not necessarily rows+columns. + */ +public interface Table { + + /** + * Handle holds both a table and its resolved name within its respective catalog. + * + * Note: This replaces ConnectorObjectHandle from versions < 1.0 + */ + public class Handle( + @JvmField public val name: Name, + @JvmField public val table: Table, + ) + + /** + * The table's name. + */ + 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/SqlTypes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/SqlTypes.kt new file mode 100644 index 000000000..0d3938ab7 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/SqlTypes.kt @@ -0,0 +1,252 @@ +package org.partiql.planner.internal + +import org.partiql.ast.Type +import org.partiql.types.Field +import org.partiql.types.PType + +/** + * A factory for single-source of truth for type creations — DO NOT CREATE PTYPE DIRECTLY. + * + * This allows us to raise an interface if we need custom type factories; for now just use defaults with static methods. + */ +internal object SqlTypes { + + private const val MAX_SIZE = Int.MAX_VALUE + + // + // DYNAMIC + // + + @JvmStatic + fun dynamic(): PType = PType.typeDynamic() + + // + // BOOLEAN + // + + @JvmStatic + fun bool(): PType = PType.typeBool() + + // + // NUMERIC + // + + @JvmStatic + fun tinyint(): PType = PType.typeTinyInt() + + @JvmStatic + fun smallint(): PType = PType.typeSmallInt() + + @JvmStatic + fun int(): PType = PType.typeInt() + + @JvmStatic + fun bigint(): PType = PType.typeBigInt() + + /** + * NUMERIC represents an integer with arbitrary precision. It is equivalent to Ion’s integer type, and is conformant to SQL-99s rules for the NUMERIC type. In SQL-99, if a scale is omitted then we choose zero — and if a precision is omitted then the precision is implementation defined. For PartiQL, we define this precision to be inf — aka arbitrary precision. + * + * @param precision Defaults to inf. + * @param scale Defaults to 0. + * @return + */ + @JvmStatic + fun numeric(precision: Int? = null, scale: Int? = null): PType { + if (scale != null && precision == null) { + error("Precision can never be null while scale is specified.") + } + return when { + precision != null && scale != null -> PType.typeDecimal(precision, scale) + precision != null -> PType.typeDecimal(precision, 0) + else -> PType.typeIntArbitrary() + } + } + + /** + * DECIMAL represents an exact numeric type with arbitrary precision and arbitrary scale. It is equivalent to Ion’s decimal type. For a DECIMAL with no given scale we choose inf rather than the SQL prescribed 0 (zero). Here we diverge from SQL-99 for Ion compatibility. Finally, SQL defines decimals as having precision equal to or greater than the given precision. Like other systems, we truncate extraneous precision so that NUMERIC(p,s) is equivalent to DECIMAL(p,s). The only difference between them is the default scale when it’s not specified — we follow SQL-99 for NUMERIC, and we follow Postgres for DECIMAL. + * + * @param precision Defaults to inf. + * @param scale Defaults to 0 when precision is given, otherwise inf. + * @return + */ + @JvmStatic + fun decimal(precision: Int? = null, scale: Int? = null): PType { + if (scale != null && precision == null) { + error("Precision can never be null while scale is specified.") + } + return when { + precision != null && scale != null -> PType.typeDecimal(precision, scale) + precision != null -> PType.typeDecimal(precision, 0) + else -> PType.typeDecimalArbitrary() + } + } + + @JvmStatic + fun real(): PType = PType.typeReal() + + @JvmStatic + fun double(): PType = PType.typeDoublePrecision() + + // + // CHARACTER STRINGS + // + + @JvmStatic + fun char(length: Int? = null): PType = PType.typeChar(length ?: 1) + + @JvmStatic + fun varchar(length: Int? = null): PType = PType.typeVarChar(length ?: MAX_SIZE) + + @JvmStatic + fun string(): PType = PType.typeString() + + @JvmStatic + fun clob(length: Int? = null) = PType.typeClob(length ?: MAX_SIZE) + + // + // BIT STRINGS + // + + @JvmStatic + fun blob(length: Int? = null) = PType.typeBlob(length ?: MAX_SIZE) + + // + // DATETIME + // + + @JvmStatic + fun date(): PType = TODO() + + @JvmStatic + fun time(precision: Int? = null): PType = PType.typeTimeWithoutTZ(precision ?: 6) + + @JvmStatic + fun timez(precision: Int? = null): PType = PType.typeTimeWithTZ(precision ?: 6) + + @JvmStatic + fun timestamp(precision: Int? = null): PType = PType.typeTimeWithoutTZ(precision ?: 6) + + @JvmStatic + fun timestampz(precision: Int? = null): PType = PType.typeTimestampWithTZ(precision ?: 6) + + // + // COLLECTIONS + // + + @JvmStatic + fun array(element: PType? = null, size: Int? = null): PType { + if (size != null) { + error("Fixed-length ARRAY [N] is not supported.") + } + return when (element) { + null -> PType.typeList() + else -> PType.typeList(element) + } + } + + @JvmStatic + fun bag(element: PType? = null, size: Int? = null): PType { + if (size != null) { + error("Fixed-length BAG [N] is not supported.") + } + return when (element) { + null -> PType.typeBag() + else -> PType.typeBag(element) + } + } + + // + // STRUCTURAL + // + + @JvmStatic + fun struct(): PType = PType.typeStruct() + + @JvmStatic + fun row(fields: List): PType = PType.typeRow(fields) + + /** + * Create PType from the AST type. + */ + @JvmStatic + fun from(type: Type): PType = when (type) { + is Type.NullType -> error("Casting to NULL is not supported.") + is Type.Missing -> error("Casting to MISSING is not supported.") + is Type.Bool -> bool() + is Type.Tinyint -> tinyint() + is Type.Smallint, is Type.Int2 -> smallint() + is Type.Int4, is Type.Int -> int() + is Type.Bigint, is Type.Int8 -> bigint() + is Type.Numeric -> numeric(type.precision, type.scale) + is Type.Decimal -> decimal(type.precision, type.scale) + is Type.Real -> real() + is Type.Float32 -> real() + is Type.Float64 -> double() + is Type.Char -> char(type.length) + is Type.Varchar -> varchar(type.length) + is Type.String -> string() + is Type.Symbol -> { + // TODO will we continue supporting symbol? + PType.typeSymbol() + } + is Type.Bit -> error("BIT is not supported yet.") + is Type.BitVarying -> error("BIT VARYING is not supported yet.") + is Type.ByteString -> error("BINARY is not supported yet.") + is Type.Blob -> blob(type.length) + is Type.Clob -> clob(type.length) + is Type.Date -> date() + is Type.Time -> time(type.precision) + is Type.TimeWithTz -> timez(type.precision) + is Type.Timestamp -> timestamp(type.precision) + is Type.TimestampWithTz -> timestampz(type.precision) + is Type.Interval -> error("INTERVAL is not supported yet.") + is Type.Bag -> bag() + is Type.Sexp -> { + // TODO will we continue supporting s-expression? + PType.typeSexp() + } + is Type.Any -> dynamic() + is Type.List -> array() + is Type.Array -> array(type.type?.let { from(it) }) + is Type.Tuple -> struct() + is Type.Struct -> struct() + is Type.Custom -> TODO("Custom type not supported ") + } + + @JvmStatic + fun from(kind: PType.Kind): PType = when (kind) { + PType.Kind.DYNAMIC -> dynamic() + PType.Kind.BOOL -> bool() + PType.Kind.TINYINT -> tinyint() + PType.Kind.SMALLINT -> smallint() + PType.Kind.INT -> int() + PType.Kind.BIGINT -> bigint() + PType.Kind.INT_ARBITRARY -> numeric() + PType.Kind.DECIMAL, PType.Kind.DECIMAL_ARBITRARY -> decimal() + PType.Kind.REAL -> real() + PType.Kind.DOUBLE_PRECISION -> double() + PType.Kind.CHAR -> char() + PType.Kind.VARCHAR -> varchar() + PType.Kind.STRING -> string() + PType.Kind.SYMBOL -> { + // TODO will we continue supporting symbol? + PType.typeSymbol() + } + PType.Kind.BLOB -> blob() + PType.Kind.CLOB -> clob() + PType.Kind.DATE -> date() + PType.Kind.TIME_WITH_TZ -> timez() + PType.Kind.TIME_WITHOUT_TZ -> time() + PType.Kind.TIMESTAMP_WITH_TZ -> timestampz() + PType.Kind.TIMESTAMP_WITHOUT_TZ -> timestamp() + PType.Kind.BAG -> bag() + PType.Kind.LIST -> array() + PType.Kind.ROW -> error("Cannot create a ROW from Kind") + PType.Kind.SEXP -> { + // TODO will we continue supporting sexp? + PType.typeSexp() + } + PType.Kind.STRUCT -> struct() + PType.Kind.UNKNOWN -> PType.typeUnknown() + } +}