vitessio/vitess · error
unhandled case: evalIsTruthy
Error message
unhandled case: evalIsTruthy
What it means
evalIsTruthy converts an evaluated value to a boolean for truthiness checks (WHERE clauses, IF, AND/OR). The switch covers all eval types the engine can produce and panics on any other concrete eval implementation. Hitting it means an eval type exists that truthiness conversion was never taught to handle — an engine invariant violation.
Source
Thrown at go/vt/vtgate/evalengine/eval.go:171
}
return makeboolean(hex.u != 0)
}
if e.isBitLiteral() {
bit, ok := e.toNumericBit()
if !ok {
// overflow
return makeboolean(true)
}
return makeboolean(bit.i != 0)
}
f, _ := fastparse.ParseFloat64(e.string())
return makeboolean(f != 0.0)
case *evalJSON:
return makeboolean(e.ToBoolean())
case *evalTemporal:
return makeboolean(!e.isZero())
default:
panic("unhandled case: evalIsTruthy")
}
}
func evalCoerce(e eval, typ sqltypes.Type, size, scale int32, col collations.ID, now time.Time, allowZero bool) (eval, error) {
if e == nil {
return nil, nil
}
if col == collations.Unknown {
panic("EvalResult.coerce with no collation")
}
if typ == sqltypes.VarChar || typ == sqltypes.Char {
// if we have an explicit VARCHAR coercion, always force it so the collation is replaced in the target
return evalToVarchar(e, col, false)
}
if e.SQLType() == typ && e.Size() == size && e.Scale() == scale {
// nothing to be done here
return e, nil
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Add a case for the missing eval type in evalIsTruthy (eval.go:~150) defining its truthiness semantics (mirror MySQL: nonzero numbers true, zero-date temporal false, etc.)
- Search the codebase for other exhaustive switches over eval types (evalCoerce, evalToNumeric, evalConvert_nj) and update them in the same change
- If it occurs on stock Vitess, capture the query and stack trace and file an issue
Example fix
// before
case *evalTemporal:
return makeboolean(!e.isZero())
default:
panic("unhandled case: evalIsTruthy")
// after
case *evalTemporal:
return makeboolean(!e.isZero())
case *evalNewType:
return makeboolean(e.IsTruthy())
default:
panic("unhandled case: evalIsTruthy") Defensive patterns
Strategy: type-guard
Validate before calling
// Go: check the value type before truthiness evaluation
switch e.(type) {
case *evalInt8, *evalInt16, *evalInt32, *evalInt64, *evalUint64,
*evalFloat, *evalDecimal, *evalBytes, *evalJSON, *evalTemporal:
// safe to eval truthiness
default:
return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "no truthiness for %T", e)
} Type guard
func hasTruthySemantics(e eval) bool {
switch e.(type) {
case *evalInt8, *evalInt16, *evalInt32, *evalInt64,
*evalUint64, *evalFloat, *evalDecimal, *evalBytes, *evalJSON, *evalTemporal:
return true
}
return false
} Try / catch
func safeIsTruthy(e eval) (b boolean, err error) {
defer func() {
if r := recover(); r != nil {
err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "truthiness failed: %v", r)
}
}()
return evalIsTruthy(e), nil
} Prevention
- When adding an eval type, grep for `panic("unhandled case"` and update each switch
- Keep a central list of eval types and table-test every exhaustive switch against it
- Validate operand types at plan time so unsupported types never reach runtime evaluation
When it happens
Trigger: A new eval type (e.g. a hypothetical *evalTuple or a newly added temporal/geometry variant) reaches ToBoolean/eval without a case in evalIsTruthy's switch in eval.go; typically after adding a type to the engine without updating all switches.
Common situations: Developers extending the evalengine with new value types; end users see it as a vtgate crash from a specific expression (e.g. a WHERE or CASE using the unhandled type).
Related errors
- bad unsigned integer type
- malformed hex literal from parser
- bad type aggregation for signed/unsigned types
- unreachable
- EvalResult.coerce with no collation
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/b99cae9089b4eeba.
Report an issue: GitHub.