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(); |
97
|
|
|
} |
98
|
|
|
else if (self::LARGE_OBJECT === $type) |
|
|
|
|
99
|
|
|
{ |
100
|
|
|
//TODO |
101
|
|
|
} |
102
|
|
|
else if (self::SMALL_ARRAY === $type) |
103
|
|
|
{ |
104
|
|
|
$this->parseArray(); |
105
|
|
|
} |
106
|
|
|
else if (self::LARGE_ARRAY === $type) |
|
|
|
|
107
|
|
|
{ |
108
|
|
|
//TODO |
109
|
|
|
} |
110
|
|
|
else |
111
|
|
|
{ |
112
|
|
|
$this->parseScalar($type); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
private function parseObject() |
117
|
|
|
{ |
118
|
|
|
$elementCount = $this->binaryDataReader->readUInt16(); |
119
|
|
|
$size = $this->binaryDataReader->readUInt16(); |
120
|
|
|
|
121
|
|
|
// Read each key-entry, consisting of the offset and length of each key ... |
122
|
|
|
$keyLengths = []; |
123
|
|
View Code Duplication |
for ($i = 0; $i !== $elementCount; ++$i) |
|
|
|
|
124
|
|
|
{ |
125
|
|
|
$this->binaryDataReader->readUInt16(); // $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); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// Read each key ... |
136
|
|
|
$keys = []; |
137
|
|
View Code Duplication |
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
|
|
|
* @return JsonBinaryDecoderValue |
163
|
|
|
* @throws BinaryDataReaderException |
164
|
|
|
* @throws \LengthException |
165
|
|
|
*/ |
166
|
|
|
private function parseValueType($numBytes) |
167
|
|
|
{ |
168
|
|
|
$type = $this->binaryDataReader->readInt8(); |
169
|
|
|
|
170
|
|
|
if (self::LITERAL === $type) |
171
|
|
|
{ |
172
|
|
|
return new JsonBinaryDecoderValue( |
173
|
|
|
true, |
174
|
|
|
$this->readLiteral(), |
175
|
|
|
$type |
176
|
|
|
); |
177
|
|
|
} |
178
|
|
|
else if (self::INT16 === $type) |
179
|
|
|
{ |
180
|
|
|
return new JsonBinaryDecoderValue( |
181
|
|
|
true, |
182
|
|
|
$this->binaryDataReader->readInt16(), |
183
|
|
|
$type |
184
|
|
|
); |
185
|
|
|
} |
186
|
|
|
else if (self::UINT16 === $type) |
187
|
|
|
{ |
188
|
|
|
return new JsonBinaryDecoderValue( |
189
|
|
|
true, |
190
|
|
|
$this->binaryDataReader->readUInt16(), |
191
|
|
|
$type |
192
|
|
|
); |
193
|
|
|
} |
194
|
|
|
else if (self::INT32 === $type) |
195
|
|
|
{ |
196
|
|
|
return new JsonBinaryDecoderValue( |
197
|
|
|
true, |
198
|
|
|
$this->binaryDataReader->readInt32(), |
199
|
|
|
$type |
200
|
|
|
); |
201
|
|
|
} |
202
|
|
|
else if (self::UINT32 === $type) |
203
|
|
|
{ |
204
|
|
|
return new JsonBinaryDecoderValue( |
205
|
|
|
true, |
206
|
|
|
$this->binaryDataReader->readUInt32(), |
207
|
|
|
$type |
208
|
|
|
); |
209
|
|
|
} |
210
|
|
|
else |
211
|
|
|
{ |
212
|
|
|
$offset = $this->binaryDataReader->readUInt16(); |
213
|
|
|
if ($offset > $numBytes) |
214
|
|
|
{ |
215
|
|
|
throw new \LengthException( |
216
|
|
|
'The offset for the value in the JSON binary document is ' . |
217
|
|
|
$offset . |
218
|
|
|
', which is larger than the binary form of the JSON document (' . |
219
|
|
|
$numBytes . ' bytes)' |
220
|
|
|
); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
return new JsonBinaryDecoderValue( |
224
|
|
|
false, |
225
|
|
|
null, |
226
|
|
|
$type |
227
|
|
|
); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* @return bool|null |
233
|
|
|
* @throws BinaryDataReaderException |
234
|
|
|
*/ |
235
|
|
|
private function readLiteral() |
236
|
|
|
{ |
237
|
|
|
$literal = ord($this->binaryDataReader->read(2)); |
238
|
|
|
if (0 === $literal) |
239
|
|
|
{ |
240
|
|
|
return null; |
241
|
|
|
} |
242
|
|
|
else if (1 === $literal) |
243
|
|
|
{ |
244
|
|
|
return true; |
245
|
|
|
} |
246
|
|
|
else if (2 === $literal) |
247
|
|
|
{ |
248
|
|
|
return false; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @param JsonBinaryDecoderValue $jsonBinaryDecoderValue |
254
|
|
|
* @throws JsonBinaryDecoderException |
255
|
|
|
* @throws BinaryDataReaderException |
256
|
|
|
*/ |
257
|
|
|
private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue) |
258
|
|
|
{ |
259
|
|
|
if (false === $jsonBinaryDecoderValue->isIsResolved()) |
260
|
|
|
{ |
261
|
|
|
$this->parseJson($jsonBinaryDecoderValue->getType()); |
262
|
|
|
} |
263
|
|
|
else |
264
|
|
|
{ |
265
|
|
|
if (null === $jsonBinaryDecoderValue->getValue()) |
266
|
|
|
{ |
267
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNull(); |
268
|
|
|
} |
269
|
|
|
elseif (is_bool($jsonBinaryDecoderValue->getValue())) |
270
|
|
|
{ |
271
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue()); |
272
|
|
|
} |
273
|
|
|
elseif (is_numeric($jsonBinaryDecoderValue->getValue())) |
274
|
|
|
{ |
275
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue()); |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
private function parseArray() |
281
|
|
|
{ |
282
|
|
|
$numElements = $this->binaryDataReader->readUInt16(); |
283
|
|
|
$numBytes = $this->binaryDataReader->readUInt16(); |
284
|
|
|
|
285
|
|
|
$entries = []; |
286
|
|
|
for ($i = 0; $i !== $numElements; ++$i) |
287
|
|
|
{ |
288
|
|
|
$entries[$i] = $this->parseValueType($numBytes); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
$this->jsonBinaryDecoderFormatter->formatBeginArray(); |
292
|
|
|
|
293
|
|
View Code Duplication |
for ($i = 0; $i !== $numElements; ++$i) |
|
|
|
|
294
|
|
|
{ |
295
|
|
|
if ($i !== 0) |
296
|
|
|
{ |
297
|
|
|
$this->jsonBinaryDecoderFormatter->formatNextEntry(); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/* @var JsonBinaryDecoderValue[] $entries */ |
301
|
|
|
$this->assignValues($entries[$i]); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$this->jsonBinaryDecoderFormatter->formatEndArray(); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
private function parseBoolean() |
308
|
|
|
{ |
309
|
|
|
$r = $this->readLiteral(); |
310
|
|
|
if (null === $r) |
311
|
|
|
{ |
312
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueNull(); |
313
|
|
|
} |
314
|
|
|
else |
315
|
|
|
{ |
316
|
|
|
$this->jsonBinaryDecoderFormatter->formatValueBool($r); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
private function parseScalar($type) |
321
|
|
|
{ |
322
|
|
|
if (self::LITERAL === $type) |
323
|
|
|
{ |
324
|
|
|
$this->parseBoolean(); |
325
|
|
|
} |
326
|
|
|
else if (self::INT16 === $type) |
327
|
|
|
{ |
328
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16()); |
329
|
|
|
} |
330
|
|
|
else if (self::INT32 === $type) |
331
|
|
|
{ |
332
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32()); |
333
|
|
|
} |
334
|
|
|
else if (self::INT64 === $type) |
335
|
|
|
{ |
336
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64()); |
337
|
|
|
} |
338
|
|
|
else if (self::UINT16 === $type) |
339
|
|
|
{ |
340
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16()); |
341
|
|
|
} |
342
|
|
|
else if (self::UINT64 === $type) |
343
|
|
|
{ |
344
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64()); |
345
|
|
|
} |
346
|
|
|
else if (self::DOUBLE === $type) |
347
|
|
|
{ |
348
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble()); |
349
|
|
|
} |
350
|
|
|
else if (self::STRING === $type) |
351
|
|
|
{ |
352
|
|
|
$this->jsonBinaryDecoderFormatter->formatValue( |
353
|
|
|
$this->binaryDataReader->read($this->readVariableInt()) |
354
|
|
|
); |
355
|
|
|
} |
356
|
|
|
/** |
357
|
|
|
* else if (self::OPAQUE === $type) |
358
|
|
|
* { |
359
|
|
|
* |
360
|
|
|
* } |
361
|
|
|
*/ |
362
|
|
|
else |
363
|
|
|
{ |
364
|
|
|
throw new JsonBinaryDecoderException( |
365
|
|
|
JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type, |
366
|
|
|
JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE |
367
|
|
|
); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
} |
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.