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

  1. 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.)
  2. Search the codebase for other exhaustive switches over eval types (evalCoerce, evalToNumeric, evalConvert_nj) and update them in the same change
  3. 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 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


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