{"record":{"id":"41385c53ed74aebb","repo":"vitessio/vitess","slug":"vt12001-41385c","errorCode":"VT12001","errorMessage":"pushing predicates on UNION where the first SELECT contains * or NEXT","messagePattern":"pushing predicates on UNION where the first SELECT contains \\* or NEXT","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"go/vt/vtgate/planbuilder/operators/union.go","lineNumber":103,"sourceCode":"to end up with an operator tree that looks something like this:\n\nselect * (\n \tselect foo as col, bar from tbl1 where foo = 42\n\tunion\n\tselect id, baz from tbl2 where id = 42\n) as X\n\nNotice how `X.col = 42` has been translated to `foo = 42` and `id = 42` on respective WHERE clause.\nThe first SELECT of the union dictates the column names, and the second is whatever expression\ncan be found on the same offset. The names of the RHS are discarded.\n*/\nfunc (u *Union) AddPredicate(ctx *plancontext.PlanningContext, expr sqlparser.Expr) Operator {\n\toffsets := make(map[string]int)\n\tsel := u.GetSelectFor(0)\n\tfor i, selectExpr := range sel.GetColumns() {\n\t\tae, ok := selectExpr.(*sqlparser.AliasedExpr)\n\t\tif !ok {\n\t\t\tpanic(vterrors.VT12001(\"pushing predicates on UNION where the first SELECT contains * or NEXT\"))\n\t\t}\n\t\toffsets[strings.ToLower(ae.ColumnName())] = i\n\t}\n\n\texprPerSource := u.predicatePerSource(ctx, expr, offsets)\n\tfor i, src := range u.Sources {\n\t\tu.Sources[i] = src.AddPredicate(ctx, exprPerSource[i])\n\t}\n\n\treturn u\n}\n\nfunc (u *Union) predicatePerSource(ctx *plancontext.PlanningContext, expr sqlparser.Expr, offsets map[string]int) []sqlparser.Expr {\n\texprPerSource := make([]sqlparser.Expr, len(u.Sources))\n\n\tfor i := range u.Sources {\n\t\tpredicate := expr\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/union.go#L85-L121","documentation":"VT12001 is an unsupported-feature error raised when the planner attempts to push a predicate onto a UNION whose first SELECT contains a star (*) expression or a NEXT-sequence expression. Pushing predicates to each UNION source requires mapping the predicate's column to positional offsets in the first SELECT's column list, which is impossible when the first SELECT expands `*` or uses NEXT, so the planner fails fast instead of producing an incorrect plan.","triggerScenarios":"A WHERE/HAVING/join predicate is pushed into a UNION operator while sel.GetColumns()[0..n] of the first SELECT contains anything other than *sqlparser.AliasedExpr — specifically `SELECT *` (or table.*) or `NEXT VALUE FOR seq` in the first arm of the UNION.","commonSituations":"Users writing `SELECT * FROM a UNION SELECT ...` with a WHERE clause on the union in a sharded Vitess deployment; often surfaces after upgrading planner versions where predicate pushdown into UNIONs became more aggressive.","solutions":["Replace `SELECT *` in the first SELECT of the UNION with an explicit column list so the planner can match predicate columns to offsets.","Remove NEXT VALUE FOR expressions from the first SELECT of the UNION, or compute them outside the union.","Restructure the query: wrap the UNION in a derived table with explicit column names and apply the WHERE clause outside it.","If the query must keep `*`, disable the failing pushdown path via planner flags/version-appropriate settings and report the query as an unsupported-shape bug."],"exampleFix":"// before\nSELECT * FROM a UNION SELECT x, y FROM b WHERE id = 1\n\n// after\nSELECT id, name FROM a UNION SELECT x, y FROM b WHERE id = 1","handlingStrategy":"validation","validationCode":"firstSel := unionOp.GetSelectFor(0)\nfor _, se := range firstSel.GetColumns() {\n    if _, ok := se.(*sqlparser.AliasedExpr); !ok {\n        // first SELECT has * or NEXT; don't attempt predicate pushdown\n        return\n    }\n}","typeGuard":"func isPushable(union *operators.Union) bool {\n    for _, se := range union.GetSelectFor(0).GetColumns() {\n        if _, ok := se.(*sqlparser.AliasedExpr); !ok {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":"// Unsupported-feature panics should be pre-checked; if wrapping a recover boundary:\ndefer func() {\n    if r := recover(); r != nil {\n        if vte, ok := r.(error); ok && vterrors.Code(vte) == vtrpcpb.Code_VT12001 {\n            // fall back to non-pushdown planning for this union\n        }\n    }\n}()","preventionTips":["Always write explicit column lists in the first SELECT of a UNION used with WHERE clauses in Vitess.","Avoid NEXT VALUE FOR expressions in the first UNION arm when predicates are applied to the union.","Prefer applying filters to the derived table wrapping a UNION rather than relying on pushdown into it."],"tags":["planbuilder","union","predicate-pushdown","unsupported-sql"],"backgroundTag":"unsupported-union-pushdown","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}