Completed
Pull Request — master (#53)
by kacper
04:34
created

RowEvent::getDatetime2()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0213

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 3
eloc 15
c 6
b 0
f 0
nc 3
nop 1
dl 0
loc 23
ccs 13
cts 15
cp 0.8667
crap 3.0213
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 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('Y-m-d H:i:s', $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
        $date = DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s');
668
        if (array_sum(DateTime::getLastErrors()) > 0) {
669
            return null;
670
        }
671
672
        return $date;
673
    }
674
675
    /**
676
     * Date Time
677
     * 1 bit  sign           (1= non-negative, 0= negative)
678
     * 17 bits year*13+month  (year 0-9999, month 0-12)
679
     * 5 bits day            (0-31)
680
     * 5 bits hour           (0-23)
681
     * 6 bits minute         (0-59)
682
     * 6 bits second         (0-59)
683
     * ---------------------------
684
     * 40 bits = 5 bytes
685
     *
686
     * @throws BinaryDataReaderException
687
     *
688
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
689
     */
690 4
    protected function getDatetime2(ColumnDTO $columnDTO): ?string
691
    {
692 4
        $data = $this->binaryDataReader->readIntBeBySize(5);
693
694 4
        $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40);
695
696 4
        $year = (int)($yearMonth / 13);
697 4
        $month = $yearMonth % 13;
698 4
        $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40);
699 4
        $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40);
700 4
        $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40);
701 4
        $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40);
702
703
        try {
704 4
            $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
705
        } catch (Exception $exception) {
706
            return null;
707
        }
708 4
        if (array_sum(DateTime::getLastErrors()) > 0) {
709 3
            return null;
710
        }
711
712 1
        return $date->format('Y-m-d H:i:s') . $this->getFSP($columnDTO);
713
    }
714
715
    /**
716
     * @throws BinaryDataReaderException
717
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
718
     */
719 5
    protected function getFSP(ColumnDTO $columnDTO): string
720
    {
721 5
        $read = 0;
722 5
        $time = '';
723 5
        $fsp = $columnDTO->getFsp();
724 5
        if ($fsp === 1 || $fsp === 2) {
725 1
            $read = 1;
726 5
        } else if ($fsp === 3 || $fsp === 4) {
727 1
            $read = 2;
728 5
        } else if ($fsp === 5 || $fsp === 6) {
729 1
            $read = 3;
730
        }
731 5
        if ($read > 0) {
732 1
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
733 1
            if ($fsp % 2) {
734 1
                $microsecond = (int)($microsecond / 10);
735
736
            }
737 1
            $time = $microsecond * (10 ** (6 - $fsp));
738
        }
739
740 5
        return (string)$time;
741
    }
742
743
    protected function getTime(): string
744
    {
745
        $data = $this->binaryDataReader->readUInt24();
746
        if (0 === $data) {
747
            return '00:00:00';
748
        }
749
750
        return sprintf('%s%02d:%02d:%02d', $data < 0 ? '-' : '', $data / 10000, ($data % 10000) / 100, $data % 100);
751
    }
752
753
    /**
754
     * TIME encoding for non fractional part:
755
     * 1 bit sign    (1= non-negative, 0= negative)
756
     * 1 bit unused  (reserved for future extensions)
757
     * 10 bits hour   (0-838)
758
     * 6 bits minute (0-59)
759
     * 6 bits second (0-59)
760
     * ---------------------
761
     * 24 bits = 3 bytes
762
     *
763
     * @throws BinaryDataReaderException
764
     */
765 2
    protected function getTime2(ColumnDTO $columnDTO): string
766
    {
767 2
        $data = $this->binaryDataReader->readInt24Be();
768
769 2
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
770 2
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
771 2
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
772
773 2
        return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO);
774
    }
775
776
    /**
777
     * @throws BinaryDataReaderException
778
     */
779 2
    protected function getTimestamp2(ColumnDTO $columnDTO): string
780
    {
781 2
        $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
782 2
        $fsp = $this->getFSP($columnDTO);
783 2
        if ('' !== $fsp) {
784 1
            $datetime .= '.' . $fsp;
785
        }
786
787 2
        return $datetime;
788
    }
789
790 4
    protected function getDate(): ?string
791
    {
792 4
        $time = $this->binaryDataReader->readUInt24();
793 4
        if (0 === $time) {
794
            return null;
795
        }
796
797 4
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
798 4
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
799 4
        $day = ($time & ((1 << 5) - 1));
800 4
        if ($year === 0 || $month === 0 || $day === 0) {
801 3
            return null;
802
        }
803
804 1
        return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d');
805
    }
806
807
    /**
808
     * @throws BinaryDataReaderException
809
     */
810 1
    protected function getEnum(ColumnDTO $columnDTO): string
811
    {
812 1
        $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1;
813
814
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
815 1
        if (array_key_exists($value, $columnDTO->getEnumValues())) {
816 1
            return $columnDTO->getEnumValues()[$value];
817
        }
818
819 1
        return '';
820
    }
821
822
    /**
823
     * @throws BinaryDataReaderException
824
     */
825 1
    protected function getSet(ColumnDTO $columnDTO): array
826
    {
827
        // we read set columns as a bitmap telling us which options are enabled
828 1
        $bit_mask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize());
829 1
        $sets = [];
830 1
        foreach ($columnDTO->getSetValues() as $k => $item) {
831 1
            if ($bit_mask & (2 ** $k)) {
832 1
                $sets[] = $item;
833
            }
834
        }
835
836 1
        return $sets;
837
    }
838
839 1
    protected function getBit(ColumnDTO $columnDTO): string
840
    {
841 1
        $res = '';
842 1
        for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) {
843 1
            $current_byte = '';
844 1
            $data = $this->binaryDataReader->readUInt8();
845 1
            if (0 === $byte) {
846 1
                if (1 === $columnDTO->getBytes()) {
847 1
                    $end = $columnDTO->getBits();
848
                } else {
849 1
                    $end = $columnDTO->getBits() % 8;
850 1
                    if (0 === $end) {
851 1
                        $end = 8;
852
                    }
853
                }
854
            } else {
855 1
                $end = 8;
856
            }
857
858 1
            for ($bit = 0; $bit < $end; ++$bit) {
859 1
                if ($data & (1 << $bit)) {
860 1
                    $current_byte .= '1';
861
                } else {
862 1
                    $current_byte .= '0';
863
                }
864
865
            }
866 1
            $res .= strrev($current_byte);
867
        }
868
869 1
        return $res;
870
    }
871
872
    /**
873
     * @throws InvalidArgumentException
874
     * @throws BinaryDataReaderException
875
     * @throws JsonBinaryDecoderException
876
     * @throws MySQLReplicationException
877
     */
878 1
    public function makeDeleteRowsDTO(): ?DeleteRowsDTO
879
    {
880 1
        if (!$this->rowInit()) {
881
            return null;
882
        }
883
884 1
        $values = $this->getValues();
885
886 1
        return new DeleteRowsDTO(
887 1
            $this->eventInfo,
888 1
            $this->currentTableMap,
889 1
            count($values),
890 1
            $values
891
        );
892
    }
893
894
    /**
895
     * @throws InvalidArgumentException
896
     * @throws BinaryDataReaderException
897
     * @throws JsonBinaryDecoderException
898
     * @throws MySQLReplicationException
899
     */
900 1
    public function makeUpdateRowsDTO(): ?UpdateRowsDTO
901
    {
902 1
        if (!$this->rowInit()) {
903
            return null;
904
        }
905
906 1
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
907 1
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
908 1
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
909
910 1
        $values = [];
911 1
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
912 1
            $values[] = [
913 1
                'before' => $this->getColumnData($beforeBinaryData),
914 1
                'after' => $this->getColumnData($afterBinaryData)
915
            ];
916
        }
917
918 1
        return new UpdateRowsDTO(
919 1
            $this->eventInfo,
920 1
            $this->currentTableMap,
921 1
            count($values),
922 1
            $values
923
        );
924
    }
925
}
926