vitess.io/vitess@v0.16.2/go/mysql/binlog_event_json.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mysql
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"math"
    23  
    24  	"vitess.io/vitess/go/vt/log"
    25  
    26  	"github.com/spyzhov/ajson"
    27  
    28  	querypb "vitess.io/vitess/go/vt/proto/query"
    29  )
    30  
    31  /*
    32  
    33  References:
    34  
    35  * C source of mysql json data type implementation
    36  https://fossies.org/linux/mysql/sql/json_binary.cc
    37  
    38  * nice description of MySQL's json representation
    39  https://lafengnan.gitbooks.io/blog/content/mysql/chapter2.html
    40  
    41  * java/python connector links: useful for test cases and reverse engineering
    42  https://github.com/shyiko/mysql-binlog-connector-java/pull/119/files
    43  https://github.com/noplay/python-mysql-replication/blob/175df28cc8b536a68522ff9b09dc5440adad6094/pymysqlreplication/packet.py
    44  
    45  */
    46  
    47  // region debug-only
    48  // TODO remove once the json refactor is tested live
    49  var jsonDebug = false
    50  
    51  func jlog(tpl string, vals ...any) {
    52  	if !jsonDebug {
    53  		return
    54  	}
    55  	log.Infof("JSON:"+tpl+"\n", vals...)
    56  	_ = printASCIIBytes
    57  }
    58  
    59  func printASCIIBytes(data []byte) {
    60  	if !jsonDebug {
    61  		return
    62  	}
    63  	s := ""
    64  	for _, c := range data {
    65  		if c < 127 && c > 32 {
    66  			s += fmt.Sprintf("%c ", c)
    67  		} else {
    68  			s += fmt.Sprintf("%02d ", c)
    69  		}
    70  	}
    71  	log.Infof("[%s]", s)
    72  }
    73  
    74  // only used for logging/debugging
    75  var jsonTypeToName = map[uint]string{
    76  	jsonSmallObject: "sObject",
    77  	jsonLargeObject: "lObject",
    78  	jsonSmallArray:  "sArray",
    79  	jsonLargeArray:  "lArray",
    80  	jsonLiteral:     "literal",
    81  	jsonInt16:       "int16",
    82  	jsonUint16:      "uint16",
    83  	jsonInt32:       "int32",
    84  	jsonUint32:      "uint32",
    85  	jsonInt64:       "int64",
    86  	jsonUint64:      "uint64",
    87  	jsonDouble:      "double", //0x0b
    88  	jsonString:      "string", //0x0c a utf8mb4 string
    89  	jsonOpaque:      "opaque", //0x0f "custom" data
    90  }
    91  
    92  func jsonDataTypeToString(typ uint) string {
    93  	sType, ok := jsonTypeToName[typ]
    94  	if !ok {
    95  		return "undefined"
    96  	}
    97  	return sType
    98  }
    99  
   100  //endregion
   101  
   102  // provides the single API function, used to convert json from binary format used in binlogs to a string representation
   103  func getJSONValue(data []byte) (string, error) {
   104  	var ast *ajson.Node
   105  	var err error
   106  	if len(data) == 0 {
   107  		ast = ajson.NullNode("")
   108  	} else {
   109  		ast, err = binlogJSON.parse(data)
   110  		if err != nil {
   111  			return "", err
   112  		}
   113  	}
   114  	bytes, err := ajson.Marshal(ast)
   115  	if err != nil {
   116  		return "", err
   117  	}
   118  	return string(bytes), nil
   119  }
   120  
   121  var binlogJSON *BinlogJSON
   122  
   123  func init() {
   124  	binlogJSON = &BinlogJSON{
   125  		plugins: make(map[jsonDataType]jsonPlugin),
   126  	}
   127  }
   128  
   129  //region plugin manager
   130  
   131  // BinlogJSON contains the plugins for all json types and methods for parsing the
   132  // binary json representation of a specific type from the binlog
   133  type BinlogJSON struct {
   134  	plugins map[jsonDataType]jsonPlugin
   135  }
   136  
   137  // parse decodes a value from the binlog
   138  func (jh *BinlogJSON) parse(data []byte) (node *ajson.Node, err error) {
   139  	// pos keeps track of the offset of the current node being parsed
   140  	pos := 0
   141  	typ := data[pos]
   142  	jlog("Top level object is type %s\n", jsonDataTypeToString(uint(typ)))
   143  	pos++
   144  	return jh.getNode(jsonDataType(typ), data, pos)
   145  }
   146  
   147  // each plugin registers itself in init()s
   148  func (jh *BinlogJSON) register(typ jsonDataType, Plugin jsonPlugin) {
   149  	jh.plugins[typ] = Plugin
   150  }
   151  
   152  // gets the node at this position
   153  func (jh *BinlogJSON) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   154  	Plugin := jh.plugins[typ]
   155  	if Plugin == nil {
   156  		return nil, fmt.Errorf("plugin not found for type %d", typ)
   157  	}
   158  	return Plugin.getNode(typ, data, pos)
   159  }
   160  
   161  //endregion
   162  
   163  //region enums
   164  
   165  // jsonDataType has the values used in the mysql json binary representation to denote types.
   166  // We have string, literal(true/false/null), number, object or array types.
   167  // large object => doc size > 64K: you get pointers instead of inline values.
   168  type jsonDataType byte
   169  
   170  // type mapping as defined by the mysql json representation
   171  const (
   172  	jsonSmallObject = 0
   173  	jsonLargeObject = 1
   174  	jsonSmallArray  = 2
   175  	jsonLargeArray  = 3
   176  	jsonLiteral     = 4
   177  	jsonInt16       = 5
   178  	jsonUint16      = 6
   179  	jsonInt32       = 7
   180  	jsonUint32      = 8
   181  	jsonInt64       = 9
   182  	jsonUint64      = 10 //0x0a
   183  	jsonDouble      = 11 //0x0b
   184  	jsonString      = 12 //0x0c a utf8mb4 string
   185  	jsonOpaque      = 15 //0x0f "custom" data
   186  )
   187  
   188  // literals in the binary json format can be one of three types: null, true, false
   189  type jsonDataLiteral byte
   190  
   191  // this is how mysql maps the three literals in the binlog
   192  const (
   193  	jsonNullLiteral  = '\x00'
   194  	jsonTrueLiteral  = '\x01'
   195  	jsonFalseLiteral = '\x02'
   196  )
   197  
   198  //endregion
   199  
   200  //region util funcs
   201  
   202  // in objects and arrays some values are inlined, other types have offsets into the raw data.
   203  // literals (true/false/null) and 16bit integers are always inlined.
   204  // for large documents 32bit integers are also inlined.
   205  // principle is that two byte values are inlined in "small", and four byte in "large" docs
   206  func isInline(typ jsonDataType, large bool) bool {
   207  	switch typ {
   208  	case jsonLiteral, jsonInt16, jsonUint16:
   209  		return true
   210  	case jsonInt32, jsonUint32:
   211  		if large {
   212  			return true
   213  		}
   214  	}
   215  	return false
   216  }
   217  
   218  // readInt returns either a 32-bit or a 16-bit int from the passed buffer. Which one it is,
   219  // depends on whether the document is "large" or not.
   220  // JSON documents stored are considered "large" if the size of the stored json document is
   221  // more than 64K bytes. Values of non-inlined types are stored as offsets into the document.
   222  // The int returned is either an (i) offset into the raw data, (ii) count of elements, or (iii) size of the represented data structure.
   223  // (This design decision allows a fixed number of bytes to be used for representing object keys and array indices.)
   224  // readInt also returns the new position (by advancing the position by the number of bytes read).
   225  func readInt(data []byte, pos int, large bool) (int, int) {
   226  	if large {
   227  		return int(data[pos]) +
   228  				int(data[pos+1])<<8 +
   229  				int(data[pos+2])<<16 +
   230  				int(data[pos+3])<<24,
   231  			pos + 4
   232  	}
   233  	return int(data[pos]) +
   234  		int(data[pos+1])<<8, pos + 2
   235  }
   236  
   237  // readVariableLength implements the logic to decode the length
   238  // of an arbitrarily long string as implemented by the mysql server
   239  // https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.cc#L234
   240  // https://github.com/mysql/mysql-server/blob/8.0/sql/json_binary.cc#L283
   241  // readVariableLength also returns the new position (by advancing the position by the number of bytes read).
   242  func readVariableLength(data []byte, pos int) (int, int) {
   243  	var bb byte
   244  	var length int
   245  	var idx byte
   246  	for {
   247  		bb = data[pos]
   248  		pos++
   249  		length |= int(bb&0x7f) << (7 * idx)
   250  		// if the high bit is 1, the integer value of the byte will be negative.
   251  		// high bit of 1 signifies that the next byte is part of the length encoding.
   252  		if int8(bb) >= 0 {
   253  			break
   254  		}
   255  		idx++
   256  	}
   257  	return length, pos
   258  }
   259  
   260  // getElem returns the json value found inside json objects and arrays at the provided position
   261  func getElem(data []byte, pos int, large bool) (*ajson.Node, int, error) {
   262  	var elem *ajson.Node
   263  	var err error
   264  	var offset int
   265  	typ := jsonDataType(data[pos])
   266  	pos++
   267  	if isInline(typ, large) {
   268  		elem, err = binlogJSON.getNode(typ, data, pos)
   269  		if err != nil {
   270  			return nil, 0, err
   271  		}
   272  		if large {
   273  			pos += 4
   274  		} else {
   275  			pos += 2
   276  		}
   277  	} else {
   278  		offset, pos = readInt(data, pos, large)
   279  		if offset >= len(data) { // consistency check, should only come here is there is a bug in the code
   280  			log.Errorf("unable to decode element")
   281  			return nil, 0, fmt.Errorf("unable to decode element: %+v", data)
   282  		}
   283  		newData := data[offset:]
   284  		//newPos ignored because this is an offset into the "extra" section of the buffer
   285  		elem, err = binlogJSON.getNode(typ, newData, 1)
   286  		if err != nil {
   287  			return nil, 0, err
   288  		}
   289  	}
   290  	return elem, pos, nil
   291  }
   292  
   293  //endregion
   294  
   295  // json sub-type interface
   296  // one plugin for each sub-type, plugins are stateless and initialized on load via individual init() functions
   297  type jsonPlugin interface {
   298  	getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error)
   299  }
   300  
   301  type jsonPluginInfo struct {
   302  	name  string
   303  	types []jsonDataType
   304  }
   305  
   306  //region int plugin
   307  
   308  func init() {
   309  	newIntPlugin()
   310  }
   311  
   312  type intPlugin struct {
   313  	info  *jsonPluginInfo
   314  	sizes map[jsonDataType]int
   315  }
   316  
   317  var _ jsonPlugin = (*intPlugin)(nil)
   318  
   319  func (ipl intPlugin) getVal(typ jsonDataType, data []byte, pos int) (value int64) {
   320  	var val uint64
   321  	var val2 int64
   322  	size := ipl.sizes[typ]
   323  	for i := 0; i < size; i++ {
   324  		val = val + uint64(data[pos+i])<<(8*i)
   325  	}
   326  	switch typ {
   327  	case jsonInt16:
   328  		val2 = int64(int16(val))
   329  	case jsonInt32:
   330  		val2 = int64(int32(val))
   331  	case jsonInt64:
   332  		val2 = int64(val)
   333  	}
   334  	return val2
   335  }
   336  
   337  func (ipl intPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   338  	val := ipl.getVal(typ, data, pos)
   339  	node = ajson.IntegerNode("", val)
   340  	return node, nil
   341  }
   342  
   343  func newIntPlugin() *intPlugin {
   344  	ipl := &intPlugin{
   345  		info: &jsonPluginInfo{
   346  			name:  "Int",
   347  			types: []jsonDataType{jsonInt64, jsonInt32, jsonInt16},
   348  		},
   349  		sizes: make(map[jsonDataType]int),
   350  	}
   351  	ipl.sizes = map[jsonDataType]int{
   352  		jsonInt64: 8,
   353  		jsonInt32: 4,
   354  		jsonInt16: 2,
   355  	}
   356  	for _, typ := range ipl.info.types {
   357  		binlogJSON.register(typ, ipl)
   358  	}
   359  	return ipl
   360  }
   361  
   362  //endregion
   363  
   364  //region uint plugin
   365  
   366  func init() {
   367  	newUintPlugin()
   368  }
   369  
   370  type uintPlugin struct {
   371  	info  *jsonPluginInfo
   372  	sizes map[jsonDataType]int
   373  }
   374  
   375  var _ jsonPlugin = (*uintPlugin)(nil)
   376  
   377  func (upl uintPlugin) getVal(typ jsonDataType, data []byte, pos int) (value uint64) {
   378  	var val uint64
   379  	var val2 uint64
   380  	size := upl.sizes[typ]
   381  	for i := 0; i < size; i++ {
   382  		val = val + uint64(data[pos+i])<<(8*i)
   383  	}
   384  	switch typ {
   385  	case jsonUint16:
   386  		val2 = uint64(uint16(val))
   387  	case jsonUint32:
   388  		val2 = uint64(uint32(val))
   389  	case jsonUint64:
   390  		val2 = val
   391  	}
   392  	return val2
   393  }
   394  
   395  func (upl uintPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   396  	val := upl.getVal(typ, data, pos)
   397  	node = ajson.UnsignedIntegerNode("", val)
   398  	return node, nil
   399  }
   400  
   401  func newUintPlugin() *uintPlugin {
   402  	upl := &uintPlugin{
   403  		info: &jsonPluginInfo{
   404  			name:  "Uint",
   405  			types: []jsonDataType{jsonUint16, jsonUint32, jsonUint64},
   406  		},
   407  		sizes: make(map[jsonDataType]int),
   408  	}
   409  	upl.sizes = map[jsonDataType]int{
   410  		jsonUint64: 8,
   411  		jsonUint32: 4,
   412  		jsonUint16: 2,
   413  	}
   414  	for _, typ := range upl.info.types {
   415  		binlogJSON.register(typ, upl)
   416  	}
   417  	return upl
   418  }
   419  
   420  //endregion
   421  
   422  //region float plugin
   423  
   424  func init() {
   425  	newFloatPlugin()
   426  }
   427  
   428  type floatPlugin struct {
   429  	info  *jsonPluginInfo
   430  	sizes map[jsonDataType]int
   431  }
   432  
   433  var _ jsonPlugin = (*floatPlugin)(nil)
   434  
   435  func (flp floatPlugin) getVal(typ jsonDataType, data []byte, pos int) (value float64) {
   436  	var val uint64
   437  	var val2 float64
   438  	size := flp.sizes[typ]
   439  	for i := 0; i < size; i++ {
   440  		val = val + uint64(data[pos+i])<<(8*i)
   441  	}
   442  	switch typ {
   443  	case jsonDouble:
   444  		val2 = math.Float64frombits(val)
   445  	}
   446  	return val2
   447  }
   448  
   449  func (flp floatPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   450  	val := flp.getVal(typ, data, pos)
   451  	node = ajson.NumericNode("", val)
   452  	return node, nil
   453  }
   454  
   455  func newFloatPlugin() *floatPlugin {
   456  	fp := &floatPlugin{
   457  		info: &jsonPluginInfo{
   458  			name:  "Float",
   459  			types: []jsonDataType{jsonDouble},
   460  		},
   461  		sizes: make(map[jsonDataType]int),
   462  	}
   463  	fp.sizes = map[jsonDataType]int{
   464  		jsonDouble: 8,
   465  	}
   466  	for _, typ := range fp.info.types {
   467  		binlogJSON.register(typ, fp)
   468  	}
   469  	return fp
   470  }
   471  
   472  //endregion
   473  
   474  //region literal plugin
   475  
   476  func init() {
   477  	newLiteralPlugin()
   478  }
   479  
   480  type literalPlugin struct {
   481  	info *jsonPluginInfo
   482  }
   483  
   484  var _ jsonPlugin = (*literalPlugin)(nil)
   485  
   486  func (lpl literalPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   487  	val := jsonDataLiteral(data[pos])
   488  	switch val {
   489  	case jsonNullLiteral:
   490  		node = ajson.NullNode("")
   491  	case jsonTrueLiteral:
   492  		node = ajson.BoolNode("", true)
   493  	case jsonFalseLiteral:
   494  		node = ajson.BoolNode("", false)
   495  	default:
   496  		return nil, fmt.Errorf("unknown literal value %v", val)
   497  	}
   498  	return node, nil
   499  }
   500  
   501  func newLiteralPlugin() *literalPlugin {
   502  	lpl := &literalPlugin{
   503  		info: &jsonPluginInfo{
   504  			name:  "Literal",
   505  			types: []jsonDataType{jsonLiteral},
   506  		},
   507  	}
   508  	binlogJSON.register(jsonLiteral, lpl)
   509  	return lpl
   510  }
   511  
   512  //endregion
   513  
   514  //region opaque plugin
   515  
   516  func init() {
   517  	newOpaquePlugin()
   518  }
   519  
   520  type opaquePlugin struct {
   521  	info *jsonPluginInfo
   522  }
   523  
   524  var _ jsonPlugin = (*opaquePlugin)(nil)
   525  
   526  // other types are stored as catch-all opaque types: documentation on these is scarce.
   527  // we currently know about (and support) date/time/datetime/decimal.
   528  func (opl opaquePlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   529  	dataType := data[pos]
   530  	start := 3       // account for length of stored value
   531  	end := start + 8 // all currently supported opaque data types are 8 bytes in size
   532  	switch dataType {
   533  	case TypeDate:
   534  		raw := binary.LittleEndian.Uint64(data[start:end])
   535  		value := raw >> 24
   536  		yearMonth := (value >> 22) & 0x01ffff // 17 bits starting at 22nd
   537  		year := yearMonth / 13
   538  		month := yearMonth % 13
   539  		day := (value >> 17) & 0x1f // 5 bits starting at 17th
   540  		dateString := fmt.Sprintf("%04d-%02d-%02d", year, month, day)
   541  		node = ajson.StringNode("", dateString)
   542  	case TypeTime:
   543  		raw := binary.LittleEndian.Uint64(data[start:end])
   544  		value := raw >> 24
   545  		hour := (value >> 12) & 0x03ff // 10 bits starting at 12th
   546  		minute := (value >> 6) & 0x3f  // 6 bits starting at 6th
   547  		second := value & 0x3f         // 6 bits starting at 0th
   548  		microSeconds := raw & 0xffffff // 24 lower bits
   549  		timeString := fmt.Sprintf("%02d:%02d:%02d.%06d", hour, minute, second, microSeconds)
   550  		node = ajson.StringNode("", timeString)
   551  	case TypeDateTime:
   552  		raw := binary.LittleEndian.Uint64(data[start:end])
   553  		value := raw >> 24
   554  		yearMonth := (value >> 22) & 0x01ffff // 17 bits starting at 22nd
   555  		year := yearMonth / 13
   556  		month := yearMonth % 13
   557  		day := (value >> 17) & 0x1f    // 5 bits starting at 17th
   558  		hour := (value >> 12) & 0x1f   // 5 bits starting at 12th
   559  		minute := (value >> 6) & 0x3f  // 6 bits starting at 6th
   560  		second := value & 0x3f         // 6 bits starting at 0th
   561  		microSeconds := raw & 0xffffff // 24 lower bits
   562  		timeString := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", year, month, day, hour, minute, second, microSeconds)
   563  		node = ajson.StringNode("", timeString)
   564  	case TypeNewDecimal:
   565  		decimalData := data[start:end]
   566  		precision := decimalData[0]
   567  		scale := decimalData[1]
   568  		metadata := (uint16(precision) << 8) + uint16(scale)
   569  		val, _, err := CellValue(decimalData, 2, TypeNewDecimal, metadata, &querypb.Field{Type: querypb.Type_DECIMAL})
   570  		if err != nil {
   571  			return nil, err
   572  		}
   573  		float, err := val.ToFloat64()
   574  		if err != nil {
   575  			return nil, err
   576  		}
   577  		node = ajson.NumericNode("", float)
   578  	default:
   579  		return nil, fmt.Errorf("opaque type %d is not supported yet, data %v", dataType, data[2:])
   580  	}
   581  	return node, nil
   582  }
   583  
   584  func newOpaquePlugin() *opaquePlugin {
   585  	opl := &opaquePlugin{
   586  		info: &jsonPluginInfo{
   587  			name:  "Opaque",
   588  			types: []jsonDataType{jsonOpaque},
   589  		},
   590  	}
   591  	binlogJSON.register(jsonOpaque, opl)
   592  	return opl
   593  }
   594  
   595  //endregion
   596  
   597  //region string plugin
   598  
   599  func init() {
   600  	newStringPlugin()
   601  }
   602  
   603  type stringPlugin struct {
   604  	info *jsonPluginInfo
   605  }
   606  
   607  var _ jsonPlugin = (*stringPlugin)(nil)
   608  
   609  func (spl stringPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   610  	size, pos := readVariableLength(data, pos)
   611  	node = ajson.StringNode("", string(data[pos:pos+size]))
   612  
   613  	return node, nil
   614  }
   615  
   616  func newStringPlugin() *stringPlugin {
   617  	spl := &stringPlugin{
   618  		info: &jsonPluginInfo{
   619  			name:  "String",
   620  			types: []jsonDataType{jsonString},
   621  		},
   622  	}
   623  	binlogJSON.register(jsonString, spl)
   624  	return spl
   625  }
   626  
   627  //endregion
   628  
   629  //region array plugin
   630  
   631  func init() {
   632  	newArrayPlugin()
   633  }
   634  
   635  type arrayPlugin struct {
   636  	info *jsonPluginInfo
   637  }
   638  
   639  var _ jsonPlugin = (*arrayPlugin)(nil)
   640  
   641  // arrays are stored thus:
   642  // | type_identifier(one of [2,3]) | elem count | obj size | list of offsets+lengths of values | actual values |
   643  func (apl arrayPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   644  	jlog("JSON Array %s, len %d", jsonDataTypeToString(uint(typ)), len(data))
   645  	var nodes []*ajson.Node
   646  	var elem *ajson.Node
   647  	var elementCount, size int
   648  	large := typ == jsonLargeArray
   649  	elementCount, pos = readInt(data, pos, large)
   650  	jlog("Array(%t): elem count: %d\n", large, elementCount)
   651  	size, pos = readInt(data, pos, large)
   652  	jlog("Array(%t): elem count: %d, size:%d\n", large, elementCount, size)
   653  	for i := 0; i < elementCount; i++ {
   654  		elem, pos, err = getElem(data, pos, large)
   655  		if err != nil {
   656  			return nil, err
   657  		}
   658  		nodes = append(nodes, elem)
   659  		jlog("Index is %d:%s", i, jsonDataTypeToString(uint(typ)))
   660  	}
   661  	node = ajson.ArrayNode("", nodes)
   662  	return node, nil
   663  }
   664  
   665  func newArrayPlugin() *arrayPlugin {
   666  	apl := &arrayPlugin{
   667  		info: &jsonPluginInfo{
   668  			name:  "Array",
   669  			types: []jsonDataType{jsonSmallArray, jsonLargeArray},
   670  		},
   671  	}
   672  	binlogJSON.register(jsonSmallArray, apl)
   673  	binlogJSON.register(jsonLargeArray, apl)
   674  	return apl
   675  }
   676  
   677  //endregion
   678  
   679  //region object plugin
   680  
   681  func init() {
   682  	newObjectPlugin()
   683  }
   684  
   685  type objectPlugin struct {
   686  	info *jsonPluginInfo
   687  }
   688  
   689  var _ jsonPlugin = (*objectPlugin)(nil)
   690  
   691  // objects are stored thus:
   692  // | type_identifier(0/1) | elem count | obj size | list of offsets+lengths of keys | list of offsets+lengths of values | actual keys | actual values |
   693  func (opl objectPlugin) getNode(typ jsonDataType, data []byte, pos int) (node *ajson.Node, err error) {
   694  	jlog("JSON Type is %s, len %d", jsonDataTypeToString(uint(typ)), len(data))
   695  
   696  	// "large" decides number of bytes used to specify element count and total object size: 4 bytes for large, 2 for small
   697  	var large = typ == jsonLargeObject
   698  
   699  	var elementCount int // total number of elements (== keys) in this object map. (element can be another object: recursively handled)
   700  	var size int         // total size of object
   701  
   702  	elementCount, pos = readInt(data, pos, large)
   703  	size, pos = readInt(data, pos, large)
   704  	jlog("Object: elem count: %d, size %d\n", elementCount, size)
   705  
   706  	keys := make([]string, elementCount) // stores all the keys in this object
   707  	for i := 0; i < elementCount; i++ {
   708  		var keyOffset int
   709  		var keyLength int
   710  		keyOffset, pos = readInt(data, pos, large)
   711  		keyLength, pos = readInt(data, pos, false) // keyLength is always a 16-bit int
   712  
   713  		keyOffsetStart := keyOffset + 1
   714  		// check that offsets are not out of bounds (can happen only if there is a bug in the parsing code)
   715  		if keyOffsetStart >= len(data) || keyOffsetStart+keyLength > len(data) {
   716  			log.Errorf("unable to decode object elements")
   717  			return nil, fmt.Errorf("unable to decode object elements: %v", data)
   718  		}
   719  		keys[i] = string(data[keyOffsetStart : keyOffsetStart+keyLength])
   720  	}
   721  	jlog("Object keys: %+v", keys)
   722  
   723  	object := make(map[string]*ajson.Node)
   724  	var elem *ajson.Node
   725  
   726  	// get the value for each key
   727  	for i := 0; i < elementCount; i++ {
   728  		elem, pos, err = getElem(data, pos, large)
   729  		if err != nil {
   730  			return nil, err
   731  		}
   732  		object[keys[i]] = elem
   733  		jlog("Key is %s:%s", keys[i], jsonDataTypeToString(uint(typ)))
   734  	}
   735  
   736  	node = ajson.ObjectNode("", object)
   737  	return node, nil
   738  }
   739  
   740  func newObjectPlugin() *objectPlugin {
   741  	opl := &objectPlugin{
   742  		info: &jsonPluginInfo{
   743  			name:  "Object",
   744  			types: []jsonDataType{jsonSmallObject, jsonLargeObject},
   745  		},
   746  	}
   747  	binlogJSON.register(jsonSmallObject, opl)
   748  	binlogJSON.register(jsonLargeObject, opl)
   749  	return opl
   750  }
   751  
   752  //endregion