Passed
Push — master ( e41b5c...f40e90 )
by kacper
02:51
created

RowEvent::getDatetime2()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0026

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 14
cts 15
cp 0.9333
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 16
nc 3
nop 1
crap 3.0026
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
        return \DateTime::createFromFormat('YmdHis', $value)->format('Y-m-d H:i:s');
494
    }
495
496
    /**
497
     * Date Time
498
     * 1 bit  sign           (1= non-negative, 0= negative)
499
     * 17 bits year*13+month  (year 0-9999, month 0-12)
500
     * 5 bits day            (0-31)
501
     * 5 bits hour           (0-23)
502
     * 6 bits minute         (0-59)
503
     * 6 bits second         (0-59)
504
     * ---------------------------
505
     * 40 bits = 5 bytes
506
     * @param array $column
507
     * @return string|null
508
     * @throws BinaryDataReaderException
509
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
510
     */
511 4
    protected function getDatetime2(array $column)
512
    {
513 4
        $data = $this->binaryDataReader->readIntBeBySize(5);
514
515 4
        $yearMonth = $this->binaryDataReader->getBinarySlice($data, 1, 17, 40);
516
517 4
        $year = (int)($yearMonth / 13);
518 4
        $month = $yearMonth % 13;
519 4
        $day = $this->binaryDataReader->getBinarySlice($data, 18, 5, 40);
520 4
        $hour = $this->binaryDataReader->getBinarySlice($data, 23, 5, 40);
521 4
        $minute = $this->binaryDataReader->getBinarySlice($data, 28, 6, 40);
522 4
        $second = $this->binaryDataReader->getBinarySlice($data, 34, 6, 40);
523
524
        try {
525 4
            $date = new \DateTime($year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second);
526 4
        } catch (\Exception $exception) {
527
            return null;
528
        }
529
        // not all errors are thrown as exception :(
530 4
        if (array_sum(\DateTime::getLastErrors()) > 0) {
531 3
            return null;
532
        }
533
534 1
        return $date->format('Y-m-d H:i:s') . $this->getFSP($column);
535
    }
536
537
    /**
538
     * @param array $column
539
     * @return string
540
     * @throws BinaryDataReaderException
541
     * @link https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
542
     */
543 5
    protected function getFSP(array $column)
544
    {
545 5
        $read = 0;
546 5
        $time = '';
547 5
        if ($column['fsp'] === 1 || $column['fsp'] === 2) {
548 1
            $read = 1;
549 5
        } elseif ($column['fsp'] === 3 || $column['fsp'] === 4) {
550 1
            $read = 2;
551 5
        } elseif ($column ['fsp'] === 5 || $column['fsp'] === 6) {
552 1
            $read = 3;
553 1
        }
554 5
        if ($read > 0) {
555 1
            $microsecond = $this->binaryDataReader->readIntBeBySize($read);
556
557 1
            $time = $microsecond;
558 1
            if ($column['fsp'] % 2) {
559 1
                $time = (int)($microsecond / 10);
560 1
            }
561 1
        }
562
563 5
        return (string)$time;
564
    }
565
566
    /**
567
     * TIME encoding for non fractional part:
568
     * 1 bit sign    (1= non-negative, 0= negative)
569
     * 1 bit unused  (reserved for future extensions)
570
     * 10 bits hour   (0-838)
571
     * 6 bits minute (0-59)
572
     * 6 bits second (0-59)
573
     * ---------------------
574
     * 24 bits = 3 bytes
575
     *
576
     * @param array $column
577
     * @return string
578
     * @throws BinaryDataReaderException
579
     */
580 2
    protected function getTime2(array $column)
581
    {
582 2
        $data = $this->binaryDataReader->readInt24Be();
583
584 2
        $hour = $this->binaryDataReader->getBinarySlice($data, 2, 10, 24);
585 2
        $minute = $this->binaryDataReader->getBinarySlice($data, 12, 6, 24);
586 2
        $second = $this->binaryDataReader->getBinarySlice($data, 18, 6, 24);
587
588 2
        return (new \DateTime())->setTime($hour, $minute, $second)->format('H:i:s') . $this->getFSP($column);
589
    }
590
591
    /**
592
     * @param array $column
593
     * @return bool|string
594
     * @throws EventException
595
     * @throws BinaryDataReaderException
596
     */
597 2
    protected function getTimestamp2(array $column)
598
    {
599 2
        $datetime = date('Y-m-d H:i:s', $this->binaryDataReader->readInt32Be());
600 2
        $fsp = $this->getFSP($column);
601 2
        if ('' !== $fsp) {
602 1
            $datetime .= '.' . $fsp;
603 1
        }
604
605 2
        return $datetime;
606
    }
607
608
    /**
609
     * @return string
610
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
611
     */
612 4
    protected function getDate()
613
    {
614 4
        $time = $this->binaryDataReader->readUInt24();
615 4
        if (0 === $time) {
616
            return null;
617
        }
618
619 4
        $year = ($time & ((1 << 15) - 1) << 9) >> 9;
620 4
        $month = ($time & ((1 << 4) - 1) << 5) >> 5;
621 4
        $day = ($time & ((1 << 5) - 1));
622 4
        if ($year === 0 || $month === 0 || $day === 0) {
623 3
            return null;
624
        }
625
626 1
        return (new \DateTime())->setDate($year, $month, $day)->format('Y-m-d');
627
    }
628
629
    /**
630
     * @param array $column
631
     * @return array
632
     * @throws EventException
633
     * @throws BinaryDataReaderException
634
     */
635 1
    protected function getSet(array $column)
636
    {
637
        // we read set columns as a bitmap telling us which options are enabled
638 1
        $bit_mask = $this->binaryDataReader->readUIntBySize($column['size']);
639 1
        $sets = [];
640 1
        foreach ((array)$column['set_values'] as $k => $item) {
641 1
            if ($bit_mask & pow(2, $k)) {
642 1
                $sets[] = $item;
643 1
            }
644 1
        }
645
646 1
        return $sets;
647
    }
648
649
    /**
650
     * Read MySQL BIT type
651
     * @param array $column
652
     * @return string
653
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
654
     */
655 1
    protected function getBit(array $column)
656
    {
657 1
        $res = '';
658 1
        for ($byte = 0; $byte < $column['bytes']; ++$byte) {
659 1
            $current_byte = '';
660 1
            $data = $this->binaryDataReader->readUInt8();
661 1
            if (0 === $byte) {
662 1
                if (1 === $column['bytes']) {
663 1
                    $end = $column['bits'];
664 1
                } else {
665 1
                    $end = $column['bits'] % 8;
666 1
                    if (0 === $end) {
667 1
                        $end = 8;
668 1
                    }
669
                }
670 1
            } else {
671 1
                $end = 8;
672
            }
673
674 1
            for ($bit = 0; $bit < $end; ++$bit) {
675 1
                if ($data & (1 << $bit)) {
676 1
                    $current_byte .= '1';
677 1
                } else {
678 1
                    $current_byte .= '0';
679
                }
680
681 1
            }
682 1
            $res .= strrev($current_byte);
683 1
        }
684
685 1
        return $res;
686
    }
687
688
    /**
689
     * @return DeleteRowsDTO
690
     * @throws InvalidArgumentException
691
     * @throws BinaryDataReaderException
692
     * @throws EventException
693
     * @throws JsonBinaryDecoderException
694
     * @throws MySQLReplicationException
695
     */
696 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...
697
    {
698 1
        if (!$this->rowInit()) {
699
            return null;
700
        }
701
702 1
        $values = $this->getValues();
703
704 1
        return new DeleteRowsDTO(
705 1
            $this->eventInfo,
706 1
            $this->currentTableMap,
707 1
            count($values),
708
            $values
709 1
        );
710
    }
711
712
    /**
713
     * @return UpdateRowsDTO
714
     * @throws InvalidArgumentException
715
     * @throws BinaryDataReaderException
716
     * @throws EventException
717
     * @throws JsonBinaryDecoderException
718
     * @throws MySQLReplicationException
719
     */
720 1
    public function makeUpdateRowsDTO()
721
    {
722 1
        if (!$this->rowInit()) {
723
            return null;
724
        }
725
726 1
        $columnsBinarySize = $this->getColumnsBinarySize($this->currentTableMap->getColumnsAmount());
727 1
        $beforeBinaryData = $this->binaryDataReader->read($columnsBinarySize);
728 1
        $afterBinaryData = $this->binaryDataReader->read($columnsBinarySize);
729
730 1
        $values = [];
731 1
        while (!$this->binaryDataReader->isComplete($this->eventInfo->getSizeNoHeader())) {
732 1
            $values[] = [
733 1
                'before' => $this->getColumnData($beforeBinaryData),
734 1
                'after' => $this->getColumnData($afterBinaryData)
735 1
            ];
736 1
        }
737
738 1
        return new UpdateRowsDTO(
739 1
            $this->eventInfo,
740 1
            $this->currentTableMap,
741 1
            count($values),
742
            $values
743 1
        );
744
    }
745
746
    /**
747
     * @param array $column
748
     * @return string
749
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
750
     */
751 1
    protected function getEnum(array $column)
752
    {
753 1
        $value = $this->binaryDataReader->readUIntBySize($column['size']) - 1;
754
755
        // check if given value exists in enums, if there not existing enum mysql returns empty string.
756 1
        if (array_key_exists($value, $column['enum_values'])) {
757 1
            return $column['enum_values'][$value];
758
        }
759
760 1
        return '';
761
    }
762
}
763