Passed
Pull Request — master (#53)
by kacper
04:21
created

RowEvent   F

Complexity

Total Complexity 113

Size/Duplication

Total Lines 904
Duplicated Lines 0 %

Test Coverage

Coverage 91.41%

Importance

Changes 12
Bugs 0 Features 0
Metric Value
eloc 319
c 12
b 0
f 0
dl 0
loc 904
ccs 298
cts 326
cp 0.9141
rs 2
wmc 113

25 Methods

Rating   Name   Duplication   Size   Complexity  
A bitGet() 0 3 1
A getValues() 0 17 4
A getTime() 0 8 3
B getFSP() 0 22 9
A getSet() 0 12 3
A makeDeleteRowsDTO() 0 13 2
B getBit() 0 31 7
B makeTableMapDTO() 0 63 8
A getDatetime() 0 14 3
B getDecimal() 0 45 6
A getEnum() 0 10 2
A getString() 0 3 1
A makeWriteRowsDTO() 0 13 2
A getDate() 0 15 5
A makeUpdateRowsDTO() 0 23 3
A __construct() 0 10 1
A rowInit() 0 25 3
A getDatetime2() 0 23 3
A bitCount() 0 13 3
D getColumnData() 0 104 37
A getColumnsBinarySize() 0 3 1
A getTime2() 0 9 1
A getTimestamp2() 0 9 2
A getBitFromBitmap() 0 8 2
A checkNull() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like RowEvent often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RowEvent, and based on these observations, apply Extract Interface, too.

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)) {
353 53
                    $type = ord($data['column_types'][$offset]);
354
                } else {
355 1
                    $fieldDTOCollection->offsetSet($offset, FieldDTO::makeDummy($offset));
356 1
                    $type = ConstFieldType::IGNORE;
357
                }
358
359
                /** @var FieldDTO $fieldDTO */
360 53
                $fieldDTO = $fieldDTOCollection->offsetGet($offset);
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 54
            $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 53
            $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->getPrecision());
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
712
        try {
713 4
            $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
714
        } catch (Exception $exception) {
715
            return null;
716
        }
717 4
        if (array_sum(DateTime::getLastErrors()) > 0) {
718 3
            return null;
719
        }
720
721 1
        return $date->format('Y-m-d H:i:s') . $this->getFSP($columnDTO);
722
    }
723
724
    /**
725
     * @throws BinaryDataReaderException
726
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
727
     */
728 5
    protected function getFSP(ColumnDTO $columnDTO): string
729
    {
730 5
        $read = 0;
731 5
        $time = '';
732 5
        $fsp = $columnDTO->getFsp();
733 5
        if ($fsp === 1 || $fsp === 2) {
734 1
            $read = 1;
735 5
        } else if ($fsp === 3 || $fsp === 4) {
736 1
            $read = 2;
737 5
        } else if ($fsp === 5 || $fsp === 6) {
738 1
            $read = 3;
739
        }
740 5
        if ($read > 0) {
741 1
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
742 1
            if ($fsp % 2) {
743 1
                $microsecond = (int)($microsecond / 10);
744
745
            }
746 1
            $time = $microsecond * (10 ** (6 - $fsp));
747
        }
748
749 5
        return (string)$time;
750
    }
751
752
    protected function getTime(): string
753
    {
754
        $data = $this->binaryDataReader->readUInt24();
755
        if (0 === $data) {
756
            return '00:00:00';
757
        }
758
759
        return sprintf('%s%02d:%02d:%02d', $data < 0 ? '-' : '', $data / 10000, ($data % 10000) / 100, $data % 100);
760
    }
761
762
    /**
763
     * TIME encoding for non fractional part:
764
     * 1 bit sign    (1= non-negative, 0= negative)
765
     * 1 bit unused  (reserved for future extensions)
766
     * 10 bits hour   (0-838)
767
     * 6 bits minute (0-59)
768
     * 6 bits second (0-59)
769
     * ---------------------
770
     * 24 bits = 3 bytes
771
     *
772
     * @throws BinaryDataReaderException
773
     */
774 2
    protected function getTime2(ColumnDTO $columnDTO): string
775
    {
776 2
        $data = $this->binaryDataReader->readInt24Be();
777
778 2
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
779 2
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
780 2
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
781
782 2
        return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO);
783
    }
784
785
    /**
786
     * @throws BinaryDataReaderException
787
     */
788 2
    protected function getTimestamp2(ColumnDTO $columnDTO): string
789
    {
790 2
        $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
791 2
        $fsp = $this->getFSP($columnDTO);
792 2
        if ('' !== $fsp) {
793 1
            $datetime .= '.' . $fsp;
794
        }
795
796 2
        return $datetime;
797
    }
798
799 4
    protected function getDate(): ?string
800
    {
801 4
        $time = $this->binaryDataReader->readUInt24();
802 4
        if (0 === $time) {
803
            return null;
804
        }
805
806 4
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
807 4
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
808 4
        $day = ($time & ((1 << 5) - 1));
809 4
        if ($year === 0 || $month === 0 || $day === 0) {
810 3
            return null;
811
        }
812
813 1
        return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d');
814
    }
815
816
    /**
817
     * @throws BinaryDataReaderException
818
     */
819 1
    protected function getEnum(ColumnDTO $columnDTO): string
820
    {
821 1
        $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1;
822
823
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
824 1
        if (array_key_exists($value, $columnDTO->getEnumValues())) {
825 1
            return $columnDTO->getEnumValues()[$value];
826
        }
827
828 1
        return '';
829
    }
830
831
    /**
832
     * @throws BinaryDataReaderException
833
     */
834 1
    protected function getSet(ColumnDTO $columnDTO): array
835
    {
836
        // we read set columns as a bitmap telling us which options are enabled
837 1
        $bit_mask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize());
838 1
        $sets = [];
839 1
        foreach ($columnDTO->getSetValues() as $k => $item) {
840 1
            if ($bit_mask & (2 ** $k)) {
841 1
                $sets[] = $item;
842
            }
843
        }
844
845 1
        return $sets;
846
    }
847
848 1
    protected function getBit(ColumnDTO $columnDTO): string
849
    {
850 1
        $res = '';
851 1
        for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) {
852 1
            $current_byte = '';
853 1
            $data = $this->binaryDataReader->readUInt8();
854 1
            if (0 === $byte) {
855 1
                if (1 === $columnDTO->getBytes()) {
856 1
                    $end = $columnDTO->getBits();
857
                } else {
858 1
                    $end = $columnDTO->getBits() % 8;
859 1
                    if (0 === $end) {
860 1
                        $end = 8;
861
                    }
862
                }
863
            } else {
864 1
                $end = 8;
865
            }
866
867 1
            for ($bit = 0; $bit < $end; ++$bit) {
868 1
                if ($data & (1 << $bit)) {
869 1
                    $current_byte .= '1';
870
                } else {
871 1
                    $current_byte .= '0';
872
                }
873
874
            }
875 1
            $res .= strrev($current_byte);
876
        }
877
878 1
        return $res;
879
    }
880
881
    /**
882
     * @throws InvalidArgumentException
883
     * @throws BinaryDataReaderException
884
     * @throws JsonBinaryDecoderException
885
     * @throws MySQLReplicationException
886
     */
887 1
    public function makeDeleteRowsDTO(): ?DeleteRowsDTO
888
    {
889 1
        if (!$this->rowInit()) {
890
            return null;
891
        }
892
893 1
        $values = $this->getValues();
894
895 1
        return new DeleteRowsDTO(
896 1
            $this->eventInfo,
897 1
            $this->currentTableMap,
898 1
            count($values),
899 1
            $values
900
        );
901
    }
902
903
    /**
904
     * @throws InvalidArgumentException
905
     * @throws BinaryDataReaderException
906
     * @throws JsonBinaryDecoderException
907
     * @throws MySQLReplicationException
908
     */
909 1
    public function makeUpdateRowsDTO(): ?UpdateRowsDTO
910
    {
911 1
        if (!$this->rowInit()) {
912
            return null;
913
        }
914
915 1
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
916 1
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
917 1
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
918
919 1
        $values = [];
920 1
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
921 1
            $values[] = [
922 1
                'before' => $this->getColumnData($beforeBinaryData),
923 1
                'after' => $this->getColumnData($afterBinaryData)
924
            ];
925
        }
926
927 1
        return new UpdateRowsDTO(
928 1
            $this->eventInfo,
929 1
            $this->currentTableMap,
930 1
            count($values),
931 1
            $values
932
        );
933
    }
934
}
935