Passed
Pull Request — master (#53)
by kacper
05:58
created

RowEvent   F

Complexity

Total Complexity 109

Size/Duplication

Total Lines 892
Duplicated Lines 0 %

Test Coverage

Coverage 92.48%

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 312
c 11
b 0
f 0
dl 0
loc 892
ccs 295
cts 319
cp 0.9248
rs 2
wmc 109

25 Methods

Rating   Name   Duplication   Size   Complexity  
A bitGet() 0 3 1
A getValues() 0 17 3
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 59 7
A getDatetime() 0 9 2
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 24 3
A bitCount() 0 13 3
D getColumnData() 0 100 36
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
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 53
                $columnDTOCollection->set($offset, ColumnDTO::make($type, $fieldDTOCollection->offsetGet($offset), $this->binaryDataReader));
0 ignored issues
show
Bug introduced by
It seems like $fieldDTOCollection->offsetGet($offset) can also be of type null; however, parameter $fieldDTO of MySQLReplication\Event\RowEvent\ColumnDTO::make() does only seem to accept MySQLReplication\Repository\FieldDTO, maybe add an additional type check? ( Ignorable by Annotation )

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

358
                $columnDTOCollection->set($offset, ColumnDTO::make($type, /** @scrutinizer ignore-type */ $fieldDTOCollection->offsetGet($offset), $this->binaryDataReader));
Loading history...
359
            }
360
        }
361
362 54
        $tableMap = new TableMap(
363 54
            $data['schema_name'],
364 54
            $data['table_name'],
365 54
            $data['table_id'],
366 54
            $data['columns_amount'],
367 54
            $columnDTOCollection
368
        );
369
370 54
        $this->cache->set($data['table_id'], $tableMap);
371
372 54
        return new TableMapDTO($this->eventInfo, $tableMap);
373
    }
374
375
    /**
376
     * @throws BinaryDataReaderException
377
     * @throws InvalidArgumentException
378
     * @throws JsonBinaryDecoderException
379
     * @throws MySQLReplicationException
380
     */
381 53
    public function makeWriteRowsDTO(): ?WriteRowsDTO
382
    {
383 53
        if (!$this->rowInit()) {
384 1
            return null;
385
        }
386
387 53
        $values = $this->getValues();
388
389 53
        return new WriteRowsDTO(
390 53
            $this->eventInfo,
391 53
            $this->currentTableMap,
392 53
            count($values),
393 53
            $values
394
        );
395
    }
396
397
    /**
398
     * @throws InvalidArgumentException
399
     * @throws BinaryDataReaderException
400
     */
401 53
    protected function rowInit(): bool
402
    {
403 53
        $tableId = $this->binaryDataReader->readTableId();
404 53
        $this->binaryDataReader->advance(2);
405
406 53
        if (in_array(
407 53
            $this->eventInfo->getType(), [
408 53
            ConstEventType::DELETE_ROWS_EVENT_V2,
409
            ConstEventType::WRITE_ROWS_EVENT_V2,
410
            ConstEventType::UPDATE_ROWS_EVENT_V2
411 53
        ], true
412
        )) {
413 53
            $this->binaryDataReader->read((int)($this->binaryDataReader->readUInt16() / 8));
414
        }
415
416 53
        $this->binaryDataReader->readCodedBinary();
417
418 53
        if ($this->cache->has($tableId)) {
419
            /** @var TableMap $tableMap */
420 53
            $this->currentTableMap = $this->cache->get($tableId);
421
422 53
            return true;
423
        }
424
425 1
        return false;
426
    }
427
428
    /**
429
     * @throws BinaryDataReaderException
430
     * @throws JsonBinaryDecoderException
431
     * @throws MySQLReplicationException
432
     */
433 53
    protected function getValues(): array
434
    {
435
        // if we don't get columns from information schema we don't know how to assign them
436 53
        if ($this->currentTableMap->getColumnDTOCollection()->isEmpty()) {
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

436
        if ($this->currentTableMap->/** @scrutinizer ignore-call */ getColumnDTOCollection()->isEmpty()) {

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