vitessio/vitess · error

can't convert to decimal: too many .s

Error message

can't convert %s to decimal: too many .s

What it means

Returned by decimal.Scan conversion helpers when the input string contains more than one '.' character, so it cannot be interpreted as a decimal literal.

Solutions

  1. Inspect the quoted string in the error and fix the producer so only one dot is emitted.
  2. If joining integral/fractional parts programmatically, trim dots from the parts before concatenation.
  3. Validate the payload with a single-dot check before calling NewFromMySQL to fail earlier with better context.

Example fix

// before
payload := integral + "." + fractional // fractional = "2.3"
NewFromMySQL([]byte(payload))

// after
fractional = strings.ReplaceAll(fractional, ".", "")
payload := integral + "." + fractional
Defensive patterns

Strategy: validation

Validate before calling

func hasSingleDot(s []byte) bool {
    return bytes.Count(s, []byte(".")) <= 1
}
// before calling:
// if !hasSingleDot(data) { return error }

Try / catch

dec, err := decimal.NewFromMySQL(data)
if err != nil {
    if strings.Contains(err.Error(), "too many .s") {
        return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "malformed decimal %q", data)
    }
    return vterrors.Wrapf(err, "decimal conversion failed")
}

Prevention

When it happens

Trigger: Calling NewFromMySQL with a payload like "1.2.3" — bytes.IndexByte finds the first dot, then a second dot in the remainder triggers the rejection.

Common situations: Concatenation bugs when building decimal strings (e.g. joining integral and fractional parts that already contain dots); corrupted wire data; callers passing formatted output like "1.23.45" from other systems.

Related errors


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

Appendix: source

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

	}

	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
	}

	// Check if the size of this bigint would fit in the limits
	// that MySQL has by default. To do that, we must convert the
	// length of our integral and fractional part to "mysql digits"
	myintg := myBigDigits(int32(len(integral)))
	myfrac := myBigDigits(int32(len(fractional)))
	if myintg > MyMaxBigDigits {
		return largestForm(MyMaxPrecision, 0, neg), nil

View on GitHub (pinned to 01a25a7d17)