{"record":{"id":"85e29e6cbe834e5b","repo":"vitessio/vitess","slug":"vt13001-85e29e","errorCode":"VT13001","errorMessage":"joins can only compare columns: %s","messagePattern":"joins can only compare columns: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/subquery.go","lineNumber":84,"sourceCode":"\tfor _, jc := range columns {\n\t\tfor _, lhsExpr := range jc.LHSExprs {\n\t\t\toffset := sq.Outer.AddColumn(ctx, true, false, aeWrap(lhsExpr.Expr))\n\t\t\tsq.Vars[lhsExpr.Name] = offset\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (sq *SubQuery) OuterExpressionsNeeded(ctx *plancontext.PlanningContext, outer Operator) (result []*sqlparser.ColName) {\n\tjoinColumns, err := sq.GetJoinColumns(ctx, outer)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tfor _, jc := range joinColumns {\n\t\tfor _, lhsExpr := range jc.LHSExprs {\n\t\t\tcol, ok := lhsExpr.Expr.(*sqlparser.ColName)\n\t\t\tif !ok {\n\t\t\t\tpanic(vterrors.VT13001(\"joins can only compare columns: %s\", sqlparser.String(lhsExpr.Expr)))\n\t\t\t}\n\t\t\tresult = append(result, col)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (sq *SubQuery) GetJoinColumns(ctx *plancontext.PlanningContext, outer Operator) ([]applyJoinColumn, error) {\n\tif outer == nil {\n\t\treturn nil, vterrors.VT13001(\"outer operator cannot be nil\")\n\t}\n\touterID := TableID(outer)\n\tif sq.JoinColumns != nil {\n\t\tif sq.outerID == outerID {\n\t\t\treturn sq.JoinColumns, nil\n\t\t}\n\t}\n\tsq.outerID = outerID","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/subquery.go#L66-L102","documentation":"VT13001 panic in OuterExpressionsNeeded: when planning a join containing a subquery, the planner expects every join column expression on the left-hand side to be a plain column reference. If the LHS of a join comparison is a more complex expression, this internal invariant is violated and the planner panics rather than producing a wrong plan.","triggerScenarios":"Planning a query where a mergeable join/subquery comparison uses a non-column expression on the left side, e.g. `WHERE t.id + 1 = (SELECT ...)` or any function/complex expression as a join column.","commonSituations":"Queries with computed expressions in join or subquery correlation predicates instead of bare column equality; often surfaced when the planner attempts an unexpected merge of a subquery.","solutions":["Rewrite the predicate so both sides are plain column comparisons (e.g. compare t.id directly, or precompute the expression in a derived table)","Reduce the expression to an equality on raw columns so the subquery can be merged","If it looks like a valid query that should work, file a bug with the SQL and stack trace","Simplify the query or execute the subquery separately in application code"],"exampleFix":"// before\nSELECT * FROM t WHERE t.id + 1 = (SELECT max(id) FROM u)\n// after\nSELECT * FROM t WHERE t.id = (SELECT max(id) - 1 FROM u)","handlingStrategy":"validation","validationCode":"// ensure subquery correlation predicates compare bare columns\n// e.g. reject: t.id + 1 = (SELECT ...) ; accept: t.id = (SELECT ...)","typeGuard":"func isPlainColumn(e sqlparser.Expr) bool {\n    _, ok := e.(*sqlparser.ColName)\n    return ok\n}","tryCatchPattern":"err := doQuery(q)\nif err != nil && strings.Contains(err.Error(), \"VT13001\") && strings.Contains(err.Error(), \"joins can only compare columns\") {\n    return doQuery(rewriteToColumnEquality(q))\n}","preventionTips":["Write subquery predicates as direct column-to-column equality","Precompute expressions in a derived table before joining","Test queries with expressions in join predicates against your Vitess version","Avoid function calls on join columns in subquery correlation"],"tags":["subquery","join","planner","internal-panic"],"backgroundTag":"planner-internal-invariant","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}