{"record":{"id":"4814a6802aaa200e","repo":"vitessio/vitess","slug":"unexpected-character-q","errorCode":null,"errorMessage":"unexpected character %q","messagePattern":"unexpected character %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/mysql/decimal/scan.go","lineNumber":50,"sourceCode":"\nfunc parseDecimal64(s []byte) (Decimal, error) {\n\tconst cutoff = math.MaxUint64/10 + 1\n\tvar n uint64\n\tdot := -1\n\n\tfor i, c := range s {\n\t\tvar d byte\n\t\tswitch {\n\t\tcase c == '.':\n\t\t\tif dot > -1 {\n\t\t\t\treturn Decimal{}, errors.New(\"too many .s\")\n\t\t\t}\n\t\t\tdot = i\n\t\t\tcontinue\n\t\tcase '0' <= c && c <= '9':\n\t\t\td = c - '0'\n\t\tdefault:\n\t\t\treturn Decimal{}, fmt.Errorf(\"unexpected character %q\", c)\n\t\t}\n\n\t\tif n >= cutoff {\n\t\t\t// n*base overflows\n\t\t\treturn Decimal{}, errOverflow\n\t\t}\n\t\tn *= 10\n\t\tn1 := n + uint64(d)\n\t\tif n1 < n {\n\t\t\treturn Decimal{}, errOverflow\n\t\t}\n\t\tn = n1\n\t}\n\n\tvar exp int32\n\tif dot != -1 {\n\t\texp = -int32(len(s) - dot - 1)\n\t}","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/mysql/decimal/scan.go#L32-L68","documentation":"parseDecimal64 parses short (<=18 digit) decimal strings for NewFromMySQL. It only accepts digits, a single dot, and a leading sign; any other character (letter, space, second sign, etc.) aborts parsing with this error naming the offending character.","triggerScenarios":"NewFromMySQL receives a wire-format MySQL binary decimal string containing a character outside [0-9.] (after optional leading '-'/'+' handled by the caller) while the string is <=18 chars, so it routes into parseDecimal64.","commonSituations":"Corrupted or non-standard wire data from a misbehaving proxy or older/incompatible MySQL server; a caller passing a human-formatted string (with spaces, thousands separators, or exponent notation) directly to NewFromMySQL instead of clean binary decimal bytes.","solutions":["Inspect the input string (it is quoted in the error) and strip or fix the invalid character.","Ensure the bytes come from MySQL's binary DECIMAL wire encoding or a clean decimal literal — no spaces, commas, or exponent notation.","If parsing user text, convert with a lenient parser (strings.TrimSpace, remove separators) before calling NewFromMySQL.","Verify the upstream sender/proxy is not mangling the decimal payload."],"exampleFix":"// before\nNewFromMySQL([]byte(\"1 234.5\"))\n\n// after\nclean := strings.Map(func(r rune) rune { if r == ' ' { return -1 }; return r }, \"1 234.5\")\nNewFromMySQL([]byte(clean))","handlingStrategy":"validation","validationCode":"func isCleanDecimal(b []byte) bool {\n    s := b\n    if len(s) > 0 && (s[0] == '+' || s[0] == '-') {\n        s = s[1:]\n    }\n    seenDot := false\n    for _, c := range s {\n        if c == '.' {\n            if seenDot {\n                return false\n            }\n            seenDot = true\n            continue\n        }\n        if c < '0' || c > '9' {\n            return false\n        }\n    }\n    return len(s) > 0\n}","typeGuard":null,"tryCatchPattern":"dec, err := decimal.NewFromMySQL(data)\nif err != nil {\n    return vterrors.Wrapf(err, \"cannot parse decimal payload %q\", data)\n}","preventionTips":["Only feed NewFromMySQL bytes produced by MySQL's binary DECIMAL encoding.","Strip formatting characters (spaces, commas) from user text before conversion.","Log the raw payload (it is quoted in the error) when this fires to identify the corrupting producer.","Add fuzz/roundtrip tests if you serialize decimals yourself."],"tags":["mysql","decimal","parsing","invalid-input"],"backgroundTag":"invalid-decimal-string","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}