vitessio/vitess · error
bad unsigned integer type
Error message
bad unsigned integer type
What it means
nextSignedTypeForUnsigned maps an unsigned integer type to the next-wider signed type (Uint8->Int16 ... Uint32->Int64, Uint64->Decimal) when aggregating mixed signed/unsigned numeric types. It panics on any input that is not one of the five unsigned MySQL integer types. This is an internal invariant guard: the only caller (typeAggregation.result) should always pass ta.unsignedMax, which is only ever set from unsigned types, so this panic indicates a broken new type was wired into the aggregation without updating this switch.
Source
Thrown at go/vt/vtgate/evalengine/api_type_aggregation.go:197
return
}
ta.total++
}
func nextSignedTypeForUnsigned(t sqltypes.Type) sqltypes.Type {
switch t {
case sqltypes.Uint8:
return sqltypes.Int16
case sqltypes.Uint16:
return sqltypes.Int24
case sqltypes.Uint24:
return sqltypes.Int32
case sqltypes.Uint32:
return sqltypes.Int64
case sqltypes.Uint64:
return sqltypes.Decimal
default:
panic("bad unsigned integer type")
}
}
func (ta *typeAggregation) result() sqltypes.Type {
/*
If all types are numeric, the aggregated type is also numeric:
If at least one argument is double precision, the result is double precision.
Otherwise, if at least one argument is DECIMAL, the result is DECIMAL.
Otherwise, the result is an integer type (with one exception):
If all integer types are all signed or all unsigned, the result is the same sign and the precision is the highest of all specified integer types (that is, TINYINT, SMALLINT, MEDIUMINT, INT, or BIGINT).
If there is a combination of signed and unsigned integer types, the result is signed and the precision may be higher. For example, if the types are signed INT and unsigned INT, the result is signed BIGINT.
The exception is unsigned BIGINT combined with any signed integer type. The result is DECIMAL with sufficient precision and scale 0.
If all types are BIT, the result is BIT. Otherwise, BIT arguments are treated similar to BIGINT.
If all types are YEAR, the result is YEAR. Otherwise, YEAR arguments are treated similar to INT.
If all types are character string (CHAR or VARCHAR), the result is VARCHAR with maximum length determined by the longest character length of the operands.
If all types are character or binary string, the result is VARBINARY.
SET and ENUM are treated similar to VARCHAR; the result is VARCHAR.
If all types are JSON, the result is JSON.View on GitHub (pinned to 01a25a7d17)
Solutions
- Add the missing unsigned type as a case in nextSignedTypeForUnsigned (go/vt/vtgate/evalengine/api_type_aggregation.go:184) mapping it to the correct wider signed type
- Verify the new type is correctly classified in typeAggregation.aggregate so it only reaches here as a genuine unsigned integer
- Capture the full panic stack trace and file a Vitess issue with the query and column types that triggered it
Example fix
// before
case sqltypes.Uint64:
return sqltypes.Decimal
default:
panic("bad unsigned integer type")
// after
case sqltypes.Uint64:
return sqltypes.Decimal
case sqltypes.UintNew: // newly added unsigned type
return sqltypes.Decimal
default:
panic("bad unsigned integer type") Defensive patterns
Strategy: type-guard
Validate before calling
// Go: before calling
typ := ta.unsignedMax
if !sqltypes.IsUnsigned(typ) {
return sqltypes.Decimal // or handle explicitly
} Type guard
func isUnsignedInt(t sqltypes.Type) bool {
switch t {
case sqltypes.Uint8, sqltypes.Uint16, sqltypes.Uint24, sqltypes.Uint32, sqltypes.Uint64:
return true
}
return false
} Try / catch
// Go: panics are not recoverable per-call; wrap at evaluation boundary
func safeResult(ta *typeAggregation) (t sqltypes.Type, err error) {
defer func() {
if r := recover(); r != nil {
err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "type aggregation failed: %v", r)
}
}()
return ta.result(), nil
} Prevention
- When adding any new sqltypes.Type, grep evalengine for exhaustive switches and update all of them
- Add a table-driven test over all unsigned types through nextSignedTypeForUnsigned
- Keep type classification (aggregate()) and type mapping (nextSignedTypeForUnsigned) in lockstep in the same PR
When it happens
Trigger: A developer adds a new sqltypes.Type that is classified as unsigned in typeAggregation.aggregate (incrementing ta.unsigned) but forgets to add a case to nextSignedTypeForUnsigned; or the function is called with a non-unsigned type directly. Not reachable from user SQL queries on supported types.
Common situations: Contributing a new numeric type to the evalengine (e.g. a new MySQL integer variant or a custom type) and type-aggregating it in expressions like `unsigned_col + signed_col`; upgrading Vitess where a type's classification changed.
Related errors
- bad type aggregation for signed/unsigned types
- unreachable
- unhandled case: evalIsTruthy
- malformed hex literal from parser
- negative stack position
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/c3cba6b03f6fabca.
Report an issue: GitHub.