diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/planner/transforms/PartiQLSchemaInferencerTests.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/planner/transforms/PartiQLSchemaInferencerTests.kt index a79b395972..d2ca7149c8 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/planner/transforms/PartiQLSchemaInferencerTests.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/planner/transforms/PartiQLSchemaInferencerTests.kt @@ -115,6 +115,16 @@ class PartiQLSchemaInferencerTests { @Execution(ExecutionMode.CONCURRENT) fun testAggregations(tc: TestCase) = runTest(tc) + @ParameterizedTest + @MethodSource("scalarFunctions") + @Execution(ExecutionMode.CONCURRENT) + fun testScalarFunctions(tc: TestCase) = runTest(tc) + + @ParameterizedTest + @MethodSource("pathExpressions") + @Execution(ExecutionMode.CONCURRENT) + fun testPathExpressions(tc: TestCase) = runTest(tc) + companion object { private val root = this::class.java.getResource("/catalogs/default")!!.toURI().toPath().pathString @@ -2127,6 +2137,139 @@ class PartiQLSchemaInferencerTests { ), ) + @JvmStatic + fun pathExpressions() = listOf( + SuccessTestCase( + name = "Index on literal list", + query = """ + [0, 1, 2, 3][0] + """, + expected = INT4 + ), + SuccessTestCase( + name = "Index on global list", + query = """ + dogs[0].breed + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = STRING + ), + SuccessTestCase( + name = "Index on list attribute of global table", + query = """ + SELECT typical_allergies[0] AS main_allergy FROM dogs + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = BagType( + StructType( + fields = mapOf( + "main_allergy" to STRING, + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Ordered + ) + ) + ) + ), + ) + + @JvmStatic + fun scalarFunctions() = listOf( + SuccessTestCase( + name = "UPPER on binding tuple of literal string", + query = """ + SELECT + UPPER(some_str) AS upper_str + FROM + << { 'some_str': 'hello world!' } >> + AS t + """, + expected = BagType( + StructType( + fields = mapOf( + "upper_str" to STRING, + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Ordered + ) + ) + ) + ), + SuccessTestCase( + name = "UPPER on literal string", + query = """ + UPPER('hello world') + """, + expected = STRING + ), + SuccessTestCase( + name = "UPPER on global string", + query = """ + UPPER(os) + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = STRING + ), + SuccessTestCase( + name = "UPPER on global string", + query = """ + UPPER(os) + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = STRING + ), + SuccessTestCase( + name = "UPPER on global struct", + query = """ + UPPER(person.ssn) + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = STRING + ), + SuccessTestCase( + name = "UPPER on global nested struct", + query = """ + UPPER(person.name."first") + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = STRING + ), + SuccessTestCase( + name = "UPPER on global table", + query = """ + SELECT UPPER(breed) AS upper_breed + FROM dogs + """, + catalog = "pql", + catalogPath = listOf("main"), + expected = BagType( + StructType( + fields = mapOf( + "upper_breed" to STRING, + ), + contentClosed = true, + constraints = setOf( + TupleConstraint.Open(false), + TupleConstraint.UniqueAttrs(true), + TupleConstraint.Ordered + ) + ) + ) + ), + ) + @JvmStatic fun aggregationCases() = listOf( SuccessTestCase( diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/typer/PlanTyper.kt index e0a61160c7..b8829e17d1 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/typer/PlanTyper.kt @@ -453,8 +453,9 @@ internal class PlanTyper( // rewrite root rex(type, op) to steps } - else -> node.root to node.steps + else -> visitRex(node.root, node.root.type) to node.steps } + // short-circuit if whole path was matched if (steps.isEmpty()) { return root diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/dogs.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/dogs.ion new file mode 100644 index 0000000000..6b1187ea20 --- /dev/null +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/dogs.ion @@ -0,0 +1,24 @@ +{ + type: "list", + items: { + type: "struct", + constraints: [closed], + fields: [ + { + name: "breed", + type: "string", + }, + { + name: "avg_height", + type: "float32", + }, + { + name: "typical_allergies", + type: { + type: "list", + items: "string" + } + } + ] + } +} diff --git a/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/os.ion b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/os.ion new file mode 100644 index 0000000000..ace60fe3b2 --- /dev/null +++ b/partiql-planner/src/testFixtures/resources/catalogs/default/pql/main/os.ion @@ -0,0 +1,2 @@ +// String representing the current operating system +"string"