diff --git a/pydough/unqualified/qualification.py b/pydough/unqualified/qualification.py index 87cf2017f..5cef3fa70 100644 --- a/pydough/unqualified/qualification.py +++ b/pydough/unqualified/qualification.py @@ -34,6 +34,7 @@ SubCollection, WindowCall, ) +from pydough.qdag.collections.calculate import Calculate from pydough.types import PyDoughType from pydough.utilities import ExplodeSpec @@ -638,6 +639,11 @@ def qualify_access( typ: PyDoughType = context.get_expr(name).pydough_type return self.builder.build_reference(context, name, typ) else: + # Accessing `child.name`: forbid reaching a down-streamed + # (inherited) term through a child collection. + self.verify_not_inherited_through_child( + unqualified, unqualified_parent, qualified_parent, context, name + ) # Otherwise, the access is a reference to a scalar attribute of # a child collection node of the current context. Add this new # child to the list of children, unless already present, then @@ -652,6 +658,96 @@ def qualify_access( children, ref_num, name ) + def names_defined_in_child_chain( + self, + qualified_node: PyDoughCollectionQDAG, + context: PyDoughCollectionQDAG, + ) -> set[str]: + """ + Walks the qualified chain of a child access up to (but not including) + the context the child is evaluated in, collecting every term name + defined by a CALCULATE that is part of the child chain itself. + + Args: + `qualified_node`: the qualified collection the term is accessed from. + `context`: the context the child chain is attached to; the walk + stops here so CALCULATEs from the enclosing context are ignored. + + Returns: + The set of term names defined by CALCULATEs within the child chain. + """ + defined: set[str] = set() + node: PyDoughCollectionQDAG | None = qualified_node + while ( + node is not None + and node is not context + and not isinstance(node, GlobalContext) + ): + if isinstance(node, Calculate): + defined.update(name for name in node.calc_term_values.keys()) + # Operators (CALCULATE, WHERE, SINGULAR, ...) step back through + # their preceding context; child accesses step up to their parent. + if node.preceding_context is not None: + node = node.preceding_context + else: + node = node.ancestor_context + return defined + + def verify_not_inherited_through_child( + self, + unqualified: UnqualifiedAccess, + unqualified_parent: UnqualifiedNode, + qualified_parent: PyDoughCollectionQDAG, + context: PyDoughCollectionQDAG, + name: str, + ) -> None: + """ + Verifies that an expression accessed as `child.name` is not a term + that the child only has because it was down-streamed from the context + the child is being evaluated in. For example, in + `customers.CALCULATE(cust_name=name).orders.CALCULATE(x=customer.cust_name)` + `customer` only sees `cust_name` because it inherits it from the + enclosing context, so the access is invalid (use `cust_name` directly). + + Accesses are still valid when the term comes from the child's own + data, e.g. a CALCULATE inside the child expression + (`customer.CALCULATE(customer_name=name).orders.customer_name`) or + inside the data of a PARTITION child (`pricing_collection.price`). + + Raises: + `PyDoughUnqualifiedException` if the term is inherited from the + enclosing context through the child collection. + """ + # Only terms the child inherits (not its own columns/calcs) matter. + is_inherited: bool = ( + name in qualified_parent.ancestral_mapping + or name in qualified_parent.inherited_downstreamed_terms + ) + if not is_inherited or name in qualified_parent.calc_terms: + return + + # Only invalid if the term comes from the enclosing context. If the + # context itself does not inherit it, it originated inside the + # child's own data (e.g. a PARTITION child's partitioned data). + context_inherits: bool = ( + name in context.ancestral_mapping + or name in context.inherited_downstreamed_terms + ) + if not context_inherits: + return + + # The child chain may redefine (shadow) the same name; then the + # value comes from inside the child, so it is valid. + if name in self.names_defined_in_child_chain(qualified_parent, context): + return + + raise PyDoughUnqualifiedException( + f"Cannot access inherited term {name!r} through child collection " + f"{qualified_parent.standalone_string!r} in {unqualified!r}. " + f"Terms down-streamed from an ancestor CALCULATE can only be " + f"referenced directly, e.g. {name!r}." + ) + def qualify_calculate( self, unqualified: UnqualifiedCalculate, diff --git a/tests/conftest.py b/tests/conftest.py index 2aeebe16e..e77087eb9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3210,6 +3210,12 @@ def mock_server_setup(): # Wait until server is ready for _ in range(20): + if proc.poll() is not None: + output, _ = proc.communicate() + raise RuntimeError( + f"Mock server exited with code {proc.returncode}:\n{output}" + ) + try: r: httpx.Response = httpx.get(server_url + "/health", timeout=1) if r.status_code == 200: @@ -3218,7 +3224,12 @@ def mock_server_setup(): time.sleep(0.5) else: proc.terminate() - raise RuntimeError("Mock server failed to start") + try: + output, _ = proc.communicate(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + output, _ = proc.communicate() + raise RuntimeError(f"Mock server failed to start:\n{output}") yield server_url diff --git a/tests/test_pipeline_tpch_custom.py b/tests/test_pipeline_tpch_custom.py index 04c9ae6ac..8b532a165 100644 --- a/tests/test_pipeline_tpch_custom.py +++ b/tests/test_pipeline_tpch_custom.py @@ -128,9 +128,11 @@ order_info_per_priority, order_quarter_test, orders_versus_first_orders, + parent_calc_access_error, part_reduced_size, partition_by_child_reference, partition_by_literal, + partition_child_inherited_term, partition_key_name_collision, partition_self_reference, parts_quantity_increase_95_96, @@ -155,6 +157,7 @@ regional_suppliers_percentile, replace_non_string_arg, richest_customer_per_region, + shadowed_calc_term, simple_cross_1, simple_cross_2, simple_cross_3, @@ -6818,6 +6821,47 @@ ), id="partition_key_name_collision", ), + pytest.param( + PyDoughPandasTest( + shadowed_calc_term, + "TPCH", + lambda: pd.DataFrame( + { + "key": [1, 2, 3, 4, 5], + "x": [ + "23-644-998-4944", + "20-715-308-7926", + "25-884-345-1592", + "20-500-807-1549", + "30-452-969-2072", + ], + } + ), + "shadowed_calc_term", + ), + id="shadowed_calc_term", + ), + pytest.param( + PyDoughPandasTest( + partition_child_inherited_term, + "TPCH", + lambda: pd.DataFrame( + { + "rkey": [0, 1, 2, 3, 4], + "region_name": [ + "AFRICA", + "AMERICA", + "ASIA", + "EUROPE", + "MIDDLE EAST", + ], + "n_nations": [5, 5, 5, 5, 5], + } + ), + "partition_child_inherited_term", + ), + id="partition_child_inherited_term", + ), pytest.param( PyDoughPandasTest( chained_partition_transformed_key, @@ -7676,6 +7720,17 @@ def test_pipeline_e2e_simple_week( ), id="isin_mixed_literal_list", ), + pytest.param( + parent_calc_access_error, + None, + re.escape( + "Cannot access inherited term 'cust_name' through child collection " + "'customer' in customer.cust_name. Terms down-streamed from an " + "ancestor CALCULATE can only be referenced directly, " + "e.g. 'cust_name'." + ), + id="parent_calc_access_error", + ), ], ) def test_pipeline_e2e_errors( diff --git a/tests/test_plan_refsols/partition_child_inherited_term.txt b/tests/test_plan_refsols/partition_child_inherited_term.txt new file mode 100644 index 000000000..98c0f4cd0 --- /dev/null +++ b/tests/test_plan_refsols/partition_child_inherited_term.txt @@ -0,0 +1,5 @@ +ROOT(columns=[('rkey', r_regionkey), ('region_name', max_r_name), ('n_nations', n_rows)], orderings=[(r_regionkey):asc_first]) + AGGREGATE(keys={'r_regionkey': r_regionkey}, aggregations={'max_r_name': MAX(r_name), 'n_rows': COUNT()}) + JOIN(condition=t0.r_regionkey == t1.n_regionkey, type=INNER, cardinality=PLURAL_FILTER, reverse_cardinality=PLURAL_FILTER, columns={'r_name': t0.r_name, 'r_regionkey': t0.r_regionkey}) + SCAN(table=tpch.REGION, columns={'r_name': r_name, 'r_regionkey': r_regionkey}) + SCAN(table=tpch.NATION, columns={'n_regionkey': n_regionkey}) diff --git a/tests/test_plan_refsols/shadowed_calc_term.txt b/tests/test_plan_refsols/shadowed_calc_term.txt new file mode 100644 index 000000000..45959c79c --- /dev/null +++ b/tests/test_plan_refsols/shadowed_calc_term.txt @@ -0,0 +1,7 @@ +ROOT(columns=[('key', o_orderkey), ('x', max_c_phone)], orderings=[(o_orderkey):asc_first], limit=5:numeric) + JOIN(condition=t0.o_custkey == t1.c_custkey, type=LEFT, cardinality=SINGULAR_FILTER, reverse_cardinality=PLURAL_FILTER, columns={'max_c_phone': t1.max_c_phone, 'o_orderkey': t0.o_orderkey}) + SCAN(table=tpch.ORDERS, columns={'o_custkey': o_custkey, 'o_orderkey': o_orderkey}) + AGGREGATE(keys={'c_custkey': c_custkey}, aggregations={'max_c_phone': MAX(c_phone)}) + JOIN(condition=t0.c_custkey == t1.o_custkey, type=INNER, cardinality=PLURAL_FILTER, reverse_cardinality=SINGULAR_ACCESS, columns={'c_custkey': t0.c_custkey, 'c_phone': t0.c_phone}) + SCAN(table=tpch.CUSTOMER, columns={'c_custkey': c_custkey, 'c_phone': c_phone}) + SCAN(table=tpch.ORDERS, columns={'o_custkey': o_custkey}) diff --git a/tests/test_pydough_functions/simple_pydough_functions.py b/tests/test_pydough_functions/simple_pydough_functions.py index aa9ea4d41..caafebf86 100644 --- a/tests/test_pydough_functions/simple_pydough_functions.py +++ b/tests/test_pydough_functions/simple_pydough_functions.py @@ -3967,6 +3967,63 @@ def partition_key_name_collision(): ) +def parent_calc_access_error(): + """ + Test that a child collection's CALCULATE cannot reference a term + defined in a parent collection's CALCULATE, and that doing so raises a + descriptive error message instead of silently qualifying and producing an + error later. + """ + # 'top' calculates 'cust_name' on customers + top = customers.CALCULATE(cust_name=name) + + # Referencing 'customer.cust_name' from orders causes a lowering error + result = top.orders.CALCULATE( + correct_usage=cust_name, incorrect_usage=customer.cust_name + ) + return result + + +def shadowed_calc_term(): + """ + The child `customer.CALCULATE(cust_name=phone).orders` redefines `cust_name` + as `phone`, and `.orders.cust_name` then reads it. Within that child, the nearest + definition is the inner one, so the value really comes from the child's own + data. + """ + return ( + customers.CALCULATE(cust_name=name) + .orders.CALCULATE( + key, + x=MAX(customer.CALCULATE(cust_name=phone).orders.cust_name), + ) + .TOP_K(5, by=key.ASC()) + ) + + +def partition_child_inherited_term(): + """ + Pair every region with its nations via CROSS, partition the pairs by + region, and aggregate a term (`region_name`) that was down-streamed from + the regions CALCULATE *inside the partitioned data*. The PARTITION + context only exposes `rkey`, so accessing `nations.region_name` through + the partition child is valid (context_inherits is False). + """ + + return ( + regions.CALCULATE(rkey=key, region_name=name) + .CROSS(nations) + .WHERE(region_key == rkey) + .PARTITION(name="region_groups", by=rkey) + .CALCULATE( + rkey, + region_name=MAX(nations.region_name), + n_nations=COUNT(nations), + ) + .ORDER_BY(rkey.ASC()) + ) + + def chained_partition_transformed_key(): """ Test for fixing the problem with chaining a second PARTITION on top of a diff --git a/tests/test_sql_refsols/partition_child_inherited_term_ansi.sql b/tests/test_sql_refsols/partition_child_inherited_term_ansi.sql new file mode 100644 index 000000000..5cfe7d54d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_ansi.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 diff --git a/tests/test_sql_refsols/partition_child_inherited_term_databricks.sql b/tests/test_sql_refsols/partition_child_inherited_term_databricks.sql new file mode 100644 index 000000000..5cfe7d54d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_databricks.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 diff --git a/tests/test_sql_refsols/partition_child_inherited_term_duckdb.sql b/tests/test_sql_refsols/partition_child_inherited_term_duckdb.sql new file mode 100644 index 000000000..7029b203d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_duckdb.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 NULLS FIRST diff --git a/tests/test_sql_refsols/partition_child_inherited_term_mysql.sql b/tests/test_sql_refsols/partition_child_inherited_term_mysql.sql new file mode 100644 index 000000000..4f14d5032 --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_mysql.sql @@ -0,0 +1,11 @@ +SELECT + REGION.r_regionkey AS rkey, + MAX(REGION.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.REGION AS REGION +JOIN tpch.NATION AS NATION + ON NATION.n_regionkey = REGION.r_regionkey +GROUP BY + 1 +ORDER BY + 1 diff --git a/tests/test_sql_refsols/partition_child_inherited_term_oracle.sql b/tests/test_sql_refsols/partition_child_inherited_term_oracle.sql new file mode 100644 index 000000000..b7565cde8 --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_oracle.sql @@ -0,0 +1,11 @@ +SELECT + REGION.r_regionkey AS rkey, + MAX(REGION.r_name) AS region_name, + COUNT(*) AS n_nations +FROM TPCH.REGION REGION +JOIN TPCH.NATION NATION + ON NATION.n_regionkey = REGION.r_regionkey +GROUP BY + REGION.r_regionkey +ORDER BY + 1 NULLS FIRST diff --git a/tests/test_sql_refsols/partition_child_inherited_term_postgres.sql b/tests/test_sql_refsols/partition_child_inherited_term_postgres.sql new file mode 100644 index 000000000..7029b203d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_postgres.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 NULLS FIRST diff --git a/tests/test_sql_refsols/partition_child_inherited_term_snowflake.sql b/tests/test_sql_refsols/partition_child_inherited_term_snowflake.sql new file mode 100644 index 000000000..7029b203d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_snowflake.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 NULLS FIRST diff --git a/tests/test_sql_refsols/partition_child_inherited_term_sqlite.sql b/tests/test_sql_refsols/partition_child_inherited_term_sqlite.sql new file mode 100644 index 000000000..5cfe7d54d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_sqlite.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 diff --git a/tests/test_sql_refsols/partition_child_inherited_term_trino.sql b/tests/test_sql_refsols/partition_child_inherited_term_trino.sql new file mode 100644 index 000000000..7029b203d --- /dev/null +++ b/tests/test_sql_refsols/partition_child_inherited_term_trino.sql @@ -0,0 +1,11 @@ +SELECT + region.r_regionkey AS rkey, + MAX(region.r_name) AS region_name, + COUNT(*) AS n_nations +FROM tpch.region AS region +JOIN tpch.nation AS nation + ON nation.n_regionkey = region.r_regionkey +GROUP BY + 1 +ORDER BY + 1 NULLS FIRST diff --git a/tests/test_sql_refsols/shadowed_calc_term_ansi.sql b/tests/test_sql_refsols/shadowed_calc_term_ansi.sql new file mode 100644 index 000000000..430faed0d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_ansi.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_databricks.sql b/tests/test_sql_refsols/shadowed_calc_term_databricks.sql new file mode 100644 index 000000000..430faed0d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_databricks.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_duckdb.sql b/tests/test_sql_refsols/shadowed_calc_term_duckdb.sql new file mode 100644 index 000000000..eaafb127d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_duckdb.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 NULLS FIRST +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_mysql.sql b/tests/test_sql_refsols/shadowed_calc_term_mysql.sql new file mode 100644 index 000000000..675c6300a --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_mysql.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + CUSTOMER.c_custkey, + MAX(CUSTOMER.c_phone) AS max_c_phone + FROM tpch.CUSTOMER AS CUSTOMER + JOIN tpch.ORDERS AS ORDERS + ON CUSTOMER.c_custkey = ORDERS.o_custkey + GROUP BY + 1 +) +SELECT + ORDERS.o_orderkey AS `key`, + _s3.max_c_phone AS x +FROM tpch.ORDERS AS ORDERS +LEFT JOIN _s3 AS _s3 + ON ORDERS.o_custkey = _s3.c_custkey +ORDER BY + 1 +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_oracle.sql b/tests/test_sql_refsols/shadowed_calc_term_oracle.sql new file mode 100644 index 000000000..e2f0097bf --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_oracle.sql @@ -0,0 +1,19 @@ +WITH "_S3" AS ( + SELECT + CUSTOMER.c_custkey AS C_CUSTKEY, + MAX(CUSTOMER.c_phone) AS MAX_C_PHONE + FROM TPCH.CUSTOMER CUSTOMER + JOIN TPCH.ORDERS ORDERS + ON CUSTOMER.c_custkey = ORDERS.o_custkey + GROUP BY + CUSTOMER.c_custkey +) +SELECT + ORDERS.o_orderkey AS "key", + "_S3".MAX_C_PHONE AS x +FROM TPCH.ORDERS ORDERS +LEFT JOIN "_S3" "_S3" + ON ORDERS.o_custkey = "_S3".C_CUSTKEY +ORDER BY + 1 NULLS FIRST +FETCH FIRST 5 ROWS ONLY diff --git a/tests/test_sql_refsols/shadowed_calc_term_postgres.sql b/tests/test_sql_refsols/shadowed_calc_term_postgres.sql new file mode 100644 index 000000000..eaafb127d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_postgres.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 NULLS FIRST +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_snowflake.sql b/tests/test_sql_refsols/shadowed_calc_term_snowflake.sql new file mode 100644 index 000000000..eaafb127d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_snowflake.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 NULLS FIRST +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_sqlite.sql b/tests/test_sql_refsols/shadowed_calc_term_sqlite.sql new file mode 100644 index 000000000..430faed0d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_sqlite.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 +LIMIT 5 diff --git a/tests/test_sql_refsols/shadowed_calc_term_trino.sql b/tests/test_sql_refsols/shadowed_calc_term_trino.sql new file mode 100644 index 000000000..eaafb127d --- /dev/null +++ b/tests/test_sql_refsols/shadowed_calc_term_trino.sql @@ -0,0 +1,19 @@ +WITH _s3 AS ( + SELECT + customer.c_custkey, + MAX(customer.c_phone) AS max_c_phone + FROM tpch.customer AS customer + JOIN tpch.orders AS orders + ON customer.c_custkey = orders.o_custkey + GROUP BY + 1 +) +SELECT + orders.o_orderkey AS key, + _s3.max_c_phone AS x +FROM tpch.orders AS orders +LEFT JOIN _s3 AS _s3 + ON _s3.c_custkey = orders.o_custkey +ORDER BY + 1 NULLS FIRST +LIMIT 5