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() |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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() |
|
|
|
|
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
|
|
|
|
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.