tidb json_binary 源码

  • 2022-09-19
  • 浏览 (287)

tidb json_binary 代码

文件路径:/types/json_binary.go

// Copyright 2017 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"math"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/parser/mysql"
	"github.com/pingcap/tidb/parser/terror"
	"github.com/pingcap/tidb/util/hack"
	"golang.org/x/exp/slices"
)

/*
   The binary JSON format from MySQL 5.7 is as follows:

   JSON doc ::= type value
   type ::=
       0x01 |       // large JSON object
       0x03 |       // large JSON array
       0x04 |       // literal (true/false/null)
       0x05 |       // int16
       0x06 |       // uint16
       0x07 |       // int32
       0x08 |       // uint32
       0x09 |       // int64
       0x0a |       // uint64
       0x0b |       // double
       0x0c |       // utf8mb4 string
       0x0d |       // opaque value
       0x0e |       // date
       0x0f |       // datetime
       0x10 |       // timestamp
       0x11 |       // time

   value ::=
       object  |
       array   |
       literal |
       number  |
       string  |
       opaque  |
       time    |
       duration |

   object ::= element-count size key-entry* value-entry* key* value*

   array ::= element-count size value-entry* value*

   // number of members in object or number of elements in array
   element-count ::= uint32

   // number of bytes in the binary representation of the object or array
   size ::= uint32

   key-entry ::= key-offset key-length

   key-offset ::= uint32

   key-length ::= uint16    // key length must be less than 64KB

   value-entry ::= type offset-or-inlined-value

   // This field holds either the offset to where the value is stored,
   // or the value itself if it is small enough to be inlined (that is,
   // if it is a JSON literal or a small enough [u]int).
   offset-or-inlined-value ::= uint32

   key ::= utf8mb4-data

   literal ::=
       0x00 |   // JSON null literal
       0x01 |   // JSON true literal
       0x02 |   // JSON false literal

   number ::=  ....    // little-endian format for [u]int(16|32|64), whereas
                       // double is stored in a platform-independent, eight-byte
                       // format using float8store()

   string ::= data-length utf8mb4-data

   data-length ::= uint8*    // If the high bit of a byte is 1, the length
                             // field is continued in the next byte,
                             // otherwise it is the last byte of the length
                             // field. So we need 1 byte to represent
                             // lengths up to 127, 2 bytes to represent
                             // lengths up to 16383, and so on...

   opaque ::= typeId data-length byte*

   time ::= uint64

   duration ::= uint64 uint32

   typeId ::= byte
*/

var jsonZero = CreateBinaryJSON(uint64(0))

const maxJSONDepth = 100

// BinaryJSON represents a binary encoded JSON object.
// It can be randomly accessed without deserialization.
type BinaryJSON struct {
	TypeCode JSONTypeCode
	Value    []byte
}

// String implements fmt.Stringer interface.
func (bj BinaryJSON) String() string {
	out, err := bj.MarshalJSON()
	terror.Log(err)
	return string(out)
}

// Copy makes a copy of the BinaryJSON
func (bj BinaryJSON) Copy() BinaryJSON {
	buf := make([]byte, len(bj.Value))
	copy(buf, bj.Value)
	return BinaryJSON{TypeCode: bj.TypeCode, Value: buf}
}

// MarshalJSON implements the json.Marshaler interface.
func (bj BinaryJSON) MarshalJSON() ([]byte, error) {
	buf := make([]byte, 0, len(bj.Value)*3/2)
	return bj.marshalTo(buf)
}

func (bj BinaryJSON) marshalTo(buf []byte) ([]byte, error) {
	switch bj.TypeCode {
	case JSONTypeCodeOpaque:
		return jsonMarshalOpaqueTo(buf, bj.GetOpaque()), nil
	case JSONTypeCodeString:
		return jsonMarshalStringTo(buf, bj.GetString()), nil
	case JSONTypeCodeLiteral:
		return jsonMarshalLiteralTo(buf, bj.Value[0]), nil
	case JSONTypeCodeInt64:
		return strconv.AppendInt(buf, bj.GetInt64(), 10), nil
	case JSONTypeCodeUint64:
		return strconv.AppendUint(buf, bj.GetUint64(), 10), nil
	case JSONTypeCodeFloat64:
		return bj.marshalFloat64To(buf)
	case JSONTypeCodeArray:
		return bj.marshalArrayTo(buf)
	case JSONTypeCodeObject:
		return bj.marshalObjTo(buf)
	case JSONTypeCodeDate, JSONTypeCodeDatetime, JSONTypeCodeTimestamp:
		return jsonMarshalTimeTo(buf, bj.GetTime()), nil
	case JSONTypeCodeDuration:
		return jsonMarshalDurationTo(buf, bj.GetDuration()), nil
	}
	return buf, nil
}

// IsZero return a boolean indicate whether BinaryJSON is Zero
func (bj BinaryJSON) IsZero() bool {
	// This behavior is different on MySQL 5.7 and 8.0
	//
	// In MySQL 5.7, most of these non-integer values are 0, and return a warning:
	// "Invalid JSON value for CAST to INTEGER from column j"
	//
	// In MySQL 8, most of these non-integer values are not zero, with a warning:
	// > "Evaluating a JSON value in SQL boolean context does an implicit comparison
	// > against JSON integer 0; if this is not what you want, consider converting
	// > JSON to a SQL numeric type with JSON_VALUE RETURNING"
	//
	// TODO: return a warning as MySQL 8 does

	return CompareBinaryJSON(bj, jsonZero) == 0
}

// GetInt64 gets the int64 value.
func (bj BinaryJSON) GetInt64() int64 {
	return int64(jsonEndian.Uint64(bj.Value))
}

// GetUint64 gets the uint64 value.
func (bj BinaryJSON) GetUint64() uint64 {
	return jsonEndian.Uint64(bj.Value)
}

// GetFloat64 gets the float64 value.
func (bj BinaryJSON) GetFloat64() float64 {
	return math.Float64frombits(bj.GetUint64())
}

// GetString gets the string value.
func (bj BinaryJSON) GetString() []byte {
	strLen, lenLen := binary.Uvarint(bj.Value)
	return bj.Value[lenLen : lenLen+int(strLen)]
}

// Opaque represents a raw binary type
type Opaque struct {
	// TypeCode is the same with database type code
	TypeCode byte
	// Buf is the underlying bytes of the data
	Buf []byte
}

// GetOpaque gets the opaque value
func (bj BinaryJSON) GetOpaque() Opaque {
	typ := bj.Value[0]

	strLen, lenLen := binary.Uvarint(bj.Value[1:])
	bufStart := lenLen + 1
	return Opaque{
		TypeCode: typ,
		Buf:      bj.Value[bufStart : bufStart+int(strLen)],
	}
}

// GetTime gets the time value
func (bj BinaryJSON) GetTime() Time {
	coreTime := CoreTime(bj.GetUint64())

	tp := mysql.TypeDate
	if bj.TypeCode == JSONTypeCodeDatetime {
		tp = mysql.TypeDatetime
	} else if bj.TypeCode == JSONTypeCodeTimestamp {
		tp = mysql.TypeTimestamp
	}

	return NewTime(coreTime, tp, DefaultFsp)
}

// GetDuration gets the duration value
func (bj BinaryJSON) GetDuration() Duration {
	return Duration{
		time.Duration(bj.GetInt64()),
		int(jsonEndian.Uint32(bj.Value[8:])),
	}
}

// GetOpaqueFieldType returns the type of opaque value
func (bj BinaryJSON) GetOpaqueFieldType() byte {
	return bj.Value[0]
}

// GetKeys gets the keys of the object
func (bj BinaryJSON) GetKeys() BinaryJSON {
	count := bj.GetElemCount()
	ret := make([]BinaryJSON, 0, count)
	for i := 0; i < count; i++ {
		ret = append(ret, CreateBinaryJSON(string(bj.objectGetKey(i))))
	}
	return buildBinaryJSONArray(ret)
}

// GetElemCount gets the count of Object or Array.
func (bj BinaryJSON) GetElemCount() int {
	return int(jsonEndian.Uint32(bj.Value))
}

func (bj BinaryJSON) arrayGetElem(idx int) BinaryJSON {
	return bj.valEntryGet(headerSize + idx*valEntrySize)
}

func (bj BinaryJSON) objectGetKey(i int) []byte {
	keyOff := int(jsonEndian.Uint32(bj.Value[headerSize+i*keyEntrySize:]))
	keyLen := int(jsonEndian.Uint16(bj.Value[headerSize+i*keyEntrySize+keyLenOff:]))
	return bj.Value[keyOff : keyOff+keyLen]
}

func (bj BinaryJSON) objectGetVal(i int) BinaryJSON {
	elemCount := bj.GetElemCount()
	return bj.valEntryGet(headerSize + elemCount*keyEntrySize + i*valEntrySize)
}

func (bj BinaryJSON) valEntryGet(valEntryOff int) BinaryJSON {
	tpCode := bj.Value[valEntryOff]
	valOff := jsonEndian.Uint32(bj.Value[valEntryOff+valTypeSize:])
	switch tpCode {
	case JSONTypeCodeLiteral:
		return BinaryJSON{TypeCode: JSONTypeCodeLiteral, Value: bj.Value[valEntryOff+valTypeSize : valEntryOff+valTypeSize+1]}
	case JSONTypeCodeUint64, JSONTypeCodeInt64, JSONTypeCodeFloat64:
		return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+8]}
	case JSONTypeCodeString:
		strLen, lenLen := binary.Uvarint(bj.Value[valOff:])
		totalLen := uint32(lenLen) + uint32(strLen)
		return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+totalLen]}
	case JSONTypeCodeOpaque:
		strLen, lenLen := binary.Uvarint(bj.Value[valOff+1:])
		totalLen := 1 + uint32(lenLen) + uint32(strLen)
		return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+totalLen]}
	case JSONTypeCodeDate, JSONTypeCodeDatetime, JSONTypeCodeTimestamp:
		return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+8]}
	case JSONTypeCodeDuration:
		return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+12]}
	}
	dataSize := jsonEndian.Uint32(bj.Value[valOff+dataSizeOff:])
	return BinaryJSON{TypeCode: tpCode, Value: bj.Value[valOff : valOff+dataSize]}
}

func (bj BinaryJSON) marshalFloat64To(buf []byte) ([]byte, error) {
	// NOTE: copied from Go standard library.
	f := bj.GetFloat64()
	if math.IsInf(f, 0) || math.IsNaN(f) {
		return buf, &json.UnsupportedValueError{Str: strconv.FormatFloat(f, 'g', -1, 64)}
	}

	// Convert as if by ES6 number to string conversion.
	// This matches most other JSON generators.
	// See golang.org/issue/6384 and golang.org/issue/14135.
	// Like fmt %g, but the exponent cutoffs are different
	// and exponents themselves are not padded to two digits.
	abs := math.Abs(f)
	ffmt := byte('f')
	// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
	if abs != 0 {
		if abs < 1e-6 || abs >= 1e21 {
			ffmt = 'e'
		}
	}
	buf = strconv.AppendFloat(buf, f, ffmt, -1, 64)
	if ffmt == 'e' {
		// clean up e-09 to e-9
		n := len(buf)
		if n >= 4 && buf[n-4] == 'e' && buf[n-3] == '-' && buf[n-2] == '0' {
			buf[n-2] = buf[n-1]
			buf = buf[:n-1]
		}
	}
	return buf, nil
}

func (bj BinaryJSON) marshalArrayTo(buf []byte) ([]byte, error) {
	elemCount := int(jsonEndian.Uint32(bj.Value))
	buf = append(buf, '[')
	for i := 0; i < elemCount; i++ {
		if i != 0 {
			buf = append(buf, ", "...)
		}
		var err error
		buf, err = bj.arrayGetElem(i).marshalTo(buf)
		if err != nil {
			return nil, errors.Trace(err)
		}
	}
	return append(buf, ']'), nil
}

func (bj BinaryJSON) marshalObjTo(buf []byte) ([]byte, error) {
	elemCount := int(jsonEndian.Uint32(bj.Value))
	buf = append(buf, '{')
	for i := 0; i < elemCount; i++ {
		if i != 0 {
			buf = append(buf, ", "...)
		}
		buf = jsonMarshalStringTo(buf, bj.objectGetKey(i))
		buf = append(buf, ": "...)
		var err error
		buf, err = bj.objectGetVal(i).marshalTo(buf)
		if err != nil {
			return nil, errors.Trace(err)
		}
	}
	return append(buf, '}'), nil
}

func jsonMarshalStringTo(buf, s []byte) []byte {
	// NOTE: copied from Go standard library.
	// NOTE: keep in sync with string above.
	buf = append(buf, '"')
	start := 0
	for i := 0; i < len(s); {
		if b := s[i]; b < utf8.RuneSelf {
			if jsonSafeSet[b] {
				i++
				continue
			}
			if start < i {
				buf = append(buf, s[start:i]...)
			}
			switch b {
			case '\\', '"':
				buf = append(buf, '\\', b)
			case '\n':
				buf = append(buf, '\\', 'n')
			case '\r':
				buf = append(buf, '\\', 'r')
			case '\t':
				buf = append(buf, '\\', 't')
			default:
				// This encodes bytes < 0x20 except for \t, \n and \r.
				// If escapeHTML is set, it also escapes <, >, and &
				// because they can lead to security holes when
				// user-controlled strings are rendered into JSON
				// and served to some browsers.
				buf = append(buf, `\u00`...)
				buf = append(buf, jsonHexChars[b>>4], jsonHexChars[b&0xF])
			}
			i++
			start = i
			continue
		}
		c, size := utf8.DecodeRune(s[i:])
		if c == utf8.RuneError && size == 1 {
			if start < i {
				buf = append(buf, s[start:i]...)
			}
			buf = append(buf, `\ufffd`...)
			i += size
			start = i
			continue
		}
		// U+2028 is LINE SEPARATOR.
		// U+2029 is PARAGRAPH SEPARATOR.
		// They are both technically valid characters in JSON strings,
		// but don't work in JSONP, which has to be evaluated as JavaScript,
		// and can lead to security holes there. It is valid JSON to
		// escape them, so we do so unconditionally.
		// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
		if c == '\u2028' || c == '\u2029' {
			if start < i {
				buf = append(buf, s[start:i]...)
			}
			buf = append(buf, `\u202`...)
			buf = append(buf, jsonHexChars[c&0xF])
			i += size
			start = i
			continue
		}
		i += size
	}
	if start < len(s) {
		buf = append(buf, s[start:]...)
	}
	buf = append(buf, '"')
	return buf
}

// opaque value will yield "base64:typeXX:<base64 encoded string>"
func jsonMarshalOpaqueTo(buf []byte, opaque Opaque) []byte {
	b64 := base64.StdEncoding.EncodeToString(opaque.Buf)
	output := fmt.Sprintf(`"base64:type%d:%s"`, opaque.TypeCode, b64)

	// as the base64 string is simple and predictable, it could be appended
	// to the buf directly.
	buf = append(buf, output...)
	return buf
}

func jsonMarshalLiteralTo(b []byte, litType byte) []byte {
	switch litType {
	case JSONLiteralFalse:
		return append(b, "false"...)
	case JSONLiteralTrue:
		return append(b, "true"...)
	case JSONLiteralNil:
		return append(b, "null"...)
	}
	return b
}

func jsonMarshalTimeTo(buf []byte, time Time) []byte {
	// printing json datetime/duration will always keep 6 fsp
	time.SetFsp(6)
	buf = append(buf, []byte(quoteJSONString(time.String()))...)
	return buf
}

func jsonMarshalDurationTo(buf []byte, duration Duration) []byte {
	// printing json datetime/duration will always keep 6 fsp
	duration.Fsp = 6
	buf = append(buf, []byte(quoteJSONString(duration.String()))...)
	return buf
}

// ParseBinaryJSONFromString parses a json from string.
func ParseBinaryJSONFromString(s string) (bj BinaryJSON, err error) {
	if len(s) == 0 {
		err = ErrInvalidJSONText.GenWithStackByArgs("The document is empty")
		return
	}
	data := hack.Slice(s)
	if !json.Valid(data) {
		err = ErrInvalidJSONText.GenWithStackByArgs("The document root must not be followed by other values.")
		return
	}
	if err = bj.UnmarshalJSON(data); err != nil && !ErrJSONObjectKeyTooLong.Equal(err) {
		err = ErrInvalidJSONText.GenWithStackByArgs(err)
	}
	return
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (bj *BinaryJSON) UnmarshalJSON(data []byte) error {
	var decoder = json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()
	var in interface{}
	err := decoder.Decode(&in)
	if err != nil {
		return errors.Trace(err)
	}
	newBj, err := CreateBinaryJSONWithCheck(in)
	if err != nil {
		return errors.Trace(err)
	}
	bj.TypeCode = newBj.TypeCode
	bj.Value = newBj.Value
	return nil
}

// HashValue converts certain JSON values for aggregate comparisons.
// For example int64(3) == float64(3.0)
func (bj BinaryJSON) HashValue(buf []byte) []byte {
	switch bj.TypeCode {
	case JSONTypeCodeInt64:
		// Convert to a FLOAT if no precision is lost.
		// In the future, it will be better to convert to a DECIMAL value instead
		// See: https://github.com/pingcap/tidb/issues/9988
		if bj.GetInt64() == int64(float64(bj.GetInt64())) {
			buf = appendBinaryFloat64(buf, float64(bj.GetInt64()))
		} else {
			buf = append(buf, bj.Value...)
		}
	case JSONTypeCodeArray:
		elemCount := int(jsonEndian.Uint32(bj.Value))
		for i := 0; i < elemCount; i++ {
			buf = bj.arrayGetElem(i).HashValue(buf)
		}
	case JSONTypeCodeObject:
		elemCount := int(jsonEndian.Uint32(bj.Value))
		for i := 0; i < elemCount; i++ {
			buf = append(buf, bj.objectGetKey(i)...)
			buf = bj.objectGetVal(i).HashValue(buf)
		}
	default:
		buf = append(buf, bj.Value...)
	}
	return buf
}

// CreateBinaryJSON creates a BinaryJSON from interface.
func CreateBinaryJSON(in interface{}) BinaryJSON {
	bj, err := CreateBinaryJSONWithCheck(in)
	if err != nil {
		panic(err)
	}
	return bj
}

// CreateBinaryJSONWithCheck creates a BinaryJSON from interface with error check.
func CreateBinaryJSONWithCheck(in interface{}) (BinaryJSON, error) {
	typeCode, buf, err := appendBinaryJSON(nil, in)
	if err != nil {
		return BinaryJSON{}, err
	}
	bj := BinaryJSON{TypeCode: typeCode, Value: buf}
	// GetElemDepth always returns +1.
	if bj.GetElemDepth()-1 > maxJSONDepth {
		return BinaryJSON{}, ErrJSONDocumentTooDeep
	}
	return bj, nil
}

func appendBinaryJSON(buf []byte, in interface{}) (JSONTypeCode, []byte, error) {
	var typeCode byte
	var err error
	switch x := in.(type) {
	case nil:
		typeCode = JSONTypeCodeLiteral
		buf = append(buf, JSONLiteralNil)
	case bool:
		typeCode = JSONTypeCodeLiteral
		if x {
			buf = append(buf, JSONLiteralTrue)
		} else {
			buf = append(buf, JSONLiteralFalse)
		}
	case int64:
		typeCode = JSONTypeCodeInt64
		buf = appendBinaryUint64(buf, uint64(x))
	case uint64:
		typeCode = JSONTypeCodeUint64
		buf = appendBinaryUint64(buf, x)
	case float64:
		typeCode = JSONTypeCodeFloat64
		buf = appendBinaryFloat64(buf, x)
	case json.Number:
		typeCode, buf, err = appendBinaryNumber(buf, x)
		if err != nil {
			return typeCode, nil, errors.Trace(err)
		}
	case string:
		typeCode = JSONTypeCodeString
		buf = appendBinaryString(buf, x)
	case BinaryJSON:
		typeCode = x.TypeCode
		buf = append(buf, x.Value...)
	case []interface{}:
		typeCode = JSONTypeCodeArray
		buf, err = appendBinaryArray(buf, x)
		if err != nil {
			return typeCode, nil, errors.Trace(err)
		}
	case map[string]interface{}:
		typeCode = JSONTypeCodeObject
		buf, err = appendBinaryObject(buf, x)
		if err != nil {
			return typeCode, nil, errors.Trace(err)
		}
	case Opaque:
		typeCode = JSONTypeCodeOpaque
		buf = appendBinaryOpaque(buf, x)
	case Time:
		typeCode = JSONTypeCodeDate
		if x.Type() == mysql.TypeDatetime {
			typeCode = JSONTypeCodeDatetime
		} else if x.Type() == mysql.TypeTimestamp {
			typeCode = JSONTypeCodeTimestamp
		}
		buf = appendBinaryUint64(buf, uint64(x.CoreTime()))
	case Duration:
		typeCode = JSONTypeCodeDuration
		buf = appendBinaryUint64(buf, uint64(x.Duration))
		buf = appendBinaryUint32(buf, uint32(x.Fsp))
	default:
		msg := fmt.Sprintf(unknownTypeErrorMsg, reflect.TypeOf(in))
		err = errors.New(msg)
	}
	return typeCode, buf, err
}

func appendZero(buf []byte, length int) []byte {
	var tmp [8]byte
	rem := length % 8
	loop := length / 8
	for i := 0; i < loop; i++ {
		buf = append(buf, tmp[:]...)
	}
	for i := 0; i < rem; i++ {
		buf = append(buf, 0)
	}
	return buf
}

func appendUint32(buf []byte, v uint32) []byte {
	var tmp [4]byte
	jsonEndian.PutUint32(tmp[:], v)
	return append(buf, tmp[:]...)
}

func appendBinaryNumber(buf []byte, x json.Number) (JSONTypeCode, []byte, error) {
	// The type interpretation process is as follows:
	// - Attempt float64 if it contains Ee.
	// - Next attempt int64
	// - Then uint64 (valid in MySQL JSON, not in JSON decode library)
	// - Then float64
	// - Return an error
	if strings.ContainsAny(string(x), "Ee.") {
		f64, err := x.Float64()
		if err != nil {
			return JSONTypeCodeFloat64, nil, errors.Trace(err)
		}
		return JSONTypeCodeFloat64, appendBinaryFloat64(buf, f64), nil
	} else if val, err := x.Int64(); err == nil {
		return JSONTypeCodeInt64, appendBinaryUint64(buf, uint64(val)), nil
	} else if val, err := strconv.ParseUint(string(x), 10, 64); err == nil {
		return JSONTypeCodeUint64, appendBinaryUint64(buf, val), nil
	}
	val, err := x.Float64()
	if err == nil {
		return JSONTypeCodeFloat64, appendBinaryFloat64(buf, val), nil
	}
	var typeCode JSONTypeCode
	return typeCode, nil, errors.Trace(err)
}

func appendBinaryString(buf []byte, v string) []byte {
	begin := len(buf)
	buf = appendZero(buf, binary.MaxVarintLen64)
	lenLen := binary.PutUvarint(buf[begin:], uint64(len(v)))
	buf = buf[:len(buf)-binary.MaxVarintLen64+lenLen]
	buf = append(buf, v...)
	return buf
}

func appendBinaryOpaque(buf []byte, v Opaque) []byte {
	buf = append(buf, v.TypeCode)

	lenBegin := len(buf)
	buf = appendZero(buf, binary.MaxVarintLen64)
	lenLen := binary.PutUvarint(buf[lenBegin:], uint64(len(v.Buf)))

	buf = buf[:len(buf)-binary.MaxVarintLen64+lenLen]
	buf = append(buf, v.Buf...)
	return buf
}

func appendBinaryFloat64(buf []byte, v float64) []byte {
	off := len(buf)
	buf = appendZero(buf, 8)
	jsonEndian.PutUint64(buf[off:], math.Float64bits(v))
	return buf
}

func appendBinaryUint64(buf []byte, v uint64) []byte {
	off := len(buf)
	buf = appendZero(buf, 8)
	jsonEndian.PutUint64(buf[off:], v)
	return buf
}

func appendBinaryUint32(buf []byte, v uint32) []byte {
	off := len(buf)
	buf = appendZero(buf, 4)
	jsonEndian.PutUint32(buf[off:], v)
	return buf
}

func appendBinaryArray(buf []byte, array []interface{}) ([]byte, error) {
	docOff := len(buf)
	buf = appendUint32(buf, uint32(len(array)))
	buf = appendZero(buf, dataSizeOff)
	valEntryBegin := len(buf)
	buf = appendZero(buf, len(array)*valEntrySize)
	for i, val := range array {
		var err error
		buf, err = appendBinaryValElem(buf, docOff, valEntryBegin+i*valEntrySize, val)
		if err != nil {
			return nil, errors.Trace(err)
		}
	}
	docSize := len(buf) - docOff
	jsonEndian.PutUint32(buf[docOff+dataSizeOff:], uint32(docSize))
	return buf, nil
}

func appendBinaryValElem(buf []byte, docOff, valEntryOff int, val interface{}) ([]byte, error) {
	var typeCode JSONTypeCode
	var err error
	elemDocOff := len(buf)
	typeCode, buf, err = appendBinaryJSON(buf, val)
	if err != nil {
		return nil, errors.Trace(err)
	}
	if typeCode == JSONTypeCodeLiteral {
		litCode := buf[elemDocOff]
		buf = buf[:elemDocOff]
		buf[valEntryOff] = JSONTypeCodeLiteral
		buf[valEntryOff+1] = litCode
		return buf, nil
	}
	buf[valEntryOff] = typeCode
	valOff := elemDocOff - docOff
	jsonEndian.PutUint32(buf[valEntryOff+1:], uint32(valOff))
	return buf, nil
}

type field struct {
	key string
	val interface{}
}

func appendBinaryObject(buf []byte, x map[string]interface{}) ([]byte, error) {
	docOff := len(buf)
	buf = appendUint32(buf, uint32(len(x)))
	buf = appendZero(buf, dataSizeOff)
	keyEntryBegin := len(buf)
	buf = appendZero(buf, len(x)*keyEntrySize)
	valEntryBegin := len(buf)
	buf = appendZero(buf, len(x)*valEntrySize)

	fields := make([]field, 0, len(x))
	for key, val := range x {
		fields = append(fields, field{key: key, val: val})
	}
	slices.SortFunc(fields, func(i, j field) bool {
		return i.key < j.key
	})
	for i, field := range fields {
		keyEntryOff := keyEntryBegin + i*keyEntrySize
		keyOff := len(buf) - docOff
		keyLen := uint32(len(field.key))
		if keyLen > math.MaxUint16 {
			return nil, ErrJSONObjectKeyTooLong
		}
		jsonEndian.PutUint32(buf[keyEntryOff:], uint32(keyOff))
		jsonEndian.PutUint16(buf[keyEntryOff+keyLenOff:], uint16(keyLen))
		buf = append(buf, field.key...)
	}
	for i, field := range fields {
		var err error
		buf, err = appendBinaryValElem(buf, docOff, valEntryBegin+i*valEntrySize, field.val)
		if err != nil {
			return nil, errors.Trace(err)
		}
	}
	docSize := len(buf) - docOff
	jsonEndian.PutUint32(buf[docOff+dataSizeOff:], uint32(docSize))
	return buf, nil
}

相关信息

tidb 源码目录

相关文章

tidb binary_literal 源码

tidb compare 源码

tidb convert 源码

tidb core_time 源码

tidb datum 源码

tidb datum_eval 源码

tidb enum 源码

tidb errors 源码

tidb etc 源码

tidb eval_type 源码

0  赞