vitessio/vitess · error

VT12001

VT12001

Error message

can't use [%s] with hash joins

What it means

VT12001 is thrown when AddJoinPredicate receives a join predicate that is not a *sqlparser.ComparisonExpr or whose comparison operator is not supported by hash joins (canBeSolvedWithHashJoin fails). Hash joins can only solve equi-join comparisons; anything else (non-comparison predicates, unsupported operators) is rejected as unsupported functionality.

Source

Thrown at go/vt/vtgate/planbuilder/operators/hash_join.go:261

	hj.LHS = op
}

func (hj *HashJoin) SetRHS(op Operator) {
	hj.RHS = op
}

func (hj *HashJoin) MakeInner() {
	hj.LeftJoin = false
}

func (hj *HashJoin) IsInner() bool {
	return !hj.LeftJoin
}

func (hj *HashJoin) AddJoinPredicate(ctx *plancontext.PlanningContext, expr sqlparser.Expr, pushDown bool) { // TODO: consider whether we should honor the pushDown flag
	cmp, ok := expr.(*sqlparser.ComparisonExpr)
	if !ok || !canBeSolvedWithHashJoin(cmp.Operator) {
		panic(vterrors.VT12001(fmt.Sprintf("can't use [%s] with hash joins", sqlparser.String(expr))))
	}
	lExpr := cmp.Left
	lDeps := ctx.SemTable.RecursiveDeps(lExpr)
	rExpr := cmp.Right
	rDeps := ctx.SemTable.RecursiveDeps(rExpr)
	lID := TableID(hj.LHS)
	rID := TableID(hj.RHS)
	if !lDeps.IsSolvedBy(lID) || !rDeps.IsSolvedBy(rID) {
		// we'll switch and see if things work out then
		lExpr, rExpr = rExpr, lExpr
		lDeps, rDeps = rDeps, lDeps
	}

	if !lDeps.IsSolvedBy(lID) || !rDeps.IsSolvedBy(rID) {
		panic(vterrors.VT12001(fmt.Sprintf("can't use [%s] with hash joins", sqlparser.String(expr))))
	}

	hj.JoinComparisons = append(hj.JoinComparisons, Comparison{

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Rewrite the join condition as an equality (`ON a.x = b.y`) so it can be solved with a hash join
  2. Move the non-equi condition from the ON clause into the WHERE clause so it is applied as a filter rather than a join predicate
  3. Restructure the query (e.g. use subqueries or pre-filtered derived tables) so joins are equijoins

Example fix

// before
SELECT * FROM a JOIN b ON a.x LIKE b.y
// after
SELECT * FROM a JOIN b ON a.id = b.id WHERE a.x LIKE b.y
Defensive patterns

Strategy: validation

Validate before calling

-- verify join predicates are equijoins before routing through VTGate
-- reject non-ComparisonExpr / non-equi operators in query construction layer
if cmp, ok := expr.(*sqlparser.ComparisonExpr); !ok || !isEquiOperator(cmp.Operator) {
	// rewrite to WHERE-level filter or reject
}

Try / catch

if vterrors.Code(err) == vtrpcpb.Code_UNIMPLEMENTED && strings.Contains(err.Error(), "hash joins") {
	// fall back to a rewritten equijoin query
}

Prevention

When it happens

Trigger: mergeOrJoin (or the TestJoinPredicates harness) hands a predicate to HashJoin.AddJoinPredicate that is not an equality-style comparison - e.g. `a LIKE b`, `a IN (...)`, a boolean expression, or an operator like `<`, `>` when the planner attempts to solve it with a hash join.

Common situations: Queries with non-equi JOIN conditions (range joins) that the planner tries (and fails) to solve with a hash join; ORMs generating join predicates with LIKE or inequalities; planner routing choices after predicate pushdown changes.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/9485b9b8dc88a5e1. Report an issue: GitHub.