1 | <?php |
||||
2 | declare(strict_types=1); |
||||
3 | |||||
4 | namespace MySQLReplication\Event\RowEvent; |
||||
5 | |||||
6 | use DateTime; |
||||
7 | use Exception; |
||||
8 | use MySQLReplication\BinaryDataReader\BinaryDataReader; |
||||
9 | use MySQLReplication\BinaryDataReader\BinaryDataReaderException; |
||||
10 | use MySQLReplication\Config\Config; |
||||
11 | use MySQLReplication\Definitions\ConstEventType; |
||||
12 | use MySQLReplication\Definitions\ConstFieldType; |
||||
13 | use MySQLReplication\Event\DTO\DeleteRowsDTO; |
||||
14 | use MySQLReplication\Event\DTO\TableMapDTO; |
||||
15 | use MySQLReplication\Event\DTO\UpdateRowsDTO; |
||||
16 | use MySQLReplication\Event\DTO\WriteRowsDTO; |
||||
17 | use MySQLReplication\Event\EventCommon; |
||||
18 | use MySQLReplication\Event\EventInfo; |
||||
19 | use MySQLReplication\Exception\MySQLReplicationException; |
||||
20 | use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderException; |
||||
21 | use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderService; |
||||
22 | use MySQLReplication\Repository\FieldDTO; |
||||
23 | use MySQLReplication\Repository\RepositoryInterface; |
||||
24 | use Psr\SimpleCache\CacheInterface; |
||||
25 | use Psr\SimpleCache\InvalidArgumentException; |
||||
26 | use RuntimeException; |
||||
27 | |||||
28 | class RowEvent extends EventCommon |
||||
29 | { |
||||
30 | private static $bitCountInByte = [ |
||||
31 | 0, |
||||
32 | 1, |
||||
33 | 1, |
||||
34 | 2, |
||||
35 | 1, |
||||
36 | 2, |
||||
37 | 2, |
||||
38 | 3, |
||||
39 | 1, |
||||
40 | 2, |
||||
41 | 2, |
||||
42 | 3, |
||||
43 | 2, |
||||
44 | 3, |
||||
45 | 3, |
||||
46 | 4, |
||||
47 | 1, |
||||
48 | 2, |
||||
49 | 2, |
||||
50 | 3, |
||||
51 | 2, |
||||
52 | 3, |
||||
53 | 3, |
||||
54 | 4, |
||||
55 | 2, |
||||
56 | 3, |
||||
57 | 3, |
||||
58 | 4, |
||||
59 | 3, |
||||
60 | 4, |
||||
61 | 4, |
||||
62 | 5, |
||||
63 | 1, |
||||
64 | 2, |
||||
65 | 2, |
||||
66 | 3, |
||||
67 | 2, |
||||
68 | 3, |
||||
69 | 3, |
||||
70 | 4, |
||||
71 | 2, |
||||
72 | 3, |
||||
73 | 3, |
||||
74 | 4, |
||||
75 | 3, |
||||
76 | 4, |
||||
77 | 4, |
||||
78 | 5, |
||||
79 | 2, |
||||
80 | 3, |
||||
81 | 3, |
||||
82 | 4, |
||||
83 | 3, |
||||
84 | 4, |
||||
85 | 4, |
||||
86 | 5, |
||||
87 | 3, |
||||
88 | 4, |
||||
89 | 4, |
||||
90 | 5, |
||||
91 | 4, |
||||
92 | 5, |
||||
93 | 5, |
||||
94 | 6, |
||||
95 | 1, |
||||
96 | 2, |
||||
97 | 2, |
||||
98 | 3, |
||||
99 | 2, |
||||
100 | 3, |
||||
101 | 3, |
||||
102 | 4, |
||||
103 | 2, |
||||
104 | 3, |
||||
105 | 3, |
||||
106 | 4, |
||||
107 | 3, |
||||
108 | 4, |
||||
109 | 4, |
||||
110 | 5, |
||||
111 | 2, |
||||
112 | 3, |
||||
113 | 3, |
||||
114 | 4, |
||||
115 | 3, |
||||
116 | 4, |
||||
117 | 4, |
||||
118 | 5, |
||||
119 | 3, |
||||
120 | 4, |
||||
121 | 4, |
||||
122 | 5, |
||||
123 | 4, |
||||
124 | 5, |
||||
125 | 5, |
||||
126 | 6, |
||||
127 | 2, |
||||
128 | 3, |
||||
129 | 3, |
||||
130 | 4, |
||||
131 | 3, |
||||
132 | 4, |
||||
133 | 4, |
||||
134 | 5, |
||||
135 | 3, |
||||
136 | 4, |
||||
137 | 4, |
||||
138 | 5, |
||||
139 | 4, |
||||
140 | 5, |
||||
141 | 5, |
||||
142 | 6, |
||||
143 | 3, |
||||
144 | 4, |
||||
145 | 4, |
||||
146 | 5, |
||||
147 | 4, |
||||
148 | 5, |
||||
149 | 5, |
||||
150 | 6, |
||||
151 | 4, |
||||
152 | 5, |
||||
153 | 5, |
||||
154 | 6, |
||||
155 | 5, |
||||
156 | 6, |
||||
157 | 6, |
||||
158 | 7, |
||||
159 | 1, |
||||
160 | 2, |
||||
161 | 2, |
||||
162 | 3, |
||||
163 | 2, |
||||
164 | 3, |
||||
165 | 3, |
||||
166 | 4, |
||||
167 | 2, |
||||
168 | 3, |
||||
169 | 3, |
||||
170 | 4, |
||||
171 | 3, |
||||
172 | 4, |
||||
173 | 4, |
||||
174 | 5, |
||||
175 | 2, |
||||
176 | 3, |
||||
177 | 3, |
||||
178 | 4, |
||||
179 | 3, |
||||
180 | 4, |
||||
181 | 4, |
||||
182 | 5, |
||||
183 | 3, |
||||
184 | 4, |
||||
185 | 4, |
||||
186 | 5, |
||||
187 | 4, |
||||
188 | 5, |
||||
189 | 5, |
||||
190 | 6, |
||||
191 | 2, |
||||
192 | 3, |
||||
193 | 3, |
||||
194 | 4, |
||||
195 | 3, |
||||
196 | 4, |
||||
197 | 4, |
||||
198 | 5, |
||||
199 | 3, |
||||
200 | 4, |
||||
201 | 4, |
||||
202 | 5, |
||||
203 | 4, |
||||
204 | 5, |
||||
205 | 5, |
||||
206 | 6, |
||||
207 | 3, |
||||
208 | 4, |
||||
209 | 4, |
||||
210 | 5, |
||||
211 | 4, |
||||
212 | 5, |
||||
213 | 5, |
||||
214 | 6, |
||||
215 | 4, |
||||
216 | 5, |
||||
217 | 5, |
||||
218 | 6, |
||||
219 | 5, |
||||
220 | 6, |
||||
221 | 6, |
||||
222 | 7, |
||||
223 | 2, |
||||
224 | 3, |
||||
225 | 3, |
||||
226 | 4, |
||||
227 | 3, |
||||
228 | 4, |
||||
229 | 4, |
||||
230 | 5, |
||||
231 | 3, |
||||
232 | 4, |
||||
233 | 4, |
||||
234 | 5, |
||||
235 | 4, |
||||
236 | 5, |
||||
237 | 5, |
||||
238 | 6, |
||||
239 | 3, |
||||
240 | 4, |
||||
241 | 4, |
||||
242 | 5, |
||||
243 | 4, |
||||
244 | 5, |
||||
245 | 5, |
||||
246 | 6, |
||||
247 | 4, |
||||
248 | 5, |
||||
249 | 5, |
||||
250 | 6, |
||||
251 | 5, |
||||
252 | 6, |
||||
253 | 6, |
||||
254 | 7, |
||||
255 | 3, |
||||
256 | 4, |
||||
257 | 4, |
||||
258 | 5, |
||||
259 | 4, |
||||
260 | 5, |
||||
261 | 5, |
||||
262 | 6, |
||||
263 | 4, |
||||
264 | 5, |
||||
265 | 5, |
||||
266 | 6, |
||||
267 | 5, |
||||
268 | 6, |
||||
269 | 6, |
||||
270 | 7, |
||||
271 | 4, |
||||
272 | 5, |
||||
273 | 5, |
||||
274 | 6, |
||||
275 | 5, |
||||
276 | 6, |
||||
277 | 6, |
||||
278 | 7, |
||||
279 | 5, |
||||
280 | 6, |
||||
281 | 6, |
||||
282 | 7, |
||||
283 | 6, |
||||
284 | 7, |
||||
285 | 7, |
||||
286 | 8, |
||||
287 | ]; |
||||
288 | private $repository; |
||||
289 | private $cache; |
||||
290 | |||||
291 | /** |
||||
292 | * @var TableMap|null |
||||
293 | */ |
||||
294 | private $currentTableMap; |
||||
295 | |||||
296 | 54 | public function __construct( |
|||
297 | RepositoryInterface $repository, |
||||
298 | BinaryDataReader $binaryDataReader, |
||||
299 | EventInfo $eventInfo, |
||||
300 | CacheInterface $cache |
||||
301 | ) { |
||||
302 | 54 | parent::__construct($eventInfo, $binaryDataReader); |
|||
303 | |||||
304 | 54 | $this->repository = $repository; |
|||
305 | 54 | $this->cache = $cache; |
|||
306 | 54 | } |
|||
307 | |||||
308 | /** |
||||
309 | * This describe the structure of a table. |
||||
310 | * It's send before a change append on a table. |
||||
311 | * A end user of the lib should have no usage of this |
||||
312 | * @throws BinaryDataReaderException |
||||
313 | * @throws InvalidArgumentException |
||||
314 | */ |
||||
315 | 54 | public function makeTableMapDTO(): ?TableMapDTO |
|||
316 | { |
||||
317 | 54 | $data = []; |
|||
318 | 54 | $data['table_id'] = $this->binaryDataReader->readTableId(); |
|||
319 | 54 | $this->binaryDataReader->advance(2); |
|||
320 | 54 | $data['schema_length'] = $this->binaryDataReader->readUInt8(); |
|||
321 | 54 | $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']); |
|||
322 | |||||
323 | 54 | if (Config::checkDataBasesOnly($data['schema_name'])) { |
|||
324 | return null; |
||||
325 | } |
||||
326 | |||||
327 | 54 | $this->binaryDataReader->advance(1); |
|||
328 | 54 | $data['table_length'] = $this->binaryDataReader->readUInt8(); |
|||
329 | 54 | $data['table_name'] = $this->binaryDataReader->read($data['table_length']); |
|||
330 | |||||
331 | 54 | if (Config::checkTablesOnly($data['table_name'])) { |
|||
332 | 1 | return null; |
|||
333 | } |
||||
334 | |||||
335 | 54 | $this->binaryDataReader->advance(1); |
|||
336 | 54 | $data['columns_amount'] = (int)$this->binaryDataReader->readCodedBinary(); |
|||
337 | 54 | $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']); |
|||
338 | |||||
339 | 54 | if ($this->cache->has($data['table_id'])) { |
|||
340 | 2 | return new TableMapDTO($this->eventInfo, $this->cache->get($data['table_id'])); |
|||
341 | } |
||||
342 | |||||
343 | 54 | $this->binaryDataReader->readCodedBinary(); |
|||
344 | |||||
345 | 54 | $fieldDTOCollection = $this->repository->getFields($data['schema_name'], $data['table_name']); |
|||
346 | 54 | $columnDTOCollection = new ColumnDTOCollection(); |
|||
347 | // if you drop tables and parse of logs you will get empty scheme |
||||
348 | 54 | if (!$fieldDTOCollection->isEmpty()) { |
|||
349 | 53 | $columnLength = strlen($data['column_types']); |
|||
350 | 53 | for ($offset = 0; $offset < $columnLength; ++$offset) { |
|||
351 | // this a dirty hack to prevent row events containing columns which have been dropped |
||||
352 | 53 | if ($fieldDTOCollection->offsetExists($offset)) { |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
353 | 53 | $type = ord($data['column_types'][$offset]); |
|||
354 | } else { |
||||
355 | 1 | $fieldDTOCollection->offsetSet($offset, FieldDTO::makeDummy($offset)); |
|||
0 ignored issues
–
show
$offset of type integer is incompatible with the type Doctrine\Common\Collections\TKey|null expected by parameter $offset of Doctrine\Common\Collecti...Collection::offsetSet() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
356 | 1 | $type = ConstFieldType::IGNORE; |
|||
357 | } |
||||
358 | |||||
359 | /** @var FieldDTO $fieldDTO */ |
||||
360 | 53 | $fieldDTO = $fieldDTOCollection->offsetGet($offset); |
|||
0 ignored issues
–
show
$offset of type integer is incompatible with the type Doctrine\Common\Collections\TKey expected by parameter $offset of Doctrine\Common\Collecti...Collection::offsetGet() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
361 | 53 | if (null !== $fieldDTO) { |
|||
362 | 53 | $columnDTOCollection->set($offset, ColumnDTO::make($type, $fieldDTO, $this->binaryDataReader)); |
|||
363 | } |
||||
364 | } |
||||
365 | } |
||||
366 | |||||
367 | 54 | $tableMap = new TableMap( |
|||
368 | 54 | $data['schema_name'], |
|||
369 | 54 | $data['table_name'], |
|||
370 | 54 | $data['table_id'], |
|||
371 | 54 | $data['columns_amount'], |
|||
372 | $columnDTOCollection |
||||
373 | ); |
||||
374 | |||||
375 | 54 | $this->cache->set($data['table_id'], $tableMap); |
|||
376 | |||||
377 | 54 | return new TableMapDTO($this->eventInfo, $tableMap); |
|||
378 | } |
||||
379 | |||||
380 | /** |
||||
381 | * @throws BinaryDataReaderException |
||||
382 | * @throws InvalidArgumentException |
||||
383 | * @throws JsonBinaryDecoderException |
||||
384 | * @throws MySQLReplicationException |
||||
385 | */ |
||||
386 | 53 | public function makeWriteRowsDTO(): ?WriteRowsDTO |
|||
387 | { |
||||
388 | 53 | if (!$this->rowInit()) { |
|||
389 | 1 | return null; |
|||
390 | } |
||||
391 | |||||
392 | 53 | $values = $this->getValues(); |
|||
393 | |||||
394 | 53 | return new WriteRowsDTO( |
|||
395 | 53 | $this->eventInfo, |
|||
396 | 53 | $this->currentTableMap, |
|||
397 | 53 | count($values), |
|||
398 | $values |
||||
399 | ); |
||||
400 | } |
||||
401 | |||||
402 | /** |
||||
403 | * @throws InvalidArgumentException |
||||
404 | * @throws BinaryDataReaderException |
||||
405 | */ |
||||
406 | 53 | protected function rowInit(): bool |
|||
407 | { |
||||
408 | 53 | $tableId = $this->binaryDataReader->readTableId(); |
|||
409 | 53 | $this->binaryDataReader->advance(2); |
|||
410 | |||||
411 | 53 | if (in_array( |
|||
412 | 53 | $this->eventInfo->getType(), [ |
|||
413 | 53 | ConstEventType::DELETE_ROWS_EVENT_V2, |
|||
414 | ConstEventType::WRITE_ROWS_EVENT_V2, |
||||
415 | ConstEventType::UPDATE_ROWS_EVENT_V2 |
||||
416 | 53 | ], true |
|||
417 | )) { |
||||
418 | 53 | $this->binaryDataReader->read((int)($this->binaryDataReader->readUInt16() / 8)); |
|||
419 | } |
||||
420 | |||||
421 | 53 | $this->binaryDataReader->readCodedBinary(); |
|||
422 | |||||
423 | 53 | if ($this->cache->has($tableId)) { |
|||
424 | /** @var TableMap $tableMap */ |
||||
425 | 53 | $this->currentTableMap = $this->cache->get($tableId); |
|||
426 | |||||
427 | 53 | return true; |
|||
428 | } |
||||
429 | |||||
430 | 1 | return false; |
|||
431 | } |
||||
432 | |||||
433 | /** |
||||
434 | * @throws BinaryDataReaderException |
||||
435 | * @throws JsonBinaryDecoderException |
||||
436 | * @throws MySQLReplicationException |
||||
437 | */ |
||||
438 | 53 | protected function getValues(): array |
|||
439 | { |
||||
440 | // if we don't get columns from information schema we don't know how to assign them |
||||
441 | 53 | if ($this->currentTableMap === null || $this->currentTableMap->getColumnDTOCollection()->isEmpty()) { |
|||
442 | 1 | return []; |
|||
443 | } |
||||
444 | |||||
445 | 52 | $binaryData = $this->binaryDataReader->read( |
|||
446 | 52 | $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()) |
|||
447 | ); |
||||
448 | |||||
449 | 52 | $values = []; |
|||
450 | 52 | while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) { |
|||
451 | 52 | $values[] = $this->getColumnData($binaryData); |
|||
452 | } |
||||
453 | |||||
454 | 52 | return $values; |
|||
455 | } |
||||
456 | |||||
457 | 52 | protected function getColumnsBinarySize(int $columnsAmount): int |
|||
458 | { |
||||
459 | 52 | return (int)(($columnsAmount + 7) / 8); |
|||
460 | } |
||||
461 | |||||
462 | /** |
||||
463 | * @throws BinaryDataReaderException |
||||
464 | * @throws JsonBinaryDecoderException |
||||
465 | * @throws MySQLReplicationException |
||||
466 | */ |
||||
467 | 52 | protected function getColumnData(string $colsBitmap): array |
|||
468 | { |
||||
469 | 52 | if (null === $this->currentTableMap) { |
|||
470 | throw new RuntimeException('Current table map is missing!'); |
||||
471 | } |
||||
472 | |||||
473 | 52 | $values = []; |
|||
474 | |||||
475 | // null bitmap length = (bits set in 'columns-present-bitmap'+7)/8 |
||||
476 | // see http://dev.mysql.com/doc/internals/en/rows-event.html |
||||
477 | 52 | $nullBitmap = $this->binaryDataReader->read($this->getColumnsBinarySize($this->bitCount($colsBitmap))); |
|||
478 | 52 | $nullBitmapIndex = 0; |
|||
479 | |||||
480 | 52 | foreach ($this->currentTableMap->getColumnDTOCollection() as $i => $columnDTO) { |
|||
481 | 52 | $name = $columnDTO->getName(); |
|||
482 | 52 | $type = $columnDTO->getType(); |
|||
483 | |||||
484 | 52 | if (0 === $this->bitGet($colsBitmap, $i)) { |
|||
485 | $values[$name] = null; |
||||
486 | continue; |
||||
487 | } |
||||
488 | |||||
489 | 52 | if ($this->checkNull($nullBitmap, $nullBitmapIndex)) { |
|||
490 | 6 | $values[$name] = null; |
|||
491 | 51 | } else if ($type === ConstFieldType::IGNORE) { |
|||
492 | 1 | $this->binaryDataReader->advance($columnDTO->getLengthSize()); |
|||
493 | 1 | $values[$name] = null; |
|||
494 | 51 | } else if ($type === ConstFieldType::TINY) { |
|||
495 | 5 | if ($columnDTO->isUnsigned()) { |
|||
496 | 4 | $values[$name] = $this->binaryDataReader->readUInt8(); |
|||
497 | } else { |
||||
498 | 5 | $values[$name] = $this->binaryDataReader->readInt8(); |
|||
499 | } |
||||
500 | 46 | } else if ($type === ConstFieldType::SHORT) { |
|||
501 | 1 | if ($columnDTO->isUnsigned()) { |
|||
502 | 1 | $values[$name] = $this->binaryDataReader->readUInt16(); |
|||
503 | } else { |
||||
504 | 1 | $values[$name] = $this->binaryDataReader->readInt16(); |
|||
505 | } |
||||
506 | 45 | } else if ($type === ConstFieldType::LONG) { |
|||
507 | 10 | if ($columnDTO->isUnsigned()) { |
|||
508 | 1 | $values[$name] = $this->binaryDataReader->readUInt32(); |
|||
509 | } else { |
||||
510 | 10 | $values[$name] = $this->binaryDataReader->readInt32(); |
|||
511 | } |
||||
512 | 43 | } else if ($type === ConstFieldType::LONGLONG) { |
|||
513 | 1 | if ($columnDTO->isUnsigned()) { |
|||
514 | 1 | $values[$name] = $this->binaryDataReader->readUInt64(); |
|||
515 | } else { |
||||
516 | 1 | $values[$name] = $this->binaryDataReader->readInt64(); |
|||
517 | } |
||||
518 | 42 | } else if ($type === ConstFieldType::INT24) { |
|||
519 | 1 | if ($columnDTO->isUnsigned()) { |
|||
520 | 1 | $values[$name] = $this->binaryDataReader->readUInt24(); |
|||
521 | } else { |
||||
522 | 1 | $values[$name] = $this->binaryDataReader->readInt24(); |
|||
523 | } |
||||
524 | 41 | } else if ($type === ConstFieldType::FLOAT) { |
|||
525 | // http://dev.mysql.com/doc/refman/5.7/en/floating-point-types.html FLOAT(7,4) |
||||
526 | 1 | $values[$name] = round($this->binaryDataReader->readFloat(), 4); |
|||
527 | 40 | } else if ($type === ConstFieldType::DOUBLE) { |
|||
528 | 1 | $values[$name] = $this->binaryDataReader->readDouble(); |
|||
529 | 39 | } else if ($type === ConstFieldType::VARCHAR || $type === ConstFieldType::STRING) { |
|||
530 | 8 | $values[$name] = $columnDTO->getMaxLength() > 255 ? $this->getString(2) : $this->getString(1); |
|||
531 | 31 | } else if ($type === ConstFieldType::NEWDECIMAL) { |
|||
532 | 10 | $values[$name] = $this->getDecimal($columnDTO); |
|||
533 | 21 | } else if ($type === ConstFieldType::BLOB) { |
|||
534 | 4 | $values[$name] = $this->getString($columnDTO->getLengthSize()); |
|||
535 | 17 | } else if ($type === ConstFieldType::DATETIME) { |
|||
536 | $values[$name] = $this->getDatetime(); |
||||
537 | 17 | } else if ($type === ConstFieldType::DATETIME2) { |
|||
538 | 4 | $values[$name] = $this->getDatetime2($columnDTO); |
|||
539 | 13 | } else if ($type === ConstFieldType::TIMESTAMP) { |
|||
540 | $values[$name] = date('Y-m-d H:i:s', $this->binaryDataReader->readUInt32()); |
||||
541 | 13 | } else if ($type === ConstFieldType::TIME) { |
|||
542 | $values[$name] = $this->getTime(); |
||||
543 | 13 | } else if ($type === ConstFieldType::TIME2) { |
|||
544 | 2 | $values[$name] = $this->getTime2($columnDTO); |
|||
545 | 11 | } else if ($type === ConstFieldType::TIMESTAMP2) { |
|||
546 | 2 | $values[$name] = $this->getTimestamp2($columnDTO); |
|||
547 | 9 | } else if ($type === ConstFieldType::DATE) { |
|||
548 | 4 | $values[$name] = $this->getDate(); |
|||
549 | 5 | } else if ($type === ConstFieldType::YEAR) { |
|||
550 | // https://dev.mysql.com/doc/refman/5.7/en/year.html |
||||
551 | 1 | $year = $this->binaryDataReader->readUInt8(); |
|||
552 | 1 | $values[$name] = 0 === $year ? null : 1900 + $year; |
|||
553 | 4 | } else if ($type === ConstFieldType::ENUM) { |
|||
554 | 1 | $values[$name] = $this->getEnum($columnDTO); |
|||
555 | 3 | } else if ($type === ConstFieldType::SET) { |
|||
556 | 1 | $values[$name] = $this->getSet($columnDTO); |
|||
557 | 2 | } else if ($type === ConstFieldType::BIT) { |
|||
558 | 1 | $values[$name] = $this->getBit($columnDTO); |
|||
559 | 1 | } else if ($type === ConstFieldType::GEOMETRY) { |
|||
560 | 1 | $values[$name] = $this->getString($columnDTO->getLengthSize()); |
|||
561 | } else if ($type === ConstFieldType::JSON) { |
||||
562 | $values[$name] = JsonBinaryDecoderService::makeJsonBinaryDecoder($this->getString($columnDTO->getLengthSize()))->parseToString(); |
||||
563 | } else { |
||||
564 | throw new MySQLReplicationException('Unknown row type: ' . $type); |
||||
565 | } |
||||
566 | |||||
567 | 52 | ++$nullBitmapIndex; |
|||
568 | } |
||||
569 | |||||
570 | 52 | return $values; |
|||
571 | } |
||||
572 | |||||
573 | 52 | protected function bitCount(string $bitmap): int |
|||
574 | { |
||||
575 | 52 | $n = 0; |
|||
576 | 52 | $bitmapLength = strlen($bitmap); |
|||
577 | 52 | for ($i = 0; $i < $bitmapLength; ++$i) { |
|||
578 | 52 | $bit = $bitmap[$i]; |
|||
579 | 52 | if (is_string($bit)) { |
|||
580 | 52 | $bit = ord($bit); |
|||
581 | } |
||||
582 | 52 | $n += self::$bitCountInByte[$bit]; |
|||
583 | } |
||||
584 | |||||
585 | 52 | return $n; |
|||
586 | } |
||||
587 | |||||
588 | 52 | protected function bitGet(string $bitmap, int $position): int |
|||
589 | { |
||||
590 | 52 | return $this->getBitFromBitmap($bitmap, $position) & (1 << ($position & 7)); |
|||
591 | } |
||||
592 | |||||
593 | 52 | protected function getBitFromBitmap(string $bitmap, int $position): int |
|||
594 | { |
||||
595 | 52 | $bit = $bitmap[(int)($position / 8)]; |
|||
596 | 52 | if (is_string($bit)) { |
|||
597 | 52 | $bit = ord($bit); |
|||
598 | } |
||||
599 | |||||
600 | 52 | return $bit; |
|||
601 | } |
||||
602 | |||||
603 | 52 | protected function checkNull(string $nullBitmap, int $position): int |
|||
604 | { |
||||
605 | 52 | return $this->getBitFromBitmap($nullBitmap, $position) & (1 << ($position % 8)); |
|||
606 | } |
||||
607 | |||||
608 | /** |
||||
609 | * @throws BinaryDataReaderException |
||||
610 | */ |
||||
611 | 13 | protected function getString(int $size): string |
|||
612 | { |
||||
613 | 13 | return $this->binaryDataReader->readLengthString($size); |
|||
614 | } |
||||
615 | |||||
616 | /** |
||||
617 | * Read MySQL's new decimal format introduced in MySQL 5 |
||||
618 | * https://dev.mysql.com/doc/refman/5.6/en/precision-math-decimal-characteristics.html |
||||
619 | * @throws BinaryDataReaderException |
||||
620 | */ |
||||
621 | 10 | protected function getDecimal(ColumnDTO $columnDTO): string |
|||
622 | { |
||||
623 | 10 | $digitsPerInteger = 9; |
|||
624 | 10 | $compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]; |
|||
625 | 10 | $integral = $columnDTO->getPrecision() - $columnDTO->getDecimals(); |
|||
626 | 10 | $unCompIntegral = (int)($integral / $digitsPerInteger); |
|||
627 | 10 | $unCompFractional = (int)($columnDTO->getDecimals() / $digitsPerInteger); |
|||
628 | 10 | $compIntegral = $integral - ($unCompIntegral * $digitsPerInteger); |
|||
629 | 10 | $compFractional = $columnDTO->getDecimals() - ($unCompFractional * $digitsPerInteger); |
|||
630 | |||||
631 | 10 | $value = $this->binaryDataReader->readUInt8(); |
|||
632 | 10 | if (0 !== ($value & 0x80)) { |
|||
633 | 7 | $mask = 0; |
|||
634 | 7 | $res = ''; |
|||
635 | } else { |
||||
636 | 3 | $mask = -1; |
|||
637 | 3 | $res = '-'; |
|||
638 | } |
||||
639 | 10 | $this->binaryDataReader->unread(pack('C', $value ^ 0x80)); |
|||
640 | |||||
641 | 10 | $size = $compressedBytes[$compIntegral]; |
|||
642 | 10 | if ($size > 0) { |
|||
643 | 10 | $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask; |
|||
644 | 10 | $res .= $value; |
|||
645 | } |
||||
646 | |||||
647 | 10 | for ($i = 0; $i < $unCompIntegral; ++$i) { |
|||
648 | 9 | $value = $this->binaryDataReader->readInt32Be() ^ $mask; |
|||
649 | 9 | $res .= sprintf('%09d', $value); |
|||
650 | } |
||||
651 | |||||
652 | 10 | $res .= '.'; |
|||
653 | |||||
654 | 10 | for ($i = 0; $i < $unCompFractional; ++$i) { |
|||
655 | 4 | $value = $this->binaryDataReader->readInt32Be() ^ $mask; |
|||
656 | 4 | $res .= sprintf('%09d', $value); |
|||
657 | } |
||||
658 | |||||
659 | 10 | $size = $compressedBytes[$compFractional]; |
|||
660 | 10 | if ($size > 0) { |
|||
661 | 5 | $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask; |
|||
662 | 5 | $res .= sprintf('%0' . $compFractional . 'd', $value); |
|||
663 | } |
||||
664 | |||||
665 | 10 | return bcmul($res, '1', $columnDTO->getDecimals()); |
|||
666 | } |
||||
667 | |||||
668 | protected function getDatetime(): ?string |
||||
669 | { |
||||
670 | $value = $this->binaryDataReader->readUInt64(); |
||||
671 | // nasty mysql 0000-00-00 dates |
||||
672 | if ('0' === $value) { |
||||
673 | return null; |
||||
674 | } |
||||
675 | |||||
676 | $date = DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s'); |
||||
677 | if (array_sum(DateTime::getLastErrors()) > 0) { |
||||
678 | return null; |
||||
679 | } |
||||
680 | |||||
681 | return $date; |
||||
682 | } |
||||
683 | |||||
684 | /** |
||||
685 | * Date Time |
||||
686 | * 1 bit sign (1= non-negative, 0= negative) |
||||
687 | * 17 bits year*13+month (year 0-9999, month 0-12) |
||||
688 | * 5 bits day (0-31) |
||||
689 | * 5 bits hour (0-23) |
||||
690 | * 6 bits minute (0-59) |
||||
691 | * 6 bits second (0-59) |
||||
692 | * --------------------------- |
||||
693 | * 40 bits = 5 bytes |
||||
694 | * |
||||
695 | * @throws BinaryDataReaderException |
||||
696 | * |
||||
697 | * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html |
||||
698 | */ |
||||
699 | 4 | protected function getDatetime2(ColumnDTO $columnDTO): ?string |
|||
700 | { |
||||
701 | 4 | $data = $this->binaryDataReader->readIntBeBySize(5); |
|||
702 | |||||
703 | 4 | $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40); |
|||
704 | |||||
705 | 4 | $year = (int)($yearMonth / 13); |
|||
706 | 4 | $month = $yearMonth % 13; |
|||
707 | 4 | $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40); |
|||
708 | 4 | $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40); |
|||
709 | 4 | $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40); |
|||
710 | 4 | $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40); |
|||
711 | 4 | $fsp = $this->getFSP($columnDTO); |
|||
712 | |||||
713 | try { |
||||
714 | 4 | $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second); |
|||
715 | } catch (Exception $exception) { |
||||
716 | return null; |
||||
717 | } |
||||
718 | 4 | if (array_sum(DateTime::getLastErrors()) > 0) { |
|||
719 | 3 | return null; |
|||
720 | } |
||||
721 | |||||
722 | 1 | return $date->format('Y-m-d H:i:s') . $fsp; |
|||
723 | } |
||||
724 | |||||
725 | /** |
||||
726 | * @throws BinaryDataReaderException |
||||
727 | * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html |
||||
728 | */ |
||||
729 | 8 | protected function getFSP(ColumnDTO $columnDTO): string |
|||
730 | { |
||||
731 | 8 | $read = 0; |
|||
732 | 8 | $time = ''; |
|||
733 | 8 | $fsp = $columnDTO->getFsp(); |
|||
734 | 8 | if ($fsp === 1 || $fsp === 2) { |
|||
735 | 1 | $read = 1; |
|||
736 | 8 | } else if ($fsp === 3 || $fsp === 4) { |
|||
737 | 1 | $read = 2; |
|||
738 | 8 | } else if ($fsp === 5 || $fsp === 6) { |
|||
739 | 1 | $read = 3; |
|||
740 | } |
||||
741 | 8 | if ($read > 0) { |
|||
742 | 1 | $microsecond = $this->binaryDataReader->readIntBeBySize($read); |
|||
743 | 1 | if ($fsp % 2) { |
|||
744 | 1 | $microsecond = (int)($microsecond / 10); |
|||
745 | |||||
746 | } |
||||
747 | 1 | $time = $microsecond * (10 ** (6 - $fsp)); |
|||
748 | } |
||||
749 | |||||
750 | 8 | return (string)$time; |
|||
751 | } |
||||
752 | |||||
753 | protected function getTime(): string |
||||
754 | { |
||||
755 | $data = $this->binaryDataReader->readUInt24(); |
||||
756 | if (0 === $data) { |
||||
757 | return '00:00:00'; |
||||
758 | } |
||||
759 | |||||
760 | return sprintf('%s%02d:%02d:%02d', $data < 0 ? '-' : '', $data / 10000, ($data % 10000) / 100, $data % 100); |
||||
761 | } |
||||
762 | |||||
763 | /** |
||||
764 | * TIME encoding for non fractional part: |
||||
765 | * 1 bit sign (1= non-negative, 0= negative) |
||||
766 | * 1 bit unused (reserved for future extensions) |
||||
767 | * 10 bits hour (0-838) |
||||
768 | * 6 bits minute (0-59) |
||||
769 | * 6 bits second (0-59) |
||||
770 | * --------------------- |
||||
771 | * 24 bits = 3 bytes |
||||
772 | * |
||||
773 | * @throws BinaryDataReaderException |
||||
774 | */ |
||||
775 | 2 | protected function getTime2(ColumnDTO $columnDTO): string |
|||
776 | { |
||||
777 | 2 | $data = $this->binaryDataReader->readInt24Be(); |
|||
778 | |||||
779 | 2 | $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24); |
|||
780 | 2 | $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24); |
|||
781 | 2 | $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24); |
|||
782 | |||||
783 | 2 | return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO); |
|||
784 | } |
||||
785 | |||||
786 | /** |
||||
787 | * @throws BinaryDataReaderException |
||||
788 | */ |
||||
789 | 2 | protected function getTimestamp2(ColumnDTO $columnDTO): string |
|||
790 | { |
||||
791 | 2 | $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be()); |
|||
792 | 2 | $fsp = $this->getFSP($columnDTO); |
|||
793 | 2 | if ('' !== $fsp) { |
|||
794 | 1 | $datetime .= '.' . $fsp; |
|||
795 | } |
||||
796 | |||||
797 | 2 | return $datetime; |
|||
798 | } |
||||
799 | |||||
800 | 4 | protected function getDate(): ?string |
|||
801 | { |
||||
802 | 4 | $time = $this->binaryDataReader->readUInt24(); |
|||
803 | 4 | if (0 === $time) { |
|||
804 | return null; |
||||
805 | } |
||||
806 | |||||
807 | 4 | $year = ($time & ((1 << 15) - 1) << 9) >> 9; |
|||
808 | 4 | $month = ($time & ((1 << 4) - 1) << 5) >> 5; |
|||
809 | 4 | $day = ($time & ((1 << 5) - 1)); |
|||
810 | 4 | if ($year === 0 || $month === 0 || $day === 0) { |
|||
811 | 3 | return null; |
|||
812 | } |
||||
813 | |||||
814 | 1 | return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d'); |
|||
815 | } |
||||
816 | |||||
817 | /** |
||||
818 | * @throws BinaryDataReaderException |
||||
819 | */ |
||||
820 | 1 | protected function getEnum(ColumnDTO $columnDTO): string |
|||
821 | { |
||||
822 | 1 | $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1; |
|||
823 | |||||
824 | // check if given value exists in enums, if there not existing enum mysql returns empty string. |
||||
825 | 1 | if (array_key_exists($value, $columnDTO->getEnumValues())) { |
|||
826 | 1 | return $columnDTO->getEnumValues()[$value]; |
|||
827 | } |
||||
828 | |||||
829 | 1 | return ''; |
|||
830 | } |
||||
831 | |||||
832 | /** |
||||
833 | * @throws BinaryDataReaderException |
||||
834 | */ |
||||
835 | 1 | protected function getSet(ColumnDTO $columnDTO): array |
|||
836 | { |
||||
837 | // we read set columns as a bitmap telling us which options are enabled |
||||
838 | 1 | $bit_mask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()); |
|||
839 | 1 | $sets = []; |
|||
840 | 1 | foreach ($columnDTO->getSetValues() as $k => $item) { |
|||
841 | 1 | if ($bit_mask & (2 ** $k)) { |
|||
842 | 1 | $sets[] = $item; |
|||
843 | } |
||||
844 | } |
||||
845 | |||||
846 | 1 | return $sets; |
|||
847 | } |
||||
848 | |||||
849 | 1 | protected function getBit(ColumnDTO $columnDTO): string |
|||
850 | { |
||||
851 | 1 | $res = ''; |
|||
852 | 1 | for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) { |
|||
853 | 1 | $current_byte = ''; |
|||
854 | 1 | $data = $this->binaryDataReader->readUInt8(); |
|||
855 | 1 | if (0 === $byte) { |
|||
856 | 1 | if (1 === $columnDTO->getBytes()) { |
|||
857 | 1 | $end = $columnDTO->getBits(); |
|||
858 | } else { |
||||
859 | 1 | $end = $columnDTO->getBits() % 8; |
|||
860 | 1 | if (0 === $end) { |
|||
861 | 1 | $end = 8; |
|||
862 | } |
||||
863 | } |
||||
864 | } else { |
||||
865 | 1 | $end = 8; |
|||
866 | } |
||||
867 | |||||
868 | 1 | for ($bit = 0; $bit < $end; ++$bit) { |
|||
869 | 1 | if ($data & (1 << $bit)) { |
|||
870 | 1 | $current_byte .= '1'; |
|||
871 | } else { |
||||
872 | 1 | $current_byte .= '0'; |
|||
873 | } |
||||
874 | |||||
875 | } |
||||
876 | 1 | $res .= strrev($current_byte); |
|||
877 | } |
||||
878 | |||||
879 | 1 | return $res; |
|||
880 | } |
||||
881 | |||||
882 | /** |
||||
883 | * @throws InvalidArgumentException |
||||
884 | * @throws BinaryDataReaderException |
||||
885 | * @throws JsonBinaryDecoderException |
||||
886 | * @throws MySQLReplicationException |
||||
887 | */ |
||||
888 | 1 | public function makeDeleteRowsDTO(): ?DeleteRowsDTO |
|||
889 | { |
||||
890 | 1 | if (!$this->rowInit()) { |
|||
891 | return null; |
||||
892 | } |
||||
893 | |||||
894 | 1 | $values = $this->getValues(); |
|||
895 | |||||
896 | 1 | return new DeleteRowsDTO( |
|||
897 | 1 | $this->eventInfo, |
|||
898 | 1 | $this->currentTableMap, |
|||
899 | 1 | count($values), |
|||
900 | $values |
||||
901 | ); |
||||
902 | } |
||||
903 | |||||
904 | /** |
||||
905 | * @throws InvalidArgumentException |
||||
906 | * @throws BinaryDataReaderException |
||||
907 | * @throws JsonBinaryDecoderException |
||||
908 | * @throws MySQLReplicationException |
||||
909 | */ |
||||
910 | 1 | public function makeUpdateRowsDTO(): ?UpdateRowsDTO |
|||
911 | { |
||||
912 | 1 | if (!$this->rowInit()) { |
|||
913 | return null; |
||||
914 | } |
||||
915 | |||||
916 | 1 | $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount()); |
|||
917 | 1 | $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize); |
|||
918 | 1 | $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize); |
|||
919 | |||||
920 | 1 | $values = []; |
|||
921 | 1 | while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) { |
|||
922 | 1 | $values[] = [ |
|||
923 | 1 | 'before' => $this->getColumnData($beforeBinaryData), |
|||
924 | 1 | 'after' => $this->getColumnData($afterBinaryData) |
|||
925 | ]; |
||||
926 | } |
||||
927 | |||||
928 | 1 | return new UpdateRowsDTO( |
|||
929 | 1 | $this->eventInfo, |
|||
930 | 1 | $this->currentTableMap, |
|||
931 | 1 | count($values), |
|||
932 | $values |
||||
933 | ); |
||||
934 | } |
||||
935 | } |
||||
936 |