{"record":{"id":"7ad8f4b0c56bb069","repo":"vitessio/vitess","slug":"too-many-s","errorCode":null,"errorMessage":"too many .s","messagePattern":"too many \\.s","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/mysql/decimal/scan.go","lineNumber":43,"sourceCode":"\t\"math/bits\"\n\t\"strings\"\n\n\t\"vitess.io/vitess/go/mysql/fastparse\"\n)\n\nvar errOverflow = errors.New(\"overflow\")\n\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}","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/mysql/decimal/scan.go#L25-L61","documentation":"parseDecimal64 rejects digit strings containing more than one '.' because a decimal literal may have at most one decimal point; a second '.' makes the string invalid MySQL decimal syntax. NewFromMySQL surfaces this when decoding a value from the wire protocol.","triggerScenarios":"parseDecimal64 iterating a byte slice that already recorded a dot position (dot > -1) and encounters another '.' character.","commonSituations":"Corrupted or hand-crafted wire data; upstream code that concatenated numbers (e.g. \"1.2\" + \".3\"); locale formatting where ',' and '.' are mixed; bugs in code building decimal strings manually.","solutions":["Sanitize/normalize the decimal string before parsing: keep only the first '.' and drop or reject subsequent ones","Fix the producer that emits malformed decimals so it never concatenates fractional parts","Validate input with a regex like ^-?\\d*(\\.\\d+)?$ before handing it to the decimal parser"],"exampleFix":"// before\ndec, err := decimal.NewFromMySQL([]byte(\"1.2.3\")) // \"too many .s\"\n// after\ns := strings.Replace(raw, \".\", \"\", 1) // or validate first\ndec, err := decimal.NewFromMySQL([]byte(s))","handlingStrategy":"validation","validationCode":"var decimalRe = regexp.MustCompile(`^-?\\d*(\\.\\d+)?$`)\nif !decimalRe.Match(raw) { return fmt.Errorf(\"invalid decimal %q\", raw) }","typeGuard":"func isWellFormedDecimal(s []byte) bool {\n    return bytes.Count(s, []byte{\".\"}) <= 1\n}","tryCatchPattern":"dec, err := decimal.NewFromMySQL(raw)\nif err != nil && strings.Contains(err.Error(), \"too many .s\") {\n    return normalizeDecimal(raw) // keep only first '.'\n}","preventionTips":["Never build decimal strings by concatenating fractional parts","Normalize locale-formatted numbers ('.' vs ',') before parsing","Validate wire/input data against a decimal regex upstream"],"tags":["decimal","parsing","malformed-input"],"backgroundTag":"malformed-numeric-literal","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}