1
|
|
|
<?php |
2
|
|
|
namespace Fwlib\Db; |
3
|
|
|
|
4
|
|
|
use Fwlib\Bridge\Adodb; |
5
|
|
|
use Fwlib\Util\UtilContainerAwareTrait; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Compare db data before and after write |
9
|
|
|
* |
10
|
|
|
* Diff data array format: |
11
|
|
|
* {table: [{mode, 'pk': pk, 'column': column}]} |
12
|
|
|
* |
13
|
|
|
* mode is one of INSERT, DELETE, UPDATE. |
14
|
|
|
* |
15
|
|
|
* pk is array of primary key and their new/old value, format: |
16
|
|
|
* {pk: {new, old}}. |
17
|
|
|
* |
18
|
|
|
* column is array of other columns changed, format: |
19
|
|
|
* {column: {new, old}}. |
20
|
|
|
* |
21
|
|
|
* @deprecated |
22
|
|
|
* |
23
|
|
|
* @copyright Copyright 2012-2015 Fwolf |
24
|
|
|
* @license http://www.gnu.org/licenses/lgpl.html LGPL-3.0+ |
25
|
|
|
*/ |
26
|
|
|
class DbDiff |
27
|
|
|
{ |
28
|
|
|
use UtilContainerAwareTrait; |
29
|
|
|
|
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Counter of DbDiff changed rows |
33
|
|
|
* |
34
|
|
|
* @var int |
35
|
|
|
*/ |
36
|
|
|
protected $rowCount = 0; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var Adodb |
40
|
|
|
*/ |
41
|
|
|
protected $db = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Diff data array |
45
|
|
|
* |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
protected $diff = []; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* DbDiff execute status |
52
|
|
|
* |
53
|
|
|
* 0 not executed |
54
|
|
|
* 100 committed |
55
|
|
|
* -100 rollbacked |
56
|
|
|
* |
57
|
|
|
* @var int |
58
|
|
|
*/ |
59
|
|
|
protected $executeStatus = 0; |
60
|
|
|
|
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Constructor |
64
|
|
|
* |
65
|
|
|
* @param Adodb $db |
66
|
|
|
*/ |
67
|
|
|
public function __construct($db) |
68
|
|
|
{ |
69
|
|
|
$this->db = $db; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Check PK array is valid or throw exception |
75
|
|
|
* |
76
|
|
|
* @param string $table |
77
|
|
|
* @param array $pkArray |
78
|
|
|
* @param array &$rowArray |
79
|
|
|
*/ |
80
|
|
|
protected function checkPkArray($table, array $pkArray, array &$rowArray) |
81
|
|
|
{ |
82
|
|
|
if (empty($pkArray)) { |
83
|
|
|
throw new \Exception( |
84
|
|
|
"Table $table must have PK defined" |
85
|
|
|
); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
// array_intersect() is less loop than array_key_exist() check. |
89
|
|
|
// Can't use isset when do array_key_exist(), bcs isset(null) == false |
90
|
|
|
foreach ($rowArray as $index => &$row) { |
91
|
|
|
if (count($pkArray) != |
92
|
|
|
count(array_intersect($pkArray, array_keys($row))) |
93
|
|
|
) { |
94
|
|
|
throw new \Exception( |
95
|
|
|
"PK not all assigned in new array," . |
96
|
|
|
" table $table index $index" |
97
|
|
|
); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
unset($row); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Commit diff result, change db |
106
|
|
|
* |
107
|
|
|
* @return DbDiff |
108
|
|
|
*/ |
109
|
|
View Code Duplication |
public function commit() |
|
|
|
|
110
|
|
|
{ |
111
|
|
|
if (empty($this->diff)) { |
112
|
|
|
throw new \Exception('No diff data'); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
if ($this->isCommitted()) { |
116
|
|
|
throw new \Exception( |
117
|
|
|
'Committed DbDiff can\'t commit again' |
118
|
|
|
); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$sqlArray = $this->generateCommitSql(); |
122
|
|
|
|
123
|
|
|
$this->db->BeginTrans(); |
|
|
|
|
124
|
|
|
|
125
|
|
|
try { |
126
|
|
|
foreach ((array)$sqlArray as $sql) { |
127
|
|
|
$this->db->execute($sql); |
128
|
|
|
|
129
|
|
|
if (0 != $this->db->getErrorCode()) { |
130
|
|
|
throw new \Exception($this->db->getErrorMessage()); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$this->db->CommitTrans(); |
|
|
|
|
135
|
|
|
$this->rowCount = count($sqlArray); |
136
|
|
|
$this->executeStatus = 100; |
137
|
|
|
|
138
|
|
|
} catch (\Exception $e) { |
139
|
|
|
$this->db->RollbackTrans(); |
|
|
|
|
140
|
|
|
|
141
|
|
|
throw new \Exception($e->getMessage()); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Compare new data with old data in db |
150
|
|
|
* |
151
|
|
|
* New/old array are all assoc, index by table column. PK column must |
152
|
|
|
* included in new array, but its value can be null. |
153
|
|
|
* |
154
|
|
|
* In new array, null PK value means DELETE operate. |
155
|
|
|
* In old array, null PK value means INSERT operate. |
156
|
|
|
* |
157
|
|
|
* Multi table and row supported. |
158
|
|
|
* |
159
|
|
|
* New/old array format: |
160
|
|
|
* {table: [{column: value}]} |
161
|
|
|
* If only need to change a single row, can use simple format: |
162
|
|
|
* {table: {column: value}} |
163
|
|
|
* |
164
|
|
|
* If old array is null, will read its value from db by Pk in new array. |
165
|
|
|
* |
166
|
|
|
* Row index in new/old array must match, it is used to connect same row |
167
|
|
|
* in new and old data, so either use a meaningful row index, or use default |
168
|
|
|
* integer index and keep same data order in new and old. |
169
|
|
|
* |
170
|
|
|
* @param array $dataNew |
171
|
|
|
* @param array $dataOld |
172
|
|
|
* @return DbDiff |
173
|
|
|
*/ |
174
|
|
|
public function compare(array $dataNew, array $dataOld = null) |
175
|
|
|
{ |
176
|
|
|
if (empty($dataNew)) { |
177
|
|
|
throw new \Exception('New data array can\'t be empty'); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
|
181
|
|
|
$diff = []; |
182
|
|
|
foreach ($dataNew as $table => &$rowArrayNew) { |
183
|
|
|
// Convert row array to 2-dim array |
184
|
|
|
if (!is_array(current($rowArrayNew))) { |
185
|
|
|
$rowArrayNew = [$rowArrayNew]; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$pkArray = (array)$this->db->getMetaPrimaryKey($table); |
189
|
|
|
$this->checkPkArray($table, $pkArray, $rowArrayNew); |
190
|
|
|
|
191
|
|
|
$diffOfTable = $this->compareTable( |
192
|
|
|
$table, |
193
|
|
|
$pkArray, |
194
|
|
|
$rowArrayNew, |
195
|
|
|
$dataOld |
196
|
|
|
); |
197
|
|
|
|
198
|
|
|
if (!empty($diffOfTable)) { |
199
|
|
|
$diff[$table] = $diffOfTable; |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
unset($rowArrayNew); |
203
|
|
|
|
204
|
|
|
$this->diff = $diff; |
205
|
|
|
|
206
|
|
|
return $this; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Compare different of a single row |
212
|
|
|
* |
213
|
|
|
* $rowNew MUST contain all PK columns. |
214
|
|
|
* |
215
|
|
|
* @param array &$rowNew |
216
|
|
|
* @param array $pkArray |
217
|
|
|
* @param array &$rowOld |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
|
|
protected function compareRow( |
221
|
|
|
array &$rowNew, |
222
|
|
|
array $pkArray, |
223
|
|
|
array &$rowOld = null |
224
|
|
|
) { |
225
|
|
|
$mode = $this->compareRowMode($rowNew, $pkArray, $rowOld); |
226
|
|
|
|
227
|
|
|
$pkDiff = $this->compareRowPk($rowNew, $pkArray, $rowOld); |
228
|
|
|
|
229
|
|
|
$columnArray = array_keys($rowNew); |
230
|
|
|
if (!is_null($rowOld)) { |
231
|
|
|
$columnArray = array_merge(array_keys($rowOld)); |
232
|
|
|
} |
233
|
|
|
$columnDiff = $this->compareRowColumn( |
234
|
|
|
$mode, |
235
|
|
|
$rowNew, |
236
|
|
|
$columnArray, |
237
|
|
|
$rowOld |
238
|
|
|
); |
239
|
|
|
|
240
|
|
|
// UPDATE with no column change will be skipped |
241
|
|
|
if ('UPDATE' == $mode && empty($columnDiff)) { |
242
|
|
|
return null; |
243
|
|
|
|
244
|
|
|
} else { |
245
|
|
|
return [ |
246
|
|
|
'mode' => $mode, |
247
|
|
|
'pk' => $pkDiff, |
248
|
|
|
'column' => $columnDiff, |
249
|
|
|
]; |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Compare column values, return diff array |
256
|
|
|
* |
257
|
|
|
* Pk column should be removed already. |
258
|
|
|
* |
259
|
|
|
* @param string $mode |
260
|
|
|
* @param array &$rowNew |
261
|
|
|
* @param array $columnArray |
262
|
|
|
* @param array &$rowOld |
263
|
|
|
* @return array |
264
|
|
|
*/ |
265
|
|
|
protected function compareRowColumn( |
266
|
|
|
$mode, |
267
|
|
|
array &$rowNew, |
268
|
|
|
array $columnArray, |
269
|
|
|
array &$rowOld = null |
270
|
|
|
) { |
271
|
|
|
$diff = []; |
272
|
|
|
|
273
|
|
|
foreach ((array)$columnArray as $column) { |
274
|
|
|
$valueNew = isset($rowNew[$column]) ? $rowNew[$column] : null; |
275
|
|
|
$valueOld = isset($rowOld[$column]) ? $rowOld[$column] : null; |
276
|
|
|
|
277
|
|
|
// Set useless column data to null. New value in DELETE mode is |
278
|
|
|
// useless, and old value in INSERT mode is useless too. |
279
|
|
|
if ('DELETE' == $mode) { |
280
|
|
|
$valueNew = null; |
281
|
|
|
} |
282
|
|
|
if ('INSERT' == $mode) { |
283
|
|
|
$valueOld = null; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
// Skip equal value, they are not included in result diff array. |
287
|
|
|
// Change between null and non-null value (include '' and 0, |
288
|
|
|
// although they == null) will be kept. But 1 == '1' will be |
289
|
|
|
// skipped, so not using === and check equal only if both value |
290
|
|
|
// are not null. |
291
|
|
|
if ((is_null($valueNew) && is_null($valueOld)) || |
292
|
|
|
(!is_null($valueNew) && !is_null($valueOld) && |
293
|
|
|
$valueNew == $valueOld) |
294
|
|
|
) { |
295
|
|
|
continue; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
$diff[$column] = [ |
299
|
|
|
'new' => $valueNew, |
300
|
|
|
'old' => $valueOld, |
301
|
|
|
]; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
return $diff; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
|
308
|
|
|
|
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Get compare mode of a single row |
312
|
|
|
* |
313
|
|
|
* Mode: INSERT/UPDATE/DELETE |
314
|
|
|
* |
315
|
|
|
* Empty PK value are allowed, but null PK value will change mode flag and |
316
|
|
|
* ignore other non-null pk column values. |
317
|
|
|
* |
318
|
|
|
* @param array &$rowNew |
319
|
|
|
* @param array $pkArray |
320
|
|
|
* @param array &$rowOld |
321
|
|
|
* @return string |
322
|
|
|
*/ |
323
|
|
|
protected function compareRowMode( |
324
|
|
|
array &$rowNew, |
325
|
|
|
array $pkArray, |
326
|
|
|
array &$rowOld = null |
327
|
|
|
) { |
328
|
|
|
$nullPkInNew = false; |
329
|
|
|
$nullPkInOld = false; |
330
|
|
|
|
331
|
|
|
foreach ($pkArray as $pk) { |
332
|
|
|
if (is_null($rowNew[$pk])) { |
333
|
|
|
$nullPkInNew = true; |
334
|
|
|
} |
335
|
|
|
if (!isset($rowOld[$pk]) || is_null($rowOld[$pk])) { |
336
|
|
|
$nullPkInOld = true; |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
if ($nullPkInNew && !$nullPkInOld) { |
341
|
|
|
$mode = 'DELETE'; |
342
|
|
|
} elseif (!$nullPkInNew && $nullPkInOld) { |
343
|
|
|
$mode = 'INSERT'; |
344
|
|
|
} elseif (!$nullPkInNew && !$nullPkInOld) { |
345
|
|
|
$mode = 'UPDATE'; |
346
|
|
|
} else { |
347
|
|
|
throw new \Exception( |
348
|
|
|
'Pk in new and old array are all null' |
349
|
|
|
); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
return $mode; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Compare and extract PK values, return diff array |
358
|
|
|
* |
359
|
|
|
* Pk column are removed from new and old row columns. |
360
|
|
|
* |
361
|
|
|
* @param array &$rowNew |
362
|
|
|
* @param array $pkArray |
363
|
|
|
* @param array &$rowOld |
364
|
|
|
* @return array |
365
|
|
|
*/ |
366
|
|
|
protected function compareRowPk( |
367
|
|
|
array &$rowNew, |
368
|
|
|
array $pkArray, |
369
|
|
|
array &$rowOld = null |
370
|
|
|
) { |
371
|
|
|
$diff = []; |
372
|
|
|
|
373
|
|
|
foreach ($pkArray as $pk) { |
374
|
|
|
$diff[$pk] = [ |
375
|
|
|
'new' => $rowNew[$pk], |
376
|
|
|
'old' => isset($rowOld[$pk]) ? $rowOld[$pk] : null, |
377
|
|
|
]; |
378
|
|
|
|
379
|
|
|
unset($rowNew[$pk]); |
380
|
|
|
unset($rowOld[$pk]); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
return $diff; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Compare different for a single table |
389
|
|
|
* |
390
|
|
|
* @param string $table |
391
|
|
|
* @param array $pkArray |
392
|
|
|
* @param array &$rowArrayNew |
393
|
|
|
* @param array &$dataOld |
394
|
|
|
* @return array |
395
|
|
|
*/ |
396
|
|
|
protected function compareTable( |
397
|
|
|
$table, |
398
|
|
|
array $pkArray, |
399
|
|
|
array &$rowArrayNew, |
400
|
|
|
array &$dataOld = null |
401
|
|
|
) { |
402
|
|
|
$diff = []; |
403
|
|
|
foreach ($rowArrayNew as $index => &$rowNew) { |
404
|
|
|
$columnArray = array_keys($rowNew); |
405
|
|
|
$pkValueArray = array_intersect_key( |
406
|
|
|
$rowNew, |
407
|
|
|
array_fill_keys($pkArray, null) |
408
|
|
|
); |
409
|
|
|
|
410
|
|
|
$rowOld = $this->prepareRowOld( |
411
|
|
|
$table, |
412
|
|
|
$index, |
413
|
|
|
$pkValueArray, |
414
|
|
|
$columnArray, |
415
|
|
|
$pkArray, |
416
|
|
|
$dataOld |
417
|
|
|
); |
418
|
|
|
|
419
|
|
|
$diffOfRow = $this->compareRow($rowNew, $pkArray, $rowOld); |
420
|
|
|
if (!empty($diffOfRow)) { |
421
|
|
|
$diff[] = $diffOfRow; |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
unset($rowOld); |
425
|
|
|
|
426
|
|
|
return $diff; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Compare and commit diff result |
432
|
|
|
* |
433
|
|
|
* If $dataNew is null, will use internal stored $diff. |
434
|
|
|
* |
435
|
|
|
* @param array $dataNew |
436
|
|
|
* @param array $dataOld |
437
|
|
|
* @return DbDiff |
438
|
|
|
*/ |
439
|
|
|
public function execute(array $dataNew = null, array $dataOld = null) |
440
|
|
|
{ |
441
|
|
|
if (!is_null($dataNew)) { |
442
|
|
|
$this->reset(); |
443
|
|
|
$this->compare($dataNew, $dataOld); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
if (!$this->isExecuted()) { |
447
|
|
|
$this->commit(); |
448
|
|
|
|
449
|
|
|
} else { |
450
|
|
|
throw new \Exception( |
451
|
|
|
'Committed or rollbacked DbDiff can\'t execute again' |
452
|
|
|
); |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
return $this; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Export to json string |
461
|
|
|
* |
462
|
|
|
* @return string |
463
|
|
|
*/ |
464
|
|
|
public function export() |
465
|
|
|
{ |
466
|
|
|
$json = $this->getUtilContainer()->getJson(); |
467
|
|
|
|
468
|
|
|
return $json->encodeUnicode( |
469
|
|
|
[ |
470
|
|
|
'rowCount' => $this->rowCount, |
471
|
|
|
'executeStatus' => $this->executeStatus, |
472
|
|
|
'diff' => $this->diff, |
473
|
|
|
] |
474
|
|
|
); |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Generate commit sql array from diff result |
480
|
|
|
* |
481
|
|
|
* @return array |
482
|
|
|
*/ |
483
|
|
View Code Duplication |
protected function generateCommitSql() |
|
|
|
|
484
|
|
|
{ |
485
|
|
|
$sqlArray = []; |
486
|
|
|
$db = $this->db; |
487
|
|
|
|
488
|
|
|
foreach ($this->diff as $table => $rowArray) { |
489
|
|
|
foreach ((array)$rowArray as $index => $row) { |
490
|
|
|
$sqlConfig = []; |
491
|
|
|
|
492
|
|
|
switch ($row['mode']) { |
493
|
|
|
case 'INSERT': |
494
|
|
|
$sqlConfig['INSERT'] = $table; |
495
|
|
|
|
496
|
|
|
$columnArray = $row['pk'] + $row['column']; |
497
|
|
|
foreach ($columnArray as $k => $v) { |
498
|
|
|
$sqlConfig['VALUES'][$k] = $v['new']; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
break; |
502
|
|
|
|
503
|
|
|
case 'DELETE': |
504
|
|
|
$sqlConfig['DELETE'] = $table; |
505
|
|
|
// Limit row count to 1 for safety |
506
|
|
|
$sqlConfig['LIMIT'] = 1; |
507
|
|
|
|
508
|
|
|
foreach ($row['pk'] as $k => $v) { |
509
|
|
|
$sqlConfig['WHERE'][] = $k . ' = ' . |
510
|
|
|
$db->quoteValue($table, $k, $v['old']); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
break; |
514
|
|
|
|
515
|
|
|
case 'UPDATE': |
516
|
|
|
$sqlConfig['UPDATE'] = $table; |
517
|
|
|
// Limit row count to 1 for safety |
518
|
|
|
$sqlConfig['LIMIT'] = 1; |
519
|
|
|
|
520
|
|
|
foreach ($row['column'] as $k => $v) { |
521
|
|
|
$sqlConfig['SET'][$k] = $v['new']; |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
foreach ($row['pk'] as $k => $v) { |
525
|
|
|
$sqlConfig['WHERE'][] = $k . ' = ' . |
526
|
|
|
$db->quoteValue($table, $k, $v['new']); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
break; |
530
|
|
|
|
531
|
|
|
default: |
532
|
|
|
throw new \Exception("Invalid mode {$row['mode']}"); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
$sqlArray[] = $db->generateSql($sqlConfig); |
536
|
|
|
} |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
return $sqlArray; |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
|
543
|
|
|
/** |
544
|
|
|
* Generate rollback sql array from diff result |
545
|
|
|
* |
546
|
|
|
* @return array |
547
|
|
|
*/ |
548
|
|
View Code Duplication |
protected function generateRollbackSql() |
|
|
|
|
549
|
|
|
{ |
550
|
|
|
$sqlArray = []; |
551
|
|
|
$db = $this->db; |
552
|
|
|
|
553
|
|
|
foreach ($this->diff as $table => $rowArray) { |
554
|
|
|
foreach ((array)$rowArray as $index => $row) { |
555
|
|
|
$sqlConfig = []; |
556
|
|
|
|
557
|
|
|
switch ($row['mode']) { |
558
|
|
|
case 'INSERT': |
559
|
|
|
$sqlConfig['DELETE'] = $table; |
560
|
|
|
// Limit row count to 1 for safety |
561
|
|
|
$sqlConfig['LIMIT'] = 1; |
562
|
|
|
|
563
|
|
|
foreach ($row['pk'] as $k => $v) { |
564
|
|
|
$sqlConfig['WHERE'][] = $k . ' = ' . |
565
|
|
|
$db->quoteValue($table, $k, $v['new']); |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
break; |
569
|
|
|
|
570
|
|
|
case 'DELETE': |
571
|
|
|
$sqlConfig['INSERT'] = $table; |
572
|
|
|
|
573
|
|
|
$columnArray = $row['pk'] + $row['column']; |
574
|
|
|
foreach ($columnArray as $k => $v) { |
575
|
|
|
$sqlConfig['VALUES'][$k] = $v['old']; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
break; |
579
|
|
|
|
580
|
|
|
case 'UPDATE': |
581
|
|
|
$sqlConfig['UPDATE'] = $table; |
582
|
|
|
// Limit row count to 1 for safety |
583
|
|
|
$sqlConfig['LIMIT'] = 1; |
584
|
|
|
|
585
|
|
|
foreach ($row['column'] as $k => $v) { |
586
|
|
|
$sqlConfig['SET'][$k] = $v['old']; |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
foreach ($row['pk'] as $k => $v) { |
590
|
|
|
$sqlConfig['WHERE'][] = $k . ' = ' . |
591
|
|
|
$db->quoteValue($table, $k, $v['old']); |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
break; |
595
|
|
|
|
596
|
|
|
default: |
597
|
|
|
throw new \Exception("Invalid mode {$row['mode']}"); |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
$sqlArray[] = $db->generateSql($sqlConfig); |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
return $sqlArray; |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* Getter of $diff |
610
|
|
|
* |
611
|
|
|
* @return array |
612
|
|
|
*/ |
613
|
|
|
public function getDiff() |
614
|
|
|
{ |
615
|
|
|
return $this->diff; |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
|
619
|
|
|
/** |
620
|
|
|
* Getter of $rowCount |
621
|
|
|
* |
622
|
|
|
* @return int |
623
|
|
|
*/ |
624
|
|
|
public function getRowCount() |
625
|
|
|
{ |
626
|
|
|
return $this->rowCount; |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
|
630
|
|
|
/** |
631
|
|
|
* Import from a json string |
632
|
|
|
* |
633
|
|
|
* Notice: Import empty string will report error. |
634
|
|
|
* |
635
|
|
|
* @param string $json |
636
|
|
|
* @return DbDiff |
637
|
|
|
*/ |
638
|
|
|
public function import($json) |
639
|
|
|
{ |
640
|
|
|
$this->reset(); |
641
|
|
|
|
642
|
|
|
$info = $this->getUtilContainer()->getJson() |
643
|
|
|
->decode($json, true); |
644
|
|
|
|
645
|
|
|
try { |
646
|
|
|
$this->rowCount = $info['rowCount']; |
647
|
|
|
$this->executeStatus = $info['executeStatus']; |
648
|
|
|
$this->diff = $info['diff']; |
649
|
|
|
|
650
|
|
|
} catch (\Exception $e) { |
651
|
|
|
throw new \Exception( |
652
|
|
|
'Invalid json string to import: ' . $e->getMessage() |
653
|
|
|
); |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
return $this; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
|
660
|
|
|
/** |
661
|
|
|
* Is DbDiff executed/committed ? |
662
|
|
|
* |
663
|
|
|
* @return bool |
664
|
|
|
*/ |
665
|
|
|
public function isCommitted() |
666
|
|
|
{ |
667
|
|
|
return 100 == $this->executeStatus; |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Is DbDiff executed ? |
673
|
|
|
* |
674
|
|
|
* Return true when committed or rollbacked. |
675
|
|
|
* |
676
|
|
|
* @return bool |
677
|
|
|
*/ |
678
|
|
|
public function isExecuted() |
679
|
|
|
{ |
680
|
|
|
return 0 != $this->executeStatus; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
|
684
|
|
|
/** |
685
|
|
|
* Is DbDiff rollbacked ? |
686
|
|
|
* |
687
|
|
|
* @return bool |
688
|
|
|
*/ |
689
|
|
|
public function isRollbacked() |
690
|
|
|
{ |
691
|
|
|
return -100 == $this->executeStatus; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
|
695
|
|
|
/** |
696
|
|
|
* Prepare a single old row for compare |
697
|
|
|
* |
698
|
|
|
* If old row are not set, will query db using pk value from new row, |
699
|
|
|
* return null for not exists(INSERT mode). |
700
|
|
|
* |
701
|
|
|
* Also convert old row array to 2-dim. |
702
|
|
|
* |
703
|
|
|
* @param string $table |
704
|
|
|
* @param int|string $index |
705
|
|
|
* @param array $pkValueArray |
|
|
|
|
706
|
|
|
* @param array $columnArray |
707
|
|
|
* @param array $pkArray |
|
|
|
|
708
|
|
|
* @param array &$dataOld |
709
|
|
|
* @return array |
710
|
|
|
*/ |
711
|
|
|
protected function prepareRowOld( |
712
|
|
|
$table, |
713
|
|
|
$index, |
714
|
|
|
array $keyValueArray, |
715
|
|
|
array $columnArray, |
716
|
|
|
array $keyArray, |
717
|
|
|
array &$dataOld = null |
718
|
|
|
) { |
719
|
|
|
// Convert to 2-dim array |
720
|
|
|
if (isset($dataOld[$table]) && !is_array(current($dataOld[$table]))) { |
721
|
|
|
$dataOld[$table] = [$dataOld[$table]]; |
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
if (isset($dataOld[$table][$index])) { |
725
|
|
|
return $dataOld[$table][$index]; |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
// Need query from db |
729
|
|
|
$rs = $this->db->getByKey( |
730
|
|
|
$table, |
731
|
|
|
$keyValueArray, |
|
|
|
|
732
|
|
|
$columnArray, |
733
|
|
|
$keyArray |
734
|
|
|
); |
735
|
|
|
|
736
|
|
|
// If row only have one column, convert back to array |
737
|
|
|
$rs = is_null($rs) ? $rs : (array)$rs; |
738
|
|
|
|
739
|
|
|
return $rs; |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
|
743
|
|
|
/** |
744
|
|
|
* Reset code, message and flag |
745
|
|
|
* |
746
|
|
|
* @return DbDiff |
747
|
|
|
*/ |
748
|
|
|
public function reset() |
749
|
|
|
{ |
750
|
|
|
$this->rowCount = 0; |
751
|
|
|
$this->executeStatus = 0; |
752
|
|
|
$this->diff = []; |
753
|
|
|
|
754
|
|
|
return $this; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
|
758
|
|
|
/** |
759
|
|
|
* Rollback committed diff result |
760
|
|
|
* |
761
|
|
|
* @return DbDiff |
762
|
|
|
*/ |
763
|
|
View Code Duplication |
public function rollback() |
|
|
|
|
764
|
|
|
{ |
765
|
|
|
if (empty($this->diff)) { |
766
|
|
|
throw new \Exception('No diff data'); |
767
|
|
|
} |
768
|
|
|
|
769
|
|
|
if ($this->isRollbacked()) { |
770
|
|
|
throw new \Exception( |
771
|
|
|
'Rollbacked DbDiff can\'t rollback again' |
772
|
|
|
); |
773
|
|
|
} |
774
|
|
|
|
775
|
|
|
$sqlArray = $this->generateRollbackSql(); |
776
|
|
|
|
777
|
|
|
$this->db->BeginTrans(); |
|
|
|
|
778
|
|
|
|
779
|
|
|
try { |
780
|
|
|
foreach ((array)$sqlArray as $sql) { |
781
|
|
|
$this->db->execute($sql); |
782
|
|
|
|
783
|
|
|
if (0 != $this->db->getErrorCode()) { |
784
|
|
|
throw new \Exception($this->db->getErrorMessage()); |
785
|
|
|
} |
786
|
|
|
} |
787
|
|
|
|
788
|
|
|
$this->db->CommitTrans(); |
|
|
|
|
789
|
|
|
// Rollback operate doesn't change $rowCount |
790
|
|
|
$this->executeStatus = -100; |
791
|
|
|
|
792
|
|
|
} catch (\Exception $e) { |
793
|
|
|
$this->db->RollbackTrans(); |
|
|
|
|
794
|
|
|
|
795
|
|
|
throw new \Exception($e->getMessage()); |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
return $this; |
799
|
|
|
} |
800
|
|
|
} |
801
|
|
|
|
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.