From f41e12d61f90092a275e9fd20dc5302b9b760a8b Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Wed, 15 May 2024 16:21:47 -0700 Subject: [PATCH 01/13] Deprecates absent types Removes all logic regarding absent types in planner --- CHANGELOG.md | 17 + .../internal/transforms/PlanTransform.kt | 2 +- .../internal/transforms/RexConverter.kt | 17 +- .../planner/internal/typer/DynamicTyper.kt | 94 +++-- .../planner/internal/typer/FnResolver.kt | 53 +-- .../planner/internal/typer/PlanTyper.kt | 331 ++++++----------- .../planner/internal/typer/TypeUtils.kt | 62 ++-- .../internal/typer/PartiQLTyperTestBase.kt | 2 +- .../internal/typer/PlanTyperTestsPorted.kt | 333 +++++++----------- .../internal/typer/functions/NullIfTest.kt | 2 +- .../internal/typer/logical/OpLogicalTest.kt | 28 +- .../typer/operator/OpArithmeticTest.kt | 9 +- .../typer/operator/OpBitwiseAndTest.kt | 9 +- .../internal/typer/operator/OpConcatTest.kt | 9 +- .../internal/typer/predicate/OpBetweenTest.kt | 43 +-- .../typer/predicate/OpComparisonTest.kt | 48 +-- .../internal/typer/predicate/OpInTest.kt | 26 +- .../internal/typer/predicate/OpLikeTest.kt | 24 +- .../typer/predicate/OpTypeAssertionTest.kt | 7 +- .../kotlin/org/partiql/planner/util/Utils.kt | 8 +- .../catalogs/default/pql/main/item.ion | 110 ++---- .../catalogs/default/pql/main/person.ion | 5 +- .../catalogs/default/pql/numbers.ion | 30 +- .../resources/catalogs/default/pql/t_item.ion | 32 +- .../resources/catalogs/tpc_ds/call_center.ion | 145 ++------ .../catalogs/tpc_ds/catalog_page.ion | 45 +-- .../catalogs/tpc_ds/catalog_returns.ion | 135 ++----- .../catalogs/tpc_ds/catalog_sales.ion | 160 ++------- .../resources/catalogs/tpc_ds/customer.ion | 90 +---- .../catalogs/tpc_ds/customer_address.ion | 65 +--- .../catalogs/tpc_ds/customer_demographics.ion | 45 +-- .../resources/catalogs/tpc_ds/date_dim.ion | 140 ++------ .../catalogs/tpc_ds/dbgen_version.ion | 20 +- .../tpc_ds/household_demographics.ion | 25 +- .../resources/catalogs/tpc_ds/income_band.ion | 15 +- .../resources/catalogs/tpc_ds/inventory.ion | 20 +- .../resources/catalogs/tpc_ds/item.ion | 110 ++---- .../resources/catalogs/tpc_ds/promotion.ion | 95 +---- .../resources/catalogs/tpc_ds/reason.ion | 15 +- .../resources/catalogs/tpc_ds/ship_mode.ion | 30 +- .../resources/catalogs/tpc_ds/store.ion | 135 ++----- .../catalogs/tpc_ds/store_returns.ion | 90 +---- .../resources/catalogs/tpc_ds/store_sales.ion | 105 ++---- .../resources/catalogs/tpc_ds/time_dim.ion | 50 +-- .../resources/catalogs/tpc_ds/warehouse.ion | 70 +--- .../resources/catalogs/tpc_ds/web_page.ion | 70 +--- .../resources/catalogs/tpc_ds/web_returns.ion | 120 ++----- .../resources/catalogs/tpc_ds/web_sales.ion | 170 ++------- .../resources/catalogs/tpc_ds/web_site.ion | 130 ++----- .../resources/tests/aggregations.ion | 50 +-- .../kotlin/org/partiql/types/StaticType.kt | 106 +++++- .../org/partiql/value/PartiQLValueType.kt | 10 + .../org/partiql/plugins/local/LocalSchema.kt | 5 +- 53 files changed, 1013 insertions(+), 2554 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e0ca1e7a..b92a7cde73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,25 @@ Thank you to all who have contributed! ### Added ### Changed +- **Behavioral change**: The planner now does NOT support the NullType and MissingType variants of StaticType. The logic +is that the null and missing values are part of *all* data types. Therefore, one must assume that the types returned by +the planner allow for NULL and MISSING values. Similarly, the testFixtures Ion-encoded test resources +representing the catalog do not use "null" or "missing". ### Deprecated +- We have deprecated `org.partiql.type.NullType` and `org.partiql.type.MissingType`. Please see the corresponding +information in the "Changed" section. In relation to the deprecation of the above, the following APIs have also +been deprecated: + - `org.partiql.type.StaticType.MISSING` + - `org.partiql.type.StaticType.NULL` + - `org.partiql.type.StaticType.NULL_OR_MISSING` + - `org.partiql.type.StaticType.asNullable()` + - `org.partiql.type.StaticType.isNullable()` + - `org.partiql.type.StaticType.isMissable()` + - `org.partiql.type.StaticType.asOptional()` + - `org.partiql.type.AnyOfType()` + - `org.partiql.value.PartiQLValueType.NULL` + - `org.partiql.value.PartiQLValueType.MISSING` ### Fixed 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/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..dc846b7069 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,55 @@ 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) - * + * Adds the [rex]'s [Rex.type] to the typing accumulator (if the [rex] is not a literal NULL/MISSING). + */ + fun accumulate(rex: Rex) { + when (rex.isLiteralAbsent()) { + true -> accumulateUnknown() + false -> accumulate(rex.type) + } + } + + /** + * Checks for literal NULL or MISSING. + */ + 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) + } + + /** + * 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. + */ + private fun accumulateUnknown() { + args.add(ANY) + } + + /** + * This adds non-unknown types (aka not NULL / MISSING literals) to the typing accumulator. * @param type */ - fun accumulate(type: StaticType) { - val nonAbsentTypes = mutableSetOf() + private fun accumulate(type: StaticType) { 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 +110,7 @@ internal class DynamicTyper { } else -> { // Had a union; use ANY runtime - types.addAll(nonAbsentTypes) + types.addAll(allTypes) args.add(ANY) calculate(ANY) } @@ -124,31 +125,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 +153,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 +196,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..b4d0cd1229 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 @@ -76,27 +71,19 @@ internal sealed class FnMatch { * * @property signature * @property mapping - * @property isMissable TRUE when anyone of the arguments _could_ be MISSING. We *always* propagate MISSING. */ public data class Ok( public val signature: T, public val mapping: Mapping, - public val isMissable: Boolean, ) : FnMatch() /** * This represents dynamic dispatch. * * @property candidates an ordered list of potentially applicable functions to dispatch dynamically. - * @property isMissable TRUE when the argument permutations may not definitively invoke one of the candidates. You - * can think of [isMissable] as being the same as "not exhaustive". For example, if we have ABS(INT | STRING), then - * this function call [isMissable] because there isn't an `ABS(STRING)` function signature AKA we haven't exhausted - * all the arguments. On the other hand, take an "exhaustive" scenario: ABS(INT | DEC). In this case, [isMissable] - * is false because we have functions for each potential argument AKA we have exhausted the arguments. */ public data class Dynamic( - public val candidates: List>, - public val isMissable: Boolean + public val candidates: List> ) : FnMatch() public data class Error( @@ -162,12 +149,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 +159,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) } } } @@ -190,18 +171,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) } } 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 +204,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) } } } @@ -290,9 +259,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 +407,10 @@ 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, + @Suppress("DEPRECATION") + PartiQLValueType.NULL, // TODO: Remove + @Suppress("DEPRECATION") + 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..7adb377007 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.inferRexListNotNull { type -> + val struct = type as? StructType ?: return@inferRexListNotNull null val (pathType, replacementId) = inferStructLookup( struct, identifierSymbol(node.key, Identifier.CaseSensitivity.INSENSITIVE) - ) + ) ?: return@inferRexListNotNull 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() @@ -775,9 +727,7 @@ internal class PlanTyper( override fun visitRexOpCoalesce(node: Rex.Op.Coalesce, ctx: StaticType?): Rex { val args = node.args.map { visitRex(it, it.type) }.toMutableList() val typer = DynamicTyper() - args.forEach { v -> - typer.accumulate(v.type) - } + args.forEach { v -> typer.accumulate(v) } val (type, mapping) = typer.mapping() if (mapping != null) { assert(mapping.size == args.size) { "Coercion mappings `len ${mapping.size}` did not match the number of COALESCE arguments `len ${args.size}`" } @@ -804,25 +754,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 +812,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 +836,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 +848,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 +999,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 +1027,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 +1039,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 +1061,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 +1143,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 null * - 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 null * - 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 +1154,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 +1163,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 +1181,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 +1223,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 +1234,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 +1248,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 +1288,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 +1312,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 +1399,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..f27319bac7 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,51 @@ 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.isExactNumeric(): Boolean = (this is IntType || this is DecimalType) - -internal fun StaticType.isApproxNumeric(): Boolean = (this is FloatType) - internal fun StaticType.isText(): Boolean = (this is SymbolType || this is StringType) +@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.isOptional(): Boolean = when (this) { - is AnyType, MissingType -> true // Any includes Missing type - is AnyOfType -> types.any { it.isOptional() } - else -> false +/** + * Returns whether [this] *may* be of a specific type. AKA: is it the type? Is it a union that holds the type? + */ +internal inline fun StaticType.mayBeType(): Boolean { + return this.allTypes.any { it is T } } /** - * Per SQL, runtime types are always nullable + * For each type in [this] [StaticType.allTypes], the [block] will be invoked. Non-null outputs to the [block] will be + * returned. */ -@OptIn(PartiQLValueExperimental::class) -internal fun PartiQLValueType.toStaticType(): StaticType = when (this) { - PartiQLValueType.NULL -> StaticType.NULL - PartiQLValueType.MISSING -> StaticType.MISSING - else -> toNonNullStaticType().asNullable() +internal fun StaticType.inferListNotNull(block: (StaticType) -> StaticType?): List { + return this.flatten().allTypes.mapNotNull { type -> block(type) } } +/** + * For each type in [this] [StaticType.allTypes], the [block] will be invoked. Non-null outputs to the [block] will be + * returned. + */ +internal fun StaticType.inferRexListNotNull(block: (StaticType) -> Rex?): List { + return this.flatten().allTypes.mapNotNull { type -> block(type) } +} + +/** + * Per SQL, runtime types are always nullable + */ @OptIn(PartiQLValueExperimental::class) -internal fun PartiQLValueType.toNonNullStaticType(): StaticType = when (this) { +@Suppress("DEPRECATION") +internal fun PartiQLValueType.toStaticType(): StaticType = when (this) { PartiQLValueType.ANY -> StaticType.ANY PartiQLValueType.BOOL -> StaticType.BOOL PartiQLValueType.INT8 -> StaticType.INT2 @@ -83,11 +99,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 +127,7 @@ internal fun StaticType.toRuntimeTypeOrNull(): PartiQLValueType? { } } +@Suppress("DEPRECATION") @OptIn(PartiQLValueExperimental::class) private fun StaticType.asRuntimeType(): PartiQLValueType = when (this) { is AnyOfType -> PartiQLValueType.ANY @@ -139,8 +157,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 +195,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..3a3b77e76d 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,92 +2591,92 @@ 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"), catalog = "pql", - expected = StaticType.NULL + expected = ANY ), 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 ) } ), @@ -3505,14 +3432,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 +3979,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 +4002,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 +4029,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 +4053,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 +4067,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 +4090,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 +4103,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 +4119,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 +4200,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 +4321,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 +4331,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 +4356,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-planner/src/testFixtures/resources/catalogs/default/pql/main/item.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/item.ion index 2622a7b130..cac9705ceb 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/item.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/item.ion @@ -5,17 +5,11 @@ fields: [ { name: "i_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_item_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_rec", @@ -25,27 +19,18 @@ fields: [ { name: "i_rec_start_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "i_rec_end_date", - type: [ - "int64", - "null" - ] + type: "int64" }, ] }, }, { name: "i_item_desc", - type: [ - "string", - "null" - ] + type: "string" }, { name: "pricing", @@ -54,111 +39,66 @@ fields: [ { name: "i_current_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "i_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, ] }, }, { name: "i_brand_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_brand", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_class_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_category_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_category", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_manufact_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_manufact", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_size", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_formulation", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_color", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_units", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_container", - type: [ - "string", - "null" - ] + type: "string" }, { name: "manager_info", @@ -174,12 +114,11 @@ }, { name: "manager_name", - type: ["string", "null"] + type: "string" }, { name: "manager_address", type: [ - "null", { type: "struct", constraints: [ closed, unique, ordered ], @@ -190,7 +129,7 @@ }, { name: "house_number", - type: ["int32", "null"] + type: "int32" } ] } @@ -201,10 +140,7 @@ }, { name: "i_product_name", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/person.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/person.ion index 70afca8674..cadd433342 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/person.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/person.ion @@ -26,10 +26,7 @@ }, { name: "employer", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/numbers.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/numbers.ion index 311e5a474d..fe5789214b 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/numbers.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/numbers.ion @@ -6,40 +6,28 @@ name: "nullable_int16s", type: { type: "list", - items: [ - "int16", - "null" - ] + items: "int16" } }, { name: "nullable_int32s", type: { type: "list", - items: [ - "int32", - "null" - ] + items: "int32" } }, { name: "nullable_int64s", type: { type: "list", - items: [ - "int64", - "null" - ] + items: "int64" } }, { name: "nullable_ints", type: { type: "list", - items: [ - "int", - "null" - ] + items: "int" } }, { @@ -85,20 +73,14 @@ name: "nullable_float32s", type: { type: "list", - items: [ - "float32", - "null" - ] + items: "float32" } }, { name: "nullable_float64s", type: { type: "list", - items: [ - "float64", - "null" - ] + items: "float64" } }, { diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion index e4435d624c..6aac443aa4 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion @@ -10,7 +10,7 @@ }, { name: "t_bool_nul", - type: ["bool","null"], + type: "bool", }, // Exact Numeric // { @@ -19,7 +19,7 @@ // }, // { // name: "t_int8_null", -// type: ["int8", "null"], +// type: "int8", // }, { name: "t_int16", @@ -27,7 +27,7 @@ }, { name: "t_int16_null", - type: ["int16", "null"], + type: "int16", }, { name: "t_int32", @@ -35,7 +35,7 @@ }, { name: "t_int32_null", - type: ["int32", "null"], + type: "int32", }, { name: "t_int64", @@ -43,7 +43,7 @@ }, { name: "t_int64_null", - type: ["int64", "null"], + type: "int64", }, { name: "t_int", @@ -51,7 +51,7 @@ }, { name: "t_int_null", - type: ["int", "null"], + type: "int", }, { name: "t_decimal", @@ -59,7 +59,7 @@ }, { name: "t_decimal_null", - type: ["decimal", "null"], + type: "decimal", }, // Approximate Numeric { @@ -68,7 +68,7 @@ }, { name: "t_float32_null", - type: ["float32", "null"], + type: "float32", }, { name: "t_float64", @@ -76,7 +76,7 @@ }, { name: "t_float64_null", - type: ["float64", "null"], + type: "float64", }, // Strings { @@ -85,7 +85,7 @@ }, { name: "t_string_null", - type: ["string", "null"], + type: "string", }, { name: "t_clob", @@ -93,20 +93,20 @@ }, { name: "t_clob_null", - type: ["clob", "null"], + type: "clob", }, - // absent + // potentially absent { name: "t_null", - type: "null", + type: "any", }, { name: "t_missing", - type: "missing", + type: "any", }, { name: "t_absent", - type: ["null", "missing"], + type: "any", }, // collections { @@ -174,7 +174,7 @@ }, { name: "t_num_exact_null", - type: [ "int16", "int32", "int64", "int", "decimal", "null" ], + type: [ "int16", "int32", "int64", "int", "decimal" ], }, { name: "t_str", diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/call_center.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/call_center.ion index ea3dc3c061..c946260ea9 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/call_center.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/call_center.ion @@ -13,206 +13,119 @@ call_center::{ }, { name: "cc_rec_start_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "cc_rec_end_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "cc_closed_date_sk", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_open_date_sk", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_employees", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_sq_ft", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_hours", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_mkt_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_mkt_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_mkt_desc", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_market_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_division", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_division_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_company", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cc_company_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_street_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_street_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_street_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_suite_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_city", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_county", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_state", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_zip", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cc_gmt_offset", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cc_tax_percentage", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_page.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_page.ion index 272df0daa9..e46326535d 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_page.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_page.ion @@ -5,66 +5,39 @@ catalog_page::{ fields: [ { name: "cp_catalog_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cp_catalog_page_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cp_start_date_sk", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cp_end_date_sk", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cp_department", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cp_catalog_number", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cp_catalog_page_number", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cp_description", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cp_type", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_returns.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_returns.ion index bd881456c5..6562469e16 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_returns.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_returns.ion @@ -5,192 +5,111 @@ catalog_returns::{ fields: [ { name: "cr_returned_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_returned_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_refunded_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_refunded_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_refunded_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_refunded_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_returning_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_returning_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_returning_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_returning_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_call_center_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_catalog_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_ship_mode_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_warehouse_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_reason_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_order_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cr_return_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cr_return_amount", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_return_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_return_amt_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_fee", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_return_ship_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_refunded_cash", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_reversed_charge", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_store_credit", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cr_net_loss", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_sales.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_sales.ion index b6f620e99c..086632b48d 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_sales.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/catalog_sales.ion @@ -5,108 +5,63 @@ catalog_sales::{ fields: [ { name: "cs_sold_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_sold_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_bill_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_bill_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_bill_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_bill_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_call_center_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_catalog_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_ship_mode_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_warehouse_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_item_sk", @@ -114,10 +69,7 @@ catalog_sales::{ }, { name: "cs_promo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cs_order_number", @@ -125,115 +77,67 @@ catalog_sales::{ }, { name: "cs_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cs_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_discount_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_coupon_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_ext_ship_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_net_paid", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_net_paid_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_net_paid_inc_ship", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_net_paid_inc_ship_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "cs_net_profit", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer.ion index 55b76c52ec..1f3e5a859c 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer.ion @@ -5,129 +5,75 @@ customer::{ fields: [ { name: "c_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_customer_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_current_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_current_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_current_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_first_shipto_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_first_sales_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_salutation", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_first_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_last_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_preferred_cust_flag", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_birth_day", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "c_birth_month", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "c_birth_year", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "c_birth_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_login", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_email_address", - type: [ - "string", - "null" - ] + type: "string" }, { name: "c_last_review_date_sk", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_address.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_address.ion index 7105c7d866..557721601c 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_address.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_address.ion @@ -5,94 +5,55 @@ customer_address::{ fields: [ { name: "ca_address_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_address_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_street_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_street_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_street_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_suite_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_city", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_county", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_state", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_zip", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ca_gmt_offset", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ca_location_type", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_demographics.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_demographics.ion index 9634b7762c..bf5bfda9cf 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_demographics.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/customer_demographics.ion @@ -5,66 +5,39 @@ customer_demographics::{ fields: [ { name: "cd_demo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cd_gender", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cd_marital_status", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cd_education_status", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cd_purchase_estimate", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cd_credit_rating", - type: [ - "string", - "null" - ] + type: "string" }, { name: "cd_dep_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cd_dep_employed_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "cd_dep_college_count", - type: [ - "int32", - "null" - ] + type: "int32" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/date_dim.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/date_dim.ion index 9ae5bb3789..99624477b7 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/date_dim.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/date_dim.ion @@ -5,199 +5,115 @@ date_dim::{ fields: [ { name: "d_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_date_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "d_month_seq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_week_seq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_quarter_seq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_year", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_dow", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_moy", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_dom", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_qoy", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_fy_year", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_fy_quarter_seq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_fy_week_seq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_day_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_quarter_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_holiday", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_weekend", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_following_holiday", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_first_dom", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_last_dom", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_same_day_ly", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_same_day_lq", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "d_current_day", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_current_week", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_current_month", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_current_quarter", - type: [ - "string", - "null" - ] + type: "string" }, { name: "d_current_year", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/dbgen_version.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/dbgen_version.ion index 1f66c8ea37..7c0b3760b5 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/dbgen_version.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/dbgen_version.ion @@ -5,31 +5,19 @@ dbgen_version::{ fields: [ { name: "dv_version", - type: [ - "string", - "null" - ] + type: "string" }, { name: "dv_create_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "dv_create_time", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "dv_cmdline_args", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/household_demographics.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/household_demographics.ion index 6f361cfb82..04003e132e 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/household_demographics.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/household_demographics.ion @@ -5,38 +5,23 @@ household_demographics::{ fields: [ { name: "hd_demo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "hd_income_band_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "hd_buy_potential", - type: [ - "string", - "null" - ] + type: "string" }, { name: "hd_dep_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "hd_vehicle_count", - type: [ - "int32", - "null" - ] + type: "int32" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/income_band.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/income_band.ion index ccf094c26d..4ec6f28903 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/income_band.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/income_band.ion @@ -5,24 +5,15 @@ income_band::{ fields: [ { name: "ib_income_band_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ib_lower_bound", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "ib_upper_bound", - type: [ - "int32", - "null" - ] + type: "int32" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/inventory.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/inventory.ion index 6552d9111c..2cf1765947 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/inventory.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/inventory.ion @@ -5,31 +5,19 @@ inventory::{ fields: [ { name: "inv_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "inv_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "inv_warehouse_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "inv_quantity_on_hand", - type: [ - "int32", - "null" - ] + type: "int32" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/item.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/item.ion index b6cb30233c..becd58f9c6 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/item.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/item.ion @@ -5,157 +5,91 @@ item::{ fields: [ { name: "i_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_item_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_rec_start_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "i_rec_end_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "i_item_desc", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_current_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "i_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "i_brand_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_brand", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_class_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_category_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_category", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_manufact_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_manufact", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_size", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_formulation", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_color", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_units", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_container", - type: [ - "string", - "null" - ] + type: "string" }, { name: "i_manager_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "i_product_name", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/promotion.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/promotion.ion index be727f19f6..c6b3b8f766 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/promotion.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/promotion.ion @@ -5,136 +5,79 @@ promotion::{ fields: [ { name: "p_promo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_promo_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_start_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_end_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "p_response_targe", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "p_promo_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_dmail", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_email", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_catalog", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_tv", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_radio", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_press", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_event", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_demo", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_channel_details", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_purpose", - type: [ - "string", - "null" - ] + type: "string" }, { name: "p_discount_active", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/reason.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/reason.ion index 4223e44e71..2801abf7d6 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/reason.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/reason.ion @@ -5,24 +5,15 @@ reason::{ fields: [ { name: "r_reason_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "r_reason_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "r_reason_desc", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/ship_mode.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/ship_mode.ion index 08ae3fbcb1..d80c3909df 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/ship_mode.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/ship_mode.ion @@ -5,45 +5,27 @@ ship_mode::{ fields: [ { name: "sm_ship_mode_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sm_ship_mode_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sm_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sm_code", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sm_carrier", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sm_contract", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store.ion index b0a3566a7d..65cf1aa5f6 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store.ion @@ -13,192 +13,111 @@ store::{ }, { name: "s_rec_start_date", - type: [ - "date", - "null" - ] + type: "date" }, { name: "s_rec_end_date", - type: [ - "date", - "null" - ] + type: "date" }, { name: "s_closed_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_store_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_number_employees", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "s_floor_space", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "s_hours", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_market_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "s_geography_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_market_desc", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_market_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_division_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "s_division_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_company_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "s_company_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_street_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_street_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_street_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_suite_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_city", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_county", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_state", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_zip", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "s_gmt_offset", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "s_tax_precentage", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_returns.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_returns.ion index 55347e7e46..859746a5d8 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_returns.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_returns.ion @@ -5,17 +5,11 @@ store_returns::{ fields: [ { name: "sr_returned_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_return_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_item_sk", @@ -23,45 +17,27 @@ store_returns::{ }, { name: "sr_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_store_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_reason_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "sr_ticket_number", @@ -69,73 +45,43 @@ store_returns::{ }, { name: "sr_return_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "sr_return_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_return_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_return_amt_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_fee", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_return_ship_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_refunded_cash", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_reversed_charge", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_store_credit", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "sr_net_loss", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_sales.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_sales.ion index 64676ed271..af84c06375 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_sales.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/store_sales.ion @@ -5,17 +5,11 @@ store_sales::{ fields: [ { name: "ss_sold_date_sk", - type: [ - "date", - "null" - ] + type: "date" }, { name: "ss_sold_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_item_sk", @@ -23,45 +17,27 @@ store_sales::{ }, { name: "ss_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_store_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_promo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ss_ticket_number", @@ -69,94 +45,55 @@ store_sales::{ }, { name: "ss_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "ss_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_ext_discount_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_ext_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_ext_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_ext_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_ext_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_coupon_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_net_paid", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_net_paid_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ss_net_profit", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/time_dim.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/time_dim.ion index 55b5ce4f47..7444737c03 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/time_dim.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/time_dim.ion @@ -5,73 +5,43 @@ time_dim::{ fields: [ { name: "t_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "t_time_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "t_time", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "t_hour", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "t_minute", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "t_second", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "t_am_pm", - type: [ - "string", - "null" - ] + type: "string" }, { name: "t_shift", - type: [ - "string", - "null" - ] + type: "string" }, { name: "t_sub_shift", - type: [ - "string", - "null" - ] + type: "string" }, { name: "t_meal_time", - type: [ - "string", - "null" - ] + type: "string" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/warehouse.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/warehouse.ion index 170d486ce5..dac47af60d 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/warehouse.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/warehouse.ion @@ -5,101 +5,59 @@ warehouse::{ fields: [ { name: "w_warehouse_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_warehouse_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_warehouse_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_warehouse_sq_ft", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "w_street_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_street_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_street_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_suite_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_city", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_county", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_state", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_zip", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "w_gmt_offset", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_page.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_page.ion index 56b421dc5e..e60bf06f90 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_page.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_page.ion @@ -5,101 +5,59 @@ web_page::{ fields: [ { name: "wp_web_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_web_page_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_rec_start_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "wp_rec_end_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "wp_creation_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_access_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_autogen_flag", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_url", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wp_char_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "wp_link_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "wp_image_count", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "wp_max_ad_count", - type: [ - "int32", - "null" - ] + type: "int32" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_returns.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_returns.ion index 336733c5ce..c1bfaf3b20 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_returns.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_returns.ion @@ -5,171 +5,99 @@ web_returns::{ fields: [ { name: "wr_returned_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_returned_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_refunded_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_refunded_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_refunded_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_refunded_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_returning_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_returning_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_returning_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_returning_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_web_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_reason_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_order_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "wr_return_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "wr_return_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_return_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_return_amt_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_fee", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_return_ship_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_refunded_cash", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_reversed_charge", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_account_credit", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "wr_net_loss", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_sales.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_sales.ion index cb1f356148..3e50eee4e2 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_sales.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_sales.ion @@ -5,241 +5,139 @@ web_sales::{ fields: [ { name: "ws_sold_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_sold_time_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_item_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_bill_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_bill_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_bill_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_bill_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_customer_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_cdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_hdemo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_addr_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_web_page_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_web_site_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_ship_mode_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_warehouse_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_promo_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_order_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "ws_quantity", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "ws_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_discount_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_sales_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_wholesale_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_list_price", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_coupon_amt", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_ext_ship_cost", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_net_paid", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_net_paid_inc_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_net_paid_inc_ship", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_net_paid_inc_ship_tax", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "ws_net_profit", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_site.ion b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_site.ion index 372c16bac1..109750191c 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_site.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/tpc_ds/web_site.ion @@ -5,185 +5,107 @@ web_site::{ fields: [ { name: "web_site_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_site_id", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_rec_start_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "web_rec_end_date", - type: [ - "int64", - "null" - ] + type: "int64" }, { name: "web_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_open_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_close_date_sk", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_mkt_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "web_mkt_class", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_mkt_desc", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_market_manager", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_company_id", - type: [ - "int32", - "null" - ] + type: "int32" }, { name: "web_company_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_street_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_street_name", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_street_type", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_suite_number", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_city", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_county", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_state", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_zip", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_country", - type: [ - "string", - "null" - ] + type: "string" }, { name: "web_gmt_offset", - type: [ - "float64", - "null" - ] + type: "float64" }, { name: "web_tax_percentage", - type: [ - "float64", - "null" - ] + type: "float64" } ] } diff --git a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion b/partiql-planner/src/testFixtures/resources/tests/aggregations.ion index 2badeb8dd6..b78c2a0d43 100644 --- a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion +++ b/partiql-planner/src/testFixtures/resources/tests/aggregations.ion @@ -19,10 +19,7 @@ suite::{ fields: [ { name: "avg", - type: [ - "int32", - "null", - ], + type: "int32", }, ], }, @@ -56,10 +53,7 @@ suite::{ fields: [ { name: "min", - type: [ - "int32", - "null", - ], + type: "int32", }, ], }, @@ -76,10 +70,7 @@ suite::{ fields: [ { name: "max", - type: [ - "int32", - "null", - ], + type: "int32", }, ], }, @@ -96,10 +87,7 @@ suite::{ fields: [ { name: "sum", - type: [ - "int32", - "null", - ], + type: "int32", }, ], }, @@ -116,10 +104,7 @@ suite::{ fields: [ { name: "avg", - type: [ - "int32", - "null" - ], + type: "int32", }, ], }, @@ -153,10 +138,7 @@ suite::{ fields: [ { name: "min", - type: [ - "int32", - "null" - ], + type: "int32", }, ], }, @@ -173,10 +155,7 @@ suite::{ fields: [ { name: "max", - type: [ - "int32", - "null" - ], + type: "int32", }, ], }, @@ -193,10 +172,7 @@ suite::{ fields: [ { name: "sum", - type: [ - "int32", - "null" - ], + type: "int32", }, ], }, @@ -236,10 +212,7 @@ suite::{ fields: [ { name: "_1", - type: [ - "float32", - "null" - ], + type: "float32", }, { name: "y", @@ -265,10 +238,7 @@ suite::{ fields: [ { name: "_1", - type: [ - "float32", - "null" - ], + type: "float32", }, { name: "a", 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..1603c0cc28 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 @@ -82,8 +82,6 @@ public fun StringElement.toStaticType(): StaticType = when (textValue) { "list" -> error("`list` is not an atomic type") "sexp" -> error("`sexp` is not an atomic type") "struct" -> error("`struct` is not an atomic type") - "null" -> StaticType.NULL - "missing" -> StaticType.MISSING else -> error("Invalid type `$textValue`") } @@ -168,13 +166,12 @@ public fun StaticType.toIon(): IonElement = when (this) { IntType.IntRangeConstraint.LONG -> ionString("int64") IntType.IntRangeConstraint.UNCONSTRAINED -> ionString("int") } - MissingType -> ionString("missing") - is NullType -> ionString("null") is StringType -> ionString("string") // TODO char is StructType -> this.toIon() is SymbolType -> ionString("symbol") is TimeType -> ionString("time") is TimestampType -> ionString("timestamp") + is MissingType, is NullType -> error("Cannot output absent type ($this) to Ion.") } private fun AnyOfType.toIon(): IonElement { From 4ae74d351fafa02bd82919b2a2c434d0a2fcfa17 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Fri, 17 May 2024 10:25:09 -0700 Subject: [PATCH 02/13] Addresses PR comments --- .../planner/internal/typer/DynamicTyper.kt | 4 ++- .../planner/internal/typer/FnResolver.kt | 4 +-- .../planner/internal/typer/PlanTyper.kt | 34 +++++-------------- .../planner/internal/typer/TypeUtils.kt | 24 +++---------- .../internal/typer/PartiQLTyperTestBase.kt | 2 +- .../kotlin/org/partiql/planner/util/Utils.kt | 6 +--- .../resources/catalogs/default/pql/t_item.ion | 13 ------- .../resources/inputs/basics/case.sql | 15 -------- .../resources/inputs/basics/nullif.sql | 4 +-- .../kotlin/org/partiql/types/StaticType.kt | 31 ++++++++--------- .../org/partiql/plugins/local/LocalSchema.kt | 1 + 11 files changed, 38 insertions(+), 100 deletions(-) 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 dc846b7069..eebbf36421 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 @@ -4,6 +4,8 @@ package org.partiql.planner.internal.typer import org.partiql.planner.internal.ir.Rex import org.partiql.types.StaticType +import org.partiql.value.MissingValue +import org.partiql.value.NullValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType import org.partiql.value.PartiQLValueType.ANY @@ -71,7 +73,7 @@ internal class DynamicTyper { */ 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) + return op is Rex.Op.Lit && (op.value is MissingValue || op.value is NullValue) } /** 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 b4d0cd1229..d595a051b2 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 @@ -408,9 +408,9 @@ internal class FnResolver(private val header: Header) { // This does not imply the ability to CAST; this defines function resolution behavior. private val precedence: Map = listOf( @Suppress("DEPRECATION") - PartiQLValueType.NULL, // TODO: Remove + PartiQLValueType.NULL, // TODO: Remove once functions no longer specify parameter/return types with the NULL type. @Suppress("DEPRECATION") - PartiQLValueType.MISSING, // TODO: Remove + PartiQLValueType.MISSING, // TODO: Remove once functions no longer specify parameter/return types with the MISSING type. 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 7adb377007..ba5c9cb15b 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 @@ -91,8 +91,8 @@ import org.partiql.types.StructType import org.partiql.types.TupleConstraint import org.partiql.types.function.FunctionSignature import org.partiql.value.BoolValue +import org.partiql.value.MissingValue import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType import org.partiql.value.TextValue import org.partiql.value.boolValue import org.partiql.value.stringValue @@ -475,8 +475,8 @@ internal class PlanTyper( } (type as CollectionType).elementType }.toSet() - val finalType = unionOf(elementTypes).flatten() - return rex(finalType.swallowAny(), rexOpPathIndex(root, key)) + val finalType = unionOf(elementTypes) + return rex(finalType, rexOpPathIndex(root, key)) } override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Rex { @@ -516,8 +516,7 @@ internal class PlanTyper( handleAlwaysMissing() return rex(ANY, rexOpPathKey(root, key)) } - // TODO: SwallowAny should happen by default - return rex(unionOf(elementType).swallowAny(), rexOpPathKey(root, key)) + return rex(unionOf(elementType), rexOpPathKey(root, key)) } override fun visitRexOpPathSymbol(node: Rex.Op.Path.Symbol, ctx: StaticType?): Rex { @@ -551,8 +550,7 @@ internal class PlanTyper( 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() + else -> unionOf(paths.map { it.type }.toSet()) } // replace step only if all are disambiguated @@ -562,19 +560,7 @@ internal class PlanTyper( true -> firstPathOp false -> rexOpPathSymbol(root, node.key) } - return rex(type.swallowAny(), replacementOp) - } - - /** - * "Swallows" ANY. If ANY is one of the types in the UNION type, we return ANY. If not, we flatten and return - * the [type]. - */ - private fun StaticType.swallowAny(): StaticType { - val flattened = this.flatten() - return when (flattened.allTypes.any { it is AnyType }) { - true -> ANY - false -> flattened - } + return rex(type, replacementOp) } private fun rexString(str: String) = rex(STRING, rexOpLit(stringValue(str))) @@ -633,13 +619,13 @@ internal class PlanTyper( // Check literal missing inputs val argAlwaysMissing = args.any { val op = it.op as? Rex.Op.Lit ?: return@any false - op.value.type == PartiQLValueType.MISSING + op.value is MissingValue } 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")) { + if (match.signature.name !in listOf("is_null", "is_missing", "eq", "and", "or", "not")) { handleAlwaysMissing() } } @@ -1163,9 +1149,7 @@ internal class PlanTyper( isClosed && isOrdered -> { struct.fields.firstOrNull { entry -> binding.isEquivalentTo(entry.key) }?.let { (sensitive(it.key) to it.value) - } ?: run { - return null - } + } ?: return null } // 2. Struct is closed isClosed -> { 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 f27319bac7..bf3bd07116 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 @@ -28,22 +28,8 @@ 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.isText(): Boolean = (this is SymbolType || this is StringType) -@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) - /** * Returns whether [this] *may* be of a specific type. AKA: is it the type? Is it a union that holds the type? */ @@ -52,16 +38,16 @@ internal inline fun StaticType.mayBeType(): Boolean { } /** - * For each type in [this] [StaticType.allTypes], the [block] will be invoked. Non-null outputs to the [block] will be - * returned. + * For each type in [this] type's [StaticType.allTypes], the [block] will be invoked. Non-null outputs of the [block]'s + * invocation will be returned. */ internal fun StaticType.inferListNotNull(block: (StaticType) -> StaticType?): List { return this.flatten().allTypes.mapNotNull { type -> block(type) } } /** - * For each type in [this] [StaticType.allTypes], the [block] will be invoked. Non-null outputs to the [block] will be - * returned. + * For each type in [this] type's [StaticType.allTypes], the [block] will be invoked. Non-null outputs of the [block]'s + * invocation will be returned. */ internal fun StaticType.inferRexListNotNull(block: (StaticType) -> Rex?): List { return this.flatten().allTypes.mapNotNull { type -> block(type) } @@ -195,7 +181,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) // TODO: double check this + StructType.Field(field.key, field.value) } 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 243c384ef6..a1867f4a39 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 @@ -101,7 +101,7 @@ abstract class PartiQLTyperTestBase { val actualType = root.type assert(actualType == StaticType.ANY) { buildString { - this.appendLine(" expected Type is : MISSING") + this.appendLine(" expected Type is : ANY") this.appendLine("actual Type is : $actualType") PlanPrinter.append(this, result.plan) } 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 adcd569fa9..a5da57fa49 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 @@ -29,11 +29,7 @@ fun cartesianProduct(a: List, b: List, vararg lists: List): Set = mapOf()): StaticType = - unionOf(types.toSet(), metas).flatten() + unionOf(types.toSet(), metas) /** * 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] + * are defined as the union of all values typed as [types]. The returned type is flattened. * * @param types [StaticType] to be unioned. * @return [StaticType] representing the union of [types] @@ -33,12 +34,8 @@ public sealed class StaticType { @Suppress("DEPRECATION") public fun unionOf( types: Set, - metas: Map = mapOf(), - errorOnEmptyTypes: Boolean = false + metas: Map = mapOf() ): 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() @@ -47,7 +44,7 @@ public sealed class StaticType { /** * 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] + * are defined as the union of all values typed as [types]. The returned type is flattened. * * @param types [StaticType] to be unioned. * @return [StaticType] representing the union of [types] @@ -151,7 +148,8 @@ public sealed class StaticType { */ @Suppress("DEPRECATION") @Deprecated( - message = "This will be removed in a future major-version bump.", + message = "This will be removed in a future major-version bump. All types include the null value. Therefore," + + " this method is redundant.", replaceWith = ReplaceWith("") ) public fun asNullable(): StaticType = @@ -168,7 +166,8 @@ public sealed class StaticType { */ @Suppress("DEPRECATION") @Deprecated( - message = "This will be removed in a future major-version bump.", + message = "This will be removed in a future major-version bump. All types include the missing value." + + " Therefore, this method is redundant.", replaceWith = ReplaceWith("") ) public fun asOptional(): StaticType = @@ -214,7 +213,8 @@ public sealed class StaticType { */ @Suppress("DEPRECATION") @Deprecated( - message = "This will be removed in a future major-version bump.", + message = "This will be removed in a future major-version bump. All types are considered nullable. Therefore" + + " this method is redundant.", replaceWith = ReplaceWith("true") ) public fun isNullable(): Boolean = @@ -231,7 +231,8 @@ public sealed class StaticType { */ @Suppress("DEPRECATION") @Deprecated( - message = "This will be removed in a future major-version bump.", + message = "This will be removed in a future major-version bump. All types are considered missable. Therefore," + + " this method is redundant.", replaceWith = ReplaceWith("true") ) public fun isMissable(): Boolean = @@ -245,10 +246,6 @@ 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 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 1603c0cc28..03897ca917 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 @@ -82,6 +82,7 @@ public fun StringElement.toStaticType(): StaticType = when (textValue) { "list" -> error("`list` is not an atomic type") "sexp" -> error("`sexp` is not an atomic type") "struct" -> error("`struct` is not an atomic type") + "null", "missing" -> error("Absent values ($textValue) do not have a corresponding type.") else -> error("Invalid type `$textValue`") } From b82ac8c77355be59874a57383f64813fb6b8efc8 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Mon, 20 May 2024 11:14:40 -0700 Subject: [PATCH 03/13] Per PR feedback, removes NULL/MISSING from StaticType.ALL_TYPES --- .../partiql/lang/ast/passes/inference/StaticTypeCastTests.kt | 2 ++ .../visitors/inferencer/InferencerMultipleProblemsTests.kt | 2 ++ .../eval/visitors/inferencer/InferencerNaryArithmeticTests.kt | 2 ++ .../eval/visitors/inferencer/InferencerNaryBetweenTests.kt | 2 ++ .../inferencer/InferencerNaryComparisonAndEqualityTests.kt | 2 ++ .../eval/visitors/inferencer/InferencerNaryConcatTests.kt | 2 ++ .../lang/eval/visitors/inferencer/InferencerNaryLikeTests.kt | 2 ++ .../eval/visitors/inferencer/InferencerNaryLogicalTests.kt | 2 ++ .../lang/eval/visitors/inferencer/InferencerNaryOpInTests.kt | 2 ++ .../eval/visitors/inferencer/InferencerTrimFunctionTests.kt | 2 ++ .../visitors/inferencer/InferencerUnaryArithmeticOpTests.kt | 2 ++ partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt | 4 ---- 12 files changed, 22 insertions(+), 4 deletions(-) diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeCastTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeCastTests.kt index a21eef7a44..05056cb742 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeCastTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/ast/passes/inference/StaticTypeCastTests.kt @@ -3,6 +3,7 @@ package org.partiql.lang.ast.passes.inference import junitparams.JUnitParamsRunner import junitparams.Parameters import org.junit.Assert.assertEquals +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.partiql.types.DecimalType @@ -85,6 +86,7 @@ class StaticTypeCastTests { @Test @Parameters + @Ignore("StaticType.ALL_TYPES no longer supports NULL/MISSING") // @Test comes from JUnit4, and therefore we must use @Ignore. fun unionTypeCastTests(tc: TestCase) = runTest(tc) @Test diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerMultipleProblemsTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerMultipleProblemsTests.kt index d575d29830..8bbb41a2a9 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerMultipleProblemsTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerMultipleProblemsTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -20,6 +21,7 @@ import org.partiql.types.StructType class InferencerMultipleProblemsTests { @ParameterizedTest + @Disabled @MethodSource("parametersForMultipleInferenceProblemsTests") fun multipleInferenceProblemsTests(tc: TestCase) = runTest(tc) diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryArithmeticTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryArithmeticTests.kt index d98031af4b..02e66c061d 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryArithmeticTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryArithmeticTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -29,6 +30,7 @@ class InferencerNaryArithmeticTests { @ParameterizedTest @MethodSource("parametersForNAryArithmeticTests") + @Disabled fun naryArithmeticInferenceTests(tc: InferencerTestUtil.TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryBetweenTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryBetweenTests.kt index b11b853e6c..a57184c9bd 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryBetweenTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryBetweenTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -26,6 +27,7 @@ import org.partiql.types.StaticType class InferencerNaryBetweenTests { @ParameterizedTest @MethodSource("parametersForNAryBetweenTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun naryBetweenInferenceTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryComparisonAndEqualityTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryComparisonAndEqualityTests.kt index 1b38c53ff6..c2f2f2bf11 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryComparisonAndEqualityTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryComparisonAndEqualityTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -33,6 +34,7 @@ import org.partiql.types.StructType class InferencerNaryComparisonAndEqualityTests { @ParameterizedTest @MethodSource("parametersForNAryComparisonAndEqualityTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun naryComparisonAndEqualityInferenceTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryConcatTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryConcatTests.kt index 35d364ba82..b7c20bf6f4 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryConcatTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryConcatTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -25,6 +26,7 @@ import org.partiql.types.StringType class InferencerNaryConcatTests { @ParameterizedTest @MethodSource("parametersForNAryConcatTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun naryConcatInferenceTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLikeTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLikeTests.kt index 7114ac031d..3da73fd972 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLikeTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLikeTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -22,6 +23,7 @@ class InferencerNaryLikeTests { @ParameterizedTest @MethodSource("parametersForNAryLikeTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun naryLikeInferenceTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLogicalTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLogicalTests.kt index 3211057dd3..5c29463325 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLogicalTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryLogicalTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -25,6 +26,7 @@ import org.partiql.types.StaticType class InferencerNaryLogicalTests { @ParameterizedTest @MethodSource("parametersForNAryLogicalTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun naryLogicalInferenceTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryOpInTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryOpInTests.kt index 14aa60fa93..1ed1b5c31a 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryOpInTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerNaryOpInTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -31,6 +32,7 @@ import org.partiql.types.StaticType class InferencerNaryOpInTests { @ParameterizedTest @MethodSource("parametersForNAryOpInTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun nAryOpInTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerTrimFunctionTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerTrimFunctionTests.kt index 94e9d5f420..24df6f7e59 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerTrimFunctionTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerTrimFunctionTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.lang.eval.visitors.inferencer.InferencerTestUtil.TestCase @@ -10,6 +11,7 @@ import org.partiql.types.StaticType class InferencerTrimFunctionTests { @ParameterizedTest @MethodSource("parametersForTrimFunctionTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun trimFunctionTests(tc: TestCase) = runTest(tc) companion object { diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerUnaryArithmeticOpTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerUnaryArithmeticOpTests.kt index fdcca5ee45..140dfe07c0 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerUnaryArithmeticOpTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/eval/visitors/inferencer/InferencerUnaryArithmeticOpTests.kt @@ -1,5 +1,6 @@ package org.partiql.lang.eval.visitors.inferencer +import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.errors.Problem @@ -15,6 +16,7 @@ import org.partiql.types.StaticType class InferencerUnaryArithmeticOpTests { @ParameterizedTest @MethodSource("parametersForUnaryArithmeticOpTests") + @Disabled("StaticType.ALL_TYPES no longer supports NULL/MISSING") fun unaryArithmeticInferenceTests(tc: InferencerTestUtil.TestCase) = InferencerTestUtil.runTest(tc) companion object { 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 15804766ef..b012d5ceef 100644 --- a/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt +++ b/partiql-types/src/main/kotlin/org/partiql/types/StaticType.kt @@ -115,10 +115,6 @@ public sealed class StaticType { @OptIn(PartiQLTimestampExperimental::class) @JvmStatic public val ALL_TYPES: List = listOf( - @Suppress("DEPRECATION") - MISSING, - @Suppress("DEPRECATION") - NULL, BOOL, INT2, INT4, From 8d5f1f908dc93e97a7b55ea57ff892a80f17f139 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Mon, 20 May 2024 11:39:42 -0700 Subject: [PATCH 04/13] Per PR feedback, removes NULL/MISSING from Ion-encoded typing tests --- .../internal/typer/PlanTyperTestsPorted.kt | 10 ---- .../resources/catalogs/default/pql/t_item.ion | 48 ------------------- .../resources/inputs/basics/case.sql | 47 ++++++++---------- .../resources/inputs/basics/coalesce.sql | 22 ++++----- .../resources/inputs/basics/nullif.sql | 44 ++++++++--------- .../resources/tests/aggregations.ion | 10 ++-- 6 files changed, 57 insertions(+), 124 deletions(-) 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 3a3b77e76d..e4d8e12b6f 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 @@ -2446,11 +2446,6 @@ class PlanTyperTestsPorted { catalog = "pql", expected = StaticType.INT8 ), - SuccessTestCase( - key = PartiQLTest.Key("basics", "case-when-08"), - catalog = "pql", - expected = StaticType.INT, - ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-09"), catalog = "pql", @@ -2638,11 +2633,6 @@ class PlanTyperTestsPorted { catalog = "pql", expected = StaticType.INT4 ), - SuccessTestCase( - key = PartiQLTest.Key("basics", "nullif-10"), - catalog = "pql", - expected = StaticType.INT4 - ), SuccessTestCase( key = PartiQLTest.Key("basics", "nullif-11"), catalog = "pql", diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion index 2498579413..66129fd472 100644 --- a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion @@ -8,93 +8,49 @@ name: "t_bool", type: "bool", }, - { - name: "t_bool_nul", - type: "bool", - }, // Exact Numeric // { // name: "t_int8", // type: "int8", -// }, -// { -// name: "t_int8_null", -// type: "int8", // }, { name: "t_int16", type: "int16", }, - { - name: "t_int16_null", - type: "int16", - }, { name: "t_int32", type: "int32", }, - { - name: "t_int32_null", - type: "int32", - }, { name: "t_int64", type: "int64", }, - { - name: "t_int64_null", - type: "int64", - }, { name: "t_int", type: "int", }, - { - name: "t_int_null", - type: "int", - }, { name: "t_decimal", type: "decimal", }, - { - name: "t_decimal_null", - type: "decimal", - }, // Approximate Numeric { name: "t_float32", type: "float32", }, - { - name: "t_float32_null", - type: "float32", - }, { name: "t_float64", type: "float64", }, - { - name: "t_float64_null", - type: "float64", - }, // Strings { name: "t_string", type: "string", }, - { - name: "t_string_null", - type: "string", - }, { name: "t_clob", type: "clob", }, - { - name: "t_clob_null", - type: "clob", - }, // collections { name: "t_bag", @@ -159,10 +115,6 @@ name: "t_num_exact", type: [ "int16", "int32", "int64", "int", "decimal" ], }, - { - name: "t_num_exact_null", - type: [ "int16", "int32", "int64", "int", "decimal" ], - }, { name: "t_str", type: [ "clob", "string" ], diff --git a/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql b/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql index c74b5991a1..58bbe7938c 100644 --- a/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql +++ b/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql @@ -1,3 +1,7 @@ +-- noinspection SqlDialectInspectionForFile + +-- noinspection SqlNoDataSourceInspectionForFile + -- ----------------------------- -- Exact Numeric -- ----------------------------- @@ -58,25 +62,16 @@ CASE t_item.t_string ELSE t_item.t_int16 -- cast(.. AS INT8) END; ---#[case-when-08] --- type: (int|null) --- nullable default -CASE t_item.t_string - WHEN 'a' THEN t_item.t_int16 -- cast(.. AS INT) - WHEN 'b' THEN t_item.t_int32 -- cast(.. AS INT) - ELSE t_item.t_int_null -- INT -END; - --#[case-when-09] --- type: (int|null) +-- type: (int) CASE t_item.t_string - WHEN 'a' THEN t_item.t_int16_null -- cast(.. AS INT) + WHEN 'a' THEN t_item.t_int16 -- cast(.. AS INT) WHEN 'b' THEN t_item.t_int32 -- cast(.. AS INT) ELSE t_item.t_int END; --#[case-when-10] --- type: (decimal|null) +-- type: (decimal) -- nullable branch CASE t_item.t_string WHEN 'a' THEN t_item.t_decimal @@ -103,7 +98,7 @@ CASE t_item.t_string END; --#[case-when-13] --- type: (float64|null) +-- type: (float64) -- nullable branch CASE t_item.t_string WHEN 'a' THEN t_item.t_int @@ -123,7 +118,7 @@ CASE t_item.t_string END; --#[case-when-15] --- type: (string|null) +-- type: (string) -- null default CASE t_item.t_string WHEN 'a' THEN t_item.t_string @@ -139,7 +134,7 @@ CASE t_item.t_string END; --#[case-when-17] --- type: (clob|null) +-- type: (clob) -- null default CASE t_item.t_string WHEN 'a' THEN t_item.t_string @@ -152,14 +147,14 @@ END; -- ---------------------------------- --#[case-when-18] --- type: (string|null) +-- type: (string) CASE t_item.t_string WHEN 'a' THEN NULL ELSE 'default' END; --#[case-when-19] --- type: (string|null) +-- type: (string) CASE t_item.t_string WHEN 'a' THEN NULL WHEN 'b' THEN NULL @@ -169,14 +164,14 @@ CASE t_item.t_string END; --#[case-when-20] --- type: null +-- type: any -- no default, null anyways CASE t_item.t_string WHEN 'a' THEN NULL END; --#[case-when-21] --- type: (string|null) +-- type: (string) -- no default CASE t_item.t_string WHEN 'a' THEN 'ok!' @@ -195,7 +190,7 @@ CASE t_item.t_string END; --#[case-when-25] --- type: (int32|int64|string|null) +-- type: (int32|int64|string) CASE t_item.t_string WHEN 'a' THEN t_item.t_int32 WHEN 'b' THEN t_item.t_int64 @@ -204,10 +199,10 @@ CASE t_item.t_string END; --#[case-when-26] --- type: (int32|int64|string|null) +-- type: (int32|int64|string) CASE t_item.t_string WHEN 'a' THEN t_item.t_int32 - WHEN 'b' THEN t_item.t_int64_null + WHEN 'b' THEN t_item.t_int64 ELSE 'default' END; @@ -220,21 +215,21 @@ CASE t_item.t_string END; --#[case-when-28] --- type: (int16|int32|int64|int|decimal|string|clob|null) +-- type: (int16|int32|int64|int|decimal|string|clob) CASE t_item.t_string WHEN 'a' THEN t_item.t_num_exact WHEN 'b' THEN t_item.t_str END; --#[case-when-29] --- type: (struct_a|struct_b|null) +-- type: (struct_a|struct_b) CASE t_item.t_string WHEN 'a' THEN t_item.t_struct_a WHEN 'b' THEN t_item.t_struct_b END; --#[case-when-30] --- type: missing +-- type: any CASE t_item.t_string WHEN 'a' THEN MISSING WHEN 'b' THEN MISSING @@ -272,7 +267,7 @@ END; --#[case-when-34] -- type: (any) CASE t_item.t_string - WHEN 'a' THEN t_item.t_int32_null + WHEN 'a' THEN t_item.t_int32 WHEN 'b' THEN t_item.t_any ELSE t_item.t_any END; diff --git a/partiql-planner/src/testFixtures/resources/inputs/basics/coalesce.sql b/partiql-planner/src/testFixtures/resources/inputs/basics/coalesce.sql index c8e10d18fa..6f87d4a01c 100644 --- a/partiql-planner/src/testFixtures/resources/inputs/basics/coalesce.sql +++ b/partiql-planner/src/testFixtures/resources/inputs/basics/coalesce.sql @@ -11,15 +11,15 @@ COALESCE(1, 2); COALESCE(1, 1.23); --#[coalesce-03] --- type: (null | decimal) +-- type: (decimal) COALESCE(NULL, 1, 1.23); --#[coalesce-04] --- type: (null | missing | decimal) +-- type: (decimal) COALESCE(NULL, MISSING, 1, 1.23); --#[coalesce-05] --- type: (null | missing | decimal); same as above +-- type: (decimal); same as above COALESCE(1, 1.23, NULL, MISSING); --#[coalesce-06] @@ -35,31 +35,31 @@ COALESCE(t_item.t_int32, t_item.t_int32); COALESCE(t_item.t_int64, t_item.t_int32); --#[coalesce-09] --- type: (int64 | null) -COALESCE(t_item.t_int64_null, t_item.t_int32, t_item.t_int32_null); +-- type: (int64) +COALESCE(t_item.t_int64, t_item.t_int32, t_item.t_int32); --#[coalesce-10] --- type: (int64 | null | missing) -COALESCE(t_item.t_int64_null, t_item.t_int32, t_item.t_int32_null, MISSING); +-- type: (int64) +COALESCE(t_item.t_int64, t_item.t_int32, t_item.t_int32, MISSING); --#[coalesce-11] -- type: (int64 | string) COALESCE(t_item.t_int64, t_item.t_string); --#[coalesce-12] --- type: (int64 | null | string) -COALESCE(t_item.t_int64_null, t_item.t_string); +-- type: (int64 | string) +COALESCE(t_item.t_int64, t_item.t_string); --#[coalesce-13] -- type: (int16 | int32 | int64 | int | decimal) COALESCE(t_item.t_num_exact, t_item.t_int32); --#[coalesce-14] --- type: (int16 | int32 | int64 | int | decimal, string) +-- type: (int16 | int32 | int64 | int | decimal | string) COALESCE(t_item.t_num_exact, t_item.t_string); --#[coalesce-15] --- type: (int16 | int32 | int64 | int | decimal, string, null) +-- type: (int16 | int32 | int64 | int | decimal | string) COALESCE(t_item.t_num_exact, t_item.t_string, NULL); --#[coalesce-16] diff --git a/partiql-planner/src/testFixtures/resources/inputs/basics/nullif.sql b/partiql-planner/src/testFixtures/resources/inputs/basics/nullif.sql index 770ad4195a..f6c4b24f65 100644 --- a/partiql-planner/src/testFixtures/resources/inputs/basics/nullif.sql +++ b/partiql-planner/src/testFixtures/resources/inputs/basics/nullif.sql @@ -1,75 +1,71 @@ --#[nullif-00] -- Currently, no constant-folding. If there was, return type could be int32. --- type: (int32 | null) +-- type: (int32) NULLIF(1, 2); --#[nullif-01] -- Currently, no constant-folding. If there was, return type could be null. --- type: (int32 | null) +-- type: (int32) NULLIF(1, 1); --#[nullif-02] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, t_item.t_int32); --#[nullif-03] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, t_item.t_int64); --#[nullif-04] --- type: (int64 | null) +-- type: (int64) NULLIF(t_item.t_int64, t_item.t_int32); --#[nullif-05] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, NULL); --#[nullif-06] --- type: (null) +-- type: (any) NULLIF(NULL, t_item.t_int32); --#[nullif-07] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, MISSING); --#[nullif-08] --- type: (missing | null) +-- type: (any) NULLIF(MISSING, t_item.t_int32); --#[nullif-09] --- type: (int32 | null) -NULLIF(t_item.t_int32, t_item.t_int32_null); - ---#[nullif-10] --- type: (int32 | null) -NULLIF(t_item.t_int32_null, t_item.t_int32); +-- type: (int32) +NULLIF(t_item.t_int32, t_item.t_int32); --#[nullif-11] --- type: (int32 | null) -NULLIF(t_item.t_int32, t_item.t_int64_null); +-- type: (int32) +NULLIF(t_item.t_int32, t_item.t_int64); --#[nullif-12] --- type: (int64 | null) -NULLIF(t_item.t_int64_null, t_item.t_int32); +-- type: (int64) +NULLIF(t_item.t_int64, t_item.t_int32); --#[nullif-13] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, t_item.t_string); --#[nullif-14] --- type: (string | null) +-- type: (string) NULLIF(t_item.t_string, t_item.t_int32); --#[nullif-15] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, t_item.t_num_exact); --#[nullif-16] --- type: (int16 | int32 | int64 | int | decimal | null) +-- type: (int16 | int32 | int64 | int | decimal) NULLIF(t_item.t_num_exact, t_item.t_int32); --#[nullif-17] --- type: (int32 | null) +-- type: (int32) NULLIF(t_item.t_int32, t_item.t_any); --#[nullif-18] diff --git a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion b/partiql-planner/src/testFixtures/resources/tests/aggregations.ion index b78c2a0d43..515b6f2e6b 100644 --- a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion +++ b/partiql-planner/src/testFixtures/resources/tests/aggregations.ion @@ -8,7 +8,7 @@ suite::{ vars: {}, }, tests: { - 'avg(int32|null)': { + 'avg(int32)': { statement: ''' SELECT AVG(n) as "avg" FROM numbers.nullable_int32s AS n ''', @@ -25,7 +25,7 @@ suite::{ }, }, }, - 'count(int32|null)': { + 'count(int32)': { statement: ''' SELECT COUNT(n) as "count" FROM numbers.nullable_int32s AS n ''', @@ -42,7 +42,7 @@ suite::{ }, }, }, - 'min(int32|null)': { + 'min(int32)': { statement: ''' SELECT MIN(n) as "min" FROM numbers.nullable_int32s AS n ''', @@ -59,7 +59,7 @@ suite::{ }, }, }, - 'max(int32|null)': { + 'max(int32)': { statement: ''' SELECT MAX(n) as "max" FROM numbers.nullable_int32s AS n ''', @@ -76,7 +76,7 @@ suite::{ }, }, }, - 'sum(int32|null)': { + 'sum(int32)': { statement: ''' SELECT SUM(n) as "sum" FROM numbers.nullable_int32s AS n ''', From 994635a09c5c461df2b753789b141077c85360ec Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Wed, 22 May 2024 11:35:07 -0700 Subject: [PATCH 05/13] Addresses PR Feedback Adds TODO Simplifies typing of index operator Updates comment --- .../internal/transforms/PlanTransform.kt | 2 +- .../planner/internal/typer/DynamicTyper.kt | 2 +- .../planner/internal/typer/PlanTyper.kt | 30 +++++++++++-------- test/partiql-tests | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) 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 05f31e1b94..5381615977 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.ANY, + returns = PartiQLValueType.ANY, // TODO: Make unknown or something similar parameters = emptyList() ) ) 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 eebbf36421..2adf627763 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 @@ -86,7 +86,7 @@ internal class DynamicTyper { } /** - * This adds non-unknown types (aka not NULL / MISSING literals) to the typing accumulator. + * This adds non-absent types (aka not NULL / MISSING literals) to the typing accumulator. * @param type */ private fun accumulate(type: StaticType) { 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 ba5c9cb15b..71a38f6c37 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 @@ -122,6 +122,10 @@ internal class PlanTyper( return statementQuery(root) } + private companion object { + private val FUNCTIONS_HANDLING_MISSING = setOf("is_null", "is_missing", "eq", "and", "or", "not") + } + /** * Types the relational operators of a query expression. * @@ -462,21 +466,21 @@ internal class PlanTyper( return rex(ANY, rexOpErr("Collections must be indexed with integers, found ${key.type}")) } - // Check Root Type - if (!root.type.mayBeType() && !root.type.mayBeType()) { + // Get Element Type(s) + val elementTypes = root.type.allTypes.mapNotNull { type -> + when (type) { + is ListType -> type.elementType + is SexpType -> type.elementType + else -> null + } + } + + // Check that Root was LIST or SEXP by checking accumuated element types + if (elementTypes.isEmpty()) { handleAlwaysMissing() return rex(ANY, rexOpErr("Only lists and s-expressions can be indexed with integers, found ${root.type}")) } - - // Get Element Type - val elementTypes = root.type.allTypes.mapNotNull { type -> - if (type !is ListType && type !is SexpType) { - return@mapNotNull null - } - (type as CollectionType).elementType - }.toSet() - val finalType = unionOf(elementTypes) - return rex(finalType, rexOpPathIndex(root, key)) + return rex(unionOf(elementTypes), rexOpPathIndex(root, key)) } override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Rex { @@ -625,7 +629,7 @@ internal class PlanTyper( // 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", "and", "or", "not")) { + if (match.signature.name !in FUNCTIONS_HANDLING_MISSING) { handleAlwaysMissing() } } diff --git a/test/partiql-tests b/test/partiql-tests index 233713b784..be88ae732b 160000 --- a/test/partiql-tests +++ b/test/partiql-tests @@ -1 +1 @@ -Subproject commit 233713b7841eb559c4f4978f2955facf3c92c7d8 +Subproject commit be88ae732bec0388c88acab108a392f586094fc7 From a8c618accc65b27f06ee354d47426119845ec261 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 30 May 2024 09:52:17 -0700 Subject: [PATCH 06/13] Fixes ANTLR parser grammar and renames parser g4 file (#1474) --- .../org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt | 8 ++++---- partiql-parser/build.gradle.kts | 2 ++ .../src/main/antlr/{PartiQL.g4 => PartiQLParser.g4} | 2 +- .../org/partiql/parser/internal/PartiQLParserDefault.kt | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) rename partiql-parser/src/main/antlr/{PartiQL.g4 => PartiQLParser.g4} (99%) diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index 98a864e91e..a74ef956d4 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -60,8 +60,8 @@ import org.partiql.lang.util.checkThreadInterrupted import org.partiql.lang.util.error import org.partiql.lang.util.getPrecisionFromTimeString import org.partiql.lang.util.unaryMinus -import org.partiql.parser.antlr.PartiQLBaseVisitor import org.partiql.parser.antlr.PartiQLParser +import org.partiql.parser.antlr.PartiQLParserBaseVisitor import org.partiql.pig.runtime.SymbolPrimitive import org.partiql.value.datetime.DateTimeException import org.partiql.value.datetime.TimeZone @@ -73,12 +73,12 @@ import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException /** - * Extends ANTLR's generated [PartiQLBaseVisitor] to visit an ANTLR ParseTree and convert it into a PartiQL AST. This + * Extends ANTLR's generated [PartiQLParserBaseVisitor] to visit an ANTLR ParseTree and convert it into a PartiQL AST. This * class uses the [PartiqlAst.PartiqlAstNode] to represent all nodes within the new AST. * * When the grammar in PartiQL.g4 is extended with a new rule, one needs to override corresponding visitor methods * in this class, in order to extend the transformation from an ANTLR parse tree into a [PartqlAst] tree. - * (Trivial implementations of these methods are generated into [PartiQLBaseVisitor].) + * (Trivial implementations of these methods are generated into [PartiQLParserBaseVisitor].) * * For a rule of the form * ``` @@ -119,7 +119,7 @@ internal class PartiQLPigVisitor( val customTypes: List = listOf(), private val parameterIndexes: Map = mapOf(), ) : - PartiQLBaseVisitor() { + PartiQLParserBaseVisitor() { companion object { internal val TRIM_SPECIFICATION_KEYWORDS = setOf("both", "leading", "trailing") diff --git a/partiql-parser/build.gradle.kts b/partiql-parser/build.gradle.kts index 4739362984..5d0fc1eb55 100644 --- a/partiql-parser/build.gradle.kts +++ b/partiql-parser/build.gradle.kts @@ -82,6 +82,8 @@ tasks.processResources { from("src/main/antlr") { include("**/*.g4") } + // TODO remove in next major version release. + rename("PartiQLParser.g4", "PartiQL.g4") } publish { diff --git a/partiql-parser/src/main/antlr/PartiQL.g4 b/partiql-parser/src/main/antlr/PartiQLParser.g4 similarity index 99% rename from partiql-parser/src/main/antlr/PartiQL.g4 rename to partiql-parser/src/main/antlr/PartiQLParser.g4 index ec5d85e098..62144b47e6 100644 --- a/partiql-parser/src/main/antlr/PartiQL.g4 +++ b/partiql-parser/src/main/antlr/PartiQLParser.g4 @@ -1,4 +1,4 @@ -grammar PartiQL; +parser grammar PartiQLParser; options { tokenVocab=PartiQLTokens; diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index 0f971f15f5..c3c15ed212 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -208,7 +208,7 @@ import org.partiql.parser.PartiQLParserException import org.partiql.parser.PartiQLSyntaxException import org.partiql.parser.SourceLocation import org.partiql.parser.SourceLocations -import org.partiql.parser.antlr.PartiQLBaseVisitor +import org.partiql.parser.antlr.PartiQLParserBaseVisitor import org.partiql.parser.internal.util.DateTimeUtils import org.partiql.value.NumericValue import org.partiql.value.PartiQLValueExperimental @@ -422,7 +422,7 @@ internal class PartiQLParserDefault : PartiQLParser { private class Visitor( private val locations: SourceLocations.Mutable, private val parameters: Map = mapOf(), - ) : PartiQLBaseVisitor() { + ) : PartiQLParserBaseVisitor() { companion object { From a2d539896e2b1faa842ffcd652a572bc2b81910f Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Thu, 30 May 2024 09:52:36 -0700 Subject: [PATCH 07/13] Changes INT/INTEGER to be an alias for INT4 (#1473) --- CHANGELOG.md | 5 +++++ .../kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt | 1 + .../org/partiql/parser/internal/PartiQLParserDefault.kt | 4 ++-- .../partiql/planner/internal/typer/PlanTyperTestsPorted.kt | 6 +++--- .../src/testFixtures/resources/inputs/basics/case.sql | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b92a7cde73..1dbdafbbde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ Thank you to all who have contributed! is that the null and missing values are part of *all* data types. Therefore, one must assume that the types returned by the planner allow for NULL and MISSING values. Similarly, the testFixtures Ion-encoded test resources representing the catalog do not use "null" or "missing". +- **Behavioral change**: The `INTEGER/INT` type is now an alias to the `INT4` type. Previously the INTEGER type was +unconstrained which is not SQL-conformant and is causing issues in integrating with other systems. This release makes +INTEGER an alias for INT4 which is the internal type name. In a later release, we will make INTEGER the default 32-bit +integer with INT/INT4/INTEGER4 being aliases per other systems. This change only applies to +org.partiql.parser.PartiQLParser, not the org.partiql.lang.syntax.PartiQLParser. ### Deprecated - We have deprecated `org.partiql.type.NullType` and `org.partiql.type.MissingType`. Please see the corresponding diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt index 4209f726d0..a72fefa81f 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt @@ -3998,6 +3998,7 @@ class PartiQLParserTest : PartiQLParserTestBase() { ) @Test + @Ignore("This test is disabled while the new parser uses INT as an INT4 alias whereas the older parser does not.") fun createTableWithConstraints() = assertExpression( """ CREATE TABLE Customer ( diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index c3c15ed212..ac30689f17 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -183,7 +183,6 @@ import org.partiql.ast.typeDate import org.partiql.ast.typeDecimal import org.partiql.ast.typeFloat32 import org.partiql.ast.typeFloat64 -import org.partiql.ast.typeInt import org.partiql.ast.typeInt2 import org.partiql.ast.typeInt4 import org.partiql.ast.typeInt8 @@ -2051,9 +2050,10 @@ internal class PartiQLParserDefault : PartiQLParser { GeneratedParser.NULL -> typeNullType() GeneratedParser.BOOL, GeneratedParser.BOOLEAN -> typeBool() GeneratedParser.SMALLINT, GeneratedParser.INT2, GeneratedParser.INTEGER2 -> typeInt2() + // TODO, we have INT aliased to INT4 when it should be visa-versa. GeneratedParser.INT4, GeneratedParser.INTEGER4 -> typeInt4() + GeneratedParser.INT, GeneratedParser.INTEGER -> typeInt4() GeneratedParser.BIGINT, GeneratedParser.INT8, GeneratedParser.INTEGER8 -> typeInt8() - GeneratedParser.INT, GeneratedParser.INTEGER -> typeInt() GeneratedParser.FLOAT -> typeFloat32() GeneratedParser.DOUBLE -> typeFloat64() GeneratedParser.REAL -> typeReal() 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 e4d8e12b6f..700404339e 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 @@ -496,7 +496,7 @@ class PlanTyperTestsPorted { name = "DECIMAL AS INT", key = key("cast-03"), catalog = "pql", - expected = StaticType.INT, + expected = StaticType.INT4, ), SuccessTestCase( name = "DECIMAL AS BIGINT", @@ -2459,7 +2459,7 @@ class PlanTyperTestsPorted { SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-11"), catalog = "pql", - expected = StaticType.INT, + expected = StaticType.INT4, ), SuccessTestCase( key = PartiQLTest.Key("basics", "case-when-12"), @@ -4190,7 +4190,7 @@ class PlanTyperTestsPorted { query = "SELECT CAST(breed AS INT) AS cast_breed FROM pets", expected = BagType( StructType( - fields = mapOf("cast_breed" to StaticType.INT), + fields = mapOf("cast_breed" to StaticType.INT4), contentClosed = true, constraints = setOf( TupleConstraint.Open(false), diff --git a/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql b/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql index 58bbe7938c..277c7c0c9f 100644 --- a/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql +++ b/partiql-planner/src/testFixtures/resources/inputs/basics/case.sql @@ -80,7 +80,7 @@ CASE t_item.t_string END; --#[case-when-11] --- type: (int|missing) +-- type: (int4|missing) COALESCE(CAST(t_item.t_string AS INT), 1); -- ----------------------------- From cc3654a69562d9e2a3231f7456ea196f3f796281 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 4 Jun 2024 15:43:31 -0700 Subject: [PATCH 08/13] Fixes tests Updates planning tests Removes exhaustive attribute from dynamic call --- .../eval/internal/PartiQLEngineDefaultTest.kt | 119 +++++++------- .../lang/syntax/impl/PartiQLPigVisitor.kt | 3 +- .../org/partiql/planner/internal/Env.kt | 4 +- .../org/partiql/planner/internal/FnMatch.kt | 2 - .../partiql/planner/internal/FnResolver.kt | 28 +--- .../org/partiql/planner/internal/ir/Nodes.kt | 1 - .../planner/internal/typer/PlanTyper.kt | 42 +++-- .../main/resources/partiql_plan_internal.ion | 1 - .../planner/PlannerErrorReportingTests.kt | 79 +++++----- .../internal/typer/PlanTyperTestsPorted.kt | 146 +++++++++++------- partiql-types/api/partiql-types.api | 3 + 11 files changed, 237 insertions(+), 191 deletions(-) diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt index c294c4b255..eec8eebe24 100644 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -414,47 +414,6 @@ class PartiQLEngineDefaultTest { @JvmStatic fun aggregationTestCases() = kotlin.collections.listOf( - SuccessTestCase( - input = """ - SELECT - gk_0, SUM(t.c) AS t_c_sum - FROM << - { 'b': NULL, 'c': 1 }, - { 'b': MISSING, 'c': 2 }, - { 'b': 1, 'c': 1 }, - { 'b': 1, 'c': 2 }, - { 'b': 2, 'c': NULL }, - { 'b': 2, 'c': 2 }, - { 'b': 3, 'c': MISSING }, - { 'b': 3, 'c': 2 }, - { 'b': 4, 'c': MISSING }, - { 'b': 4, 'c': NULL } - >> AS t GROUP BY t.b AS gk_0; - """.trimIndent(), - expected = org.partiql.value.bagValue( - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(1), - "t_c_sum" to org.partiql.value.int32Value(3) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(2), - "t_c_sum" to org.partiql.value.int32Value(2) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(3), - "t_c_sum" to org.partiql.value.int32Value(2) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.int32Value(4), - "t_c_sum" to org.partiql.value.int32Value(null) - ), - org.partiql.value.structValue( - "gk_0" to org.partiql.value.nullValue(), - "t_c_sum" to org.partiql.value.int32Value(3) - ), - ), - mode = org.partiql.eval.PartiQLEngine.Mode.PERMISSIVE - ), SuccessTestCase( input = """ SELECT VALUE { 'sensor': sensor, @@ -900,17 +859,6 @@ class PartiQLEngineDefaultTest { mode = PartiQLEngine.Mode.STRICT ), // PartiQL Specification Section 8 - SuccessTestCase( - input = "MISSING AND TRUE;", - expected = boolValue(null), - ), - // PartiQL Specification Section 8 - SuccessTestCase( - input = "MISSING AND TRUE;", - expected = boolValue(null), // TODO: Is this right? - mode = PartiQLEngine.Mode.STRICT - ), - // PartiQL Specification Section 8 SuccessTestCase( input = "NULL IS MISSING;", expected = boolValue(false), @@ -1385,6 +1333,73 @@ class PartiQLEngineDefaultTest { ) ).assert() + @Test + @Disabled( + """ + We currently do not have support for consolidating collections containing MISSING/NULL. The current + result (value) is correct. However, the types are slightly wrong due to the SUM__ANY_ANY being resolved. + """ + ) + fun aggregationOnLiteralBagOfStructs() = SuccessTestCase( + input = """ + SELECT + gk_0, SUM(t.c) AS t_c_sum + FROM << + { 'b': NULL, 'c': 1 }, + { 'b': MISSING, 'c': 2 }, + { 'b': 1, 'c': 1 }, + { 'b': 1, 'c': 2 }, + { 'b': 2, 'c': NULL }, + { 'b': 2, 'c': 2 }, + { 'b': 3, 'c': MISSING }, + { 'b': 3, 'c': 2 }, + { 'b': 4, 'c': MISSING }, + { 'b': 4, 'c': NULL } + >> AS t GROUP BY t.b AS gk_0; + """.trimIndent(), + expected = org.partiql.value.bagValue( + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(1), + "t_c_sum" to org.partiql.value.int32Value(3) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(2), + "t_c_sum" to org.partiql.value.int32Value(2) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(3), + "t_c_sum" to org.partiql.value.int32Value(2) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.int32Value(4), + "t_c_sum" to org.partiql.value.int32Value(null) + ), + org.partiql.value.structValue( + "gk_0" to org.partiql.value.nullValue(), + "t_c_sum" to org.partiql.value.int32Value(3) + ), + ), + mode = org.partiql.eval.PartiQLEngine.Mode.PERMISSIVE + ).assert() + + // PartiQL Specification Section 8 + @Test + @Disabled("Currently, .check() is failing for MISSING. This will be resolved by Datum.") + fun missingAndTruePermissive() = + SuccessTestCase( + input = "MISSING AND TRUE;", + expected = boolValue(null), + ).assert() + + // PartiQL Specification Section 8 + @Test + @Disabled("Currently, .check() is failing for MISSING. This will be resolved by Datum.") + fun missingAndTrueStrict() = SuccessTestCase( + input = "MISSING AND TRUE;", + expected = boolValue(null), // TODO: Is this right? + mode = PartiQLEngine.Mode.STRICT + ).assert() + @Test @Disabled("Support for ORDER BY needs to be added for this to pass.") // PartiQL Specification says that SQL's SELECT is coerced, but SELECT VALUE is not. diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index d2a9c24932..69971bed5c 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -60,9 +60,8 @@ import org.partiql.lang.util.checkThreadInterrupted import org.partiql.lang.util.error import org.partiql.lang.util.getPrecisionFromTimeString import org.partiql.lang.util.unaryMinus -import org.partiql.parser.antlr.PartiQLParser -import org.partiql.parser.antlr.PartiQLParserBaseVisitor import org.partiql.parser.internal.antlr.PartiQLParser +import org.partiql.parser.internal.antlr.PartiQLParserBaseVisitor import org.partiql.pig.runtime.SymbolPrimitive import org.partiql.value.datetime.DateTimeException import org.partiql.value.datetime.TimeZone diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt index 478ca2f3a5..18ca6447a6 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -108,7 +108,7 @@ internal class Env(private val session: PartiQLPlanner.Session) { ) } return ProblemGenerator.missingRex( - rexOpCallDynamic(args, candidates, false), + rexOpCallDynamic(args, candidates), ProblemGenerator.incompatibleTypesForOp(args.map { it.type }, path.normalized.joinToString(".")) ) } @@ -126,7 +126,7 @@ internal class Env(private val session: PartiQLPlanner.Session) { ) } // Rewrite as a dynamic call to be typed by PlanTyper - rex(StaticType.ANY, rexOpCallDynamic(args, candidates, match.exhaustive)) + rex(StaticType.ANY, rexOpCallDynamic(args, candidates)) } is FnMatch.Static -> { // Create an internal typed reference diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt index f614fc11de..8fb4197af9 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnMatch.kt @@ -51,10 +51,8 @@ internal sealed class FnMatch { * This represents dynamic dispatch. * * @property candidates Ordered list of potentially applicable functions to dispatch dynamically. - * @property exhaustive True if all argument permutations (branches) are matched. */ data class Dynamic( val candidates: List, - val exhaustive: Boolean, ) : FnMatch() } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt index eb8693b5b3..79fae42136 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt @@ -5,8 +5,6 @@ import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.typer.toRuntimeTypeOrNull import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnSignature -import org.partiql.types.AnyOfType -import org.partiql.types.NullType import org.partiql.types.StaticType import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType @@ -55,15 +53,7 @@ internal object FnResolver { } // Match candidates on all argument permutations - var exhaustive = true - val matches = argPermutations.mapNotNull { - val m = match(candidates, it) - if (m == null) { - // we had a branch whose arguments did not match a static call - exhaustive = false - } - m - } + val matches = argPermutations.mapNotNull { match(candidates, it) } // Order based on original candidate function ordering val orderedUniqueMatches = matches.toSet().toList() @@ -73,10 +63,10 @@ internal object FnResolver { // Static call iff only one match for every branch val n = orderedCandidates.size - return when { - n == 0 -> null - n == 1 && exhaustive -> orderedCandidates.first() - else -> FnMatch.Dynamic(orderedCandidates, exhaustive) + return when (n) { + 0 -> null + 1 -> orderedCandidates.first() + else -> FnMatch.Dynamic(orderedCandidates) } } @@ -149,13 +139,7 @@ internal object FnResolver { } 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()) } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 370ad08e8f..7c4b5230e0 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -531,7 +531,6 @@ internal data class Rex( internal data class Dynamic( @JvmField internal val args: List, @JvmField internal val candidates: List, - @JvmField internal val exhaustive: Boolean, ) : Call() { public override val children: List by lazy { val kids = mutableListOf() 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 377bfc89f8..da35eafb61 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 @@ -87,6 +87,7 @@ import org.partiql.value.MissingValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.TextValue import org.partiql.value.boolValue +import org.partiql.value.missingValue import org.partiql.value.stringValue import kotlin.math.max @@ -578,6 +579,14 @@ internal class PlanTyper(private val env: Env) { } } + // Check that root is not literal missing + if (root.isLiteralMissing()) { + return ProblemGenerator.missingRex( + rexOpPathIndex(root, key), + ProblemGenerator.expressionAlwaysReturnsMissing() + ) + } + // Check that Root was LIST or SEXP by checking accumuated element types if (elementTypes.isEmpty()) { return ProblemGenerator.missingRex( @@ -588,6 +597,8 @@ internal class PlanTyper(private val env: Env) { return rex(unionOf(elementTypes), rexOpPathIndex(root, key)) } + private fun Rex.isLiteralMissing(): Boolean = this.op is Rex.Op.Lit && this.op.value.withoutAnnotations() == missingValue() + override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Rex { val root = visitRex(node.root, node.root.type) val key = visitRex(node.key, node.key.type) @@ -608,6 +619,14 @@ internal class PlanTyper(private val env: Env) { ) } + // Check that root is not literal missing + if (root.isLiteralMissing()) { + return ProblemGenerator.missingRex( + rexOpPathKey(root, key), + ProblemGenerator.expressionAlwaysReturnsMissing() + ) + } + // Get Element Type val elementType = root.type.inferListNotNull { type -> val struct = type as? StructType ?: return@inferListNotNull null @@ -645,6 +664,14 @@ internal class PlanTyper(private val env: Env) { ) } + // Check that root is not literal missing + if (root.isLiteralMissing()) { + return ProblemGenerator.missingRex( + rexOpPathSymbol(root, node.key), + ProblemGenerator.expressionAlwaysReturnsMissing() + ) + } + // Get Element Types val paths = root.type.inferRexListNotNull { type -> val struct = type as? StructType ?: return@inferRexListNotNull null @@ -663,9 +690,11 @@ internal class PlanTyper(private val env: Env) { val type = when (paths.size) { // Escape early since no inference could be made 0 -> { + val key = org.partiql.plan.Identifier.Symbol(node.key, org.partiql.plan.Identifier.CaseSensitivity.SENSITIVE) + val inScopeVariables = locals.schema.map { it.name }.toSet() return ProblemGenerator.missingRex( rexOpPathSymbol(root, node.key), - ProblemGenerator.expressionAlwaysReturnsMissing("No output types could be gathered.") + ProblemGenerator.undefinedVariable(key, inScopeVariables) ) } else -> unionOf(paths.map { it.type }.toSet()) @@ -740,13 +769,6 @@ internal class PlanTyper(private val env: Env) { /** * Typing of a dynamic function call. * - * isMissable TRUE when the argument permutations may not definitively invoke one of the candidates. - * You can think of [isMissable] as being the same as "not exhaustive". For example, if we have ABS(INT | STRING), then - * this function call [isMissable] because there isn't an `ABS(STRING)` function signature AKA we haven't exhausted - * all the arguments. On the other hand, take an "exhaustive" scenario: ABS(INT | DEC). In this case, [isMissable] - * is false because we have functions for each potential argument AKA we have exhausted the arguments. - * - * * @param node * @param ctx * @return @@ -760,8 +782,8 @@ internal class PlanTyper(private val env: Env) { }.toMutableSet() if (types.isEmpty()) { return ProblemGenerator.missingRex( - rexOpCallDynamic(node.args, node.candidates, exhaustive = true), // TODO: Remove exhaustive - ProblemGenerator.expressionAlwaysReturnsMissing("Function is always passed MISSING values.") + rexOpCallDynamic(node.args, node.candidates), + ProblemGenerator.expressionAlwaysReturnsMissing("Function argument is always the missing value.") ) } return rex(type = unionOf(types).flatten(), op = node) diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 590134f693..3c0027ca46 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -148,7 +148,6 @@ rex::{ dynamic::{ args: list::[rex], candidates: list::[candidate], - exhaustive: bool, _: [ candidate::{ fn: '.ref.fn', diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index f154d2c55c..399f359cf2 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -24,16 +24,15 @@ internal class PlannerErrorReportingTests { val catalog = MemoryCatalog .PartiQL() .name(catalogName) - .define("missing_binding", StaticType.MISSING) + .define("missing_binding", StaticType.ANY) .define("atomic", StaticType.INT2) .define("collection_no_missing_atomic", BagType(StaticType.INT2)) - .define("collection_contain_missing_atomic", BagType(StaticType.unionOf(StaticType.INT2, StaticType.MISSING))) + .define("collection_contain_missing_atomic", BagType(StaticType.INT2)) .define("struct_no_missing", closedStruct(StructType.Field("f1", StaticType.INT2))) .define( "struct_with_missing", closedStruct( - StructType.Field("f1", StaticType.unionOf(StaticType.INT2, StaticType.MISSING)), - StructType.Field("f2", StaticType.MISSING), + StructType.Field("f1", StaticType.INT2), ) ) .build() @@ -84,7 +83,7 @@ internal class PlannerErrorReportingTests { val query: String, val isSignal: Boolean, val assertion: (List) -> List<() -> Boolean>, - val expectedType: StaticType = StaticType.MISSING + val expectedType: StaticType = StaticType.ANY ) companion object { @@ -182,15 +181,19 @@ internal class PlannerErrorReportingTests { assertOnProblemCount(0, 1) ), // Chained, demostrate missing trace. + // TODO: We currently don't have a good way to retain missing value information. The following test + // should have 2 errors. TestCase( "MISSING['a'].a", false, - assertOnProblemCount(2, 0) + assertOnProblemCount(1, 0) ), + // TODO: We currently don't have a good way to retain missing value information. The following test + // should have 2 errors. TestCase( "MISSING['a'].a", true, - assertOnProblemCount(0, 2) + assertOnProblemCount(0, 1) ), TestCase( """ @@ -201,7 +204,7 @@ internal class PlannerErrorReportingTests { """.trimIndent(), false, assertOnProblemCount(0, 0), - StaticType.unionOf(StaticType.INT4, StaticType.MISSING) + StaticType.INT4 ), TestCase( """ @@ -212,27 +215,7 @@ internal class PlannerErrorReportingTests { """.trimIndent(), true, assertOnProblemCount(0, 0), - StaticType.unionOf(StaticType.INT4, StaticType.MISSING) - ), - TestCase( - """ - -- both branches are missing, problem - CASE WHEN - 1 = 1 THEN MISSING - ELSE MISSING END - """.trimIndent(), - false, - assertOnProblemCount(1, 0), - ), - TestCase( - """ - -- both branches are missing, problem - CASE WHEN - 1 = 1 THEN MISSING - ELSE MISSING END - """.trimIndent(), - true, - assertOnProblemCount(0, 1), + StaticType.INT4 ), ) @@ -248,13 +231,13 @@ internal class PlannerErrorReportingTests { " 'a' + 'b' ", false, assertOnProblemCount(1, 0), - StaticType.MISSING + StaticType.ANY ), TestCase( " 'a' + 'b' ", true, assertOnProblemCount(0, 1), - StaticType.MISSING + StaticType.ANY ), // No function with given name is registered. @@ -264,28 +247,30 @@ internal class PlannerErrorReportingTests { "not_a_function(1)", false, assertOnProblemCount(0, 1), - StaticType.MISSING + StaticType.ANY ), TestCase( "not_a_function(1)", true, assertOnProblemCount(0, 1), - StaticType.MISSING + StaticType.ANY ), // 1 + not_a_function(1) // The continuation will return all numeric type + // TODO: Should the warning count be 1? Does it matter if it is zero? TestCase( "1 + not_a_function(1)", false, - assertOnProblemCount(1, 1), - StaticType.MISSING, + assertOnProblemCount(0, 1), + StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.FLOAT, StaticType.DECIMAL), ), + // TODO: Should the warning count be 1? Does it matter if it is zero? TestCase( "1 + not_a_function(1)", false, - assertOnProblemCount(1, 1), - StaticType.MISSING, + assertOnProblemCount(0, 1), + StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.FLOAT, StaticType.DECIMAL), ), TestCase( @@ -297,7 +282,7 @@ internal class PlannerErrorReportingTests { """.trimIndent(), false, assertOnProblemCount(1, 0), - BagType(closedStruct(StructType.Field("f1", StaticType.INT2))) + BagType(closedStruct(StructType.Field("f1", StaticType.INT2), StructType.Field("f2", StaticType.ANY))) ), TestCase( """ @@ -308,7 +293,7 @@ internal class PlannerErrorReportingTests { """.trimIndent(), true, assertOnProblemCount(0, 1), - BagType(closedStruct(StructType.Field("f1", StaticType.INT2))) + BagType(closedStruct(StructType.Field("f1", StaticType.INT2), StructType.Field("f2", StaticType.ANY))) ), TestCase( """ @@ -320,7 +305,13 @@ internal class PlannerErrorReportingTests { """.trimIndent(), false, assertOnProblemCount(2, 0), - BagType(closedStruct(StructType.Field("f1", StaticType.unionOf(StaticType.INT2, StaticType.MISSING)))) + BagType( + closedStruct( + StructType.Field("f1", StaticType.INT2), + StructType.Field("f2", StaticType.ANY), + StructType.Field("f3", StaticType.ANY) + ) + ) ), TestCase( """ @@ -332,7 +323,13 @@ internal class PlannerErrorReportingTests { """.trimIndent(), true, assertOnProblemCount(0, 2), - BagType(closedStruct(StructType.Field("f1", StaticType.unionOf(StaticType.INT2, StaticType.MISSING)))) + BagType( + closedStruct( + StructType.Field("f1", StaticType.INT2), + StructType.Field("f2", StaticType.ANY), + StructType.Field("f3", StaticType.ANY) + ) + ) ), // TODO: EXCLUDE ERROR reporting is not completed. 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 4975f5f261..1ba3024610 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 @@ -689,12 +689,12 @@ class PlanTyperTestsPorted { query = "CURRENT_USER + 'hello'", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "plus", + ProblemGenerator.incompatibleTypesForOp( listOf( StaticType.STRING, StaticType.STRING, ), + "PLUS", ) ) ), @@ -760,12 +760,9 @@ class PlanTyperTestsPorted { ErrorTestCase( name = "BITWISE_AND_MISSING_OPERAND", query = "1 & MISSING", - expected = unionOf(INT4, INT8, INT), + expected = ANY, // TODO: Is this unionOf(INT4, INT8, INT) ? problemHandler = assertProblemExists( - ProblemGenerator.incompatibleTypesForOp( - listOf(StaticType.INT4, StaticType.MISSING), - "BITWISE_AND", - ) + ProblemGenerator.expressionAlwaysReturnsMissing("Function argument is always the missing value.") ) ), ErrorTestCase( @@ -773,9 +770,9 @@ class PlanTyperTestsPorted { query = "1 & 'NOT AN INT'", expected = StaticType.ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "bitwise_and", - listOf(StaticType.INT4, StaticType.STRING) + ProblemGenerator.incompatibleTypesForOp( + listOf(StaticType.INT4, StaticType.STRING), + "BITWISE_AND", ) ) ), @@ -2669,7 +2666,7 @@ class PlanTyperTestsPorted { ), ), ), - ErrorTestCase( + SuccessTestCase( name = "CASE-WHEN always MISSING", key = PartiQLTest.Key("basics", "case-when-30"), catalog = "pql", @@ -3056,7 +3053,7 @@ class PlanTyperTestsPorted { """, expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.expressionAlwaysReturnsMissing("Path Navigation always returns MISSING") + ProblemGenerator.expressionAlwaysReturnsMissing("Collections must be indexed with integers, found string") ) ), // The reason this is ANY is because we do not have support for constant-folding. We don't know what @@ -3415,9 +3412,9 @@ class PlanTyperTestsPorted { """.trimIndent(), expected = BagType(unionOf(StaticType.INT2, INT4, INT8, INT, StaticType.FLOAT, StaticType.DECIMAL)), problemHandler = assertProblemExists( - ProblemGenerator.incompatibleTypesForOp( - listOf(StaticType.MISSING), - "POS", + ProblemGenerator.undefinedVariable( + Identifier.Symbol("a", Identifier.CaseSensitivity.SENSITIVE), + setOf("t"), ) ) ), @@ -3429,12 +3426,9 @@ class PlanTyperTestsPorted { query = """ +MISSING """.trimIndent(), - expected = StaticType.MISSING, + expected = StaticType.ANY, problemHandler = assertProblemExists( - ProblemGenerator.incompatibleTypesForOp( - listOf(StaticType.MISSING), - "POS", - ) + ProblemGenerator.expressionAlwaysReturnsMissing("Function argument is always the missing value.") ) ), ) @@ -3827,15 +3821,26 @@ class PlanTyperTestsPorted { name = "Pets should not be accessible #1", query = "SELECT * FROM pets", expected = BagType( - StructType( - fields = emptyMap(), - contentClosed = true, - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered, - ) - ), + unionOf( + StructType( + fields = emptyMap(), + contentClosed = false, + constraints = setOf( + TupleConstraint.Open(true), + TupleConstraint.UniqueAttrs(false), + ) + ), + StructType( + fields = listOf( + StructType.Field("_1", ANY) + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + ) + ), + ) ), problemHandler = assertProblemExists( ProblemGenerator.undefinedVariable(insensitive("pets")) @@ -3846,15 +3851,26 @@ class PlanTyperTestsPorted { catalog = CATALOG_AWS, query = "SELECT * FROM pets", expected = BagType( - StructType( - fields = emptyMap(), - contentClosed = true, - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered, - ) - ), + unionOf( + StructType( + fields = emptyMap(), + contentClosed = false, + constraints = setOf( + TupleConstraint.Open(true), + TupleConstraint.UniqueAttrs(false), + ) + ), + StructType( + fields = listOf( + StructType.Field("_1", ANY) + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + ) + ), + ) ), problemHandler = assertProblemExists( ProblemGenerator.undefinedVariable(insensitive("pets")) @@ -3899,15 +3915,26 @@ class PlanTyperTestsPorted { name = "Test #7", query = "SELECT * FROM ddb.pets", expected = BagType( - StructType( - fields = emptyList(), - contentClosed = true, - constraints = setOf( - TupleConstraint.Open(false), - TupleConstraint.UniqueAttrs(true), - TupleConstraint.Ordered, - ) - ), + unionOf( + StructType( + fields = emptyList(), + contentClosed = false, + constraints = setOf( + TupleConstraint.Open(true), + TupleConstraint.UniqueAttrs(false), + ) + ), + StructType( + fields = listOf( + StructType.Field("_1", ANY) + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + ) + ), + ) ), problemHandler = assertProblemExists( ProblemGenerator.undefinedVariable(id(insensitive("ddb"), insensitive("pets"))) @@ -4066,9 +4093,10 @@ class PlanTyperTestsPorted { query = "order_info.customer_id IN 'hello'", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "in_collection", + ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.INT4, StaticType.STRING), + "" + + "IN_COLLECTION", ) ) ), @@ -4086,13 +4114,13 @@ class PlanTyperTestsPorted { query = "order_info.customer_id BETWEEN 1 AND 'a'", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "between", + ProblemGenerator.incompatibleTypesForOp( listOf( StaticType.INT4, StaticType.INT4, StaticType.STRING ), + "BETWEEN", ) ) ), @@ -4110,9 +4138,9 @@ class PlanTyperTestsPorted { query = "order_info.ship_option LIKE 3", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "like", + ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.STRING, StaticType.INT4), + "LIKE", ) ) ), @@ -4180,9 +4208,9 @@ class PlanTyperTestsPorted { query = "order_info.customer_id = 1 AND 1", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "and", + ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.BOOL, StaticType.INT4), + "AND", ) ) ), @@ -4193,9 +4221,9 @@ class PlanTyperTestsPorted { query = "1 AND order_info.customer_id = 1", expected = ANY, problemHandler = assertProblemExists( - ProblemGenerator.undefinedFunction( - "and", + ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.INT4, StaticType.BOOL), + "AND", ) ) ), @@ -4206,7 +4234,9 @@ class PlanTyperTestsPorted { query = "SELECT unknown_col FROM orders WHERE customer_id = 1", expected = BagType( StructType( - fields = listOf(), + fields = listOf( + StructType.Field("unknown_col", ANY) + ), contentClosed = true, constraints = setOf( TupleConstraint.Open(false), diff --git a/partiql-types/api/partiql-types.api b/partiql-types/api/partiql-types.api index 5df16b7fd3..69e04aa709 100644 --- a/partiql-types/api/partiql-types.api +++ b/partiql-types/api/partiql-types.api @@ -631,6 +631,7 @@ public abstract class org/partiql/types/StaticType { public abstract fun getMetas ()Ljava/util/Map; public final fun isMissable ()Z public final fun isNullable ()Z + public static final fun unionOf (Ljava/util/Collection;Ljava/util/Map;)Lorg/partiql/types/StaticType; public static final fun unionOf (Ljava/util/Set;Ljava/util/Map;)Lorg/partiql/types/StaticType; public static final fun unionOf ([Lorg/partiql/types/StaticType;Ljava/util/Map;)Lorg/partiql/types/StaticType; public final fun withMetas (Ljava/util/Map;)Lorg/partiql/types/StaticType; @@ -638,8 +639,10 @@ public abstract class org/partiql/types/StaticType { public final class org/partiql/types/StaticType$Companion { public final fun getALL_TYPES ()Ljava/util/List; + public final fun unionOf (Ljava/util/Collection;Ljava/util/Map;)Lorg/partiql/types/StaticType; public final fun unionOf (Ljava/util/Set;Ljava/util/Map;)Lorg/partiql/types/StaticType; public final fun unionOf ([Lorg/partiql/types/StaticType;Ljava/util/Map;)Lorg/partiql/types/StaticType; + public static synthetic fun unionOf$default (Lorg/partiql/types/StaticType$Companion;Ljava/util/Collection;Ljava/util/Map;ILjava/lang/Object;)Lorg/partiql/types/StaticType; public static synthetic fun unionOf$default (Lorg/partiql/types/StaticType$Companion;Ljava/util/Set;Ljava/util/Map;ILjava/lang/Object;)Lorg/partiql/types/StaticType; public static synthetic fun unionOf$default (Lorg/partiql/types/StaticType$Companion;[Lorg/partiql/types/StaticType;Ljava/util/Map;ILjava/lang/Object;)Lorg/partiql/types/StaticType; } From 145991eae8b4d5e0eb5e1d1ec2985228b72a43ea Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 11 Jun 2024 12:03:25 -0700 Subject: [PATCH 09/13] Addresses PR comments --- .../org/partiql/planner/internal/typer/PlanTyper.kt | 3 +-- .../org/partiql/planner/PlannerErrorReportingTests.kt | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) 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 da35eafb61..4b77eb5a32 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 @@ -87,7 +87,6 @@ import org.partiql.value.MissingValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.TextValue import org.partiql.value.boolValue -import org.partiql.value.missingValue import org.partiql.value.stringValue import kotlin.math.max @@ -597,7 +596,7 @@ internal class PlanTyper(private val env: Env) { return rex(unionOf(elementTypes), rexOpPathIndex(root, key)) } - private fun Rex.isLiteralMissing(): Boolean = this.op is Rex.Op.Lit && this.op.value.withoutAnnotations() == missingValue() + private fun Rex.isLiteralMissing(): Boolean = this.op is Rex.Op.Lit && this.op.value is MissingValue override fun visitRexOpPathKey(node: Rex.Op.Path.Key, ctx: StaticType?): Rex { val root = visitRex(node.root, node.root.type) diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index 399f359cf2..8994aa5a8c 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -182,14 +182,16 @@ internal class PlannerErrorReportingTests { ), // Chained, demostrate missing trace. // TODO: We currently don't have a good way to retain missing value information. The following test - // should have 2 errors. + // could have 2 warnings. One for executing a path operation on a literal missing. And one for + // executing a path operation on an expression that is known to result in the missing value. TestCase( "MISSING['a'].a", false, assertOnProblemCount(1, 0) ), // TODO: We currently don't have a good way to retain missing value information. The following test - // should have 2 errors. + // could have 2 errors. One for executing a path operation on a literal missing. And one for + // executing a path operation on an expression that is known to result in the missing value. TestCase( "MISSING['a'].a", true, @@ -258,17 +260,15 @@ internal class PlannerErrorReportingTests { // 1 + not_a_function(1) // The continuation will return all numeric type - // TODO: Should the warning count be 1? Does it matter if it is zero? TestCase( "1 + not_a_function(1)", false, assertOnProblemCount(0, 1), StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.FLOAT, StaticType.DECIMAL), ), - // TODO: Should the warning count be 1? Does it matter if it is zero? TestCase( "1 + not_a_function(1)", - false, + true, assertOnProblemCount(0, 1), StaticType.unionOf(StaticType.INT4, StaticType.INT8, StaticType.INT, StaticType.FLOAT, StaticType.DECIMAL), ), From 20a8434d3bb57436366a1fb7fe611da6c6f7b420 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 11 Jun 2024 13:37:55 -0700 Subject: [PATCH 10/13] Updates Gradle to 8.7 --- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 ++ gradlew | 41 ++++++++++++++++------- gradlew.bat | 35 ++++++++++--------- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02ca4..b82aa23a4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f938..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 0e1564e837be65337de60a8f31eef46201ab9fb8 Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 11 Jun 2024 13:52:34 -0700 Subject: [PATCH 11/13] Updates conformance tests to Gradle 8.7 --- .github/workflows/conformance-report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conformance-report.yml b/.github/workflows/conformance-report.yml index bdcf83d8f9..81e2c62e65 100644 --- a/.github/workflows/conformance-report.yml +++ b/.github/workflows/conformance-report.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 7.5.1 + gradle-version: 8.7 # Run the conformance tests and save to an Ion file. - name: gradle test of the conformance tests (can fail) and save to Ion file continue-on-error: true @@ -68,7 +68,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 7.5.1 + gradle-version: 8.7 - name: Download `conformance_test_results.ion` from target branch uses: dawidd6/action-download-artifact@v2 id: download-report From 53f3502e9d4d0fb2807c006134fa8580e0e9eb8e Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 11 Jun 2024 13:56:13 -0700 Subject: [PATCH 12/13] Revert "Updates Gradle to 8.7" This reverts commit 20a8434d3bb57436366a1fb7fe611da6c6f7b420. --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 -- gradlew | 41 +++++++---------------- gradlew.bat | 35 +++++++++---------- 4 files changed, 29 insertions(+), 49 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c z&{(nX_ht=QbJ%DzAya}!&uNu!4V0xI)QE$SY__m)SAKcN0P(&JcoK*Lxr@P zY&P=}&B3*UWNlc|&$Oh{BEqwK2+N2U$4WB7Fd|aIal`FGANUa9E-O)!gV`((ZGCc$ zBJA|FFrlg~9OBp#f7aHodCe{6= zay$6vN~zj1ddMZ9gQ4p32(7wD?(dE>KA2;SOzXRmPBiBc6g`eOsy+pVcHu=;Yd8@{ zSGgXf@%sKKQz~;!J;|2fC@emm#^_rnO0esEn^QxXgJYd`#FPWOUU5b;9eMAF zZhfiZb|gk8aJIw*YLp4!*(=3l8Cp{(%p?ho22*vN9+5NLV0TTazNY$B5L6UKUrd$n zjbX%#m7&F#U?QNOBXkiiWB*_tk+H?N3`vg;1F-I+83{M2!8<^nydGr5XX}tC!10&e z7D36bLaB56WrjL&HiiMVtpff|K%|*{t*ltt^5ood{FOG0<>k&1h95qPio)2`eL${YAGIx(b4VN*~nKn6E~SIQUuRH zQ+5zP6jfnP$S0iJ@~t!Ai3o`X7biohli;E zT#yXyl{bojG@-TGZzpdVDXhbmF%F9+-^YSIv|MT1l3j zrxOFq>gd2%U}?6}8mIj?M zc077Zc9fq(-)4+gXv?Az26IO6eV`RAJz8e3)SC7~>%rlzDwySVx*q$ygTR5kW2ds- z!HBgcq0KON9*8Ff$X0wOq$`T7ml(@TF)VeoF}x1OttjuVHn3~sHrMB++}f7f9H%@f z=|kP_?#+fve@{0MlbkC9tyvQ_R?lRdRJ@$qcB(8*jyMyeME5ns6ypVI1Xm*Zr{DuS zZ!1)rQfa89c~;l~VkCiHI|PCBd`S*2RLNQM8!g9L6?n`^evQNEwfO@&JJRme+uopQX0%Jo zgd5G&#&{nX{o?TQwQvF1<^Cg3?2co;_06=~Hcb6~4XWpNFL!WU{+CK;>gH%|BLOh7@!hsa(>pNDAmpcuVO-?;Bic17R}^|6@8DahH)G z!EmhsfunLL|3b=M0MeK2vqZ|OqUqS8npxwge$w-4pFVXFq$_EKrZY?BuP@Az@(k`L z`ViQBSk`y+YwRT;&W| z2e3UfkCo^uTA4}Qmmtqs+nk#gNr2W4 zTH%hhErhB)pkXR{B!q5P3-OM+M;qu~f>}IjtF%>w{~K-0*jPVLl?Chz&zIdxp}bjx zStp&Iufr58FTQ36AHU)0+CmvaOpKF;W@sMTFpJ`j;3d)J_$tNQI^c<^1o<49Z(~K> z;EZTBaVT%14(bFw2ob@?JLQ2@(1pCdg3S%E4*dJ}dA*v}_a4_P(a`cHnBFJxNobAv zf&Zl-Yt*lhn-wjZsq<9v-IsXxAxMZ58C@e0!rzhJ+D@9^3~?~yllY^s$?&oNwyH!#~6x4gUrfxplCvK#!f z$viuszW>MFEcFL?>ux*((!L$;R?xc*myjRIjgnQX79@UPD$6Dz0jutM@7h_pq z0Zr)#O<^y_K6jfY^X%A-ip>P%3saX{!v;fxT-*0C_j4=UMH+Xth(XVkVGiiKE#f)q z%Jp=JT)uy{&}Iq2E*xr4YsJ5>w^=#-mRZ4vPXpI6q~1aFwi+lQcimO45V-JXP;>(Q zo={U`{=_JF`EQj87Wf}{Qy35s8r1*9Mxg({CvOt}?Vh9d&(}iI-quvs-rm~P;eRA@ zG5?1HO}puruc@S{YNAF3vmUc2B4!k*yi))<5BQmvd3tr}cIs#9)*AX>t`=~{f#Uz0 z0&Nk!7sSZwJe}=)-R^$0{yeS!V`Dh7w{w5rZ9ir!Z7Cd7dwZcK;BT#V0bzTt>;@Cl z#|#A!-IL6CZ@eHH!CG>OO8!%G8&8t4)Ro@}USB*k>oEUo0LsljsJ-%5Mo^MJF2I8- z#v7a5VdJ-Cd%(a+y6QwTmi+?f8Nxtm{g-+WGL>t;s#epv7ug>inqimZCVm!uT5Pf6 ziEgQt7^%xJf#!aPWbuC_3Nxfb&CFbQy!(8ANpkWLI4oSnH?Q3f?0k1t$3d+lkQs{~(>06l&v|MpcFsyAv zin6N!-;pggosR*vV=DO(#+}4ps|5$`udE%Kdmp?G7B#y%H`R|i8skKOd9Xzx8xgR$>Zo2R2Ytktq^w#ul4uicxW#{ zFjG_RNlBroV_n;a7U(KIpcp*{M~e~@>Q#Av90Jc5v%0c>egEdY4v3%|K1XvB{O_8G zkTWLC>OZKf;XguMH2-Pw{BKbFzaY;4v2seZV0>^7Q~d4O=AwaPhP3h|!hw5aqOtT@ z!SNz}$of**Bl3TK209@F=Tn1+mgZa8yh(Png%Zd6Mt}^NSjy)etQrF zme*llAW=N_8R*O~d2!apJnF%(JcN??=`$qs3Y+~xs>L9x`0^NIn!8mMRFA_tg`etw z3k{9JAjnl@ygIiJcNHTy02GMAvBVqEss&t2<2mnw!; zU`J)0>lWiqVqo|ex7!+@0i>B~BSU1A_0w#Ee+2pJx0BFiZ7RDHEvE*ptc9md(B{&+ zKE>TM)+Pd>HEmdJao7U@S>nL(qq*A)#eLOuIfAS@j`_sK0UEY6OAJJ-kOrHG zjHx`g!9j*_jRcJ%>CE9K2MVf?BUZKFHY?EpV6ai7sET-tqk=nDFh-(65rhjtlKEY% z@G&cQ<5BKatfdA1FKuB=i>CCC5(|9TMW%K~GbA4}80I5%B}(gck#Wlq@$nO3%@QP_ z8nvPkJFa|znk>V92cA!K1rKtr)skHEJD;k8P|R8RkCq1Rh^&}Evwa4BUJz2f!2=MH zo4j8Y$YL2313}H~F7@J7mh>u%556Hw0VUOz-Un@ZASCL)y8}4XXS`t1AC*^>PLwIc zUQok5PFS=*#)Z!3JZN&eZ6ZDP^-c@StY*t20JhCnbMxXf=LK#;`4KHEqMZ-Ly9KsS zI2VUJGY&PmdbM+iT)zek)#Qc#_i4uH43 z@T5SZBrhNCiK~~esjsO9!qBpaWK<`>!-`b71Y5ReXQ4AJU~T2Njri1CEp5oKw;Lnm)-Y@Z3sEY}XIgSy%xo=uek(kAAH5MsV$V3uTUsoTzxp_rF=tx zV07vlJNKtJhCu`b}*#m&5LV4TAE&%KtHViDAdv#c^x`J7bg z&N;#I2GkF@SIGht6p-V}`!F_~lCXjl1BdTLIjD2hH$J^YFN`7f{Q?OHPFEM$65^!u zNwkelo*5+$ZT|oQ%o%;rBX$+?xhvjb)SHgNHE_yP%wYkkvXHS{Bf$OiKJ5d1gI0j< zF6N}Aq=(WDo(J{e-uOecxPD>XZ@|u-tgTR<972`q8;&ZD!cep^@B5CaqFz|oU!iFj zU0;6fQX&~15E53EW&w1s9gQQ~Zk16X%6 zjG`j0yq}4deX2?Tr(03kg>C(!7a|b9qFI?jcE^Y>-VhudI@&LI6Qa}WQ>4H_!UVyF z((cm&!3gmq@;BD#5P~0;_2qgZhtJS|>WdtjY=q zLnHH~Fm!cxw|Z?Vw8*~?I$g#9j&uvgm7vPr#&iZgPP~v~BI4jOv;*OQ?jYJtzO<^y z7-#C={r7CO810!^s(MT!@@Vz_SVU)7VBi(e1%1rvS!?PTa}Uv`J!EP3s6Y!xUgM^8 z4f!fq<3Wer_#;u!5ECZ|^c1{|q_lh3m^9|nsMR1#Qm|?4Yp5~|er2?W^7~cl;_r4WSme_o68J9p03~Hc%X#VcX!xAu%1`R!dfGJCp zV*&m47>s^%Ib0~-2f$6oSgn3jg8m%UA;ArcdcRyM5;}|r;)?a^D*lel5C`V5G=c~k zy*w_&BfySOxE!(~PI$*dwG><+-%KT5p?whOUMA*k<9*gi#T{h3DAxzAPxN&Xws8o9Cp*`PA5>d9*Z-ynV# z9yY*1WR^D8|C%I@vo+d8r^pjJ$>eo|j>XiLWvTWLl(^;JHCsoPgem6PvegHb-OTf| zvTgsHSa;BkbG=(NgPO|CZu9gUCGr$8*EoH2_Z#^BnxF0yM~t`|9ws_xZ8X8iZYqh! zAh;HXJ)3P&)Q0(&F>!LN0g#bdbis-cQxyGn9Qgh`q+~49Fqd2epikEUw9caM%V6WgP)532RMRW}8gNS%V%Hx7apSz}tn@bQy!<=lbhmAH=FsMD?leawbnP5BWM0 z5{)@EEIYMu5;u)!+HQWhQ;D3_Cm_NADNeb-f56}<{41aYq8p4=93d=-=q0Yx#knGYfXVt z+kMxlus}t2T5FEyCN~!}90O_X@@PQpuy;kuGz@bWft%diBTx?d)_xWd_-(!LmVrh**oKg!1CNF&LX4{*j|) zIvjCR0I2UUuuEXh<9}oT_zT#jOrJAHNLFT~Ilh9hGJPI1<5`C-WA{tUYlyMeoy!+U zhA#=p!u1R7DNg9u4|QfED-2TuKI}>p#2P9--z;Bbf4Op*;Q9LCbO&aL2i<0O$ByoI z!9;Ght733FC>Pz>$_mw(F`zU?`m@>gE`9_p*=7o=7av`-&ifU(^)UU`Kg3Kw`h9-1 z6`e6+im=|m2v`pN(2dE%%n8YyQz;#3Q-|x`91z?gj68cMrHl}C25|6(_dIGk*8cA3 zRHB|Nwv{@sP4W+YZM)VKI>RlB`n=Oj~Rzx~M+Khz$N$45rLn6k1nvvD^&HtsMA4`s=MmuOJID@$s8Ph4E zAmSV^+s-z8cfv~Yd(40Sh4JG#F~aB>WFoX7ykaOr3JaJ&Lb49=B8Vk-SQT9%7TYhv z?-Pprt{|=Y5ZQ1?od|A<_IJU93|l4oAfBm?3-wk{O<8ea+`}u%(kub(LFo2zFtd?4 zwpN|2mBNywv+d^y_8#<$r>*5+$wRTCygFLcrwT(qc^n&@9r+}Kd_u@Ithz(6Qb4}A zWo_HdBj#V$VE#l6pD0a=NfB0l^6W^g`vm^sta>Tly?$E&{F?TTX~DsKF~poFfmN%2 z4x`Dc{u{Lkqz&y!33;X}weD}&;7p>xiI&ZUb1H9iD25a(gI|`|;G^NwJPv=1S5e)j z;U;`?n}jnY6rA{V^ zxTd{bK)Gi^odL3l989DQlN+Zs39Xe&otGeY(b5>rlIqfc7Ap4}EC?j<{M=hlH{1+d zw|c}}yx88_xQr`{98Z!d^FNH77=u(p-L{W6RvIn40f-BldeF-YD>p6#)(Qzf)lfZj z?3wAMtPPp>vMehkT`3gToPd%|D8~4`5WK{`#+}{L{jRUMt zrFz+O$C7y8$M&E4@+p+oV5c%uYzbqd2Y%SSgYy#xh4G3hQv>V*BnuKQhBa#=oZB~w{azUB+q%bRe_R^ z>fHBilnRTUfaJ201czL8^~Ix#+qOHSO)A|xWLqOxB$dT2W~)e-r9;bm=;p;RjYahB z*1hegN(VKK+ztr~h1}YP@6cfj{e#|sS`;3tJhIJK=tVJ-*h-5y9n*&cYCSdg#EHE# zSIx=r#qOaLJoVVf6v;(okg6?*L_55atl^W(gm^yjR?$GplNP>BZsBYEf_>wM0Lc;T zhf&gpzOWNxS>m+mN92N0{;4uw`P+9^*|-1~$uXpggj4- z^SFc4`uzj2OwdEVT@}Q`(^EcQ_5(ZtXTql*yGzdS&vrS_w>~~ra|Nb5abwf}Y!uq6R5f&6g2ge~2p(%c< z@O)cz%%rr4*cRJ5f`n@lvHNk@lE1a*96Kw6lJ~B-XfJW%?&-y?;E&?1AacU@`N`!O z6}V>8^%RZ7SQnZ-z$(jsX`amu*5Fj8g!3RTRwK^`2_QHe;_2y_n|6gSaGyPmI#kA0sYV<_qOZc#-2BO%hX)f$s-Z3xlI!ub z^;3ru11DA`4heAu%}HIXo&ctujzE2!6DIGE{?Zs>2}J+p&C$rc7gJC35gxhflorvsb%sGOxpuWhF)dL_&7&Z99=5M0b~Qa;Mo!j&Ti_kXW!86N%n= zSC@6Lw>UQ__F&+&Rzv?gscwAz8IP!n63>SP)^62(HK98nGjLY2*e^OwOq`3O|C92? z;TVhZ2SK%9AGW4ZavTB9?)mUbOoF`V7S=XM;#3EUpR+^oHtdV!GK^nXzCu>tpR|89 zdD{fnvCaN^^LL%amZ^}-E+214g&^56rpdc@yv0b<3}Ys?)f|fXN4oHf$six)-@<;W&&_kj z-B}M5U*1sb4)77aR=@%I?|Wkn-QJVuA96an25;~!gq(g1@O-5VGo7y&E_srxL6ZfS z*R%$gR}dyONgju*D&?geiSj7SZ@ftyA|}(*Y4KbvU!YLsi1EDQQCnb+-cM=K1io78o!v*);o<XwjaQH%)uIP&Zm?)Nfbfn;jIr z)d#!$gOe3QHp}2NBak@yYv3m(CPKkwI|{;d=gi552u?xj9ObCU^DJFQp4t4e1tPzM zvsRIGZ6VF+{6PvqsplMZWhz10YwS={?`~O0Ec$`-!klNUYtzWA^f9m7tkEzCy<_nS z=&<(awFeZvt51>@o_~>PLs05CY)$;}Oo$VDO)?l-{CS1Co=nxjqben*O1BR>#9`0^ zkwk^k-wcLCLGh|XLjdWv0_Hg54B&OzCE^3NCP}~OajK-LuRW53CkV~Su0U>zN%yQP zH8UH#W5P3-!ToO-2k&)}nFe`t+mdqCxxAHgcifup^gKpMObbox9LFK;LP3}0dP-UW z?Zo*^nrQ6*$FtZ(>kLCc2LY*|{!dUn$^RW~m9leoF|@Jy|M5p-G~j%+P0_#orRKf8 zvuu5<*XO!B?1E}-*SY~MOa$6c%2cM+xa8}_8x*aVn~57v&W(0mqN1W`5a7*VN{SUH zXz98DDyCnX2EPl-`Lesf`=AQT%YSDb`$%;(jUTrNen$NPJrlpPDP}prI>Ml!r6bCT;mjsg@X^#&<}CGf0JtR{Ecwd&)2zuhr#nqdgHj+g2n}GK9CHuwO zk>oZxy{vcOL)$8-}L^iVfJHAGfwN$prHjYV0ju}8%jWquw>}_W6j~m<}Jf!G?~r5&Rx)!9JNX!ts#SGe2HzobV5); zpj@&`cNcO&q+%*<%D7za|?m5qlmFK$=MJ_iv{aRs+BGVrs)98BlN^nMr{V_fcl_;jkzRju+c-y?gqBC_@J0dFLq-D9@VN&-`R9U;nv$Hg?>$oe4N&Ht$V_(JR3TG^! zzJsbQbi zFE6-{#9{G{+Z}ww!ycl*7rRdmU#_&|DqPfX3CR1I{Kk;bHwF6jh0opI`UV2W{*|nn zf_Y@%wW6APb&9RrbEN=PQRBEpM(N1w`81s=(xQj6 z-eO0k9=Al|>Ej|Mw&G`%q8e$2xVz1v4DXAi8G};R$y)ww638Y=9y$ZYFDM$}vzusg zUf+~BPX>(SjA|tgaFZr_e0{)+z9i6G#lgt=F_n$d=beAt0Sa0a7>z-?vcjl3e+W}+ z1&9=|vC=$co}-Zh*%3588G?v&U7%N1Qf-wNWJ)(v`iO5KHSkC5&g7CrKu8V}uQGcfcz zmBz#Lbqwqy#Z~UzHgOQ;Q-rPxrRNvl(&u6ts4~0=KkeS;zqURz%!-ERppmd%0v>iRlEf+H$yl{_8TMJzo0 z>n)`On|7=WQdsqhXI?#V{>+~}qt-cQbokEbgwV3QvSP7&hK4R{Z{aGHVS3;+h{|Hz z6$Js}_AJr383c_+6sNR|$qu6dqHXQTc6?(XWPCVZv=)D#6_;D_8P-=zOGEN5&?~8S zl5jQ?NL$c%O)*bOohdNwGIKM#jSAC?BVY={@A#c9GmX0=T(0G}xs`-%f3r=m6-cpK z!%waekyAvm9C3%>sixdZj+I(wQlbB4wv9xKI*T13DYG^T%}zZYJ|0$Oj^YtY+d$V$ zAVudSc-)FMl|54n=N{BnZTM|!>=bhaja?o7s+v1*U$!v!qQ%`T-6fBvmdPbVmro&d zk07TOp*KuxRUSTLRrBj{mjsnF8`d}rMViY8j`jo~Hp$fkv9F_g(jUo#Arp;Xw0M$~ zRIN!B22~$kx;QYmOkos@%|5k)!QypDMVe}1M9tZfkpXKGOxvKXB!=lo`p?|R1l=tA zp(1}c6T3Fwj_CPJwVsYtgeRKg?9?}%oRq0F+r+kdB=bFUdVDRPa;E~~>2$w}>O>v=?|e>#(-Lyx?nbg=ckJ#5U6;RT zNvHhXk$P}m9wSvFyU3}=7!y?Y z=fg$PbV8d7g25&-jOcs{%}wTDKm>!Vk);&rr;O1nvO0VrU&Q?TtYVU=ir`te8SLlS zKSNmV=+vF|ATGg`4$N1uS|n??f}C_4Sz!f|4Ly8#yTW-FBfvS48Tef|-46C(wEO_%pPhUC5$-~Y?!0vFZ^Gu`x=m7X99_?C-`|h zfmMM&Y@zdfitA@KPw4Mc(YHcY1)3*1xvW9V-r4n-9ZuBpFcf{yz+SR{ zo$ZSU_|fgwF~aakGr(9Be`~A|3)B=9`$M-TWKipq-NqRDRQc}ABo*s_5kV%doIX7LRLRau_gd@Rd_aLFXGSU+U?uAqh z8qusWWcvgQ&wu{|sRXmv?sl=xc<$6AR$+cl& zFNh5q1~kffG{3lDUdvEZu5c(aAG~+64FxdlfwY^*;JSS|m~CJusvi-!$XR`6@XtY2 znDHSz7}_Bx7zGq-^5{stTRy|I@N=>*y$zz>m^}^{d&~h;0kYiq8<^Wq7Dz0w31ShO^~LUfW6rfitR0(=3;Uue`Y%y@ex#eKPOW zO~V?)M#AeHB2kovn1v=n^D?2{2jhIQd9t|_Q+c|ZFaWt+r&#yrOu-!4pXAJuxM+Cx z*H&>eZ0v8Y`t}8{TV6smOj=__gFC=eah)mZt9gwz>>W$!>b3O;Rm^Ig*POZP8Rl0f zT~o=Nu1J|lO>}xX&#P58%Yl z83`HRs5#32Qm9mdCrMlV|NKNC+Z~ z9OB8xk5HJ>gBLi+m@(pvpw)1(OaVJKs*$Ou#@Knd#bk+V@y;YXT?)4eP9E5{J%KGtYinNYJUH9PU3A}66c>Xn zZ{Bn0<;8$WCOAL$^NqTjwM?5d=RHgw3!72WRo0c;+houoUA@HWLZM;^U$&sycWrFd zE7ekt9;kb0`lps{>R(}YnXlyGY}5pPd9zBpgXeJTY_jwaJGSJQC#-KJqmh-;ad&F- z-Y)E>!&`Rz!HtCz>%yOJ|v(u7P*I$jqEY3}(Z-orn4 zlI?CYKNl`6I){#2P1h)y(6?i;^z`N3bxTV%wNvQW+eu|x=kbj~s8rhCR*0H=iGkSj zk23lr9kr|p7#qKL=UjgO`@UnvzU)`&fI>1Qs7ubq{@+lK{hH* zvl6eSb9%yngRn^T<;jG1SVa)eA>T^XX=yUS@NCKpk?ovCW1D@!=@kn;l_BrG;hOTC z6K&H{<8K#dI(A+zw-MWxS+~{g$tI7|SfP$EYKxA}LlVO^sT#Oby^grkdZ^^lA}uEF zBSj$weBJG{+Bh@Yffzsw=HyChS(dtLE3i*}Zj@~!_T-Ay7z=B)+*~3|?w`Zd)Co2t zC&4DyB!o&YgSw+fJn6`sn$e)29`kUwAc+1MND7YjV%lO;H2}fNy>hD#=gT ze+-aFNpyKIoXY~Vq-}OWPBe?Rfu^{ps8>Xy%42r@RV#*QV~P83jdlFNgkPN=T|Kt7 zV*M`Rh*30&AWlb$;ae130e@}Tqi3zx2^JQHpM>j$6x`#{mu%tZlwx9Gj@Hc92IuY* zarmT|*d0E~vt6<+r?W^UW0&#U&)8B6+1+;k^2|FWBRP9?C4Rk)HAh&=AS8FS|NQaZ z2j!iZ)nbEyg4ZTp-zHwVlfLC~tXIrv(xrP8PAtR{*c;T24ycA-;auWsya-!kF~CWZ zw_uZ|%urXgUbc@x=L=_g@QJ@m#5beS@6W195Hn7>_}z@Xt{DIEA`A&V82bc^#!q8$ zFh?z_Vn|ozJ;NPd^5uu(9tspo8t%&-U9Ckay-s@DnM*R5rtu|4)~e)`z0P-sy?)kc zs_k&J@0&0!q4~%cKL)2l;N*T&0;mqX5T{Qy60%JtKTQZ-xb%KOcgqwJmb%MOOKk7N zgq})R_6**{8A|6H?fO+2`#QU)p$Ei2&nbj6TpLSIT^D$|`TcSeh+)}VMb}LmvZ{O| ze*1IdCt3+yhdYVxcM)Q_V0bIXLgr6~%JS<<&dxIgfL=Vnx4YHuU@I34JXA|+$_S3~ zy~X#gO_X!cSs^XM{yzDGNM>?v(+sF#<0;AH^YrE8smx<36bUsHbN#y57K8WEu(`qHvQ6cAZPo=J5C(lSmUCZ57Rj6cx!e^rfaI5%w}unz}4 zoX=nt)FVNV%QDJH`o!u9olLD4O5fl)xp+#RloZlaA92o3x4->?rB4`gS$;WO{R;Z3>cG3IgFX2EA?PK^M}@%1%A;?f6}s&CV$cIyEr#q5;yHdNZ9h{| z-=dX+a5elJoDo?Eq&Og!nN6A)5yYpnGEp}?=!C-V)(*~z-+?kY1Q7qs#Rsy%hu_60rdbB+QQNr?S1 z?;xtjUv|*E3}HmuNyB9aFL5H~3Ho0UsmuMZELp1a#CA1g`P{-mT?BchuLEtK}!QZ=3AWakRu~?f9V~3F;TV`5%9Pcs_$gq&CcU}r8gOO zC2&SWPsSG{&o-LIGTBqp6SLQZPvYKp$$7L4WRRZ0BR$Kf0I0SCFkqveCp@f)o8W)! z$%7D1R`&j7W9Q9CGus_)b%+B#J2G;l*FLz#s$hw{BHS~WNLODV#(!u_2Pe&tMsq={ zdm7>_WecWF#D=?eMjLj=-_z`aHMZ=3_-&E8;ibPmM}61i6J3is*=dKf%HC>=xbj4$ zS|Q-hWQ8T5mWde6h@;mS+?k=89?1FU<%qH9B(l&O>k|u_aD|DY*@~(`_pb|B#rJ&g zR0(~(68fpUPz6TdS@4JT5MOPrqDh5_H(eX1$P2SQrkvN8sTxwV>l0)Qq z0pzTuvtEAKRDkKGhhv^jk%|HQ1DdF%5oKq5BS>szk-CIke{%js?~%@$uaN3^Uz6Wf z_iyx{bZ(;9y4X&>LPV=L=d+A}7I4GkK0c1Xts{rrW1Q7apHf-))`BgC^0^F(>At1* za@e7{lq%yAkn*NH8Q1{@{lKhRg*^TfGvv!Sn*ed*x@6>M%aaqySxR|oNadYt1mpUZ z6H(rupHYf&Z z29$5g#|0MX#aR6TZ$@eGxxABRKakDYtD%5BmKp;HbG_ZbT+=81E&=XRk6m_3t9PvD zr5Cqy(v?gHcYvYvXkNH@S#Po~q(_7MOuCAB8G$a9BC##gw^5mW16cML=T=ERL7wsk zzNEayTG?mtB=x*wc@ifBCJ|irFVMOvH)AFRW8WE~U()QT=HBCe@s$dA9O!@`zAAT) zaOZ7l6vyR+Nk_OOF!ZlZmjoImKh)dxFbbR~z(cMhfeX1l7S_`;h|v3gI}n9$sSQ>+3@AFAy9=B_y$)q;Wdl|C-X|VV3w8 z2S#>|5dGA8^9%Bu&fhmVRrTX>Z7{~3V&0UpJNEl0=N32euvDGCJ>#6dUSi&PxFW*s zS`}TB>?}H(T2lxBJ!V#2taV;q%zd6fOr=SGHpoSG*4PDaiG0pdb5`jelVipkEk%FV zThLc@Hc_AL1#D&T4D=w@UezYNJ%0=f3iVRuVL5H?eeZM}4W*bomebEU@e2d`M<~uW zf#Bugwf`VezG|^Qbt6R_=U0}|=k;mIIakz99*>FrsQR{0aQRP6ko?5<7bkDN8evZ& zB@_KqQG?ErKL=1*ZM9_5?Pq%lcS4uLSzN(Mr5=t6xHLS~Ym`UgM@D&VNu8e?_=nSFtF$u@hpPSmI4Vo_t&v?>$~K4y(O~Rb*(MFy_igM7 z*~yYUyR6yQgzWnWMUgDov!!g=lInM+=lOmOk4L`O?{i&qxy&D*_qorRbDwj6?)!ef z#JLd7F6Z2I$S0iYI={rZNk*<{HtIl^mx=h>Cim*04K4+Z4IJtd*-)%6XV2(MCscPiw_a+y*?BKbTS@BZ3AUao^%Zi#PhoY9Vib4N>SE%4>=Jco0v zH_Miey{E;FkdlZSq)e<{`+S3W=*ttvD#hB8w=|2aV*D=yOV}(&p%0LbEWH$&@$X3x~CiF-?ejQ*N+-M zc8zT@3iwkdRT2t(XS`d7`tJQAjRmKAhiw{WOqpuvFp`i@Q@!KMhwKgsA}%@sw8Xo5Y=F zhRJZg)O4uqNWj?V&&vth*H#je6T}}p_<>!Dr#89q@uSjWv~JuW(>FqoJ5^ho0%K?E z9?x_Q;kmcsQ@5=}z@tdljMSt9-Z3xn$k)kEjK|qXS>EfuDmu(Z8|(W?gY6-l z@R_#M8=vxKMAoi&PwnaIYw2COJM@atcgfr=zK1bvjW?9B`-+Voe$Q+H$j!1$Tjn+* z&LY<%)L@;zhnJlB^Og6I&BOR-m?{IW;tyYC%FZ!&Z>kGjHJ6cqM-F z&19n+e1=9AH1VrVeHrIzqlC`w9=*zfmrerF?JMzO&|Mmv;!4DKc(sp+jy^Dx?(8>1 zH&yS_4yL7m&GWX~mdfgH*AB4{CKo;+egw=PrvkTaoBU+P-4u?E|&!c z)DKc;>$$B6u*Zr1SjUh2)FeuWLWHl5TH(UHWkf zLs>7px!c5n;rbe^lO@qlYLzlDVp(z?6rPZel=YB)Uv&n!2{+Mb$-vQl=xKw( zve&>xYx+jW_NJh!FV||r?;hdP*jOXYcLCp>DOtJ?2S^)DkM{{Eb zS$!L$e_o0(^}n3tA1R3-$SNvgBq;DOEo}fNc|tB%%#g4RA3{|euq)p+xd3I8^4E&m zFrD%}nvG^HUAIKe9_{tXB;tl|G<%>yk6R;8L2)KUJw4yHJXUOPM>(-+jxq4R;z8H#>rnJy*)8N+$wA$^F zN+H*3t)eFEgxLw+Nw3};4WV$qj&_D`%ADV2%r zJCPCo%{=z7;`F98(us5JnT(G@sKTZ^;2FVitXyLe-S5(hV&Ium+1pIUB(CZ#h|g)u zSLJJ<@HgrDiA-}V_6B^x1>c9B6%~847JkQ!^KLZ2skm;q*edo;UA)~?SghG8;QbHh z_6M;ouo_1rq9=x$<`Y@EA{C%6-pEV}B(1#sDoe_e1s3^Y>n#1Sw;N|}8D|s|VPd+g z-_$QhCz`vLxxrVMx3ape1xu3*wjx=yKSlM~nFgkNWb4?DDr*!?U)L_VeffF<+!j|b zZ$Wn2$TDv3C3V@BHpSgv3JUif8%hk%OsGZ=OxH@8&4`bbf$`aAMchl^qN>Eyu3JH} z9-S!x8-s4fE=lad%Pkp8hAs~u?|uRnL48O|;*DEU! zuS0{cpk%1E0nc__2%;apFsTm0bKtd&A0~S3Cj^?72-*Owk3V!ZG*PswDfS~}2<8le z5+W^`Y(&R)yVF*tU_s!XMcJS`;(Tr`J0%>p=Z&InR%D3@KEzzI+-2)HK zuoNZ&o=wUC&+*?ofPb0a(E6(<2Amd6%uSu_^-<1?hsxs~0K5^f(LsGqgEF^+0_H=uNk9S0bb!|O8d?m5gQjUKevPaO+*VfSn^2892K~%crWM8+6 z25@V?Y@J<9w%@NXh-2!}SK_(X)O4AM1-WTg>sj1{lj5@=q&dxE^9xng1_z9w9DK>| z6Iybcd0e zyi;Ew!KBRIfGPGytQ6}z}MeXCfLY0?9%RiyagSp_D1?N&c{ zyo>VbJ4Gy`@Fv+5cKgUgs~na$>BV{*em7PU3%lloy_aEovR+J7TfQKh8BJXyL6|P8un-Jnq(ghd!_HEOh$zlv2$~y3krgeH;9zC}V3f`uDtW(%mT#944DQa~^8ZI+zAUu4U(j0YcDfKR$bK#gvn_{JZ>|gZ5+)u?T$w7Q%F^;!Wk?G z(le7r!ufT*cxS}PR6hIVtXa)i`d$-_1KkyBU>qmgz-=T};uxx&sKgv48akIWQ89F{ z0XiY?WM^~;|T8zBOr zs#zuOONzH?svv*jokd5SK8wG>+yMC)LYL|vLqm^PMHcT=`}V$=nIRHe2?h)8WQa6O zPAU}d`1y(>kZiP~Gr=mtJLMu`i<2CspL|q2DqAgAD^7*$xzM`PU4^ga`ilE134XBQ z99P(LhHU@7qvl9Yzg$M`+dlS=x^(m-_3t|h>S}E0bcFMn=C|KamQ)=w2^e)35p`zY zRV8X?d;s^>Cof2SPR&nP3E+-LCkS0J$H!eh8~k0qo$}00b=7!H_I2O+Ro@3O$nPdm ztmbOO^B+IHzQ5w>@@@J4cKw5&^_w6s!s=H%&byAbUtczPQ7}wfTqxxtQNfn*u73Qw zGuWsrky_ajPx-5`R<)6xHf>C(oqGf_Fw|-U*GfS?xLML$kv;h_pZ@Kk$y0X(S+K80 z6^|z)*`5VUkawg}=z`S;VhZhxyDfrE0$(PMurAxl~<>lfZa>JZ288ULK7D` zl9|#L^JL}Y$j*j`0-K6kH#?bRmg#5L3iB4Z)%iF@SqT+Lp|{i`m%R-|ZE94Np7Pa5 zCqC^V3}B(FR340pmF*qaa}M}+h6}mqE~7Sh!9bDv9YRT|>vBNAqv09zXHMlcuhKD| zcjjA(b*XCIwJ33?CB!+;{)vX@9xns_b-VO{i0y?}{!sdXj1GM8+$#v>W7nw;+O_9B z_{4L;C6ol?(?W0<6taGEn1^uG=?Q3i29sE`RfYCaV$3DKc_;?HsL?D_fSYg}SuO5U zOB_f4^vZ_x%o`5|C@9C5+o=mFy@au{s)sKw!UgC&L35aH(sgDxRE2De%(%OT=VUdN ziVLEmdOvJ&5*tCMKRyXctCwQu_RH%;m*$YK&m;jtbdH#Ak~13T1^f89tn`A%QEHWs~jnY~E}p_Z$XC z=?YXLCkzVSK+Id`xZYTegb@W8_baLt-Fq`Tv|=)JPbFsKRm)4UW;yT+J`<)%#ue9DPOkje)YF2fsCilK9MIIK>p*`fkoD5nGfmLwt)!KOT+> zOFq*VZktDDyM3P5UOg`~XL#cbzC}eL%qMB=Q5$d89MKuN#$6|4gx_Jt0Gfn8w&q}%lq4QU%6#jT*MRT% zrLz~C8FYKHawn-EQWN1B75O&quS+Z81(zN)G>~vN8VwC+e+y(`>HcxC{MrJ;H1Z4k zZWuv$w_F0-Ub%MVcpIc){4PGL^I7M{>;hS?;eH!;gmcOE66z3;Z1Phqo(t zVP(Hg6q#0gIKgsg7L7WE!{Y#1nI(45tx2{$34dDd#!Z0NIyrm)HOn5W#7;f4pQci# zDW!FI(g4e668kI9{2+mLwB+=#9bfqgX%!B34V-$wwSN(_cm*^{y0jQtv*4}eO^sOV z*9xoNvX)c9isB}Tgx&ZRjp3kwhTVK?r9;n!x>^XYT z@Q^7zp{rkIs{2mUSE^2!Gf6$6;j~&4=-0cSJJDizZp6LTe8b45;{AKM%v99}{{FfC zz709%u0mC=1KXTo(=TqmZQ;c?$M3z(!xah>aywrj40sc2y3rKFw4jCq+Y+u=CH@_V zxz|qeTwa>+<|H%8Dz5u>ZI5MmjTFwXS-Fv!TDd*`>3{krWoNVx$<133`(ftS?ZPyY z&4@ah^3^i`vL$BZa>O|Nt?ucewzsF)0zX3qmM^|waXr=T0pfIb0*$AwU=?Ipl|1Y; z*Pk6{C-p4MY;j@IJ|DW>QHZQJcp;Z~?8(Q+Kk3^0qJ}SCk^*n4W zu9ZFwLHUx-$6xvaQ)SUQcYd6fF8&x)V`1bIuX@>{mE$b|Yd(qomn3;bPwnDUc0F=; zh*6_((%bqAYQWQ~odER?h>1mkL4kpb3s7`0m@rDKGU*oyF)$j~Ffd4fXV$?`f~rHf zB%Y)@5SXZvfwm10RY5X?TEo)PK_`L6qgBp=#>fO49$D zDq8Ozj0q6213tV5Qq=;fZ0$|KroY{Dz=l@lU^J)?Ko@ti20TRplXzphBi>XGx4bou zEWrkNjz0t5j!_ke{g5I#PUlEU$Km8g8TE|XK=MkU@PT4T><2OVamoK;wJ}3X0L$vX zgd7gNa359*nc)R-0!`2X@FOTB`+oETOPc=ubp5R)VQgY+5BTZZJ2?9QwnO=dnulIUF3gFn;BODC2)65)HeVd%t86sL7Rv^Y+nbn+&l z6BAJY(ETvwI)Ts$aiE8rht4KD*qNyE{8{x6R|%akbTBzw;2+6Echkt+W+`u^XX z_z&x%n /dev/null && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -131,29 +133,22 @@ location of your Java installation." fi else JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then done fi - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -214,12 +205,6 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 7101f8e467..ac1b06f938 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%"=="" @echo off +@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,8 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused +if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute +if "%ERRORLEVEL%" == "0" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail @@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail @@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd +if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal From a938485778cc9500e08aef04d05e9fe5b9bcd5cc Mon Sep 17 00:00:00 2001 From: John Ed Quinn Date: Tue, 11 Jun 2024 13:56:16 -0700 Subject: [PATCH 13/13] Revert "Updates conformance tests to Gradle 8.7" This reverts commit 0e1564e837be65337de60a8f31eef46201ab9fb8. --- .github/workflows/conformance-report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conformance-report.yml b/.github/workflows/conformance-report.yml index 81e2c62e65..bdcf83d8f9 100644 --- a/.github/workflows/conformance-report.yml +++ b/.github/workflows/conformance-report.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 8.7 + gradle-version: 7.5.1 # Run the conformance tests and save to an Ion file. - name: gradle test of the conformance tests (can fail) and save to Ion file continue-on-error: true @@ -68,7 +68,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 8.7 + gradle-version: 7.5.1 - name: Download `conformance_test_results.ion` from target branch uses: dawidd6/action-download-artifact@v2 id: download-report