Passed
Branch master (e090ea)
by kacper
04:22
created

RowEvent::getBinarySlice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 4
1
<?php
2
3
namespace MySQLReplication\Event\RowEvent;
4
5
use MySQLReplication\BinaryDataReader\BinaryDataReader;
6
use MySQLReplication\BinaryDataReader\BinaryDataReaderException;
7
use MySQLReplication\Config\Config;
8
use MySQLReplication\Definitions\ConstEventType;
9
use MySQLReplication\Definitions\ConstFieldType;
10
use MySQLReplication\Event\DTO\DeleteRowsDTO;
11
use MySQLReplication\Event\DTO\TableMapDTO;
12
use MySQLReplication\Event\DTO\UpdateRowsDTO;
13
use MySQLReplication\Event\DTO\WriteRowsDTO;
14
use MySQLReplication\Event\EventCommon;
15
use MySQLReplication\Event\EventException;
16
use MySQLReplication\Event\EventInfo;
17
use MySQLReplication\Exception\MySQLReplicationException;
18
use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderException;
19
use MySQLReplication\JsonBinaryDecoder\JsonBinaryDecoderFactory;
20
use MySQLReplication\Repository\RepositoryInterface;
21
use Psr\SimpleCache\CacheInterface;
22
use Psr\SimpleCache\InvalidArgumentException;
23
24
/**
25
 * Class RowEvent
26
 * @package MySQLReplication\RowEvent
27
 */
28
class RowEvent extends EventCommon
29
{
30
    /**
31
     * @var array
32
     */
33
    private static $bitCountInByte = [
34
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
35
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
36
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
37
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
38
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
39
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
40
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
41
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
42
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
43
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
44
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
45
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
46
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
47
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
48
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
49
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
50
    ];
51
    /**
52
     * @var RepositoryInterface
53
     */
54
    private $repository;
55
    /**
56
     * @var TableMap
57
     */
58
    private $currentTableMap;
59
    /**
60
     * @var JsonBinaryDecoderFactory
61
     */
62
    private $jsonBinaryDecoderFactory;
63
    /**
64
     * @var CacheInterface
65
     */
66
    private $cache;
67
68
    /**
69
     * RowEvent constructor.
70
     * @param RepositoryInterface $repository
71
     * @param BinaryDataReader $binaryDataReader
72
     * @param EventInfo $eventInfo
73
     * @param JsonBinaryDecoderFactory $jsonBinaryDecoderFactory
74
     * @param CacheInterface $cache
75
     */
76 53
    public function __construct(
77
        RepositoryInterface $repository,
78
        BinaryDataReader $binaryDataReader,
79
        EventInfo $eventInfo,
80
        JsonBinaryDecoderFactory $jsonBinaryDecoderFactory,
81
        CacheInterface $cache
82
    ) {
83 53
        parent::__construct($eventInfo, $binaryDataReader);
84
85 53
        $this->repository = $repository;
86 53
        $this->jsonBinaryDecoderFactory = $jsonBinaryDecoderFactory;
87 53
        $this->cache = $cache;
88 53
    }
89
90
    /**
91
     * This describe the structure of a table.
92
     * It's send before a change append on a table.
93
     * A end user of the lib should have no usage of this
94
     *
95
     * @return TableMapDTO
96
     * @throws \Psr\SimpleCache\InvalidArgumentException
97
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
98
     */
99 52
    public function makeTableMapDTO()
100
    {
101 52
        $data = [];
102 52
        $data['table_id'] = $this->binaryDataReader->readTableId();
103 52
        $this->binaryDataReader->advance(2);
104 52
        $data['schema_length'] = $this->binaryDataReader->readUInt8();
105 52
        $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']);
106
107 52
        if (Config::checkDataBasesOnly($data['schema_name'])) {
108
            return null;
109
        }
110
111 52
        $this->binaryDataReader->advance(1);
112 52
        $data['table_length'] = $this->binaryDataReader->readUInt8();
113 52
        $data['table_name'] = $this->binaryDataReader->read($data['table_length']);
114
115 52
        if (Config::checkTablesOnly($data['table_name'])) {
116 1
            return null;
117
        }
118
119 52
        $this->binaryDataReader->advance(1);
120 52
        $data['columns_amount'] = $this->binaryDataReader->readCodedBinary();
121 52
        $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']);
122
123 52
        if ($this->cache->has($data['table_id'])) {
124 2
            return new TableMapDTO($this->eventInfo, $this->cache->get($data['table_id']));
125
        }
126
127 52
        $this->binaryDataReader->readCodedBinary();
128
129 52
        $columns = $this->repository->getFields($data['schema_name'], $data['table_name']);
130
131 52
        $fields = [];
132
        // if you drop tables and parse of logs you will get empty scheme
133 52
        if (!empty($columns)) {
134 51
            $columnLength = strlen($data['column_types']);
135 51
            for ($i = 0; $i < $columnLength; ++$i) {
136
                // this a dirty hack to prevent row events containing columns which have been dropped
137 51
                if (!isset($columns[$i])) {
138 1
                    $columns[$i] = [
139 1
                        'COLUMN_NAME' => 'DROPPED_COLUMN_' . $i,
140 1
                        'COLLATION_NAME' => null,
141 1
                        'CHARACTER_SET_NAME' => null,
142 1
                        'COLUMN_COMMENT' => null,
143 1
                        'COLUMN_TYPE' => 'BLOB',
144
                        'COLUMN_KEY' => ''
145 1
                    ];
146
147 1
                    $type = ConstFieldType::IGNORE;
148 1
                } else {
149 51
                    $type = ord($data['column_types'][$i]);
150
                }
151
152 51
                $fields[$i] = Columns::parse($type, $columns[$i], $this->binaryDataReader);
153 51
            }
154 51
        }
155
156 52
        $tableMap = new TableMap(
157 52
            $data['schema_name'],
158 52
            $data['table_name'],
159 52
            $data['table_id'],
160 52
            $data['columns_amount'],
161
            $fields
162 52
        );
163
164 52
        $this->cache->set($data['table_id'], $tableMap);
165
166 52
        return new TableMapDTO($this->eventInfo, $tableMap);
167
    }
168
169
    /**
170
     * @return WriteRowsDTO|null
171
     * @throws InvalidArgumentException
172
     * @throws BinaryDataReaderException
173
     * @throws EventException
174
     * @throws JsonBinaryDecoderException
175
     * @throws MySQLReplicationException
176
     */
177 52 View Code Duplication
    public function makeWriteRowsDTO()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
    {
179 52
        if (!$this->rowInit()) {
180 1
            return null;
181
        }
182
183 52
        $values = $this->getValues();
184
185 52
        return new WriteRowsDTO(
186 52
            $this->eventInfo,
187 52
            $this->currentTableMap,
188 52
            count($values),
189
            $values
190 52
        );
191
    }
192
193
    /**
194
     * @return bool
195
     * @throws InvalidArgumentException
196
     * @throws BinaryDataReaderException
197
     */
198 52
    protected function rowInit()
199
    {
200 52
        $tableId = $this->binaryDataReader->readTableId();
201 52
        $this->binaryDataReader->advance(2);
202
203 52
        if (in_array(
204 52
            $this->eventInfo->getType(), [
205 52
            ConstEventType::DELETE_ROWS_EVENT_V2,
206 52
            ConstEventType::WRITE_ROWS_EVENT_V2,
207
            ConstEventType::UPDATE_ROWS_EVENT_V2
208 52
        ], true
209 52
        )) {
210 52
            $this->binaryDataReader->read($this->binaryDataReader->readUInt16() / 8);
211 52
        }
212
213 52
        $this->binaryDataReader->readCodedBinary();
214
215 52
        if ($this->cache->has($tableId)) {
216
            /** @var TableMap $tableMap */
217 52
            $this->currentTableMap = $this->cache->get($tableId);
218 52
            return true;
219
        }
220
221 1
        return false;
222
    }
223
224
    /**
225
     * @return array
226
     * @throws BinaryDataReaderException
227
     * @throws EventException
228
     * @throws JsonBinaryDecoderException
229
     * @throws MySQLReplicationException
230
     */
231 52
    protected function getValues()
232
    {
233
        // if we don't get columns from information schema we don't know how to assign them
234 52
        if ([] === $this->currentTableMap->getFields()) {
235 1
            return [];
236
        }
237
238 51
        $binaryData = $this->binaryDataReader->read(
239 51
            $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount())
240 51
        );
241
242 51
        $values = [];
243 51
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
244 51
            $values[] = $this->getColumnData($binaryData);
245 51
        }
246
247 51
        return $values;
248
    }
249
250
    /**
251
     * @param int $columnsAmount
252
     * @return int
253
     */
254 51
    protected function getColumnsBinarySize($columnsAmount)
255
    {
256 51
        return (int)(($columnsAmount + 7) / 8);
257
    }
258
259
    /**
260
     * @param int $colsBitmap
261
     * @return array
262
     * @throws BinaryDataReaderException
263
     * @throws EventException
264
     * @throws JsonBinaryDecoderException
265
     * @throws MySQLReplicationException
266
     */
267 51
    protected function getColumnData($colsBitmap)
268
    {
269 51
        $values = [];
270
271
        // null bitmap length = (bits set in 'columns-present-bitmap'+7)/8
272
        // see http://dev.mysql.com/doc/internals/en/rows-event.html
273 51
        $null_bitmap = $this->binaryDataReader->read($this->getColumnsBinarySize($this->bitCount($colsBitmap)));
274 51
        $nullBitmapIndex = 0;
275
276 51
        foreach ($this->currentTableMap->getFields() as $i => $column) {
277 51
            $name = $column['name'];
278
279 51
            if (0 === $this->bitGet($colsBitmap, $i)) {
280
                $values[$name] = null;
281
                continue;
282
            }
283
284 51
            if ($this->checkNull($null_bitmap, $nullBitmapIndex)) {
285 5
                $values[$name] = null;
286 51
            } elseif ($column['type'] === ConstFieldType::IGNORE) {
287 1
                $this->binaryDataReader->advance($column['length_size']);
288 1
                $values[$name] = null;
289 51 View Code Duplication
            } elseif ($column['type'] === ConstFieldType::TINY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
290 5
                if ($column['unsigned']) {
291 4
                    $values[$name] = $this->binaryDataReader->readUInt8();
292 4
                } else {
293 4
                    $values[$name] = $this->binaryDataReader->readInt8();
294
                }
295 51
            } elseif ($column['type'] === ConstFieldType::SHORT) {
296 1
                if ($column['unsigned']) {
297 1
                    $values[$name] = $this->binaryDataReader->readUInt16();
298 1
                } else {
299 1
                    $values[$name] = $this->binaryDataReader->readInt16();
300
                }
301 46 View Code Duplication
            } elseif ($column['type'] === ConstFieldType::LONG) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302 10
                if ($column['unsigned']) {
303 1
                    $values[$name] = $this->binaryDataReader->readUInt32();
304 1
                } else {
305 10
                    $values[$name] = $this->binaryDataReader->readInt32();
306
                }
307 45
            } elseif ($column['type'] === ConstFieldType::LONGLONG) {
308 1
                if ($column['unsigned']) {
309 1
                    $values[$name] = $this->binaryDataReader->readUInt64();
310 1
                } else {
311 1
                    $values[$name] = $this->binaryDataReader->readInt64();
312
                }
313 43 View Code Duplication
            } elseif ($column['type'] === ConstFieldType::INT24) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314 1
                if ($column['unsigned']) {
315 1
                    $values[$name] = $this->binaryDataReader->readUInt24();
316 1
                } else {
317 1
                    $values[$name] = $this->binaryDataReader->readInt24();
318
                }
319 42
            } elseif ($column['type'] === ConstFieldType::FLOAT) {
320
                // http://dev.mysql.com/doc/refman/5.7/en/floating-point-types.html FLOAT(7,4)
321 1
                $values[$name] = round($this->binaryDataReader->readFloat(), 4);
322 41
            } elseif ($column['type'] === ConstFieldType::DOUBLE) {
323 1
                $values[$name] = $this->binaryDataReader->readDouble();
324 40
            } elseif ($column['type'] === ConstFieldType::VARCHAR || $column['type'] === ConstFieldType::STRING) {
325 8
                $values[$name] = $column['max_length'] > 255 ? $this->getString(2) : $this->getString(1);
326 39
            } elseif ($column['type'] === ConstFieldType::NEWDECIMAL) {
327 10
                $values[$name] = $this->getDecimal($column);
328 31 View Code Duplication
            } elseif ($column['type'] === ConstFieldType::BLOB) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329 4
                $values[$name] = $this->getString($column['length_size']);
330 21
            } elseif ($column['type'] === ConstFieldType::DATETIME) {
331
                $values[$name] = $this->getDatetime();
332 17
            } elseif ($column['type'] === ConstFieldType::DATETIME2) {
333 4
                $values[$name] = $this->getDatetime2($column);
334 17
            } elseif ($column['type'] === ConstFieldType::TIMESTAMP) {
335
                $values[$name] = date('c', $this->binaryDataReader->readUInt32());
336 13
            } elseif ($column['type'] === ConstFieldType::TIME2) {
337 2
                $values[$name] = $this->getTime2($column);
338 13
            } elseif ($column['type'] === ConstFieldType::TIMESTAMP2) {
339 2
                $values[$name] = $this->getTimestamp2($column);
340 11
            } elseif ($column['type'] === ConstFieldType::DATE) {
341 4
                $values[$name] = $this->getDate();
342 9
            } elseif ($column['type'] === ConstFieldType::YEAR) {
343
                // https://dev.mysql.com/doc/refman/5.7/en/year.html
344 1
                $year = $this->binaryDataReader->readUInt8();
345 1
                $values[$name] = 0 === $year ? null : 1900 + $year;
346 5
            } elseif ($column['type'] === ConstFieldType::ENUM) {
347 1
                $values[$name] = $this->getEnum($column);
348 4
            } elseif ($column['type'] === ConstFieldType::SET) {
349 1
                $values[$name] = $this->getSet($column);
350 3
            } elseif ($column['type'] === ConstFieldType::BIT) {
351 1
                $values[$name] = $this->getBit($column);
352 2 View Code Duplication
            } elseif ($column['type'] === ConstFieldType::GEOMETRY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
353 1
                $values[$name] = $this->getString($column['length_size']);
354 1
            } elseif ($column['type'] === ConstFieldType::JSON) {
355
                $values[$name] = $this->jsonBinaryDecoderFactory->makeJsonBinaryDecoder(
356
                    $this->getString($column['length_size'])
357
                )->parseToString();
358
            } else {
359
                throw new MySQLReplicationException('Unknown row type: ' . $column['type']);
360
            }
361
362 51
            ++$nullBitmapIndex;
363 51
        }
364
365 51
        return $values;
366
    }
367
368
    /**
369
     * @param string $bitmap
370
     * @return int
371
     */
372 51
    protected function bitCount($bitmap)
373
    {
374 51
        $n = 0;
375 51
        $bitmapLength = strlen($bitmap);
376 51
        for ($i = 0; $i < $bitmapLength; ++$i) {
377 51
            $bit = $bitmap[$i];
378 51
            if (is_string($bit)) {
379 51
                $bit = ord($bit);
380 51
            }
381 51
            $n += self::$bitCountInByte[$bit];
382 51
        }
383
384 51
        return $n;
385
    }
386
387
    /**
388
     * @param string $bitmap
389
     * @param int $position
390
     * @return int
391
     */
392 51 View Code Duplication
    protected function bitGet($bitmap, $position)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
393
    {
394 51
        $bit = $bitmap[(int)($position / 8)];
395 51
        if (is_string($bit)) {
396 51
            $bit = ord($bit);
397 51
        }
398
399 51
        return $bit & (1 << ($position & 7));
400
    }
401
402
    /**
403
     * @param string $nullBitmap
404
     * @param int $position
405
     * @return int
406
     */
407 51 View Code Duplication
    protected function checkNull($nullBitmap, $position)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
    {
409 51
        $bit = $nullBitmap[(int)($position / 8)];
410 51
        if (is_string($bit)) {
411 51
            $bit = ord($bit);
412 51
        }
413
414 51
        return $bit & (1 << ($position % 8));
415
    }
416
417
    /**
418
     * @param int $size
419
     * @return string
420
     * @throws BinaryDataReaderException
421
     */
422 13
    protected function getString($size)
423
    {
424 13
        return $this->binaryDataReader->readLengthCodedPascalString($size);
425
    }
426
427
    /**
428
     * Read MySQL's new decimal format introduced in MySQL 5
429
     * https://dev.mysql.com/doc/refman/5.6/en/precision-math-decimal-characteristics.html
430
     * @param array $column
431
     * @return string
432
     * @throws BinaryDataReaderException
433
     */
434 10
    protected function getDecimal(array $column)
435
    {
436 10
        $digitsPerInteger = 9;
437 10
        $compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4];
438 10
        $integral = $column['precision'] - $column['decimals'];
439 10
        $unCompIntegral = (int)($integral / $digitsPerInteger);
440 10
        $unCompFractional = (int)($column['decimals'] / $digitsPerInteger);
441 10
        $compIntegral = $integral - ($unCompIntegral * $digitsPerInteger);
442 10
        $compFractional = $column['decimals'] - ($unCompFractional * $digitsPerInteger);
443
444 10
        $value = $this->binaryDataReader->readUInt8();
445 10
        if (0 !== ($value & 0x80)) {
446 7
            $mask = 0;
447 7
            $res = '';
448 7
        } else {
449 3
            $mask = -1;
450 3
            $res = '-';
451
        }
452 10
        $this->binaryDataReader->unread(pack('C', $value ^ 0x80));
453
454 10
        $size = $compressedBytes[$compIntegral];
455 10
        if ($size > 0) {
456 10
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
457 10
            $res .= $value;
458 10
        }
459
460 10 View Code Duplication
        for ($i = 0; $i < $unCompIntegral; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
461 9
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
462 9
            $res .= sprintf('%09d', $value);
463 9
        }
464
465 10
        $res .= '.';
466
467 10 View Code Duplication
        for ($i = 0; $i < $unCompFractional; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468 4
            $value = $this->binaryDataReader->readInt32Be() ^ $mask;
469 4
            $res .= sprintf('%09d', $value);
470 4
        }
471
472 10
        $size = $compressedBytes[$compFractional];
473 10
        if ($size > 0) {
474 5
            $value = $this->binaryDataReader->readIntBeBySize($size) ^ $mask;
475 5
            $res .= sprintf('%0' . $compFractional . 'd', $value);
476 5
        }
477
478 10
        return bcmul($res, 1, $column['precision']);
479
    }
480
481
    /**
482
     * @return null|string
483
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
484
     */
485
    protected function getDatetime()
486
    {
487
        $value = $this->binaryDataReader->readUInt64();
488
        // nasty mysql 0000-00-00 dates
489
        if ('0' === $value) {
490
            return null;
491
        }
492
493
        $date = $value / 1000000;
494
        $year = (int)($date / 10000);
495
        $month = (int)(($date % 10000) / 100);
496
        $day = (int)($date % 100);
497
        if ($year === 0 || $month === 0 || $day === 0) {
498
            return null;
499
        }
500
501
        return (new \DateTime())->setDate($year, $month, $day)->format('Y-m-d');
502
    }
503
504
    /**
505
     * Date Time
506
     * 1 bit  sign           (1= non-negative, 0= negative)
507
     * 17 bits year*13+month  (year 0-9999, month 0-12)
508
     * 5 bits day            (0-31)
509
     * 5 bits hour           (0-23)
510
     * 6 bits minute         (0-59)
511
     * 6 bits second         (0-59)
512
     * ---------------------------
513
     * 40 bits = 5 bytes
514
     * @param array $column
515
     * @return string|null
516
     * @throws BinaryDataReaderException
517
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
518
     */
519 4
    protected function getDatetime2(array $column)
520
    {
521 4
        $data = $this->binaryDataReader->readIntBeBySize(5);
522
523 4
        $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40);
524
525 4
        $year = (int)($yearMonth / 13);
526 4
        $month = $yearMonth % 13;
527 4
        $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40);
528 4
        $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40);
529 4
        $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40);
530 4
        $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40);
531
532
        try {
533 4
            $date = new \DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
534 4
        } catch (\Exception $exception) {
535
            return null;
536
        }
537
        // not all errors are thrown as exception :(
538 4
        if (array_sum(\DateTime::getLastErrors()) > 0) {
539 3
            return null;
540
        }
541
542 1
        return $date->format('Y-m-d H:i:s') . $this->getFSP($column);
543
    }
544
545
    /**
546
     * @param array $column
547
     * @return string
548
     * @throws BinaryDataReaderException
549
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
550
     */
551 5
    protected function getFSP(array $column)
552
    {
553 5
        $read = 0;
554 5
        $time = '';
555 5
        if ($column['fsp'] === 1 || $column['fsp'] === 2) {
556 1
            $read = 1;
557 5
        } elseif ($column['fsp'] === 3 || $column['fsp'] === 4) {
558 1
            $read = 2;
559 5
        } elseif ($column ['fsp'] === 5 || $column['fsp'] === 6) {
560 1
            $read = 3;
561 1
        }
562 5
        if ($read > 0) {
563 1
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
564
565 1
            $time = $microsecond;
566 1
            if ($column['fsp'] % 2) {
567 1
                $time = (int)($microsecond / 10);
568 1
            }
569 1
        }
570
571 5
        return (string)$time;
572
    }
573
574
    /**
575
     * TIME encoding for non fractional part:
576
     * 1 bit sign    (1= non-negative, 0= negative)
577
     * 1 bit unused  (reserved for future extensions)
578
     * 10 bits hour   (0-838)
579
     * 6 bits minute (0-59)
580
     * 6 bits second (0-59)
581
     * ---------------------
582
     * 24 bits = 3 bytes
583
     *
584
     * @param array $column
585
     * @return string
586
     * @throws BinaryDataReaderException
587
     */
588 2
    protected function getTime2(array $column)
589
    {
590 2
        $data = $this->binaryDataReader->readInt24Be();
591
592 2
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
593 2
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
594 2
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
595
596 2
        return (new \DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($column);
597
    }
598
599
    /**
600
     * @param array $column
601
     * @return bool|string
602
     * @throws EventException
603
     * @throws BinaryDataReaderException
604
     */
605 2
    protected function getTimestamp2(array $column)
606
    {
607 2
        $datetime = date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
608 2
        $fsp = $this->getFSP($column);
609 2
        if ('' !== $fsp) {
610 1
            $datetime .= '.' . $fsp;
611 1
        }
612
613 2
        return $datetime;
614
    }
615
616
    /**
617
     * @return string
618
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
619
     */
620 4
    protected function getDate()
621
    {
622 4
        $time = $this->binaryDataReader->readUInt24();
623 4
        if (0 === $time) {
624
            return null;
625
        }
626
627 4
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
628 4
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
629 4
        $day = ($time & ((1 << 5) - 1));
630 4
        if ($year === 0 || $month === 0 || $day === 0) {
631 3
            return null;
632
        }
633
634 1
        return (new \DateTime())->setDate($year, $month, $day)->format('Y-m-d');
635
    }
636
637
    /**
638
     * @param array $column
639
     * @return array
640
     * @throws EventException
641
     * @throws BinaryDataReaderException
642
     */
643 1
    protected function getSet(array $column)
644
    {
645
        // we read set columns as a bitmap telling us which options are enabled
646 1
        $bit_mask = $this->binaryDataReader->readUIntBySize($column['size']);
647 1
        $sets = [];
648 1
        foreach ((array)$column['set_values'] as $k => $item) {
649 1
            if ($bit_mask & pow(2, $k)) {
650 1
                $sets[] = $item;
651 1
            }
652 1
        }
653
654 1
        return $sets;
655
    }
656
657
    /**
658
     * Read MySQL BIT type
659
     * @param array $column
660
     * @return string
661
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
662
     */
663 1
    protected function getBit(array $column)
664
    {
665 1
        $res = '';
666 1
        for ($byte = 0; $byte < $column['bytes']; ++$byte) {
667 1
            $current_byte = '';
668 1
            $data = $this->binaryDataReader->readUInt8();
669 1
            if (0 === $byte) {
670 1
                if (1 === $column['bytes']) {
671 1
                    $end = $column['bits'];
672 1
                } else {
673 1
                    $end = $column['bits'] % 8;
674 1
                    if (0 === $end) {
675 1
                        $end = 8;
676 1
                    }
677
                }
678 1
            } else {
679 1
                $end = 8;
680
            }
681
682 1
            for ($bit = 0; $bit < $end; ++$bit) {
683 1
                if ($data & (1 << $bit)) {
684 1
                    $current_byte .= '1';
685 1
                } else {
686 1
                    $current_byte .= '0';
687
                }
688
689 1
            }
690 1
            $res .= strrev($current_byte);
691 1
        }
692
693 1
        return $res;
694
    }
695
696
    /**
697
     * @return DeleteRowsDTO
698
     * @throws InvalidArgumentException
699
     * @throws BinaryDataReaderException
700
     * @throws EventException
701
     * @throws JsonBinaryDecoderException
702
     * @throws MySQLReplicationException
703
     */
704 1 View Code Duplication
    public function makeDeleteRowsDTO()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
705
    {
706 1
        if (!$this->rowInit()) {
707
            return null;
708
        }
709
710 1
        $values = $this->getValues();
711
712 1
        return new DeleteRowsDTO(
713 1
            $this->eventInfo,
714 1
            $this->currentTableMap,
715 1
            count($values),
716
            $values
717 1
        );
718
    }
719
720
    /**
721
     * @return UpdateRowsDTO
722
     * @throws InvalidArgumentException
723
     * @throws BinaryDataReaderException
724
     * @throws EventException
725
     * @throws JsonBinaryDecoderException
726
     * @throws MySQLReplicationException
727
     */
728 1
    public function makeUpdateRowsDTO()
729
    {
730 1
        if (!$this->rowInit()) {
731
            return null;
732
        }
733
734 1
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
735 1
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
736 1
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
737
738 1
        $values = [];
739 1
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
740 1
            $values[] = [
741 1
                'before' => $this->getColumnData($beforeBinaryData),
742 1
                'after' => $this->getColumnData($afterBinaryData)
743 1
            ];
744 1
        }
745
746 1
        return new UpdateRowsDTO(
747 1
            $this->eventInfo,
748 1
            $this->currentTableMap,
749 1
            count($values),
750
            $values
751 1
        );
752
    }
753
754
    /**
755
     * @param array $column
756
     * @return string
757
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
758
     */
759 1
    protected function getEnum(array $column)
760
    {
761 1
        $value = $this->binaryDataReader->readUIntBySize($column['size']) - 1;
762
763
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
764 1
        if (array_key_exists($value, $column['enum_values'])) {
765 1
            return $column['enum_values'][$value];
766
        }
767
768 1
        return '';
769
    }
770
}
771