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
- Rewrite the join condition as an equality (`ON a.x = b.y`) so it can be solved with a hash join
- 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
- 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
- Design joins as equi-joins; move LIKE/range conditions to WHERE
- Lint generated SQL (ORM templates) for non-equi ON clauses
- Test join-heavy queries against Vitess before rollout
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.