// This is used for extracting type / length info from string field's metadata. func parseTypeAndLength(metadata []byte) ( fieldType mysql_proto.FieldType_Type, length int, remaining []byte, err error) { if len(metadata) < 2 { return mysql_proto.FieldType_STRING, 0, nil, errors.New( "not enough metadata bytes") } byte1 := int(metadata[0]) byte2 := int(metadata[1]) var realType mysql_proto.FieldType_Type if (byte1 & 0x30) != 0x30 { // see mysql issue #37426 realType = mysql_proto.FieldType_Type(byte1 | 0x30) length = byte2 | (((byte1 & 0x30) ^ 0x30) << 4) } else { realType = mysql_proto.FieldType_Type(byte1) length = byte2 } if realType != mysql_proto.FieldType_SET && realType != mysql_proto.FieldType_ENUM && realType != mysql_proto.FieldType_STRING && realType != mysql_proto.FieldType_VAR_STRING { return mysql_proto.FieldType_STRING, 0, nil, errors.Newf( "Invalid real type: %s (%d)", realType.String(), realType) } return realType, length, metadata[2:], nil }
func (p *TableMapEventParser) parseColumns(t *TableMapEvent) error { numCols := len(t.columnTypesBytes) t.columnDescriptors = make([]ColumnDescriptor, numCols, numCols) metadata := t.metadataBytes nullVector, _, err := readBitArray(t.nullColumnsBytes, numCols) if err != nil { return err } for idx, colTypeByte := range t.columnTypesBytes { colType := mysql_proto.FieldType_Type(colTypeByte) realType := colType metaLength := 0 if colType == mysql_proto.FieldType_STRING || colType == mysql_proto.FieldType_VAR_STRING { realType, metaLength, metadata, err = parseTypeAndLength(metadata) if err != nil { return err } // mysql_proto.FieldType_VAR_STRING is not type polymorphic. if colType == mysql_proto.FieldType_VAR_STRING && colType != realType { return errors.Newf("Invalid real type: %s (%d)", realType.String(), realType) } } var fd FieldDescriptor nullable := NullableColumn(nullVector[idx]) switch realType { case mysql_proto.FieldType_DECIMAL: fd = NewDecimalFieldDescriptor(nullable) case mysql_proto.FieldType_TINY: fd = NewTinyFieldDescriptor(nullable) case mysql_proto.FieldType_SHORT: fd = NewShortFieldDescriptor(nullable) case mysql_proto.FieldType_LONG: fd = NewLongFieldDescriptor(nullable) case mysql_proto.FieldType_FLOAT: fd, metadata, err = NewFloatFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_DOUBLE: fd, metadata, err = NewDoubleFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_NULL: fd = NewNullFieldDescriptor(nullable) case mysql_proto.FieldType_TIMESTAMP: fd = NewTimestampFieldDescriptor(nullable) case mysql_proto.FieldType_LONGLONG: fd = NewLongLongFieldDescriptor(nullable) case mysql_proto.FieldType_INT24: fd = NewInt24FieldDescriptor(nullable) case mysql_proto.FieldType_DATE: return errors.New("TODO") case mysql_proto.FieldType_TIME: return errors.New("TODO") case mysql_proto.FieldType_DATETIME: fd = NewDateTimeFieldDescriptor(nullable) case mysql_proto.FieldType_YEAR: fd = NewYearFieldDescriptor(nullable) case mysql_proto.FieldType_NEWDATE: return errors.New("TODO") case mysql_proto.FieldType_VARCHAR: fd, metadata, err = NewVarcharFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_BIT: fd, metadata, err = NewBitFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_TIMESTAMP2: fd, metadata, err = NewTimestamp2FieldDescriptor(nullable, metadata) case mysql_proto.FieldType_DATETIME2: fd, metadata, err = NewDateTime2FieldDescriptor(nullable, metadata) case mysql_proto.FieldType_TIME2: return errors.New("TODO") case mysql_proto.FieldType_NEWDECIMAL: fd, metadata, err = NewNewDecimalFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_ENUM: return errors.New("Enum type should not appear in binlog") case mysql_proto.FieldType_SET: return errors.New("Set type should not appear in binlog") case mysql_proto.FieldType_TINY_BLOB: return errors.New("Tiny blog type should not appear in binlog") case mysql_proto.FieldType_MEDIUM_BLOB: return errors.New("Medium blog type should not appear in binlog") case mysql_proto.FieldType_LONG_BLOB: return errors.New("Long blog type should not appear in binlog") case mysql_proto.FieldType_BLOB: fd, metadata, err = NewBlobFieldDescriptor(nullable, metadata) case mysql_proto.FieldType_VAR_STRING, mysql_proto.FieldType_STRING: fd = NewStringFieldDescriptor(realType, nullable, metaLength) case mysql_proto.FieldType_GEOMETRY: return errors.New("TODO") default: return errors.Newf("Unknown field type: %d", int(realType)) } if err != nil { return err } t.columnDescriptors[idx] = NewColumnDescriptor(fd, idx) } if len(metadata) != 0 { // sanity check return errors.New("Not all column metadata is consumed") } return nil }