From f55b5d8700450bea495306cab35d10e745b9323c Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Fri, 27 Sep 2024 10:31:46 -0700 Subject: [PATCH 1/2] Removes unused AggSignature --- partiql-spi/api/partiql-spi.api | 15 ----- .../org/partiql/spi/function/AggSignature.kt | 67 ------------------- .../org/partiql/spi/function/Aggregation.kt | 10 --- 3 files changed, 92 deletions(-) delete mode 100644 partiql-spi/src/main/kotlin/org/partiql/spi/function/AggSignature.kt diff --git a/partiql-spi/api/partiql-spi.api b/partiql-spi/api/partiql-spi.api index 220a7b495c..57cdc363fd 100644 --- a/partiql-spi/api/partiql-spi.api +++ b/partiql-spi/api/partiql-spi.api @@ -510,21 +510,6 @@ public final class org/partiql/spi/catalog/Table$DefaultImpls { public static fun getSchema (Lorg/partiql/spi/catalog/Table;)Lorg/partiql/types/PType; } -public final class org/partiql/spi/function/AggSignature { - public final field description Ljava/lang/String; - public final field isDecomposable Z - public final field isNullable Z - public final field name Ljava/lang/String; - public final field parameters Ljava/util/List; - public final field returns Lorg/partiql/types/PType; - public fun (Ljava/lang/String;Lorg/partiql/types/PType;Ljava/util/List;Ljava/lang/String;ZZ)V - public synthetic fun (Ljava/lang/String;Lorg/partiql/types/PType;Ljava/util/List;Ljava/lang/String;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getSpecific ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public abstract interface class org/partiql/spi/function/Aggregation : org/partiql/spi/function/Routine { public static final field Companion Lorg/partiql/spi/function/Aggregation$Companion; public abstract fun getAccumulator ([Lorg/partiql/types/PType;)Lorg/partiql/spi/function/Aggregation$Accumulator; diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/AggSignature.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/AggSignature.kt deleted file mode 100644 index c0d3f5d0c4..0000000000 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/AggSignature.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.partiql.spi.function - -import org.partiql.types.PType - -/** - * Represents the signature of a PartiQL aggregation function. - * - * @property isDecomposable Flag indicating this aggregation can be decomposed - * @constructor - */ -public class AggSignature( - @JvmField public val name: String, - @JvmField public val returns: PType, - @JvmField public val parameters: List, - @JvmField public val description: String? = null, - @JvmField public val isNullable: Boolean = true, - @JvmField public val isDecomposable: Boolean = true, -) { - - /** - * Symbolic name of this operator of the form NAME__INPUTS__RETURNS - */ - public val specific: String = buildString { - append(name.uppercase()) - append("__") - append(parameters.joinToString("_") { it.getType().kind.name }) - append("__") - append(returns) - } - - /** - * Use the symbolic name for easy debugging - * - * @return - */ - override fun toString(): String = specific - - override fun equals(other: Any?): Boolean { - if (other !is AggSignature) return false - if ( - other.name != name || - other.returns != returns || - other.parameters.size != parameters.size || - other.isDecomposable != isDecomposable || - other.isNullable != isNullable - ) { - return false - } - // all other parts equal, compare parameters (ignore names) - for (i in parameters.indices) { - val p1 = parameters[i] - val p2 = other.parameters[i] - if (p1.getType() != p2.getType()) return false - } - return true - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + returns.hashCode() - result = 31 * result + parameters.hashCode() - result = 31 * result + isDecomposable.hashCode() - result = 31 * result + isNullable.hashCode() - result = 31 * result + (description?.hashCode() ?: 0) - return result - } -} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt index 443d5ff4db..80d9ef85d8 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt @@ -8,16 +8,6 @@ import org.partiql.types.PType */ public interface Aggregation : Routine { - // /** - // * !! DO NOT OVERRIDE !! - // */ - // public override fun getSpecific(): String { - // val name = getName() - // val parameters = getParameters().joinToString("__") { it.getType().kind.name } - // val returnType = getReturnType().kind.name - // return "AGG_${name}___${parameters}___$returnType" - // } - /** * Instantiates a stateful accumulator for this aggregation function. */ From c601c5c57448cfec36e3ef5d37b82bb1532f925e Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Mon, 30 Sep 2024 17:16:31 -0700 Subject: [PATCH 2/2] [2/2] Parameter match on SqlTypeFamily --- .../org/partiql/planner/internal/Env.kt | 25 +- .../partiql/planner/internal/FnResolver.kt | 39 +- .../planner/internal/casts/CastTable.kt | 13 +- .../planner/internal/casts/Coercions.kt | 211 ----------- partiql-spi/api/partiql-spi.api | 17 +- .../org/partiql/spi/function/Aggregation.kt | 11 +- .../org/partiql/spi/function/Function.kt | 3 +- .../org/partiql/spi/function/Parameter.kt | 92 ++++- .../partiql/spi/function/builtins/AggAny.kt | 4 +- .../partiql/spi/function/builtins/AggAvg.kt | 18 +- .../partiql/spi/function/builtins/AggCount.kt | 2 +- .../partiql/spi/function/builtins/AggEvery.kt | 4 +- .../spi/function/builtins/AggGroupAs.kt | 2 +- .../partiql/spi/function/builtins/AggMax.kt | 18 +- .../partiql/spi/function/builtins/AggMin.kt | 18 +- .../partiql/spi/function/builtins/AggSome.kt | 4 +- .../partiql/spi/function/builtins/AggSum.kt | 18 +- .../org/partiql/spi/internal/SqlTypeFamily.kt | 79 ++++ .../org/partiql/spi/internal/SqlTypes.kt | 348 +++++++----------- 19 files changed, 401 insertions(+), 525 deletions(-) delete mode 100644 partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt create mode 100644 partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypeFamily.kt 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 e1e6998a85..9928f7ae79 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,7 +1,6 @@ package org.partiql.planner.internal 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 @@ -15,6 +14,7 @@ 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.PlanTyper.Companion.toCType import org.partiql.planner.internal.typer.Scope.Companion.toPath import org.partiql.spi.catalog.Catalog import org.partiql.spi.catalog.Catalogs @@ -23,7 +23,6 @@ import org.partiql.spi.catalog.Name import org.partiql.spi.catalog.Session import org.partiql.spi.function.Aggregation import org.partiql.types.PType -import org.partiql.types.PType.Kind /** * [Env] is similar to the database type environment from the PartiQL Specification. This includes resolution of @@ -265,13 +264,12 @@ internal class Env(private val session: Session) { /** * Check if this function accepts the exact input argument types. Assume same arity. */ - private fun Aggregation.matches(args: List): Boolean { val parameters = getParameters() for (i in args.indices) { val a = args[i] val p = parameters[i] - if (p.getType().kind != Kind.DYNAMIC && a != p.getType()) return false + if (p.getMatch(a) != a) return false } return true } @@ -286,20 +284,19 @@ internal class Env(private val session: Session) { val parameters = getParameters() val mapping = arrayOfNulls(args.size) for (i in args.indices) { - val arg = args[i] + val a = args[i] val p = parameters[i] + val m = p.getMatch(a) when { - // 1. Exact match - arg == p.getType() -> continue - // 2. Match ANY, no coercion needed - p.getType().kind == Kind.DYNAMIC -> continue - // 3. Check for a coercion - else -> when (val coercion = Coercions.get(arg, p.getType())) { - null -> return null // short-circuit - else -> mapping[i] = coercion - } + m == null -> return null + m == a -> continue + else -> mapping[i] = coercion(a, m) } } return this to mapping } + + private fun coercion(arg: PType, target: PType): Ref.Cast { + return Ref.Cast(arg.toCType(), target.toCType(), Ref.Cast.Safety.COERCION, true) + } } 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 ce0c346871..43253d3aa3 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,10 @@ package org.partiql.planner.internal -import org.partiql.planner.internal.casts.Coercions import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.typer.CompilerType import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.spi.function.Function +import org.partiql.types.PType import org.partiql.types.PType.Kind /** @@ -121,13 +121,13 @@ internal object FnResolver { for (i in args.indices) { val a = args[i] val p = parameters[i] - if (a != p.getType()) return false + if (p.getMatch(a) != a) return false } return true } /** - * Attempt to match arguments to the parameters; return the implicit casts if necessary. + * Attempt to match arguments to the parameters; return the coercions if necessary. * * @param args * @return @@ -135,28 +135,19 @@ internal object FnResolver { private fun Function.match(args: List): MatchResult? { val parameters = getParameters() val mapping = arrayOfNulls(args.size) - var exactInputTypes: Int = 0 + var exactInputTypes = 0 for (i in args.indices) { - val arg = args[i] + val a = args[i] + if (a.kind == Kind.UNKNOWN) { + continue // skip unknown arguments + } + // check match val p = parameters[i] + val m = p.getMatch(a) when { - // 1. Exact match - arg == p.getType() -> { - exactInputTypes++ - continue - } - // 2. Match ANY parameter, no coercion needed - p.getType().kind == Kind.DYNAMIC -> continue - arg.kind == Kind.UNKNOWN -> continue - // 3. Allow for ANY arguments - arg.kind == Kind.DYNAMIC -> { - mapping[i] = Ref.Cast(arg, p.getType().toCType(), Ref.Cast.Safety.UNSAFE, true) - } - // 4. Check for a coercion - else -> when (val coercion = Coercions.get(arg, p.getType())) { - null -> return null // short-circuit - else -> mapping[i] = coercion - } + m == null -> return null // short-circuit + m == a -> exactInputTypes++ + else -> mapping[i] = coercion(a, m) } } return MatchResult( @@ -165,6 +156,10 @@ internal object FnResolver { ) } + private fun coercion(arg: PType, target: PType): Ref.Cast { + return Ref.Cast(arg.toCType(), target.toCType(), Ref.Cast.Safety.COERCION, true) + } + private class MatchResult( val match: FnMatch.Static, val numberOfExactInputTypes: Int, diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/CastTable.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/CastTable.kt index defbfd9b40..e68a175037 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/CastTable.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/CastTable.kt @@ -5,8 +5,6 @@ import org.partiql.planner.internal.ir.Ref.Cast import org.partiql.planner.internal.typer.CompilerType import org.partiql.types.PType import org.partiql.types.PType.Kind -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType /** * A place to model type relationships (for now this is to answer CAST inquiries). @@ -14,7 +12,6 @@ import org.partiql.value.PartiQLValueType * @property types * @property graph Going with a matrix here (using enum ordinals) as it's simple and avoids walking. */ -@OptIn(PartiQLValueExperimental::class) internal class CastTable private constructor( private val types: Array, private val graph: Array>, @@ -29,8 +26,6 @@ internal class CastTable private constructor( } } - private operator fun Array.get(t: PartiQLValueType): T = get(t.ordinal) - /** * This represents the Y, M, and N in the table listed in SQL:1999 Section 6.22. */ @@ -42,9 +37,7 @@ internal class CastTable private constructor( companion object { - private val N = Kind.values().size - - private operator fun Array.set(t: PartiQLValueType, value: T): Unit = this.set(t.ordinal, value) + private val N = Kind.entries.size private fun relationships(block: RelationshipBuilder.() -> Unit): Array { return with(RelationshipBuilder()) { @@ -60,7 +53,7 @@ internal class CastTable private constructor( */ @JvmStatic val partiql: CastTable = run { - val types = Kind.values() + val types = Kind.entries.toTypedArray() val graph = arrayOfNulls>(N) for (type in types) { // initialize all with empty relationships @@ -68,7 +61,7 @@ internal class CastTable private constructor( } graph[Kind.DYNAMIC.ordinal] = relationships { cast(Kind.DYNAMIC) - Kind.values().filterNot { it == Kind.DYNAMIC }.forEach { + Kind.entries.filterNot { it == Kind.DYNAMIC }.forEach { cast(it) } } 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 deleted file mode 100644 index 2ef2a53eac..0000000000 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/casts/Coercions.kt +++ /dev/null @@ -1,211 +0,0 @@ -package org.partiql.planner.internal.casts - -import org.partiql.planner.internal.ir.Ref -import org.partiql.planner.internal.typer.CompilerType -import org.partiql.types.Field -import org.partiql.types.PType -import org.partiql.types.PType.Kind - -/** - * Important SQL Definitions: - * - assignable: The characteristic of a data type that permits a value of that data type to be - * assigned to a site of a specified data type. - */ -internal object Coercions { - - fun get(input: PType, target: PType): Ref.Cast? { - return getCoercion(input, target) - } - - /** - * Remaining coercions from SQL:1999: - * - Values corresponding to the binary data type are mutually assignable. - * - Values corresponding to the data types BIT and BIT VARYING are always mutually comparable and - * are mutually assignable. - * - Values of type interval are mutually assignable only if the source and target of the assignment are - * both year-month intervals or if they are both day-time intervals. - * - Values corresponding to user-defined types are discussed in Subclause 4.8.4, ‘‘User-defined type - * comparison and assignment’’. - */ - private fun getCoercion(input: PType, target: PType): Ref.Cast? { - return when { - isAssignable(input, target) -> coercion(input, target) - else -> null - } - } - - private val TYPES_NUMBER = setOf( - Kind.TINYINT, - Kind.SMALLINT, - Kind.INTEGER, - Kind.BIGINT, - Kind.NUMERIC, - Kind.REAL, - Kind.DOUBLE, - Kind.DECIMAL, - Kind.DECIMAL_ARBITRARY - ) - - private val TYPES_TEXT = setOf( - Kind.CHAR, - Kind.VARCHAR, - Kind.STRING, - Kind.CLOB, - Kind.SYMBOL - ) - - private val TYPES_COLLECTION = setOf( - Kind.ARRAY, - Kind.SEXP, - Kind.BAG - ) - - private fun isAssignable(input: PType, target: PType): Boolean { - return areAssignableNumberTypes(input, target) || - areAssignableTextTypes(input, target) || - areAssignableBooleanTypes(input, target) || - areAssignableDateTimeTypes(input, target) || - areAssignableCollectionTypes(input, target) || - areAssignableStructuralTypes(input, target) || - areAssignableDynamicTypes(target) - } - - /** - * NOT specified by SQL:1999. We assume that we can coerce a collection of one type to another if the subtype - * of each collection is assignable. - */ - private fun areAssignableCollectionTypes(input: PType, target: PType): Boolean { - return input.kind in TYPES_COLLECTION && target.kind in TYPES_COLLECTION && isAssignable(input.typeParameter, target.typeParameter) - } - - /** - * NOT specified by SQL:1999. We assume that we can statically coerce anything to DYNAMIC. However, note that - * CAST( AS DYNAMIC) is NEVER inserted. We check for the use of DYNAMIC at function resolution. This is merely - * for the [PType.getTypeParameter] and [PType.getFields] - */ - private fun areAssignableDynamicTypes(target: PType): Boolean { - return target.kind == Kind.DYNAMIC - } - - /** - * NOT completely specified by SQL:1999. - * - * From SQL:1999: - * ``` - * Values corresponding to row types are mutually assignable if and only if both have the same degree - * and every field in one row type is mutually assignable to the field in the same ordinal position of - * the other row type. Values corresponding to row types are mutually comparable if and only if both - * have the same degree and every field in one row type is mutually comparable to the field in the - * same ordinal position of the other row type. - * ``` - */ - private fun areAssignableStructuralTypes(input: PType, target: PType): Boolean { - return when { - input.kind == Kind.ROW && target.kind == Kind.ROW -> fieldsAreAssignable(input.fields.toList(), target.fields!!.toList()) - input.kind == Kind.STRUCT && target.kind == Kind.ROW -> true - input.kind == Kind.ROW && target.kind == Kind.STRUCT -> true - input.kind == Kind.STRUCT && target.kind == Kind.STRUCT -> true - else -> false - } - } - - private fun fieldsAreAssignable(input: List, target: List): Boolean { - if (input.size != target.size) { return false } - val iIter = input.iterator() - val tIter = target.iterator() - while (iIter.hasNext()) { - val iField = iIter.next() - val tField = tIter.next() - if (!isAssignable(iField.type, tField.type)) { - return false - } - } - return true - } - - /** - * This is a PartiQL extension. We assume that structs/rows with the same field names may be assignable - * if all names match AND types are assignable. - */ - private fun namedFieldsAreAssignableUnordered(input: List, target: List): Boolean { - if (input.size != target.size) { return false } - val inputSorted = input.sortedBy { it.name } - val targetSorted = target.sortedBy { it.name } - val iIter = inputSorted.iterator() - val tIter = targetSorted.iterator() - while (iIter.hasNext()) { - val iField = iIter.next() - val tField = tIter.next() - if (iField.name != tField.name) { - return false - } - if (!isAssignable(iField.type, tField.type)) { - return false - } - } - return true - } - - /** - * From SQL:1999: - * ``` - * Values of the data types NUMERIC, DECIMAL, INTEGER, SMALLINT, FLOAT, REAL, and - * DOUBLE PRECISION are numbers and are all mutually comparable and mutually assignable. - * ``` - */ - private fun areAssignableNumberTypes(input: PType, target: PType): Boolean { - return input.kind in TYPES_NUMBER && target.kind in TYPES_NUMBER - } - - /** - * From SQL:1999: - * ``` - * Values corresponding to the data type boolean are always mutually comparable and are mutually - * assignable. - * ``` - */ - private fun areAssignableBooleanTypes(input: PType, target: PType): Boolean { - return input.kind == Kind.BOOL && target.kind == Kind.BOOL - } - - /** - * From SQL:1999: - * ``` - * Values corresponding to the data types CHARACTER, CHARACTER VARYING, and CHARACTER - * LARGE OBJECT are mutually assignable if and only if they are taken from the same character - * repertoire. (For this implementation, we shall assume that all text types share the same - * character repertoire.) - * ``` - */ - private fun areAssignableTextTypes(input: PType, target: PType): Boolean { - return input.kind in TYPES_TEXT && target.kind in TYPES_TEXT - } - - /** - * From SQL:1999: - * ``` - * Values of type datetime are mutually assignable only if the source and target of the assignment are - * both of type DATE, or both of type TIME (regardless whether WITH TIME ZONE or WITHOUT - * TIME ZONE is specified or implicit), or both of type TIMESTAMP (regardless whether WITH TIME - * ZONE or WITHOUT TIME ZONE is specified or implicit) - * ``` - */ - private fun areAssignableDateTimeTypes(input: PType, target: PType): Boolean { - val i = input.kind - val t = target.kind - return when { - i == Kind.DATE && t == Kind.DATE -> true - (i == Kind.TIMEZ || i == Kind.TIME) && (t == Kind.TIMEZ || t == Kind.TIME) -> true - (i == Kind.TIMESTAMPZ || i == Kind.TIMESTAMP) && (t == Kind.TIMESTAMPZ || t == Kind.TIMESTAMP) -> true - else -> false - } - } - - private fun explicit(input: PType, target: PType): Ref.Cast { - return Ref.Cast(CompilerType(input), CompilerType(target), Ref.Cast.Safety.EXPLICIT, isNullable = true) - } - - private fun coercion(input: PType, target: PType): Ref.Cast { - return Ref.Cast(CompilerType(input), CompilerType(target), Ref.Cast.Safety.EXPLICIT, isNullable = true) - } -} diff --git a/partiql-spi/api/partiql-spi.api b/partiql-spi/api/partiql-spi.api index 57cdc363fd..fcebd234cb 100644 --- a/partiql-spi/api/partiql-spi.api +++ b/partiql-spi/api/partiql-spi.api @@ -513,7 +513,7 @@ public final class org/partiql/spi/catalog/Table$DefaultImpls { public abstract interface class org/partiql/spi/function/Aggregation : org/partiql/spi/function/Routine { public static final field Companion Lorg/partiql/spi/function/Aggregation$Companion; public abstract fun getAccumulator ([Lorg/partiql/types/PType;)Lorg/partiql/spi/function/Aggregation$Accumulator; - public static fun standard (Ljava/lang/String;[Lorg/partiql/spi/function/Parameter;Lorg/partiql/types/PType;Lkotlin/jvm/functions/Function0;)Lorg/partiql/spi/function/Aggregation; + public static fun static (Ljava/lang/String;[Lorg/partiql/spi/function/Parameter;Lorg/partiql/types/PType;Lkotlin/jvm/functions/Function0;)Lorg/partiql/spi/function/Aggregation; } public abstract interface class org/partiql/spi/function/Aggregation$Accumulator { @@ -522,7 +522,7 @@ public abstract interface class org/partiql/spi/function/Aggregation$Accumulator } public final class org/partiql/spi/function/Aggregation$Companion { - public final fun standard (Ljava/lang/String;[Lorg/partiql/spi/function/Parameter;Lorg/partiql/types/PType;Lkotlin/jvm/functions/Function0;)Lorg/partiql/spi/function/Aggregation; + public final fun static (Ljava/lang/String;[Lorg/partiql/spi/function/Parameter;Lorg/partiql/types/PType;Lkotlin/jvm/functions/Function0;)Lorg/partiql/spi/function/Aggregation; } public final class org/partiql/spi/function/Aggregation$DefaultImpls { @@ -557,9 +557,22 @@ public abstract class org/partiql/spi/function/Function$Instance { } public final class org/partiql/spi/function/Parameter { + public static final field Companion Lorg/partiql/spi/function/Parameter$Companion; public fun (Ljava/lang/String;Lorg/partiql/types/PType;)V + public static final fun collection (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public static final fun dynamic (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public final fun getMatch (Lorg/partiql/types/PType;)Lorg/partiql/types/PType; public final fun getName ()Ljava/lang/String; public final fun getType ()Lorg/partiql/types/PType; + public static final fun numeric (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public static final fun text (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; +} + +public final class org/partiql/spi/function/Parameter$Companion { + public final fun collection (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public final fun dynamic (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public final fun numeric (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; + public final fun text (Ljava/lang/String;)Lorg/partiql/spi/function/Parameter; } public abstract interface class org/partiql/spi/function/Routine { diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt index 80d9ef85d8..6545d93f3d 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Aggregation.kt @@ -38,8 +38,17 @@ public interface Aggregation : Routine { public companion object { + /** + * TODO consider replacing with a builder prior to 1.0 + * + * @param name + * @param parameters + * @param returns + * @param accumulator + * @return + */ @JvmStatic - public fun standard( + public fun static( name: String, parameters: Array, returns: PType, diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Function.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Function.kt index fd8fa65e58..2941429b37 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Function.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Function.kt @@ -89,7 +89,8 @@ public interface Function : Routine { override fun getReturnType(args: Array): PType = returns override fun getInstance(args: Array): Instance = instance override fun toString(): String { - val parameters = parameters.joinToString("__") { it.getType().kind.name } + // TODO debug strings for SqlTypeFamily + // val parameters = parameters.joinToString("__") { it.getType().kind.name } val returnType = returns.kind.name return "FN_${name}___${parameters}___$returnType" } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Parameter.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Parameter.kt index 3d55ac871c..36710acbb2 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/Parameter.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/Parameter.kt @@ -1,26 +1,108 @@ package org.partiql.spi.function +import org.partiql.spi.internal.SqlTypeFamily +import org.partiql.spi.internal.SqlTypes import org.partiql.types.PType +import org.partiql.types.PType.Kind.DYNAMIC /** * [Parameter] is a formal argument's definition. */ public class Parameter private constructor( private var name: String, - private var type: PType, - private var variadic: Boolean, + private var type: SqlTypeFamily, + private var dynamic: Boolean = false, + private var variadic: Boolean = false, ) { + /** + * DEVELOPER NOTES + * ------------------------------------------------ + * Representation of a "family" is internalized + * to the library, and is not (and should not) be + * exposed to the library consumers. + * + * Consumers may only instantiate parameters by + * using the static factory methods or the simple + * constructor. This enables refinement of the + * parameter representation without impacting the + * public API. + * + * This is not an interface)because it is NOT meant + * to be extended, as that may interfere with + * function resolution and query semantics. + * + * Note that there are several places to enhance + * this, and some things may be redundant. Again, + * this is an internal API and any refinements + * and optimizations are certainly welcomed. + * ------------------------------------------------ + */ + /** * @constructor - * Default public constructor forms a regular (non-variadic + * Create a non-variadic parameter with the exact type. * * @param name Parameter name used for error reporting, debugging, and documentation. * @param type Parameter type used for function resolution. */ - public constructor(name: String, type: PType) : this(name, type, false) + public constructor(name: String, type: PType) : this(name, SqlTypeFamily(type)) { + dynamic = type.kind == DYNAMIC + variadic = false + } + /** + * Get the parameter name; used for debugging and error reporting. + */ public fun getName(): String = name - public fun getType(): PType = type + /** + * Get the parameter preferred type. + */ + public fun getType(): PType = type.preferred + + /** + * Get match is used for function resolution; it indicates an exact match, coercion, or no match. + * + * Rules + * 1. If arg matches the parameter exactly, return arg. + * 2. If arg can be coerced to the parameter, return the coercion type. + * 3. If arg does NOT match the parameter, return null. + */ + public fun getMatch(arg: PType): PType? { + if (dynamic || arg in type) { + return arg // exact match + } + if (arg.kind == DYNAMIC || SqlTypes.isAssignable(arg, type.preferred)) { + return type.preferred + } + return null + } + + public companion object { + + /** + * Create a dynamic [Parameter]. + */ + @JvmStatic + public fun dynamic(name: String): Parameter = Parameter(name, PType.dynamic()) + + /** + * Create a character string [Parameter]. + */ + @JvmStatic + public fun text(name: String): Parameter = Parameter(name, SqlTypeFamily.TEXT, false) + + /** + * Create a numeric [Parameter]. + */ + @JvmStatic + public fun numeric(name: String): Parameter = Parameter(name, SqlTypeFamily.NUMERIC, false) + + /** + * Create a collection [Parameter]. + */ + @JvmStatic + public fun collection(name: String): Parameter = Parameter(name, SqlTypeFamily.COLLECTION, false) + } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAny.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAny.kt index d8948bfffe..cadee2aaa3 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAny.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAny.kt @@ -8,14 +8,14 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorAnySome import org.partiql.types.PType -internal val Agg_ANY__BOOL__BOOL = Aggregation.standard( +internal val Agg_ANY__BOOL__BOOL = Aggregation.static( name = "any", returns = PType.bool(), parameters = arrayOf(Parameter("value", PType.bool())), accumulator = ::AccumulatorAnySome ) -internal val Agg_ANY__ANY__BOOL = Aggregation.standard( +internal val Agg_ANY__ANY__BOOL = Aggregation.static( name = "any", returns = PType.bool(), parameters = arrayOf(Parameter("value", PType.dynamic())), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAvg.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAvg.kt index 80f0fc8757..8625193d77 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAvg.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggAvg.kt @@ -8,21 +8,21 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorAvg import org.partiql.types.PType -internal val Agg_AVG__INT8__INT8 = Aggregation.standard( +internal val Agg_AVG__INT8__INT8 = Aggregation.static( name = "avg", returns = PType.decimal(), parameters = arrayOf(Parameter("value", PType.tinyint())), accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__INT16__INT16 = Aggregation.standard( +internal val Agg_AVG__INT16__INT16 = Aggregation.static( name = "avg", returns = PType.decimal(), parameters = arrayOf(Parameter("value", PType.smallint())), accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__INT32__INT32 = Aggregation.standard( +internal val Agg_AVG__INT32__INT32 = Aggregation.static( name = "avg", returns = PType.decimal(), @@ -32,7 +32,7 @@ internal val Agg_AVG__INT32__INT32 = Aggregation.standard( accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__INT64__INT64 = Aggregation.standard( +internal val Agg_AVG__INT64__INT64 = Aggregation.static( name = "avg", returns = PType.decimal(), @@ -42,7 +42,7 @@ internal val Agg_AVG__INT64__INT64 = Aggregation.standard( accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__INT__INT = Aggregation.standard( +internal val Agg_AVG__INT__INT = Aggregation.static( name = "avg", returns = PType.decimal(), @@ -52,7 +52,7 @@ internal val Agg_AVG__INT__INT = Aggregation.standard( accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standard( +internal val Agg_AVG__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.static( name = "avg", returns = PType.decimal(), @@ -62,7 +62,7 @@ internal val Agg_AVG__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standar accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__FLOAT32__FLOAT32 = Aggregation.standard( +internal val Agg_AVG__FLOAT32__FLOAT32 = Aggregation.static( name = "avg", returns = PType.real(), @@ -72,7 +72,7 @@ internal val Agg_AVG__FLOAT32__FLOAT32 = Aggregation.standard( accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__FLOAT64__FLOAT64 = Aggregation.standard( +internal val Agg_AVG__FLOAT64__FLOAT64 = Aggregation.static( name = "avg", returns = PType.doublePrecision(), @@ -82,7 +82,7 @@ internal val Agg_AVG__FLOAT64__FLOAT64 = Aggregation.standard( accumulator = ::AccumulatorAvg, ) -internal val Agg_AVG__ANY__ANY = Aggregation.standard( +internal val Agg_AVG__ANY__ANY = Aggregation.static( name = "avg", returns = PType.decimal(), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggCount.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggCount.kt index 35ceca419f..50abf38616 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggCount.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggCount.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorCount import org.partiql.types.PType -internal val Agg_COUNT__ANY__INT64 = Aggregation.standard( +internal val Agg_COUNT__ANY__INT64 = Aggregation.static( name = "count", returns = PType.bigint(), parameters = arrayOf(Parameter("value", PType.dynamic())), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggEvery.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggEvery.kt index 46f8a39736..6973374d05 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggEvery.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggEvery.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorEvery import org.partiql.types.PType -internal val Agg_EVERY__BOOL__BOOL = Aggregation.standard( +internal val Agg_EVERY__BOOL__BOOL = Aggregation.static( name = "every", returns = PType.bool(), parameters = arrayOf( @@ -17,7 +17,7 @@ internal val Agg_EVERY__BOOL__BOOL = Aggregation.standard( accumulator = ::AccumulatorEvery, ) -internal val Agg_EVERY__ANY__BOOL = Aggregation.standard( +internal val Agg_EVERY__ANY__BOOL = Aggregation.static( name = "every", returns = PType.bool(), parameters = arrayOf( diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggGroupAs.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggGroupAs.kt index f00d97c61b..90630e8cff 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggGroupAs.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggGroupAs.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorGroupAs import org.partiql.types.PType -internal val Agg_GROUP_AS__ANY__ANY = Aggregation.standard( +internal val Agg_GROUP_AS__ANY__ANY = Aggregation.static( name = "group_as", returns = PType.dynamic(), parameters = arrayOf( diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMax.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMax.kt index 0d32bf0d87..2832b0a607 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMax.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMax.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorMax import org.partiql.types.PType -internal val Agg_MAX__INT8__INT8 = Aggregation.standard( +internal val Agg_MAX__INT8__INT8 = Aggregation.static( name = "max", returns = PType.tinyint(), @@ -18,7 +18,7 @@ internal val Agg_MAX__INT8__INT8 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__INT16__INT16 = Aggregation.standard( +internal val Agg_MAX__INT16__INT16 = Aggregation.static( name = "max", returns = PType.smallint(), @@ -28,7 +28,7 @@ internal val Agg_MAX__INT16__INT16 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__INT32__INT32 = Aggregation.standard( +internal val Agg_MAX__INT32__INT32 = Aggregation.static( name = "max", returns = PType.integer(), @@ -38,7 +38,7 @@ internal val Agg_MAX__INT32__INT32 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__INT64__INT64 = Aggregation.standard( +internal val Agg_MAX__INT64__INT64 = Aggregation.static( name = "max", returns = PType.bigint(), @@ -48,7 +48,7 @@ internal val Agg_MAX__INT64__INT64 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__INT__INT = Aggregation.standard( +internal val Agg_MAX__INT__INT = Aggregation.static( name = "max", returns = PType.numeric(), @@ -58,7 +58,7 @@ internal val Agg_MAX__INT__INT = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standard( +internal val Agg_MAX__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.static( name = "max", returns = PType.decimal(), @@ -69,7 +69,7 @@ internal val Agg_MAX__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standar accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__FLOAT32__FLOAT32 = Aggregation.standard( +internal val Agg_MAX__FLOAT32__FLOAT32 = Aggregation.static( name = "max", returns = PType.real(), @@ -79,7 +79,7 @@ internal val Agg_MAX__FLOAT32__FLOAT32 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__FLOAT64__FLOAT64 = Aggregation.standard( +internal val Agg_MAX__FLOAT64__FLOAT64 = Aggregation.static( name = "max", returns = PType.doublePrecision(), @@ -89,7 +89,7 @@ internal val Agg_MAX__FLOAT64__FLOAT64 = Aggregation.standard( accumulator = ::AccumulatorMax, ) -internal val Agg_MAX__ANY__ANY = Aggregation.standard( +internal val Agg_MAX__ANY__ANY = Aggregation.static( name = "max", returns = PType.dynamic(), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMin.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMin.kt index 2e9c0c2270..df1aba810c 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMin.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggMin.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorMin import org.partiql.types.PType -internal val Agg_MIN__INT8__INT8 = Aggregation.standard( +internal val Agg_MIN__INT8__INT8 = Aggregation.static( name = "min", returns = PType.tinyint(), parameters = arrayOf( @@ -17,7 +17,7 @@ internal val Agg_MIN__INT8__INT8 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__INT16__INT16 = Aggregation.standard( +internal val Agg_MIN__INT16__INT16 = Aggregation.static( name = "min", returns = PType.smallint(), parameters = arrayOf( @@ -26,7 +26,7 @@ internal val Agg_MIN__INT16__INT16 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__INT32__INT32 = Aggregation.standard( +internal val Agg_MIN__INT32__INT32 = Aggregation.static( name = "min", returns = PType.integer(), parameters = arrayOf( @@ -35,7 +35,7 @@ internal val Agg_MIN__INT32__INT32 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__INT64__INT64 = Aggregation.standard( +internal val Agg_MIN__INT64__INT64 = Aggregation.static( name = "min", returns = PType.bigint(), parameters = arrayOf( @@ -44,7 +44,7 @@ internal val Agg_MIN__INT64__INT64 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__INT__INT = Aggregation.standard( +internal val Agg_MIN__INT__INT = Aggregation.static( name = "min", returns = PType.numeric(), parameters = arrayOf( @@ -53,7 +53,7 @@ internal val Agg_MIN__INT__INT = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standard( +internal val Agg_MIN__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.static( name = "min", returns = PType.decimal(), parameters = arrayOf( @@ -62,7 +62,7 @@ internal val Agg_MIN__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standar accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__FLOAT32__FLOAT32 = Aggregation.standard( +internal val Agg_MIN__FLOAT32__FLOAT32 = Aggregation.static( name = "min", returns = PType.real(), parameters = arrayOf( @@ -71,7 +71,7 @@ internal val Agg_MIN__FLOAT32__FLOAT32 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__FLOAT64__FLOAT64 = Aggregation.standard( +internal val Agg_MIN__FLOAT64__FLOAT64 = Aggregation.static( name = "min", returns = PType.doublePrecision(), parameters = arrayOf( @@ -80,7 +80,7 @@ internal val Agg_MIN__FLOAT64__FLOAT64 = Aggregation.standard( accumulator = ::AccumulatorMin, ) -internal val Agg_MIN__ANY__ANY = Aggregation.standard( +internal val Agg_MIN__ANY__ANY = Aggregation.static( name = "min", returns = PType.dynamic(), parameters = arrayOf( diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSome.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSome.kt index 815370d000..6b28cfaee1 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSome.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSome.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorAnySome import org.partiql.types.PType -internal val Agg_SOME__BOOL__BOOL = Aggregation.standard( +internal val Agg_SOME__BOOL__BOOL = Aggregation.static( name = "some", returns = PType.bool(), @@ -18,7 +18,7 @@ internal val Agg_SOME__BOOL__BOOL = Aggregation.standard( accumulator = ::AccumulatorAnySome, ) -internal val Agg_SOME__ANY__BOOL = Aggregation.standard( +internal val Agg_SOME__ANY__BOOL = Aggregation.static( name = "some", returns = PType.bool(), diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSum.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSum.kt index 1360fb3608..8e6ec801eb 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSum.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/builtins/AggSum.kt @@ -8,7 +8,7 @@ import org.partiql.spi.function.Parameter import org.partiql.spi.function.builtins.internal.AccumulatorSum import org.partiql.types.PType -internal val Agg_SUM__INT8__INT8 = Aggregation.standard( +internal val Agg_SUM__INT8__INT8 = Aggregation.static( name = "sum", returns = PType.tinyint(), parameters = arrayOf( @@ -17,7 +17,7 @@ internal val Agg_SUM__INT8__INT8 = Aggregation.standard( accumulator = { AccumulatorSum(PType.tinyint()) }, ) -internal val Agg_SUM__INT16__INT16 = Aggregation.standard( +internal val Agg_SUM__INT16__INT16 = Aggregation.static( name = "sum", returns = PType.smallint(), parameters = arrayOf( @@ -26,7 +26,7 @@ internal val Agg_SUM__INT16__INT16 = Aggregation.standard( accumulator = { AccumulatorSum(PType.smallint()) }, ) -internal val Agg_SUM__INT32__INT32 = Aggregation.standard( +internal val Agg_SUM__INT32__INT32 = Aggregation.static( name = "sum", returns = PType.integer(), parameters = arrayOf( @@ -35,7 +35,7 @@ internal val Agg_SUM__INT32__INT32 = Aggregation.standard( accumulator = { AccumulatorSum(PType.integer()) }, ) -internal val Agg_SUM__INT64__INT64 = Aggregation.standard( +internal val Agg_SUM__INT64__INT64 = Aggregation.static( name = "sum", returns = PType.bigint(), parameters = arrayOf( @@ -44,7 +44,7 @@ internal val Agg_SUM__INT64__INT64 = Aggregation.standard( accumulator = { AccumulatorSum(PType.bigint()) }, ) -internal val Agg_SUM__INT__INT = Aggregation.standard( +internal val Agg_SUM__INT__INT = Aggregation.static( name = "sum", returns = PType.numeric(), parameters = arrayOf( @@ -53,7 +53,7 @@ internal val Agg_SUM__INT__INT = Aggregation.standard( accumulator = { AccumulatorSum(PType.numeric()) }, ) -internal val Agg_SUM__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standard( +internal val Agg_SUM__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.static( name = "sum", returns = PType.decimal(), parameters = arrayOf( @@ -62,7 +62,7 @@ internal val Agg_SUM__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY = Aggregation.standar accumulator = { AccumulatorSum(PType.decimal()) }, ) -internal val Agg_SUM__FLOAT32__FLOAT32 = Aggregation.standard( +internal val Agg_SUM__FLOAT32__FLOAT32 = Aggregation.static( name = "sum", returns = PType.real(), parameters = arrayOf( @@ -71,7 +71,7 @@ internal val Agg_SUM__FLOAT32__FLOAT32 = Aggregation.standard( accumulator = { AccumulatorSum(PType.real()) }, ) -internal val Agg_SUM__FLOAT64__FLOAT64 = Aggregation.standard( +internal val Agg_SUM__FLOAT64__FLOAT64 = Aggregation.static( name = "sum", returns = PType.doublePrecision(), parameters = arrayOf( @@ -80,7 +80,7 @@ internal val Agg_SUM__FLOAT64__FLOAT64 = Aggregation.standard( accumulator = { AccumulatorSum(PType.doublePrecision()) }, ) -internal val Agg_SUM__ANY__ANY = Aggregation.standard( +internal val Agg_SUM__ANY__ANY = Aggregation.static( name = "sum", returns = PType.dynamic(), parameters = arrayOf( diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypeFamily.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypeFamily.kt new file mode 100644 index 0000000000..a421f02cba --- /dev/null +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypeFamily.kt @@ -0,0 +1,79 @@ +package org.partiql.spi.internal + +import org.partiql.types.PType +import org.partiql.types.PType.Kind + +/** + * A basic "set" representation for type categorization; perhaps we optimize later.. + * + * From Calcite, + * > SqlTypeFamily provides SQL type categorization. + * > Primary Families + * > CHARACTER, + * > BINARY, + * > NUMERIC, + * > DATE, + * > TIME, + * > TIMESTAMP, + * > BOOLEAN, + * > https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java + * + * + * From Postgres, + * > Data types are divided into several basic type categories, including boolean, numeric, string, bitstring, datetime, + * > timespan, geometric, network, and user-defined. Within each category there can be one or more preferred types, + * > which are preferred when there is a choice of possible types. + */ +internal class SqlTypeFamily private constructor( + @JvmField val preferred: PType, + @JvmField val members: Set, +) { + + /** + * Constructor a singleton [SqlTypeFamily]. + */ + constructor(preferred: PType) : this(preferred, setOf(preferred.kind)) + + operator fun contains(type: PType) = type.kind in members + + companion object { + + @JvmStatic + val TEXT = SqlTypeFamily( + preferred = PType.string(), + members = setOf( + Kind.CHAR, + Kind.VARCHAR, + Kind.STRING, + Kind.SYMBOL, + Kind.CLOB, + ) + ) + + @JvmStatic + val COLLECTION = SqlTypeFamily( + preferred = PType.bag(), + members = setOf( + Kind.ARRAY, + Kind.SEXP, + Kind.BAG + ) + ) + + @JvmStatic + val NUMERIC = SqlTypeFamily( + preferred = PType.decimal(), + members = setOf( + Kind.TINYINT, + Kind.SMALLINT, + Kind.INTEGER, + Kind.BIGINT, + Kind.NUMERIC, + Kind.REAL, + Kind.DOUBLE, + Kind.DECIMAL, + Kind.DECIMAL_ARBITRARY + ) + ) + } +} diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypes.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypes.kt index 4c676b182d..68818c85d9 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypes.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/internal/SqlTypes.kt @@ -2,249 +2,167 @@ package org.partiql.spi.internal import org.partiql.types.Field import org.partiql.types.PType +import org.partiql.types.PType.Kind /** - * A factory for single-source of truth for type creations — DO NOT CREATE PTYPE DIRECTLY. + * Important SQL Definitions: + * - assignable: The characteristic of a data type that permits a value of that data type to be + * assigned to a site of a specified data type. * - * This allows us to raise an interface if we need custom type factories; for now just use defaults with static methods. + * This came from the internal planner Coercion.kt + * + * TODO consider modeling with enums and/or additional optimizations. */ internal object SqlTypes { - private const val MAX_SIZE = Int.MAX_VALUE - - // - // DYNAMIC - // - - @JvmStatic - fun dynamic(): PType = PType.dynamic() - - // - // BOOLEAN - // - - @JvmStatic - fun bool(): PType = PType.bool() - - // - // NUMERIC - // - - @JvmStatic - fun tinyint(): PType = PType.tinyint() - - @JvmStatic - fun smallint(): PType = PType.smallint() - - @JvmStatic - fun int(): PType = PType.integer() + /** + * Remaining coercions from SQL:1999: + * - Values corresponding to the binary data type are mutually assignable. + * - Values corresponding to the data types BIT and BIT VARYING are always mutually comparable and + * are mutually assignable. + * - Values of type interval are mutually assignable only if the source and target of the assignment are + * both year-month intervals or if they are both day-time intervals. + * - Values corresponding to user-defined types are discussed in Subclause 4.8.4, ‘‘User-defined type + * comparison and assignment’’. + */ + fun isAssignable(input: PType, target: PType): Boolean { + return areAssignableNumberTypes(input, target) || + areAssignableTextTypes(input, target) || + areAssignableBooleanTypes(input, target) || + areAssignableDateTimeTypes(input, target) || + areAssignableCollectionTypes(input, target) || + areAssignableStructuralTypes(input, target) || + areAssignableDynamicTypes(target) + } - @JvmStatic - fun bigint(): PType = PType.bigint() + /** + * NOT specified by SQL:1999. We assume that we can coerce a collection of one type to another if the subtype + * of each collection is assignable. + */ + private fun areAssignableCollectionTypes(input: PType, target: PType): Boolean { + return input in SqlTypeFamily.COLLECTION && target in SqlTypeFamily.COLLECTION && isAssignable(input.typeParameter, target.typeParameter) + } /** - * 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 + * NOT specified by SQL:1999. We assume that we can statically coerce anything to DYNAMIC. However, note that + * CAST( AS DYNAMIC) is NEVER inserted. We check for the use of DYNAMIC at function resolution. This is merely + * for the [PType.getTypeParameter] and [PType.getFields] */ - @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.decimal(precision, scale) - precision != null -> PType.decimal(precision, 0) - else -> PType.numeric() - } + private fun areAssignableDynamicTypes(target: PType): Boolean { + return target.kind == Kind.DYNAMIC } /** - * 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. + * NOT completely specified by SQL:1999. * - * @param precision Defaults to inf. - * @param scale Defaults to 0 when precision is given, otherwise inf. - * @return + * From SQL:1999: + * ``` + * Values corresponding to row types are mutually assignable if and only if both have the same degree + * and every field in one row type is mutually assignable to the field in the same ordinal position of + * the other row type. Values corresponding to row types are mutually comparable if and only if both + * have the same degree and every field in one row type is mutually comparable to the field in the + * same ordinal position of the other row type. + * ``` */ - @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.") - } + private fun areAssignableStructuralTypes(input: PType, target: PType): Boolean { return when { - precision != null && scale != null -> PType.decimal(precision, scale) - precision != null -> PType.decimal(precision, 0) - else -> PType.decimal() + input.kind == Kind.ROW && target.kind == Kind.ROW -> fieldsAreAssignable(input.fields.toList(), target.fields!!.toList()) + input.kind == Kind.STRUCT && target.kind == Kind.ROW -> true + input.kind == Kind.ROW && target.kind == Kind.STRUCT -> true + input.kind == Kind.STRUCT && target.kind == Kind.STRUCT -> true + else -> false } } - @JvmStatic - fun real(): PType = PType.real() - - @JvmStatic - fun double(): PType = PType.doublePrecision() - - // - // CHARACTER STRINGS - // - - @JvmStatic - fun char(length: Int? = null): PType = PType.character(length ?: 1) - - @JvmStatic - fun varchar(length: Int? = null): PType = PType.varchar(length ?: MAX_SIZE) - - @JvmStatic - fun string(): PType = PType.string() - - @JvmStatic - fun clob(length: Int? = null) = PType.clob(length ?: MAX_SIZE) - - // - // BIT STRINGS - // - - @JvmStatic - fun blob(length: Int? = null) = PType.blob(length ?: MAX_SIZE) - - // - // DATETIME - // - - @JvmStatic - fun date(): PType = TODO() - - @JvmStatic - fun time(precision: Int? = null): PType = PType.time(precision ?: 6) - - @JvmStatic - fun timez(precision: Int? = null): PType = PType.timez(precision ?: 6) - - @JvmStatic - fun timestamp(precision: Int? = null): PType = PType.time(precision ?: 6) - - @JvmStatic - fun timestampz(precision: Int? = null): PType = PType.timestampz(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.array() - else -> PType.array(element) + private fun fieldsAreAssignable(input: List, target: List): Boolean { + if (input.size != target.size) { return false } + val iIter = input.iterator() + val tIter = target.iterator() + while (iIter.hasNext()) { + val iField = iIter.next() + val tField = tIter.next() + if (!isAssignable(iField.type, tField.type)) { + return false + } } + return true } - @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.bag() - else -> PType.bag(element) + /** + * This is a PartiQL extension. We assume that structs/rows with the same field names may be assignable + * if all names match AND types are assignable. + */ + private fun namedFieldsAreAssignableUnordered(input: List, target: List): Boolean { + if (input.size != target.size) { return false } + val inputSorted = input.sortedBy { it.name } + val targetSorted = target.sortedBy { it.name } + val iIter = inputSorted.iterator() + val tIter = targetSorted.iterator() + while (iIter.hasNext()) { + val iField = iIter.next() + val tField = tIter.next() + if (iField.name != tField.name) { + return false + } + if (!isAssignable(iField.type, tField.type)) { + return false + } } + return true } - // - // STRUCTURAL - // - - @JvmStatic - fun struct(): PType = PType.struct() + /** + * From SQL:1999: + * ``` + * Values of the data types NUMERIC, DECIMAL, INTEGER, SMALLINT, FLOAT, REAL, and + * DOUBLE PRECISION are numbers and are all mutually comparable and mutually assignable. + * ``` + */ + private fun areAssignableNumberTypes(input: PType, target: PType): Boolean { + return input in SqlTypeFamily.NUMERIC && target in SqlTypeFamily.NUMERIC + } - @JvmStatic - fun row(fields: List): PType = PType.row(fields) + /** + * From SQL:1999: + * ``` + * Values corresponding to the data type boolean are always mutually comparable and are mutually + * assignable. + * ``` + */ + private fun areAssignableBooleanTypes(input: PType, target: PType): Boolean { + return input.kind == Kind.BOOL && target.kind == Kind.BOOL + } - // /** - // * 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.Tuple -> struct() - // is Type.Struct -> struct() - // is Type.Custom -> TODO("Custom type not supported ") - // } + /** + * From SQL:1999: + * ``` + * Values corresponding to the data types CHARACTER, CHARACTER VARYING, and CHARACTER + * LARGE OBJECT are mutually assignable if and only if they are taken from the same character + * repertoire. (For this implementation, we shall assume that all text types share the same + * character repertoire.) + * ``` + */ + private fun areAssignableTextTypes(input: PType, target: PType): Boolean { + return input in SqlTypeFamily.TEXT && target in SqlTypeFamily.TEXT + } - @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.INTEGER -> int() - PType.Kind.BIGINT -> bigint() - PType.Kind.NUMERIC -> numeric() - PType.Kind.DECIMAL, PType.Kind.DECIMAL_ARBITRARY -> decimal() - PType.Kind.REAL -> real() - PType.Kind.DOUBLE -> double() - PType.Kind.CHAR -> char() - PType.Kind.VARCHAR -> varchar() - PType.Kind.STRING -> string() - PType.Kind.SYMBOL -> { - // TODO will we continue supporting symbol? - PType.symbol() - } - PType.Kind.BLOB -> blob() - PType.Kind.CLOB -> clob() - PType.Kind.DATE -> date() - PType.Kind.TIMEZ -> timez() - PType.Kind.TIME -> time() - PType.Kind.TIMESTAMPZ -> timestampz() - PType.Kind.TIMESTAMP -> timestamp() - PType.Kind.BAG -> bag() - PType.Kind.ARRAY -> array() - PType.Kind.ROW -> error("Cannot create a ROW from Kind") - PType.Kind.SEXP -> { - // TODO will we continue supporting sexp? - PType.sexp() + /** + * From SQL:1999: + * ``` + * Values of type datetime are mutually assignable only if the source and target of the assignment are + * both of type DATE, or both of type TIME (regardless whether WITH TIME ZONE or WITHOUT + * TIME ZONE is specified or implicit), or both of type TIMESTAMP (regardless whether WITH TIME + * ZONE or WITHOUT TIME ZONE is specified or implicit) + * ``` + */ + private fun areAssignableDateTimeTypes(input: PType, target: PType): Boolean { + val i = input.kind + val t = target.kind + return when { + i == Kind.DATE && t == Kind.DATE -> true + (i == Kind.TIMEZ || i == Kind.TIME) && (t == Kind.TIMEZ || t == Kind.TIME) -> true + (i == Kind.TIMESTAMPZ || i == Kind.TIMESTAMP) && (t == Kind.TIMESTAMPZ || t == Kind.TIMESTAMP) -> true + else -> false } - PType.Kind.STRUCT -> struct() - PType.Kind.UNKNOWN -> PType.unknown() } }