vitessio/vitess · error

unreachable

Error message

unreachable

What it means

Inside evalToDecimal, when converting an evalJSON value, the code switches on the JSON number kind. Any NumberType other than Int/Uint/Float hits the default branch and panics "unreachable". It encodes the assumption that JSON numbers only have those three representations.

Source

Thrown at go/vt/vtgate/evalengine/eval_numeric.go:282

			switch e.NumberType() {
			case json.NumberTypeSigned:
				i, _ := e.Int64()
				return newEvalDecimal(decimal.NewFromInt(i), m, d)
			case json.NumberTypeUnsigned:
				// If the value fits in an unsigned integer, convert to that
				// and then cast it to a signed integer and then turn it into a decimal.
				// SELECT CAST(CAST(18446744073709551615 AS JSON) AS DECIMAL) -> -1
				u, _ := e.Uint64()
				return newEvalDecimal(decimal.NewFromInt(int64(u)), m, d)
			case json.NumberTypeDecimal:
				dec, _ := e.Decimal()
				return newEvalDecimal(dec, m, d)
			case json.NumberTypeFloat:
				f, _ := e.Float64()
				dec := decimal.NewFromFloat(f)
				return newEvalDecimal(dec, m, d)
			default:
				panic("unreachable")
			}
		case json.TypeString:
			dec, _ := decimal.NewFromString(e.Raw())
			return newEvalDecimal(dec, m, d)
		default:
			return newEvalDecimal(decimal.Zero, m, d)
		}
	case *evalTemporal:
		return newEvalDecimal(e.toDecimal(), m, d)
	case *evalEnum:
		return newEvalDecimal(decimal.NewFromInt(enumNumeric(e.value)), m, d)
	case *evalSet:
		return newEvalDecimal(decimal.NewFromUint(e.set), m, d)
	default:
		panic("unsupported")
	}
}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Log/handle the unknown json.NumberType explicitly and treat it like NumberTypeFloat instead of panicking
  2. Check the json package version used; align eval_numeric.go's switch with the current NumberType enum
  3. If reachable via a query, work around by casting the JSON value to a numeric type explicitly in SQL before comparison

Example fix

// before
default:
    panic("unreachable")
// after
default:
    f, _ := e.Float64()
    return newEvalDecimal(decimal.NewFromFloat(f), m, d)
Defensive patterns

Strategy: validation

Validate before calling

nt, _ := jsonVal.Number()
if nt != json.NumberTypeInt && nt != json.NumberTypeUint && nt != json.NumberTypeFloat {
    return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "unsupported JSON number type %v", nt)
}

Type guard

func isKnownJSONNumber(t json.NumberType) bool {
    switch t {
    case json.NumberTypeInt, json.NumberTypeUint, json.NumberTypeFloat:
        return true
    default:
        return false
    }
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "evalToDecimal: %v", r)
    }
}()

Prevention

When it happens

Trigger: evalToDecimal receives an *evalJSON whose Number() returns a number type not in {Int, Uint, Float} — e.g. a new json.NumberType added by a library upgrade, or corrupted JSON state.

Common situations: Vitess/go-vtupgrades where the json library gained a new NumberType; passing malformed JSON documents through decimal comparison paths (compareAllDecimal, integerDivideConvert).

Related errors


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