// Copyright 2016 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. package codec import ( "bytes" "encoding/binary" "github.com/pkg/errors" ) var ( tablePrefix = []byte{'t'} metaPrefix = []byte{'m'} ) const ( signMask uint64 = 0x8000000000000000 encGroupSize = 8 encMarker = byte(0xFF) encPad = byte(0x0) ) // Key represents high-level Key type. type Key []byte // TableID returns the table ID of the key, if the key is not table key, returns 0. func (k Key) TableID() int64 { _, key, err := DecodeBytes(k) if err != nil { // should never happen return 0 } if !bytes.HasPrefix(key, tablePrefix) { return 0 } key = key[len(tablePrefix):] _, tableID, _ := DecodeInt(key) return tableID } // MetaOrTable checks if the key is a meta key or table key. // If the key is a meta key, it returns true and 0. // If the key is a table key, it returns false and table ID. // Otherwise, it returns false and 0. func (k Key) MetaOrTable() (bool, int64) { _, key, err := DecodeBytes(k) if err != nil { return false, 0 } if bytes.HasPrefix(key, metaPrefix) { return true, 0 } if bytes.HasPrefix(key, tablePrefix) { key = key[len(tablePrefix):] _, tableID, _ := DecodeInt(key) return false, tableID } return false, 0 } var pads = make([]byte, encGroupSize) // EncodeBytes guarantees the encoded value is in ascending order for comparison, // encoding with the following rule: // [group1][marker1]...[groupN][markerN] // group is 8 bytes slice which is padding with 0. // marker is `0xFF - padding 0 count` // For example: // [] -> [0, 0, 0, 0, 0, 0, 0, 0, 247] // [1, 2, 3] -> [1, 2, 3, 0, 0, 0, 0, 0, 250] // [1, 2, 3, 0] -> [1, 2, 3, 0, 0, 0, 0, 0, 251] // [1, 2, 3, 4, 5, 6, 7, 8] -> [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 247] // Refer: https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format#memcomparable-format func EncodeBytes(data []byte) Key { // Allocate more space to avoid unnecessary slice growing. // Assume that the byte slice size is about `(len(data) / encGroupSize + 1) * (encGroupSize + 1)` bytes, // that is `(len(data) / 8 + 1) * 9` in our implement. dLen := len(data) result := make([]byte, 0, (dLen/encGroupSize+1)*(encGroupSize+1)) for idx := 0; idx <= dLen; idx += encGroupSize { remain := dLen - idx padCount := 0 if remain >= encGroupSize { result = append(result, data[idx:idx+encGroupSize]...) } else { padCount = encGroupSize - remain result = append(result, data[idx:]...) result = append(result, pads[:padCount]...) } marker := encMarker - byte(padCount) result = append(result, marker) } return result } // DecodeInt decodes value encoded by EncodeInt before. // It returns the leftover un-decoded slice, decoded value if no error. func DecodeInt(b []byte) ([]byte, int64, error) { if len(b) < 8 { return nil, 0, errors.New("insufficient bytes to decode value") } u := binary.BigEndian.Uint64(b[:8]) v := decodeCmpUintToInt(u) b = b[8:] return b, v, nil } func decodeCmpUintToInt(u uint64) int64 { return int64(u ^ signMask) } // DecodeBytes decodes bytes which is encoded by EncodeBytes before, // returns the leftover bytes and decoded value if no error. func DecodeBytes(b []byte) ([]byte, []byte, error) { data := make([]byte, 0, len(b)) for { if len(b) < encGroupSize+1 { return nil, nil, errors.New("insufficient bytes to decode value") } groupBytes := b[:encGroupSize+1] group := groupBytes[:encGroupSize] marker := groupBytes[encGroupSize] padCount := encMarker - marker if padCount > encGroupSize { return nil, nil, errors.Errorf("invalid marker byte, group bytes %q", groupBytes) } realGroupSize := encGroupSize - padCount data = append(data, group[:realGroupSize]...) b = b[encGroupSize+1:] if padCount != 0 { var padByte = encPad // Check validity of padding bytes. for _, v := range group[realGroupSize:] { if v != padByte { return nil, nil, errors.Errorf("invalid padding byte, group bytes %q", groupBytes) } } break } } return b, data, nil }