Skip to content

vtgate: validate the declared column list length of CTEs and derived tables - #20633

Draft
GrahamCampbell wants to merge 3 commits into
vitessio:mainfrom
GrahamCampbell:recursive-cte-column-list-validation
Draft

vtgate: validate the declared column list length of CTEs and derived tables#20633
GrahamCampbell wants to merge 3 commits into
vitessio:mainfrom
GrahamCampbell:recursive-cte-column-list-validation

Conversation

@GrahamCampbell

@GrahamCampbell GrahamCampbell commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Description

A declared column list whose length does not match the select list it names passes analysis and fails much later inside vtgate, or does not fail at all. Recursive CTEs hit recovered panics or internal errors while resolving columns, ordinary CTEs and direct derived tables hit an index out of range panic through the derived table path, and a too long list on that path is silently accepted with the extra names ignored. MySQL rejects every form up front. Since malformed client SQL can drive the recovered panic paths, this is probably worth backporting.

The validation lives on the paths every such definition flows through: derived table collection, which is also where non-recursive CTEs are inlined, and the construction of the table info for a recursive CTE reference. Both run after star expansion, so a star the analyzer has expanded is validated like any other projection; an unexpanded star cannot be fully validated, but a list with fewer names than select expressions is still rejected, since every star expands to at least one column. A mismatch is rejected with VT03033, the error vtgate already returns for the same mistake on insert row aliases, and MySQL's error 1353 for every non-self-referencing form.

Self-referencing recursive CTEs follow MySQL's resolution model, which testing against 8.0.46 and 8.4.10 pinned down: a declared list that pairs with the seed select renames the columns everywhere and hides the seed names, while a list that cannot be paired leaves the recursive reference on the seed select names, with the count check applying where the CTE is used. When the term then references a declared name, MySQL reports an unknown column (1054); vtgate's strict analysis surfaces the count check instead, because the unresolved column parks as a sharded error that only reports when no hard error follows, and unsharded pass-through returns MySQL's own error. Definitions that are never referenced are accepted, exactly as MySQL accepts them, and a reference inside another definition only counts as a use if that definition is itself used, so unused dependency chains stay silent too. A dormant body binds safely even when it reads a column an unpairable list fails to provide, and a derived table mismatch inside an unused definition defers the same way, surfacing only when a use reaches its enclosing definition. #20631 relies on this validation when resolving declared columns positionally, and only projections with a statically known cardinality plan far enough to reach that resolution. Thirty-nine single-fault analyzer cases cover the short, long, star, union, matching, name-visibility, unused, and unused-chain forms across recursive CTEs, plain CTEs, and derived tables, plus a qualified-star pair that stacks on the corrected expansion in #20759, and all existing plans are unchanged.

Related Issue(s)

#20631 depends on this change.

Checklist

  • "Backport to:" labels have been added if this change should be back-ported to release branches
  • If this change is to be back-ported to previous releases, a justification is included in the PR description
  • Tests were added or are not required
  • Did the new or modified tests pass consistently locally and on CI?
  • Documentation was added or is not required

Deployment Notes

CTEs and derived tables with a declared column list whose length does not match their select list previously caused a recovered panic or an internal error, or were silently accepted with the extra names ignored. They now fail with the error MySQL returns, except inside a self-referencing recursive CTE definition, where an unpairable list exposes the seed select names to the recursive reference and sharded queries report the count mismatch where MySQL reports an unknown column. Unreferenced definitions stay accepted, as in MySQL, including derived table mismatches nested inside them. 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
Loading

Copilot AI review requested due to automatic review settings July 19, 2026 16:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added this to the v25.0.0 milestone Jul 19, 2026
@vitess-bot vitess-bot Bot added NeedsWebsiteDocsUpdate What it says NeedsDescriptionUpdate The description is not clear or comprehensive enough, and needs work NeedsIssue A linked issue is missing for this Pull Request NeedsBackportReason If backport labels have been applied to a PR, a justification is required labels Jul 19, 2026
@vitess-bot

vitess-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Checklist

Hello reviewers! 👋 Please follow this checklist when reviewing this Pull Request.

General

  • Ensure that the Pull Request has a descriptive title.
  • Ensure there is a link to an issue (except for internal cleanup and flaky test fixes), new features should have an RFC that documents use cases and test cases.

Tests

  • Bug fixes should have at least one unit or end-to-end test, enhancement and new features should have a sufficient number of tests.

Documentation

  • Apply the release notes (needs details) label if users need to know about this change.
  • New features should be documented.
  • There should be some code comments as to why things are implemented the way they are.
  • There should be a comment at the top of each new or modified test to explain what the test does.

New flags

  • Is this flag really necessary?
  • Flag names must be clear and intuitive, use dashes (-), and have a clear help text.

If a workflow is added or modified:

  • Each item in Jobs should be named in order to mark it as required.
  • If the workflow needs to be marked as required, the maintainer team must be notified.

Backward compatibility

  • Protobuf changes should be wire-compatible.
  • Changes to _vt tables and RPCs need to be backward compatible.
  • RPC changes should be compatible with vitess-operator
  • If a flag is removed, then it should also be removed from vitess-operator and arewefastyet, if used there.
  • vtctl command output order should be stable and awk-able.

@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 40f4d18 to 7197760 Compare July 19, 2026 17:05
Copilot AI review requested due to automatic review settings July 19, 2026 17:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 19, 2026 17:07
@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 7197760 to ae94a0a Compare July 19, 2026 17:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from ae94a0a to 073c6e5 Compare July 25, 2026 12:00
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 073c6e5 to 4865729 Compare July 25, 2026 13:07
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

Comment thread go/vt/vtgate/semantics/table_collector.go Outdated
Comment thread go/vt/vtgate/semantics/table_collector.go Outdated
arthurschreiber added a commit that referenced this pull request Jul 27, 2026
The seed selected * from user_metadata, which testdata/schemas/user.sql
defines with five columns, against a single-column recursive term. MySQL
rejects that with error 1222, so no plan recorded for it could ever have
run. cte_cases.json is not one of the files the plan e2e test executes,
so nothing caught it. Spell out all five columns in the recursive term
instead.

Drop the declared column list variant. Writing it with a matching count,
x(a, b, c, d, e), panics in extractColumnsFromCTE, which compares the
declared list against the length of the unexpanded projection: one
StarExpr. The version that was here passed only because a single
declared column happened to equal that length, which is not a property
worth resting a test on. That panic belongs with the column list
validation in #20633, not here, and the remaining case already covers
this fix.

Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Comment thread go/vt/vtgate/semantics/analyzer_test.go Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 16:18
@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 4865729 to af92512 Compare July 27, 2026 16:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@arthurschreiber arthurschreiber added Backport to: release-23.0 Needs to be backport to release-23.0 Backport to: release-24.0 Needs to be backport to release-24.0 Type: Bug Type: Compatibility Bug and removed NeedsDescriptionUpdate The description is not clear or comprehensive enough, and needs work NeedsWebsiteDocsUpdate What it says NeedsIssue A linked issue is missing for this Pull Request NeedsBackportReason If backport labels have been applied to a PR, a justification is required labels Jul 29, 2026
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.10%. Comparing base (70c7a72) to head (0c2bdd6).
⚠️ Report is 457 commits behind head on main.

Files with missing lines Patch % Lines
go/vt/vtgate/semantics/table_collector.go 94.44% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main   #20633       +/-   ##
===========================================
+ Coverage   69.67%   87.10%   +17.43%     
===========================================
  Files        1614       21     -1593     
  Lines      216793     5040   -211753     
===========================================
- Hits       151044     4390   -146654     
+ Misses      65749      650    -65099     
Flag Coverage Δ
partial 87.10% <95.45%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@arthurschreiber

Copy link
Copy Markdown
Member

During review I also found a pre-existing bug and opened an issue at #20743 (and a draft PR at #20702)

@GrahamCampbell

Copy link
Copy Markdown
Collaborator Author

Thanks I left some feedback on that other PR. I'll rebase this one and apply your feedback here once that one lands.

@arthurschreiber

Copy link
Copy Markdown
Member

@GrahamCampbell I think this PR can land before the draft PR I opened, no? No need to be blocked on an unrelated fix.

Copilot AI review requested due to automatic review settings July 29, 2026 14:07
@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 0c2bdd6 to 0ced915 Compare July 29, 2026 14:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@GrahamCampbell

Copy link
Copy Markdown
Collaborator Author

You're right — the star-seed panic pre-dates this change and lands in the same place with or without it, so there's nothing to wait for. I've applied the naming suggestion, kept it squashed, and rebased this and #20631 on current main.

Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
…tables

Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
Copilot AI review requested due to automatic review settings July 29, 2026 21:51
@GrahamCampbell
GrahamCampbell force-pushed the recursive-cte-column-list-validation branch from 0ced915 to 3500478 Compare July 29, 2026 21:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@GrahamCampbell

Copy link
Copy Markdown
Collaborator Author

Two fixes from the latest review round, now at 3500478. Validation is lazier, matching MySQL: a use inside another definition only counts if that definition is itself used, so with recursive x(a) as (select 1, 2), y as (select 1 from x) select 1 analyzes cleanly while the used-chain variant still errors — worth noting the used chain panics outright on main today, so the check strictly improves on it. And the qualified-star case select a from (select r.* from t l join t r using (col1)) x(a, b, c) exposed a pre-existing expansion bug — Vitess coalesces r.* after JOIN USING where MySQL does not, silently dropping the join column from results — which is now fixed underneath in #20759, and this PR stacks on it with integration cases pinning both name-list lengths.

@GrahamCampbell
GrahamCampbell marked this pull request as draft July 29, 2026 21:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3500478d87

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread go/vt/vtgate/semantics/zz_scratch_review_test.go Outdated
Comment thread go/vt/vtgate/semantics/early_rewriter.go
Comment thread go/vt/vtgate/semantics/table_collector.go
Signed-off-by: Graham Campbell <hello@gjcampbell.co.uk>
Copilot AI review requested due to automatic review settings July 29, 2026 23:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@GrahamCampbell

Copy link
Copy Markdown
Collaborator Author

Follow-up to the stack re-review: both findings here plus the one on #20631 are addressed, at 02ad377 here and 6efd084 there.

An unpairable declared list read through an unused dependency chain no longer panics: extractColumnsFromCTE falls back to the select list names, matching the CTETable rule, and the same unpairing now marks the table non-authoritative, so a dormant self-referencing body reading a declared name stays silent as MySQL does (checked against 8.4.10). Derived-table mismatches inside unused definitions defer through the same mechanism as CTE references: recorded against the enclosing definition and surfaced only when a use reaches it, with the dormant table kept on unpaired select-list names non-authoritatively so nothing indexes past the declared list.

On #20631, CTETable.getExprFor now maps declared names positionally, so projecting the declared names of an unmerged recursive CTE plans instead of failing with VT13001. One deliberate deviation from the suggested patch: when the list pairs, the declared names mask the seed names entirely rather than keeping a name-based fallback, because that fallback resolves the wrong column when declared names swap the seed aliases; a golden pins the swapped case and MySQL 8.4.10 confirms the positional semantics.

The scratch test file is deleted, ErrorContains is actually applied this time, and the two longest comment blocks are trimmed. The unmerged-path UNION DISTINCT loss went to #20703 as scope expansion rather than inline feedback here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants