vitessio/vitess · error

metadataLength: unhandled data type: %v

Error message

metadataLength: unhandled data type: %v

What it means

metadataLength computes the byte size of the optional metadata for a column type; for unrecognized types it panics with a vterrors INTERNAL error, since this path is expected only in tests with synthetic types. Valid production binlog types never reach the default branch.

Source

Thrown at go/mysql/binlog_event_rbr.go:154

	case binlog.TypeDecimal, binlog.TypeTiny, binlog.TypeShort, binlog.TypeLong, binlog.TypeNull, binlog.TypeTimestamp, binlog.TypeLongLong, binlog.TypeInt24, binlog.TypeDate, binlog.TypeTime, binlog.TypeDateTime, binlog.TypeYear, binlog.TypeNewDate:
		// No data here.
		return 0

	case binlog.TypeFloat, binlog.TypeDouble, binlog.TypeTimestamp2, binlog.TypeDateTime2, binlog.TypeTime2, binlog.TypeJSON, binlog.TypeTinyBlob, binlog.TypeMediumBlob, binlog.TypeLongBlob, binlog.TypeBlob, binlog.TypeGeometry, binlog.TypeVector:
		// One byte.
		return 1

	case binlog.TypeNewDecimal, binlog.TypeEnum, binlog.TypeSet, binlog.TypeString:
		// Two bytes, Big Endian because of crazy encoding.
		return 2

	case binlog.TypeVarchar, binlog.TypeBit, binlog.TypeVarString:
		// Two bytes, Little Endian
		return 2

	default:
		// Unknown type. This is used in tests only, so panic.
		panic(vterrors.Errorf(vtrpcpb.Code_INTERNAL, "metadataLength: unhandled data type: %v", typ))
	}
}

// metadataTotalLength returns the total size of the metadata for an
// array of types.
func metadataTotalLength(types []byte) int {
	sum := 0
	for _, t := range types {
		sum += metadataLength(t)
	}
	return sum
}

// metadataRead reads a single value from the metadata string.
func metadataRead(data []byte, pos int, typ byte) (uint16, int, error) {
	switch typ {
	case binlog.TypeDecimal, binlog.TypeTiny, binlog.TypeShort, binlog.TypeLong, binlog.TypeNull, binlog.TypeTimestamp, binlog.TypeLongLong, binlog.TypeInt24, binlog.TypeDate, binlog.TypeTime, binlog.TypeDateTime, binlog.TypeYear, binlog.TypeNewDate:
		// No data here.

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Replace the unsupported type byte with a valid binlog.Type* constant.
  2. Add the missing type to metadataLength (and the matching read/write functions) if it is a real new MySQL type.
  3. Sanitize the TableMap before building events, rejecting unknown type codes with a proper error.

Example fix

// before
tm.Types = []byte{0x99} // unknown
ev := mysql.NewTableMapEvent(f, s, id, tm) // panics
// after
tm.Types = []byte{binlog.TypeFloat}
ev := mysql.NewTableMapEvent(f, s, id, tm)
Defensive patterns

Strategy: validation

Validate before calling

for _, typ := range tm.Types {
    if !isKnownBinlogType(typ) {
        return errors.Errorf("unknown column type %d before building table map", typ)
    }
}

Try / catch

func safeMetadataTotal(types []byte) (n int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("metadataLength: %v", r)
        }
    }()
    return mysql.MetadataTotalLength(types), nil
}

Prevention

When it happens

Trigger: Calling NewTableMapEvent (via metadataTotalLength -> metadataLength) with a TableMap.Types entry containing a byte that is not a supported MySQL column type.

Common situations: Unit tests using made-up type bytes; corrupted or truncated binlog data supplying invalid type codes; new upstream MySQL types not yet added to the switch.

Related errors


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