vitessio/vitess · error
VT13001
VT13001
Error message
should not happen: we should be able to merge cross joins
What it means
VT13001 panic raised in mergeRoutes when the greedy route planner fails to merge two routes into a join even though cross joins were expected to be mergeable at that point. The planner first tries to merge only routes connected by predicates; it flips crossJoinsOK to true when nothing mergeable remains, so hitting the panic means an internal invariant was broken. This indicates a planner bug, not a user query problem.
Source
Thrown at go/vt/vtgate/planbuilder/operators/route_planning.go:220
if len(physicalOps) == 0 {
return nil
}
for len(physicalOps) > 1 {
bestTree, lIdx, rIdx := findBestJoin(ctx, qg, physicalOps, planCache, crossJoinsOK)
// if we found a plan, we'll replace the two plans that were joined with the join plan created
if bestTree != nil {
// we remove one plan, and replace the other
if rIdx > lIdx {
physicalOps = removeAt(physicalOps, rIdx)
physicalOps = removeAt(physicalOps, lIdx)
} else {
physicalOps = removeAt(physicalOps, lIdx)
physicalOps = removeAt(physicalOps, rIdx)
}
physicalOps = append(physicalOps, bestTree)
} else {
if crossJoinsOK {
panic(vterrors.VT13001("should not happen: we should be able to merge cross joins"))
}
// we will only fail to find a join plan when there are only cross joins left
// when that happens, we switch over to allow cross joins as well.
// this way we prioritize joining physicalOps with predicates first
crossJoinsOK = true
}
}
return physicalOps[0]
}
func removeAt(plans []Operator, idx int) []Operator {
return append(plans[:idx], plans[idx+1:]...)
}
func findBestJoin(
ctx *plancontext.PlanningContext,
qg *QueryGraph,
plans []Operator,View on GitHub (pinned to 01a25a7d17)
Solutions
- File a bug with the failing SQL query and full panic stack trace at the Vitess repository
- Check the Vitess version and try the latest patch release, since planner merge bugs are frequently fixed
- Rewrite the query to include an explicit join predicate between the tables instead of relying on a cross join
- As a temporary workaround, split the query into multiple simpler queries executed separately
Defensive patterns
Strategy: fallback
Try / catch
// vtgate returns the panic as an error to the client
rows, err := db.Query(sql)
if err != nil && strings.Contains(err.Error(), "VT13001") {
// retry with a rewritten query avoiding the cross join
rows, err = db.Query(rewrittenSQL)
} Prevention
- Keep Vitess up to date; planner merge bugs are fixed in patch releases
- Test new query shapes against your Vitess version before production rollout
- Prefer explicit join predicates over bare cross joins
- Include the failing SQL and stack trace when reporting planner panics
When it happens
Trigger: greedySolve calls mergeRoutes with a pair of physical operators that could not be merged while crossJoinsOK was false; on a later iteration the planner expects cross joins to now be mergeable but no bestTree can be produced for the remaining pair.
Common situations: Encountered during Vitess development or when running unreleased planner changes; occasionally reported by users running unusual queries (e.g. exotic cross joins, derived tables, or new operator types) that expose planner invariants.
Related errors
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/8c25788c3af63b10.
Report an issue: GitHub.