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

RowEvent::makeUpdateRowsDTO()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3.0021

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 3
eloc 15
c 6
b 0
f 0
nc 3
nop 0
dl 0
loc 23
ccs 15
cts 16
cp 0.9375
crap 3.0021
rs 9.7666
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
27
class RowEvent extends EventCommon
28
{
29
    private static $bitCountInByte = [
30
        0,
31
        1,
32
        1,
33
        2,
34
        1,
35
        2,
36
        2,
37
        3,
38
        1,
39
        2,
40
        2,
41
        3,
42
        2,
43
        3,
44
        3,
45
        4,
46
        1,
47
        2,
48
        2,
49
        3,
50
        2,
51
        3,
52
        3,
53
        4,
54
        2,
55
        3,
56
        3,
57
        4,
58
        3,
59
        4,
60
        4,
61
        5,
62
        1,
63
        2,
64
        2,
65
        3,
66
        2,
67
        3,
68
        3,
69
        4,
70
        2,
71
        3,
72
        3,
73
        4,
74
        3,
75
        4,
76
        4,
77
        5,
78
        2,
79
        3,
80
        3,
81
        4,
82
        3,
83
        4,
84
        4,
85
        5,
86
        3,
87
        4,
88
        4,
89
        5,
90
        4,
91
        5,
92
        5,
93
        6,
94
        1,
95
        2,
96
        2,
97
        3,
98
        2,
99
        3,
100
        3,
101
        4,
102
        2,
103
        3,
104
        3,
105
        4,
106
        3,
107
        4,
108
        4,
109
        5,
110
        2,
111
        3,
112
        3,
113
        4,
114
        3,
115
        4,
116
        4,
117
        5,
118
        3,
119
        4,
120
        4,
121
        5,
122
        4,
123
        5,
124
        5,
125
        6,
126
        2,
127
        3,
128
        3,
129
        4,
130
        3,
131
        4,
132
        4,
133
        5,
134
        3,
135
        4,
136
        4,
137
        5,
138
        4,
139
        5,
140
        5,
141
        6,
142
        3,
143
        4,
144
        4,
145
        5,
146
        4,
147
        5,
148
        5,
149
        6,
150
        4,
151
        5,
152
        5,
153
        6,
154
        5,
155
        6,
156
        6,
157
        7,
158
        1,
159
        2,
160
        2,
161
        3,
162
        2,
163
        3,
164
        3,
165
        4,
166
        2,
167
        3,
168
        3,
169
        4,
170
        3,
171
        4,
172
        4,
173
        5,
174
        2,
175
        3,
176
        3,
177
        4,
178
        3,
179
        4,
180
        4,
181
        5,
182
        3,
183
        4,
184
        4,
185
        5,
186
        4,
187
        5,
188
        5,
189
        6,
190
        2,
191
        3,
192
        3,
193
        4,
194
        3,
195
        4,
196
        4,
197
        5,
198
        3,
199
        4,
200
        4,
201
        5,
202
        4,
203
        5,
204
        5,
205
        6,
206
        3,
207
        4,
208
        4,
209
        5,
210
        4,
211
        5,
212
        5,
213
        6,
214
        4,
215
        5,
216
        5,
217
        6,
218
        5,
219
        6,
220
        6,
221
        7,
222
        2,
223
        3,
224
        3,
225
        4,
226
        3,
227
        4,
228
        4,
229
        5,
230
        3,
231
        4,
232
        4,
233
        5,
234
        4,
235
        5,
236
        5,
237
        6,
238
        3,
239
        4,
240
        4,
241
        5,
242
        4,
243
        5,
244
        5,
245
        6,
246
        4,
247
        5,
248
        5,
249
        6,
250
        5,
251
        6,
252
        6,
253
        7,
254
        3,
255
        4,
256
        4,
257
        5,
258
        4,
259
        5,
260
        5,
261
        6,
262
        4,
263
        5,
264
        5,
265
        6,
266
        5,
267
        6,
268
        6,
269
        7,
270
        4,
271
        5,
272
        5,
273
        6,
274
        5,
275
        6,
276
        6,
277
        7,
278
        5,
279
        6,
280
        6,
281
        7,
282
        6,
283
        7,
284
        7,
285
        8,
286
    ];
287
    private $repository;
288
    private $cache;
289
290
    /**
291
     * @var TableMap|null
292
     */
293
    private $currentTableMap;
294
295 54
    public function __construct(
296
        RepositoryInterface $repository,
297
        BinaryDataReader $binaryDataReader,
298
        EventInfo $eventInfo,
299
        CacheInterface $cache
300
    ) {
301 54
        parent::__construct($eventInfo, $binaryDataReader);
302
303 54
        $this->repository = $repository;
304 54
        $this->cache = $cache;
305 54
    }
306
307
    /**
308
     * This describe the structure of a table.
309
     * It's send before a change append on a table.
310
     * A end user of the lib should have no usage of this
311
     * @throws BinaryDataReaderException
312
     * @throws InvalidArgumentException
313
     */
314 54
    public function makeTableMapDTO(): ?TableMapDTO
315
    {
316 54
        $data = [];
317 54
        $data['table_id'] = $this->binaryDataReader->readTableId();
318 54
        $this->binaryDataReader->advance(2);
319 54
        $data['schema_length'] = $this->binaryDataReader->readUInt8();
320 54
        $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']);
321
322 54
        if (Config::checkDataBasesOnly($data['schema_name'])) {
323
            return null;
324
        }
325
326 54
        $this->binaryDataReader->advance(1);
327 54
        $data['table_length'] = $this->binaryDataReader->readUInt8();
328 54
        $data['table_name'] = $this->binaryDataReader->read($data['table_length']);
329
330 54
        if (Config::checkTablesOnly($data['table_name'])) {
331 1
            return null;
332
        }
333
334 54
        $this->binaryDataReader->advance(1);
335 54
        $data['columns_amount'] = (int)$this->binaryDataReader->readCodedBinary();
336 54
        $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']);
337
338 54
        if ($this->cache->has($data['table_id'])) {
339 2
            return new TableMapDTO($this->eventInfo, $this->cache->get($data['table_id']));
340
        }
341
342 54
        $this->binaryDataReader->readCodedBinary();
343
344 54
        $fieldDTOCollection = $this->repository->getFields($data['schema_name'], $data['table_name']);
345 54
        $columnDTOCollection = new ColumnDTOCollection();
346
        // if you drop tables and parse of logs you will get empty scheme
347 54
        if (!$fieldDTOCollection->isEmpty()) {
348 53
            $columnLength = strlen($data['column_types']);
349 53
            for ($offset = 0; $offset < $columnLength; ++$offset) {
350
                // this a dirty hack to prevent row events containing columns which have been dropped
351 53
                if ($fieldDTOCollection->offsetExists($offset)) {
352 53
                    $type = ord($data['column_types'][$offset]);
353
                } else {
354 1
                    $fieldDTOCollection->offsetSet($offset, FieldDTO::makeDummy($offset));
355 1
                    $type = ConstFieldType::IGNORE;
356
                }
357
358
                /** @var FieldDTO $fieldDTO */
359 53
                $fieldDTO = $fieldDTOCollection->offsetGet($offset);
360 53
                if (null !== $fieldDTO) {
361 53
                    $columnDTOCollection->set($offset, ColumnDTO::make($type, $fieldDTO, $this->binaryDataReader));
362
                }
363
            }
364
        }
365
366 54
        $tableMap = new TableMap(
367 54
            $data['schema_name'],
368 54
            $data['table_name'],
369 54
            $data['table_id'],
370 54
            $data['columns_amount'],
371 54
            $columnDTOCollection
372
        );
373
374 54
        $this->cache->set($data['table_id'], $tableMap);
375
376 54
        return new TableMapDTO($this->eventInfo, $tableMap);
377
    }
378
379
    /**
380
     * @throws BinaryDataReaderException
381
     * @throws InvalidArgumentException
382
     * @throws JsonBinaryDecoderException
383
     * @throws MySQLReplicationException
384
     */
385 53
    public function makeWriteRowsDTO(): ?WriteRowsDTO
386
    {
387 53
        if (!$this->rowInit()) {
388 1
            return null;
389
        }
390
391 53
        $values = $this->getValues();
392
393 53
        return new WriteRowsDTO(
394 53
            $this->eventInfo,
395 53
            $this->currentTableMap,
396 53
            count($values),
397 53
            $values
398
        );
399
    }
400
401
    /**
402
     * @throws InvalidArgumentException
403
     * @throws BinaryDataReaderException
404
     */
405 53
    protected function rowInit(): bool
406
    {
407 53
        $tableId = $this->binaryDataReader->readTableId();
408 53
        $this->binaryDataReader->advance(2);
409
410 53
        if (in_array(
411 53
            $this->eventInfo->getType(), [
412 53
            ConstEventType::DELETE_ROWS_EVENT_V2,
413
            ConstEventType::WRITE_ROWS_EVENT_V2,
414
            ConstEventType::UPDATE_ROWS_EVENT_V2
415 53
        ], true
416
        )) {
417 53
            $this->binaryDataReader->read((int)($this->binaryDataReader->readUInt16() / 8));
418
        }
419
420 53
        $this->binaryDataReader->readCodedBinary();
421
422 53
        if ($this->cache->has($tableId)) {
423
            /** @var TableMap $tableMap */
424 53
            $this->currentTableMap = $this->cache->get($tableId);
425
426 53
            return true;
427
        }
428
429 1
        return false;
430
    }
431
432
    /**
433
     * @throws BinaryDataReaderException
434
     * @throws JsonBinaryDecoderException
435
     * @throws MySQLReplicationException
436
     */
437 53
    protected function getValues(): array
438
    {
439
        // if we don't get columns from information schema we don't know how to assign them
440 53
        if ($this->currentTableMap === null || $this->currentTableMap->getColumnDTOCollection()->isEmpty()) {
441 1
            return [];
442
        }
443
444 52
        $binaryData = $this->binaryDataReader->read(
445 52
            $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount())
446
        );
447
448 52
        $values = [];
449 52
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
450 52
            $values[] = $this->getColumnData($binaryData);
451
        }
452
453 52
        return $values;
454
    }
455
456 52
    protected function getColumnsBinarySize(int $columnsAmount): int
457
    {
458 52
        return (int)(($columnsAmount + 7) / 8);
459
    }
460
461
    /**
462
     * @throws BinaryDataReaderException
463
     * @throws JsonBinaryDecoderException
464
     * @throws MySQLReplicationException
465
     */
466 52
    protected function getColumnData(string $colsBitmap): array
467
    {
468 52
        $values = [];
469
470
        // null bitmap length = (bits set in 'columns-present-bitmap'+7)/8
471
        // see http://dev.mysql.com/doc/internals/en/rows-event.html
472 52
        $nullBitmap = $this->binaryDataReader->read($this->getColumnsBinarySize($this->bitCount($colsBitmap)));
473 52
        $nullBitmapIndex = 0;
474
475 52
        foreach ($this->currentTableMap->getColumnDTOCollection() as $i => $columnDTO) {
0 ignored issues
show
Bug introduced by
The method getColumnDTOCollection() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

475
        foreach ($this->currentTableMap->/** @scrutinizer ignore-call */ getColumnDTOCollection() as $i => $columnDTO) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
476 52
            $name = $columnDTO->getName();
477 52
            $type = $columnDTO->getType();
478
479 52
            if (0 === $this->bitGet($colsBitmap, $i)) {
480
                $values[$name] = null;
481
                continue;
482
            }
483
484 52
            if ($this->checkNull($nullBitmap, $nullBitmapIndex)) {
485 6
                $values[$name] = null;
486 51
            } else if ($type === ConstFieldType::IGNORE) {
487 1
                $this->binaryDataReader->advance($columnDTO->getLengthSize());
488 1
                $values[$name] = null;
489 51
            } else if ($type === ConstFieldType::TINY) {
490 5
                if ($columnDTO->isUnsigned()) {
491 4
                    $values[$name] = $this->binaryDataReader->readUInt8();
492
                } else {
493 5
                    $values[$name] = $this->binaryDataReader->readInt8();
494
                }
495 46
            } else if ($type === ConstFieldType::SHORT) {
496 1
                if ($columnDTO->isUnsigned()) {
497 1
                    $values[$name] = $this->binaryDataReader->readUInt16();
498
                } else {
499 1
                    $values[$name] = $this->binaryDataReader->readInt16();
500
                }
501 45
            } else if ($type === ConstFieldType::LONG) {
502 10
                if ($columnDTO->isUnsigned()) {
503 1
                    $values[$name] = $this->binaryDataReader->readUInt32();
504
                } else {
505 10
                    $values[$name] = $this->binaryDataReader->readInt32();
506
                }
507 43
            } else if ($type === ConstFieldType::LONGLONG) {
508 1
                if ($columnDTO->isUnsigned()) {
509 1
                    $values[$name] = $this->binaryDataReader->readUInt64();
510
                } else {
511 1
                    $values[$name] = $this->binaryDataReader->readInt64();
512
                }
513 42
            } else if ($type === ConstFieldType::INT24) {
514 1
                if ($columnDTO->isUnsigned()) {
515 1
                    $values[$name] = $this->binaryDataReader->readUInt24();
516
                } else {
517 1
                    $values[$name] = $this->binaryDataReader->readInt24();
518
                }
519 41
            } else if ($type === ConstFieldType::FLOAT) {
520
                // http://dev.mysql.com/doc/refman/5.7/en/floating-point-types.html FLOAT(7,4)
521 1
                $values[$name] = round($this->binaryDataReader->readFloat(), 4);
522 40
            } else if ($type === ConstFieldType::DOUBLE) {
523 1
                $values[$name] = $this->binaryDataReader->readDouble();
524 39
            } else if ($type === ConstFieldType::VARCHAR || $type === ConstFieldType::STRING) {
525 8
                $values[$name] = $columnDTO->getMaxLength() > 255 ? $this->getString(2) : $this->getString(1);
526 31
            } else if ($type === ConstFieldType::NEWDECIMAL) {
527 10
                $values[$name] = $this->getDecimal($columnDTO);
528 21
            } else if ($type === ConstFieldType::BLOB) {
529 4
                $values[$name] = $this->getString($columnDTO->getLengthSize());
530 17
            } else if ($type === ConstFieldType::DATETIME) {
531
                $values[$name] = $this->getDatetime();
532 17
            } else if ($type === ConstFieldType::DATETIME2) {
533 4
                $values[$name] = $this->getDatetime2($columnDTO);
534 13
            } else if ($type === ConstFieldType::TIMESTAMP) {
535
                $values[$name] = date('Y-m-d H:i:s', $this->binaryDataReader->readUInt32());
536 13
            } else if ($type === ConstFieldType::TIME) {
537
                $values[$name] = $this->getTime();
538 13
            } else if ($type === ConstFieldType::TIME2) {
539 2
                $values[$name] = $this->getTime2($columnDTO);
540 11
            } else if ($type === ConstFieldType::TIMESTAMP2) {
541 2
                $values[$name] = $this->getTimestamp2($columnDTO);
542 9
            } else if ($type === ConstFieldType::DATE) {
543 4
                $values[$name] = $this->getDate();
544 5
            } else if ($type === ConstFieldType::YEAR) {
545
                // https://dev.mysql.com/doc/refman/5.7/en/year.html
546 1
                $year = $this->binaryDataReader->readUInt8();
547 1
                $values[$name] = 0 === $year ? null : 1900 + $year;
548 4
            } else if ($type === ConstFieldType::ENUM) {
549 1
                $values[$name] = $this->getEnum($columnDTO);
550 3
            } else if ($type === ConstFieldType::SET) {
551 1
                $values[$name] = $this->getSet($columnDTO);
552 2
            } else if ($type === ConstFieldType::BIT) {
553 1
                $values[$name] = $this->getBit($columnDTO);
554 1
            } else if ($type === ConstFieldType::GEOMETRY) {
555 1
                $values[$name] = $this->getString($columnDTO->getLengthSize());
556
            } else if ($type === ConstFieldType::JSON) {
557
                $values[$name] = JsonBinaryDecoderService::makeJsonBinaryDecoder($this->getString($columnDTO->getLengthSize()))->parseToString();
558
            } else {
559
                throw new MySQLReplicationException('Unknown row type: ' . $type);
560
            }
561
562 52
            ++$nullBitmapIndex;
563
        }
564
565 52
        return $values;
566
    }
567
568 52
    protected function bitCount(string $bitmap): int
569
    {
570 52
        $n = 0;
571 52
        $bitmapLength = strlen($bitmap);
572 52
        for ($i = 0; $i < $bitmapLength; ++$i) {
573 52
            $bit = $bitmap[$i];
574 52
            if (is_string($bit)) {
575 52
                $bit = ord($bit);
576
            }
577 52
            $n += self::$bitCountInByte[$bit];
578
        }
579
580 52
        return $n;
581
    }
582
583 52
    protected function bitGet(string $bitmap, int $position): int
584
    {
585 52
        return $this->getBitFromBitmap($bitmap, $position) & (1 << ($position & 7));
586
    }
587
588 52
    protected function getBitFromBitmap(string $bitmap, int $position): int
589
    {
590 52
        $bit = $bitmap[(int)($position / 8)];
591 52
        if (is_string($bit)) {
592 52
            $bit = ord($bit);
593
        }
594
595 52
        return $bit;
596
    }
597
598 52
    protected function checkNull(string $nullBitmap, int $position): int
599
    {
600 52
        return $this->getBitFromBitmap($nullBitmap, $position) & (1 << ($position % 8));
601
    }
602
603
    /**
604
     * @throws BinaryDataReaderException
605
     */
606 13
    protected function getString(int $size): string
607
    {
608 13
        return $this->binaryDataReader->readLengthString($size);
609
    }
610
611
    /**
612
     * Read MySQL's new decimal format introduced in MySQL 5
613
     * https://dev.mysql.com/doc/refman/5.6/en/precision-math-decimal-characteristics.html
614
     * @throws BinaryDataReaderException
615
     */
616 10
    protected function getDecimal(ColumnDTO $columnDTO): string
617
    {
618 10
        $digitsPerInteger = 9;
619 10
        $compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4];
620 10
        $integral = $columnDTO->getPrecision() - $columnDTO->getDecimals();
621 10
        $unCompIntegral = (int)($integral / $digitsPerInteger);
622 10
        $unCompFractional = (int)($columnDTO->getDecimals() / $digitsPerInteger);
623 10
        $compIntegral = $integral - ($unCompIntegral * $digitsPerInteger);
624 10
        $compFractional = $columnDTO->getDecimals() - ($unCompFractional * $digitsPerInteger);
625
626 10
        $value = $this->binaryDataReader->readUInt8();
627 10
        if (0 !== ($value & 0x80)) {
628 7
            $mask = 0;
629 7
            $res = '';
630
        } else {
631 3
            $mask = -1;
632 3
            $res = '-';
633
        }
634 10
        $this->binaryDataReader->unread(pack('C', $value ^ 0x80));
635
636 10
        $size = $compressedBytes[$compIntegral];
637 10
        if ($size > 0) {
638 10
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
639 10
            $res .= $value;
640
        }
641
642 10
        for ($i = 0; $i < $unCompIntegral; ++$i) {
643 9
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
644 9
            $res .= sprintf('%09d', $value);
645
        }
646
647 10
        $res .= '.';
648
649 10
        for ($i = 0; $i < $unCompFractional; ++$i) {
650 4
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
651 4
            $res .= sprintf('%09d', $value);
652
        }
653
654 10
        $size = $compressedBytes[$compFractional];
655 10
        if ($size > 0) {
656 5
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
657 5
            $res .= sprintf('%0' . $compFractional . 'd', $value);
658
        }
659
660 10
        return bcmul($res, '1', $columnDTO->getPrecision());
661
    }
662
663
    protected function getDatetime(): ?string
664
    {
665
        $value = $this->binaryDataReader->readUInt64();
666
        // nasty mysql 0000-00-00 dates
667
        if ('0' === $value) {
668
            return null;
669
        }
670
671
        $date = DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s');
672
        if (array_sum(DateTime::getLastErrors()) > 0) {
673
            return null;
674
        }
675
676
        return $date;
677
    }
678
679
    /**
680
     * Date Time
681
     * 1 bit  sign           (1= non-negative, 0= negative)
682
     * 17 bits year*13+month  (year 0-9999, month 0-12)
683
     * 5 bits day            (0-31)
684
     * 5 bits hour           (0-23)
685
     * 6 bits minute         (0-59)
686
     * 6 bits second         (0-59)
687
     * ---------------------------
688
     * 40 bits = 5 bytes
689
     *
690
     * @throws BinaryDataReaderException
691
     *
692
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
693
     */
694 4
    protected function getDatetime2(ColumnDTO $columnDTO): ?string
695
    {
696 4
        $data = $this->binaryDataReader->readIntBeBySize(5);
697
698 4
        $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40);
699
700 4
        $year = (int)($yearMonth / 13);
701 4
        $month = $yearMonth % 13;
702 4
        $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40);
703 4
        $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40);
704 4
        $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40);
705 4
        $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40);
706
707
        try {
708 4
            $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
709
        } catch (Exception $exception) {
710
            return null;
711
        }
712 4
        if (array_sum(DateTime::getLastErrors()) > 0) {
713 3
            return null;
714
        }
715
716 1
        return $date->format('Y-m-d H:i:s') . $this->getFSP($columnDTO);
717
    }
718
719
    /**
720
     * @throws BinaryDataReaderException
721
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
722
     */
723 5
    protected function getFSP(ColumnDTO $columnDTO): string
724
    {
725 5
        $read = 0;
726 5
        $time = '';
727 5
        $fsp = $columnDTO->getFsp();
728 5
        if ($fsp === 1 || $fsp === 2) {
729 1
            $read = 1;
730 5
        } else if ($fsp === 3 || $fsp === 4) {
731 1
            $read = 2;
732 5
        } else if ($fsp === 5 || $fsp === 6) {
733 1
            $read = 3;
734
        }
735 5
        if ($read > 0) {
736 1
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
737 1
            if ($fsp % 2) {
738 1
                $microsecond = (int)($microsecond / 10);
739
740
            }
741 1
            $time = $microsecond * (10 ** (6 - $fsp));
742
        }
743
744 5
        return (string)$time;
745
    }
746
747
    protected function getTime(): string
748
    {
749
        $data = $this->binaryDataReader->readUInt24();
750
        if (0 === $data) {
751
            return '00:00:00';
752
        }
753
754
        return sprintf('%s%02d:%02d:%02d', $data < 0 ? '-' : '', $data / 10000, ($data % 10000) / 100, $data % 100);
755
    }
756
757
    /**
758
     * TIME encoding for non fractional part:
759
     * 1 bit sign    (1= non-negative, 0= negative)
760
     * 1 bit unused  (reserved for future extensions)
761
     * 10 bits hour   (0-838)
762
     * 6 bits minute (0-59)
763
     * 6 bits second (0-59)
764
     * ---------------------
765
     * 24 bits = 3 bytes
766
     *
767
     * @throws BinaryDataReaderException
768
     */
769 2
    protected function getTime2(ColumnDTO $columnDTO): string
770
    {
771 2
        $data = $this->binaryDataReader->readInt24Be();
772
773 2
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
774 2
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
775 2
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
776
777 2
        return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO);
778
    }
779
780
    /**
781
     * @throws BinaryDataReaderException
782
     */
783 2
    protected function getTimestamp2(ColumnDTO $columnDTO): string
784
    {
785 2
        $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
786 2
        $fsp = $this->getFSP($columnDTO);
787 2
        if ('' !== $fsp) {
788 1
            $datetime .= '.' . $fsp;
789
        }
790
791 2
        return $datetime;
792
    }
793
794 4
    protected function getDate(): ?string
795
    {
796 4
        $time = $this->binaryDataReader->readUInt24();
797 4
        if (0 === $time) {
798
            return null;
799
        }
800
801 4
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
802 4
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
803 4
        $day = ($time & ((1 << 5) - 1));
804 4
        if ($year === 0 || $month === 0 || $day === 0) {
805 3
            return null;
806
        }
807
808 1
        return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d');
809
    }
810
811
    /**
812
     * @throws BinaryDataReaderException
813
     */
814 1
    protected function getEnum(ColumnDTO $columnDTO): string
815
    {
816 1
        $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1;
817
818
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
819 1
        if (array_key_exists($value, $columnDTO->getEnumValues())) {
820 1
            return $columnDTO->getEnumValues()[$value];
821
        }
822
823 1
        return '';
824
    }
825
826
    /**
827
     * @throws BinaryDataReaderException
828
     */
829 1
    protected function getSet(ColumnDTO $columnDTO): array
830
    {
831
        // we read set columns as a bitmap telling us which options are enabled
832 1
        $bit_mask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize());
833 1
        $sets = [];
834 1
        foreach ($columnDTO->getSetValues() as $k => $item) {
835 1
            if ($bit_mask & (2 ** $k)) {
836 1
                $sets[] = $item;
837
            }
838
        }
839
840 1
        return $sets;
841
    }
842
843 1
    protected function getBit(ColumnDTO $columnDTO): string
844
    {
845 1
        $res = '';
846 1
        for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) {
847 1
            $current_byte = '';
848 1
            $data = $this->binaryDataReader->readUInt8();
849 1
            if (0 === $byte) {
850 1
                if (1 === $columnDTO->getBytes()) {
851 1
                    $end = $columnDTO->getBits();
852
                } else {
853 1
                    $end = $columnDTO->getBits() % 8;
854 1
                    if (0 === $end) {
855 1
                        $end = 8;
856
                    }
857
                }
858
            } else {
859 1
                $end = 8;
860
            }
861
862 1
            for ($bit = 0; $bit < $end; ++$bit) {
863 1
                if ($data & (1 << $bit)) {
864 1
                    $current_byte .= '1';
865
                } else {
866 1
                    $current_byte .= '0';
867
                }
868
869
            }
870 1
            $res .= strrev($current_byte);
871
        }
872
873 1
        return $res;
874
    }
875
876
    /**
877
     * @throws InvalidArgumentException
878
     * @throws BinaryDataReaderException
879
     * @throws JsonBinaryDecoderException
880
     * @throws MySQLReplicationException
881
     */
882 1
    public function makeDeleteRowsDTO(): ?DeleteRowsDTO
883
    {
884 1
        if (!$this->rowInit()) {
885
            return null;
886
        }
887
888 1
        $values = $this->getValues();
889
890 1
        return new DeleteRowsDTO(
891 1
            $this->eventInfo,
892 1
            $this->currentTableMap,
893 1
            count($values),
894 1
            $values
895
        );
896
    }
897
898
    /**
899
     * @throws InvalidArgumentException
900
     * @throws BinaryDataReaderException
901
     * @throws JsonBinaryDecoderException
902
     * @throws MySQLReplicationException
903
     */
904 1
    public function makeUpdateRowsDTO(): ?UpdateRowsDTO
905
    {
906 1
        if (!$this->rowInit()) {
907
            return null;
908
        }
909
910 1
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
911 1
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
912 1
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
913
914 1
        $values = [];
915 1
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
916 1
            $values[] = [
917 1
                'before' => $this->getColumnData($beforeBinaryData),
918 1
                'after' => $this->getColumnData($afterBinaryData)
919
            ];
920
        }
921
922 1
        return new UpdateRowsDTO(
923 1
            $this->eventInfo,
924 1
            $this->currentTableMap,
925 1
            count($values),
926 1
            $values
927
        );
928
    }
929
}
930