MDEV-32326 Recursive CTE reference in a scalar subquery must be rejected#5262
MDEV-32326 Recursive CTE reference in a scalar subquery must be rejected#5262Olernov wants to merge 1 commit into
Conversation
A recursive reference to a WITH RECURSIVE table that is reachable only through a scalar subquery (in particular through a WITH clause nested inside such a subquery) was not recognized as a subquery reference. As a result the recursive CTE was wrongly accepted as standard-compliant instead of being rejected with ER_NOT_STANDARD_COMPLIANT_RECURSIVE, and execution later dereferenced an uninitialized join_tab for that reference in st_select_lex_unit::exec_recursive(), crashing the server. The cause was in With_element::check_dependencies_in_unit(). The flag marking that we are inside a subquery (in_subq) was updated only after the unit's own WITH clause had already been analyzed. Hence dependencies discovered while descending into a WITH clause attached to a scalar subquery were recorded in top_level_dep_map instead of sq_dep_map, so contains_sq_with_recursive_reference() failed to report the violation. The fix: set in_subq from unit->item before processing the unit's WITH clause, so that dependencies found in nested WITH clauses of a scalar subquery are correctly attributed to the subquery context. The expected error for the MDEV-32299 cases in cte_recursive.test changes accordingly from ER_BAD_FIELD_ERROR to ER_NOT_STANDARD_COMPLIANT_RECURSIVE: the query is now rejected at dependency analysis for its non-compliant recursive reference rather than failing later during name resolution. The test still verifies that no crash occurs.
There was a problem hiding this comment.
Code Review
This pull request addresses MDEV-32326 by rejecting recursive references in scalar subqueries, updating test cases to expect ER_NOT_STANDARD_COMPLIANT_RECURSIVE instead of ER_BAD_FIELD_ERROR. In sql/sql_cte.cc, the dependency check is modified to set in_subq before checking the with_clause. However, because in_subq is a member variable of With_element, modifying it permanently affects subsequent sibling subqueries or top-level query blocks, potentially rejecting valid CTEs. It is recommended to save and restore the state of in_subq using an RAII helper upon entering and leaving check_dependencies_in_unit.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| table_map *dep_map) | ||
| { | ||
| st_unit_ctxt_elem unit_ctxt_elem= {ctxt, unit}; | ||
| in_subq |= unit->item != NULL; |
There was a problem hiding this comment.
Because in_subq is a member variable of With_element, modifying it with in_subq |= unit->item != NULL; during the recursive traversal of nested units (subqueries) permanently sets it to true for the remainder of the dependency check of this With_element. This means that any subsequent sibling subqueries or top-level query blocks (such as the recursive step of a UNION) will incorrectly inherit in_subq == true, causing valid, standard-compliant recursive CTEs to be rejected with ER_NOT_STANDARD_COMPLIANT_RECURSIVE.
To fix this, we should save and restore the state of in_subq when entering and leaving check_dependencies_in_unit. Since the end of the function is outside the current diff hunk, we can use a simple local RAII struct to automatically restore the value upon function exit.
| in_subq |= unit->item != NULL; | |
| struct Save_in_subq { | |
| bool &ref; | |
| bool val; | |
| Save_in_subq(bool &r) : ref(r), val(r) {} | |
| ~Save_in_subq() { ref = val; } | |
| } save_in_subq(in_subq); | |
| in_subq |= unit->item != NULL; |
There was a problem hiding this comment.
You are wrong, in_subq is not a member of With_element but a by-value parameter of the function, so it's scoped to only this frame and doesn't mutate the caller's in_subq value upstream.
A recursive reference to a WITH RECURSIVE table that is reachable only through a scalar subquery (in particular through a WITH clause nested inside such a subquery) was not recognized as a subquery reference. As a result the recursive CTE was wrongly accepted as standard-compliant instead of being rejected with ER_NOT_STANDARD_COMPLIANT_RECURSIVE, and execution later dereferenced an uninitialized join_tab for that reference in st_select_lex_unit::exec_recursive(), crashing the server.
The cause was in With_element::check_dependencies_in_unit(). The flag marking that we are inside a subquery (in_subq) was updated only after the unit's own WITH clause had already been analyzed. Hence dependencies discovered while descending into a WITH clause attached to a scalar subquery were recorded in top_level_dep_map instead of sq_dep_map, so contains_sq_with_recursive_reference() failed to report the violation.
The fix: set in_subq from unit->item before processing the unit's WITH clause, so that dependencies found in nested WITH clauses of a scalar subquery are correctly attributed to the subquery context.
The expected error for the MDEV-32299 cases in cte_recursive.test changes accordingly from ER_BAD_FIELD_ERROR to ER_NOT_STANDARD_COMPLIANT_RECURSIVE: the query is now rejected at dependency analysis for its non-compliant recursive reference rather than failing later during name resolution. The test still verifies that no crash occurs.