{"record":{"id":"5dccffdb42b4d008","repo":"vitessio/vitess","slug":"vt13001-5dccff","errorCode":"VT13001","errorMessage":"VT13001: [BUG] pushed wrong predicate to the join: %s","messagePattern":"VT13001: \\[BUG\\] pushed wrong predicate to the join: (.+?)","errorType":"error_code","errorClass":"VitessError","httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/joins.go","lineNumber":90,"sourceCode":"\n\tcase deps.IsSolvedBy(TableID(join)):\n\t\t// if we are dealing with an outer join, always start by checking if this predicate can turn\n\t\t// the join into an inner join\n\t\tif !joinPredicates && IsOuter(join) && canConvertToInner(ctx, expr, TableID(join.GetRHS())) {\n\t\t\tjoin.MakeInner()\n\t\t}\n\n\t\tif !joinPredicates && IsOuter(join) {\n\t\t\t// if we still are dealing with an outer join\n\t\t\t// we need to filter after the join has been evaluated\n\t\t\treturn newFilter(join, expr)\n\t\t}\n\n\t\tjoin.AddJoinPredicate(ctx, expr, true)\n\n\t\treturn join\n\t}\n\tpanic(vterrors.VT13001(\"pushed wrong predicate to the join: \" + sqlparser.String(expr)))\n}\n\n// we are looking for predicates like `tbl.col = <>` or `<> = tbl.col`,\n// where tbl is on the rhs of the left outer join\n// When a predicate uses information from an outer table, we can convert from an outer join to an inner join\n// if the predicate is \"null-intolerant\".\n//\n// Null-intolerant in this context means that the predicate will not be true if the table columns are null.\n//\n// Since an outer join is an inner join with the addition of all the rows from the left-hand side that\n// matched no rows on the right-hand, if we are later going to remove all the rows where the right-hand\n// side did not match, we might as well turn the join into an inner join.\n//\n// This is based on the paper \"Canonical Abstraction for Outerjoin Optimization\" by J Rao et al.\nfunc canConvertToInner(ctx *plancontext.PlanningContext, expr sqlparser.Expr, rhs semantics.TableSet) bool {\n\tisColNameFromRHS := func(e sqlparser.Expr) bool {\n\t\treturn sqlparser.IsColName(e) && ctx.SemTable.RecursiveDeps(e).IsSolvedBy(rhs)\n\t}","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/joins.go#L72-L108","documentation":"VT13001 is Vitess's internal-bug panic code. This one is raised in AddPredicate on a join operator when a predicate could not be classified as belonging to the left side, right side, or as a join predicate — i.e. the predicate-pushdown logic hit an expression shape it was not designed to handle. It signals a planner bug or an unsupported query construct, not a user configuration problem.","triggerScenarios":"Calling AddPredicate on a *Join operator with a predicate expression that is neither pushable to the LHS, pushable to the RHS, nor splittable as a join predicate (e.g. predicates referencing tables in ways the LHS/RHS column sets don't recognize). Reached via the planner's predicate-pushdown phase while planning a query with joins.","commonSituations":"Running an unusual SQL query (odd ON-clause or WHERE predicate over a join, correlated references, mixed keyspace joins) through vtgate; a query that regressed after a Vitess upgrade because new predicate shapes are routed into this code path.","solutions":["Reduce the query to the minimal failing statement and file a bug at github.com/vitessio/vitess with the SQL, VSchema, and full stack trace","Rewrite the query so the predicate is a simple column equality usable as a join predicate or clearly bound to one side of the join","Test on the latest Vitess release; this path has been actively replaced by AST rewriters in newer versions"],"exampleFix":"// before: predicate shape the join cannot classify\n... WHERE a.x + b.y = 5 ...\n// after: rewrite as a predicate each side/planner understands\n... WHERE a.x = 5 AND b.y = 5 ...","handlingStrategy":"fallback","validationCode":"// pre-check in app layer: keep join predicates simple column equalities\nif (!/^\\w+\\.\\w+\\s*=\\s*\\w+\\.\\w+$|^[\\w.]+\\s*=\\s*\\?/.test(whereClause)) {\n  console.warn('complex join predicate may hit VT13001');\n}","typeGuard":"function isSimpleEquality(expr) {\n  return expr != null && expr.type === 'binary' &&\n    expr.operator === '=' &&\n    expr.operands.every(o => o.type === 'column' || o.type === 'value');\n}","tryCatchPattern":"try {\n  await vtgate.execute(session, query, bindVars);\n} catch (e) {\n  if (String(e.message).includes('VT13001')) {\n    // internal planner bug: log SQL + stack, fall back to simplified query, report upstream\n  }\n  throw e;\n}","preventionTips":["Keep join predicates as simple column equalities","Avoid exotic predicates (functions over multiple joined tables) in WHERE/ON","Pin and test the Vitess version before planner-heavy query rollouts","Report any VT13001 to Vitess with minimal repro"],"tags":["vtgate","planbuilder","internal-bug","join-predicate"],"backgroundTag":"internal-bug-panic-vt13001","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}