• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Moln / php-mysql-replication / 3882601767

pending completion
3882601767

Pull #9

github

GitHub
Merge f4db3085f into c603b8f64
Pull Request #9: Fix PHP8.2 'RowEvent' calling 'array_sum' that might be `false`.

2 of 2 new or added lines in 1 file covered. (100.0%)

1252 of 1549 relevant lines covered (80.83%)

27.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.06
/src/MySQLReplication/Event/RowEvent/RowEvent.php
1
<?php
2
declare(strict_types=1);
3

4
namespace MySQLReplication\Event\RowEvent;
5

6
use DateTime;
7
use Exception;
8
use MySQLReplication\BinaryDataReader\BinaryDataReader;
9
use MySQLReplication\BinaryDataReader\BinaryDataReaderException;
10
use MySQLReplication\Config\Config;
11
use MySQLReplication\Definitions\ConstEventType;
12
use MySQLReplication\Definitions\ConstFieldType;
13
use MySQLReplication\Event\DTO\DeleteRowsDTO;
14
use MySQLReplication\Event\DTO\TableMapDTO;
15
use MySQLReplication\Event\DTO\UpdateRowsDTO;
16
use MySQLReplication\Event\DTO\WriteRowsDTO;
17
use MySQLReplication\Event\EventCommon;
18
use MySQLReplication\Event\EventInfo;
19
use MySQLReplication\Exception\MySQLReplicationException;
20
use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderException;
21
use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderService;
22
use MySQLReplication\Repository\FieldDTO;
23
use MySQLReplication\Repository\RepositoryInterface;
24
use Psr\SimpleCache\CacheInterface;
25
use Psr\SimpleCache\InvalidArgumentException;
26
use RuntimeException;
27

28
class RowEvent extends EventCommon
29
{
30
    private static $bitCountInByte = [
31
        0,
32
        1,
33
        1,
34
        2,
35
        1,
36
        2,
37
        2,
38
        3,
39
        1,
40
        2,
41
        2,
42
        3,
43
        2,
44
        3,
45
        3,
46
        4,
47
        1,
48
        2,
49
        2,
50
        3,
51
        2,
52
        3,
53
        3,
54
        4,
55
        2,
56
        3,
57
        3,
58
        4,
59
        3,
60
        4,
61
        4,
62
        5,
63
        1,
64
        2,
65
        2,
66
        3,
67
        2,
68
        3,
69
        3,
70
        4,
71
        2,
72
        3,
73
        3,
74
        4,
75
        3,
76
        4,
77
        4,
78
        5,
79
        2,
80
        3,
81
        3,
82
        4,
83
        3,
84
        4,
85
        4,
86
        5,
87
        3,
88
        4,
89
        4,
90
        5,
91
        4,
92
        5,
93
        5,
94
        6,
95
        1,
96
        2,
97
        2,
98
        3,
99
        2,
100
        3,
101
        3,
102
        4,
103
        2,
104
        3,
105
        3,
106
        4,
107
        3,
108
        4,
109
        4,
110
        5,
111
        2,
112
        3,
113
        3,
114
        4,
115
        3,
116
        4,
117
        4,
118
        5,
119
        3,
120
        4,
121
        4,
122
        5,
123
        4,
124
        5,
125
        5,
126
        6,
127
        2,
128
        3,
129
        3,
130
        4,
131
        3,
132
        4,
133
        4,
134
        5,
135
        3,
136
        4,
137
        4,
138
        5,
139
        4,
140
        5,
141
        5,
142
        6,
143
        3,
144
        4,
145
        4,
146
        5,
147
        4,
148
        5,
149
        5,
150
        6,
151
        4,
152
        5,
153
        5,
154
        6,
155
        5,
156
        6,
157
        6,
158
        7,
159
        1,
160
        2,
161
        2,
162
        3,
163
        2,
164
        3,
165
        3,
166
        4,
167
        2,
168
        3,
169
        3,
170
        4,
171
        3,
172
        4,
173
        4,
174
        5,
175
        2,
176
        3,
177
        3,
178
        4,
179
        3,
180
        4,
181
        4,
182
        5,
183
        3,
184
        4,
185
        4,
186
        5,
187
        4,
188
        5,
189
        5,
190
        6,
191
        2,
192
        3,
193
        3,
194
        4,
195
        3,
196
        4,
197
        4,
198
        5,
199
        3,
200
        4,
201
        4,
202
        5,
203
        4,
204
        5,
205
        5,
206
        6,
207
        3,
208
        4,
209
        4,
210
        5,
211
        4,
212
        5,
213
        5,
214
        6,
215
        4,
216
        5,
217
        5,
218
        6,
219
        5,
220
        6,
221
        6,
222
        7,
223
        2,
224
        3,
225
        3,
226
        4,
227
        3,
228
        4,
229
        4,
230
        5,
231
        3,
232
        4,
233
        4,
234
        5,
235
        4,
236
        5,
237
        5,
238
        6,
239
        3,
240
        4,
241
        4,
242
        5,
243
        4,
244
        5,
245
        5,
246
        6,
247
        4,
248
        5,
249
        5,
250
        6,
251
        5,
252
        6,
253
        6,
254
        7,
255
        3,
256
        4,
257
        4,
258
        5,
259
        4,
260
        5,
261
        5,
262
        6,
263
        4,
264
        5,
265
        5,
266
        6,
267
        5,
268
        6,
269
        6,
270
        7,
271
        4,
272
        5,
273
        5,
274
        6,
275
        5,
276
        6,
277
        6,
278
        7,
279
        5,
280
        6,
281
        6,
282
        7,
283
        6,
284
        7,
285
        7,
286
        8,
287
    ];
288
    private $repository;
289
    private $cache;
290

291
    /**
292
     * @var TableMap|null
293
     */
294
    private $currentTableMap;
295
    /**
296
     * @var Config
297
     */
298
    private $config;
299

300
    public function __construct(
301
        Config $config,
302
        RepositoryInterface $repository,
303
        BinaryDataReader $binaryDataReader,
304
        EventInfo $eventInfo,
305
        CacheInterface $cache
306
    ) {
307
        parent::__construct($eventInfo, $binaryDataReader);
58✔
308

309
        $this->repository = $repository;
58✔
310
        $this->cache = $cache;
58✔
311
        $this->config = $config;
58✔
312
    }
313

314
    /**
315
     * This describe the structure of a table.
316
     * It's send before a change append on a table.
317
     * A end user of the lib should have no usage of this
318
     * @throws BinaryDataReaderException
319
     * @throws InvalidArgumentException
320
     */
321
    public function makeTableMapDTO(): ?TableMapDTO
322
    {
323
        $data = [];
58✔
324
        $data['table_id'] = $this->binaryDataReader->readTableId();
58✔
325
        $this->binaryDataReader->advance(2);
58✔
326
        $data['schema_length'] = $this->binaryDataReader->readUInt8();
58✔
327
        $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']);
58✔
328

329
        if ($this->config->checkDataBasesOnly($data['schema_name'])) {
58✔
330
            return null;
×
331
        }
332

333
        $this->binaryDataReader->advance(1);
58✔
334
        $data['table_length'] = $this->binaryDataReader->readUInt8();
58✔
335
        $data['table_name'] = $this->binaryDataReader->read($data['table_length']);
58✔
336

337
        if ($this->config->checkTablesOnly($data['table_name'])) {
58✔
338
            return null;
1✔
339
        }
340

341
        $this->binaryDataReader->advance(1);
58✔
342
        $data['columns_amount'] = (int)$this->binaryDataReader->readCodedBinary();
58✔
343
        $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']);
58✔
344

345
        if ($this->cache->has($data['table_id'])) {
58✔
346
            return new TableMapDTO($this->eventInfo, $this->cache->get($data['table_id']));
5✔
347
        }
348

349
        $this->binaryDataReader->readCodedBinary();
58✔
350

351
        $fieldDTOCollection = $this->repository->getFields($data['schema_name'], $data['table_name']);
58✔
352
        $columnDTOCollection = new ColumnDTOCollection();
58✔
353
        // if you drop tables and parse of logs you will get empty scheme
354
        if (! $fieldDTOCollection->isEmpty()) {
58✔
355
            $columnLength = strlen($data['column_types']);
57✔
356
            for ($offset = 0; $offset < $columnLength; ++$offset) {
57✔
357
                // this a dirty hack to prevent row events containing columns which have been dropped
358
                if ($fieldDTOCollection->offsetExists($offset)) {
57✔
359
                    $type = ord($data['column_types'][$offset]);
57✔
360
                } else {
361
                    $fieldDTOCollection->offsetSet($offset, FieldDTO::makeDummy($offset));
1✔
362
                    $type = ConstFieldType::IGNORE;
1✔
363
                }
364

365
                /** @var FieldDTO $fieldDTO */
366
                $fieldDTO = $fieldDTOCollection->offsetGet($offset);
57✔
367
                if (null !== $fieldDTO) {
57✔
368
                    $columnDTOCollection->set($offset, ColumnDTO::make($type, $fieldDTO, $this->binaryDataReader));
57✔
369
                }
370
            }
371
        }
372

373
        $tableMap = new TableMap(
58✔
374
            $data['schema_name'],
58✔
375
            $data['table_name'],
58✔
376
            $data['table_id'],
58✔
377
            $data['columns_amount'],
58✔
378
            $columnDTOCollection
58✔
379
        );
58✔
380

381
        $this->cache->set($data['table_id'], $tableMap);
58✔
382

383
        return new TableMapDTO($this->eventInfo, $tableMap);
58✔
384
    }
385

386
    /**
387
     * @throws BinaryDataReaderException
388
     * @throws InvalidArgumentException
389
     * @throws JsonBinaryDecoderException
390
     * @throws MySQLReplicationException
391
     */
392
    public function makeWriteRowsDTO(): ?WriteRowsDTO
393
    {
394
        if (! $this->rowInit()) {
57✔
395
            return null;
1✔
396
        }
397

398
        $values = $this->getValues();
57✔
399

400
        return new WriteRowsDTO(
57✔
401
            $this->eventInfo,
57✔
402
            $this->currentTableMap,
57✔
403
            count($values),
57✔
404
            $values
57✔
405
        );
57✔
406
    }
407

408
    /**
409
     * @throws InvalidArgumentException
410
     * @throws BinaryDataReaderException
411
     */
412
    protected function rowInit(): bool
413
    {
414
        $tableId = $this->binaryDataReader->readTableId();
57✔
415
        $this->binaryDataReader->advance(2);
57✔
416

417
        if (in_array(
57✔
418
            $this->eventInfo->getType(),
57✔
419
            [
57✔
420
            ConstEventType::DELETE_ROWS_EVENT_V2,
57✔
421
            ConstEventType::WRITE_ROWS_EVENT_V2,
57✔
422
            ConstEventType::UPDATE_ROWS_EVENT_V2
57✔
423
            ],
57✔
424
            true
57✔
425
        )) {
57✔
426
            $this->binaryDataReader->read((int)($this->binaryDataReader->readUInt16() / 8));
57✔
427
        }
428

429
        $this->binaryDataReader->readCodedBinary();
57✔
430

431
        if ($this->cache->has($tableId)) {
57✔
432
            /** @var TableMap $tableMap */
433
            $this->currentTableMap = $this->cache->get($tableId);
57✔
434

435
            return true;
57✔
436
        }
437

438
        return false;
1✔
439
    }
440

441
    /**
442
     * @throws BinaryDataReaderException
443
     * @throws JsonBinaryDecoderException
444
     * @throws MySQLReplicationException
445
     */
446
    protected function getValues(): array
447
    {
448
        // if we don't get columns from information schema we don't know how to assign them
449
        if ($this->currentTableMap === null || $this->currentTableMap->getColumnDTOCollection()->isEmpty()) {
57✔
450
            return [];
1✔
451
        }
452

453
        $binaryData = $this->binaryDataReader->read(
56✔
454
            $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount())
56✔
455
        );
56✔
456

457
        $values = [];
56✔
458
        while (! $this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
56✔
459
            $values[] = $this->getColumnData($binaryData);
56✔
460
        }
461

462
        return $values;
56✔
463
    }
464

465
    protected function getColumnsBinarySize(int $columnsAmount): int
466
    {
467
        return (int)(($columnsAmount + 7) / 8);
56✔
468
    }
469

470
    /**
471
     * @throws BinaryDataReaderException
472
     * @throws JsonBinaryDecoderException
473
     * @throws MySQLReplicationException
474
     */
475
    protected function getColumnData(string $colsBitmap): array
476
    {
477
        if (null === $this->currentTableMap) {
56✔
478
            throw new RuntimeException('Current table map is missing!');
×
479
        }
480

481
        $values = [];
56✔
482

483
        // null bitmap length = (bits set in 'columns-present-bitmap'+7)/8
484
        // see http://dev.mysql.com/doc/internals/en/rows-event.html
485
        $nullBitmap = $this->binaryDataReader->read($this->getColumnsBinarySize($this->bitCount($colsBitmap)));
56✔
486
        $nullBitmapIndex = 0;
56✔
487

488
        foreach ($this->currentTableMap->getColumnDTOCollection() as $i => $columnDTO) {
56✔
489
            $name = $columnDTO->getName();
56✔
490
            $type = $columnDTO->getType();
56✔
491

492
            if (0 === $this->bitGet($colsBitmap, $i)) {
56✔
493
                $values[$name] = null;
×
494
                continue;
×
495
            }
496

497
            if ($this->checkNull($nullBitmap, $nullBitmapIndex)) {
56✔
498
                $values[$name] = null;
7✔
499
            } elseif ($type === ConstFieldType::IGNORE) {
55✔
500
                $this->binaryDataReader->advance($columnDTO->getLengthSize());
1✔
501
                $values[$name] = null;
1✔
502
            } elseif ($type === ConstFieldType::TINY) {
55✔
503
                if ($columnDTO->isUnsigned()) {
5✔
504
                    $values[$name] = $this->binaryDataReader->readUInt8();
4✔
505
                } else {
506
                    $values[$name] = $this->binaryDataReader->readInt8();
5✔
507
                }
508
            } elseif ($type === ConstFieldType::SHORT) {
50✔
509
                if ($columnDTO->isUnsigned()) {
1✔
510
                    $values[$name] = $this->binaryDataReader->readUInt16();
1✔
511
                } else {
512
                    $values[$name] = $this->binaryDataReader->readInt16();
1✔
513
                }
514
            } elseif ($type === ConstFieldType::LONG) {
49✔
515
                if ($columnDTO->isUnsigned()) {
11✔
516
                    $values[$name] = $this->binaryDataReader->readUInt32();
1✔
517
                } else {
518
                    $values[$name] = $this->binaryDataReader->readInt32();
11✔
519
                }
520
            } elseif ($type === ConstFieldType::LONGLONG) {
47✔
521
                if ($columnDTO->isUnsigned()) {
1✔
522
                    $values[$name] = $this->binaryDataReader->readUInt64();
1✔
523
                } else {
524
                    $values[$name] = $this->binaryDataReader->readInt64();
1✔
525
                }
526
            } elseif ($type === ConstFieldType::INT24) {
46✔
527
                if ($columnDTO->isUnsigned()) {
1✔
528
                    $values[$name] = $this->binaryDataReader->readUInt24();
1✔
529
                } else {
530
                    $values[$name] = $this->binaryDataReader->readInt24();
1✔
531
                }
532
            } elseif ($type === ConstFieldType::FLOAT) {
45✔
533
                // http://dev.mysql.com/doc/refman/5.7/en/floating-point-types.html FLOAT(7,4)
534
                $values[$name] = round($this->binaryDataReader->readFloat(), 4);
1✔
535
            } elseif ($type === ConstFieldType::DOUBLE) {
44✔
536
                $values[$name] = $this->binaryDataReader->readDouble();
1✔
537
            } elseif ($type === ConstFieldType::VARCHAR || $type === ConstFieldType::STRING) {
43✔
538
                $values[$name] = $columnDTO->getMaxLength() > 255 ? $this->getString(2) : $this->getString(1);
8✔
539
            } elseif ($type === ConstFieldType::NEWDECIMAL) {
35✔
540
                $values[$name] = $this->getDecimal($columnDTO);
10✔
541
            } elseif ($type === ConstFieldType::BLOB) {
25✔
542
                $values[$name] = $this->getString($columnDTO->getLengthSize());
4✔
543
            } elseif ($type === ConstFieldType::DATETIME) {
21✔
544
                $values[$name] = $this->getDatetime();
×
545
            } elseif ($type === ConstFieldType::DATETIME2) {
21✔
546
                $values[$name] = $this->getDatetime2($columnDTO);
4✔
547
            } elseif ($type === ConstFieldType::TIMESTAMP) {
17✔
548
                $values[$name] = date('Y-m-d H:i:s', $this->binaryDataReader->readUInt32());
×
549
            } elseif ($type === ConstFieldType::TIME) {
17✔
550
                $values[$name] = $this->getTime();
×
551
            } elseif ($type === ConstFieldType::TIME2) {
17✔
552
                $values[$name] = $this->getTime2($columnDTO);
2✔
553
            } elseif ($type === ConstFieldType::TIMESTAMP2) {
15✔
554
                $values[$name] = $this->getTimestamp2($columnDTO);
2✔
555
            } elseif ($type === ConstFieldType::DATE) {
13✔
556
                $values[$name] = $this->getDate();
4✔
557
            } elseif ($type === ConstFieldType::YEAR) {
9✔
558
                // https://dev.mysql.com/doc/refman/5.7/en/year.html
559
                $year = $this->binaryDataReader->readUInt8();
1✔
560
                $values[$name] = 0 === $year ? null : 1900 + $year;
1✔
561
            } elseif ($type === ConstFieldType::ENUM) {
8✔
562
                $values[$name] = $this->getEnum($columnDTO);
1✔
563
            } elseif ($type === ConstFieldType::SET) {
7✔
564
                $values[$name] = $this->getSet($columnDTO);
1✔
565
            } elseif ($type === ConstFieldType::BIT) {
6✔
566
                $values[$name] = $this->getBit($columnDTO);
1✔
567
            } elseif ($type === ConstFieldType::GEOMETRY) {
5✔
568
                $values[$name] = $this->getString($columnDTO->getLengthSize());
1✔
569
            } elseif ($type === ConstFieldType::JSON) {
4✔
570
                $values[$name] = JsonBinaryDecoderService::makeJsonBinaryDecoder($this->getString($columnDTO->getLengthSize()))->parseToString();
4✔
571
            } else {
572
                throw new MySQLReplicationException('Unknown row type: ' . $type);
×
573
            }
574

575
            ++$nullBitmapIndex;
56✔
576
        }
577

578
        return $values;
56✔
579
    }
580

581
    protected function bitCount(string $bitmap): int
582
    {
583
        $n = 0;
56✔
584
        $bitmapLength = strlen($bitmap);
56✔
585
        for ($i = 0; $i < $bitmapLength; ++$i) {
56✔
586
            $bit = $bitmap[$i];
56✔
587
            if (is_string($bit)) {
56✔
588
                $bit = ord($bit);
56✔
589
            }
590
            $n += self::$bitCountInByte[$bit];
56✔
591
        }
592

593
        return $n;
56✔
594
    }
595

596
    protected function bitGet(string $bitmap, int $position): int
597
    {
598
        return $this->getBitFromBitmap($bitmap, $position) & (1 << ($position & 7));
56✔
599
    }
600

601
    protected function getBitFromBitmap(string $bitmap, int $position): int
602
    {
603
        $bit = $bitmap[(int)($position / 8)];
56✔
604
        if (is_string($bit)) {
56✔
605
            $bit = ord($bit);
56✔
606
        }
607

608
        return $bit;
56✔
609
    }
610

611
    protected function checkNull(string $nullBitmap, int $position): int
612
    {
613
        return $this->getBitFromBitmap($nullBitmap, $position) & (1 << ($position % 8));
56✔
614
    }
615

616
    /**
617
     * @throws BinaryDataReaderException
618
     */
619
    protected function getString(int $size): string
620
    {
621
        return $this->binaryDataReader->readLengthString($size);
17✔
622
    }
623

624
    /**
625
     * Read MySQL's new decimal format introduced in MySQL 5
626
     * https://dev.mysql.com/doc/refman/5.6/en/precision-math-decimal-characteristics.html
627
     * @throws BinaryDataReaderException
628
     */
629
    protected function getDecimal(ColumnDTO $columnDTO): string
630
    {
631
        $digitsPerInteger = 9;
10✔
632
        $compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4];
10✔
633
        $integral = $columnDTO->getPrecision() - $columnDTO->getDecimals();
10✔
634
        $unCompIntegral = (int)($integral / $digitsPerInteger);
10✔
635
        $unCompFractional = (int)($columnDTO->getDecimals() / $digitsPerInteger);
10✔
636
        $compIntegral = $integral - ($unCompIntegral * $digitsPerInteger);
10✔
637
        $compFractional = $columnDTO->getDecimals() - ($unCompFractional * $digitsPerInteger);
10✔
638

639
        $value = $this->binaryDataReader->readUInt8();
10✔
640
        if (0 !== ($value & 0x80)) {
10✔
641
            $mask = 0;
7✔
642
            $res = '';
7✔
643
        } else {
644
            $mask = -1;
3✔
645
            $res = '-';
3✔
646
        }
647
        $this->binaryDataReader->unread(pack('C', $value ^ 0x80));
10✔
648

649
        $size = $compressedBytes[$compIntegral];
10✔
650
        if ($size > 0) {
10✔
651
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
10✔
652
            $res .= $value;
10✔
653
        }
654

655
        for ($i = 0; $i < $unCompIntegral; ++$i) {
10✔
656
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
9✔
657
            $res .= sprintf('%09d', $value);
9✔
658
        }
659

660
        $res .= '.';
10✔
661

662
        for ($i = 0; $i < $unCompFractional; ++$i) {
10✔
663
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
4✔
664
            $res .= sprintf('%09d', $value);
4✔
665
        }
666

667
        $size = $compressedBytes[$compFractional];
10✔
668
        if ($size > 0) {
10✔
669
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
5✔
670
            $res .= sprintf('%0' . $compFractional . 'd', $value);
5✔
671
        }
672

673
        return bcmul($res, '1', $columnDTO->getDecimals());
10✔
674
    }
675

676
    protected function getDatetime(): ?string
677
    {
678
        $value = $this->binaryDataReader->readUInt64();
×
679
        // nasty mysql 0000-00-00 dates
680
        if ('0' === $value) {
×
681
            return null;
×
682
        }
683

684
        $date = DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s');
×
685
        $dateLastErrors = DateTime::getLastErrors();
×
686
        if ($dateLastErrors && array_sum($dateLastErrors) > 0) {
×
687
            return null;
×
688
        }
689

690
        return $date;
×
691
    }
692

693
    /**
694
     * Date Time
695
     * 1 bit  sign           (1= non-negative, 0= negative)
696
     * 17 bits year*13+month  (year 0-9999, month 0-12)
697
     * 5 bits day            (0-31)
698
     * 5 bits hour           (0-23)
699
     * 6 bits minute         (0-59)
700
     * 6 bits second         (0-59)
701
     * ---------------------------
702
     * 40 bits = 5 bytes
703
     *
704
     * @throws BinaryDataReaderException
705
     *
706
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
707
     */
708
    protected function getDatetime2(ColumnDTO $columnDTO): ?string
709
    {
710
        $data = $this->binaryDataReader->readIntBeBySize(5);
4✔
711

712
        $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40);
4✔
713

714
        $year = (int)($yearMonth / 13);
4✔
715
        $month = $yearMonth % 13;
4✔
716
        $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40);
4✔
717
        $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40);
4✔
718
        $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40);
4✔
719
        $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40);
4✔
720
        $fsp = $this->getFSP($columnDTO);
4✔
721

722
        try {
723
            $date = new DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
4✔
724
        } catch (Exception $exception) {
×
725
            return null;
×
726
        }
727
        $dateLastErrors = DateTime::getLastErrors();
4✔
728
        if ($dateLastErrors && array_sum($dateLastErrors) > 0) {
4✔
729
            return null;
3✔
730
        }
731

732
        return $date->format('Y-m-d H:i:s') . $fsp;
1✔
733
    }
734

735
    /**
736
     * @throws BinaryDataReaderException
737
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
738
     */
739
    protected function getFSP(ColumnDTO $columnDTO): string
740
    {
741
        $read = 0;
8✔
742
        $time = '';
8✔
743
        $fsp = $columnDTO->getFsp();
8✔
744
        if ($fsp === 1 || $fsp === 2) {
8✔
745
            $read = 1;
1✔
746
        } elseif ($fsp === 3 || $fsp === 4) {
8✔
747
            $read = 2;
1✔
748
        } elseif ($fsp === 5 || $fsp === 6) {
8✔
749
            $read = 3;
1✔
750
        }
751
        if ($read > 0) {
8✔
752
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
1✔
753
            if ($fsp % 2) {
1✔
754
                $microsecond = (int)($microsecond / 10);
1✔
755
            }
756
            $time = $microsecond * (10 ** (6 - $fsp));
1✔
757
        }
758

759
        return (string)$time;
8✔
760
    }
761

762
    protected function getTime(): string
763
    {
764
        $data = $this->binaryDataReader->readUInt24();
×
765
        if (0 === $data) {
×
766
            return '00:00:00';
×
767
        }
768

769
        return sprintf('%s%02d:%02d:%02d', $data < 0 ? '-' : '', $data / 10000, ($data % 10000) / 100, $data % 100);
×
770
    }
771

772
    /**
773
     * TIME encoding for non fractional part:
774
     * 1 bit sign    (1= non-negative, 0= negative)
775
     * 1 bit unused  (reserved for future extensions)
776
     * 10 bits hour   (0-838)
777
     * 6 bits minute (0-59)
778
     * 6 bits second (0-59)
779
     * ---------------------
780
     * 24 bits = 3 bytes
781
     *
782
     * @throws BinaryDataReaderException
783
     */
784
    protected function getTime2(ColumnDTO $columnDTO): string
785
    {
786
        $data = $this->binaryDataReader->readInt24Be();
2✔
787

788
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
2✔
789
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
2✔
790
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
2✔
791

792
        return (new DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($columnDTO);
2✔
793
    }
794

795
    /**
796
     * @throws BinaryDataReaderException
797
     */
798
    protected function getTimestamp2(ColumnDTO $columnDTO): string
799
    {
800
        $datetime = (string)date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
2✔
801
        $fsp = $this->getFSP($columnDTO);
2✔
802
        if ('' !== $fsp) {
2✔
803
            $datetime .= '.' . $fsp;
1✔
804
        }
805

806
        return $datetime;
2✔
807
    }
808

809
    protected function getDate(): ?string
810
    {
811
        $time = $this->binaryDataReader->readUInt24();
4✔
812
        if (0 === $time) {
4✔
813
            return null;
×
814
        }
815

816
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
4✔
817
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
4✔
818
        $day = ($time & ((1 << 5) - 1));
4✔
819
        if ($year === 0 || $month === 0 || $day === 0) {
4✔
820
            return null;
3✔
821
        }
822

823
        return (new DateTime())->setDate($year, $month, $day)->format('Y-m-d');
1✔
824
    }
825

826
    /**
827
     * @throws BinaryDataReaderException
828
     */
829
    protected function getEnum(ColumnDTO $columnDTO): string
830
    {
831
        $value = $this->binaryDataReader->readUIntBySize($columnDTO->getSize()) - 1;
1✔
832

833
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
834
        if (array_key_exists($value, $columnDTO->getEnumValues())) {
1✔
835
            return $columnDTO->getEnumValues()[$value];
1✔
836
        }
837

838
        return '';
1✔
839
    }
840

841
    /**
842
     * @throws BinaryDataReaderException
843
     */
844
    protected function getSet(ColumnDTO $columnDTO): array
845
    {
846
        // we read set columns as a bitmap telling us which options are enabled
847
        $bitMask = $this->binaryDataReader->readUIntBySize($columnDTO->getSize());
1✔
848
        $sets = [];
1✔
849
        foreach ($columnDTO->getSetValues() as $k => $item) {
1✔
850
            if ($bitMask & (2 ** $k)) {
1✔
851
                $sets[] = $item;
1✔
852
            }
853
        }
854

855
        return $sets;
1✔
856
    }
857

858
    protected function getBit(ColumnDTO $columnDTO): string
859
    {
860
        $res = '';
1✔
861
        for ($byte = 0; $byte < $columnDTO->getBytes(); ++$byte) {
1✔
862
            $currentByte = '';
1✔
863
            $data = $this->binaryDataReader->readUInt8();
1✔
864
            if (0 === $byte) {
1✔
865
                if (1 === $columnDTO->getBytes()) {
1✔
866
                    $end = $columnDTO->getBits();
1✔
867
                } else {
868
                    $end = $columnDTO->getBits() % 8;
1✔
869
                    if (0 === $end) {
1✔
870
                        $end = 8;
1✔
871
                    }
872
                }
873
            } else {
874
                $end = 8;
1✔
875
            }
876

877
            for ($bit = 0; $bit < $end; ++$bit) {
1✔
878
                if ($data & (1 << $bit)) {
1✔
879
                    $currentByte .= '1';
1✔
880
                } else {
881
                    $currentByte .= '0';
1✔
882
                }
883
            }
884
            $res .= strrev($currentByte);
1✔
885
        }
886

887
        return $res;
1✔
888
    }
889

890
    /**
891
     * @throws InvalidArgumentException
892
     * @throws BinaryDataReaderException
893
     * @throws JsonBinaryDecoderException
894
     * @throws MySQLReplicationException
895
     */
896
    public function makeDeleteRowsDTO(): ?DeleteRowsDTO
897
    {
898
        if (! $this->rowInit()) {
1✔
899
            return null;
×
900
        }
901

902
        $values = $this->getValues();
1✔
903

904
        return new DeleteRowsDTO(
1✔
905
            $this->eventInfo,
1✔
906
            $this->currentTableMap,
1✔
907
            count($values),
1✔
908
            $values
1✔
909
        );
1✔
910
    }
911

912
    /**
913
     * @throws InvalidArgumentException
914
     * @throws BinaryDataReaderException
915
     * @throws JsonBinaryDecoderException
916
     * @throws MySQLReplicationException
917
     */
918
    public function makeUpdateRowsDTO(): ?UpdateRowsDTO
919
    {
920
        if (! $this->rowInit()) {
4✔
921
            return null;
×
922
        }
923

924
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
4✔
925
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
4✔
926
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
4✔
927

928
        $values = [];
4✔
929
        while (! $this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
4✔
930
            $values[] = [
4✔
931
                'before' => $this->getColumnData($beforeBinaryData),
4✔
932
                'after' => $this->getColumnData($afterBinaryData)
4✔
933
            ];
4✔
934
        }
935

936
        return new UpdateRowsDTO(
4✔
937
            $this->eventInfo,
4✔
938
            $this->currentTableMap,
4✔
939
            count($values),
4✔
940
            $values
4✔
941
        );
4✔
942
    }
943
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc