1
|
|
|
<?php |
2
|
|
|
namespace Graze\Morphism\Parse; |
3
|
|
|
|
4
|
|
|
use RuntimeException; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Represents a table definition. |
8
|
|
|
*/ |
9
|
|
|
class CreateTable |
10
|
|
|
{ |
11
|
|
|
/** @var string */ |
12
|
|
|
private $name = ''; |
13
|
|
|
|
14
|
|
|
/** @var ColumnDefinition[] */ |
15
|
|
|
public $columns = []; |
16
|
|
|
|
17
|
|
|
/** @var IndexDefinition[] definitions of non-foreign keys */ |
18
|
|
|
public $indexes = []; |
19
|
|
|
|
20
|
|
|
/** @var IndexDefinition[] definitions of foreign keys */ |
21
|
|
|
public $foreigns = []; |
22
|
|
|
|
23
|
|
|
/** @var TableOptions */ |
24
|
|
|
public $options = null; |
25
|
|
|
|
26
|
|
|
/** @var array */ |
27
|
|
|
private $covers = []; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Constructor. |
31
|
|
|
* |
32
|
|
|
* @param CollationInfo $databaseCollation |
33
|
|
|
*/ |
34
|
93 |
|
public function __construct(CollationInfo $databaseCollation) |
35
|
|
|
{ |
36
|
93 |
|
$this->options = new TableOptions($databaseCollation); |
37
|
93 |
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @return string |
41
|
|
|
*/ |
42
|
1 |
|
public function getName() |
43
|
|
|
{ |
44
|
1 |
|
return $this->name; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Sets the storage engine the table is assumed to use, unless |
49
|
|
|
* explicitly overridden via an ENGINE= clause at the end of |
50
|
|
|
* the table definition. |
51
|
|
|
* |
52
|
|
|
* @param string $engine |
53
|
|
|
* @return void |
54
|
|
|
*/ |
55
|
91 |
|
public function setDefaultEngine($engine) |
56
|
|
|
{ |
57
|
91 |
|
$this->options->setDefaultEngine($engine); |
58
|
91 |
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Parses a table definition from $stream. |
62
|
|
|
* |
63
|
|
|
* The DDL may be of the form 'CREATE TABLE ...' or 'CREATE TABLE IF NOT EXISTS ...'. |
64
|
|
|
* |
65
|
|
|
* An exception will be thrown if a valid CREATE TABLE statement cannot be recognised. |
66
|
|
|
* |
67
|
|
|
* @param TokenStream $stream |
68
|
|
|
*/ |
69
|
90 |
|
public function parse(TokenStream $stream) |
70
|
|
|
{ |
71
|
90 |
|
if ($stream->consume('CREATE TABLE')) { |
72
|
89 |
|
$stream->consume('IF NOT EXISTS'); |
73
|
|
|
} else { |
74
|
1 |
|
throw new RuntimeException("Expected CREATE TABLE"); |
75
|
|
|
} |
76
|
|
|
|
77
|
89 |
|
$this->name = $stream->expectName(); |
78
|
89 |
|
$stream->expectOpenParen(); |
79
|
|
|
|
80
|
89 |
|
while (true) { |
81
|
89 |
|
$hasConstraintKeyword = $stream->consume('CONSTRAINT'); |
82
|
89 |
|
if ($stream->consume('PRIMARY KEY')) { |
83
|
10 |
|
$this->parseIndex($stream, 'PRIMARY KEY'); |
84
|
89 |
|
} elseif ($stream->consume('KEY') || |
85
|
89 |
|
$stream->consume('INDEX') |
86
|
|
|
) { |
87
|
18 |
|
if ($hasConstraintKeyword) { |
88
|
2 |
|
throw new RuntimeException("Bad CONSTRAINT"); |
89
|
|
|
} |
90
|
16 |
|
$this->parseIndex($stream, 'KEY'); |
91
|
89 |
|
} elseif ($stream->consume('FULLTEXT')) { |
92
|
4 |
|
if ($hasConstraintKeyword) { |
93
|
1 |
|
throw new RuntimeException("Bad CONSTRAINT"); |
94
|
|
|
} |
95
|
3 |
|
$stream->consume('KEY') || $stream->consume('INDEX'); |
96
|
3 |
|
$this->parseIndex($stream, 'FULLTEXT KEY'); |
97
|
89 |
|
} elseif ($stream->consume('UNIQUE')) { |
98
|
5 |
|
$stream->consume('KEY') || $stream->consume('INDEX'); |
99
|
5 |
|
$this->parseIndex($stream, 'UNIQUE KEY'); |
100
|
89 |
|
} elseif ($stream->consume('FOREIGN KEY')) { |
101
|
8 |
|
$this->parseIndex($stream, 'FOREIGN KEY'); |
102
|
89 |
|
} elseif ($hasConstraintKeyword) { |
103
|
4 |
|
$constraint = $stream->expectName(); |
104
|
4 |
|
if ($stream->consume('PRIMARY KEY')) { |
105
|
1 |
|
$this->parseIndex($stream, 'PRIMARY KEY', $constraint); |
106
|
3 |
|
} elseif ($stream->consume('UNIQUE')) { |
107
|
1 |
|
$stream->consume('KEY') || $stream->consume('INDEX'); |
108
|
1 |
|
$this->parseIndex($stream, 'UNIQUE KEY', $constraint); |
109
|
2 |
|
} elseif ($stream->consume('FOREIGN KEY')) { |
110
|
1 |
|
$this->parseIndex($stream, 'FOREIGN KEY', $constraint); |
111
|
|
|
} else { |
112
|
4 |
|
throw new RuntimeException("Bad CONSTRAINT"); |
113
|
|
|
} |
114
|
|
|
} else { |
115
|
89 |
|
$this->parseColumn($stream); |
116
|
|
|
} |
117
|
86 |
|
$token = $stream->nextToken(); |
118
|
86 |
|
if ($token->eq(Token::SYMBOL, ',')) { |
119
|
61 |
|
continue; |
120
|
80 |
|
} elseif ($token->eq(Token::SYMBOL, ')')) { |
121
|
79 |
|
break; |
122
|
|
|
} else { |
123
|
1 |
|
throw new RuntimeException("Expected ',' or ')'"); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
79 |
|
$this->processTimestamps(); |
128
|
79 |
|
$this->processIndexes(); |
129
|
75 |
|
$this->processAutoIncrement(); |
130
|
73 |
|
$this->parseTableOptions($stream); |
131
|
73 |
|
$this->processColumnCollations(); |
132
|
73 |
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Returns the table's collation. |
136
|
|
|
* |
137
|
|
|
* @return CollationInfo |
138
|
|
|
*/ |
139
|
73 |
|
public function getCollation() |
140
|
|
|
{ |
141
|
73 |
|
return $this->options->collation; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Returns an array of SQL DDL statements to create the table. |
146
|
|
|
* |
147
|
|
|
* @return array |
148
|
|
|
*/ |
149
|
53 |
|
public function getDDL() |
150
|
|
|
{ |
151
|
53 |
|
$lines = []; |
152
|
53 |
|
foreach ($this->columns as $column) { |
153
|
53 |
|
$lines[] = " " . $column->toString($this->getCollation()); |
154
|
|
|
} |
155
|
53 |
|
foreach ($this->indexes as $index) { |
156
|
35 |
|
$lines[] = " " . $index->toString(); |
157
|
|
|
} |
158
|
53 |
|
foreach ($this->foreigns as $foreign) { |
159
|
9 |
|
$lines[] = " " . $foreign->toString(); |
160
|
|
|
} |
161
|
|
|
|
162
|
53 |
|
$text = "CREATE TABLE " . Token::escapeIdentifier($this->name) . " (\n" . |
163
|
53 |
|
implode(",\n", $lines) . |
164
|
53 |
|
"\n" . |
165
|
53 |
|
")"; |
166
|
|
|
|
167
|
53 |
|
$options = $this->options->toString(); |
168
|
53 |
|
if ($options !== '') { |
169
|
53 |
|
$text .= " " . $this->options->toString(); |
170
|
|
|
} |
171
|
|
|
|
172
|
53 |
|
return [$text]; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @param TokenStream $stream |
177
|
|
|
*/ |
178
|
89 |
|
private function parseColumn(TokenStream $stream) |
179
|
|
|
{ |
180
|
89 |
|
$column = new ColumnDefinition(); |
181
|
89 |
|
$column->parse($stream); |
182
|
86 |
|
if (array_key_exists(strtolower($column->name), $this->columns)) { |
183
|
2 |
|
throw new RuntimeException("Duplicate column name '" . $column->name . "'"); |
184
|
|
|
} |
185
|
86 |
|
$this->columns[strtolower($column->name)] = $column; |
186
|
86 |
|
$this->indexes = array_merge( |
187
|
86 |
|
$this->indexes, |
188
|
86 |
|
$column->indexes |
189
|
|
|
); |
190
|
86 |
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @param TokenStream $stream |
194
|
|
|
* @param string $type |
195
|
|
|
* @param string|null $constraint |
196
|
|
|
*/ |
197
|
38 |
|
private function parseIndex(TokenStream $stream, $type, $constraint = null) |
198
|
|
|
{ |
199
|
38 |
|
$index = new IndexDefinition(); |
200
|
38 |
|
$index->parse($stream, $type, $constraint); |
201
|
38 |
|
$this->indexes[] = $index; |
202
|
38 |
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* @param TokenStream $stream |
206
|
|
|
*/ |
207
|
73 |
|
private function parseTableOptions(TokenStream $stream) |
208
|
|
|
{ |
209
|
73 |
|
$this->options->parse($stream); |
210
|
73 |
|
} |
211
|
|
|
|
212
|
79 |
|
private function processTimestamps() |
213
|
|
|
{ |
214
|
|
|
// To specify automatic properties, use the DEFAULT CURRENT_TIMESTAMP |
215
|
|
|
// and ON UPDATE CURRENT_TIMESTAMP clauses. The order of the clauses |
216
|
|
|
// does not matter. If both are present in a column definition, either |
217
|
|
|
// can occur first. |
218
|
|
|
|
219
|
|
|
// collect all timestamps |
220
|
79 |
|
$ts = []; |
221
|
79 |
|
foreach ($this->columns as $column) { |
222
|
79 |
|
if ($column->type === 'timestamp') { |
223
|
12 |
|
$ts[] = $column; |
224
|
|
|
} |
225
|
|
|
} |
226
|
79 |
|
if (count($ts) === 0) { |
227
|
67 |
|
return; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
// none of NULL, DEFAULT or ON UPDATE CURRENT_TIMESTAMP have been specified |
231
|
12 |
|
if (!$ts[0]->nullable && is_null($ts[0]->default) && !$ts[0]->onUpdateCurrentTimestamp) { |
232
|
3 |
|
$ts[0]->nullable = false; |
233
|
3 |
|
$ts[0]->default = 'CURRENT_TIMESTAMP'; |
234
|
3 |
|
$ts[0]->onUpdateCurrentTimestamp = true; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
// [[ this restriction no longer exists as of MySQL 5.6.5 and MariaDB 10.0.1 ]] |
238
|
|
|
|
239
|
|
|
// One TIMESTAMP column in a table can have the current timestamp as |
240
|
|
|
// the default value for initializing the column, as the auto-update |
241
|
|
|
// value, or both. It is not possible to have the current timestamp |
242
|
|
|
// be the default value for one column and the auto-update value for |
243
|
|
|
// another column. |
244
|
|
|
|
245
|
|
|
// $specials = 0; |
|
|
|
|
246
|
|
|
// foreach($ts as $column) { |
|
|
|
|
247
|
|
|
// if ($column->default === 'CURRENT_TIMESTAMP' || |
|
|
|
|
248
|
|
|
// $column->onUpdateCurrentTimestamp |
249
|
|
|
// ) { |
250
|
|
|
// if (++$specials > 1) { |
|
|
|
|
251
|
|
|
// throw new RuntimeException("There can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause"); |
|
|
|
|
252
|
|
|
// } |
253
|
|
|
// } |
254
|
|
|
// } |
255
|
|
|
|
256
|
12 |
|
foreach ($ts as $column) { |
257
|
12 |
|
if (!$column->nullable && is_null($column->default)) { |
258
|
4 |
|
$column->default = '0000-00-00 00:00:00'; |
259
|
|
|
} |
260
|
|
|
} |
261
|
12 |
|
} |
262
|
|
|
|
263
|
79 |
|
private function processIndexes() |
264
|
|
|
{ |
265
|
|
|
// check indexes are sane wrt available columns |
266
|
79 |
|
foreach ($this->indexes as $index) { |
267
|
46 |
|
foreach ($index->columns as $indexColumn) { |
268
|
46 |
|
$indexColumnName = $indexColumn['name']; |
269
|
46 |
|
if (!array_key_exists(strtolower($indexColumnName), $this->columns)) { |
270
|
1 |
|
throw new RuntimeException("Key column '$indexColumnName' doesn't exist in table"); |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
// figure out all sequences of columns covered by non-FK indexes |
276
|
78 |
|
foreach ($this->indexes as $index) { |
277
|
45 |
|
if ($index->type !== 'FOREIGN KEY') { |
278
|
41 |
|
foreach ($index->getCovers() as $cover) { |
279
|
41 |
|
$lookup = implode('\0', $cover); |
280
|
41 |
|
$this->covers[$lookup] = true; |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
285
|
78 |
|
$indexes = []; |
286
|
78 |
|
$foreigns = []; |
287
|
78 |
|
$ibfkCounter = 0; |
288
|
|
|
|
289
|
78 |
|
foreach ($this->indexes as $index) { |
290
|
45 |
|
if ($index->type === 'FOREIGN KEY') { |
291
|
|
|
// TODO - doesn't correctly deal with indexes like foo(10) |
|
|
|
|
292
|
9 |
|
$lookup = implode('\0', $index->getColumns()); |
293
|
9 |
|
if (!array_key_exists($lookup, $this->covers)) { |
294
|
7 |
|
$newIndex = new IndexDefinition(); |
295
|
7 |
|
$newIndex->type = 'KEY'; |
296
|
7 |
|
$newIndex->columns = $index->columns; |
297
|
7 |
|
if (!is_null($index->constraint)) { |
298
|
1 |
|
$newIndex->name = $index->constraint; |
299
|
6 |
|
} elseif (!is_null($index->name)) { |
300
|
1 |
|
$newIndex->name = $index->name; |
301
|
|
|
} |
302
|
7 |
|
$indexes[] = $newIndex; |
303
|
|
|
} |
304
|
9 |
|
$foreign = new IndexDefinition(); |
305
|
9 |
|
if (is_null($index->constraint)) { |
306
|
8 |
|
$foreign->constraint = $this->name . '_ibfk_' . ++$ibfkCounter; |
|
|
|
|
307
|
|
|
} else { |
308
|
1 |
|
$foreign->constraint = $index->constraint; |
309
|
|
|
} |
310
|
9 |
|
$foreign->type = 'FOREIGN KEY'; |
311
|
9 |
|
$foreign->columns = $index->columns; |
312
|
9 |
|
$foreign->reference = $index->reference; |
313
|
9 |
|
$foreigns[] = $foreign; |
314
|
|
|
} else { |
315
|
41 |
|
$indexes[] = $index; |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
// now synthesise names for any unnamed indexes, |
320
|
|
|
// and collect indexes by type |
321
|
78 |
|
$usedName = []; |
322
|
|
|
$keyTypes = [ |
323
|
78 |
|
'PRIMARY KEY', |
324
|
|
|
'UNIQUE KEY', |
325
|
|
|
'KEY', |
326
|
|
|
'FULLTEXT KEY', |
327
|
|
|
'FOREIGN KEY', |
328
|
|
|
]; |
329
|
78 |
|
$indexesByType = array_fill_keys($keyTypes, []); |
330
|
78 |
|
foreach ($indexes as $index) { |
331
|
45 |
|
$name = $index->name; |
332
|
45 |
|
if ($index->type === 'PRIMARY KEY') { |
333
|
19 |
|
$name = 'PRIMARY'; |
334
|
27 |
|
} elseif (is_null($name)) { |
335
|
16 |
|
$base = $index->columns[0]['name']; |
336
|
16 |
|
$name = $base; |
337
|
16 |
|
$i = 1; |
338
|
16 |
|
while (isset($usedName[$name])) { |
339
|
1 |
|
$name = $base . '_' . ++$i; |
|
|
|
|
340
|
|
|
} |
341
|
16 |
|
$index->name = $name; |
342
|
14 |
|
} elseif (array_key_exists(strtolower($name), $usedName)) { |
343
|
2 |
|
throw new RuntimeException("Duplicate key name '$name'"); |
344
|
|
|
} |
345
|
45 |
|
$index->name = $name; |
346
|
45 |
|
$usedName[strtolower($name)] = true; |
347
|
|
|
|
348
|
45 |
|
$indexesByType[$index->type][] = $index; |
349
|
|
|
} |
350
|
|
|
|
351
|
76 |
|
if (count($indexesByType['PRIMARY KEY']) > 1) { |
352
|
1 |
|
throw new RuntimeException("Multiple PRIMARY KEYs defined"); |
353
|
|
|
} |
354
|
|
|
|
355
|
75 |
|
foreach ($indexesByType['PRIMARY KEY'] as $pk) { |
356
|
18 |
|
foreach ($pk->columns as $indexColumn) { |
357
|
18 |
|
$column = $this->columns[strtolower($indexColumn['name'])]; |
358
|
18 |
|
if ($column->nullable) { |
359
|
10 |
|
$column->nullable = false; |
360
|
10 |
|
if (is_null($column->default)) { |
361
|
10 |
|
$column->default = $column->getUninitialisedValue(); |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
75 |
|
$this->indexes = []; |
368
|
75 |
|
foreach (array_reduce($indexesByType, 'array_merge', []) as $index) { |
369
|
42 |
|
$this->indexes[$index->name] = $index; |
370
|
|
|
} |
371
|
75 |
|
foreach ($foreigns as $foreign) { |
372
|
9 |
|
$this->foreigns[] = $foreign; |
373
|
|
|
} |
374
|
75 |
|
} |
375
|
|
|
|
376
|
75 |
|
private function processAutoIncrement() |
377
|
|
|
{ |
378
|
75 |
|
$count = 0; |
379
|
75 |
|
foreach ($this->columns as $column) { |
380
|
75 |
|
if ($column->autoIncrement) { |
381
|
3 |
|
if (++$count > 1) { |
382
|
1 |
|
throw new RuntimeException("There can be only one AUTO_INCREMENT column"); |
383
|
|
|
} |
384
|
3 |
|
if (! array_key_exists($column->name, $this->covers)) { |
385
|
1 |
|
throw new RuntimeException("AUTO_INCREMENT column must be defined as a key"); |
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
} |
389
|
73 |
|
} |
390
|
|
|
|
391
|
73 |
|
private function processColumnCollations() |
392
|
|
|
{ |
393
|
73 |
|
foreach ($this->columns as $column) { |
394
|
73 |
|
$column->applyTableCollation($this->getCollation()); |
395
|
|
|
} |
396
|
73 |
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* Returns ALTER TABLE statement to transform this table into the one |
400
|
|
|
* represented by $that. If the tables are already equivalent, just |
401
|
|
|
* returns the empty string. |
402
|
|
|
* |
403
|
|
|
* $flags | |
404
|
|
|
* :-------------|---- |
405
|
|
|
* 'alterEngine' | (bool) include ALTER TABLE ... ENGINE= [default: true] |
406
|
|
|
* |
407
|
|
|
* @param CreateTable $that |
408
|
|
|
* @param array $flags |
409
|
|
|
* @return string[] |
410
|
|
|
*/ |
411
|
20 |
|
public function diff(CreateTable $that, array $flags = []) |
412
|
|
|
{ |
413
|
|
|
$flags += [ |
414
|
20 |
|
'alterEngine' => true |
415
|
|
|
]; |
416
|
|
|
|
417
|
20 |
|
$alters = array_merge( |
418
|
20 |
|
$this->diffColumns($that), |
419
|
20 |
|
$this->diffIndexes($that), |
420
|
20 |
|
$this->diffOptions($that, [ |
421
|
20 |
|
'alterEngine' => $flags['alterEngine'] |
422
|
|
|
]) |
423
|
|
|
); |
424
|
|
|
|
425
|
20 |
|
if (count($alters) === 0) { |
426
|
2 |
|
return []; |
427
|
|
|
} |
428
|
|
|
|
429
|
18 |
|
return ["ALTER TABLE " . Token::escapeIdentifier($this->name) . "\n" . implode(",\n", $alters)]; |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* @param CreateTable $that |
434
|
|
|
* @return array |
435
|
|
|
*/ |
436
|
20 |
|
private function diffColumns(CreateTable $that) |
437
|
|
|
{ |
438
|
20 |
|
$alters = []; |
439
|
20 |
|
$permutation = []; |
440
|
20 |
|
foreach (array_keys($this->columns) as $columnName) { |
441
|
20 |
|
if (array_key_exists($columnName, $that->columns)) { |
442
|
20 |
|
$permutation[] = $columnName; |
443
|
|
|
} else { |
444
|
1 |
|
$alters[] = "DROP COLUMN " . Token::escapeIdentifier($columnName); |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
|
448
|
20 |
|
$prevColumn = null; |
449
|
20 |
|
$thatPosition = " FIRST"; |
450
|
20 |
|
$j = 0; |
451
|
20 |
|
foreach ($that->columns as $columnName => $column) { |
452
|
20 |
|
if (array_key_exists($columnName, $this->columns)) { |
453
|
|
|
// An existing column is being changed |
454
|
20 |
|
$thisDefinition = $this->columns[$columnName]->toString($this->getCollation()); |
455
|
20 |
|
$thatDefinition = $that->columns[$columnName]->toString($that->getCollation()); |
456
|
|
|
|
457
|
|
|
// about to 'add' $columnName - get its location in the currently |
458
|
|
|
// permuted state of the tabledef |
459
|
20 |
|
$i = array_search($columnName, $permutation); |
460
|
|
|
|
461
|
|
|
// figure out the column it currently sits after, in case we |
462
|
|
|
// need to change it |
463
|
20 |
|
$thisPosition = ($i === 0) ? " FIRST" : " AFTER " . Token::escapeIdentifier($permutation[$i - 1]); |
464
|
|
|
|
465
|
20 |
|
if ($thisDefinition !== $thatDefinition || |
466
|
20 |
|
$thisPosition !== $thatPosition |
467
|
|
|
) { |
468
|
8 |
|
$alter = "MODIFY COLUMN " . $thatDefinition; |
469
|
|
|
|
470
|
|
|
// position has changed |
471
|
8 |
|
if ($thisPosition !== $thatPosition) { |
472
|
3 |
|
$alter .= $thatPosition; |
473
|
|
|
|
474
|
|
|
// We need to update our permutation to reflect the new position. |
475
|
|
|
// Column is being inserted at position $j, and is currently residing at $i. |
476
|
|
|
|
477
|
|
|
// remove from current location |
478
|
3 |
|
array_splice($permutation, $i, 1, []); |
|
|
|
|
479
|
|
|
|
480
|
|
|
// insert at new location |
481
|
3 |
|
array_splice($permutation, $j, 0, $columnName); |
482
|
|
|
} |
483
|
|
|
|
484
|
20 |
|
$alters[] = $alter; |
485
|
|
|
} |
486
|
|
|
} else { |
487
|
|
|
// A new column is being added |
488
|
3 |
|
$alter = "ADD COLUMN " . $column->toString($this->getCollation()); |
489
|
3 |
|
if ($j < count($permutation)) { |
490
|
2 |
|
$alter .= $thatPosition; |
491
|
|
|
} |
492
|
3 |
|
$alters[] = $alter; |
493
|
|
|
|
494
|
3 |
|
$i = is_null($prevColumn) ? 0 : 1 + array_search($prevColumn, $permutation); |
495
|
3 |
|
array_splice($permutation, $i, 0, [$columnName]); |
496
|
|
|
} |
497
|
|
|
|
498
|
20 |
|
$prevColumn = $columnName; |
499
|
20 |
|
$thatPosition = " AFTER " . Token::escapeIdentifier($prevColumn); |
500
|
20 |
|
$j++; |
501
|
|
|
} |
502
|
|
|
|
503
|
20 |
|
return $alters; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
/** |
507
|
|
|
* @param CreateTable $that |
508
|
|
|
* @return array |
509
|
|
|
*/ |
510
|
20 |
|
private function diffIndexes(CreateTable $that) |
511
|
|
|
{ |
512
|
20 |
|
$alters = []; |
513
|
|
|
|
514
|
20 |
|
foreach ($this->indexes as $indexName => $index) { |
515
|
4 |
|
if (!array_key_exists($indexName, $that->indexes) || |
516
|
4 |
|
$index->toString() !== $that->indexes[$indexName]->toString() |
517
|
|
|
) { |
518
|
4 |
|
switch ($index->type) { |
519
|
4 |
|
case 'PRIMARY KEY': |
520
|
1 |
|
$alter = "DROP PRIMARY KEY"; |
521
|
1 |
|
break; |
522
|
|
|
|
523
|
|
|
// TODO - foreign keys??? |
|
|
|
|
524
|
|
|
|
525
|
|
|
default: |
526
|
3 |
|
$alter = "DROP KEY " . Token::escapeIdentifier($indexName); |
527
|
3 |
|
break; |
528
|
|
|
} |
529
|
4 |
|
$alters[] = $alter; |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|
533
|
20 |
|
foreach ($that->indexes as $indexName => $index) { |
534
|
4 |
|
if (!array_key_exists($indexName, $this->indexes) || |
535
|
4 |
|
$index->toString() !== $this->indexes[$indexName]->toString() |
536
|
|
|
) { |
537
|
4 |
|
$alters[] = "ADD " . $index->toString(); |
538
|
|
|
} |
539
|
|
|
} |
540
|
|
|
|
541
|
20 |
|
return $alters; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* @param CreateTable $that |
546
|
|
|
* @param array $flags |
547
|
|
|
* @return array |
548
|
|
|
*/ |
549
|
20 |
|
private function diffOptions(CreateTable $that, array $flags = []) |
550
|
|
|
{ |
551
|
|
|
$flags += [ |
552
|
20 |
|
'alterEngine' => true |
553
|
|
|
]; |
554
|
20 |
|
$diff = $this->options->diff($that->options, [ |
555
|
20 |
|
'alterEngine' => $flags['alterEngine'] |
556
|
|
|
]); |
557
|
20 |
|
return ($diff == '') ? [] : [$diff]; |
558
|
|
|
} |
559
|
|
|
} |
560
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.