1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
namespace MySQLReplication\JsonBinaryDecoder; |
5
|
|
|
|
6
|
|
|
use MySQLReplication\BinaryDataReader\BinaryDataReader; |
7
|
|
|
use MySQLReplication\BinaryDataReader\Exception\BinaryDataReaderException; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Class JsonBinaryDecoderService |
11
|
|
|
* @package MySQLReplication\JsonBinaryDecoder |
12
|
|
|
* @see https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.cc |
13
|
|
|
* @see https://github.com/shyiko/mysql-binlog-connector-java/blob/master/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinary.java |
14
|
|
|
*/ |
15
|
|
|
class JsonBinaryDecoderService |
16
|
|
|
{ |
17
|
|
|
const SMALL_OBJECT = 0; |
18
|
|
|
const LARGE_OBJECT = 1; |
19
|
|
|
const SMALL_ARRAY = 2; |
20
|
|
|
const LARGE_ARRAY = 3; |
21
|
|
|
const LITERAL = 4; |
22
|
|
|
const INT16 = 5; |
23
|
|
|
const UINT16 = 6; |
24
|
|
|
const INT32 = 7; |
25
|
|
|
const UINT32 = 8; |
26
|
|
|
const INT64 = 9; |
27
|
|
|
const UINT64 = 10; |
28
|
|
|
const DOUBLE = 11; |
29
|
|
|
const STRING = 12; |
30
|
|
|
const OPAQUE = 15; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var BinaryDataReader |
34
|
|
|
*/ |
35
|
|
|
private $binaryDataReader; |
36
|
|
|
/** |
37
|
|
|
* @var JsonBinaryDecoderFormatter |
38
|
|
|
*/ |
39
|
|
|
private $jsonBinaryDecoderFormatter; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* JsonBinaryDecoderService constructor. |
43
|
|
|
* @param BinaryDataReader $binaryDataReader |
44
|
|
|
* @param JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter |
45
|
|
|
*/ |
46
|
|
|
public function __construct(BinaryDataReader $binaryDataReader, JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter) |
47
|
|
|
{ |
48
|
|
|
$this->binaryDataReader = $binaryDataReader; |
49
|
|
|
$this->jsonBinaryDecoderFormatter = $jsonBinaryDecoderFormatter; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @return string |
54
|
|
|
* @throws JsonBinaryDecoderException |
55
|
|
|
* @throws BinaryDataReaderException |
56
|
|
|
*/ |
57
|
|
|
public function parseToString() |
58
|
|
|
{ |
59
|
|
|
$this->parseJson($this->binaryDataReader->readUInt8()); |
60
|
|
|
|
61
|
|
|
return $this->jsonBinaryDecoderFormatter->getJsonString(); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @return int |
66
|
|
|
* @throws BinaryDataReaderException |
67
|
|
|
*/ |
68
|
|
|
private function readVariableInt() |
69
|
|
|
{ |
70
|
|
|
$length = $this->binaryDataReader->getBinaryDataLength(); |
71
|
|
|
$len = 0; |
72
|
|
|
for ($i = 0; $i < $length; $i++) |
73
|
|
|
{ |
74
|
|
|
$size = $this->binaryDataReader->readUInt8(); |
75
|
|
|
// Get the next 7 bits of the length. |
76
|
|
|
$len |= ($size & 127) << (7 * $i); |
77
|
|
|
if (($size & 128) === 0) |
78
|
|
|
{ |
79
|
|
|
// This was the last byte. Return successfully. |
80
|
|
|
return $len; |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return $len; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @param int $type |
89
|
|
|
* @throws JsonBinaryDecoderException |
90
|
|
|
* @throws BinaryDataReaderException |
91
|
|
|
*/ |
92
|
|
|
private function parseJson($type) |
93
|
|
|
{ |
94
|
|
|
if (self::SMALL_OBJECT === $type) |
95
|
|
|
{ |
96
|
|
|
$this->parseObject(BinaryDataReader::UNSIGNED_SHORT_LENGTH); |
97
|
|
|
} |
98
|
|
|
else if (self::LARGE_OBJECT === $type) |
99
|
|
|
{ |
100
|
|
|
$this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH); |
101
|
|
|
} |
102
|
|
|
else if (self::SMALL_ARRAY === $type) |
103
|
|
|
{ |
104
|
|
|
$this->parseArray(BinaryDataReader::UNSIGNED_SHORT_LENGTH); |
105
|
|
|
} |
106
|
|
|
else if (self::LARGE_ARRAY === $type) |
107
|
|
|
{ |
108
|
|
|
$this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH); |
109
|
|
|
} |
110
|
|
|
else |
111
|
|
|
{ |
112
|
|
|
$this->parseScalar($type); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
private function parseObject($intSize) |
117
|
|
|
{ |
118
|
|
|
$elementCount = $this->binaryDataReader->readUIntBySize($intSize); |
119
|
|
|
$size = $this->binaryDataReader->readUIntBySize($intSize); |
120
|
|
|
|
121
|
|
|
// Read each key-entry, consisting of the offset and length of each key ... |
122
|
|
|
$keyLengths = []; |
123
|
|
|
for ($i = 0; $i !== $elementCount; ++$i) |
124
|
|
|
{ |
125
|
|
|
$this->binaryDataReader->readUIntBySize($intSize); // $keyOffset unused |
126
|
|
|
$keyLengths[$i] = $this->binaryDataReader->readUInt16(); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$entries = []; |
130
|
|
|
for ($i = 0; $i !== $elementCount; ++$i) |
131
|
|
|
{ |
132
|
|
|
$entries[$i] = $this->parseValueType($size, $intSize); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// Read each key ... |
136
|
|
|
$keys = []; |
137
|
|
|
for ($i = 0; $i !== $elementCount; ++$i) |
138
|
|
|
{ |
139
|
|
|
$keys[$i] = $this->binaryDataReader->read($keyLengths[$i]); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$this->jsonBinaryDecoderFormatter->formatBeginObject(); |
143
|
|
|
|
144
|
|
View Code Duplication |
for ($i = 0; $i !== $elementCount; ++$i) |
|
|
|
|
145
|
|
|
{ |
146
|
|
|
if ($i !== 0) |
147
|
|
|
{ |
148
|
|
|
$this->jsonBinaryDecoderFormatter->formatNextEntry(); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$this->jsonBinaryDecoderFormatter->formatName($keys[$i]); |
152
|
|
|
|
153
|
|
|
/* @var JsonBinaryDecoderValue[] $entries */ |
154
|
|
|
$this->assignValues($entries[$i]); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$this->jsonBinaryDecoderFormatter->formatEndObject(); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param int $numBytes |
162
|
|
|
* @param int $intSize |
163
|
|
|
* @return JsonBinaryDecoderValue |
164
|
|
|
* @throws BinaryDataReaderException |
165
|
|
|
* @throws \LengthException |
166
|
|
|
*/ |
167
|
|
|
private function parseValueType($numBytes, $intSize) |
168
|
|
|
{ |
169
|
|
|
$type = $this->binaryDataReader->readInt8(); |
170
|
|
|
|
171
|
|
|
if (self::LITERAL === $type) |
172
|
|
|
{ |
173
|
|
|
return new JsonBinaryDecoderValue( |
174
|
|
|
true, |
175
|
|
|
$this->readLiteral(), |
176
|
|
|
$type |
177
|
|
|
); |
178
|
|
|
} |
179
|
|
|
else if (self::INT16 === $type) |
180
|
|
|
{ |
181
|
|
|
return new JsonBinaryDecoderValue( |
182
|
|
|
true, |
183
|
|
|
$this->binaryDataReader->readInt16(), |
184
|
|
|
$type |
185
|
|
|
); |
186
|
|
|
} |
187
|
|
|
else if (self::UINT16 === $type) |
188
|
|
|
{ |
189
|
|
|
return new JsonBinaryDecoderValue( |
190
|
|
|
true, |
191
|
|
|
$this->binaryDataReader->readUInt16(), |
192
|
|
|
$type |
193
|
|
|
); |
194
|
|
|
} |
195
|
|
|
else if (BinaryDataReader::UNSIGNED_INT32_LENGTH === $intSize) |
196
|
|
|
{ |
197
|
|
|
if (self::INT32 === $type) |
198
|
|
|
{ |
199
|
|
|
return new JsonBinaryDecoderValue( |
200
|
|
|
true, |
201
|
|
|
$this->binaryDataReader->readInt32(), |
202
|
|
|
$type |
203
|
|
|
); |
204
|
|
|
} |
205
|
|
|
else if (self::UINT32 === $type) |
206
|
|
|
{ |
207
|
|
|
return new JsonBinaryDecoderValue( |
208
|
|
|
true, |
209
|
|
|
$this->binaryDataReader->readUInt32(), |
210
|
|
|
$type |
211
|
|
|
); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
else |
215
|
|
|
{ |
216
|
|
|
$offset = $this->binaryDataReader->readUIntBySize($intSize); |
217
|
|
|
if ($offset > $numBytes) |
218
|
|
|
{ |
219
|
|
|
throw new \LengthException( |
220
|
|
|
'The offset for the value in the JSON binary document is ' . |
221
|
|
|
$offset . |
222
|
|
|
', which is larger than the binary form of the JSON document (' . |
223
|
|
|
$numBytes . ' bytes)' |
224
|
|
|
); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return new JsonBinaryDecoderValue( |
228
|
|
|
false, |
229
|
|
|
null, |
230
|
|
|
$type |
231
|
|
|
); |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @return bool|null |
237
|
|
|
* @throws BinaryDataReaderException |
238
|
|
|
*/ |
239
|
|
|
private function readLiteral() |
240
|
|
|
{ |
241
|
|
|
$literal = ord($this->binaryDataReader->read(2)); |
242
|
|
|
if (0 === $literal) |
243
|
|
|
{ |
244
|
|
|
return null; |
245
|
|
|
} |
246
|
|
|
else if (1 === $literal) |
247
|
|
|
{ |
248
|
|
|
return true; |
249
|
|
|
} |
250
|
|
|
else if (2 === $literal) |
251
|
|
|
{ |
252
|
|
|
return false; |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* @param JsonBinaryDecoderValue $jsonBinaryDecoderValue |
258
|
|
|
* @throws JsonBinaryDecoderException |
259
|
|
|
* @throws BinaryDataReaderException |
260
|
|
|
*/ |
261
|
|
|
private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue) |
262
|
|
|
{ |
263
|
|
|
if (false === $jsonBinaryDecoderValue->isIsResolved()) |
264
|
|
|
{ |
265
|
|
|
$this->parseJson($jsonBinaryDecoderValue->getType()); |
266
|
|
|
} |
267
|
|
|
else |
268
|
|
|
{ |
269
|
|
|
if (null === $jsonBinaryDecoderValue->getValue()) |
270
|
|
|
{ |
271
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNull(); |
272
|
|
|
} |
273
|
|
|
elseif (is_bool($jsonBinaryDecoderValue->getValue())) |
274
|
|
|
{ |
275
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue()); |
276
|
|
|
} |
277
|
|
|
elseif (is_numeric($jsonBinaryDecoderValue->getValue())) |
278
|
|
|
{ |
279
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue()); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
private function parseArray($size) |
285
|
|
|
{ |
286
|
|
|
$numElements = $this->binaryDataReader->readUInt16(); |
287
|
|
|
$numBytes = $this->binaryDataReader->readUInt16(); |
288
|
|
|
|
289
|
|
|
$entries = []; |
290
|
|
|
for ($i = 0; $i !== $numElements; ++$i) |
291
|
|
|
{ |
292
|
|
|
$entries[$i] = $this->parseValueType($numBytes, $size); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$this->jsonBinaryDecoderFormatter->formatBeginArray(); |
296
|
|
|
|
297
|
|
View Code Duplication |
for ($i = 0; $i !== $numElements; ++$i) |
|
|
|
|
298
|
|
|
{ |
299
|
|
|
if ($i !== 0) |
300
|
|
|
{ |
301
|
|
|
$this->jsonBinaryDecoderFormatter->formatNextEntry(); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/* @var JsonBinaryDecoderValue[] $entries */ |
305
|
|
|
$this->assignValues($entries[$i]); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$this->jsonBinaryDecoderFormatter->formatEndArray(); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
private function parseBoolean() |
312
|
|
|
{ |
313
|
|
|
$r = $this->readLiteral(); |
314
|
|
|
if (null === $r) |
315
|
|
|
{ |
316
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNull(); |
317
|
|
|
} |
318
|
|
|
else |
319
|
|
|
{ |
320
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueBool($r); |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
private function parseScalar($type) |
325
|
|
|
{ |
326
|
|
|
if (self::LITERAL === $type) |
327
|
|
|
{ |
328
|
|
|
$this->parseBoolean(); |
329
|
|
|
} |
330
|
|
|
else if (self::INT16 === $type) |
331
|
|
|
{ |
332
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16()); |
333
|
|
|
} |
334
|
|
|
else if (self::INT32 === $type) |
335
|
|
|
{ |
336
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32()); |
337
|
|
|
} |
338
|
|
|
else if (self::INT64 === $type) |
339
|
|
|
{ |
340
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64()); |
341
|
|
|
} |
342
|
|
|
else if (self::UINT16 === $type) |
343
|
|
|
{ |
344
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16()); |
345
|
|
|
} |
346
|
|
|
else if (self::UINT64 === $type) |
347
|
|
|
{ |
348
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64()); |
349
|
|
|
} |
350
|
|
|
else if (self::DOUBLE === $type) |
351
|
|
|
{ |
352
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble()); |
353
|
|
|
} |
354
|
|
|
else if (self::STRING === $type) |
355
|
|
|
{ |
356
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue( |
357
|
|
|
$this->binaryDataReader->read($this->readVariableInt()) |
358
|
|
|
); |
359
|
|
|
} |
360
|
|
|
/** |
361
|
|
|
* else if (self::OPAQUE === $type) |
362
|
|
|
* { |
363
|
|
|
* |
364
|
|
|
* } |
365
|
|
|
*/ |
366
|
|
|
else |
367
|
|
|
{ |
368
|
|
|
throw new JsonBinaryDecoderException( |
369
|
|
|
JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type, |
370
|
|
|
JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE |
371
|
|
|
); |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.