vitessio/vitess · error

can't convert %s 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.

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)

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.