{"record":{"id":"27ca695522f99ead","repo":"vitessio/vitess","slug":"bad-type-aggregation-for-signed-unsigned-types","errorCode":null,"errorMessage":"bad type aggregation for signed/unsigned types","messagePattern":"bad type aggregation for signed/unsigned types","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/evalengine/api_type_aggregation.go","lineNumber":255,"sourceCode":"\t\t\tta.signedMax = sqltypes.Int32\n\t\t}\n\t}\n\n\tif ta.double+ta.decimal+ta.signed+ta.unsigned == ta.total {\n\t\tif ta.double > 0 {\n\t\t\treturn sqltypes.Float64\n\t\t}\n\t\tif ta.decimal > 0 {\n\t\t\treturn sqltypes.Decimal\n\t\t}\n\t\tif ta.signed == ta.total {\n\t\t\treturn ta.signedMax\n\t\t}\n\t\tif ta.unsigned == ta.total {\n\t\t\treturn ta.unsignedMax\n\t\t}\n\t\tif ta.signed == 0 {\n\t\t\tpanic(\"bad type aggregation for signed/unsigned types\")\n\t\t}\n\t\tagtype := nextSignedTypeForUnsigned(ta.unsignedMax)\n\t\tif sqltypes.IsSigned(agtype) {\n\t\t\treturn max(agtype, ta.signedMax)\n\t\t}\n\t\treturn agtype\n\t}\n\n\tif ta.char == ta.total {\n\t\treturn sqltypes.VarChar\n\t}\n\tif ta.char+ta.binary == ta.total {\n\t\t// HACK: this is not in the official documentation, but groups of strings where\n\t\t// one of the strings is not directly a VARCHAR or VARBINARY (e.g. a hex literal,\n\t\t// or a VARCHAR that has been explicitly collated) will result in VARCHAR when\n\t\t// aggregated\n\t\tif ta.charother > 0 {\n\t\t\treturn sqltypes.VarChar","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/evalengine/api_type_aggregation.go#L237-L273","documentation":"typeAggregation.result computes the aggregated type when all operands are numeric. After handling all-signed and all-unsigned cases, if ta.signed == 0 the state is inconsistent (a numeric mix with zero signed operands already fell into the all-unsigned branch), so it panics. This is a defensive invariant guard mirroring MySQL's mixed signed/unsigned aggregation rules and should be unreachable for well-formed aggregation state.","triggerScenarios":"Mixed numeric aggregation (e.g. `unsigned_col = signed_col`, arithmetic in SELECT lists) where the aggregation counters got corrupted — e.g. a type counted as unsigned in ta.unsigned but its max type ta.unsignedMax was not set to an unsigned type, or new type-introduction code double-counts/mis-counts operands.","commonSituations":"Developers extending the evalengine with new types or changing type classification; not reachable by end users through normal SQL on supported Vitess types.","solutions":["Audit how ta.unsigned/ta.signed/ta.unsignedMax are updated in aggregate() (api_type_aggregation.go) for the types in your query and fix the miscount","Add a unit test in the evalengine package reproducing the type combination to pin the aggregation result","Report the query and schema to the Vitess project with the panic stack trace if it occurs on unmodified code"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Go: sanity-check aggregation state before result()\nif ta.double+ta.decimal+ta.signed+ta.unsigned == ta.total &&\n\tta.signed == 0 && ta.unsigned != ta.total {\n\t// inconsistent state, do not call result()\n\treturn sqltypes.VarChar, nil\n}","typeGuard":"func aggregationStateValid(ta *typeAggregation) bool {\n\treturn ta.unsigned == 0 || ta.unsignedMax != 0\n}","tryCatchPattern":"func (ta *typeAggregation) safeResult() (t sqltypes.Type, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = vterrors.Errorf(vtrpcpb.Code_INTERNAL, \"bad aggregation: %v\", r)\n\t\t}\n\t}()\n\treturn ta.result(), nil\n}","preventionTips":["Unit-test typeAggregation with mixed signed/unsigned combinations covering every integer width","Assert ta.unsignedMax is set whenever ta.unsigned > 0 in aggregate()","Keep MySQL's aggregation table (docs in result()) as the test oracle"],"tags":["go","panic","evalengine","type-aggregation","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"}