vitessio/vitess · error
unreachable
Error message
unreachable
What it means
compareDateAndString compares a temporal value (DATE/DATETIME/TIMESTAMP/TIME) with a string by best-effort parsing the string to a date. The final panic('unreachable') asserts one side is an evalTemporal; the caller (evalCompare dispatch) must guarantee that. It fires if the function is invoked with two non-temporal operands — an internal dispatch invariant, not a user-facing SQL error.
Source
Thrown at go/vt/vtgate/evalengine/compare.go:169
// least compare something and to handle equality checks.
return strings.Compare(l.string, r.string)
}
return 0
}
if l.set < r.set {
return -1
}
return 1
}
func compareDateAndString(l, r eval) int {
if tt, ok := l.(*evalTemporal); ok {
return tt.dt.Compare(r.(*evalBytes).toDateBestEffort())
}
if tt, ok := r.(*evalTemporal); ok {
return l.(*evalBytes).toDateBestEffort().Compare(tt.dt)
}
panic("unreachable")
}
// More on string collations coercibility on MySQL documentation:
// - https://dev.mysql.com/doc/refman/8.0/en/charset-collation-coercibility.html
func compareStrings(l, r eval, env *collations.Environment) (int, error) {
l, r, col, err := mergeAndCoerceCollations(l, r, env)
if err != nil {
return 0, err
}
collation := colldata.Lookup(col.Collation)
if collation == nil {
return 0, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "cannot compare strings, collation is unknown or unsupported (collation ID: %d)", col.Collation)
}
return collation.Collate(l.ToRawBytes(), r.ToRawBytes(), false), nil
}
func compareJSON(l, r eval) (int, error) {
lj, err := argToJSON(l)View on GitHub (pinned to 01a25a7d17)
Solutions
- Check the call site that dispatches to compareDateAndString and ensure it only routes temporal-vs-string comparisons
- Fix the dispatch table in evalCompare (compare.go) so non-temporal pairs go to the numeric/string comparators
- File a Vitess bug with the query that produced the panic if it reproduces on unmodified code
Example fix
// before
return compareDateAndString(l, r)
// after
if isTemporal(l) || isTemporal(r) {
return compareDateAndString(l, r)
}
return compareStrings(l, r, env) Defensive patterns
Strategy: type-guard
Validate before calling
// Go: before calling compareDateAndString
if _, ok := l.(*evalTemporal); !ok {
if _, ok := r.(*evalTemporal); !ok {
// neither side temporal; use compareStrings instead
}
} Type guard
func isTemporalEval(e eval) bool {
_, ok := e.(*evalTemporal)
return ok
} Try / catch
func safeCompareDateAndString(l, r eval) (result int) {
defer func() {
if r := recover(); r != nil {
result = strings.Compare(l.String(), r.String())
}
}()
return compareDateAndString(l, r)
} Prevention
- Route comparisons through a single dispatch function that type-switches once
- Add tests comparing every eval type pair against every other
- Prefer returning vterrors over panics at dispatch boundaries
When it happens
Trigger: A refactor or new comparison path in evalCompare that routes a comparison to compareDateAndString without checking that at least one operand is *evalTemporal, e.g. comparing two strings or two numerics through the temporal branch by mistake.
Common situations: Hitting this after modifying the evalengine's comparison dispatch (compare.go) or adding a new eval type that gets misrouted; end users only see it as a Vitess crash from a specific query.
Related errors
- bad unsigned integer type
- bad type aggregation for signed/unsigned types
- malformed hex literal from parser
- negative stack position
- unhandled case: evalIsTruthy
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/f202f3bad6b56c06.
Report an issue: GitHub.