vitessio/vitess · error

can't convert %s to decimal: %v

Error message

can't convert %s to decimal: %v

What it means

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.

Source

Thrown at go/mysql/decimal/scan.go:116

			neg = true
			s = s[1:]
		}
	}

	if len(s) == 0 {
		return Decimal{}, fmt.Errorf("can't convert %q to decimal: too short", original)
	}

	if len(s) <= 18 {
		dec, err := parseDecimal64(s)
		if err == nil {
			if neg {
				dec.value.Neg(dec.value)
			}
			return dec, nil
		}
		if err != errOverflow {
			return Decimal{}, fmt.Errorf("can't convert %s to decimal: %v", original, err)
		}
	}

	var fractional, integral []byte
	if pIndex := bytes.IndexByte(s, '.'); pIndex >= 0 {
		if bytes.IndexByte(s[pIndex+1:], '.') != -1 {
			return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", original)
		}
		if pIndex+1 < len(s) {
			integral = s[:pIndex]
			fractional = s[pIndex+1:]
		} else {
			integral = s[:pIndex]
		}
	} else {
		integral = s
	}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Read the wrapped %v cause in the message to identify the exact problem character or condition.
  2. Sanitize the input so it contains only optional sign, digits, and at most one dot.
  3. Confirm the sender encodes DECIMAL in MySQL binary format before transmission.
  4. If the value is user text, parse it with a lenient formatter first, then feed clean digits to NewFromMySQL.

Example fix

// before
NewFromMySQL([]byte("12,345.67")) // comma -> error

// after
s := strings.ReplaceAll("12,345.67", ",", "")
NewFromMySQL([]byte(s))
Defensive patterns

Strategy: validation

Validate before calling

func isPlainDecimal(s string) bool {
    s = strings.TrimLeft(s, "+-")
    parts := strings.SplitN(s, ".", 2)
    for _, p := range parts {
        if p == "" {
            continue
        }
        for _, c := range p {
            if c < '0' || c > '9' {
                return false
            }
        }
    }
    return s != ""
}

Try / catch

dec, err := decimal.NewFromMySQL(data)
if err != nil {
    // the wrapped cause names the bad character; log payload + cause
    return vterrors.Wrapf(err, "decimal conversion failed for %q", data)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/4f72fd3240d7dedb. Report an issue: GitHub.