diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTest.kt new file mode 100644 index 000000000..e6889638b --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTest.kt @@ -0,0 +1,236 @@ +package org.partiql.planner + +import com.amazon.ionelement.api.loadSingleElement +import org.partiql.parser.PartiQLParser +import org.partiql.plan.Statement +import org.partiql.plan.debug.PlanPrinter +import org.partiql.planner.catalog.Catalog +import org.partiql.planner.catalog.Name +import org.partiql.planner.catalog.Session +import org.partiql.planner.internal.TestCatalog +import org.partiql.planner.test.Test +import org.partiql.planner.util.ProblemCollector +import org.partiql.plugins.local.toStaticType +import org.partiql.types.PType + +/** + * This is a port of the original assertion logic from [org.partiql.planner.internal.typer.PlanTyperTestsPorted]. + * + * @see Success + * @see org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.SuccessTestCase + * @see Failure + * @see org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.ErrorTestCase + */ +abstract class TyperTest( + private val _name: String, + open var expectedType: PType?, + var statement: String, + var catalog: String, + var directory: List, +) : Test { + + val parser = PartiQLParser.default() + val planner = PartiQLPlanner.builder().signal().build() + + override fun getName(): String { + return _name + } + + override fun toString(): String { + return getName() + } + + val catalogs: List by lazy { + // Make a map from catalog name to tables. + val inputStream = this::class.java.getResourceAsStream("/resource_path.txt")!! + val map = mutableMapOf>>() + inputStream.reader().readLines().forEach { path -> + if (path.startsWith("catalogs/default")) { + val schema = this::class.java.getResourceAsStream("/$path")!! + val ion = loadSingleElement(schema.reader().readText()) + val staticType = ion.toStaticType() + val steps = path.substring(0, path.length - 4).split('/').drop(2) // drop the catalogs/default + val catalogName = steps.first() + // args + val name = Name.of(steps.drop(1)) + val ptype = PType.fromStaticType(staticType) + if (map.containsKey(catalogName)) { + map[catalogName]!!.add(name to ptype) + } else { + map[catalogName] = mutableListOf(name to ptype) + } + } + } + // Make a catalogs list + map.map { (catalog, tables) -> + TestCatalog.builder() + .name(catalog) + .apply { + for ((name, schema) in tables) { + createTable(name, schema) + } + } + .build() + } + } + + /** + * This represents a successful test case. + * + * The serialization of a test case looks as follows: + * ``` + * test::{ + * name:"Test #4", + * type:"type", + * body:{ + * statement:"b", + * session:{ + * catalog:"b", + * cwd:[ + * "b" + * ] + * }, + * status:SUCCESS, + * expected:( + * ROW + * "b" + * ( + * ROW + * "b" + * INTEGER + * ) + * "c" + * INTEGER + * ) + * } + * } + * ``` + * @see org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.SuccessTestCase + */ + class Success( + name: String, + expectedType: PType, + statement: String, + catalog: String, + directory: List, + ) : TyperTest(name, expectedType, statement, catalog, directory) { + + override fun assert() { + val session = Session.builder() + .catalog(catalog) + .catalogs(*catalogs.toTypedArray()) + .namespace(directory) + .build() + + val collector = ProblemCollector() + val ast = parser.parse(statement).root + val plan = planner.plan(ast, session, collector).plan + when (val statement = plan.statement) { + is Statement.Query -> { + assert(collector.problems.isEmpty()) { + buildString { + appendLine(collector.problems.toString()) + appendLine() + PlanPrinter.append(this, plan) + } + } + val actual = statement.root.type + assert(expectedType == actual) { + buildString { + appendLine() + appendLine("Name: ${getName()}") + appendLine("Expect: $expectedType") + appendLine("Actual: $actual") + appendLine("Statement: $statement") + appendLine() + PlanPrinter.append(this, plan) + } + } + } + } + } + } + + /** + * This represents an error test case. + * + * The serialization of an error test case looks as follows: + * ``` + * test::{ + * name:"Pets should not be accessible #1", + * type:"type", + * body:{ + * statement:"SELECT * FROM pets", + * session:{ + * catalog:"pql", + * cwd:[ + * ] + * }, + * status:FAILURE, + * assertProblemExists:( + * ERROR + * '''Variable pets does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + * ) + * } + * } + * ``` + * The `assertProblemExists` is optional. + * + * @see org.partiql.planner.internal.typer.PlanTyperTestsPorted.TestCase.ErrorTestCase + */ + class Failure( + name: String, + expectedType: PType?, + private val assertion: TyperTestBuilder.ProblemAssertion?, + statement: String, + catalog: String, + directory: List, + ) : TyperTest(name, expectedType, statement, catalog, directory) { + override fun assert() { + val session = Session.builder() + .catalog(catalog) + .catalogs(*catalogs.toTypedArray()) + .namespace(directory) + .build() + val collector = ProblemCollector() + val ast = parser.parse(statement).root + val plan = planner.plan(ast, session, collector).plan + + when (val statement = plan.statement) { + is Statement.Query -> { + assert(collector.problems.isNotEmpty()) { + buildString { + appendLine("Expected to find problems, but none were found.") + appendLine() + PlanPrinter.append(this, plan) + } + } + if (expectedType != null) { + assert(expectedType == statement.root.type) { + buildString { + appendLine() + appendLine("Name: ${getName()}") + appendLine("Expect: $expectedType") + appendLine("Actual: ${statement.root.type}") + appendLine("Statement: $statement") + appendLine() + PlanPrinter.append(this, plan) + } + } + } + assert(collector.problems.isNotEmpty()) { + "Expected to find problems, but none were found." + } + if (assertion != null) { + val problemFound = collector.problems.any { + it.details.severity.name == assertion.severity && it.details.message == assertion.message + } + assert(problemFound) { + "Could not find problem: ${assertion.severity}: ${assertion.message}." + } + } + } + } + } + } +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTestBuilder.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTestBuilder.kt new file mode 100644 index 000000000..b5261e3eb --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/TyperTestBuilder.kt @@ -0,0 +1,196 @@ +package org.partiql.planner + +import com.amazon.ionelement.api.AnyElement +import com.amazon.ionelement.api.ElementType +import com.amazon.ionelement.api.SexpElement +import com.amazon.ionelement.api.StructElement +import org.partiql.planner.test.PartiQLTest +import org.partiql.planner.test.PartiQLTestProvider +import org.partiql.planner.test.Test +import org.partiql.planner.test.TestBuilder +import org.partiql.planner.test.TestId +import org.partiql.types.Field +import org.partiql.types.PType + +/** + * This parses an Ion test configuration where the test type is set to "type". Based on the test body's [FIELD_STATUS], + * this will either return a [TyperTest.Success] or [TyperTest.Failure]. + * @see FIELD_STATUS + * @see STATUS_SUCCESS + * @see STATUS_FAILURE + * @see TyperTest + * @see TyperTest.Success + * @see TyperTest.Failure + */ +class TyperTestBuilder : TestBuilder { + + private var name: TestId? = null + private var expectedType: PType? = null + private var isSuccess: Boolean = true + private var statement: String? = null + private var sessionCatalog: String? = null + private var sessionDirectory: List? = null + private var inputProvider: PartiQLTestProvider? = null + private var assertion: ProblemAssertion? = null + + companion object { + const val STATUS_SUCCESS = "SUCCESS" + const val STATUS_FAILURE = "FAILURE" + const val FIELD_EXPECTED = "expected" + const val FIELD_STATUS = "status" + const val FIELD_STATEMENT = "statement" + const val FIELD_SESSION = "session" + const val FIELD_CATALOG = "catalog" + const val FIELD_CWD = "cwd" + const val FIELD_ASSERT_PROBLEM_EXISTS = "assertProblemExists" + } + + override fun id(id: TestId): TestBuilder = this.apply { + this.name = id + } + + override fun config(body: StructElement, statementProvider: PartiQLTestProvider): TestBuilder = this.apply { + this.inputProvider = statementProvider + val session = body[FIELD_SESSION].asStruct() + this.sessionCatalog = session[FIELD_CATALOG].asString().textValue + this.sessionDirectory = session[FIELD_CWD].asList().values.map { it.asString().textValue } + this.statement = getStatement(body[FIELD_STATEMENT]) + val status = body[FIELD_STATUS].asSymbol().textValue + when (status) { + STATUS_SUCCESS -> { + this.isSuccess = true + this.expectedType = TypeParser.parse(body[FIELD_EXPECTED]) + } + STATUS_FAILURE -> { + this.isSuccess = false + if (body.containsField(FIELD_EXPECTED)) { + this.expectedType = TypeParser.parse(body[FIELD_EXPECTED]) + } + if (body.containsField(FIELD_ASSERT_PROBLEM_EXISTS)) { + val sexp = body[FIELD_ASSERT_PROBLEM_EXISTS].asSexp() + val severity = sexp.values[0].textValue + val message = sexp.values[1].textValue + this.assertion = ProblemAssertion(severity, message) + } + } + } + } + + override fun build(): Test { + return when (this.isSuccess) { + true -> TyperTest.Success(this.name.toString(), this.expectedType!!, this.statement!!, this.sessionCatalog!!, this.sessionDirectory!!) + false -> TyperTest.Failure(this.name.toString(), this.expectedType, this.assertion, this.statement!!, this.sessionCatalog!!, this.sessionDirectory!!) + } + } + + private fun getStatement(element: AnyElement): String { + return when (element.type) { + ElementType.STRING, ElementType.SYMBOL -> element.textValue + ElementType.SEXP -> { + val sexp = element.asSexp() + val values = sexp.values + assert(values[0].textValue == "@") + val remaining = values.subList(1, values.size) + assert(remaining.size == 3) + assert(remaining[0].textValue == "\$inputs") + val group = remaining[1].textValue + val name = remaining[2].textValue + return inputProvider?.get(PartiQLTest.Key(group, name))?.statement ?: error("Input Provider: $inputProvider") + } + else -> "Unsupported element type for statement: ${element.type}" + } + } + + private object TypeParser { + fun parse(element: AnyElement): PType { + return when (element.type) { + ElementType.STRING, ElementType.SYMBOL -> parseTypeSimple(element.textValue.trim()) + ElementType.SEXP -> parseTypeComplex(element.asSexp()) + else -> error("Cannot parse type from ${element.type}.") + } + } + + val SIMPLE_TYPE_MAP = mapOf( + PType.Kind.DECIMAL to PType.decimal(), + PType.Kind.STRING to PType.string(), + PType.Kind.BOOL to PType.bool(), + PType.Kind.TINYINT to PType.tinyint(), + PType.Kind.SMALLINT to PType.smallint(), + PType.Kind.INTEGER to PType.integer(), + PType.Kind.BIGINT to PType.bigint(), + PType.Kind.NUMERIC to PType.numeric(), + PType.Kind.DECIMAL_ARBITRARY to PType.decimal(), + PType.Kind.STRING to PType.string(), + PType.Kind.STRUCT to PType.struct(), + PType.Kind.ARRAY to PType.array(), + PType.Kind.BAG to PType.bag(), + PType.Kind.BOOL to PType.bool(), + PType.Kind.DYNAMIC to PType.dynamic(), + PType.Kind.DOUBLE to PType.doublePrecision(), + PType.Kind.DATE to PType.date() + ) + + val COMPLEX_TYPE_MAP = mapOf( + PType.Kind.DECIMAL to TypeParser::typeDecimal, + PType.Kind.CHAR to { params -> typeWithParamInt(params, PType::character) }, + PType.Kind.VARCHAR to { params -> typeWithParamInt(params, PType::varchar) }, + PType.Kind.CLOB to { params -> typeWithParamInt(params, PType::clob) }, + PType.Kind.BLOB to { params -> typeWithParamInt(params, PType::blob) }, + PType.Kind.TIME to { params -> typeWithParamInt(params, PType::time) }, + PType.Kind.TIMEZ to { params -> typeWithParamInt(params, PType::timez) }, + PType.Kind.TIMESTAMP to { params -> typeWithParamInt(params, PType::timestamp) }, + PType.Kind.TIMESTAMPZ to { params -> typeWithParamInt(params, PType::timestampz) }, + PType.Kind.BAG to { params -> typeWithParamType(params, PType::bag) }, + PType.Kind.ARRAY to { params -> typeWithParamType(params, PType::array) }, + PType.Kind.SEXP to { params -> typeWithParamType(params, PType::sexp) }, + PType.Kind.ROW to TypeParser::typeRow, + ) + + private fun parseTypeComplex(element: SexpElement): PType { + val values = element.values + val kind = PType.Kind.valueOf(values.first().textValue.trim()) + val params = values.drop(1) + return COMPLEX_TYPE_MAP[kind]?.invoke(params) ?: error("Unsupported type: $kind") + } + + private fun typeDecimal(params: List): PType { + assert(params.size == 2) + val precision = params[0].asInt().longValue.toInt() + val scale = params[1].asInt().longValue.toInt() + return PType.decimal(precision, scale) + } + + private fun typeWithParamInt(params: List, typeInit: (Int) -> PType): PType { + assert(params.size == 1) + val length = params[0].asInt().longValue.toInt() + return typeInit(length) + } + + private fun typeWithParamType(params: List, typeInit: (PType) -> PType): PType { + assert(params.size == 1) + val typeParam = parse(params[0]) + return typeInit(typeParam) + } + + private fun typeRow(params: List): PType { + assert(params.size % 2 == 0) + val fields = mutableListOf() + for (i in params.indices.filter { it % 2 == 0 }) { + val name = params[i].textValue + val type = parse(params[i + 1]) + fields.add(Field.of(name, type)) + } + return PType.row(fields) + } + + private fun parseTypeSimple(name: String): PType { + val kind = PType.Kind.valueOf(name) + return SIMPLE_TYPE_MAP[kind] ?: error("Unsupported type: $name") + } + } + + class ProblemAssertion( + val severity: String, + val message: String + ) +} 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 500b77bc3..22a4e0382 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 @@ -53,15 +53,27 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue +/** + * NOTE: These have been serialized as Ion. + * TODO: Delete this file once errors have a better serialization. + * @see PrintPlanTyperTestsPorted + * @see TyperTests + */ internal class PlanTyperTestsPorted { sealed class TestCase { + abstract val name: String? + abstract val key: PartiQLTest.Key? + abstract val query: String? + abstract val catalog: String + abstract val catalogPath: List + class SuccessTestCase( - val name: String? = null, - val key: PartiQLTest.Key? = null, - val query: String? = null, - val catalog: String = "pql", - val catalogPath: List = emptyList(), + override val name: String? = null, + override val key: PartiQLTest.Key? = null, + override val query: String? = null, + override val catalog: String = "pql", + override val catalogPath: List = emptyList(), val expected: CompilerType, val warnings: ProblemHandler? = null, ) : TestCase() { @@ -84,14 +96,14 @@ internal class PlanTyperTestsPorted { } class ErrorTestCase( - val name: String, - val key: PartiQLTest.Key?, - val query: String?, - val catalog: String = "pql", - val catalogPath: List = emptyList(), + override val name: String, + override val key: PartiQLTest.Key?, + override val query: String?, + override val catalog: String = "pql", + override val catalogPath: List = emptyList(), val note: String?, val expected: CompilerType?, - val problemHandler: ProblemHandler?, + val problemHandler: AssertProblemExists?, ) : TestCase() { constructor( @@ -102,20 +114,21 @@ internal class PlanTyperTestsPorted { catalogPath: List = emptyList(), note: String? = null, expected: StaticType? = null, - problemHandler: ProblemHandler? = null, + problemHandler: AssertProblemExists? = null, ) : this(name, key, query, catalog, catalogPath, note, expected?.let { PType.fromStaticType(it).toCType() }, problemHandler) override fun toString(): String = "$name : ${query ?: key}" } class ThrowingExceptionTestCase( - val name: String, - val query: String, - val catalog: String = "pql", - val catalogPath: List = emptyList(), + override val name: String, + override val query: String, + override val catalog: String = "pql", + override val catalogPath: List = emptyList(), val note: String? = null, val expectedThrowable: KClass, ) : TestCase() { + override val key: PartiQLTest.Key? = null override fun toString(): String { return "$name : $query" } @@ -127,19 +140,21 @@ internal class PlanTyperTestsPorted { private val parser = PartiQLParser.default() private val planner = PartiQLPlanner.builder().signal().build() - private fun assertProblemExists(problem: Problem) = ProblemHandler { problems, ignoreSourceLocation -> - val message = buildString { - appendLine("Expected problems to include: $problem") - appendLine("Received: [") - problems.forEach { - append("\t") - appendLine(it) + class AssertProblemExists(val problem: Problem) : ProblemHandler { + override fun handle(problems: List, ignoreSourceLocation: Boolean) { + val message = buildString { + appendLine("Expected problems to include: $problem") + appendLine("Received: [") + problems.forEach { + append("\t") + appendLine(it) + } + appendLine("]") + } + when (ignoreSourceLocation) { + true -> assertTrue(message) { problems.any { it.details == problem.details } } + false -> assertTrue(message) { problems.any { it == problem } } } - appendLine("]") - } - when (ignoreSourceLocation) { - true -> assertTrue(message) { problems.any { it.details == problem.details } } - false -> assertTrue(message) { problems.any { it == problem } } } } @@ -754,7 +769,7 @@ internal class PlanTyperTestsPorted { CURRENT_DATE AS "curr_date", CURRENT_USER || ' is my name.' AS name_desc FROM << 0, 1 >>; - """.trimIndent(), + """, expected = BagType( StructType( fields = listOf( @@ -777,7 +792,7 @@ internal class PlanTyperTestsPorted { name = "Current User (String) PLUS String", query = "CURRENT_USER + 'hello'", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf( StaticType.STRING, @@ -850,7 +865,7 @@ internal class PlanTyperTestsPorted { name = "BITWISE_AND_MISSING_OPERAND", query = "1 & MISSING", expected = INT4, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.expressionAlwaysReturnsMissing("Static function always receives MISSING arguments.") ) ), @@ -858,7 +873,7 @@ internal class PlanTyperTestsPorted { name = "BITWISE_AND_NON_INT_OPERAND", query = "1 & 'NOT AN INT'", expected = StaticType.ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.INT4, StaticType.STRING), "BITWISE_AND", @@ -1014,7 +1029,7 @@ internal class PlanTyperTestsPorted { ) ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("a"), setOf("t1", "t2")) ) ), @@ -1025,7 +1040,7 @@ internal class PlanTyperTestsPorted { FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs LEFT OUTER JOIN lhs AS rhs ON lhs[2] = rhs - """.trimIndent(), + """, expected = BagType(INT4) ), SuccessTestCase( @@ -1035,7 +1050,7 @@ internal class PlanTyperTestsPorted { FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs INNER JOIN lhs AS rhs ON lhs[2] = rhs - """.trimIndent(), + """, expected = BagType(INT4) ), ErrorTestCase( @@ -1045,8 +1060,8 @@ internal class PlanTyperTestsPorted { FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs RIGHT OUTER JOIN lhs AS rhs ON lhs[2] = rhs - """.trimIndent(), - problemHandler = assertProblemExists( + """, + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("lhs"), setOf()) ) ), @@ -1057,8 +1072,8 @@ internal class PlanTyperTestsPorted { FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs FULL OUTER JOIN lhs AS rhs ON lhs[2] = rhs - """.trimIndent(), - problemHandler = assertProblemExists( + """, + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("lhs"), setOf()) ) ), @@ -2127,7 +2142,7 @@ internal class PlanTyperTestsPorted { ) ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.unresolvedExcludedExprRoot("nonsense") ) ), @@ -2214,7 +2229,7 @@ internal class PlanTyperTestsPorted { catalogPath = listOf("ddb"), query = "SELECT * FROM pets ORDER BY unknown_col", expected = TABLE_AWS_DDB_PETS_LIST, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("unknown_col"), setOf("pets")) ) ), @@ -3139,7 +3154,7 @@ internal class PlanTyperTestsPorted { SELECT VALUE 1 FROM "pql"."main"['employer'] AS e; """, expected = BagType(StaticType.INT4), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable( id(sensitive("pql"), sensitive("main")) ) @@ -3151,7 +3166,7 @@ internal class PlanTyperTestsPorted { SELECT VALUE 1 FROM "pql"['main']."employer" AS e; """, expected = BagType(StaticType.INT4), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(sensitive("pql")) ) ), @@ -3169,7 +3184,7 @@ internal class PlanTyperTestsPorted { { 'aBc': 1, 'AbC': 2.0 }['Ab' || 'C']; """, expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.expressionAlwaysReturnsMissing("Collections must be indexed with integers, found STRING") ) ), @@ -3362,7 +3377,7 @@ internal class PlanTyperTestsPorted { FROM T3 AS T3_mapping ) ; - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -3390,7 +3405,7 @@ internal class PlanTyperTestsPorted { { 'a': CAST(1 AS INT8) }, { 'a': CAST(1 AS INT4) } >> AS t - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -3414,7 +3429,7 @@ internal class PlanTyperTestsPorted { { 'a': CAST(1 AS INT4) }, { } >> AS t - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -3438,7 +3453,7 @@ internal class PlanTyperTestsPorted { { 'a': CAST(1 AS INT4) }, { 'a': 'hello world!' } >> AS t - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -3462,7 +3477,7 @@ internal class PlanTyperTestsPorted { { 'a': CAST(1 AS INT4), 'b': TRUE }, { 'a': 'hello world!!', 'b': DATE '2023-01-01' } >> AS t - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -3481,15 +3496,15 @@ internal class PlanTyperTestsPorted { name = """ unary plus on non-compatible type -- this cannot resolve to a dynamic call since no function will ever be invoked. - """.trimIndent(), + """, query = """ SELECT VALUE +t.a FROM << { 'a': 'hello world!' } >> AS t - """.trimIndent(), + """, expected = BagType(ANY), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.STRING), "POS", @@ -3499,29 +3514,29 @@ internal class PlanTyperTestsPorted { SuccessTestCase( name = """ unary plus on dynamic types - """.trimIndent(), + """, query = """ SELECT VALUE +t.a FROM << { 'a': 'hello world!' }, { 'a': <<>> } >> AS t - """.trimIndent(), + """, expected = BagType(ANY), ), ErrorTestCase( name = """ unary plus on missing type -- this cannot resolve to a dynamic call since no function will ever be invoked. - """.trimIndent(), + """, query = """ SELECT VALUE +t.a FROM << { 'NOT_A': 1 } >> AS t - """.trimIndent(), + """, expected = BagType(ANY), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable( Identifier.Symbol("a", Identifier.CaseSensitivity.INSENSITIVE), setOf("t"), @@ -3532,12 +3547,12 @@ internal class PlanTyperTestsPorted { name = """ unary plus on missing type -- this cannot resolve to a dynamic call since no function will ever be invoked. - """.trimIndent(), + """, query = """ +MISSING - """.trimIndent(), + """, expected = StaticType.DECIMAL, // This is due to it being the highest precedence type - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.expressionAlwaysReturnsMissing("Static function always receives MISSING arguments.") ) ), @@ -3677,7 +3692,7 @@ internal class PlanTyperTestsPorted { { 'a': 3, 'b': 4 }, { 'a': 5, 'b': NULL } >> GROUP BY a - """.trimIndent(), + """, expected = BagType( StructType( fields = mapOf( @@ -4010,7 +4025,7 @@ internal class PlanTyperTestsPorted { ), ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("pets")) ) ), @@ -4040,7 +4055,7 @@ internal class PlanTyperTestsPorted { ), ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("pets")) ) ), @@ -4104,7 +4119,7 @@ internal class PlanTyperTestsPorted { ), ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(id(insensitive("ddb"), insensitive("pets"))) ) ), @@ -4260,7 +4275,7 @@ internal class PlanTyperTestsPorted { catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id IN 'hello'", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.INT4, StaticType.STRING), "" + @@ -4281,7 +4296,7 @@ internal class PlanTyperTestsPorted { catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id BETWEEN 1 AND 'a'", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf( StaticType.INT4, @@ -4305,7 +4320,7 @@ internal class PlanTyperTestsPorted { catalogPath = DB_SCHEMA_MARKETS, query = "order_info.ship_option LIKE 3", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.STRING, StaticType.INT4), "LIKE", @@ -4365,7 +4380,7 @@ internal class PlanTyperTestsPorted { // Function resolves to EQ__ANY_ANY__BOOL // Which can return BOOL Or NULL expected = StaticType.BOOL, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("non_existing_column"), emptySet()) ) ), @@ -4375,7 +4390,7 @@ internal class PlanTyperTestsPorted { catalogPath = DB_SCHEMA_MARKETS, query = "order_info.customer_id = 1 AND 1", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.BOOL, StaticType.INT4), "AND", @@ -4388,7 +4403,7 @@ internal class PlanTyperTestsPorted { catalogPath = DB_SCHEMA_MARKETS, query = "1 AND order_info.customer_id = 1", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.INT4, StaticType.BOOL), "AND", @@ -4413,7 +4428,7 @@ internal class PlanTyperTestsPorted { ) ) ), - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.undefinedVariable(insensitive("unknown_col"), setOf("orders")) ) ), @@ -4430,7 +4445,7 @@ internal class PlanTyperTestsPorted { catalogPath = listOf("ddb"), query = "SELECT * FROM pets LIMIT '5'", expected = TABLE_AWS_DDB_PETS, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.unexpectedType( StaticType.STRING, setOf(StaticType.INT) ) @@ -4449,7 +4464,7 @@ internal class PlanTyperTestsPorted { catalogPath = listOf("ddb"), query = "SELECT * FROM pets LIMIT 1 OFFSET '5'", expected = TABLE_AWS_DDB_PETS, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.unexpectedType(StaticType.STRING, setOf(StaticType.INT)) ) ), @@ -4597,7 +4612,7 @@ internal class PlanTyperTestsPorted { name = "TRIM_2_error", query = "trim(2 FROM ' Hello, World! ')", expected = ANY, - problemHandler = assertProblemExists( + problemHandler = AssertProblemExists( ProblemGenerator.incompatibleTypesForOp( listOf(StaticType.STRING, StaticType.INT4), "TRIM_CHARS", diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PrintPlanTyperTestsPorted.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PrintPlanTyperTestsPorted.kt new file mode 100644 index 000000000..46d1b1278 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PrintPlanTyperTestsPorted.kt @@ -0,0 +1,179 @@ +package org.partiql.planner.internal.typer + +import com.amazon.ion.IonType +import com.amazon.ion.IonWriter +import com.amazon.ion.system.IonTextWriterBuilder +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.partiql.planner.TyperTestBuilder +import org.partiql.types.PType +import java.io.OutputStream +import kotlin.streams.toList + +/** + * This serializes the [PlanTyperTestsPorted]. + * TODO: Remove this once we have a better way to serialize errors. + * + * @see TyperTests + */ +class PrintPlanTyperTestsPorted { + + /** + * This "test" serializes the [PlanTyperTestsPorted] as Ion. It is copied and pasted into the resource folder. + */ + @Test + @Disabled + fun print() { + val other = PlanTyperTestsPorted.TestProvider().provideArguments(null).map { it.get()[0] as PlanTyperTestsPorted.TestCase }.toList() + val testCases = + other + PlanTyperTestsPorted.collections() + PlanTyperTestsPorted.decimalCastCases() + PlanTyperTestsPorted.selectStar() + PlanTyperTestsPorted.sessionVariables() + PlanTyperTestsPorted.bitwiseAnd() + PlanTyperTestsPorted.unpivotCases() + PlanTyperTestsPorted.joinCases() + PlanTyperTestsPorted.excludeCases() + PlanTyperTestsPorted.orderByCases() + PlanTyperTestsPorted.tupleUnionCases() + PlanTyperTestsPorted.aggregationCases() + PlanTyperTestsPorted.scalarFunctions() + PlanTyperTestsPorted.distinctClauseCases() + PlanTyperTestsPorted.pathExpressions() + PlanTyperTestsPorted.caseWhens() + PlanTyperTestsPorted.nullIf() + PlanTyperTestsPorted.coalesce() + PlanTyperTestsPorted.subqueryCases() + PlanTyperTestsPorted.dynamicCalls() + PlanTyperTestsPorted.scanCases() + PlanTyperTestsPorted.pivotCases() + PlanTyperTestsPorted.isTypeCases() + PlanTyperTestsPorted.castCases() + testCases.forEachIndexed { index, it -> + it.print(index) + } + } + + private fun PlanTyperTestsPorted.TestCase.print(index: Int) { + val writer = IonTextWriterBuilder.pretty().withLongStringThreshold(50).build(System.out as OutputStream) + when (this) { + is PlanTyperTestsPorted.TestCase.SuccessTestCase -> writer.print(this, index) { + it.setFieldName(TyperTestBuilder.FIELD_STATUS) + it.writeSymbol(TyperTestBuilder.STATUS_SUCCESS) + it.setFieldName(TyperTestBuilder.FIELD_EXPECTED) + it.writeType(this.expected) + } + + is PlanTyperTestsPorted.TestCase.ErrorTestCase -> writer.print(this, index) { + it.setFieldName(TyperTestBuilder.FIELD_STATUS) + it.writeSymbol(TyperTestBuilder.STATUS_FAILURE) + if (this.problemHandler != null) { + it.writeProblemHandler(this.problemHandler) + } + } + + is PlanTyperTestsPorted.TestCase.ThrowingExceptionTestCase -> error("Not implemented") + } + } + + private fun IonWriter.print(tc: PlanTyperTestsPorted.TestCase, index: Int, expectedFields: (IonWriter) -> Unit) { + this.addTypeAnnotation("test") + this.stepIn(IonType.STRUCT) + + // Write name + this.setFieldName("name") + this.writeString(tc.name ?: "no-name-found-$index") + + // Write type + this.setFieldName("type") + this.writeString("type") + + // Write payload + this.setFieldName("body") + this.stepIn(IonType.STRUCT) + + // Write statement + this.setFieldName(TyperTestBuilder.FIELD_STATEMENT) + writeStatement(this, tc) + + // Session + this.setFieldName(TyperTestBuilder.FIELD_SESSION) + writeSession(this, tc) + + // Expectation + expectedFields.invoke(this) + this.stepOut() + this.stepOut() + this.flush() + } + + private fun writeStatement(writer: IonWriter, tc: PlanTyperTestsPorted.TestCase) { + if (tc.query != null) { + writer.writeString(tc.query) + } + val key = tc.key + if (key != null) { + writer.stepIn(IonType.SEXP) + writer.writeSymbol("@") + writer.writeString("\$inputs") + writer.writeString(key.group) + writer.writeString(key.name) + writer.stepOut() + } + } + + private fun writeSession(writer: IonWriter, tc: PlanTyperTestsPorted.TestCase) { + writer.stepIn(IonType.STRUCT) + writer.setFieldName(TyperTestBuilder.FIELD_CATALOG) + writer.writeString(tc.catalog) + writer.setFieldName(TyperTestBuilder.FIELD_CWD) + writer.stepIn(IonType.LIST) + tc.catalogPath.forEach { writer.writeString(it) } + writer.stepOut() + writer.stepOut() + } + + private fun IonWriter.writeType(type: PType) { + when (val kind = type.kind) { + // No params + PType.Kind.DYNAMIC, PType.Kind.BOOL, PType.Kind.TINYINT, PType.Kind.SMALLINT, PType.Kind.INTEGER, PType.Kind.BIGINT, PType.Kind.REAL, PType.Kind.DOUBLE, PType.Kind.DATE, PType.Kind.STRUCT, PType.Kind.UNKNOWN, PType.Kind.STRING, PType.Kind.SYMBOL, PType.Kind.NUMERIC -> this.writeSymbol( + kind.name + ) + + // Override name + PType.Kind.DECIMAL_ARBITRARY -> this.writeSymbol("DECIMAL") + + // Precision and scale + PType.Kind.DECIMAL -> { + this.stepIn(IonType.SEXP) + this.writeSymbol(kind.name) + this.writeInt(type.precision.toLong()) + this.writeInt(type.scale.toLong()) + this.stepOut() + } + + // Uses Length + PType.Kind.VARCHAR, PType.Kind.BLOB, PType.Kind.CLOB, PType.Kind.CHAR -> { + this.stepIn(IonType.SEXP) + this.writeSymbol(kind.name) + this.writeInt(type.length.toLong()) + this.stepOut() + } + + // Uses type param + PType.Kind.BAG, PType.Kind.SEXP, PType.Kind.ARRAY -> { + this.stepIn(IonType.SEXP) + this.writeSymbol(kind.name) + this.writeType(type.typeParameter) + this.stepOut() + } + + // Precision + PType.Kind.TIME, PType.Kind.TIMEZ, PType.Kind.TIMESTAMP, PType.Kind.TIMESTAMPZ -> { + this.stepIn(IonType.SEXP) + this.writeSymbol(kind.name) + this.writeInt(type.precision.toLong()) + this.stepOut() + } + + // Row + PType.Kind.ROW -> { + this.stepIn(IonType.SEXP) + this.writeSymbol(kind.name) + type.fields.forEach { field -> + this.writeString(field.name) + this.writeType(field.type) + } + this.stepOut() + } + } + } + + // TODO: We need to make error reporting more flexible. Potentially create error codes to assert on with + // attributes attached. + private fun IonWriter.writeProblemHandler(handler: PlanTyperTestsPorted.Companion.AssertProblemExists) { + this.setFieldName(TyperTestBuilder.FIELD_ASSERT_PROBLEM_EXISTS) + this.stepIn(IonType.SEXP) + this.writeSymbol(handler.problem.details.severity.name) + this.writeString(handler.problem.details.message) + this.stepOut() + } +} diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TyperTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TyperTests.kt new file mode 100644 index 000000000..ed624cc10 --- /dev/null +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TyperTests.kt @@ -0,0 +1,38 @@ +package org.partiql.planner.internal.typer + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import org.partiql.planner.TyperTestBuilder +import org.partiql.planner.test.Test +import org.partiql.planner.test.TestBuilder +import org.partiql.planner.test.TestBuilderFactory +import org.partiql.planner.test.TestProviderBuilder +import java.util.stream.Stream + +class TyperTests { + + @ParameterizedTest + @ArgumentsSource(TestProvider::class) + fun test(test: Test) { + test.assert() + } + + class TestProvider : ArgumentsProvider { + private val provider = TestProviderBuilder().factory(Factory).build() + override fun provideArguments(context: ExtensionContext?): Stream { + return provider.map { Arguments.of(it) }.stream() + } + } + + private object Factory : TestBuilderFactory { + override fun get(type: String): TestBuilder? { + return when (type.trim().lowercase()) { + "type" -> TyperTestBuilder() + else -> null + } + } + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/IteratorChain.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/IteratorChain.kt new file mode 100644 index 000000000..2397bd719 --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/IteratorChain.kt @@ -0,0 +1,24 @@ +package org.partiql.planner.test + +internal class IteratorChain( + iterators: Iterable> +) : Iterator { + + private var _iter: Iterator> = iterators.iterator() + private var _currentIter: Iterator = emptyList().iterator() + + override fun hasNext(): Boolean { + if (!_currentIter.hasNext()) { + if (!_iter.hasNext()) { + return false + } + _currentIter = _iter.next() + return hasNext() + } + return true + } + + override fun next(): T { + return _currentIter.next() + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt index 086571fe3..3a968b34e 100644 --- a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/PartiQLTestProvider.kt @@ -23,6 +23,7 @@ import kotlin.io.path.toPath * The PartiQLTestProvider is a simple utility for indexing SQL statements within files for re-use across library tests. */ class PartiQLTestProvider { + // TODO: Rename this to StatementProvider to better reflect its purpose /** * Backing map for test input lookup. diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/Test.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/Test.kt new file mode 100644 index 000000000..d73db409b --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/Test.kt @@ -0,0 +1,14 @@ +package org.partiql.planner.test + +interface Test { + + /** + * @return the name of the test. + */ + fun getName(): String + + /** + * Should throw an exception if a failure occurs. + */ + fun assert() +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilder.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilder.kt new file mode 100644 index 000000000..19599fd14 --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilder.kt @@ -0,0 +1,36 @@ +package org.partiql.planner.test + +import com.amazon.ionelement.api.StructElement + +/** + * A [TestBuilder] is used to construct a [Test] object. + * @see Test + * @see TestId + * @see StructElement + * @see PartiQLTestProvider + */ +interface TestBuilder { + + /** + * Sets the unique identifier for a [Test]. + * + * @see TestId + */ + fun id(id: TestId): TestBuilder + + /** + * Configures the [Test] with a [StructElement] and a [PartiQLTestProvider]. + * + * @see StructElement + * @see PartiQLTestProvider + * @see Test + */ + fun config(body: StructElement, statementProvider: PartiQLTestProvider): TestBuilder + + /** + * Builds and returns a [Test] object. + * + * @see Test + */ + fun build(): Test +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilderFactory.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilderFactory.kt new file mode 100644 index 000000000..912c1cecc --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestBuilderFactory.kt @@ -0,0 +1,15 @@ +package org.partiql.planner.test + +/** + * A factory for creating [TestBuilder] instances based on the type of test. + */ +interface TestBuilderFactory { + + /** + * Creates a [TestBuilder] instance based on the given type. + * + * @param type The type of test to create a [TestBuilder] for. + * @return The [TestBuilder] instance or `null` if the type is not supported. + */ + infix operator fun get(type: String): TestBuilder? +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestFileIterator.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestFileIterator.kt new file mode 100644 index 000000000..fa89732bd --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestFileIterator.kt @@ -0,0 +1,35 @@ +package org.partiql.planner.test + +import com.amazon.ion.system.IonReaderBuilder +import com.amazon.ionelement.api.createIonElementLoader +import java.io.InputStream + +/** + * Iterates over the tests defined in a file. + */ +internal class TestFileIterator( + private val path: List, + stream: InputStream, + private val factory: TestBuilderFactory, + private val inputProvider: PartiQLTestProvider +) : Iterator { + + private val reader = IonReaderBuilder.standard().build(stream) + private val loader = createIonElementLoader() + private var _type = reader.next() + + override fun hasNext(): Boolean { + return _type != null + } + + override fun next(): Test { + val element = loader.loadCurrentElement(reader) + _type = reader.next() + val struct = element.asStruct() + val name = struct["name"].asString().textValue + val type = struct["type"].asString().textValue + val payload = struct["body"].asStruct() + val testId = TestId.of(path + listOf(name)) + return factory[type]?.id(testId)?.config(payload, inputProvider)?.build() ?: error("Could not find test builder for: $type") + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestId.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestId.kt new file mode 100644 index 000000000..2419cd40b --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestId.kt @@ -0,0 +1,39 @@ +package org.partiql.planner.test + +/** + * Represents a unique identifier for a test case. + * + * This is used to uniquely identify a test case within a test suite. + */ +class TestId private constructor( + private val _qualifier: Array, + private val _name: String +) { + + private fun getQualifier(): Array { + return _qualifier + } + + private fun getName(): String { + return _name + } + + override fun toString(): String { + return (getQualifier() + arrayOf(getName())).joinToString(".") + } + + companion object { + @JvmStatic + fun of(vararg parts: String): TestId = of(parts.toList()) + + @JvmStatic + fun of(parts: Collection): TestId { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.take(parts.size - 1).toTypedArray() + val identifier = parts.last() + return TestId(qualifier, identifier) + } + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProvider.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProvider.kt new file mode 100644 index 000000000..1c6030cab --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProvider.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at: + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package org.partiql.planner.test + +import java.io.File +import java.nio.file.Path + +/** + * Provides a set of tests to be run by the test runner. + * + * The tests are loaded from a directory or a resource path. + * + * @param factory The test builder factory to use. + * @param root The root directory to load tests from. If null, the tests will be loaded from resources. + * @see TestBuilderFactory + * @see Test + * @see PartiQLTestProvider + */ +class TestProvider( + private val factory: TestBuilderFactory, + private val root: Path? +) : Iterable { + + private val inputProvider: PartiQLTestProvider = PartiQLTestProvider() + + init { + inputProvider.load() + } + + /** + * Load test groups from a directory. + */ + override fun iterator(): Iterator { + val testIterators: MutableList> = mutableListOf() + if (root != null) { + val dir = root.toFile() + val tests = load(dir, emptyList()) + testIterators.add(tests) + } else { + // user default resources + val inputStream = this::class.java.getResourceAsStream("/resource_path.txt")!! + inputStream.reader().forEachLine { path -> + val pathSteps = path.split("/") + val outMostDir = pathSteps.first() + // Open tests directory + if (outMostDir == "tests") { + val resource = this::class.java.getResourceAsStream("/$path")!! + val dirs = pathSteps.drop(1).dropLast(1) + val tests = TestFileIterator(dirs, resource, factory, inputProvider) + testIterators.add(tests) + } + } + } + return IteratorChain(testIterators) + } + + // load all tests in a directory + private fun load(file: File, parent: List): Iterator { + if (file.isDirectory) { + val iterators = file.listFiles()!!.map { + load(it, parent + listOf(it.nameWithoutExtension)) + } + return IteratorChain(iterators) + } else { + return TestFileIterator(parent, file.inputStream(), factory, inputProvider) + } + } +} diff --git a/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProviderBuilder.kt b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProviderBuilder.kt new file mode 100644 index 000000000..b8ddfb1ab --- /dev/null +++ b/partiql-planner/src/testFixtures/kotlin/org/partiql/planner/test/TestProviderBuilder.kt @@ -0,0 +1,21 @@ +package org.partiql.planner.test + +import java.nio.file.Path + +class TestProviderBuilder { + + private var root: Path? = null + private var factory: TestBuilderFactory? = null + + fun root(path: Path): TestProviderBuilder = this.apply { + this.root = path + } + + fun factory(factory: TestBuilderFactory): TestProviderBuilder = this.apply { + this.factory = factory + } + + fun build(): TestProvider { + return TestProvider(factory!!, root) + } +} diff --git a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion b/partiql-planner/src/testFixtures/resources/tests/aggregations.ion deleted file mode 100644 index 515b6f2e6..000000000 --- a/partiql-planner/src/testFixtures/resources/tests/aggregations.ion +++ /dev/null @@ -1,256 +0,0 @@ -suite::{ - name: "aggregations", - session: { - catalog: "default", - path: [ - "pql" - ], - vars: {}, - }, - tests: { - 'avg(int32)': { - statement: ''' - SELECT AVG(n) as "avg" FROM numbers.nullable_int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "avg", - type: "int32", - }, - ], - }, - }, - }, - 'count(int32)': { - statement: ''' - SELECT COUNT(n) as "count" FROM numbers.nullable_int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "count", - type: "int32", - }, - ], - }, - }, - }, - 'min(int32)': { - statement: ''' - SELECT MIN(n) as "min" FROM numbers.nullable_int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "min", - type: "int32", - }, - ], - }, - }, - }, - 'max(int32)': { - statement: ''' - SELECT MAX(n) as "max" FROM numbers.nullable_int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "max", - type: "int32", - }, - ], - }, - }, - }, - 'sum(int32)': { - statement: ''' - SELECT SUM(n) as "sum" FROM numbers.nullable_int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "sum", - type: "int32", - }, - ], - }, - }, - }, - 'avg(int32)': { - statement: ''' - SELECT AVG(n) as "avg" FROM numbers.int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "avg", - type: "int32", - }, - ], - }, - }, - }, - 'count(int32)': { - statement: ''' - SELECT COUNT(n) as "count" FROM numbers.int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "count", - type: "int32", - }, - ], - }, - }, - }, - 'min(int32)': { - statement: ''' - SELECT MIN(n) as "min" FROM numbers.int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "min", - type: "int32", - }, - ], - }, - }, - }, - 'max(int32)': { - statement: ''' - SELECT MAX(n) as "max" FROM numbers.int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "max", - type: "int32", - }, - ], - }, - }, - }, - 'sum(int32)': { - statement: ''' - SELECT SUM(n) as "sum" FROM numbers.int32s AS n - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "sum", - type: "int32", - }, - ], - }, - }, - }, - 'group_by_key': { - statement: ''' - SELECT COUNT(*) as "count", isOdd FROM numbers.int32s AS n - GROUP BY n % 2 = 0 AS isOdd - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "count", - type: "int32" - }, - { - name: "isOdd", - type: "bool" - }, - ], - }, - }, - }, - 'group_by_keys_noalias': { - statement: ''' - SELECT AVG(x), y, z FROM points - GROUP BY y, z - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "_1", - type: "float32", - }, - { - name: "y", - type: "float32" - }, - { - name: "z", - type: "float32" - }, - ], - }, - }, - }, - 'group_by_keys_alias': { - statement: ''' - SELECT AVG(x), a, b FROM points - GROUP BY y as a, z as b - ''', - schema: { - type: "bag", - items: { - type: "struct", - fields: [ - { - name: "_1", - type: "float32", - }, - { - name: "a", - type: "float32" - }, - { - name: "b", - type: "float32" - }, - ], - }, - }, - }, - }, -} diff --git a/partiql-planner/src/testFixtures/resources/tests/plan-typer/plan-typer-tests-ported.ion b/partiql-planner/src/testFixtures/resources/tests/plan-typer/plan-typer-tests-ported.ion new file mode 100644 index 000000000..5941b3b77 --- /dev/null +++ b/partiql-planner/src/testFixtures/resources/tests/plan-typer/plan-typer-tests-ported.ion @@ -0,0 +1,6717 @@ + +test::{ + name:"Pets should not be accessible #1", + type:"type", + body:{ + statement:"SELECT * FROM pets", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable pets does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"Pets should not be accessible #2", + type:"type", + body:{ + statement:"SELECT * FROM pets", + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable pets does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"Project all explicitly", + type:"type", + body:{ + statement:"SELECT * FROM pets", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"Project all implicitly", + type:"type", + body:{ + statement:"SELECT id, breed FROM pets", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"Test #4", + type:"type", + body:{ + statement:"b", + session:{ + catalog:"b", + cwd:[ + "b" + ] + }, + status:SUCCESS, + expected:( + ROW + "b" + ( + ROW + "b" + INTEGER + ) + "c" + INTEGER + ) + } +} +test::{ + name:"Test #5", + type:"type", + body:{ + statement:"SELECT * FROM b", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + STRING + ) + ) + } +} +test::{ + name:"Test #6", + type:"type", + body:{ + statement:"SELECT * FROM b", + session:{ + catalog:"aws", + cwd:[ + "b" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + INTEGER + ) + ) + } +} +test::{ + name:"Test #7", + type:"type", + body:{ + statement:"SELECT * FROM ddb.pets", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable ddb.pets does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"Test #10", + type:"type", + body:{ + statement:"b.b", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + "b" + ( + ROW + "b" + INTEGER + ) + "c" + INTEGER + ) + } +} +test::{ + name:"Test #11", + type:"type", + body:{ + statement:"b.b", + session:{ + catalog:"b", + cwd:[ + "b" + ] + }, + status:SUCCESS, + expected:( + ROW + "b" + INTEGER + ) + } +} +test::{ + name:"Test #12", + type:"type", + body:{ + statement:"SELECT * FROM b", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + STRING + ) + ) + } +} +test::{ + name:"Test #13", + type:"type", + body:{ + statement:"SELECT * FROM ddb.b", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + STRING + ) + ) + } +} +test::{ + name:"Test #14", + type:"type", + body:{ + statement:"SELECT * FROM aws.ddb.pets", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"Test #15", + type:"type", + body:{ + statement:"SELECT * FROM aws.b.b", + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + INTEGER + ) + ) + } +} +test::{ + name:"Test #16", + type:"type", + body:{ + statement:"b.b.b", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + "b" + INTEGER + ) + } +} +test::{ + name:"Test #17", + type:"type", + body:{ + statement:"b.b.c", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #18", + type:"type", + body:{ + statement:"b.b.b", + session:{ + catalog:"b", + cwd:[ + "b" + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #19", + type:"type", + body:{ + statement:"b.b.b.c", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #20", + type:"type", + body:{ + statement:"b.b.b.b", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + "b" + INTEGER + ) + } +} +test::{ + name:"Test #21", + type:"type", + body:{ + statement:"b.b.b.b", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #22", + type:"type", + body:{ + statement:"b.b.c", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #23", + type:"type", + body:{ + statement:"b.b.b", + session:{ + catalog:"b", + cwd:[ + "b" + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #24", + type:"type", + body:{ + statement:"b.b.b.b.b", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Test #25", + type:"type", + body:{ + statement:"b.b.b.b", + session:{ + catalog:"b", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"EQ", + type:"type", + body:{ + statement:"order_info.customer_id = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"NEQ", + type:"type", + body:{ + statement:"order_info.customer_id <> 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"GEQ", + type:"type", + body:{ + statement:"order_info.customer_id >= 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"GT", + type:"type", + body:{ + statement:"order_info.customer_id > 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"LEQ", + type:"type", + body:{ + statement:"order_info.customer_id <= 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"LT", + type:"type", + body:{ + statement:"order_info.customer_id < 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"IN", + type:"type", + body:{ + statement:"order_info.customer_id IN (1, 2, 3)", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"IN Failure", + type:"type", + body:{ + statement:"order_info.customer_id IN 'hello'", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''INTEGER, STRING is/are incompatible data types for the \'IN_COLLECTION\' operator.''' + ) + } +} +test::{ + name:"BETWEEN", + type:"type", + body:{ + statement:"order_info.customer_id BETWEEN 1 AND 2", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"BETWEEN Failure", + type:"type", + body:{ + statement:"order_info.customer_id BETWEEN 1 AND 'a'", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''INTEGER, INTEGER, STRING is/are incompatible data types for the \'BETWEEN\' operator.''' + ) + } +} +test::{ + name:"LIKE", + type:"type", + body:{ + statement:"order_info.ship_option LIKE '%ABC%'", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"LIKE Failure", + type:"type", + body:{ + statement:"order_info.ship_option LIKE 3", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''STRING, INTEGER is/are incompatible data types for the \'LIKE\' operator.''' + ) + } +} +test::{ + name:"Case Insensitive success", + type:"type", + body:{ + statement:"order_info.CUSTOMER_ID = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"Case Sensitive failure", + type:"type", + body:{ + statement:"order_info.\"CUSTOMER_ID\" = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE + } +} +test::{ + name:"Case Sensitive success", + type:"type", + body:{ + statement:"order_info.\"customer_id\" = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"1-Level Junction", + type:"type", + body:{ + statement:'''(order_info.customer_id = 1) AND (order_info.marketplace_id = 2)''', + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"2-Level Junction", + type:"type", + body:{ + statement:'''(order_info.customer_id = 1) AND (order_info.marketplace_id = 2) OR (order_info.customer_id = 3) AND (order_info.marketplace_id = 4)''', + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"INT and STR Comparison", + type:"type", + body:{ + statement:"order_info.customer_id = 'something'", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"Nonexisting Comparison", + type:"type", + body:{ + statement:"non_existing_column = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable non_existing_column does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"Bad comparison", + type:"type", + body:{ + statement:"order_info.customer_id = 1 AND 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''BOOL, INTEGER is/are incompatible data types for the \'AND\' operator.''' + ) + } +} +test::{ + name:"Bad comparison", + type:"type", + body:{ + statement:"1 AND order_info.customer_id = 1", + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''INTEGER, BOOL is/are incompatible data types for the \'AND\' operator.''' + ) + } +} +test::{ + name:"Unknown column", + type:"type", + body:{ + statement:'''SELECT unknown_col FROM orders WHERE customer_id = 1''', + session:{ + catalog:"db", + cwd:[ + "markets" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable unknown_col does not exist in the database environment and is not an attribute of the following in-scope variables [orders].''' + ) + } +} +test::{ + name:"LIMIT INT", + type:"type", + body:{ + statement:"SELECT * FROM pets LIMIT 5", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"LIMIT STR", + type:"type", + body:{ + statement:"SELECT * FROM pets LIMIT '5'", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + "Unexpected type STRING, expected one of NUMERIC" + ) + } +} +test::{ + name:"OFFSET INT", + type:"type", + body:{ + statement:"SELECT * FROM pets LIMIT 1 OFFSET 5", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"OFFSET STR", + type:"type", + body:{ + statement:"SELECT * FROM pets LIMIT 1 OFFSET '5'", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + "Unexpected type STRING, expected one of NUMERIC" + ) + } +} +test::{ + name:"CAST", + type:"type", + body:{ + statement:"SELECT CAST(breed AS INT) AS cast_breed FROM pets", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "cast_breed" + INTEGER + ) + ) + } +} +test::{ + name:"UPPER", + type:"type", + body:{ + statement:"SELECT UPPER(breed) AS upper_breed FROM pets", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "upper_breed" + STRING + ) + ) + } +} +test::{ + name:"Non-tuples", + type:"type", + body:{ + statement:"SELECT a FROM << [ 1, 1.0 ] >> AS a", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + DYNAMIC + ) + ) + ) + } +} +test::{ + name:"Non-tuples in SELECT VALUE", + type:"type", + body:{ + statement:"SELECT VALUE a FROM << [ 1, 1.0 ] >> AS a", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ARRAY + DYNAMIC + ) + ) + } +} +test::{ + name:"SELECT VALUE", + type:"type", + body:{ + statement:"SELECT VALUE [1, 1.0] FROM <<>>", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ARRAY + DYNAMIC + ) + ) + } +} +test::{ + name:"Duplicate fields in struct", + type:"type", + body:{ + statement:''' + SELECT t.a AS a + FROM << + { \'a\': 1, \'a\': \'hello\' } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DYNAMIC + ) + ) + } +} +test::{ + name:"Duplicate fields in struct", + type:"type", + body:{ + statement:''' + SELECT a AS a + FROM << + { \'a\': 1, \'a\': \'hello\' } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DYNAMIC + ) + ) + } +} +test::{ + name:"Current User", + type:"type", + body:{ + statement:"CURRENT_USER", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Trim", + type:"type", + body:{ + statement:"trim(' ')", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Current User Concat", + type:"type", + body:{ + statement:"CURRENT_USER || 'hello'", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Current User Concat in WHERE", + type:"type", + body:{ + statement:'''SELECT VALUE a FROM [ 0 ] AS a WHERE CURRENT_USER = \'hello\'''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"TRIM_2", + type:"type", + body:{ + statement:"trim(' ' FROM ' Hello, World! ')", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"TRIM_1", + type:"type", + body:{ + statement:"trim(' Hello, World! ')", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"TRIM_3", + type:"type", + body:{ + statement:"trim(LEADING ' ' FROM ' Hello, World! ')", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"TRIM_2_error", + type:"type", + body:{ + statement:"trim(2 FROM ' Hello, World! ')", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''STRING, INTEGER is/are incompatible data types for the \'TRIM_CHARS\' operator.''' + ) + } +} +test::{ + name:"Collection BAG", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-01" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"Collection LIST", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + INTEGER + ) + } +} +test::{ + name:"Collection LIST", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + INTEGER + ) + } +} +test::{ + name:"Collection SEXP", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + SEXP + INTEGER + ) + } +} +test::{ + name:"SELECT from array", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"SELECT from array", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "collections-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "x" + INTEGER + ) + ) + } +} +test::{ + name:"cast decimal", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"cast decimal(1)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(1))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 0 + ) + } +} +test::{ + name:"cast decimal(1,0)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(1,0))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 0 + ) + } +} +test::{ + name:"cast decimal(1,1)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(1,1))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 1 + ) + } +} +test::{ + name:"cast decimal(38)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(38))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 0 + ) + } +} +test::{ + name:"cast decimal(38,0)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(38,0))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 0 + ) + } +} +test::{ + name:"cast decimal(38,38)", + type:"type", + body:{ + statement:"CAST(1 AS DECIMAL(38,38))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 38 + ) + } +} +test::{ + name:"cast decimal string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"cast decimal(1) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(1))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 0 + ) + } +} +test::{ + name:"cast decimal(1,0) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(1,0))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 0 + ) + } +} +test::{ + name:"cast decimal(1,1) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(1,1))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 1 + 1 + ) + } +} +test::{ + name:"cast decimal(38) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(38))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 0 + ) + } +} +test::{ + name:"cast decimal(38,0) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(38,0))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 0 + ) + } +} +test::{ + name:"cast decimal(38,38) string", + type:"type", + body:{ + statement:"CAST('1' AS DECIMAL(38,38))", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 38 + 38 + ) + } +} +test::{ + name:"Test #8", + type:"type", + body:{ + statement:"SELECT * FROM ddb.pets", + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"Test #9", + type:"type", + body:{ + statement:"SELECT * FROM b.b", + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "identifier" + INTEGER + ) + ) + } +} +test::{ + name:"Select star with join", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "sanity-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "name" + ( + ROW + "first" + STRING + "last" + STRING + ) + "ssn" + STRING + "employer" + STRING + "name" + STRING + "tax_id" + BIGINT + "address" + ( + ROW + "street" + STRING + "zip" + INTEGER + ) + ) + ) + } +} +test::{ + name:"Select star", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "sanity-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "first" + STRING + "last" + STRING + "full_name" + STRING + ) + ) + } +} +test::{ + name:"Current User", + type:"type", + body:{ + statement:"CURRENT_USER", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Current User Concat", + type:"type", + body:{ + statement:"CURRENT_USER || 'hello'", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Current User in WHERE", + type:"type", + body:{ + statement:'''SELECT VALUE a FROM [ 0 ] AS a WHERE CURRENT_USER = \'hello\'''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"Current User in WHERE", + type:"type", + body:{ + statement:'''SELECT VALUE a FROM [ 0 ] AS a WHERE CURRENT_USER = 5''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"Testing CURRENT_USER and CURRENT_DATE Binders", + type:"type", + body:{ + statement:''' + SELECT + CURRENT_USER, + CURRENT_DATE, + CURRENT_USER AS "curr_user", + CURRENT_DATE AS "curr_date", + CURRENT_USER || \' is my name.\' AS name_desc + FROM << 0, 1 >>; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "CURRENT_USER" + STRING + "CURRENT_DATE" + DATE + "curr_user" + STRING + "curr_date" + DATE + "name_desc" + STRING + ) + ) + } +} +test::{ + name:"Current User (String) PLUS String", + type:"type", + body:{ + statement:"CURRENT_USER + 'hello'", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''STRING, STRING is/are incompatible data types for the \'PLUS\' operator.''' + ) + } +} +test::{ + name:"BITWISE_AND_1", + type:"type", + body:{ + statement:"1 & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_2", + type:"type", + body:{ + statement:"CAST(1 AS INT2) & CAST(2 AS INT2)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:SMALLINT + } +} +test::{ + name:"BITWISE_AND_3", + type:"type", + body:{ + statement:"1 & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_4", + type:"type", + body:{ + statement:"CAST(1 AS INT8) & CAST(2 AS INT8)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"BITWISE_AND_5", + type:"type", + body:{ + statement:"CAST(1 AS INT2) & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_6", + type:"type", + body:{ + statement:"CAST(1 AS INT2) & CAST(2 AS INT8)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"BITWISE_AND_7", + type:"type", + body:{ + statement:"CAST(1 AS INT2) & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_8", + type:"type", + body:{ + statement:"1 & CAST(2 AS INT8)", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"BITWISE_AND_9", + type:"type", + body:{ + statement:"1 & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_10", + type:"type", + body:{ + statement:"CAST(1 AS INT8) & 2", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"BITWISE_AND_NULL_OPERAND", + type:"type", + body:{ + statement:"1 & NULL", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"BITWISE_AND_MISSING_OPERAND", + type:"type", + body:{ + statement:"1 & MISSING", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Expression always returns missing: caused by Static function always receives MISSING arguments.''' + ) + } +} +test::{ + name:"BITWISE_AND_NON_INT_OPERAND", + type:"type", + body:{ + statement:"1 & 'NOT AN INT'", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''INTEGER, STRING is/are incompatible data types for the \'BITWISE_AND\' operator.''' + ) + } +} +test::{ + name:"UNPIVOT", + type:"type", + body:{ + statement:'''SELECT VALUE v FROM UNPIVOT { \'a\': 2 } AS v AT attr WHERE attr = \'a\'''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"CROSS JOIN", + type:"type", + body:{ + statement:'''SELECT * FROM <<{ \'a\': 1 }>> AS t1, <<{ \'b\': 2.0 }>> AS t2''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "b" + DECIMAL + ) + ) + } +} +test::{ + name:"LEFT JOIN", + type:"type", + body:{ + statement:'''SELECT * FROM <<{ \'a\': 1 }>> AS t1 LEFT JOIN <<{ \'b\': 2.0 }>> AS t2 ON TRUE''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "b" + DECIMAL + ) + ) + } +} +test::{ + name:"LEFT JOIN", + type:"type", + body:{ + statement:'''SELECT b, a FROM <<{ \'a\': 1 }>> AS t1 LEFT JOIN <<{ \'b\': 2.0 }>> AS t2 ON TRUE''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "b" + DECIMAL + "a" + INTEGER + ) + ) + } +} +test::{ + name:"LEFT JOIN", + type:"type", + body:{ + statement:'''SELECT t1.a, t2.a FROM <<{ \'a\': 1 }>> AS t1 LEFT JOIN <<{ \'a\': 2.0 }>> AS t2 ON t1.a = t2.a''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "a" + DECIMAL + ) + ) + } +} +test::{ + name:"LEFT JOIN ALL", + type:"type", + body:{ + statement:'''SELECT * FROM <<{ \'a\': 1 }>> AS t1 LEFT JOIN <<{ \'a\': 2.0 }>> AS t2 ON t1.a = t2.a''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "a" + DECIMAL + ) + ) + } +} +test::{ + name:"LEFT JOIN ALL", + type:"type", + body:{ + statement:''' + SELECT * + FROM + <<{ \'a\': 1 }>> AS t1 + LEFT JOIN + <<{ \'a\': 2.0 }>> AS t2 + ON t1.a = t2.a + LEFT JOIN + <<{ \'a\': \'hello, world\' }>> AS t3 + ON t3.a = \'hello\' + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "a" + DECIMAL + "a" + STRING + ) + ) + } +} +test::{ + name:"LEFT JOIN Ambiguous Reference in ON", + type:"type", + body:{ + statement:'''SELECT * FROM <<{ \'a\': 1 }>> AS t1 LEFT JOIN <<{ \'a\': 2.0 }>> AS t2 ON a = 3''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable a does not exist in the database environment and is not an attribute of the following in-scope variables [t1, t2].''' + ) + } +} +test::{ + name:"LEFT JOIN (Lateral references)", + type:"type", + body:{ + statement:''' + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + LEFT OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"INNER JOIN (Lateral references)", + type:"type", + body:{ + statement:''' + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + INNER JOIN lhs AS rhs + ON lhs[2] = rhs + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"RIGHT JOIN (Doesn't support lateral references)", + type:"type", + body:{ + statement:''' + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + RIGHT OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable lhs does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"FULL JOIN (Doesn't support lateral references)", + type:"type", + body:{ + statement:''' + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + FULL OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable lhs does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:"EXCLUDE SELECT star", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-01" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "name" + STRING + "custId" + INTEGER + "address" + ( + ROW + "city" + STRING + "zipcode" + INTEGER + "street" + STRING + ) + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star multiple paths", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "name" + STRING + "custId" + INTEGER + "address" + ( + ROW + "city" + STRING + "zipcode" + INTEGER + ) + ) + ) + } +} +test::{ + name:'''EXCLUDE SELECT star list index and list index field''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + ( + ARRAY + ( + ROW + "field" + INTEGER + ) + ) + ) + ) + "foo" + STRING + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star collection index as last step", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + ( + ARRAY + INTEGER + ) + ) + ) + "foo" + STRING + ) + ) + } +} +test::{ + name:'''EXCLUDE SELECT star collection wildcard as last step''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + INTEGER + ) + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star list wildcard", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + ( + ARRAY + ( + ROW + "field_y" + INTEGER + ) + ) + ) + ) + "foo" + STRING + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star tuple wildcard as last step", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + ( + ARRAY + ( + ROW + ) + ) + ) + ) + "foo" + STRING + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star order by", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-08" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + ( + ROW + "foo" + STRING + ) + ) + } +} +test::{ + name:"EXCLUDE SELECT star with JOIN", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-09" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "b" + INTEGER + "c" + INTEGER + ) + ) + } +} +test::{ + name:"SELECT t.b EXCLUDE ex 1", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-10" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "b" + ( + ARRAY + ( + ROW + "b_2" + INTEGER + ) + ) + ) + ) + } +} +test::{ + name:"SELECT * EXCLUDE ex 2", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-11" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "a_1" + INTEGER + "a_2" + INTEGER + ) + "b" + ( + ARRAY + ( + ROW + "b_2" + INTEGER + ) + ) + "c" + INTEGER + "d" + INTEGER + ) + ) + } +} +test::{ + name:"SELECT VALUE t.b EXCLUDE", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-12" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ARRAY + ( + ROW + "b_2" + INTEGER + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection wildcard and nested tuple attr''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-13" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "d" + STRING + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection index and nested tuple attr''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-14" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + STRING + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection wildcard and nested tuple wildcard''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-15" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection index and nested tuple wildcard''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-16" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + STRING + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection wildcard and nested collection wildcard''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-17" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + ( + ARRAY + ( + ROW + "f" + BOOL + ) + ) + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection index and nested collection wildcard''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-18" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + ( + ARRAY + ( + ROW + "e" + STRING + "f" + BOOL + ) + ) + ) + ) + ) + ) + ) + } +} +test::{ + name:'''SELECT * EXCLUDE collection index and nested collection index''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-19" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + ( + ARRAY + ( + ROW + "e" + STRING + "f" + BOOL + ) + ) + ) + ) + ) + ) + ) + } +} +test::{ + name:"EXCLUDE case sensitive lookup", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-20" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "B" + ( + ROW + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:'''EXCLUDE case sensitive lookup with capitalized and uncapitalized attr''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-21" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "B" + ( + ROW + "C" + BOOL + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:'''EXCLUDE case sensitive lookup with both capitalized and uncapitalized removed''', + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-22" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "B" + ( + ROW + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"EXCLUDE with both duplicates", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-23" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "B" + ( + ROW + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"EXCLUDE with removed attribute later referenced", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-24" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "c" + INTEGER + ) + ) + } +} +test::{ + name:"EXCLUDE with non-existent attribute reference", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-25" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + ) + ) + } +} +test::{ + name:"exclude union of types", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-26" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "t" + ( + ROW + "a" + ( + ROW + "c" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"exclude union of types exclude same type", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-27" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "t" + ( + ROW + "a" + ( + ROW + "c" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"exclude union of types exclude different type", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-28" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "t" + ( + ROW + "a" + ( + ROW + "b" + INTEGER + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude collection wildcard", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-29" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude collection index", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-30" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ROW + "b" + ( + ROW + "c" + INTEGER + "d" + STRING + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude tuple attr", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-31" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + INTEGER + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude tuple wildcard", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-32" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + INTEGER + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude tuple attr step", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-33" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + BAG + ( + ROW + "b" + INTEGER + ) + ) + ) + ) + } +} +test::{ + name:"invalid exclude root", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-34" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Exclude expression given an unresolvable root \'nonsense\'''' + ) + } +} +test::{ + name:"exclude with unions and last step collection index", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "exclude-35" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + ( + ARRAY + ( + ROW + "b" + INTEGER + "c" + DYNAMIC + ) + ) + ) + ) + } +} +test::{ + name:"ORDER BY int", + type:"type", + body:{ + statement:"SELECT * FROM pets ORDER BY id", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + ARRAY + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"ORDER BY str", + type:"type", + body:{ + statement:"SELECT * FROM pets ORDER BY breed", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:SUCCESS, + expected:( + ARRAY + ( + ROW + "id" + INTEGER + "breed" + STRING + ) + ) + } +} +test::{ + name:"ORDER BY str", + type:"type", + body:{ + statement:"SELECT * FROM pets ORDER BY unknown_col", + session:{ + catalog:"aws", + cwd:[ + "ddb" + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable unknown_col does not exist in the database environment and is not an attribute of the following in-scope variables [pets].''' + ) + } +} +test::{ + name:"Empty Tuple Union", + type:"type", + body:{ + statement:"TUPLEUNION()", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + ) + } +} +test::{ + name:"Tuple Union with Literal Struct", + type:"type", + body:{ + statement:"TUPLEUNION({ 'a': 1, 'b': 'hello' })", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + "a" + INTEGER + "b" + STRING + ) + } +} +test::{ + name:"Tuple Union with Literal Struct AND Duplicates", + type:"type", + body:{ + statement:"TUPLEUNION({ 'a': 1, 'a': 'hello' })", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ROW + "a" + INTEGER + "a" + STRING + ) + } +} +test::{ + name:"Tuple Union with Nested Struct", + type:"type", + body:{ + statement:''' + SELECT VALUE TUPLEUNION( + t.a + ) FROM << + { \'a\': { \'b\': 1 } } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "b" + INTEGER + ) + ) + } +} +test::{ + name:"Tuple Union with Heterogeneous Data", + type:"type", + body:{ + statement:''' + SELECT VALUE TUPLEUNION( + t.a + ) FROM << + { \'a\': { \'b\': 1 } }, + { \'a\': 1 } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + DYNAMIC + ) + } +} +test::{ + name:"Tuple Union with Heterogeneous Data (2)", + type:"type", + body:{ + statement:''' + SELECT VALUE TUPLEUNION( + t.a + ) FROM << + { \'a\': { \'b\': 1 } }, + { \'a\': { \'b\': \'hello\' } }, + { \'a\': \'world\' }, + { \'a\': 4.5 }, + { } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + DYNAMIC + ) + } +} +test::{ + name:"Tuple Union with Heterogeneous Data (3)", + type:"type", + body:{ + statement:''' + SELECT VALUE TUPLEUNION( + p.name + ) FROM aws.ddb.persons AS p + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + DYNAMIC + ) + } +} +test::{ + name:"Complex Tuple Union with Heterogeneous Data", + type:"type", + body:{ + statement:''' + SELECT VALUE TUPLEUNION( + p.name, + p.name + ) FROM aws.ddb.persons AS p + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + DYNAMIC + ) + } +} +test::{ + name:"AGGREGATE over INTS, without alias", + type:"type", + body:{ + statement:'''SELECT a, COUNT(*), COUNT(a), SUM(a), MIN(b), MAX(a) FROM << {\'a\': 1, \'b\': 2} >> GROUP BY a''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "_1" + BIGINT + "_2" + BIGINT + "_3" + INTEGER + "_4" + INTEGER + "_5" + INTEGER + ) + ) + } +} +test::{ + name:"AGGREGATE over INTS, with alias", + type:"type", + body:{ + statement:'''SELECT a, COUNT(*) AS c_s, COUNT(a) AS c, SUM(a) AS s, MIN(b) AS m FROM << {\'a\': 1, \'b\': 2} >> GROUP BY a''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "c_s" + BIGINT + "c" + BIGINT + "s" + INTEGER + "m" + INTEGER + ) + ) + } +} +test::{ + name:"AGGREGATE over DECIMALS", + type:"type", + body:{ + statement:'''SELECT a, COUNT(*) AS c, SUM(a) AS s, MIN(b) AS m FROM << {\'a\': 1.0, \'b\': 2.0}, {\'a\': 1.0, \'b\': 2.0} >> GROUP BY a''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DECIMAL + "c" + BIGINT + "s" + DECIMAL + "m" + DECIMAL + ) + ) + } +} +test::{ + name:"AGGREGATE over nullable integers", + type:"type", + body:{ + statement:''' + SELECT T1.a + FROM T1 + LEFT JOIN T2 AS T2_1 + ON T2_1.d = + ( + SELECT + CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e + FROM T3 AS T3_mapping + ) + LEFT JOIN T2 AS T2_2 + ON T2_2.d = + ( + SELECT + CASE WHEN COUNT(f) = 1 THEN MAX(f) ELSE 0 END AS e + FROM T3 AS T3_mapping + ) + ; + ''', + session:{ + catalog:"aggregations", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + BOOL + ) + ) + } +} +test::{ + name:"UPPER on binding tuple of literal string", + type:"type", + body:{ + statement:''' + SELECT + UPPER(some_str) AS upper_str + FROM + << { \'some_str\': \'hello world!\' } >> + AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "upper_str" + STRING + ) + ) + } +} +test::{ + name:"UPPER on literal string", + type:"type", + body:{ + statement:''' + UPPER(\'hello world\') + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"UPPER on global string", + type:"type", + body:{ + statement:"\n UPPER(os)\n ", + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"UPPER on global string", + type:"type", + body:{ + statement:"\n UPPER(os)\n ", + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"UPPER on global struct", + type:"type", + body:{ + statement:''' + UPPER(person.ssn) + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"UPPER on global nested struct", + type:"type", + body:{ + statement:''' + UPPER(person.name."first") + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"UPPER on global table", + type:"type", + body:{ + statement:''' + SELECT UPPER(breed) AS upper_breed + FROM dogs + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "upper_breed" + STRING + ) + ) + } +} +test::{ + name:"Distinct SQL Select", + type:"type", + body:{ + statement:'''SELECT DISTINCT a, b FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >>;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "b" + STRING + ) + ) + } +} +test::{ + name:"Distinct SQL Select with Ordering", + type:"type", + body:{ + statement:'''SELECT DISTINCT a, b FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >> ORDER BY a;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + ( + ROW + "a" + INTEGER + "b" + STRING + ) + ) + } +} +test::{ + name:"Distinct SQL Select *", + type:"type", + body:{ + statement:'''SELECT DISTINCT * FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >>;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + INTEGER + "b" + STRING + ) + ) + } +} +test::{ + name:"Distinct SQL Select * with Ordering", + type:"type", + body:{ + statement:'''SELECT DISTINCT * FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >> ORDER BY a;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + ( + ROW + "a" + INTEGER + "b" + STRING + ) + ) + } +} +test::{ + name:"Distinct PartiQL Select Value *", + type:"type", + body:{ + statement:'''SELECT DISTINCT VALUE a FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >>;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + INTEGER + ) + } +} +test::{ + name:"Distinct PartiQL Select Value * with Ordering", + type:"type", + body:{ + statement:'''SELECT DISTINCT VALUE a FROM << { \'a\': 1, \'b\': \'Hello, world!\' } >> ORDER BY a;''', + session:{ + catalog:"aws", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + ARRAY + INTEGER + ) + } +} +test::{ + name:"Index on literal list", + type:"type", + body:{ + statement:''' + [0, 1, 2, 3][0] + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Index on global list", + type:"type", + body:{ + statement:''' + dogs[0].breed + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Index on list attribute of global table", + type:"type", + body:{ + statement:''' + SELECT typical_allergies[0] AS main_allergy FROM dogs + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "main_allergy" + STRING + ) + ) + } +} +test::{ + name:'''Pathing into resolved local variable without qualification''', + type:"type", + body:{ + statement:''' + SELECT address.street AS s FROM employer; + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "s" + STRING + ) + ) + } +} +test::{ + name:'''Pathing into resolved local variable without qualification and with sensitivity''', + type:"type", + body:{ + statement:''' + SELECT address."street" AS s FROM employer; + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "s" + STRING + ) + ) + } +} +test::{ + name:'''Pathing into resolved local variable without qualification and with indexing syntax''', + type:"type", + body:{ + statement:''' + SELECT address[\'street\'] AS s FROM employer; + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "s" + STRING + ) + ) + } +} +test::{ + name:'''Pathing into resolved local variable without qualification and with indexing syntax and fully-qualified FROM''', + type:"type", + body:{ + statement:''' + SELECT e.address[\'street\'] AS s FROM "pql"."main"."employer" AS e; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "s" + STRING + ) + ) + } +} +test::{ + name:'''Show that we can\'t use [] to reference a value in a schema. It can only be used on tuples.''', + type:"type", + body:{ + statement:''' + SELECT VALUE 1 FROM "pql"."main"[\'employer\'] AS e; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable "pql"."main" does not exist in the database environment and is not an attribute of the following in-scope variables [].''' + ) + } +} +test::{ + name:'''Show that we can\'t use [] to reference a schema in a catalog. It can only be used on tuples.''', + type:"type", + body:{ + statement:''' + SELECT VALUE 1 FROM "pql"[\'main\']."employer" AS e; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable "pql" does not exist in the database environment and is not an attribute of the following in-scope variables []. Hint: did you intend to use single-quotes (\') here? Remember that double-quotes (") denote quoted identifiers and single-quotes denote strings.''' + ) + } +} +test::{ + name:'''Tuple indexing syntax on literal tuple with literal string key''', + type:"type", + body:{ + statement:''' + { \'aBc\': 1, \'AbC\': 2.0 }[\'AbC\']; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:'''Array indexing syntax on literal tuple with non-literal and non-cast key''', + type:"type", + body:{ + statement:''' + { \'aBc\': 1, \'AbC\': 2.0 }[\'Ab\' || \'C\']; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Expression always returns missing: caused by Collections must be indexed with integers, found STRING''' + ) + } +} +test::{ + name:'''Tuple indexing syntax on literal tuple with explicit cast key''', + type:"type", + body:{ + statement:''' + { \'aBc\': 1, \'AbC\': 2.0 }[CAST(\'Ab\' || \'C\' AS STRING)]; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"Easy case when", + type:"type", + body:{ + statement:''' + CASE + WHEN FALSE THEN 0 + WHEN TRUE THEN 1 + ELSE 2 + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"Folded case when to grab the true", + type:"type", + body:{ + statement:''' + CASE + WHEN FALSE THEN 0 + WHEN TRUE THEN \'hello\' + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"Boolean case when", + type:"type", + body:{ + statement:''' + CASE \'Hello World\' + WHEN \'Hello World\' THEN TRUE + ELSE FALSE + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"Typing even with false condition", + type:"type", + body:{ + statement:''' + CASE + WHEN FALSE THEN \'IMPOSSIBLE TO GET\' + ELSE TRUE + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"Folded out false without default", + type:"type", + body:{ + statement:''' + CASE + WHEN FALSE THEN \'IMPOSSIBLE TO GET\' + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"Not folded gives us a nullable without default", + type:"type", + body:{ + statement:''' + CASE 1 + WHEN 1 THEN TRUE + WHEN 2 THEN FALSE + END; + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:'''Not folded gives us a nullable without default for query''', + type:"type", + body:{ + statement:''' + SELECT + CASE breed + WHEN \'golden retriever\' THEN \'fluffy dog\' + WHEN \'pitbull\' THEN \'short-haired dog\' + END AS breed_descriptor + FROM dogs + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "breed_descriptor" + STRING + ) + ) + } +} +test::{ + name:"Query", + type:"type", + body:{ + statement:''' + SELECT + CASE breed + WHEN \'golden retriever\' THEN \'fluffy dog\' + WHEN \'pitbull\' THEN \'short-haired dog\' + ELSE \'something else\' + END AS breed_descriptor + FROM dogs + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "breed_descriptor" + STRING + ) + ) + } +} +test::{ + name:"Query with heterogeneous data", + type:"type", + body:{ + statement:''' + SELECT + CASE breed + WHEN \'golden retriever\' THEN \'fluffy dog\' + WHEN \'pitbull\' THEN 2 + ELSE 2.0 + END AS breed_descriptor + FROM dogs + ''', + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "breed_descriptor" + DYNAMIC + ) + ) + } +} +test::{ + name:"no-name-found-204", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-00" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-205", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-206", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-207", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:NUMERIC + } +} +test::{ + name:"no-name-found-208", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:NUMERIC + } +} +test::{ + name:"no-name-found-209", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:NUMERIC + } +} +test::{ + name:"no-name-found-210", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-211", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-09" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:NUMERIC + } +} +test::{ + name:"no-name-found-212", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-10" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-213", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-11" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-214", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-12" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DOUBLE + } +} +test::{ + name:"no-name-found-215", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-13" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DOUBLE + } +} +test::{ + name:"no-name-found-216", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-14" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-217", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-15" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-218", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-16" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + CLOB + 2147483647 + ) + } +} +test::{ + name:"no-name-found-219", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-17" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + CLOB + 2147483647 + ) + } +} +test::{ + name:"no-name-found-220", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-18" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-221", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-19" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-222", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-20" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-223", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-21" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-224", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-24" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-225", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-25" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-226", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-26" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-227", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-27" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-228", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-28" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-229", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-29" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRUCT + } +} +test::{ + name:"CASE-WHEN always MISSING", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-30" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-231", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-31" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-232", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-32" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-233", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-33" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-234", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-34" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-235", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-35" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 10 + 5 + ) + } +} +test::{ + name:"no-name-found-236", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-36" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + DECIMAL + 10 + 5 + ) + } +} +test::{ + name:"no-name-found-237", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-37" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-238", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-38" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-239", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-39" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-240", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-40" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-241", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-41" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-242", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-42" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-243", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-43" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-244", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-44" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-245", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-45" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-246", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "case-when-46" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-247", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-00" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-248", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-01" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-249", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-250", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-251", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-252", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-253", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-254", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-255", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-08" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-256", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-09" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-257", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-11" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-258", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-12" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-259", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-13" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-260", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-14" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRING + } +} +test::{ + name:"no-name-found-261", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-15" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-262", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-16" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-263", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-17" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-264", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "nullif-18" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-265", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-00" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-266", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-01" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-267", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-268", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-269", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-270", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} +test::{ + name:"no-name-found-271", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-272", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"no-name-found-273", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-08" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-274", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-09" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-275", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-10" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"no-name-found-276", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-11" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-277", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-12" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-278", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-13" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-279", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-14" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-280", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-15" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-281", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-16" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"no-name-found-282", + type:"type", + body:{ + statement:( + @ + "$inputs" + "basics" + "coalesce-17" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DYNAMIC + } +} +test::{ + name:"Subquery IN collection", + type:"type", + body:{ + statement:( + @ + "$inputs" + "subquery" + "subquery-00" + ), + session:{ + catalog:"subqueries", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "x" + INTEGER + ) + ) + } +} +test::{ + name:"Subquery scalar coercion", + type:"type", + body:{ + statement:( + @ + "$inputs" + "subquery" + "subquery-01" + ), + session:{ + catalog:"subqueries", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "x" + INTEGER + ) + ) + } +} +test::{ + name:"Subquery simple JOIN", + type:"type", + body:{ + statement:( + @ + "$inputs" + "subquery" + "subquery-02" + ), + session:{ + catalog:"subqueries", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "x" + INTEGER + "y" + INTEGER + "z" + INTEGER + "a" + INTEGER + "b" + INTEGER + "c" + INTEGER + ) + ) + } +} +test::{ + name:"Subquery scalar coercion", + type:"type", + body:{ + statement:( + @ + "$inputs" + "subquery" + "subquery-03" + ), + session:{ + catalog:"subqueries", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:'''unary plus on varying numeric types -- this cannot return missing!''', + type:"type", + body:{ + statement:''' + SELECT +t.a AS a + FROM << + { \'a\': CAST(1 AS INT8) }, + { \'a\': CAST(1 AS INT4) } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DYNAMIC + ) + ) + } +} +test::{ + name:'''unary plus on varying numeric types including missing -- this may return missing''', + type:"type", + body:{ + statement:''' + SELECT +t.a AS a + FROM << + { \'a\': CAST(1 AS INT8) }, + { \'a\': CAST(1 AS INT4) }, + { } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DYNAMIC + ) + ) + } +} +test::{ + name:'''unary plus on varying numeric types including string -- this may return missing''', + type:"type", + body:{ + statement:''' + SELECT +t.a AS a + FROM << + { \'a\': CAST(1 AS INT8) }, + { \'a\': CAST(1 AS INT4) }, + { \'a\': \'hello world!\' } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "a" + DYNAMIC + ) + ) + } +} +test::{ + name:'''binary plus on varying types -- this will return missing if one of the operands is not a number''', + type:"type", + body:{ + statement:''' + SELECT t.a + t.b AS c + FROM << + { \'a\': CAST(1 AS INT8), \'b\': CAST(1.0 AS DECIMAL) }, + { \'a\': CAST(1 AS INT4), \'b\': TRUE }, + { \'a\': \'hello world!!\', \'b\': DATE \'2023-01-01\' } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "c" + DYNAMIC + ) + ) + } +} +test::{ + name:''' + unary plus on non-compatible type -- this cannot resolve to a dynamic call since no function + will ever be invoked. + ''', + type:"type", + body:{ + statement:''' + SELECT VALUE +t.a + FROM << + { \'a\': \'hello world!\' } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''STRING is/are incompatible data types for the \'POS\' operator.''' + ) + } +} +test::{ + name:''' + unary plus on dynamic types + ''', + type:"type", + body:{ + statement:''' + SELECT VALUE +t.a + FROM << + { \'a\': \'hello world!\' }, + { \'a\': <<>> } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + DYNAMIC + ) + } +} +test::{ + name:''' + unary plus on missing type -- this cannot resolve to a dynamic call since no function + will ever be invoked. + ''', + type:"type", + body:{ + statement:''' + SELECT VALUE +t.a + FROM << + { \'NOT_A\': 1 } + >> AS t + ''', + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Variable a does not exist in the database environment and is not an attribute of the following in-scope variables [t].''' + ) + } +} +test::{ + name:''' + unary plus on missing type -- this cannot resolve to a dynamic call since no function + will ever be invoked. + ''', + type:"type", + body:{ + statement:"\n +MISSING\n ", + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE, + assertProblemExists:( + ERROR + '''Expression always returns missing: caused by Static function always receives MISSING arguments.''' + ) + } +} +test::{ + name:"Basic Scan Indexed", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "sanity-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:( + BAG + ( + ROW + "first" + STRING + "i" + BIGINT + ) + ) + } +} +test::{ + name:"Basic PIVOT", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "pivot-00" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:STRUCT + } +} +test::{ + name:"IS BOOL", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-00" + ), + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"IS INT", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-01" + ), + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"IS STRING", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-02" + ), + session:{ + catalog:"pql", + cwd:[ + "main" + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"IS NULL", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"MISSING IS NULL", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"NULL IS NULL", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"MISSING IS MISSING", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-06" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"NULL IS MISSING", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-07" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BOOL + } +} +test::{ + name:"ERROR always MISSING", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "is-type-08" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:FAILURE + } +} +test::{ + name:"DECIMAL AS INT2", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-00" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:SMALLINT + } +} +test::{ + name:"DECIMAL AS INT4", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-01" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"DECIMAL AS INT8", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-02" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"DECIMAL AS INT", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-03" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:INTEGER + } +} +test::{ + name:"DECIMAL AS BIGINT", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-04" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:BIGINT + } +} +test::{ + name:"DECIMAL_ARBITRARY AS DECIMAL", + type:"type", + body:{ + statement:( + @ + "$inputs" + "schema_inferencer" + "cast-05" + ), + session:{ + catalog:"pql", + cwd:[ + ] + }, + status:SUCCESS, + expected:DECIMAL + } +} \ No newline at end of file