vtgate: bind recursive CTE column filters as arguments in unmerged term queries - #20631
Draft
GrahamCampbell wants to merge 5 commits into
Draft
vtgate: bind recursive CTE column filters as arguments in unmerged term queries#20631GrahamCampbell wants to merge 5 commits into
GrahamCampbell wants to merge 5 commits into
Conversation
GrahamCampbell
requested review from
arthurschreiber,
frouioui,
harshit-gangal and
systay
as code owners
July 19, 2026 15:31
Contributor
Review ChecklistHello reviewers! 👋 Please follow this checklist when reviewing this Pull Request. General
Tests
Documentation
New flags
If a workflow is added or modified:
Backward compatibility
|
GrahamCampbell
marked this pull request as draft
July 19, 2026 15:37
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 19, 2026 16:28
9543ee5 to
290264a
Compare
5 tasks
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 19, 2026 16:35
290264a to
40f4d18
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 19, 2026 17:05
e37e61c to
e4de51a
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 19, 2026 17:07
e4de51a to
0541e46
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 25, 2026 12:00
0541e46 to
b9d9685
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
2 times, most recently
from
July 27, 2026 16:19
229689e to
d2d31b2
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 27, 2026 20:06
d2d31b2 to
e61cb36
Compare
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 29, 2026 14:07
e61cb36 to
5e24eeb
Compare
Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
…tables Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
…rm queries Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
GrahamCampbell
force-pushed
the
recursive-cte-term-filter-arguments
branch
from
July 29, 2026 21:51
5e24eeb to
0f09e63
Compare
Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When a recursive CTE does not merge into a single route, WHERE predicates in the recursive term that reference CTE columns are never rewritten to per iteration bind variables. Filters over CTE columns alone are emitted as references to a table that does not exist in the term query, so every execution fails with an unknown column error. Predicates mixing CTE and term table columns fail the same way, including through comma join linkage, and OR predicates spanning both sides are silently dropped, which returns wrong results. A column referenced only in the WHERE clause is also never registered in the RecurseCTE primitive's join variables.
The term's WHERE path now binds these predicates at translation time, exactly as the join ON path always has. They become reserved arguments supplied per iteration, the referenced columns are registered in the join variables, and the tracked original form is restored when the whole CTE later merges into a single route, where the column reference is correct inside the pushed WITH RECURSIVE. Routes gain the same binding as a safety net for predicates arriving through other paths. Offset planning and the projection path both resolve recursion columns positionally through a declared column list; each previously failed with an internal error, the former reachable through the ON clause and the latter whenever the outer query selected a declared name. A declared list that reaches this resolution is guaranteed well formed by #20633, since only seed projections with a statically known cardinality plan this far.
Eight planner cases pin the behavior, covering the plain filter, the comma join, mixed, and OR forms, a filter on a column absent from the term select list, a declared column list selected through its declared names, a declared list whose names swap the seed aliases, and a fully merged CTE keeping its legal column form. Two end to end tests walk a chain of rows across two shards and compare results against MySQL, one through seed names and one through a declared column list, with the data sized so a dropped filter changes the result. Predicates containing subqueries settle after term translation and are not covered by this change.
Related Issue(s)
This PR depends on #20633 being merged first. Its first commit is that change.
Checklist
Deployment Notes
Recursive CTEs whose terms filter on CTE columns previously failed with unknown column errors or silently dropped those filters unless the whole CTE merged into a single route. Direct term predicates are now bound per iteration, and selecting the declared column names of such a CTE previously failed with an internal error and now works. Predicates containing subqueries remain affected in the unmerged form. No migrations or configuration changes.
AI Disclosure
This PR was written primarily by Fable with review from GPT 5.6 Sol.
graph LR subgraph json [JSON support] direction TB PR20625["#20625 (merged)<br>mysql/json: fix MarshalTo discarding accumulated output for nested blob and bit values"] PR20632["#20632<br>vtgate: preserve IN value lists in complex aggregate projections"] PRA["#20691<br>evalengine: disable the static IN hash table for JSON operands"] PR20682["#20682 (draft)<br>evalengine: support constant-folded JSON values as literals"] PR20683["#20683 (draft)<br>evalengine, sqlparser: MySQL comparison domains; nested BETWEEN parentheses"] PR20626["#20626 (draft)<br>vtgate: support cross-shard JSON_ARRAYAGG and JSON_OBJECTAGG"] PR20625 --> PR20682 PRA --> PR20682 PR20682 --> PR20683 PR20632 --> PR20626 PR20683 --> PR20626 end subgraph union [Union routing] direction TB PR20628["#20628<br>vtgate: track per-source copies of pushed join predicates so merges skip them"] PR20629["#20629<br>vtgate: fix None routing handling when merging unions"] PR20630["#20630 (draft)<br>vtgate: merge unions on join-predicate-free routings when all sources agree"] PR20755["#20755<br>vtgate: fix union merging through reference-table alternates"] PR20756["#20756 (draft)<br>vtgate: merge empty reference branches through rewritten copies"] PR20628 --> PR20630 PR20629 --> PR20630 PR20630 --> PR20756 PR20755 --> PR20756 end subgraph cte [Recursive CTEs] direction TB PR20759["#20759<br>vtgate: expand qualified stars without JOIN USING coalescing"] PR20633["#20633 (draft)<br>vtgate: validate the declared column list length of CTEs and derived tables"] PR20631["#20631 (draft)<br>vtgate: bind recursive CTE column filters as arguments in unmerged term queries"] PR20759 --> PR20633 PR20633 --> PR20631 end json ~~~ union ~~~ cte