Not looking for external contributions on this one. We're already working in this area and intend to fix it ourselves. Please don't open a PR against this issue — it's filed to track the problem and record the analysis, not as an invitation. Comments and additional repro cases are very welcome.
Overview of the Issue
A recursive CTE whose seed selects * from a table without authoritative column information panics during semantic analysis. The star never gets expanded, so column resolution finds a StarExpr where it expects only AliasedExpr entries. Semantic analysis runs before operators.PlanQuery, so it is outside the planner's PanicHandler; the panic travels to VTGate.HandlePanic and comes back to the client as an uncaught panic INTERNAL error, with a stack trace in the vtgate logs. Plain client SQL is enough to trigger it, e.g. whenever schema tracking is disabled, stale, or has not yet seen the table.
There are two shapes, with different panic sites:
- Without a declared column list —
CTETable.getColumns panics on the unexpanded star (go/vt/vtgate/semantics/cte_table.go, the VT12001("should not be called") branch) while resolving the recursive reference inside the definition.
- With a declared column list —
extractColumnsFromCTE (go/vt/vtgate/semantics/real_table.go) compares the declared list against the length of the unexpanded projection — one StarExpr — and hits the raw panic("mismatch of columns") while resolving columns at the CTE's use site. Because this is a string panic rather than an error-typed one, operators.PanicHandler would re-panic on it even where that handler is on the stack.
Reproduction Steps
Sharded keyspace with a table the schema tracker has no authoritative column list for (user_metadata in go/vt/vtgate/planbuilder/testdata/vschemas/schema.json works in the planner test harness).
Shape 1:
WITH RECURSIVE x AS (SELECT * FROM user_metadata UNION ALL SELECT id + 1 FROM x WHERE id < 10) SELECT id FROM x
Shape 2:
WITH RECURSIVE x(a, b, c, d, e) AS (SELECT * FROM user_metadata UNION ALL SELECT a + 1, b, c, d, e FROM x WHERE a < 10) SELECT a FROM x
Both return uncaught panic to the client instead of planning (shape 1 today panics in CTETable.getColumns; shape 2 panics at the same place first, and at extractColumnsFromCTE once #20702 is applied). An unsharded pass-through does not reach the panic — the query is routed without ever resolving the CTE's columns.
Analysis
Status against the two in-flight PRs in this area:
| Shape |
main |
after #20633 |
after #20633 + #20702 |
| No declared list, star seed |
panics (getColumns) |
panics (getColumns) |
✅ plans as a single route |
| Declared list, star seed |
panics (getColumns) |
panics (getColumns) |
❌ panics (extractColumnsFromCTE) |
#20633 validates declared column list lengths but deliberately skips full validation when the projection still contains a star (a star expands to at least one column, so only fewer names than select expressions is provably wrong). That skip admits exactly the shape-2 combination: the declared list passes the check, getColumns no longer panics after #20702, and extractColumnsFromCTE then trips over the arity it compares against the unexpanded projection.
The likely fix for the remaining shape is to make extractColumnsFromCTE tolerate a star-containing projection the way extractSelectExprsFromCTE already does (return nil and fall back to non-authoritative resolution) instead of panicking, plus a regression test for the declared-list-over-star shape. Converting the raw string panic into a typed error would also stop this class of bug from escaping PanicHandler in the future.
Binary Version
main @ 5b214194df458aa2957f232c3c2a2b9ba6890012
Operating System and Environment details
Linux aarch64 (semantic analyzer unit tests; not environment-specific)
Log Fragments
vtgate log: uncaught panic: VT12001: unsupported: should not be called (shape 1)
vtgate log: uncaught panic: mismatch of columns (shape 2, with #20702 applied)
Overview of the Issue
A recursive CTE whose seed selects
*from a table without authoritative column information panics during semantic analysis. The star never gets expanded, so column resolution finds aStarExprwhere it expects onlyAliasedExprentries. Semantic analysis runs beforeoperators.PlanQuery, so it is outside the planner'sPanicHandler; the panic travels toVTGate.HandlePanicand comes back to the client as anuncaught panicINTERNAL error, with a stack trace in the vtgate logs. Plain client SQL is enough to trigger it, e.g. whenever schema tracking is disabled, stale, or has not yet seen the table.There are two shapes, with different panic sites:
CTETable.getColumnspanics on the unexpanded star (go/vt/vtgate/semantics/cte_table.go, theVT12001("should not be called")branch) while resolving the recursive reference inside the definition.extractColumnsFromCTE(go/vt/vtgate/semantics/real_table.go) compares the declared list against the length of the unexpanded projection — oneStarExpr— and hits the rawpanic("mismatch of columns")while resolving columns at the CTE's use site. Because this is a string panic rather than an error-typed one,operators.PanicHandlerwould re-panic on it even where that handler is on the stack.Reproduction Steps
Sharded keyspace with a table the schema tracker has no authoritative column list for (
user_metadataingo/vt/vtgate/planbuilder/testdata/vschemas/schema.jsonworks in the planner test harness).Shape 1:
Shape 2:
Both return
uncaught panicto the client instead of planning (shape 1 today panics inCTETable.getColumns; shape 2 panics at the same place first, and atextractColumnsFromCTEonce #20702 is applied). An unsharded pass-through does not reach the panic — the query is routed without ever resolving the CTE's columns.Analysis
Status against the two in-flight PRs in this area:
getColumns)getColumns)getColumns)getColumns)extractColumnsFromCTE)#20633 validates declared column list lengths but deliberately skips full validation when the projection still contains a star (a star expands to at least one column, so only
fewer names than select expressionsis provably wrong). That skip admits exactly the shape-2 combination: the declared list passes the check,getColumnsno longer panics after #20702, andextractColumnsFromCTEthen trips over the arity it compares against the unexpanded projection.The likely fix for the remaining shape is to make
extractColumnsFromCTEtolerate a star-containing projection the wayextractSelectExprsFromCTEalready does (return nil and fall back to non-authoritative resolution) instead of panicking, plus a regression test for the declared-list-over-star shape. Converting the raw string panic into a typed error would also stop this class of bug from escapingPanicHandlerin the future.Binary Version
Operating System and Environment details
Linux aarch64 (semantic analyzer unit tests; not environment-specific)Log Fragments
vtgate log: uncaught panic: VT12001: unsupported: should not be called (shape 1) vtgate log: uncaught panic: mismatch of columns (shape 2, with #20702 applied)