{"record":{"id":"c3cba6b03f6fabca","repo":"vitessio/vitess","slug":"bad-unsigned-integer-type","errorCode":null,"errorMessage":"bad unsigned integer type","messagePattern":"bad unsigned integer type","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/evalengine/api_type_aggregation.go","lineNumber":197,"sourceCode":"\t\treturn\n\t}\n\tta.total++\n}\n\nfunc nextSignedTypeForUnsigned(t sqltypes.Type) sqltypes.Type {\n\tswitch t {\n\tcase sqltypes.Uint8:\n\t\treturn sqltypes.Int16\n\tcase sqltypes.Uint16:\n\t\treturn sqltypes.Int24\n\tcase sqltypes.Uint24:\n\t\treturn sqltypes.Int32\n\tcase sqltypes.Uint32:\n\t\treturn sqltypes.Int64\n\tcase sqltypes.Uint64:\n\t\treturn sqltypes.Decimal\n\tdefault:\n\t\tpanic(\"bad unsigned integer type\")\n\t}\n}\n\nfunc (ta *typeAggregation) result() sqltypes.Type {\n\t/*\n\t\tIf all types are numeric, the aggregated type is also numeric:\n\t\t\tIf at least one argument is double precision, the result is double precision.\n\t\t\tOtherwise, if at least one argument is DECIMAL, the result is DECIMAL.\n\t\t\tOtherwise, the result is an integer type (with one exception):\n\t\t\t\tIf 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).\n\t\t\t\tIf 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.\n\t\t\t\tThe exception is unsigned BIGINT combined with any signed integer type. The result is DECIMAL with sufficient precision and scale 0.\n\t\tIf all types are BIT, the result is BIT. Otherwise, BIT arguments are treated similar to BIGINT.\n\t\tIf all types are YEAR, the result is YEAR. Otherwise, YEAR arguments are treated similar to INT.\n\t\tIf all types are character string (CHAR or VARCHAR), the result is VARCHAR with maximum length determined by the longest character length of the operands.\n\t\tIf all types are character or binary string, the result is VARBINARY.\n\t\tSET and ENUM are treated similar to VARCHAR; the result is VARCHAR.\n\t\tIf all types are JSON, the result is JSON.","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/evalengine/api_type_aggregation.go#L179-L215","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ncase sqltypes.Uint64:\n\treturn sqltypes.Decimal\ndefault:\n\tpanic(\"bad unsigned integer type\")\n// after\ncase sqltypes.Uint64:\n\treturn sqltypes.Decimal\ncase sqltypes.UintNew: // newly added unsigned type\n\treturn sqltypes.Decimal\ndefault:\n\tpanic(\"bad unsigned integer type\")","handlingStrategy":"type-guard","validationCode":"// Go: before calling\ntyp := ta.unsignedMax\nif !sqltypes.IsUnsigned(typ) {\n\treturn sqltypes.Decimal // or handle explicitly\n}","typeGuard":"func isUnsignedInt(t sqltypes.Type) bool {\n\tswitch t {\n\tcase sqltypes.Uint8, sqltypes.Uint16, sqltypes.Uint24, sqltypes.Uint32, sqltypes.Uint64:\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"// Go: panics are not recoverable per-call; wrap at evaluation boundary\nfunc safeResult(ta *typeAggregation) (t sqltypes.Type, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = vterrors.Errorf(vtrpcpb.Code_INTERNAL, \"type aggregation failed: %v\", r)\n\t\t}\n\t}()\n\treturn ta.result(), nil\n}","preventionTips":["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"],"tags":["go","panic","evalengine","type-system","internal-invariant"],"backgroundTag":"unhandled-type-switch-panic","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}