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

  1. Check the call site that dispatches to compareDateAndString and ensure it only routes temporal-vs-string comparisons
  2. Fix the dispatch table in evalCompare (compare.go) so non-temporal pairs go to the numeric/string comparators
  3. 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

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


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