{"record":{"id":"4f72fd3240d7dedb","repo":"vitessio/vitess","slug":"can-t-convert-s-to-decimal-v","errorCode":null,"errorMessage":"can't convert %s to decimal: %v","messagePattern":"can't convert (.+?) to decimal: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/mysql/decimal/scan.go","lineNumber":116,"sourceCode":"\t\t\tneg = true\n\t\t\ts = s[1:]\n\t\t}\n\t}\n\n\tif len(s) == 0 {\n\t\treturn Decimal{}, fmt.Errorf(\"can't convert %q to decimal: too short\", original)\n\t}\n\n\tif len(s) <= 18 {\n\t\tdec, err := parseDecimal64(s)\n\t\tif err == nil {\n\t\t\tif neg {\n\t\t\t\tdec.value.Neg(dec.value)\n\t\t\t}\n\t\t\treturn dec, nil\n\t\t}\n\t\tif err != errOverflow {\n\t\t\treturn Decimal{}, fmt.Errorf(\"can't convert %s to decimal: %v\", original, err)\n\t\t}\n\t}\n\n\tvar fractional, integral []byte\n\tif pIndex := bytes.IndexByte(s, '.'); pIndex >= 0 {\n\t\tif bytes.IndexByte(s[pIndex+1:], '.') != -1 {\n\t\t\treturn Decimal{}, fmt.Errorf(\"can't convert %s to decimal: too many .s\", original)\n\t\t}\n\t\tif pIndex+1 < len(s) {\n\t\t\tintegral = s[:pIndex]\n\t\t\tfractional = s[pIndex+1:]\n\t\t} else {\n\t\t\tintegral = s[:pIndex]\n\t\t}\n\t} else {\n\t\tintegral = s\n\t}\n","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/mysql/decimal/scan.go#L98-L134","documentation":"NewFromMySQL wraps any error from parseDecimal64 that is not errOverflow with this generic message, embedding the original string and the underlying cause (e.g. \"unexpected character\"). It is the catch-all conversion failure for long (>18 char) decimal strings or short strings whose parse failed for a non-overflow reason.","triggerScenarios":"NewFromMySQL receives a decimal string that fails parseDecimal64 with a syntax error (invalid character) rather than overflow — typically because the string contains a character other than digits/dot/sign.","commonSituations":"Non-numeric bytes inside a DECIMAL wire payload; double-encoded text where the payload is a string representation with unexpected characters; corrupted replication or proxy traffic.","solutions":["Read the wrapped %v cause in the message to identify the exact problem character or condition.","Sanitize the input so it contains only optional sign, digits, and at most one dot.","Confirm the sender encodes DECIMAL in MySQL binary format before transmission.","If the value is user text, parse it with a lenient formatter first, then feed clean digits to NewFromMySQL."],"exampleFix":"// before\nNewFromMySQL([]byte(\"12,345.67\")) // comma -> error\n\n// after\ns := strings.ReplaceAll(\"12,345.67\", \",\", \"\")\nNewFromMySQL([]byte(s))","handlingStrategy":"validation","validationCode":"func isPlainDecimal(s string) bool {\n    s = strings.TrimLeft(s, \"+-\")\n    parts := strings.SplitN(s, \".\", 2)\n    for _, p := range parts {\n        if p == \"\" {\n            continue\n        }\n        for _, c := range p {\n            if c < '0' || c > '9' {\n                return false\n            }\n        }\n    }\n    return s != \"\"\n}","typeGuard":null,"tryCatchPattern":"dec, err := decimal.NewFromMySQL(data)\nif err != nil {\n    // the wrapped cause names the bad character; log payload + cause\n    return vterrors.Wrapf(err, \"decimal conversion failed for %q\", data)\n}","preventionTips":["Inspect the wrapped %v cause — it pinpoints the offending character.","Normalize user-formatted numbers (remove separators) before conversion.","Keep a single serialization path for DECIMAL data end to end.","Test with payloads from every producer in your pipeline."],"tags":["mysql","decimal","parsing","wrapped-error"],"backgroundTag":"invalid-decimal-string","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}