vitessio/vitess · error
unexpected character %q
Error message
unexpected character %q
What it means
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.
Source
Thrown at go/mysql/decimal/scan.go:50
func parseDecimal64(s []byte) (Decimal, error) {
const cutoff = math.MaxUint64/10 + 1
var n uint64
dot := -1
for i, c := range s {
var d byte
switch {
case c == '.':
if dot > -1 {
return Decimal{}, errors.New("too many .s")
}
dot = i
continue
case '0' <= c && c <= '9':
d = c - '0'
default:
return Decimal{}, fmt.Errorf("unexpected character %q", c)
}
if n >= cutoff {
// n*base overflows
return Decimal{}, errOverflow
}
n *= 10
n1 := n + uint64(d)
if n1 < n {
return Decimal{}, errOverflow
}
n = n1
}
var exp int32
if dot != -1 {
exp = -int32(len(s) - dot - 1)
}View on GitHub (pinned to 01a25a7d17)
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.
Example fix
// before
NewFromMySQL([]byte("1 234.5"))
// after
clean := strings.Map(func(r rune) rune { if r == ' ' { return -1 }; return r }, "1 234.5")
NewFromMySQL([]byte(clean)) Defensive patterns
Strategy: validation
Validate before calling
func isCleanDecimal(b []byte) bool {
s := b
if len(s) > 0 && (s[0] == '+' || s[0] == '-') {
s = s[1:]
}
seenDot := false
for _, c := range s {
if c == '.' {
if seenDot {
return false
}
seenDot = true
continue
}
if c < '0' || c > '9' {
return false
}
}
return len(s) > 0
} Try / catch
dec, err := decimal.NewFromMySQL(data)
if err != nil {
return vterrors.Wrapf(err, "cannot parse decimal payload %q", data)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- overflow
- can't convert %q to decimal: too short
- can't convert %s to decimal: %v
- can't convert %s to decimal: too many .s
- invalid decimal string: %q
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/4814a6802aaa200e.
Report an issue: GitHub.