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

  1. File a bug with the failing SQL query and full panic stack trace at the Vitess repository
  2. Check the Vitess version and try the latest patch release, since planner merge bugs are frequently fixed
  3. Rewrite the query to include an explicit join predicate between the tables instead of relying on a cross join
  4. 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

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.