diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt index 73ad6deb2e..1c08a195e4 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeExtensions.kt @@ -71,10 +71,10 @@ internal fun StaticType.cast(targetType: StaticType): StaticType { // union source types, recursively process them when (this) { - is AnyType -> return AnyOfType(this.toAnyOfType().types.map { it.cast(targetType) }.toSet()).flatten() + is AnyType -> return StaticType.unionOf(this.toAnyOfType().types.map { it.cast(targetType) }.toSet()).flatten() is AnyOfType -> return when (val flattened = this.flatten()) { is SingleType, is AnyType -> flattened.cast(targetType) - is AnyOfType -> AnyOfType(flattened.types.map { it.cast(targetType) }.toSet()).flatten() + is AnyOfType -> StaticType.unionOf(flattened.types.map { it.cast(targetType) }.toSet()).flatten() } } @@ -221,7 +221,7 @@ internal fun StaticType.cast(targetType: StaticType): StaticType { */ internal fun StaticType.filterNullMissing(): StaticType = when (this) { - is AnyOfType -> AnyOfType(this.types.filter { !it.isNullOrMissing() }.toSet()).flatten() + is AnyOfType -> StaticType.unionOf(this.types.filter { !it.isNullOrMissing() }.toSet()).flatten() else -> this } diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt index 34dc6822be..17234c2267 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/builtins/ScalarBuiltinsSql.kt @@ -50,7 +50,6 @@ import org.partiql.lang.util.isPosInf import org.partiql.lang.util.ln import org.partiql.lang.util.power import org.partiql.lang.util.squareRoot -import org.partiql.types.AnyOfType import org.partiql.types.StaticType import org.partiql.types.StaticType.Companion.unionOf import java.math.BigDecimal @@ -342,7 +341,7 @@ internal object ExprFunctionUpper : ExprFunction { override val signature = FunctionSignature( name = "upper", - requiredParameters = listOf(AnyOfType(setOf(StaticType.STRING, StaticType.SYMBOL))), + requiredParameters = listOf(StaticType.unionOf(setOf(StaticType.STRING, StaticType.SYMBOL))), returnType = StaticType.STRING ) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt index 8cba006426..4271002e42 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/eval/visitors/StaticTypeInferenceVisitorTransform.kt @@ -477,9 +477,9 @@ internal class StaticTypeInferenceVisitorTransform( // where the other arg is an incompatible type (not an unknown or bool), the result type is MISSING. args.any { it == StaticType.BOOL } -> when { // If other argument is missing, then return union(bool, missing) - args.any { it is MissingType } -> AnyOfType(setOf(StaticType.MISSING, StaticType.BOOL)) + args.any { it is MissingType } -> StaticType.unionOf(setOf(StaticType.MISSING, StaticType.BOOL)) // If other argument is null, then return union(bool, null) - args.any { it is NullType } -> AnyOfType(setOf(StaticType.NULL, StaticType.BOOL)) + args.any { it is NullType } -> StaticType.unionOf(setOf(StaticType.NULL, StaticType.BOOL)) // If other type is anything other than null or missing, then it is an error case else -> StaticType.MISSING } @@ -1021,7 +1021,7 @@ internal class StaticTypeInferenceVisitorTransform( StaticType.NULL.takeIf { actualType.allTypes.any { it is NullType } } ) } - AnyOfType(finalReturnTypes.toSet()).flatten() + StaticType.unionOf(finalReturnTypes.toSet()).flatten() } else { // otherwise, has an invalid arg type and errors. continuation type of [FunctionSignature.returnType] signature.returnType @@ -1093,7 +1093,7 @@ internal class StaticTypeInferenceVisitorTransform( values: List, compute: (StaticType) -> StaticType ): PartiqlAst.Expr { - val valuesTypes = AnyOfType(values.getStaticType().toSet()).flatten() + val valuesTypes = StaticType.unionOf(values.getStaticType().toSet()).flatten() val inferredType = compute(valuesTypes) return expr.withStaticType(inferredType) } @@ -1201,7 +1201,7 @@ internal class StaticTypeInferenceVisitorTransform( } val possibleTypes = thenExprsTypes + elseExprType - return AnyOfType(possibleTypes.toSet()).flatten() + return StaticType.unionOf(possibleTypes.toSet()).flatten() } // PIG ast Types => CanCast, CanLosslessCast, IsType, ExprCast @@ -1319,7 +1319,7 @@ internal class StaticTypeInferenceVisitorTransform( is BagType -> fromSourceType.elementType is ListType -> fromSourceType.elementType is AnyType -> StaticType.ANY - is AnyOfType -> AnyOfType(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) + is AnyOfType -> StaticType.unionOf(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) // All the other types coerce into a bag of themselves (including null/missing/sexp). else -> fromSourceType } @@ -1419,13 +1419,13 @@ internal class StaticTypeInferenceVisitorTransform( private fun getUnpivotValueType(fromSourceType: StaticType): StaticType = when (fromSourceType) { is StructType -> if (fromSourceType.contentClosed) { - AnyOfType(fromSourceType.fields.map { it.value }.toSet()).flatten() + StaticType.unionOf(fromSourceType.fields.map { it.value }.toSet()).flatten() } else { // Content is open, so value can be of any type StaticType.ANY } is AnyType -> StaticType.ANY - is AnyOfType -> AnyOfType(fromSourceType.types.map { getUnpivotValueType(it) }.toSet()) + is AnyOfType -> StaticType.unionOf(fromSourceType.types.map { getUnpivotValueType(it) }.toSet()) // All the other types coerce into a struct of themselves with synthetic key names else -> fromSourceType } @@ -1506,7 +1506,7 @@ internal class StaticTypeInferenceVisitorTransform( StaticType.ANY } else { val staticTypes = prevTypes.map { inferPathComponentExprType(it, currentPathComponent) } - AnyOfType(staticTypes.toSet()).flatten() + StaticType.unionOf(staticTypes.toSet()).flatten() } } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt index a814cc7a35..05f31e1b94 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt @@ -365,7 +365,7 @@ internal object PlanTransform : PlanBaseVisitor() { org.partiql.plan.Agg( FunctionSignature.Aggregation( "UNKNOWN_AGG::$name", - returns = PartiQLValueType.MISSING, + returns = PartiQLValueType.ANY, parameters = emptyList() ) ) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt index 73cba006fc..9491577de1 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RelConverter.kt @@ -294,11 +294,11 @@ internal object RelConverter { } private fun convertProjectionItem(item: Select.Project.Item) = when (item) { - is Select.Project.Item.All -> convertProjectItemAll(item) + is Select.Project.Item.All -> convertProjectItemAll() is Select.Project.Item.Expression -> convertProjectItemRex(item) } - private fun convertProjectItemAll(item: Select.Project.Item.All): Pair { + private fun convertProjectItemAll(): Pair { throw IllegalArgumentException("AST not normalized") } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 8c30083c11..ec696d417c 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -42,7 +42,6 @@ import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpSubquery import org.partiql.planner.internal.ir.rexOpTupleUnion import org.partiql.planner.internal.ir.rexOpVarUnresolved -import org.partiql.planner.internal.typer.toNonNullStaticType import org.partiql.planner.internal.typer.toStaticType import org.partiql.types.StaticType import org.partiql.types.TimeType @@ -70,10 +69,7 @@ internal object RexConverter { throw IllegalArgumentException("unsupported rex $node") override fun visitExprLit(node: Expr.Lit, context: Env): Rex { - val type = when (node.value.isNull) { - true -> node.value.type.toStaticType() - else -> node.value.type.toNonNullStaticType() - } + val type = node.value.type.toStaticType() val op = rexOpLit(node.value) return rex(type, op) } @@ -82,10 +78,7 @@ internal object RexConverter { val value = PartiQLValueIonReaderBuilder .standard().build(node.value).read() - val type = when (value.isNull) { - true -> value.type.toStaticType() - else -> value.type.toNonNullStaticType() - } + val type = value.type.toStaticType() return rex(type, rexOpLit(value)) } @@ -287,7 +280,7 @@ internal object RexConverter { }.toMutableList() val defaultRex = when (val default = node.default) { - null -> rex(type = StaticType.NULL, op = rexOpLit(value = nullValue())) + null -> rex(type = StaticType.ANY, op = rexOpLit(value = nullValue())) else -> visitExprCoerce(default, context) } val op = rexOpCase(branches = branches, default = defaultRex) @@ -528,8 +521,8 @@ internal object RexConverter { val type = node.asType val arg0 = visitExprCoerce(node.value, ctx) return when (type) { - is Type.NullType -> rex(StaticType.NULL, call("cast_null", arg0)) - is Type.Missing -> rex(StaticType.MISSING, call("cast_missing", arg0)) + is Type.NullType -> error("Cannot cast any value to NULL") + is Type.Missing -> error("Cannot cast any value to MISSING") is Type.Bool -> rex(StaticType.BOOL, call("cast_bool", arg0)) is Type.Tinyint -> TODO("Static Type does not have TINYINT type") is Type.Smallint, is Type.Int2 -> rex(StaticType.INT2, call("cast_int16", arg0)) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt index 9ae72cc6ad..62cc6fed7a 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt @@ -2,8 +2,7 @@ package org.partiql.planner.internal.typer -import org.partiql.types.MissingType -import org.partiql.types.NullType +import org.partiql.planner.internal.ir.Rex import org.partiql.types.StaticType import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType @@ -27,8 +26,6 @@ import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.INT8 import org.partiql.value.PartiQLValueType.INTERVAL import org.partiql.value.PartiQLValueType.LIST -import org.partiql.value.PartiQLValueType.MISSING -import org.partiql.value.PartiQLValueType.NULL import org.partiql.value.PartiQLValueType.SEXP import org.partiql.value.PartiQLValueType.STRING import org.partiql.value.PartiQLValueType.STRUCT @@ -57,51 +54,49 @@ internal class DynamicTyper { private var supertype: PartiQLValueType? = null private var args = mutableListOf() - private var nullable = false - private var missable = false private val types = mutableSetOf() /** - * This primarily unpacks a StaticType because of NULL, MISSING. - * - * - T - * - NULL - * - MISSING - * - (NULL) - * - (MISSING) - * - (T..) - * - (T..|NULL) - * - (T..|MISSING) - * - (T..|NULL|MISSING) - * - (NULL|MISSING) - * + * When a literal null or missing value is present in the query, its type is unknown. Therefore, its type must be + * inferred. This function ignores literal null/missing values, yet adds their indices to know how to return the + * mapping. + */ + fun accumulateUnknown() { + args.add(ANY) + } + + fun accumulate(rex: Rex) { + when (rex.isLiteralAbsent()) { + true -> accumulateUnknown() + false -> accumulate(rex.type) + } + } + + private fun Rex.isLiteralAbsent(): Boolean { + val op = this.op + return op is Rex.Op.Lit && (op.value.type == PartiQLValueType.MISSING || op.value.type == PartiQLValueType.NULL) + } + + /** + * This cleans the input type. * @param type */ fun accumulate(type: StaticType) { - val nonAbsentTypes = mutableSetOf() val flatType = type.flatten() if (flatType == StaticType.ANY) { - // Use ANY runtime; do not expand ANY types.add(flatType) args.add(ANY) calculate(ANY) return } - for (t in flatType.allTypes) { - when (t) { - is NullType -> nullable = true - is MissingType -> missable = true - else -> nonAbsentTypes.add(t) - } - } - when (nonAbsentTypes.size) { + val allTypes = flatType.allTypes + when (allTypes.size) { 0 -> { - // Ignore in calculating supertype. - args.add(NULL) + error("This should not have happened.") } 1 -> { // Had single type - val single = nonAbsentTypes.first() + val single = allTypes.first() val singleRuntime = single.toRuntimeType() types.add(single) args.add(singleRuntime) @@ -109,7 +104,7 @@ internal class DynamicTyper { } else -> { // Had a union; use ANY runtime - types.addAll(nonAbsentTypes) + types.addAll(allTypes) args.add(ANY) calculate(ANY) } @@ -124,31 +119,20 @@ internal class DynamicTyper { * @return */ fun mapping(): Pair>?> { - val modifiers = mutableSetOf() - if (nullable) modifiers.add(StaticType.NULL) - if (missable) modifiers.add(StaticType.MISSING) // If at top supertype, then return union of all accumulated types if (supertype == ANY) { - return StaticType.unionOf(types + modifiers).flatten() to null + return StaticType.unionOf(types).flatten() to null } // If a collection, then return union of all accumulated types as these coercion rules are not defined by SQL. if (supertype == STRUCT || supertype == BAG || supertype == LIST || supertype == SEXP) { - return StaticType.unionOf(types + modifiers) to null + return StaticType.unionOf(types) to null } // If not initialized, then return null, missing, or null|missing. - val s = supertype - if (s == null) { - val t = if (modifiers.isEmpty()) StaticType.MISSING else StaticType.unionOf(modifiers).flatten() - return t to null - } + val s = supertype ?: return StaticType.ANY to null // Otherwise, return the supertype along with the coercion mapping - val type = s.toNonNullStaticType() + val type = s.toStaticType() val mapping = args.map { it to s } - return if (modifiers.isEmpty()) { - type to mapping - } else { - StaticType.unionOf(setOf(type) + modifiers).flatten() to mapping - } + return type to mapping } private fun calculate(type: PartiQLValueType) { @@ -163,7 +147,7 @@ internal class DynamicTyper { // Lookup and set the new minimum common supertype supertype = when { type == ANY -> type - type == NULL || type == MISSING || s == type -> return // skip + s == type -> return // skip else -> graph[s][type] ?: ANY // lookup, if missing then go to top. } } @@ -206,8 +190,6 @@ internal class DynamicTyper { graph[type] = arrayOfNulls(N) } graph[ANY] = edges() - graph[NULL] = edges() - graph[MISSING] = edges() graph[BOOL] = edges( BOOL to BOOL ) diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/FnResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/FnResolver.kt index 0cf04f2c59..7ba72f0648 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/FnResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/FnResolver.kt @@ -5,9 +5,6 @@ import org.partiql.planner.internal.ir.Agg import org.partiql.planner.internal.ir.Fn import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rex -import org.partiql.planner.internal.typer.FnResolver.Companion.compareTo -import org.partiql.types.AnyOfType -import org.partiql.types.NullType import org.partiql.types.StaticType import org.partiql.types.function.FunctionParameter import org.partiql.types.function.FunctionSignature @@ -33,8 +30,6 @@ import org.partiql.value.PartiQLValueType.INT64 import org.partiql.value.PartiQLValueType.INT8 import org.partiql.value.PartiQLValueType.INTERVAL import org.partiql.value.PartiQLValueType.LIST -import org.partiql.value.PartiQLValueType.MISSING -import org.partiql.value.PartiQLValueType.NULL import org.partiql.value.PartiQLValueType.SEXP import org.partiql.value.PartiQLValueType.STRING import org.partiql.value.PartiQLValueType.STRUCT @@ -162,12 +157,8 @@ internal class FnResolver(private val header: Header) { */ public fun resolveFn(fn: Fn.Unresolved, args: List): FnMatch { val candidates = lookup(fn) - var canReturnMissing = false val parameterPermutations = buildArgumentPermutations(args.map { it.type }).mapNotNull { argList -> argList.mapIndexed { i, arg -> - if (arg.isMissable()) { - canReturnMissing = true - } // Skip over if we cannot convert type to runtime type. val argType = arg.toRuntimeTypeOrNull() ?: return@mapNotNull null FunctionParameter("arg-$i", argType) @@ -176,12 +167,10 @@ internal class FnResolver(private val header: Header) { val potentialFunctions = parameterPermutations.mapNotNull { parameters -> when (val match = match(candidates, parameters)) { null -> { - canReturnMissing = true null } else -> { - val isMissable = canReturnMissing || isUnsafeCast(match.signature.specific) - FnMatch.Ok(match.signature, match.mapping, isMissable) + FnMatch.Ok(match.signature, match.mapping, true) } } } @@ -190,18 +179,12 @@ internal class FnResolver(private val header: Header) { return when (orderedUniqueFunctions.size) { 0 -> FnMatch.Error(fn.identifier, args, candidates) 1 -> orderedUniqueFunctions.first() - else -> FnMatch.Dynamic(orderedUniqueFunctions, canReturnMissing) + else -> FnMatch.Dynamic(orderedUniqueFunctions, true) } } private fun buildArgumentPermutations(args: List): List> { - val flattenedArgs = args.map { - if (it is AnyOfType) { - it.flatten().allTypes.filter { it !is NullType } - } else { - it.flatten().allTypes - } - } + val flattenedArgs = args.map { it.flatten().allTypes } return buildArgumentPermutations(flattenedArgs, accumulator = emptyList()) } @@ -229,19 +212,13 @@ internal class FnResolver(private val header: Header) { */ public fun resolveAgg(agg: Agg.Unresolved, args: List): FnMatch { val candidates = lookup(agg) - var hadMissingArg = false val parameters = args.mapIndexed { i, arg -> - if (!hadMissingArg && arg.type.isMissable()) { - hadMissingArg = true - } FunctionParameter("arg-$i", arg.type.toRuntimeType()) } - val match = match(candidates, parameters) - return when (match) { + return when (val match = match(candidates, parameters)) { null -> FnMatch.Error(agg.identifier, args, candidates) else -> { - val isMissable = hadMissingArg || isUnsafeCast(match.signature.specific) - FnMatch.Ok(match.signature, match.mapping, isMissable) + FnMatch.Ok(match.signature, match.mapping, true) } } } @@ -290,9 +267,7 @@ internal class FnResolver(private val header: Header) { a.type == p.type -> mapping.add(null) // 2. Match ANY, no coercion needed p.type == ANY -> mapping.add(null) - // 3. Match NULL argument - a.type == NULL -> mapping.add(null) - // 4. Check for a coercion + // 3. Check for a coercion else -> { val coercion = lookupCoercion(a.type, p.type) when (coercion) { @@ -440,8 +415,8 @@ internal class FnResolver(private val header: Header) { // This is not explicitly defined in the PartiQL Specification!! // This does not imply the ability to CAST; this defines function resolution behavior. private val precedence: Map = listOf( - NULL, - MISSING, + PartiQLValueType.NULL, // TODO: Remove + PartiQLValueType.MISSING, // TODO: Remove BOOL, INT8, INT16, diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 3b275d9d39..fb57088069 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -33,7 +33,6 @@ import org.partiql.planner.internal.ir.aggResolved import org.partiql.planner.internal.ir.fnResolved import org.partiql.planner.internal.ir.identifierSymbol import org.partiql.planner.internal.ir.rel -import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.ir.relOpAggregate import org.partiql.planner.internal.ir.relOpAggregateCall import org.partiql.planner.internal.ir.relOpDistinct @@ -81,14 +80,10 @@ import org.partiql.types.BoolType import org.partiql.types.CollectionType import org.partiql.types.IntType import org.partiql.types.ListType -import org.partiql.types.MissingType -import org.partiql.types.NullType import org.partiql.types.SexpType import org.partiql.types.StaticType import org.partiql.types.StaticType.Companion.ANY import org.partiql.types.StaticType.Companion.BOOL -import org.partiql.types.StaticType.Companion.MISSING -import org.partiql.types.StaticType.Companion.NULL import org.partiql.types.StaticType.Companion.STRING import org.partiql.types.StaticType.Companion.unionOf import org.partiql.types.StringType @@ -97,9 +92,9 @@ import org.partiql.types.TupleConstraint import org.partiql.types.function.FunctionSignature import org.partiql.value.BoolValue import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.PartiQLValueType import org.partiql.value.TextValue import org.partiql.value.boolValue -import org.partiql.value.missingValue import org.partiql.value.stringValue /** @@ -301,14 +296,7 @@ internal class PlanTyper( val rhs = visitRel(node.rhs, ctx) // Calculate output schema given JOIN type - val l = lhs.type.schema - val r = rhs.type.schema - val schema = when (node.type) { - Rel.Op.Join.Type.INNER -> l + r - Rel.Op.Join.Type.LEFT -> l + r.pad() - Rel.Op.Join.Type.RIGHT -> l.pad() + r - Rel.Op.Join.Type.FULL -> l.pad() + r.pad() - } + val schema = lhs.type.schema + rhs.type.schema val type = relType(schema, ctx!!.props) // Type the condition on the output schema @@ -467,16 +455,25 @@ internal class PlanTyper( override fun visitRexOpPathIndex(node: Rex.Op.Path.Index, ctx: StaticType?): Rex { val root = visitRex(node.root, node.root.type) val key = visitRex(node.key, node.key.type) - if (key.type !is IntType) { + + // Check Index Type + if (!key.type.mayBeType()) { + handleAlwaysMissing() + return rex(ANY, rexOpErr("Collections must be indexed with integers, found ${key.type}")) + } + + // Check Root Type + if (!root.type.mayBeType() && !root.type.mayBeType()) { handleAlwaysMissing() - return rex(MISSING, rexOpErr("Collections must be indexed with integers, found ${key.type}")) + return rex(ANY, rexOpErr("Only lists and s-expressions can be indexed with integers, found ${root.type}")) } - val elementTypes = root.type.allTypes.map { type -> - val rootType = type as? CollectionType ?: return@map MISSING - if (rootType !is ListType && rootType !is SexpType) { - return@map MISSING + + // Get Element Type + val elementTypes = root.type.allTypes.mapNotNull { type -> + if (type !is ListType && type !is SexpType) { + return@mapNotNull null } - rootType.elementType + (type as CollectionType).elementType }.toSet() val finalType = unionOf(elementTypes).flatten() return rex(finalType.swallowAny(), rexOpPathIndex(root, key)) @@ -487,26 +484,25 @@ internal class PlanTyper( val key = visitRex(node.key, node.key.type) // Check Key Type - val toAddTypes = key.type.allTypes.mapNotNull { keyType -> - when (keyType) { - is StringType -> null - is NullType -> NULL - else -> MISSING - } - } - if (toAddTypes.size == key.type.allTypes.size && toAddTypes.all { it is MissingType }) { + if (!key.type.mayBeType()) { handleAlwaysMissing() - return rex(MISSING, rexOpErr("Expected string but found: ${key.type}")) + return rex(ANY, rexOpErr("Expected string but found: ${key.type}.")) } - val pathTypes = root.type.allTypes.map { type -> - val struct = type as? StructType ?: return@map MISSING + // Check Root Type + if (!root.type.mayBeType()) { + handleAlwaysMissing() + return rex(ANY, rexOpErr("Key lookup may only occur on structs, not ${root.type}.")) + } + // Get Element Type + val elementType = root.type.inferListNotNull { type -> + val struct = type as? StructType ?: return@inferListNotNull null if (key.op is Rex.Op.Lit) { val lit = key.op.value if (lit is TextValue<*> && !lit.isNull) { val id = identifierSymbol(lit.string!!, Identifier.CaseSensitivity.SENSITIVE) - inferStructLookup(struct, id).first + inferStructLookup(struct, id)?.first } else { error("Expected text literal, but got $lit") } @@ -515,20 +511,31 @@ internal class PlanTyper( // we might improve upon this with some constant folding prior to typing ANY } - }.toSet() - val finalType = unionOf(pathTypes + toAddTypes).flatten() - return rex(finalType.swallowAny(), rexOpPathKey(root, key)) + } + if (elementType.isEmpty()) { + handleAlwaysMissing() + return rex(ANY, rexOpPathKey(root, key)) + } + // TODO: SwallowAny should happen by default + return rex(unionOf(elementType).swallowAny(), rexOpPathKey(root, key)) } override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: StaticType?): Rex { val root = visitRex(node.root, node.root.type) - val paths = root.type.allTypes.map { type -> - val struct = type as? StructType ?: return@map rex(MISSING, rexOpLit(missingValue())) + // Check Root Type + if (!root.type.mayBeType()) { + handleAlwaysMissing() + return rex(ANY, rexOpErr("Symbol lookup may only occur on structs, not ${root.type}.")) + } + + // Get Element Types + val paths = root.type.inferRexList { type -> + val struct = type as? StructType ?: return@inferRexList null val (pathType, replacementId) = inferStructLookup( struct, identifierSymbol(node.key, Identifier.CaseSensitivity.INSENSITIVE) - ) + ) ?: return@inferRexList null when (replacementId.caseSensitivity) { Identifier.CaseSensitivity.INSENSITIVE -> rex(pathType, rexOpPathSymbol(root, replacementId.symbol)) Identifier.CaseSensitivity.SENSITIVE -> rex( @@ -537,11 +544,21 @@ internal class PlanTyper( ) } } - val type = unionOf(paths.map { it.type }.toSet()).flatten() + // Determine output type + val type = when (paths.size) { + // Escape early since no inference could be made + 0 -> { + handleAlwaysMissing() + return rex(ANY, Rex.Op.Path.Symbol(root, node.key)) + } + // TODO: Flatten() should occur by default + else -> unionOf(paths.map { it.type }.toSet()).flatten() + } // replace step only if all are disambiguated + val allElementsInferred = paths.size == root.type.allTypes.size val firstPathOp = paths.first().op - val replacementOp = when (paths.map { it.op }.all { it == firstPathOp }) { + val replacementOp = when (allElementsInferred && paths.map { it.op }.all { it == firstPathOp }) { true -> firstPathOp false -> rexOpPathSymbol(root, node.key) } @@ -562,15 +579,6 @@ internal class PlanTyper( private fun rexString(str: String) = rex(STRING, rexOpLit(stringValue(str))) - override fun visitRexOpPath(node: Rex.Op.Path, ctx: StaticType?): Rex { - val path = super.visitRexOpPath(node, ctx) as Rex - if (path.type == MISSING) { - handleAlwaysMissing() - return rexErr("Path always returns missing $node") - } - return path - } - /** * Resolve and type scalar function calls. * @@ -584,19 +592,15 @@ internal class PlanTyper( // Type the arguments val fn = node.fn as Fn.Unresolved - val isNotMissable = fn.isNotMissable() val args = node.args.map { visitRex(it, null) } // Try to match the arguments to functions defined in the catalog return when (val match = env.resolveFn(fn, args)) { - is FnMatch.Ok -> toRexCall(match, args, isNotMissable) + is FnMatch.Ok -> toRexCall(match, args) is FnMatch.Dynamic -> { val types = mutableSetOf() - if (match.isMissable && !isNotMissable) { - types.add(MISSING) - } val candidates = match.candidates.map { candidate -> - val rex = toRexCall(candidate, args, isNotMissable) + val rex = toRexCall(candidate, args) val staticCall = rex.op as? Rex.Op.Call.Static ?: error("ToRexCall should always return a static call.") val resolvedFn = staticCall.fn as? Fn.Resolved ?: error("This should have been resolved") @@ -605,7 +609,7 @@ internal class PlanTyper( rexOpCallDynamicCandidate(fn = resolvedFn, coercions = coercions) } val op = rexOpCallDynamic(args = args, candidates = candidates) - rex(type = StaticType.unionOf(types).flatten(), op = op) + rex(type = unionOf(types).flatten(), op = op) } is FnMatch.Error -> { handleUnknownFunction(match) @@ -621,71 +625,29 @@ internal class PlanTyper( private fun toRexCall( match: FnMatch.Ok, args: List, - isNotMissable: Boolean, ): Rex { // Found a match! val newFn = fnResolved(match.signature) val newArgs = rewriteFnArgs(match.mapping, args) - val returns = newFn.signature.returns - // 7.1 All functions return MISSING when one of their inputs is MISSING (except `=`) - newArgs.forEach { - if (it.type == MissingType && !isNotMissable) { - handleAlwaysMissing() - return rex(MISSING, rexOpCallStatic(newFn, newArgs)) - } + // Check literal missing inputs + val argAlwaysMissing = args.any { + val op = it.op as? Rex.Op.Lit ?: return@any false + op.value.type == PartiQLValueType.MISSING } - - // If a function is NOT Missable (i.e., does not propagate MISSING) - // then treat MISSING as null. - var isMissing = false - var isMissable = false - if (isNotMissable) { - if (newArgs.any { it.type is MissingType }) { - isMissing = true - } else if (newArgs.any { it.type.isMissable() }) { - isMissable = true - } - } - - // Determine the nullability of the return type - var isNull = false // True iff NULL CALL and has a NULL arg - var isNullable = false // True iff NULL CALL and has a NULLABLE arg; or is a NULLABLE operator - if (newFn.signature.isNullCall) { - if (isMissing) { - isNull = true - } else if (isMissable) { - isNullable = true - } else { - for (arg in newArgs) { - if (arg.type is NullType) { - isNull = true - break - } - if (arg.type.isNullable()) { - isNullable = true - break - } - } + if (argAlwaysMissing) { + // TODO: The V1 branch has support for isMissable and isMissingCall. This codebase, however, does not + // have support for these concepts yet. This specific commit (see Git blame) does not seek to add this + // functionality. Below is a work-around for the lack of "isMissable" and "isMissingCall" + if (match.signature.name !in listOf("is_null", "is_missing", "eq")) { + handleAlwaysMissing() } } - isNullable = isNullable || newFn.signature.isNullable - // Return type with calculated nullability - var type = when { - isNull -> NULL - isNullable -> returns.toStaticType() - else -> returns.toNonNullStaticType() - } - - // Some operators can return MISSING during runtime - if (match.isMissable && !isNotMissable) { - type = StaticType.unionOf(type, MISSING) - } - - // Finally, rewrite this node + // Type return + val returns = newFn.signature.returns val op = rexOpCallStatic(newFn, newArgs) - return rex(type.flatten(), op) + return rex(returns.toStaticType().flatten(), op) } override fun visitRexOpCase(node: Rex.Op.Case, ctx: StaticType?): Rex { @@ -699,34 +661,24 @@ internal class PlanTyper( var branch = oldBranches[i] branch = visitRexOpCaseBranch(branch, branch.rex.type) - // Check if branch condition is a literal - if (boolOrNull(branch.condition.op) == false) { - continue // prune - } - // Emit typing error if a branch condition is never a boolean (prune) - if (!canBeBoolean(branch.condition.type)) { + if (!branch.condition.type.mayBeType()) { onProblem.invoke( Problem( UNKNOWN_PROBLEM_LOCATION, PlanningProblemDetails.IncompatibleTypesForOp(branch.condition.type.allTypes, "CASE_WHEN") ) ) - // prune, always false - continue } - // Accumulate typing information - typer.accumulate(branch.rex.type) + // Accumulate typing information, but skip if literal NULL or MISSING + typer.accumulate(branch.rex) newBranches.add(branch) } // Rewrite ELSE branch var newDefault = visitRex(node.default, null) - if (newBranches.isEmpty()) { - return newDefault - } - typer.accumulate(newDefault.type) + typer.accumulate(newDefault) // Compute the CASE-WHEN type from the accumulator val (type, mapping) = typer.mapping() @@ -776,7 +728,13 @@ internal class PlanTyper( val args = node.args.map { visitRex(it, it.type) }.toMutableList() val typer = DynamicTyper() args.forEach { v -> - typer.accumulate(v.type) + // Skip if literal NULL or MISSING + val branchReturnOp = v.op + val branchReturnIsLiteralAbsent = branchReturnOp is Rex.Op.Lit && (branchReturnOp.value.type == PartiQLValueType.MISSING || branchReturnOp.value.type == PartiQLValueType.NULL) + when (branchReturnIsLiteralAbsent) { + true -> typer.accumulateUnknown() + false -> typer.accumulate(v.type) + } } val (type, mapping) = typer.mapping() if (mapping != null) { @@ -804,25 +762,14 @@ internal class PlanTyper( val value = visitRex(node.value, node.value.type) val nullifier = visitRex(node.nullifier, node.nullifier.type) val typer = DynamicTyper() - typer.accumulate(NULL) - typer.accumulate(value.type) + + // Accumulate typing information + typer.accumulate(value) val (type, _) = typer.mapping() val op = rexOpNullif(value, nullifier) return rex(type, op) } - /** - * In this context, Boolean means PartiQLValueType Bool, which can be nullable. - * Hence, we permit Static Type BOOL, Static Type NULL, Static Type Missing here. - */ - private fun canBeBoolean(type: StaticType): Boolean { - return type.flatten().allTypes.any { - // TODO: This is a quick fix to unblock the typing or case expression. - // We need to model the truth value better in typer. - it is BoolType || it is NullType || it is MissingType - } - } - /** * Returns the boolean value of the expression. For now, only handle literals. */ @@ -873,7 +820,7 @@ internal class PlanTyper( } val ref = call.args.getOrNull(0) ?: error("IS STRUCT requires an argument.") // Replace the result's type - val type = AnyOfType(ref.type.allTypes.filterIsInstance().toSet()) + val type = unionOf(ref.type.allTypes.filterIsInstance().toSet()) val replacementVal = ref.copy(type = type) when (ref.op is Rex.Op.Var.Resolved) { true -> RexReplacer.replace(result, ref, replacementVal) @@ -897,7 +844,7 @@ internal class PlanTyper( } // Replace the result's type - val type = AnyOfType(ref.type.allTypes.filterIsInstance().toSet()).flatten() + val type = unionOf(ref.type.allTypes.filterIsInstance().toSet()).flatten() val replacementVal = ref.copy(type = type) val rex = when (ref.op is Rex.Op.Var.Resolved) { true -> RexReplacer.replace(result, ref, replacementVal) @@ -909,9 +856,10 @@ internal class PlanTyper( } override fun visitRexOpCollection(node: Rex.Op.Collection, ctx: StaticType?): Rex { + // Check Type if (ctx!! !is CollectionType) { handleUnexpectedType(ctx, setOf(StaticType.LIST, StaticType.BAG, StaticType.SEXP)) - return rex(StaticType.NULL_OR_MISSING, rexOpErr("Expected collection type")) + return rex(ANY, rexOpErr("Expected collection type")) } val values = node.values.map { visitRex(it, it.type) } val t = when (values.size) { @@ -1059,10 +1007,14 @@ internal class PlanTyper( ) else -> { val argTypes = args.map { it.type } - val potentialTypes = buildArgumentPermutations(argTypes).map { argumentList -> + val anyArgIsNotStruct = argTypes.any { argType -> !argType.mayBeType() } + if (anyArgIsNotStruct) { + handleAlwaysMissing() + } + val potentialTypes = buildArgumentPermutations(argTypes).mapNotNull { argumentList -> calculateTupleUnionOutputType(argumentList) } - StaticType.unionOf(potentialTypes.toSet()).flatten() + unionOf(potentialTypes.toSet()).flatten() } } val op = rexOpTupleUnion(args) @@ -1083,8 +1035,7 @@ internal class PlanTyper( * * The signature of TUPLEUNION is: (LIST) -> STRUCT. * - * If any of the arguments are NULL (or potentially NULL), we return NULL. - * If any of the arguments are non-struct, we return MISSING. + * If any of the arguments are not a struct, we return null. * * Now, assuming all the other arguments are STRUCT, then we compute the output based on a number of factors: * - closed content @@ -1096,13 +1047,12 @@ internal class PlanTyper( * If all arguments contain unique attributes AND all arguments are closed AND no fields clash, the output has * unique attributes. */ - private fun calculateTupleUnionOutputType(args: List): StaticType { + private fun calculateTupleUnionOutputType(args: List): StaticType? { val structFields = mutableListOf() var structAmount = 0 var structIsClosed = true var structIsOrdered = true var uniqueAttrs = true - val possibleOutputTypes = mutableListOf() args.forEach { arg -> when (arg) { is StructType -> { @@ -1119,13 +1069,9 @@ internal class PlanTyper( PlanningProblemDetails.CompileError("TupleUnion wasn't normalized to exclude union types.") ) ) - possibleOutputTypes.add(MISSING) - } - is NullType -> { - return NULL } else -> { - return MISSING + return null } } } @@ -1205,10 +1151,10 @@ internal class PlanTyper( /** * Logic is as follows: * 1. If [struct] is closed and ordered: - * - If no item is found, return [MissingType] + * - If no item is found, return [AnyType] and reports always missing value * - Else, grab first matching item and make sensitive. * 2. If [struct] is closed - * - AND no item is found, return [MissingType] + * - AND no item is found, return [AnyType] and reports always missing value * - AND only one item is present -> grab item and make sensitive. * - AND more than one item is present, keep sensitivity and grab item. * 3. If [struct] is open, return [AnyType] @@ -1216,7 +1162,7 @@ internal class PlanTyper( * @return a [Pair] where the [Pair.first] represents the type of the [step] and the [Pair.second] represents * the disambiguated [key]. */ - private fun inferStructLookup(struct: StructType, key: Identifier.Symbol): Pair { + private fun inferStructLookup(struct: StructType, key: Identifier.Symbol): Pair? { val binding = key.toBindingName() val isClosed = struct.constraints.contains(TupleConstraint.Open(false)) val isOrdered = struct.constraints.contains(TupleConstraint.Ordered) @@ -1225,13 +1171,17 @@ internal class PlanTyper( isClosed && isOrdered -> { struct.fields.firstOrNull { entry -> binding.isEquivalentTo(entry.key) }?.let { (sensitive(it.key) to it.value) - } ?: (key to MISSING) + } ?: run { + return null + } } // 2. Struct is closed isClosed -> { val matches = struct.fields.filter { entry -> binding.isEquivalentTo(entry.key) } when (matches.size) { - 0 -> (key to MISSING) + 0 -> { + return null + } 1 -> matches.first().let { (sensitive(it.key) to it.value) } else -> { val firstKey = matches.first().key @@ -1239,12 +1189,12 @@ internal class PlanTyper( true -> sensitive(firstKey) false -> key } - sharedKey to StaticType.unionOf(matches.map { it.value }.toSet()).flatten() + sharedKey to unionOf(matches.map { it.value }.toSet()).flatten() } } } // 3. Struct is open - else -> (key to ANY) + else -> key to ANY } return type to name } @@ -1281,14 +1231,9 @@ internal class PlanTyper( val returns = newAgg.signature.returns // Return type with calculated nullability - var type = when { + val type = when { newAgg.signature.isNullable -> returns.toStaticType() - else -> returns.toNonNullStaticType() - } - - // Some operators can return MISSING during runtime - if (match.isMissable) { - type = StaticType.unionOf(type, MISSING).flatten() + else -> returns.toStaticType() } // Finally, rewrite this node @@ -1297,7 +1242,7 @@ internal class PlanTyper( is FnMatch.Dynamic -> TODO("Dynamic aggregates not yet supported.") is FnMatch.Error -> { handleUnknownFunction(match) - return relOpAggregateCall(agg, listOf(rexErr("MISSING"))) to MissingType + return relOpAggregateCall(agg, listOf(rexErr("MISSING"))) to ANY } } } @@ -1311,7 +1256,7 @@ internal class PlanTyper( private fun Rex.type(locals: TypeEnv, strategy: ResolutionStrategy = ResolutionStrategy.LOCAL) = RexTyper(locals, strategy).visitRex(this, this.type) - private fun rexErr(message: String) = rex(MISSING, rexOpErr(message)) + private fun rexErr(message: String) = rex(ANY, rexOpErr(message)) /** * I found decorating the tree with the binding names (for resolution) was easier than associating introduced @@ -1351,13 +1296,13 @@ internal class PlanTyper( /** * Produce a union type from all the */ - private fun List.toUnionType(): StaticType = AnyOfType(map { it.type }.toSet()).flatten() + private fun List.toUnionType(): StaticType = unionOf(map { it.type }.toSet()).flatten() private fun getElementTypeForFromSource(fromSourceType: StaticType): StaticType = when (fromSourceType) { is BagType -> fromSourceType.elementType is ListType -> fromSourceType.elementType is AnyType -> ANY - is AnyOfType -> AnyOfType(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) + is AnyOfType -> unionOf(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) // All the other types coerce into a bag of themselves (including null/missing/sexp). else -> fromSourceType } @@ -1375,7 +1320,7 @@ internal class PlanTyper( val m = mapping[i] if (m != null) { // rewrite - val type = m.returns.toNonNullStaticType() + val type = m.returns.toStaticType() val cast = rexOpCallStatic(fnResolved(m), listOf(a)) a = rex(type, cast) } @@ -1462,48 +1407,6 @@ internal class PlanTyper( } } - /** - * Indicates whether the given functions propagate Missing. - * - * Currently, Logical Functions : AND, OR, NOT, IS NULL, IS MISSING - * the equal function, function do not propagate Missing. - */ - private fun Fn.Unresolved.isNotMissable(): Boolean { - return when (identifier) { - is Identifier.Qualified -> false - is Identifier.Symbol -> when (identifier.symbol) { - "and" -> true - "or" -> true - "not" -> true - "eq" -> true - "is_null" -> true - "is_missing" -> true - else -> false - } - } - } - - private fun Fn.Unresolved.isTypeAssertion(): Boolean { - return (identifier is Identifier.Symbol && identifier.symbol.startsWith("is")) - } - - /** - * This will make all binding values nullables. If the value is a struct, each field will be nullable. - * - * Note, this does not handle union types or nullable struct types. - */ - private fun List.pad() = map { - val type = when (val t = it.type) { - is StructType -> t.withNullableFields() - else -> t.asNullable() - } - relBinding(it.name, type) - } - - private fun StructType.withNullableFields(): StructType { - return copy(fields.map { it.copy(value = it.value.asNullable()) }) - } - private fun excludeBindings(input: List, item: Rel.Op.Exclude.Item): List { var matchedRoot = false val output = input.map { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeUtils.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeUtils.kt index d83c45de5f..f2e1b1e201 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeUtils.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeUtils.kt @@ -2,6 +2,7 @@ package org.partiql.planner.internal.typer import org.partiql.planner.internal.ir.Identifier import org.partiql.planner.internal.ir.Rel +import org.partiql.planner.internal.ir.Rex import org.partiql.types.AnyOfType import org.partiql.types.AnyType import org.partiql.types.BagType @@ -27,36 +28,45 @@ import org.partiql.types.TimestampType import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType +@Suppress("DEPRECATION") +@Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") +) internal fun StaticType.isNullOrMissing(): Boolean = (this is NullType || this is MissingType) -internal fun StaticType.isNumeric(): Boolean = (this is IntType || this is FloatType || this is DecimalType) +internal fun StaticType.isText(): Boolean = (this is SymbolType || this is StringType) -internal fun StaticType.isExactNumeric(): Boolean = (this is IntType || this is DecimalType) +@Suppress("DEPRECATION") +@Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") +) +internal fun StaticType.isUnknown(): Boolean = (this.isNullOrMissing() || this == StaticType.NULL_OR_MISSING) -internal fun StaticType.isApproxNumeric(): Boolean = (this is FloatType) +internal inline fun StaticType.mayBeType(): Boolean { + return this.allTypes.any { it is T } +} -internal fun StaticType.isText(): Boolean = (this is SymbolType || this is StringType) +internal fun StaticType.infer(block: (StaticType) -> StaticType?): StaticType { + val types = this.flatten().allTypes.mapNotNull { type -> block(type) } + return StaticType.unionOf(types.toSet()) +} -internal fun StaticType.isUnknown(): Boolean = (this.isNullOrMissing() || this == StaticType.NULL_OR_MISSING) +internal fun StaticType.inferListNotNull(block: (StaticType) -> StaticType?): List { + return this.flatten().allTypes.mapNotNull { type -> block(type) } +} -internal fun StaticType.isOptional(): Boolean = when (this) { - is AnyType, MissingType -> true // Any includes Missing type - is AnyOfType -> types.any { it.isOptional() } - else -> false +internal fun StaticType.inferRexList(block: (StaticType) -> Rex?): List { + return this.flatten().allTypes.mapNotNull { type -> block(type) } } /** * Per SQL, runtime types are always nullable */ @OptIn(PartiQLValueExperimental::class) +@Suppress("DEPRECATION") internal fun PartiQLValueType.toStaticType(): StaticType = when (this) { - PartiQLValueType.NULL -> StaticType.NULL - PartiQLValueType.MISSING -> StaticType.MISSING - else -> toNonNullStaticType().asNullable() -} - -@OptIn(PartiQLValueExperimental::class) -internal fun PartiQLValueType.toNonNullStaticType(): StaticType = when (this) { PartiQLValueType.ANY -> StaticType.ANY PartiQLValueType.BOOL -> StaticType.BOOL PartiQLValueType.INT8 -> StaticType.INT2 @@ -83,11 +93,12 @@ internal fun PartiQLValueType.toNonNullStaticType(): StaticType = when (this) { PartiQLValueType.LIST -> StaticType.LIST PartiQLValueType.SEXP -> StaticType.SEXP PartiQLValueType.STRUCT -> StaticType.STRUCT - PartiQLValueType.NULL -> StaticType.NULL - PartiQLValueType.MISSING -> StaticType.MISSING + PartiQLValueType.NULL -> StaticType.ANY + PartiQLValueType.MISSING -> StaticType.ANY } @OptIn(PartiQLValueExperimental::class) +@Suppress("DEPRECATION") internal fun StaticType.toRuntimeType(): PartiQLValueType { if (this is AnyOfType) { // handle anyOf(null, T) cases @@ -110,6 +121,7 @@ internal fun StaticType.toRuntimeTypeOrNull(): PartiQLValueType? { } } +@Suppress("DEPRECATION") @OptIn(PartiQLValueExperimental::class) private fun StaticType.asRuntimeType(): PartiQLValueType = when (this) { is AnyOfType -> PartiQLValueType.ANY @@ -139,8 +151,8 @@ private fun StaticType.asRuntimeType(): PartiQLValueType = when (this) { IntType.IntRangeConstraint.LONG -> PartiQLValueType.INT64 IntType.IntRangeConstraint.UNCONSTRAINED -> PartiQLValueType.INT } - MissingType -> PartiQLValueType.MISSING - is NullType -> PartiQLValueType.NULL + MissingType -> PartiQLValueType.ANY + is NullType -> PartiQLValueType.ANY is StringType -> PartiQLValueType.STRING is StructType -> PartiQLValueType.STRUCT is SymbolType -> PartiQLValueType.SYMBOL @@ -177,7 +189,7 @@ internal fun StructType.exclude(steps: List, lastStepOption val output = fields.map { field -> val newField = if (steps.size == 1) { if (lastStepOptional) { - StructType.Field(field.key, field.value.asOptional()) + StructType.Field(field.key, field.value) // TODO: double check this } else { null } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt index 7ad14eb2e7..243c384ef6 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PartiQLTyperTestBase.kt @@ -99,7 +99,7 @@ abstract class PartiQLTyperTestBase { val result = testingPipeline(statement, testName, metadata, pc) val root = (result.plan.statement as Statement.Query).root val actualType = root.type - assert(actualType == StaticType.MISSING) { + assert(actualType == StaticType.ANY) { buildString { this.appendLine(" expected Type is : MISSING") this.appendLine("actual Type is : $actualType") diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt index 88cf544510..3b2027a0ca 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt @@ -30,13 +30,15 @@ import org.partiql.planner.util.ProblemCollector import org.partiql.plugins.local.toStaticType import org.partiql.plugins.memory.MemoryConnector import org.partiql.spi.connector.ConnectorMetadata -import org.partiql.types.AnyOfType import org.partiql.types.AnyType import org.partiql.types.BagType import org.partiql.types.ListType import org.partiql.types.SexpType import org.partiql.types.StaticType -import org.partiql.types.StaticType.Companion.MISSING +import org.partiql.types.StaticType.Companion.ANY +import org.partiql.types.StaticType.Companion.INT +import org.partiql.types.StaticType.Companion.INT4 +import org.partiql.types.StaticType.Companion.INT8 import org.partiql.types.StaticType.Companion.unionOf import org.partiql.types.StructType import org.partiql.types.TupleConstraint @@ -328,7 +330,7 @@ class PlanTyperTestsPorted { ) ), StructType.Field("ssn", StaticType.STRING), - StructType.Field("employer", StaticType.STRING.asNullable()), + StructType.Field("employer", StaticType.STRING), StructType.Field("name", StaticType.STRING), StructType.Field("tax_id", StaticType.INT8), StructType.Field( @@ -515,12 +517,12 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "Current User", query = "CURRENT_USER", - expected = StaticType.unionOf(StaticType.STRING, StaticType.NULL) + expected = StaticType.STRING ), SuccessTestCase( name = "Current User Concat", query = "CURRENT_USER || 'hello'", - expected = StaticType.unionOf(StaticType.STRING, StaticType.NULL) + expected = StaticType.STRING ), SuccessTestCase( name = "Current User in WHERE", @@ -546,11 +548,11 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = listOf( - StructType.Field("CURRENT_USER", StaticType.STRING.asNullable()), + StructType.Field("CURRENT_USER", StaticType.STRING), StructType.Field("CURRENT_DATE", StaticType.DATE), - StructType.Field("curr_user", StaticType.STRING.asNullable()), + StructType.Field("curr_user", StaticType.STRING), StructType.Field("curr_date", StaticType.DATE), - StructType.Field("name_desc", StaticType.STRING.asNullable()), + StructType.Field("name_desc", StaticType.STRING), ), contentClosed = true, constraints = setOf( @@ -564,14 +566,14 @@ class PlanTyperTestsPorted { ErrorTestCase( name = "Current User (String) PLUS String", query = "CURRENT_USER + 'hello'", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, PlanningProblemDetails.UnknownFunction( "plus", listOf( - StaticType.unionOf(StaticType.STRING, StaticType.NULL), + StaticType.STRING, StaticType.STRING, ), ) @@ -590,7 +592,7 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "BITWISE_AND_2", query = "CAST(1 AS INT2) & CAST(2 AS INT2)", - expected = StaticType.unionOf(StaticType.INT2, StaticType.MISSING) + expected = StaticType.INT2 ), SuccessTestCase( name = "BITWISE_AND_3", @@ -605,17 +607,17 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "BITWISE_AND_5", query = "CAST(1 AS INT2) & 2", - expected = StaticType.unionOf(StaticType.INT4, StaticType.MISSING) + expected = StaticType.INT4 ), SuccessTestCase( name = "BITWISE_AND_6", query = "CAST(1 AS INT2) & CAST(2 AS INT8)", - expected = StaticType.unionOf(StaticType.INT8, StaticType.MISSING) + expected = StaticType.INT8 ), SuccessTestCase( name = "BITWISE_AND_7", query = "CAST(1 AS INT2) & 2", - expected = StaticType.unionOf(StaticType.INT4, StaticType.MISSING) + expected = StaticType.INT4 ), SuccessTestCase( name = "BITWISE_AND_8", @@ -635,26 +637,23 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "BITWISE_AND_NULL_OPERAND", query = "1 & NULL", - expected = StaticType.NULL, + expected = unionOf(INT4, INT8, INT), ), ErrorTestCase( name = "BITWISE_AND_MISSING_OPERAND", query = "1 & MISSING", - expected = StaticType.MISSING, + expected = unionOf(INT4, INT8, INT), problemHandler = assertProblemExists { Problem( sourceLocation = UNKNOWN_PROBLEM_LOCATION, - details = PlanningProblemDetails.UnknownFunction( - "bitwise_and", - listOf(StaticType.INT4, StaticType.MISSING) - ) + details = PlanningProblemDetails.ExpressionAlwaysReturnsNullOrMissing ) } ), ErrorTestCase( name = "BITWISE_AND_NON_INT_OPERAND", query = "1 & 'NOT AN INT'", - expected = StaticType.MISSING, + expected = StaticType.ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -703,7 +702,7 @@ class PlanTyperTestsPorted { StructType( fields = mapOf( "a" to StaticType.INT4, - "b" to StaticType.unionOf(StaticType.NULL, StaticType.DECIMAL), + "b" to StaticType.DECIMAL, ), contentClosed = true, constraints = setOf( @@ -720,7 +719,7 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = listOf( - StructType.Field("b", StaticType.unionOf(StaticType.NULL, StaticType.DECIMAL)), + StructType.Field("b", StaticType.DECIMAL), StructType.Field("a", StaticType.INT4), ), contentClosed = true, @@ -739,7 +738,7 @@ class PlanTyperTestsPorted { StructType( fields = listOf( StructType.Field("a", StaticType.INT4), - StructType.Field("a", StaticType.unionOf(StaticType.NULL, StaticType.DECIMAL)), + StructType.Field("a", StaticType.DECIMAL), ), contentClosed = true, constraints = setOf( @@ -757,7 +756,7 @@ class PlanTyperTestsPorted { StructType( fields = listOf( StructType.Field("a", StaticType.INT4), - StructType.Field("a", StaticType.unionOf(StaticType.NULL, StaticType.DECIMAL)), + StructType.Field("a", StaticType.DECIMAL), ), contentClosed = true, constraints = setOf( @@ -785,8 +784,8 @@ class PlanTyperTestsPorted { StructType( fields = listOf( StructType.Field("a", StaticType.INT4), - StructType.Field("a", StaticType.unionOf(StaticType.DECIMAL, StaticType.NULL)), - StructType.Field("a", StaticType.unionOf(StaticType.STRING, StaticType.NULL)), + StructType.Field("a", StaticType.DECIMAL), + StructType.Field("a", StaticType.STRING), ), contentClosed = true, constraints = setOf( @@ -804,7 +803,7 @@ class PlanTyperTestsPorted { StructType( fields = listOf( StructType.Field("a", StaticType.INT4), - StructType.Field("a", StaticType.unionOf(StaticType.DECIMAL, StaticType.NULL)), + StructType.Field("a", StaticType.DECIMAL), ), contentClosed = true, constraints = setOf( @@ -891,12 +890,7 @@ class PlanTyperTestsPorted { "c" to ListType( elementType = StructType( fields = mapOf( - "field" to AnyOfType( - setOf( - StaticType.INT4, - StaticType.MISSING // c[1]'s `field` was excluded - ) - ) + "field" to INT4 ), contentClosed = true, constraints = setOf( @@ -1226,7 +1220,7 @@ class PlanTyperTestsPorted { fields = mapOf( "b" to StructType( fields = mapOf( - "c" to StaticType.INT4.asOptional(), + "c" to StaticType.INT4, "d" to StaticType.STRING ), contentClosed = true, @@ -1293,8 +1287,8 @@ class PlanTyperTestsPorted { fields = mapOf( "b" to StructType( fields = mapOf( // all fields of b optional - "c" to StaticType.INT4.asOptional(), - "d" to StaticType.STRING.asOptional() + "c" to StaticType.INT4, + "d" to StaticType.STRING ), contentClosed = true, constraints = setOf( @@ -1378,7 +1372,7 @@ class PlanTyperTestsPorted { "d" to ListType( elementType = StructType( fields = mapOf( - "e" to StaticType.STRING.asOptional(), // last step is optional since only a[1]... is excluded + "e" to StaticType.STRING, // last step is optional since only a[1]... is excluded "f" to StaticType.BOOL ), contentClosed = true, @@ -1425,7 +1419,7 @@ class PlanTyperTestsPorted { "d" to ListType( elementType = StructType( fields = mapOf( // same as above - "e" to StaticType.STRING.asOptional(), + "e" to StaticType.STRING, "f" to StaticType.BOOL ), contentClosed = true, @@ -1649,7 +1643,7 @@ class PlanTyperTestsPorted { ), StructType( fields = mapOf( - "a" to StaticType.NULL + "a" to ANY ), contentClosed = true, constraints = setOf(TupleConstraint.Open(false), TupleConstraint.UniqueAttrs(true)) @@ -1692,7 +1686,7 @@ class PlanTyperTestsPorted { fields = mapOf( "a" to StructType( fields = mapOf( - "c" to StaticType.NULL + "c" to ANY ), contentClosed = true, constraints = setOf( @@ -1937,7 +1931,7 @@ class PlanTyperTestsPorted { StructType( fields = mapOf( "b" to StaticType.INT4, - "c" to StaticType.INT4.asOptional() + "c" to StaticType.INT4 ), contentClosed = true, constraints = setOf( @@ -1948,7 +1942,7 @@ class PlanTyperTestsPorted { StructType( fields = mapOf( "b" to StaticType.INT4, - "c" to StaticType.NULL.asOptional() + "c" to ANY ), contentClosed = true, constraints = setOf( @@ -1959,7 +1953,7 @@ class PlanTyperTestsPorted { StructType( fields = mapOf( "b" to StaticType.INT4, - "c" to StaticType.DECIMAL.asOptional() + "c" to StaticType.DECIMAL ), contentClosed = true, constraints = setOf( @@ -2121,17 +2115,15 @@ class PlanTyperTestsPorted { >> AS t """, expected = BagType( - StaticType.unionOf( - StaticType.MISSING, - StructType( - fields = listOf( - StructType.Field("b", StaticType.INT4), - ), - contentClosed = true, - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - ) + StructType( + fields = listOf( + StructType.Field("b", INT4), + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Ordered ) ) ), @@ -2144,15 +2136,13 @@ class PlanTyperTestsPorted { ) FROM << { 'a': { 'b': 1 } }, { 'a': { 'b': 'hello' } }, - { 'a': NULL }, + { 'a': 'world' }, { 'a': 4.5 }, { } >> AS t """, expected = BagType( StaticType.unionOf( - StaticType.NULL, - StaticType.MISSING, StructType( fields = listOf( StructType.Field("b", StaticType.INT4), @@ -2185,7 +2175,6 @@ class PlanTyperTestsPorted { """, expected = BagType( StaticType.unionOf( - StaticType.MISSING, StructType( fields = listOf( StructType.Field("first", StaticType.STRING), @@ -2221,7 +2210,6 @@ class PlanTyperTestsPorted { """, expected = BagType( StaticType.unionOf( - StaticType.MISSING, StructType( fields = listOf( StructType.Field("first", StaticType.STRING), @@ -2297,7 +2285,7 @@ class PlanTyperTestsPorted { WHEN TRUE THEN 'hello' END; """, - expected = StaticType.STRING + expected = unionOf(INT4, StaticType.STRING) ), SuccessTestCase( name = "Boolean case when", @@ -2310,14 +2298,14 @@ class PlanTyperTestsPorted { expected = StaticType.BOOL ), SuccessTestCase( - name = "Folded out false", + name = "Typing even with false condition", query = """ CASE WHEN FALSE THEN 'IMPOSSIBLE TO GET' ELSE TRUE END; """, - expected = StaticType.BOOL + expected = unionOf(StaticType.STRING, StaticType.BOOL) ), SuccessTestCase( name = "Folded out false without default", @@ -2326,7 +2314,7 @@ class PlanTyperTestsPorted { WHEN FALSE THEN 'IMPOSSIBLE TO GET' END; """, - expected = StaticType.NULL + expected = StaticType.STRING ), SuccessTestCase( name = "Not folded gives us a nullable without default", @@ -2336,7 +2324,7 @@ class PlanTyperTestsPorted { WHEN 2 THEN FALSE END; """, - expected = StaticType.BOOL.asNullable() + expected = StaticType.BOOL ), SuccessTestCase( name = "Not folded gives us a nullable without default for query", @@ -2353,7 +2341,7 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = mapOf( - "breed_descriptor" to StaticType.STRING.asNullable(), + "breed_descriptor" to StaticType.STRING, ), contentClosed = true, constraints = setOf( @@ -2461,22 +2449,22 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-08"), catalog = "pql", - expected = unionOf(StaticType.INT, StaticType.NULL), + expected = StaticType.INT, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-09"), catalog = "pql", - expected = unionOf(StaticType.INT, StaticType.NULL), + expected = StaticType.INT, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-10"), catalog = "pql", - expected = unionOf(StaticType.DECIMAL, StaticType.NULL), + expected = StaticType.DECIMAL, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-11"), catalog = "pql", - expected = unionOf(StaticType.INT, StaticType.MISSING), + expected = StaticType.INT, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-12"), @@ -2486,7 +2474,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-13"), catalog = "pql", - expected = unionOf(StaticType.FLOAT, StaticType.NULL), + expected = StaticType.FLOAT, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-14"), @@ -2496,7 +2484,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-15"), catalog = "pql", - expected = unionOf(StaticType.STRING, StaticType.NULL), + expected = StaticType.STRING, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-16"), @@ -2506,37 +2494,27 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-17"), catalog = "pql", - expected = unionOf(StaticType.CLOB, StaticType.NULL), + expected = StaticType.CLOB, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-18"), catalog = "pql", - expected = unionOf(StaticType.STRING, StaticType.NULL), + expected = StaticType.STRING, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-19"), catalog = "pql", - expected = unionOf(StaticType.STRING, StaticType.NULL), + expected = StaticType.STRING, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-20"), catalog = "pql", - expected = StaticType.NULL, + expected = StaticType.ANY, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-21"), catalog = "pql", - expected = unionOf(StaticType.STRING, StaticType.NULL), - ), - SuccessTestCase( - key = PartiQLTest.Key("basics", "case-when-22"), - catalog = "pql", - expected = unionOf(StaticType.INT4, StaticType.NULL, StaticType.MISSING), - ), - SuccessTestCase( - key = PartiQLTest.Key("basics", "case-when-23"), - catalog = "pql", - expected = StaticType.INT4, + expected = StaticType.STRING, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-24"), @@ -2546,12 +2524,12 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-25"), catalog = "pql", - expected = unionOf(StaticType.INT4, StaticType.INT8, StaticType.STRING, StaticType.NULL), + expected = unionOf(StaticType.INT4, StaticType.INT8, StaticType.STRING), ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-26"), catalog = "pql", - expected = unionOf(StaticType.INT4, StaticType.INT8, StaticType.STRING, StaticType.NULL), + expected = unionOf(StaticType.INT4, StaticType.INT8, StaticType.STRING), ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-27"), @@ -2561,7 +2539,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-28"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING, StaticType.CLOB, StaticType.NULL), + expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING, StaticType.CLOB), ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-29"), @@ -2579,13 +2557,12 @@ class PlanTyperTestsPorted { StructType.Field("y", StaticType.INT8), ), ), - StaticType.NULL, ), ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-30"), catalog = "pql", - expected = MISSING + expected = ANY ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-31"), @@ -2614,32 +2591,32 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-00"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-01"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-02"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-03"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-04"), catalog = "pql", - expected = StaticType.INT8.asNullable() + expected = StaticType.INT8 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-05"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-06"), @@ -2649,57 +2626,57 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-07"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-08"), catalog = "pql", - expected = StaticType.NULL_OR_MISSING + expected = ANY ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-09"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-10"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-11"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-12"), catalog = "pql", - expected = StaticType.INT8.asNullable() + expected = StaticType.INT8 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-13"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-14"), catalog = "pql", - expected = StaticType.STRING.asNullable() + expected = StaticType.STRING ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-15"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-16"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.NULL) + expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL) ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-17"), catalog = "pql", - expected = StaticType.INT4.asNullable() + expected = StaticType.INT4 ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-18"), @@ -2728,17 +2705,17 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-03"), catalog = "pql", - expected = unionOf(StaticType.NULL, StaticType.DECIMAL) + expected = StaticType.DECIMAL ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-04"), catalog = "pql", - expected = unionOf(StaticType.NULL, StaticType.MISSING, StaticType.DECIMAL) + expected = StaticType.DECIMAL ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-05"), catalog = "pql", - expected = unionOf(StaticType.NULL, StaticType.MISSING, StaticType.DECIMAL) + expected = StaticType.DECIMAL ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-06"), @@ -2758,12 +2735,12 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-09"), catalog = "pql", - expected = StaticType.INT8.asNullable() + expected = StaticType.INT8 ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-10"), catalog = "pql", - expected = unionOf(StaticType.INT8, StaticType.NULL, StaticType.MISSING) + expected = INT8 ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-11"), @@ -2773,7 +2750,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-12"), catalog = "pql", - expected = unionOf(StaticType.INT8, StaticType.NULL, StaticType.STRING) + expected = unionOf(StaticType.INT8, StaticType.STRING) ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-13"), @@ -2788,7 +2765,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-15"), catalog = "pql", - expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING, StaticType.NULL) + expected = unionOf(StaticType.INT2, StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.DECIMAL, StaticType.STRING) ), SuccessTestCase( key = PartiQLTest.Key("basics", "coalesce-16"), @@ -2962,7 +2939,7 @@ class PlanTyperTestsPorted { query = """ { 'aBc': 1, 'AbC': 2.0 }['Ab' || 'C']; """, - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( sourceLocation = UNKNOWN_PROBLEM_LOCATION, @@ -3085,9 +3062,9 @@ class PlanTyperTestsPorted { "a" to StaticType.INT4, "_1" to StaticType.INT8, "_2" to StaticType.INT8, - "_3" to StaticType.INT4.asNullable(), - "_4" to StaticType.INT4.asNullable(), - "_5" to StaticType.INT4.asNullable(), + "_3" to StaticType.INT4, + "_4" to StaticType.INT4, + "_5" to StaticType.INT4, ), contentClosed = true, constraints = setOf( @@ -3107,8 +3084,8 @@ class PlanTyperTestsPorted { "a" to StaticType.INT4, "c_s" to StaticType.INT8, "c" to StaticType.INT8, - "s" to StaticType.INT4.asNullable(), - "m" to StaticType.INT4.asNullable(), + "s" to StaticType.INT4, + "m" to StaticType.INT4, ), contentClosed = true, constraints = setOf( @@ -3127,55 +3104,8 @@ class PlanTyperTestsPorted { fields = mapOf( "a" to StaticType.DECIMAL, "c" to StaticType.INT8, - "s" to StaticType.DECIMAL.asNullable(), - "m" to StaticType.DECIMAL.asNullable(), - ), - contentClosed = true, - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered - ) - ) - ) - ), - SuccessTestCase( - name = "AGGREGATE over nullable integers", - query = """ - SELECT - a AS a, - COUNT(*) AS count_star, - COUNT(a) AS count_a, - COUNT(b) AS count_b, - SUM(a) AS sum_a, - SUM(b) AS sum_b, - MIN(a) AS min_a, - MIN(b) AS min_b, - MAX(a) AS max_a, - MAX(b) AS max_b, - AVG(a) AS avg_a, - AVG(b) AS avg_b - FROM << - { 'a': 1, 'b': 2 }, - { 'a': 3, 'b': 4 }, - { 'a': 5, 'b': NULL } - >> GROUP BY a - """.trimIndent(), - expected = BagType( - StructType( - fields = mapOf( - "a" to StaticType.INT4, - "count_star" to StaticType.INT8, - "count_a" to StaticType.INT8, - "count_b" to StaticType.INT8, - "sum_a" to StaticType.INT4.asNullable(), - "sum_b" to StaticType.INT4.asNullable(), - "min_a" to StaticType.INT4.asNullable(), - "min_b" to StaticType.INT4.asNullable(), - "max_a" to StaticType.INT4.asNullable(), - "max_b" to StaticType.INT4.asNullable(), - "avg_a" to StaticType.INT4.asNullable(), - "avg_b" to StaticType.INT4.asNullable(), + "s" to StaticType.DECIMAL, + "m" to StaticType.DECIMAL, ), contentClosed = true, constraints = setOf( @@ -3262,7 +3192,7 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = mapOf( - "a" to StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.MISSING), + "a" to StaticType.unionOf(StaticType.INT4, StaticType.INT8), ), contentClosed = true, constraints = setOf( @@ -3286,7 +3216,7 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = mapOf( - "a" to StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.MISSING), + "a" to StaticType.unionOf(StaticType.INT4, StaticType.INT8), ), contentClosed = true, constraints = setOf( @@ -3310,7 +3240,7 @@ class PlanTyperTestsPorted { expected = BagType( StructType( fields = mapOf( - "c" to StaticType.unionOf(StaticType.MISSING, StaticType.DECIMAL), + "c" to StaticType.DECIMAL, ), contentClosed = true, constraints = setOf( @@ -3332,7 +3262,7 @@ class PlanTyperTestsPorted { { 'a': 'hello world!' } >> AS t """.trimIndent(), - expected = BagType(StaticType.MISSING), + expected = BagType(ANY), problemHandler = assertProblemExists { Problem( sourceLocation = UNKNOWN_PROBLEM_LOCATION, @@ -3355,7 +3285,7 @@ class PlanTyperTestsPorted { { 'a': <<>> } >> AS t """.trimIndent(), - expected = BagType(StaticType.MISSING), + expected = BagType(ANY), problemHandler = assertProblemExists { Problem( sourceLocation = UNKNOWN_PROBLEM_LOCATION, @@ -3377,14 +3307,11 @@ class PlanTyperTestsPorted { { 'NOT_A': 1 } >> AS t """.trimIndent(), - expected = BagType(StaticType.MISSING), + expected = BagType(unionOf(StaticType.INT2, INT4, INT8, INT, StaticType.FLOAT, StaticType.DECIMAL)), problemHandler = assertProblemExists { Problem( sourceLocation = UNKNOWN_PROBLEM_LOCATION, - details = PlanningProblemDetails.UnknownFunction( - "pos", - listOf(StaticType.MISSING) - ) + details = PlanningProblemDetails.ExpressionAlwaysReturnsNullOrMissing ) } ), @@ -3472,6 +3399,112 @@ class PlanTyperTestsPorted { // // Parameterized Tests // + @Test + fun developmentTest() { +// val tc = +// SuccessTestCase( +// key = PartiQLTest.Key("basics", "coalesce-09"), +// catalog = "pql", +// expected = StaticType.INT8 +// ) +// runTest(tc) +// val tc2 = +// SuccessTestCase( +// name = "unary plus on varying numeric types including missing -- this may return missing", +// query = """ +// SELECT +t.a AS a +// FROM << +// { 'a': CAST(1 AS INT8) }, +// { 'a': CAST(1 AS INT4) }, +// { } +// >> AS t +// """.trimIndent(), +// expected = BagType( +// StructType( +// fields = mapOf( +// "a" to unionOf(INT4, INT8), +// ), +// contentClosed = true, +// constraints = setOf( +// TupleConstraint.Open(false), +// TupleConstraint.UniqueAttrs(true), +// TupleConstraint.Ordered +// ) +// ) +// ) +// ) +// runTest(tc2) +// val tc3 = +// SuccessTestCase( +// name = "Tuple Union with Heterogeneous Data (2)", +// query = """ +// SELECT VALUE TUPLEUNION( +// t.a +// ) FROM << +// { 'a': { 'b': 1 } }, +// { 'a': { 'b': 'hello' } }, +// { 'a': NULL }, +// { 'a': 4.5 }, +// { } +// >> AS t +// """, +// expected = BagType( +// StaticType.unionOf( +// StaticType.NULL, +// StaticType.MISSING, +// StructType( +// fields = listOf( +// StructType.Field("b", StaticType.INT4), +// ), +// contentClosed = true, +// constraints = setOf( +// TupleConstraint.Open(false), +// TupleConstraint.UniqueAttrs(true), +// ) +// ), +// StructType( +// fields = listOf( +// StructType.Field("b", StaticType.STRING), +// ), +// contentClosed = true, +// constraints = setOf( +// TupleConstraint.Open(false), +// TupleConstraint.UniqueAttrs(true), +// ) +// ) +// ) +// ), +// ) +// runTest(tc3) + val tc4 = + SuccessTestCase( + name = "Not folded gives us a nullable without default for query", + query = """ + SELECT + CASE breed + WHEN 'golden retriever' THEN 'fluffy dog' + WHEN 'pitbull' THEN 'short-haired dog' + END AS breed_descriptor + FROM dogs + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = BagType( + StructType( + fields = mapOf( + "breed_descriptor" to StaticType.STRING, + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Ordered + ) + ) + ) + ) + runTest(tc4) + } @Test @Disabled("The planner doesn't support heterogeneous input to aggregation functions (yet?).") @@ -3505,14 +3538,14 @@ class PlanTyperTestsPorted { "count_star" to StaticType.INT8, "count_a" to StaticType.INT8, "count_b" to StaticType.INT8, - "sum_a" to StaticType.DECIMAL.asNullable(), - "sum_b" to StaticType.DECIMAL.asNullable(), - "min_a" to StaticType.DECIMAL.asNullable(), - "min_b" to StaticType.DECIMAL.asNullable(), - "max_a" to StaticType.DECIMAL.asNullable(), - "max_b" to StaticType.DECIMAL.asNullable(), - "avg_a" to StaticType.DECIMAL.asNullable(), - "avg_b" to StaticType.DECIMAL.asNullable(), + "sum_a" to StaticType.DECIMAL, + "sum_b" to StaticType.DECIMAL, + "min_a" to StaticType.DECIMAL, + "min_b" to StaticType.DECIMAL, + "max_a" to StaticType.DECIMAL, + "max_b" to StaticType.DECIMAL, + "avg_a" to StaticType.DECIMAL, + "avg_b" to StaticType.DECIMAL, ), contentClosed = true, constraints = setOf( @@ -4052,7 +4085,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id IN 'hello'", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4075,7 +4108,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id BETWEEN 1 AND 'a'", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4102,7 +4135,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "order_info.ship_option LIKE 3", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4126,7 +4159,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "order_info.\"CUSTOMER_ID\" = 1", - expected = StaticType.NULL + expected = StaticType.BOOL ), SuccessTestCase( name = "Case Sensitive success", @@ -4140,14 +4173,14 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "(order_info.customer_id = 1) AND (order_info.marketplace_id = 2)", - expected = StaticType.unionOf(StaticType.BOOL, StaticType.NULL) + expected = StaticType.BOOL ), SuccessTestCase( name = "2-Level Junction", catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "(order_info.customer_id = 1) AND (order_info.marketplace_id = 2) OR (order_info.customer_id = 3) AND (order_info.marketplace_id = 4)", - expected = StaticType.unionOf(StaticType.BOOL, StaticType.NULL) + expected = StaticType.BOOL ), SuccessTestCase( name = "INT and STR Comparison", @@ -4163,7 +4196,7 @@ class PlanTyperTestsPorted { query = "non_existing_column = 1", // Function resolves to EQ__ANY_ANY__BOOL // Which can return BOOL Or NULL - expected = StaticType.unionOf(StaticType.BOOL, StaticType.NULL), + expected = StaticType.BOOL, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4176,7 +4209,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id = 1 AND 1", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4192,7 +4225,7 @@ class PlanTyperTestsPorted { catalog = CATALOG_DB, catalogPath = DB_SCHEMA_MARKETS, query = "1 AND order_info.customer_id = 1", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, @@ -4273,7 +4306,7 @@ class PlanTyperTestsPorted { query = "SELECT CAST(breed AS INT) AS cast_breed FROM pets", expected = BagType( StructType( - fields = mapOf("cast_breed" to StaticType.unionOf(StaticType.INT, StaticType.MISSING)), + fields = mapOf("cast_breed" to StaticType.INT), contentClosed = true, constraints = setOf( TupleConstraint.Open(false), @@ -4394,7 +4427,7 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "Current User", query = "CURRENT_USER", - expected = StaticType.unionOf(StaticType.STRING, StaticType.NULL) + expected = StaticType.STRING ), SuccessTestCase( name = "Trim", @@ -4404,7 +4437,7 @@ class PlanTyperTestsPorted { SuccessTestCase( name = "Current User Concat", query = "CURRENT_USER || 'hello'", - expected = StaticType.unionOf(StaticType.STRING, StaticType.NULL) + expected = StaticType.STRING ), SuccessTestCase( name = "Current User Concat in WHERE", @@ -4429,7 +4462,7 @@ class PlanTyperTestsPorted { ErrorTestCase( name = "TRIM_2_error", query = "trim(2 FROM ' Hello, World! ')", - expected = StaticType.MISSING, + expected = ANY, problemHandler = assertProblemExists { Problem( UNKNOWN_PROBLEM_LOCATION, diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/functions/NullIfTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/functions/NullIfTest.kt index 6f1a56a84e..dd84ba0db6 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/functions/NullIfTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/functions/NullIfTest.kt @@ -30,7 +30,7 @@ class NullIfTest : PartiQLTyperTestBase() { // Generate all success cases cartesianProduct(allSupportedType, allSupportedType).forEach { args -> - val expected = StaticType.unionOf(args[0], StaticType.NULL).flatten() + val expected = args[0] val result = TestResult.Success(expected) argsMap[result] = setOf(args) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/logical/OpLogicalTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/logical/OpLogicalTest.kt index 996c318bd8..a788a00f65 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/logical/OpLogicalTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/logical/OpLogicalTest.kt @@ -3,7 +3,6 @@ package org.partiql.planner.internal.typer.logical import org.junit.jupiter.api.DynamicContainer import org.junit.jupiter.api.TestFactory import org.partiql.planner.internal.typer.PartiQLTyperTestBase -import org.partiql.planner.internal.typer.isUnknown import org.partiql.planner.util.allSupportedType import org.partiql.planner.util.cartesianProduct import org.partiql.types.StaticType @@ -15,11 +14,7 @@ import java.util.stream.Stream class OpLogicalTest : PartiQLTyperTestBase() { @TestFactory fun not(): Stream { - val supportedType = listOf( - StaticType.BOOL, - StaticType.NULL, - StaticType.MISSING, - ) + val supportedType = listOf(StaticType.BOOL) val unsupportedType = allSupportedType.filterNot { supportedType.contains(it) @@ -32,15 +27,8 @@ class OpLogicalTest : PartiQLTyperTestBase() { val argsMap = buildMap { val successArgs = supportedType.map { t -> listOf(t) }.toSet() successArgs.forEach { args: List -> - val arg = args.first() - if (arg.isUnknown()) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } @@ -51,15 +39,9 @@ class OpLogicalTest : PartiQLTyperTestBase() { return super.testGen("not", tests, argsMap) } - // TODO: There is no good way to have the inferencer to distinguish whether the logical operator returns - // NULL, OR BOOL, OR UnionOf(Bool, NULL), other than have a lookup table in the inferencer. @TestFactory fun booleanConnective(): Stream { - val supportedType = listOf( - StaticType.BOOL, - StaticType.NULL, - StaticType.MISSING - ) + val supportedType = listOf(StaticType.BOOL) val tests = listOf( "expr-00", // OR @@ -75,7 +57,7 @@ class OpLogicalTest : PartiQLTyperTestBase() { successArgs.contains(it) }.toSet() - put(TestResult.Success(StaticType.unionOf(StaticType.BOOL, StaticType.NULL)), successArgs) + put(TestResult.Success(StaticType.BOOL), successArgs) put(TestResult.Failure, failureArgs) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpArithmeticTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpArithmeticTest.kt index 940aa1dd21..9ab1d9123d 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpArithmeticTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpArithmeticTest.kt @@ -23,8 +23,7 @@ class OpArithmeticTest : PartiQLTyperTestBase() { ).map { inputs.get("basics", it)!! } val argsMap: Map>> = buildMap { - val successArgs = (allNumberType + listOf(StaticType.NULL)) - .let { cartesianProduct(it, it) } + val successArgs = allNumberType.let { cartesianProduct(it, it) } val failureArgs = cartesianProduct( allSupportedType, allSupportedType @@ -35,11 +34,7 @@ class OpArithmeticTest : PartiQLTyperTestBase() { successArgs.forEach { args: List -> val arg0 = args.first() val arg1 = args[1] - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else if (arg0 == arg1) { + if (arg0 == arg1) { (this[TestResult.Success(arg1)] ?: setOf(args)).let { put(TestResult.Success(arg1), it + setOf(args)) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpBitwiseAndTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpBitwiseAndTest.kt index 398ebe805e..54822244b0 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpBitwiseAndTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpBitwiseAndTest.kt @@ -19,8 +19,7 @@ class OpBitwiseAndTest : PartiQLTyperTestBase() { ).map { inputs.get("basics", it)!! } val argsMap = buildMap { - val successArgs = (allIntType + listOf(StaticType.NULL)) - .let { cartesianProduct(it, it) } + val successArgs = allIntType.let { cartesianProduct(it, it) } val failureArgs = cartesianProduct( allSupportedType, allSupportedType @@ -31,11 +30,7 @@ class OpBitwiseAndTest : PartiQLTyperTestBase() { successArgs.forEach { args: List -> val arg0 = args.first() val arg1 = args[1] - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else if (arg0 == arg1) { + if (arg0 == arg1) { (this[TestResult.Success(arg1)] ?: setOf(args)).let { put(TestResult.Success(arg1), it + setOf(args)) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpConcatTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpConcatTest.kt index b445d81818..87a2989212 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpConcatTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/operator/OpConcatTest.kt @@ -19,8 +19,7 @@ class OpConcatTest : PartiQLTyperTestBase() { ).map { inputs.get("basics", it)!! } val argsMap = buildMap { - val successArgs = (allTextType + listOf(StaticType.NULL)) - .let { cartesianProduct(it, it) } + val successArgs = allTextType.let { cartesianProduct(it, it) } val failureArgs = cartesianProduct( allSupportedType, allSupportedType @@ -31,11 +30,7 @@ class OpConcatTest : PartiQLTyperTestBase() { successArgs.forEach { args: List -> val arg0 = args.first() val arg1 = args[1] - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else if (arg0 == arg1) { + if (arg0 == arg1) { (this[TestResult.Success(arg1)] ?: setOf(args)).let { put(TestResult.Success(arg1), it + setOf(args)) } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpBetweenTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpBetweenTest.kt index c42aa80fad..c51f64b4d9 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpBetweenTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpBetweenTest.kt @@ -21,25 +21,25 @@ class OpBetweenTest : PartiQLTyperTestBase() { val argsMap = buildMap { val successArgs = cartesianProduct( - allNumberType + listOf(StaticType.NULL), - allNumberType + listOf(StaticType.NULL), - allNumberType + listOf(StaticType.NULL), + allNumberType, + allNumberType, + allNumberType, ) + cartesianProduct( - StaticType.TEXT.allTypes + listOf(StaticType.CLOB, StaticType.NULL), - StaticType.TEXT.allTypes + listOf(StaticType.CLOB, StaticType.NULL), - StaticType.TEXT.allTypes + listOf(StaticType.CLOB, StaticType.NULL) + StaticType.TEXT.allTypes + listOf(StaticType.CLOB), + StaticType.TEXT.allTypes + listOf(StaticType.CLOB), + StaticType.TEXT.allTypes + listOf(StaticType.CLOB) ) + cartesianProduct( - listOf(StaticType.DATE, StaticType.NULL), - listOf(StaticType.DATE, StaticType.NULL), - listOf(StaticType.DATE, StaticType.NULL) + listOf(StaticType.DATE), + listOf(StaticType.DATE), + listOf(StaticType.DATE) ) + cartesianProduct( - listOf(StaticType.TIME, StaticType.NULL), - listOf(StaticType.TIME, StaticType.NULL), - listOf(StaticType.TIME, StaticType.NULL) + listOf(StaticType.TIME), + listOf(StaticType.TIME), + listOf(StaticType.TIME) ) + cartesianProduct( - listOf(StaticType.TIMESTAMP, StaticType.NULL), - listOf(StaticType.TIMESTAMP, StaticType.NULL), - listOf(StaticType.TIMESTAMP, StaticType.NULL) + listOf(StaticType.TIMESTAMP), + listOf(StaticType.TIMESTAMP), + listOf(StaticType.TIMESTAMP) ) val failureArgs = cartesianProduct( @@ -51,17 +51,8 @@ class OpBetweenTest : PartiQLTyperTestBase() { }.toSet() successArgs.forEach { args: List -> - val arg0 = args.first() - val arg1 = args[1] - val arg2 = args[2] - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpComparisonTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpComparisonTest.kt index 9e7130aa76..cbe2a515c5 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpComparisonTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpComparisonTest.kt @@ -22,18 +22,8 @@ class OpComparisonTest : PartiQLTyperTestBase() { val successArgs = cartesianProduct(allSupportedType, allSupportedType) successArgs.forEach { args: List -> - if (args.contains(StaticType.MISSING)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } put(TestResult.Failure, emptySet>()) } @@ -55,23 +45,23 @@ class OpComparisonTest : PartiQLTyperTestBase() { val argsMap = buildMap { val successArgs = cartesianProduct( - StaticType.NUMERIC.allTypes + listOf(StaticType.NULL), - StaticType.NUMERIC.allTypes + listOf(StaticType.NULL) + StaticType.NUMERIC.allTypes, + StaticType.NUMERIC.allTypes ) + cartesianProduct( - StaticType.TEXT.allTypes + listOf(StaticType.CLOB, StaticType.NULL), - StaticType.TEXT.allTypes + listOf(StaticType.CLOB, StaticType.NULL) + StaticType.TEXT.allTypes + listOf(StaticType.CLOB), + StaticType.TEXT.allTypes + listOf(StaticType.CLOB) ) + cartesianProduct( - listOf(StaticType.BOOL, StaticType.NULL), - listOf(StaticType.BOOL, StaticType.NULL) + listOf(StaticType.BOOL), + listOf(StaticType.BOOL) ) + cartesianProduct( - listOf(StaticType.DATE, StaticType.NULL), - listOf(StaticType.DATE, StaticType.NULL) + listOf(StaticType.DATE), + listOf(StaticType.DATE) ) + cartesianProduct( - listOf(StaticType.TIME, StaticType.NULL), - listOf(StaticType.TIME, StaticType.NULL) + listOf(StaticType.TIME), + listOf(StaticType.TIME) ) + cartesianProduct( - listOf(StaticType.TIMESTAMP, StaticType.NULL), - listOf(StaticType.TIMESTAMP, StaticType.NULL) + listOf(StaticType.TIMESTAMP), + listOf(StaticType.TIMESTAMP) ) val failureArgs = cartesianProduct( @@ -82,14 +72,8 @@ class OpComparisonTest : PartiQLTyperTestBase() { }.toSet() successArgs.forEach { args: List -> - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpInTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpInTest.kt index 04ee00a47c..d7c477d77a 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpInTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpInTest.kt @@ -6,7 +6,6 @@ import org.partiql.planner.internal.typer.PartiQLTyperTestBase import org.partiql.planner.util.allCollectionType import org.partiql.planner.util.allSupportedType import org.partiql.planner.util.cartesianProduct -import org.partiql.types.MissingType import org.partiql.types.StaticType import java.util.stream.Stream @@ -21,19 +20,12 @@ class OpInTest : PartiQLTyperTestBase() { val argsMap = buildMap { val successArgs = allSupportedType - .filterNot { it is MissingType } .map { t -> listOf(t) } .toSet() successArgs.forEach { args: List -> - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } @@ -51,8 +43,8 @@ class OpInTest : PartiQLTyperTestBase() { val argsMap = buildMap { val successArgs = cartesianProduct( - allSupportedType.filterNot { it is MissingType }, - (allCollectionType + listOf(StaticType.NULL)) + allSupportedType, + allCollectionType ) val failureArgs = cartesianProduct( allSupportedType, @@ -62,14 +54,8 @@ class OpInTest : PartiQLTyperTestBase() { }.toSet() successArgs.forEach { args: List -> - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpLikeTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpLikeTest.kt index 8f7ec051f6..3e7b3d3fee 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpLikeTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpLikeTest.kt @@ -17,7 +17,7 @@ class OpLikeTest : PartiQLTyperTestBase() { ).map { inputs.get("basics", it)!! } val argsMap = buildMap { - val successArgs = (allTextType + listOf(StaticType.NULL)) + val successArgs = (allTextType) .let { cartesianProduct(it, it) } val failureArgs = cartesianProduct( allSupportedType, @@ -27,14 +27,8 @@ class OpLikeTest : PartiQLTyperTestBase() { }.toSet() successArgs.forEach { args: List -> - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } @@ -51,7 +45,7 @@ class OpLikeTest : PartiQLTyperTestBase() { ).map { inputs.get("basics", it)!! } val argsMap = buildMap { - val successArgs = (allTextType + listOf(StaticType.NULL)) + val successArgs = (allTextType) .let { cartesianProduct(it, it, it) } val failureArgs = cartesianProduct( allSupportedType, @@ -62,14 +56,8 @@ class OpLikeTest : PartiQLTyperTestBase() { }.toSet() successArgs.forEach { args: List -> - if (args.contains(StaticType.NULL)) { - (this[TestResult.Success(StaticType.NULL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.NULL), it + setOf(args)) - } - } else { - (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { - put(TestResult.Success(StaticType.BOOL), it + setOf(args)) - } + (this[TestResult.Success(StaticType.BOOL)] ?: setOf(args)).let { + put(TestResult.Success(StaticType.BOOL), it + setOf(args)) } Unit } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpTypeAssertionTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpTypeAssertionTest.kt index d418b7b313..dad9bc9dd7 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpTypeAssertionTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/predicate/OpTypeAssertionTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.DynamicContainer import org.junit.jupiter.api.TestFactory import org.partiql.planner.internal.typer.PartiQLTyperTestBase import org.partiql.planner.util.allSupportedType -import org.partiql.types.MissingType +import org.partiql.types.SingleType import org.partiql.types.StaticType import java.util.stream.Stream @@ -18,12 +18,11 @@ class OpTypeAssertionTest : PartiQLTyperTestBase() { }.map { inputs.get("basics", it)!! } val argsMap = buildMap { - val successArgs = allSupportedType.filterNot { it is MissingType }.flatMap { t -> + val successArgs = allSupportedType.flatMap { t -> setOf(listOf(t)) }.toSet() - val failureArgs = setOf(listOf(MissingType)) put(TestResult.Success(StaticType.BOOL), successArgs) - put(TestResult.Failure, failureArgs) + put(TestResult.Failure, emptySet>()) } return super.testGen("type-assertion", tests, argsMap) diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/util/Utils.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/util/Utils.kt index a94fae4600..adcd569fa9 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/util/Utils.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/util/Utils.kt @@ -28,7 +28,13 @@ fun cartesianProduct(a: List, b: List, vararg lists: List): Set set.map { element -> list + element } } }.toSet() -val allSupportedType = StaticType.ALL_TYPES.filterNot { it == StaticType.GRAPH } +val allSupportedType = StaticType.ALL_TYPES.filterNot { + it == StaticType.GRAPH +}.filterNot { + it is NullType +}.filterNot { + it is MissingType +} val allSupportedTypeNotUnknown = allSupportedType.filterNot { it == StaticType.MISSING || it == StaticType.NULL } diff --git a/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt b/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt index 5eeba1b397..5c39019a3c 100644 --- a/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt +++ b/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt @@ -20,7 +20,7 @@ public sealed class StaticType { */ @JvmStatic public fun unionOf(vararg types: StaticType, metas: Map = mapOf()): StaticType = - unionOf(types.toSet(), metas) + unionOf(types.toSet(), metas).flatten() /** * Creates a new [StaticType] as a union of the passed [types]. The values typed by the returned type @@ -30,15 +30,65 @@ public sealed class StaticType { * @return [StaticType] representing the union of [types] */ @JvmStatic - public fun unionOf(types: Set, metas: Map = mapOf()): StaticType = AnyOfType(types, metas) + @Suppress("DEPRECATION") + public fun unionOf( + types: Set, + metas: Map = mapOf(), + errorOnEmptyTypes: Boolean = false + ): StaticType { + if (errorOnEmptyTypes && types.isEmpty()) { + throw IllegalStateException("Cannot make a union of zero types.") + } + return when (types.isEmpty()) { + true -> ANY + false -> AnyOfType(types, metas).flatten() + } + } + + /** + * Creates a new [StaticType] as a union of the passed [types]. The values typed by the returned type + * are defined as the union of all values typed as [types] + * + * @param types [StaticType] to be unioned. + * @return [StaticType] representing the union of [types] + */ + @JvmStatic + @Suppress("DEPRECATION") + public fun unionOf( + types: Collection, + metas: Map = mapOf(), + ): StaticType { + return when (types.isEmpty()) { + true -> ANY + false -> AnyOfType(types.toSet(), metas).flatten() + } + } // TODO consider making these into an enumeration... // Convenient enums to create a bare bones instance of StaticType + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") + ) @JvmField public val MISSING: MissingType = MissingType + + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") + ) @JvmField public val NULL: NullType = NullType() - @JvmField public val ANY: AnyType = AnyType() + + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") + ) @JvmField public val NULL_OR_MISSING: StaticType = unionOf(NULL, MISSING) + + @JvmField public val ANY: AnyType = AnyType() @JvmField public val BOOL: BoolType = BoolType() @JvmField public val INT2: IntType = IntType(IntType.IntRangeConstraint.SHORT) @JvmField public val INT4: IntType = IntType(IntType.IntRangeConstraint.INT4) @@ -68,7 +118,9 @@ public sealed class StaticType { @OptIn(PartiQLTimestampExperimental::class) @JvmStatic public val ALL_TYPES: List = listOf( + @Suppress("DEPRECATION") MISSING, + @Suppress("DEPRECATION") NULL, BOOL, INT2, @@ -97,8 +149,14 @@ public sealed class StaticType { * * If it already nullable, returns the original type. */ + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("") + ) public fun asNullable(): StaticType = when { + @Suppress("DEPRECATION") this.isNullable() -> this else -> unionOf(this, NULL).flatten() } @@ -108,6 +166,11 @@ public sealed class StaticType { * * If it already optional, returns the original type. */ + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("") + ) public fun asOptional(): StaticType = when { this.isOptional() -> this @@ -121,6 +184,7 @@ public sealed class StaticType { * MissingType is a singleton and there can only be one representation for it * i.e. you cannot have two instances of MissingType with different metas. */ + @Suppress("DEPRECATION") public fun withMetas(metas: Map): StaticType = when (this) { is AnyType -> copy(metas = metas) @@ -148,6 +212,11 @@ public sealed class StaticType { /** * Type is nullable if it is of Null type or is an AnyOfType that contains a Null type */ + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("true") + ) public fun isNullable(): Boolean = when (this) { is AnyOfType -> types.any { it.isNullable() } @@ -160,6 +229,11 @@ public sealed class StaticType { * * @return */ + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("true") + ) public fun isMissable(): Boolean = when (this) { is AnyOfType -> types.any { it.isMissable() } @@ -170,6 +244,11 @@ public sealed class StaticType { /** * Type is optional if it is Any, or Missing, or an AnyOfType that contains Any or Missing type */ + @Suppress("DEPRECATION") + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("true") + ) private fun isOptional(): Boolean = when (this) { is AnyType, MissingType -> true // Any includes Missing type @@ -198,7 +277,7 @@ public data class AnyType(override val metas: Map = mapOf()) : Stat * Converts this into an [AnyOfType] representation. This method is helpful in inference when * it wants to iterate over all possible types of an expression. */ - public fun toAnyOfType(): AnyOfType = AnyOfType(ALL_TYPES.toSet()) + public fun toAnyOfType(): AnyOfType = unionOf(ALL_TYPES.toSet()) as AnyOfType override fun flatten(): StaticType = this @@ -250,6 +329,10 @@ public class UnsupportedTypeConstraint(message: String) : Exception(message) * * This is not a singleton since there may be more that one representation of a Null type (each with different metas) */ +@Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("AnyType") +) public data class NullType(override val metas: Map = mapOf()) : SingleType() { override val allTypes: List get() = listOf(this) @@ -263,6 +346,10 @@ public data class NullType(override val metas: Map = mapOf()) : Sin * This is a singleton unlike the rest of the types as there cannot be * more that one representations of a missing type. */ +@Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("AnyType") +) public object MissingType : SingleType() { override val metas: Map = mapOf() @@ -621,7 +708,14 @@ public data class GraphType( /** * Represents a [StaticType] that's defined by the union of multiple [StaticType]s. */ -public data class AnyOfType(val types: Set, override val metas: Map = mapOf()) : StaticType() { +public data class AnyOfType @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("StaticType.unionOf(types)") +) public constructor( + val types: Set, + override val metas: Map = mapOf() +) : StaticType() { + /** * Flattens a union type by traversing the types and recursively bubbling up the underlying union types. * @@ -704,7 +798,9 @@ public sealed class CollectionConstraint { public data class PartitionKey(val keys: Set) : TupleCollectionConstraint, CollectionConstraint() } +@Suppress("DEPRECATION") internal fun StaticType.isNullOrMissing(): Boolean = (this is NullType || this is MissingType) internal fun StaticType.isNumeric(): Boolean = (this is IntType || this is FloatType || this is DecimalType) internal fun StaticType.isText(): Boolean = (this is SymbolType || this is StringType) +@Suppress("DEPRECATION") internal fun StaticType.isUnknown(): Boolean = (this.isNullOrMissing() || this == StaticType.NULL_OR_MISSING) diff --git a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueType.kt b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueType.kt index 118e1e380b..8cc1393447 100644 --- a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueType.kt +++ b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueType.kt @@ -45,6 +45,16 @@ public enum class PartiQLValueType { LIST, SEXP, STRUCT, + + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") + ) NULL, + + @Deprecated( + message = "This will be removed in a future major-version bump.", + replaceWith = ReplaceWith("ANY") + ) MISSING, } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalSchema.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalSchema.kt index 5e1cdde347..e8e64bf090 100644 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalSchema.kt +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalSchema.kt @@ -88,8 +88,10 @@ public fun StringElement.toStaticType(): StaticType = when (textValue) { } // Union type +@Suppress("DEPRECATION") public fun ListElement.toStaticType(): StaticType { - val types = values.map { it.toStaticType() }.toSet() + // TODO: Update all files to not include literal null. + val types = values.map { it.toStaticType() }.filter { it !is NullType }.filter { it !is MissingType }.toSet() return StaticType.unionOf(types) }