1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* MIT License |
5
|
|
|
* For full license information, please view the LICENSE file that was distributed with this source code. |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Phinx\Db\Adapter; |
9
|
|
|
|
10
|
|
|
use Cake\Database\Connection; |
11
|
|
|
use Cake\Database\Driver\Mysql as MysqlDriver; |
12
|
|
|
use InvalidArgumentException; |
13
|
|
|
use PDO; |
14
|
|
|
use Phinx\Db\Table\Column; |
15
|
|
|
use Phinx\Db\Table\ForeignKey; |
16
|
|
|
use Phinx\Db\Table\Index; |
17
|
|
|
use Phinx\Db\Table\Table; |
18
|
|
|
use Phinx\Db\Util\AlterInstructions; |
19
|
|
|
use Phinx\Util\Literal; |
20
|
|
|
use RuntimeException; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Phinx MySQL Adapter. |
24
|
|
|
* |
25
|
|
|
* @author Rob Morgan <[email protected]> |
26
|
|
|
*/ |
27
|
|
|
class MysqlAdapter extends PdoAdapter |
28
|
|
|
{ |
29
|
|
|
protected $signedColumnTypes = [ |
30
|
|
|
'integer' => true, |
31
|
|
|
'smallinteger' => true, |
32
|
|
|
'biginteger' => true, |
33
|
|
|
'float' => true, |
34
|
|
|
'decimal' => true, |
35
|
|
|
'double' => true, |
36
|
|
|
'boolean' => true, |
37
|
|
|
]; |
38
|
|
|
|
39
|
|
|
const TEXT_TINY = 255; |
40
|
|
|
const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */ |
41
|
|
|
const TEXT_REGULAR = 65535; |
42
|
|
|
const TEXT_MEDIUM = 16777215; |
43
|
|
|
const TEXT_LONG = 4294967295; |
44
|
|
|
|
45
|
|
|
// According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT |
46
|
|
|
const BLOB_TINY = 255; |
47
|
|
|
const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */ |
48
|
|
|
const BLOB_REGULAR = 65535; |
49
|
|
|
const BLOB_MEDIUM = 16777215; |
50
|
|
|
const BLOB_LONG = 4294967295; |
51
|
|
|
|
52
|
|
|
const INT_TINY = 255; |
53
|
|
|
const INT_SMALL = 65535; |
54
|
|
|
const INT_MEDIUM = 16777215; |
55
|
|
|
const INT_REGULAR = 4294967295; |
56
|
|
|
const INT_BIG = 18446744073709551615; |
57
|
|
|
|
58
|
|
|
const BIT = 64; |
59
|
|
|
|
60
|
|
|
const TYPE_YEAR = 'year'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* {@inheritDoc} |
64
|
|
|
* |
65
|
|
|
* @throws \RuntimeException |
66
|
|
|
* @throws \InvalidArgumentException |
67
|
|
|
* |
68
|
|
|
* @return void |
69
|
|
|
*/ |
70
|
80 |
|
public function connect() |
71
|
|
|
{ |
72
|
80 |
|
if ($this->connection === null) { |
73
|
80 |
|
if (!class_exists('PDO') || !in_array('mysql', PDO::getAvailableDrivers(), true)) { |
74
|
|
|
// @codeCoverageIgnoreStart |
75
|
|
|
throw new RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.'); |
76
|
|
|
// @codeCoverageIgnoreEnd |
77
|
|
|
} |
78
|
|
|
|
79
|
80 |
|
$options = $this->getOptions(); |
80
|
80 |
|
|
81
|
|
|
$dsn = 'mysql:'; |
82
|
80 |
|
|
83
|
|
|
if (!empty($options['unix_socket'])) { |
84
|
80 |
|
// use socket connection |
85
|
|
|
$dsn .= 'unix_socket=' . $options['unix_socket']; |
86
|
|
|
} else { |
87
|
|
|
// use network connection |
88
|
|
|
$dsn .= 'host=' . $options['host']; |
89
|
80 |
|
if (!empty($options['port'])) { |
90
|
80 |
|
$dsn .= ';port=' . $options['port']; |
91
|
80 |
|
} |
92
|
80 |
|
} |
93
|
|
|
|
94
|
|
|
$dsn .= ';dbname=' . $options['name']; |
95
|
80 |
|
|
96
|
|
|
// charset support |
97
|
|
|
if (!empty($options['charset'])) { |
98
|
80 |
|
$dsn .= ';charset=' . $options['charset']; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
$driverOptions = []; |
102
|
80 |
|
|
103
|
|
|
// use custom data fetch mode |
104
|
|
View Code Duplication |
if (!empty($options['fetch_mode'])) { |
|
|
|
|
105
|
|
|
$driverOptions[PDO::ATTR_DEFAULT_FETCH_MODE] = constant('\PDO::FETCH_' . strtoupper($options['fetch_mode'])); |
106
|
80 |
|
} |
107
|
80 |
|
|
108
|
|
|
// support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO |
109
|
|
|
// http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants |
110
|
80 |
View Code Duplication |
foreach ($options as $key => $option) { |
|
|
|
|
111
|
|
|
if (strpos($key, 'mysql_attr_') === 0) { |
112
|
|
|
$driverOptions[constant('\PDO::' . strtoupper($key))] = $option; |
113
|
80 |
|
} |
114
|
80 |
|
} |
115
|
1 |
|
|
116
|
1 |
|
$db = $this->createPdoConnection($dsn, $options['user'], $options['pass'], $driverOptions); |
117
|
1 |
|
|
118
|
1 |
|
$this->setConnection($db); |
119
|
|
|
} |
120
|
|
|
} |
121
|
80 |
|
|
122
|
80 |
|
/** |
123
|
80 |
|
* {@inheritDoc} |
124
|
|
|
* |
125
|
|
|
* @return void |
126
|
|
|
*/ |
127
|
|
|
public function disconnect() |
128
|
81 |
|
{ |
129
|
|
|
$this->connection = null; |
130
|
81 |
|
} |
131
|
81 |
|
|
132
|
|
|
/** |
133
|
|
|
* @inheritDoc |
134
|
|
|
*/ |
135
|
|
|
public function hasTransactions() |
136
|
6 |
|
{ |
137
|
|
|
return true; |
138
|
6 |
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* {@inheritDoc} |
142
|
|
|
* |
143
|
|
|
* @return void |
144
|
6 |
|
*/ |
145
|
|
|
public function beginTransaction() |
146
|
6 |
|
{ |
147
|
6 |
|
$this->execute('START TRANSACTION'); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* {@inheritDoc} |
152
|
6 |
|
* |
153
|
|
|
* @return void |
154
|
6 |
|
*/ |
155
|
6 |
|
public function commitTransaction() |
156
|
|
|
{ |
157
|
|
|
$this->execute('COMMIT'); |
158
|
|
|
} |
159
|
|
|
|
160
|
1 |
|
/** |
161
|
|
|
* {@inheritDoc} |
162
|
1 |
|
* |
163
|
1 |
|
* @return void |
164
|
|
|
*/ |
165
|
|
|
public function rollbackTransaction() |
166
|
|
|
{ |
167
|
|
|
$this->execute('ROLLBACK'); |
168
|
112 |
|
} |
169
|
|
|
|
170
|
112 |
|
/** |
171
|
|
|
* @inheritDoc |
172
|
|
|
*/ |
173
|
|
|
public function quoteTableName($tableName) |
174
|
|
|
{ |
175
|
|
|
return str_replace('.', '`.`', $this->quoteColumnName($tableName)); |
176
|
112 |
|
} |
177
|
|
|
|
178
|
112 |
|
/** |
179
|
|
|
* @inheritDoc |
180
|
|
|
*/ |
181
|
|
|
public function quoteColumnName($columnName) |
182
|
|
|
{ |
183
|
|
|
return '`' . str_replace('`', '``', $columnName) . '`'; |
184
|
82 |
|
} |
185
|
|
|
|
186
|
82 |
|
/** |
187
|
|
|
* @inheritDoc |
188
|
82 |
|
*/ |
189
|
|
|
public function hasTable($tableName) |
190
|
|
|
{ |
191
|
82 |
|
if ($this->hasCreatedTable($tableName)) { |
192
|
82 |
|
return true; |
193
|
|
|
} |
194
|
82 |
|
|
195
|
|
|
if (strpos($tableName, '.') !== false) { |
196
|
82 |
|
list($schema, $table) = explode('.', $tableName); |
197
|
|
|
$exists = $this->hasTableWithSchema($schema, $table); |
198
|
|
|
// Only break here on success, because it is possible for table names to contain a dot. |
199
|
|
|
if ($exists) { |
200
|
|
|
return true; |
201
|
|
|
} |
202
|
82 |
|
} |
203
|
|
|
|
204
|
|
|
$options = $this->getOptions(); |
205
|
|
|
|
206
|
82 |
|
return $this->hasTableWithSchema($options['name'], $tableName); |
207
|
|
|
} |
208
|
82 |
|
|
209
|
82 |
|
/** |
210
|
|
|
* @param string $schema The table schema |
211
|
|
|
* @param string $tableName The table name |
212
|
82 |
|
* |
213
|
82 |
|
* @return bool |
214
|
68 |
|
*/ |
215
|
68 |
|
private function hasTableWithSchema($schema, $tableName) |
216
|
68 |
|
{ |
217
|
68 |
|
$result = $this->fetchRow(sprintf( |
218
|
68 |
|
"SELECT TABLE_NAME |
219
|
|
|
FROM INFORMATION_SCHEMA.TABLES |
220
|
68 |
|
WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", |
221
|
68 |
|
$schema, |
222
|
82 |
|
$tableName |
223
|
|
|
)); |
224
|
2 |
|
|
225
|
2 |
|
return !empty($result); |
226
|
2 |
|
} |
227
|
2 |
|
/** |
228
|
|
|
* {@inheritDoc} |
229
|
2 |
|
* |
230
|
2 |
|
* @return void |
231
|
2 |
|
*/ |
232
|
|
|
public function createTable(Table $table, array $columns = [], array $indexes = []) |
233
|
|
|
{ |
234
|
|
|
// This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html |
235
|
|
|
$defaultOptions = [ |
236
|
82 |
|
'engine' => 'InnoDB', |
237
|
82 |
|
'collation' => 'utf8_general_ci', |
238
|
82 |
|
]; |
239
|
82 |
|
|
240
|
|
|
$options = array_merge( |
241
|
|
|
$defaultOptions, |
242
|
82 |
|
array_intersect_key($this->getOptions(), $defaultOptions), |
243
|
82 |
|
$table->getOptions() |
244
|
82 |
|
); |
245
|
82 |
|
|
246
|
82 |
|
// Add the default primary key |
247
|
|
View Code Duplication |
if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { |
|
|
|
|
248
|
|
|
$options['id'] = 'id'; |
249
|
82 |
|
} |
250
|
2 |
|
|
251
|
2 |
|
if (isset($options['id']) && is_string($options['id'])) { |
252
|
|
|
// Handle id => "field_name" to support AUTO_INCREMENT |
253
|
82 |
|
$column = new Column(); |
254
|
82 |
|
$column->setName($options['id']) |
255
|
82 |
|
->setType('integer') |
256
|
82 |
|
->setSigned(isset($options['signed']) ? $options['signed'] : true) |
257
|
82 |
|
->setIdentity(true); |
258
|
|
|
|
259
|
|
|
array_unshift($columns, $column); |
260
|
82 |
|
if (isset($options['primary_key']) && (array)$options['id'] !== (array)$options['primary_key']) { |
261
|
82 |
|
throw new InvalidArgumentException('You cannot enable an auto incrementing ID field and a primary key'); |
262
|
82 |
|
} |
263
|
82 |
|
$options['primary_key'] = $options['id']; |
264
|
81 |
|
} |
265
|
82 |
|
|
266
|
|
|
// TODO - process table options like collation etc |
267
|
|
|
|
268
|
2 |
|
// process table engine (default to InnoDB) |
269
|
2 |
|
$optionsStr = 'ENGINE = InnoDB'; |
270
|
2 |
|
if (isset($options['engine'])) { |
271
|
2 |
|
$optionsStr = sprintf('ENGINE = %s', $options['engine']); |
272
|
2 |
|
} |
273
|
2 |
|
|
274
|
2 |
|
// process table collation |
275
|
2 |
|
if (isset($options['collation'])) { |
276
|
2 |
|
$charset = explode('_', $options['collation']); |
277
|
2 |
|
$optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]); |
278
|
82 |
|
$optionsStr .= sprintf(' COLLATE %s', $options['collation']); |
279
|
82 |
|
} |
280
|
1 |
|
|
281
|
|
|
// set the table comment |
282
|
|
|
if (isset($options['comment'])) { |
283
|
|
|
$optionsStr .= sprintf(' COMMENT=%s ', $this->getConnection()->quote($options['comment'])); |
284
|
82 |
|
} |
285
|
82 |
|
|
286
|
10 |
|
// set the table row format |
287
|
82 |
|
if (isset($options['row_format'])) { |
288
|
|
|
$optionsStr .= sprintf(' ROW_FORMAT=%s ', $options['row_format']); |
289
|
|
|
} |
290
|
82 |
|
|
291
|
82 |
|
$sql = 'CREATE TABLE '; |
292
|
2 |
|
$sql .= $this->quoteTableName($table->getName()) . ' ('; |
293
|
82 |
|
foreach ($columns as $column) { |
294
|
|
|
$sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; |
295
|
82 |
|
} |
296
|
82 |
|
|
297
|
|
|
// set the primary key(s) |
298
|
|
View Code Duplication |
if (isset($options['primary_key'])) { |
|
|
|
|
299
|
82 |
|
$sql = rtrim($sql); |
300
|
82 |
|
$sql .= ' PRIMARY KEY ('; |
301
|
|
|
if (is_string($options['primary_key'])) { // handle primary_key => 'id' |
302
|
|
|
$sql .= $this->quoteColumnName($options['primary_key']); |
303
|
|
|
} elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') |
304
|
|
|
$sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key'])); |
305
|
5 |
|
} |
306
|
|
|
$sql .= ')'; |
307
|
5 |
|
} else { |
308
|
5 |
|
$sql = substr(rtrim($sql), 0, -1); // no primary keys |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
// set the indexes |
312
|
|
|
foreach ($indexes as $index) { |
313
|
5 |
|
$sql .= ', ' . $this->getIndexSqlDefinition($index); |
314
|
|
|
} |
315
|
5 |
|
|
316
|
5 |
|
$sql .= ') ' . $optionsStr; |
317
|
|
|
$sql = rtrim($sql); |
318
|
|
|
|
319
|
|
|
// execute the sql |
320
|
|
|
$this->execute($sql); |
321
|
1 |
|
|
322
|
|
|
$this->addCreatedTable($table->getName()); |
323
|
1 |
|
} |
324
|
1 |
|
|
325
|
1 |
|
/** |
326
|
1 |
|
* {@inheritDoc} |
327
|
|
|
* |
328
|
1 |
|
* @throws \InvalidArgumentException |
329
|
1 |
|
*/ |
330
|
|
|
protected function getChangePrimaryKeyInstructions(Table $table, $newColumns) |
331
|
|
|
{ |
332
|
|
|
$instructions = new AlterInstructions(); |
333
|
|
|
|
334
|
12 |
|
// Drop the existing primary key |
335
|
|
|
$primaryKey = $this->getPrimaryKey($table->getName()); |
336
|
12 |
|
if (!empty($primaryKey['columns'])) { |
337
|
12 |
|
$instructions->addAlter('DROP PRIMARY KEY'); |
338
|
12 |
|
} |
339
|
12 |
|
|
340
|
|
|
// Add the primary key(s) |
341
|
12 |
|
if (!empty($newColumns)) { |
342
|
12 |
|
$sql = 'ADD PRIMARY KEY ('; |
343
|
12 |
|
if (is_string($newColumns)) { // handle primary_key => 'id' |
344
|
12 |
|
$sql .= $this->quoteColumnName($newColumns); |
345
|
12 |
|
} elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id') |
346
|
12 |
|
$sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns)); |
347
|
|
|
} else { |
348
|
12 |
|
throw new InvalidArgumentException(sprintf( |
349
|
12 |
|
'Invalid value for primary key: %s', |
350
|
12 |
|
json_encode($newColumns) |
351
|
|
|
)); |
352
|
12 |
|
} |
353
|
3 |
|
$sql .= ')'; |
354
|
3 |
|
$instructions->addAlter($sql); |
355
|
|
|
} |
356
|
12 |
|
|
357
|
12 |
|
return $instructions; |
358
|
|
|
} |
359
|
12 |
|
|
360
|
|
|
/** |
361
|
|
|
* @inheritDoc |
362
|
|
|
*/ |
363
|
|
|
protected function getChangeCommentInstructions(Table $table, $newComment) |
364
|
|
|
{ |
365
|
79 |
|
$instructions = new AlterInstructions(); |
366
|
|
|
|
367
|
79 |
|
// passing 'null' is to remove table comment |
368
|
79 |
|
$newComment = ($newComment !== null) |
369
|
79 |
|
? $newComment |
370
|
77 |
|
: ''; |
371
|
|
|
$sql = sprintf(' COMMENT=%s ', $this->getConnection()->quote($newComment)); |
372
|
77 |
|
$instructions->addAlter($sql); |
373
|
|
|
|
374
|
21 |
|
return $instructions; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* @inheritDoc |
379
|
|
|
*/ |
380
|
|
View Code Duplication |
protected function getRenameTableInstructions($tableName, $newTableName) |
|
|
|
|
381
|
|
|
{ |
382
|
|
|
$this->updateCreatedTableName($tableName, $newTableName); |
383
|
95 |
|
$sql = sprintf( |
384
|
|
|
'RENAME TABLE %s TO %s', |
385
|
95 |
|
$this->quoteTableName($tableName), |
386
|
10 |
|
$this->quoteTableName($newTableName) |
387
|
95 |
|
); |
388
|
79 |
|
|
389
|
79 |
|
return new AlterInstructions([], [$sql]); |
390
|
95 |
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* @inheritDoc |
394
|
|
|
*/ |
395
|
|
View Code Duplication |
protected function getDropTableInstructions($tableName) |
|
|
|
|
396
|
18 |
|
{ |
397
|
|
|
$this->removeCreatedTable($tableName); |
398
|
18 |
|
$sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName)); |
399
|
18 |
|
|
400
|
18 |
|
return new AlterInstructions([], [$sql]); |
401
|
18 |
|
} |
402
|
18 |
|
|
403
|
18 |
|
/** |
404
|
|
|
* {@inheritDoc} |
405
|
18 |
|
* |
406
|
2 |
|
* @return void |
407
|
2 |
|
*/ |
408
|
|
|
public function truncateTable($tableName) |
409
|
18 |
|
{ |
410
|
18 |
|
$sql = sprintf( |
411
|
|
|
'TRUNCATE TABLE %s', |
412
|
|
|
$this->quoteTableName($tableName) |
413
|
|
|
); |
414
|
|
|
|
415
|
7 |
|
$this->execute($sql); |
416
|
|
|
} |
417
|
7 |
|
|
418
|
7 |
|
/** |
419
|
7 |
|
* @inheritDoc |
420
|
5 |
|
*/ |
421
|
5 |
|
public function getColumns($tableName) |
422
|
5 |
|
{ |
423
|
1 |
|
$columns = []; |
424
|
1 |
|
$rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); |
425
|
5 |
|
foreach ($rows as $columnInfo) { |
426
|
|
|
$phinxType = $this->getPhinxType($columnInfo['Type']); |
427
|
5 |
|
|
428
|
5 |
|
$column = new Column(); |
429
|
5 |
|
$column->setName($columnInfo['Field']) |
430
|
5 |
|
->setNull($columnInfo['Null'] !== 'NO') |
431
|
5 |
|
->setDefault($columnInfo['Default']) |
432
|
5 |
|
->setType($phinxType['name']) |
433
|
|
|
->setSigned(strpos($columnInfo['Type'], 'unsigned') === false) |
434
|
5 |
|
->setLimit($phinxType['limit']) |
435
|
5 |
|
->setScale($phinxType['scale']); |
436
|
5 |
|
|
437
|
|
|
if ($columnInfo['Extra'] === 'auto_increment') { |
438
|
6 |
|
$column->setIdentity(true); |
439
|
|
|
} |
440
|
2 |
|
|
441
|
|
|
if (isset($phinxType['values'])) { |
442
|
|
|
$column->setValues($phinxType['values']); |
443
|
2 |
|
} |
444
|
|
|
|
445
|
|
|
$columns[] = $column; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
return $columns; |
449
|
5 |
|
} |
450
|
|
|
|
451
|
5 |
|
/** |
452
|
5 |
|
* @inheritDoc |
453
|
5 |
|
*/ |
454
|
5 |
View Code Duplication |
public function hasColumn($tableName, $columnName) |
|
|
|
|
455
|
5 |
|
{ |
456
|
5 |
|
$rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); |
457
|
5 |
|
foreach ($rows as $column) { |
458
|
5 |
|
if (strcasecmp($column['Field'], $columnName) === 0) { |
459
|
|
|
return true; |
460
|
5 |
|
} |
461
|
5 |
|
} |
462
|
5 |
|
|
463
|
|
|
return false; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
5 |
|
* @inheritDoc |
468
|
|
|
*/ |
469
|
5 |
|
protected function getAddColumnInstructions(Table $table, Column $column) |
470
|
5 |
|
{ |
471
|
5 |
|
$alter = sprintf( |
472
|
5 |
|
'ADD %s %s', |
473
|
5 |
|
$this->quoteColumnName($column->getName()), |
474
|
5 |
|
$this->getColumnSqlDefinition($column) |
475
|
5 |
|
); |
476
|
5 |
|
|
477
|
|
|
if ($column->getAfter()) { |
478
|
|
|
$alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter()); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
return new AlterInstructions([$alter]); |
482
|
|
|
} |
483
|
|
|
|
484
|
19 |
|
/** |
485
|
|
|
* {@inheritDoc} |
486
|
19 |
|
* |
487
|
19 |
|
* @throws \InvalidArgumentException |
488
|
19 |
|
*/ |
489
|
18 |
|
protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName) |
490
|
18 |
|
{ |
491
|
18 |
|
$rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName))); |
492
|
18 |
|
|
493
|
19 |
|
foreach ($rows as $row) { |
494
|
19 |
|
if (strcasecmp($row['Field'], $columnName) === 0) { |
495
|
|
|
$null = ($row['Null'] === 'NO') ? 'NOT NULL' : 'NULL'; |
496
|
|
|
$comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : ''; |
497
|
|
|
$extra = ' ' . strtoupper($row['Extra']); |
498
|
|
|
if (($row['Default'] !== null)) { |
499
|
|
|
$extra .= $this->getDefaultValueDefinition($row['Default']); |
500
|
14 |
|
} |
501
|
|
|
$definition = $row['Type'] . ' ' . $null . $extra . $comment; |
502
|
14 |
|
|
503
|
6 |
|
$alter = sprintf( |
504
|
6 |
|
'CHANGE COLUMN %s %s %s', |
505
|
|
|
$this->quoteColumnName($columnName), |
506
|
14 |
|
$this->quoteColumnName($newColumnName), |
507
|
14 |
|
$definition |
508
|
|
|
); |
509
|
14 |
|
|
510
|
14 |
|
return new AlterInstructions([$alter]); |
511
|
12 |
|
} |
512
|
|
|
} |
513
|
13 |
|
|
514
|
|
|
throw new InvalidArgumentException(sprintf( |
515
|
11 |
|
"The specified column doesn't exist: " . |
516
|
|
|
$columnName |
517
|
|
|
)); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
1 |
|
* @inheritDoc |
522
|
|
|
*/ |
523
|
1 |
|
protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn) |
524
|
|
|
{ |
525
|
1 |
|
$after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : ''; |
526
|
1 |
|
$alter = sprintf( |
527
|
1 |
|
'CHANGE %s %s %s%s', |
528
|
|
|
$this->quoteColumnName($columnName), |
529
|
1 |
|
$this->quoteColumnName($newColumn->getName()), |
530
|
|
|
$this->getColumnSqlDefinition($newColumn), |
531
|
|
|
$after |
532
|
|
|
); |
533
|
|
|
|
534
|
|
|
return new AlterInstructions([$alter]); |
535
|
|
|
} |
536
|
|
|
|
537
|
4 |
|
/** |
538
|
|
|
* @inheritDoc |
539
|
4 |
|
*/ |
540
|
4 |
|
protected function getDropColumnInstructions($tableName, $columnName) |
541
|
4 |
|
{ |
542
|
4 |
|
$alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName)); |
543
|
4 |
|
|
544
|
4 |
|
return new AlterInstructions([$alter]); |
545
|
4 |
|
} |
546
|
4 |
|
|
547
|
|
|
/** |
548
|
|
|
* Get an array of indexes from a particular table. |
549
|
|
|
* |
550
|
|
|
* @param string $tableName Table Name |
551
|
3 |
|
* |
552
|
|
|
* @return array |
553
|
3 |
|
*/ |
554
|
2 |
|
protected function getIndexes($tableName) |
555
|
2 |
|
{ |
556
|
|
|
$indexes = []; |
557
|
3 |
|
$rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName))); |
558
|
3 |
View Code Duplication |
foreach ($rows as $row) { |
|
|
|
|
559
|
|
|
if (!isset($indexes[$row['Key_name']])) { |
560
|
3 |
|
$indexes[$row['Key_name']] = ['columns' => []]; |
561
|
3 |
|
} |
562
|
3 |
|
$indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']); |
563
|
3 |
|
} |
564
|
3 |
|
|
565
|
3 |
|
return $indexes; |
566
|
3 |
|
} |
567
|
3 |
|
|
568
|
3 |
|
/** |
569
|
3 |
|
* @inheritDoc |
570
|
|
|
*/ |
571
|
3 |
View Code Duplication |
public function hasIndex($tableName, $columns) |
|
|
|
|
572
|
1 |
|
{ |
573
|
|
|
if (is_string($columns)) { |
574
|
|
|
$columns = [$columns]; // str to array |
575
|
|
|
} |
576
|
|
|
|
577
|
2 |
|
$columns = array_map('strtolower', $columns); |
578
|
|
|
$indexes = $this->getIndexes($tableName); |
579
|
2 |
|
|
580
|
|
|
foreach ($indexes as $index) { |
581
|
2 |
|
if ($columns == $index['columns']) { |
582
|
|
|
return true; |
583
|
2 |
|
} |
584
|
2 |
|
} |
585
|
2 |
|
|
586
|
2 |
|
return false; |
587
|
2 |
|
} |
588
|
2 |
|
|
589
|
2 |
|
/** |
590
|
2 |
|
* @inheritDoc |
591
|
2 |
|
*/ |
592
|
|
View Code Duplication |
public function hasIndexByName($tableName, $indexName) |
|
|
|
|
593
|
2 |
|
{ |
594
|
|
|
$indexes = $this->getIndexes($tableName); |
595
|
|
|
|
596
|
|
|
foreach ($indexes as $name => $index) { |
597
|
|
|
if ($name === $indexName) { |
598
|
|
|
return true; |
599
|
21 |
|
} |
600
|
|
|
} |
601
|
21 |
|
|
602
|
5 |
|
return false; |
603
|
5 |
|
} |
604
|
21 |
|
|
605
|
21 |
|
/** |
606
|
6 |
|
* @inheritDoc |
607
|
4 |
|
*/ |
608
|
|
|
protected function getAddIndexInstructions(Table $table, Index $index) |
609
|
4 |
|
{ |
610
|
|
|
$instructions = new AlterInstructions(); |
611
|
15 |
|
|
612
|
12 |
|
if ($index->getType() === Index::FULLTEXT) { |
613
|
10 |
|
// Must be executed separately |
614
|
|
|
// SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time |
615
|
11 |
|
$alter = sprintf( |
616
|
11 |
|
'ALTER TABLE %s ADD %s', |
617
|
|
|
$this->quoteTableName($table->getName()), |
618
|
|
|
$this->getIndexSqlDefinition($index) |
619
|
|
|
); |
620
|
|
|
|
621
|
|
|
$instructions->addPostStep($alter); |
622
|
|
|
} else { |
623
|
|
|
$alter = sprintf( |
624
|
|
|
'ADD %s', |
625
|
|
|
$this->getIndexSqlDefinition($index) |
626
|
22 |
|
); |
627
|
|
|
|
628
|
22 |
|
$instructions->addAlter($alter); |
629
|
22 |
|
} |
630
|
|
|
|
631
|
|
|
return $instructions; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
/** |
635
|
|
|
* {@inheritDoc} |
636
|
|
|
* |
637
|
|
|
* @throws \InvalidArgumentException |
638
|
|
|
*/ |
639
|
|
|
protected function getDropIndexByColumnsInstructions($tableName, $columns) |
640
|
22 |
|
{ |
641
|
|
|
if (is_string($columns)) { |
642
|
22 |
|
$columns = [$columns]; // str to array |
643
|
22 |
|
} |
644
|
19 |
|
|
645
|
19 |
|
$indexes = $this->getIndexes($tableName); |
646
|
19 |
|
$columns = array_map('strtolower', $columns); |
647
|
19 |
|
|
648
|
22 |
View Code Duplication |
foreach ($indexes as $indexName => $index) { |
|
|
|
|
649
|
22 |
|
if ($columns == $index['columns']) { |
650
|
|
|
return new AlterInstructions([sprintf( |
651
|
|
|
'DROP INDEX %s', |
652
|
|
|
$this->quoteColumnName($indexName) |
653
|
|
|
)]); |
654
|
|
|
} |
655
|
15 |
|
} |
656
|
|
|
|
657
|
15 |
|
throw new InvalidArgumentException(sprintf( |
658
|
15 |
|
"The specified index on columns '%s' does not exist", |
659
|
15 |
|
implode(',', $columns) |
660
|
15 |
|
)); |
661
|
15 |
|
} |
662
|
15 |
|
|
663
|
15 |
|
/** |
664
|
15 |
|
* {@inheritDoc} |
665
|
|
|
* |
666
|
|
|
* @throws \InvalidArgumentException |
667
|
|
|
*/ |
668
|
|
|
protected function getDropIndexByNameInstructions($tableName, $indexName) |
669
|
8 |
|
{ |
670
|
|
|
$indexes = $this->getIndexes($tableName); |
671
|
8 |
|
|
672
|
3 |
View Code Duplication |
foreach ($indexes as $name => $index) { |
|
|
|
|
673
|
3 |
|
if ($name === $indexName) { |
674
|
|
|
return new AlterInstructions([sprintf( |
675
|
|
|
'DROP INDEX %s', |
676
|
8 |
|
$this->quoteColumnName($indexName) |
677
|
8 |
|
)]); |
678
|
8 |
|
} |
679
|
8 |
|
} |
680
|
8 |
|
|
681
|
|
|
throw new InvalidArgumentException(sprintf( |
682
|
8 |
|
"The specified index name '%s' does not exist", |
683
|
8 |
|
$indexName |
684
|
8 |
|
)); |
685
|
|
|
} |
686
|
7 |
|
|
687
|
7 |
|
/** |
688
|
|
|
* @inheritDoc |
689
|
|
|
*/ |
690
|
|
View Code Duplication |
public function hasPrimaryKey($tableName, $columns, $constraint = null) |
|
|
|
|
691
|
|
|
{ |
692
|
|
|
$primaryKey = $this->getPrimaryKey($tableName); |
693
|
|
|
|
694
|
|
|
if (empty($primaryKey['constraint'])) { |
695
|
7 |
|
return false; |
696
|
7 |
|
} |
697
|
|
|
|
698
|
7 |
|
if ($constraint) { |
|
|
|
|
699
|
7 |
|
return ($primaryKey['constraint'] === $constraint); |
700
|
7 |
|
} else { |
701
|
7 |
|
if (is_string($columns)) { |
702
|
7 |
|
$columns = [$columns]; // str to array |
703
|
|
|
} |
704
|
7 |
|
$missingColumns = array_diff($columns, $primaryKey['columns']); |
705
|
|
|
|
706
|
|
|
return empty($missingColumns); |
707
|
|
|
} |
708
|
|
|
} |
709
|
96 |
|
|
710
|
|
|
/** |
711
|
|
|
* Get the primary key from a particular table. |
712
|
96 |
|
* |
713
|
87 |
|
* @param string $tableName Table Name |
714
|
|
|
* |
715
|
96 |
|
* @return array |
716
|
4 |
|
*/ |
717
|
|
View Code Duplication |
public function getPrimaryKey($tableName) |
|
|
|
|
718
|
96 |
|
{ |
719
|
9 |
|
$rows = $this->fetchAll(sprintf( |
720
|
|
|
"SELECT |
721
|
|
|
k.constraint_name, |
722
|
6 |
|
k.column_name |
723
|
6 |
|
FROM information_schema.table_constraints t |
724
|
6 |
|
JOIN information_schema.key_column_usage k |
725
|
6 |
|
USING(constraint_name,table_name) |
726
|
6 |
|
WHERE t.constraint_type='PRIMARY KEY' |
727
|
6 |
|
AND t.table_name='%s'", |
728
|
6 |
|
$tableName |
729
|
6 |
|
)); |
730
|
|
|
|
731
|
5 |
|
$primaryKey = [ |
732
|
|
|
'columns' => [], |
733
|
5 |
|
]; |
734
|
|
|
foreach ($rows as $row) { |
735
|
95 |
|
$primaryKey['constraint'] = $row['constraint_name']; |
736
|
5 |
|
$primaryKey['columns'][] = $row['column_name']; |
737
|
|
|
} |
738
|
95 |
|
|
739
|
3 |
|
return $primaryKey; |
740
|
|
|
} |
741
|
95 |
|
|
742
|
1 |
|
/** |
743
|
|
|
* @inheritDoc |
744
|
|
|
*/ |
745
|
1 |
|
public function hasForeignKey($tableName, $columns, $constraint = null) |
746
|
1 |
|
{ |
747
|
1 |
|
if (is_string($columns)) { |
748
|
1 |
|
$columns = [$columns]; // str to array |
749
|
1 |
|
} |
750
|
1 |
|
$foreignKeys = $this->getForeignKeys($tableName); |
751
|
1 |
|
if ($constraint) { |
|
|
|
|
752
|
1 |
|
if (isset($foreignKeys[$constraint])) { |
753
|
|
|
return !empty($foreignKeys[$constraint]); |
754
|
1 |
|
} |
755
|
|
|
|
756
|
1 |
|
return false; |
757
|
|
|
} else { |
758
|
95 |
|
foreach ($foreignKeys as $key) { |
759
|
82 |
|
if ($columns == $key['columns']) { |
760
|
|
|
return true; |
761
|
|
|
} |
762
|
6 |
|
} |
763
|
6 |
|
|
764
|
6 |
|
return false; |
765
|
6 |
|
} |
766
|
6 |
|
} |
767
|
6 |
|
|
768
|
|
|
/** |
769
|
6 |
|
* Get an array of foreign keys from a particular table. |
770
|
6 |
|
* |
771
|
6 |
|
* @param string $tableName Table Name |
772
|
6 |
|
* |
773
|
6 |
|
* @return array |
774
|
6 |
|
*/ |
775
|
6 |
|
protected function getForeignKeys($tableName) |
776
|
2 |
|
{ |
777
|
2 |
View Code Duplication |
if (strpos($tableName, '.') !== false) { |
|
|
|
|
778
|
6 |
|
list($schema, $tableName) = explode('.', $tableName); |
779
|
|
|
} |
780
|
5 |
|
|
781
|
82 |
|
$foreignKeys = []; |
782
|
76 |
|
$rows = $this->fetchAll(sprintf( |
783
|
76 |
|
"SELECT |
784
|
82 |
|
CONSTRAINT_NAME, |
785
|
|
|
CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS TABLE_NAME, |
786
|
86 |
|
COLUMN_NAME, |
787
|
82 |
|
CONCAT(REFERENCED_TABLE_SCHEMA, '.', REFERENCED_TABLE_NAME) AS REFERENCED_TABLE_NAME, |
788
|
|
|
REFERENCED_COLUMN_NAME |
789
|
86 |
|
FROM information_schema.KEY_COLUMN_USAGE |
790
|
7 |
|
WHERE REFERENCED_TABLE_NAME IS NOT NULL |
791
|
|
|
AND TABLE_SCHEMA = %s |
792
|
84 |
|
AND TABLE_NAME = '%s' |
793
|
5 |
|
ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", |
794
|
|
|
empty($schema) ? 'DATABASE()' : "'$schema'", |
795
|
83 |
|
$tableName |
796
|
7 |
|
)); |
797
|
|
View Code Duplication |
foreach ($rows as $row) { |
|
|
|
|
798
|
83 |
|
$foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; |
799
|
80 |
|
$foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; |
800
|
|
|
$foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; |
801
|
83 |
|
$foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; |
802
|
4 |
|
} |
803
|
|
|
|
804
|
83 |
|
return $foreignKeys; |
805
|
4 |
|
} |
806
|
|
|
|
807
|
83 |
|
/** |
808
|
80 |
|
* @inheritDoc |
809
|
|
|
*/ |
810
|
10 |
View Code Duplication |
protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey) |
|
|
|
|
811
|
2 |
|
{ |
812
|
|
|
$alter = sprintf( |
813
|
10 |
|
'ADD %s', |
814
|
10 |
|
$this->getForeignKeySqlDefinition($foreignKey) |
815
|
10 |
|
); |
816
|
10 |
|
|
817
|
5 |
|
return new AlterInstructions([$alter]); |
818
|
8 |
|
} |
819
|
5 |
|
|
820
|
|
|
/** |
821
|
6 |
|
* @inheritDoc |
822
|
4 |
|
*/ |
823
|
|
|
protected function getDropForeignKeyInstructions($tableName, $constraint) |
824
|
2 |
|
{ |
825
|
|
|
$alter = sprintf( |
826
|
|
|
'DROP FOREIGN KEY %s', |
827
|
|
|
$constraint |
828
|
|
|
); |
829
|
|
|
|
830
|
2 |
|
return new AlterInstructions([$alter]); |
831
|
|
|
} |
832
|
|
|
|
833
|
2 |
|
/** |
834
|
2 |
|
* {@inheritDoc} |
835
|
2 |
|
* |
836
|
|
|
* @throws \InvalidArgumentException |
837
|
|
|
*/ |
838
|
|
|
protected function getDropForeignKeyByColumnsInstructions($tableName, $columns) |
839
|
|
|
{ |
840
|
|
|
$instructions = new AlterInstructions(); |
841
|
|
|
|
842
|
|
View Code Duplication |
foreach ($columns as $column) { |
|
|
|
|
843
|
|
|
$rows = $this->fetchAll(sprintf( |
844
|
|
|
"SELECT |
845
|
|
|
CONSTRAINT_NAME |
846
|
17 |
|
FROM information_schema.KEY_COLUMN_USAGE |
847
|
|
|
WHERE REFERENCED_TABLE_SCHEMA = DATABASE() |
848
|
17 |
|
AND REFERENCED_TABLE_NAME IS NOT NULL |
849
|
17 |
|
AND TABLE_NAME = '%s' |
850
|
1 |
|
AND COLUMN_NAME = '%s' |
851
|
|
|
ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", |
852
|
16 |
|
$tableName, |
853
|
16 |
|
$column |
854
|
16 |
|
)); |
855
|
16 |
|
|
856
|
14 |
|
foreach ($rows as $row) { |
857
|
14 |
|
$instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME'])); |
858
|
16 |
|
} |
859
|
4 |
|
} |
860
|
4 |
|
|
861
|
16 |
|
if (empty($instructions->getAlterParts())) { |
862
|
3 |
|
throw new InvalidArgumentException(sprintf( |
863
|
3 |
|
"Not foreign key on columns '%s' exist", |
864
|
3 |
|
implode(',', $columns) |
865
|
|
|
)); |
866
|
16 |
|
} |
867
|
6 |
|
|
868
|
6 |
|
return $instructions; |
869
|
3 |
|
} |
870
|
3 |
|
|
871
|
6 |
|
/** |
872
|
16 |
|
* {@inheritDoc} |
873
|
5 |
|
* |
874
|
5 |
|
* @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException |
875
|
1 |
|
*/ |
876
|
1 |
|
public function getSqlType($type, $limit = null) |
877
|
5 |
|
{ |
878
|
2 |
|
switch ($type) { |
879
|
2 |
|
case static::PHINX_TYPE_FLOAT: |
880
|
5 |
|
case static::PHINX_TYPE_DOUBLE: |
881
|
16 |
|
case static::PHINX_TYPE_DECIMAL: |
882
|
2 |
|
case static::PHINX_TYPE_DATE: |
883
|
2 |
|
case static::PHINX_TYPE_ENUM: |
884
|
2 |
|
case static::PHINX_TYPE_SET: |
885
|
16 |
|
case static::PHINX_TYPE_JSON: |
886
|
2 |
|
// Geospatial database types |
887
|
2 |
|
case static::PHINX_TYPE_GEOMETRY: |
888
|
2 |
|
case static::PHINX_TYPE_POINT: |
889
|
16 |
|
case static::PHINX_TYPE_LINESTRING: |
890
|
2 |
|
case static::PHINX_TYPE_POLYGON: |
891
|
2 |
|
return ['name' => $type]; |
892
|
2 |
|
case static::PHINX_TYPE_DATETIME: |
893
|
16 |
|
case static::PHINX_TYPE_TIMESTAMP: |
894
|
15 |
|
case static::PHINX_TYPE_TIME: |
895
|
15 |
|
return ['name' => $type, 'limit' => $limit]; |
896
|
12 |
|
case static::PHINX_TYPE_STRING: |
897
|
12 |
|
return ['name' => 'varchar', 'limit' => $limit ?: 255]; |
898
|
15 |
|
case static::PHINX_TYPE_CHAR: |
899
|
11 |
|
return ['name' => 'char', 'limit' => $limit ?: 255]; |
900
|
6 |
View Code Duplication |
case static::PHINX_TYPE_TEXT: |
|
|
|
|
901
|
4 |
|
if ($limit) { |
|
|
|
|
902
|
4 |
|
$sizes = [ |
903
|
6 |
|
// Order matters! Size must always be tested from longest to shortest! |
904
|
6 |
|
'longtext' => static::TEXT_LONG, |
905
|
10 |
|
'mediumtext' => static::TEXT_MEDIUM, |
906
|
2 |
|
'text' => static::TEXT_REGULAR, |
907
|
2 |
|
'tinytext' => static::TEXT_SMALL, |
908
|
10 |
|
]; |
909
|
1 |
|
foreach ($sizes as $name => $length) { |
910
|
1 |
|
if ($limit >= $length) { |
911
|
1 |
|
return ['name' => $name]; |
912
|
10 |
|
} |
913
|
1 |
|
} |
914
|
1 |
|
} |
915
|
1 |
|
|
916
|
10 |
|
return ['name' => 'text']; |
917
|
1 |
|
case static::PHINX_TYPE_BINARY: |
918
|
1 |
|
return ['name' => 'binary', 'limit' => $limit ?: 255]; |
919
|
1 |
|
case static::PHINX_TYPE_VARBINARY: |
920
|
10 |
|
return ['name' => 'varbinary', 'limit' => $limit ?: 255]; |
921
|
2 |
View Code Duplication |
case static::PHINX_TYPE_BLOB: |
|
|
|
|
922
|
2 |
|
if ($limit) { |
|
|
|
|
923
|
2 |
|
$sizes = [ |
924
|
9 |
|
// Order matters! Size must always be tested from longest to shortest! |
925
|
2 |
|
'longblob' => static::BLOB_LONG, |
926
|
2 |
|
'mediumblob' => static::BLOB_MEDIUM, |
927
|
2 |
|
'blob' => static::BLOB_REGULAR, |
928
|
8 |
|
'tinyblob' => static::BLOB_SMALL, |
929
|
2 |
|
]; |
930
|
2 |
|
foreach ($sizes as $name => $length) { |
931
|
2 |
|
if ($limit >= $length) { |
932
|
|
|
return ['name' => $name]; |
933
|
|
|
} |
934
|
|
|
} |
935
|
16 |
|
} |
936
|
|
|
|
937
|
|
|
return ['name' => 'blob']; |
938
|
15 |
|
case static::PHINX_TYPE_BIT: |
939
|
15 |
|
return ['name' => 'bit', 'limit' => $limit ?: 64]; |
940
|
|
|
case static::PHINX_TYPE_SMALL_INTEGER: |
941
|
15 |
|
return ['name' => 'smallint', 'limit' => $limit ?: 6]; |
942
|
|
|
case static::PHINX_TYPE_INTEGER: |
943
|
15 |
|
if ($limit && $limit >= static::INT_TINY) { |
|
|
|
|
944
|
3 |
|
$sizes = [ |
945
|
3 |
|
// Order matters! Size must always be tested from longest to shortest! |
946
|
|
|
'bigint' => static::INT_BIG, |
947
|
15 |
|
'int' => static::INT_REGULAR, |
948
|
|
|
'mediumint' => static::INT_MEDIUM, |
949
|
|
|
'smallint' => static::INT_SMALL, |
950
|
|
|
'tinyint' => static::INT_TINY, |
951
|
|
|
]; |
952
|
|
|
$limits = [ |
953
|
|
|
'smallint' => 6, |
954
|
83 |
|
'int' => 11, |
955
|
|
|
'bigint' => 20, |
956
|
83 |
|
]; |
957
|
|
|
foreach ($sizes as $name => $length) { |
958
|
83 |
|
if ($limit >= $length) { |
959
|
1 |
|
$def = ['name' => $name]; |
960
|
1 |
|
if (isset($limits[$name])) { |
961
|
82 |
|
$def['limit'] = $limits[$name]; |
962
|
|
|
} |
963
|
83 |
|
|
964
|
|
|
return $def; |
965
|
|
|
} |
966
|
|
|
} |
967
|
|
|
} elseif (!$limit) { |
|
|
|
|
968
|
4 |
|
$limit = 11; |
969
|
|
|
} |
970
|
4 |
|
|
971
|
4 |
|
return ['name' => 'int', 'limit' => $limit]; |
972
|
4 |
|
case static::PHINX_TYPE_BIG_INTEGER: |
973
|
|
|
return ['name' => 'bigint', 'limit' => 20]; |
974
|
4 |
|
case static::PHINX_TYPE_BOOLEAN: |
975
|
4 |
|
return ['name' => 'tinyint', 'limit' => 1]; |
976
|
|
|
case static::PHINX_TYPE_UUID: |
977
|
4 |
|
return ['name' => 'char', 'limit' => 36]; |
978
|
3 |
|
case static::TYPE_YEAR: |
979
|
3 |
|
if (!$limit || in_array($limit, [2, 4])) { |
|
|
|
|
980
|
|
|
$limit = 4; |
981
|
3 |
|
} |
982
|
|
|
|
983
|
3 |
|
return ['name' => 'year', 'limit' => $limit]; |
984
|
|
|
default: |
985
|
|
|
throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by MySQL.'); |
986
|
|
|
} |
987
|
|
|
} |
988
|
|
|
|
989
|
81 |
|
/** |
990
|
|
|
* Returns Phinx type by SQL type |
991
|
81 |
|
* |
992
|
81 |
|
* @internal param string $sqlType SQL type |
993
|
|
|
* |
994
|
|
|
* @param string $sqlTypeDef |
995
|
|
|
* |
996
|
|
|
* @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException |
997
|
|
|
* |
998
|
|
|
* @return array Phinx type |
999
|
|
|
*/ |
1000
|
89 |
|
public function getPhinxType($sqlTypeDef) |
1001
|
|
|
{ |
1002
|
89 |
|
$matches = []; |
1003
|
|
|
if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) { |
1004
|
89 |
|
throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by MySQL.'); |
1005
|
89 |
|
} else { |
1006
|
89 |
|
$limit = null; |
1007
|
2 |
|
$scale = null; |
1008
|
89 |
|
$type = $matches[1]; |
1009
|
86 |
|
if (count($matches) > 2) { |
1010
|
86 |
|
$limit = $matches[3] ? (int)$matches[3] : null; |
1011
|
89 |
|
} |
1012
|
5 |
|
if (count($matches) > 4) { |
1013
|
5 |
|
$scale = (int)$matches[5]; |
1014
|
89 |
|
} |
1015
|
89 |
View Code Duplication |
if ($type === 'tinyint' && $limit === 1) { |
|
|
|
|
1016
|
89 |
|
$type = static::PHINX_TYPE_BOOLEAN; |
1017
|
89 |
|
$limit = null; |
1018
|
89 |
|
} |
1019
|
89 |
|
switch ($type) { |
1020
|
|
|
case 'varchar': |
1021
|
89 |
|
$type = static::PHINX_TYPE_STRING; |
1022
|
2 |
|
if ($limit === 255) { |
1023
|
2 |
|
$limit = null; |
1024
|
|
|
} |
1025
|
89 |
|
break; |
1026
|
1 |
|
case 'char': |
1027
|
1 |
|
$type = static::PHINX_TYPE_CHAR; |
1028
|
|
|
if ($limit === 255) { |
1029
|
89 |
|
$limit = null; |
1030
|
|
|
} |
1031
|
|
|
if ($limit === 36) { |
1032
|
|
|
$type = static::PHINX_TYPE_UUID; |
1033
|
|
|
} |
1034
|
|
|
break; |
1035
|
|
|
case 'tinyint': |
1036
|
|
|
$type = static::PHINX_TYPE_INTEGER; |
1037
|
|
|
$limit = static::INT_TINY; |
1038
|
16 |
|
break; |
1039
|
|
|
case 'smallint': |
1040
|
16 |
|
$type = static::PHINX_TYPE_SMALL_INTEGER; |
1041
|
16 |
|
$limit = static::INT_SMALL; |
1042
|
16 |
|
break; |
1043
|
2 |
|
case 'mediumint': |
1044
|
2 |
|
$type = static::PHINX_TYPE_INTEGER; |
1045
|
|
|
$limit = static::INT_MEDIUM; |
1046
|
16 |
|
break; |
1047
|
5 |
|
case 'int': |
1048
|
5 |
|
$type = static::PHINX_TYPE_INTEGER; |
1049
|
|
|
if ($limit === 11) { |
1050
|
16 |
|
$limit = null; |
1051
|
1 |
|
} |
1052
|
1 |
|
break; |
1053
|
|
|
case 'bigint': |
1054
|
16 |
|
if ($limit === 20) { |
1055
|
|
|
$limit = null; |
1056
|
16 |
|
} |
1057
|
5 |
|
$type = static::PHINX_TYPE_BIG_INTEGER; |
1058
|
5 |
|
break; |
1059
|
|
|
case 'bit': |
1060
|
16 |
|
$type = static::PHINX_TYPE_BIT; |
1061
|
|
|
if ($limit === 64) { |
1062
|
16 |
|
$limit = null; |
1063
|
|
|
} |
1064
|
|
|
break; |
1065
|
|
|
case 'blob': |
1066
|
|
|
$type = static::PHINX_TYPE_BINARY; |
1067
|
|
|
break; |
1068
|
|
|
case 'tinyblob': |
1069
|
|
|
$type = static::PHINX_TYPE_BINARY; |
1070
|
|
|
$limit = static::BLOB_TINY; |
1071
|
17 |
|
break; |
1072
|
|
|
case 'mediumblob': |
1073
|
17 |
|
$type = static::PHINX_TYPE_BINARY; |
1074
|
17 |
|
$limit = static::BLOB_MEDIUM; |
1075
|
5 |
|
break; |
1076
|
5 |
|
case 'longblob': |
1077
|
17 |
|
$type = static::PHINX_TYPE_BINARY; |
1078
|
17 |
|
$limit = static::BLOB_LONG; |
1079
|
17 |
|
break; |
1080
|
17 |
|
case 'tinytext': |
1081
|
17 |
|
$type = static::PHINX_TYPE_TEXT; |
1082
|
17 |
|
$limit = static::TEXT_TINY; |
1083
|
17 |
|
break; |
1084
|
17 |
|
case 'mediumtext': |
1085
|
17 |
|
$type = static::PHINX_TYPE_TEXT; |
1086
|
17 |
|
$limit = static::TEXT_MEDIUM; |
1087
|
17 |
|
break; |
1088
|
2 |
|
case 'longtext': |
1089
|
2 |
|
$type = static::PHINX_TYPE_TEXT; |
1090
|
17 |
|
$limit = static::TEXT_LONG; |
1091
|
2 |
|
break; |
1092
|
2 |
|
} |
1093
|
17 |
|
|
1094
|
|
|
try { |
1095
|
|
|
// Call this to check if parsed type is supported. |
1096
|
|
|
$this->getSqlType($type, $limit); |
1097
|
|
|
} catch (UnsupportedColumnTypeException $e) { |
1098
|
|
|
$type = Literal::from($type); |
1099
|
|
|
} |
1100
|
|
|
|
1101
|
|
|
$phinxType = [ |
1102
|
2 |
|
'name' => $type, |
1103
|
|
|
'limit' => $limit, |
1104
|
2 |
|
'scale' => $scale, |
1105
|
|
|
]; |
1106
|
|
|
|
1107
|
2 |
|
if ($type === static::PHINX_TYPE_ENUM || $type === static::PHINX_TYPE_SET) { |
1108
|
|
|
$phinxType['values'] = explode("','", trim($matches[6], "()'")); |
1109
|
|
|
} |
1110
|
|
|
|
1111
|
2 |
|
return $phinxType; |
1112
|
2 |
|
} |
1113
|
|
|
} |
1114
|
2 |
|
|
1115
|
|
|
/** |
1116
|
2 |
|
* {@inheritDoc} |
1117
|
|
|
* |
1118
|
|
|
* @return void |
1119
|
|
|
*/ |
1120
|
|
|
public function createDatabase($name, $options = []) |
1121
|
|
|
{ |
1122
|
|
|
$charset = isset($options['charset']) ? $options['charset'] : 'utf8'; |
1123
|
85 |
|
|
1124
|
|
View Code Duplication |
if (isset($options['collation'])) { |
|
|
|
|
1125
|
85 |
|
$this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation'])); |
1126
|
|
|
} else { |
1127
|
|
|
$this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset)); |
1128
|
|
|
} |
1129
|
|
|
} |
1130
|
|
|
|
1131
|
|
|
/** |
1132
|
|
|
* @inheritDoc |
1133
|
|
|
*/ |
1134
|
|
|
public function hasDatabase($name) |
1135
|
|
|
{ |
1136
|
|
|
$rows = $this->fetchAll( |
1137
|
|
|
sprintf( |
1138
|
|
|
'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'', |
1139
|
|
|
$name |
1140
|
|
|
) |
1141
|
|
|
); |
1142
|
|
|
|
1143
|
|
|
foreach ($rows as $row) { |
1144
|
|
|
if (!empty($row)) { |
1145
|
|
|
return true; |
1146
|
|
|
} |
1147
|
|
|
} |
1148
|
|
|
|
1149
|
|
|
return false; |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
/** |
1153
|
|
|
* {@inheritDoc} |
1154
|
|
|
* |
1155
|
|
|
* @return void |
1156
|
|
|
*/ |
1157
|
|
|
public function dropDatabase($name) |
1158
|
|
|
{ |
1159
|
|
|
$this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name)); |
1160
|
|
|
} |
1161
|
|
|
|
1162
|
|
|
/** |
1163
|
|
|
* Gets the MySQL Column Definition for a Column object. |
1164
|
|
|
* |
1165
|
|
|
* @param \Phinx\Db\Table\Column $column Column |
1166
|
|
|
* |
1167
|
|
|
* @return string |
1168
|
|
|
*/ |
1169
|
|
|
protected function getColumnSqlDefinition(Column $column) |
1170
|
|
|
{ |
1171
|
|
|
if ($column->getType() instanceof Literal) { |
1172
|
|
|
$def = (string)$column->getType(); |
1173
|
|
|
} else { |
1174
|
|
|
$sqlType = $this->getSqlType($column->getType(), $column->getLimit()); |
1175
|
|
|
$def = strtoupper($sqlType['name']); |
1176
|
|
|
} |
1177
|
|
|
if ($column->getPrecision() && $column->getScale()) { |
1178
|
|
|
$def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; |
1179
|
|
|
} elseif (isset($sqlType['limit'])) { |
1180
|
|
|
$def .= '(' . $sqlType['limit'] . ')'; |
1181
|
|
|
} |
1182
|
|
|
if (($values = $column->getValues()) && is_array($values)) { |
1183
|
|
|
$def .= "('" . implode("', '", $values) . "')"; |
1184
|
|
|
} |
1185
|
|
|
$def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : ''; |
1186
|
|
|
$def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : ''; |
1187
|
|
|
$def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : ''; |
1188
|
|
|
$def .= $column->isNull() ? ' NULL' : ' NOT NULL'; |
1189
|
|
|
$def .= $column->isIdentity() ? ' AUTO_INCREMENT' : ''; |
1190
|
|
|
$def .= $this->getDefaultValueDefinition($column->getDefault(), $column->getType()); |
|
|
|
|
1191
|
|
|
|
1192
|
|
|
if ($column->getComment()) { |
1193
|
|
|
$def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment()); |
1194
|
|
|
} |
1195
|
|
|
|
1196
|
|
|
if ($column->getUpdate()) { |
1197
|
|
|
$def .= ' ON UPDATE ' . $column->getUpdate(); |
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
return $def; |
1201
|
|
|
} |
1202
|
|
|
|
1203
|
|
|
/** |
1204
|
|
|
* Gets the MySQL Index Definition for an Index object. |
1205
|
|
|
* |
1206
|
|
|
* @param \Phinx\Db\Table\Index $index Index |
1207
|
|
|
* |
1208
|
|
|
* @return string |
1209
|
|
|
*/ |
1210
|
|
|
protected function getIndexSqlDefinition(Index $index) |
1211
|
|
|
{ |
1212
|
|
|
$def = ''; |
1213
|
|
|
$limit = ''; |
1214
|
|
|
|
1215
|
|
|
if ($index->getType() === Index::UNIQUE) { |
1216
|
|
|
$def .= ' UNIQUE'; |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
if ($index->getType() === Index::FULLTEXT) { |
1220
|
|
|
$def .= ' FULLTEXT'; |
1221
|
|
|
} |
1222
|
|
|
|
1223
|
|
|
$def .= ' KEY'; |
1224
|
|
|
|
1225
|
|
|
if (is_string($index->getName())) { |
1226
|
|
|
$def .= ' `' . $index->getName() . '`'; |
1227
|
|
|
} |
1228
|
|
|
|
1229
|
|
|
if (!is_array($index->getLimit())) { |
1230
|
|
|
if ($index->getLimit()) { |
1231
|
|
|
$limit = '(' . $index->getLimit() . ')'; |
1232
|
|
|
} |
1233
|
|
|
$def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')'; |
1234
|
|
|
} else { |
1235
|
|
|
$columns = $index->getColumns(); |
1236
|
|
|
$limits = $index->getLimit(); |
1237
|
|
|
$def .= ' ('; |
1238
|
|
|
foreach ($columns as $column) { |
1239
|
|
|
$limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')'; |
1240
|
|
|
$def .= '`' . $column . '`' . $limit . ', '; |
1241
|
|
|
} |
1242
|
|
|
$def = rtrim($def, ', '); |
1243
|
|
|
$def .= ' )'; |
1244
|
|
|
} |
1245
|
|
|
|
1246
|
|
|
return $def; |
1247
|
|
|
} |
1248
|
|
|
|
1249
|
|
|
/** |
1250
|
|
|
* Gets the MySQL Foreign Key Definition for an ForeignKey object. |
1251
|
|
|
* |
1252
|
|
|
* @param \Phinx\Db\Table\ForeignKey $foreignKey |
1253
|
|
|
* |
1254
|
|
|
* @return string |
1255
|
|
|
*/ |
1256
|
|
View Code Duplication |
protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) |
|
|
|
|
1257
|
|
|
{ |
1258
|
|
|
$def = ''; |
1259
|
|
|
if ($foreignKey->getConstraint()) { |
1260
|
|
|
$def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); |
|
|
|
|
1261
|
|
|
} |
1262
|
|
|
$columnNames = []; |
1263
|
|
|
foreach ($foreignKey->getColumns() as $column) { |
1264
|
|
|
$columnNames[] = $this->quoteColumnName($column); |
1265
|
|
|
} |
1266
|
|
|
$def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; |
1267
|
|
|
$refColumnNames = []; |
1268
|
|
|
foreach ($foreignKey->getReferencedColumns() as $column) { |
1269
|
|
|
$refColumnNames[] = $this->quoteColumnName($column); |
1270
|
|
|
} |
1271
|
|
|
$def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; |
1272
|
|
|
if ($foreignKey->getOnDelete()) { |
1273
|
|
|
$def .= ' ON DELETE ' . $foreignKey->getOnDelete(); |
1274
|
|
|
} |
1275
|
|
|
if ($foreignKey->getOnUpdate()) { |
1276
|
|
|
$def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); |
1277
|
|
|
} |
1278
|
|
|
|
1279
|
|
|
return $def; |
1280
|
|
|
} |
1281
|
|
|
|
1282
|
|
|
/** |
1283
|
|
|
* Describes a database table. This is a MySQL adapter specific method. |
1284
|
|
|
* |
1285
|
|
|
* @param string $tableName Table name |
1286
|
|
|
* |
1287
|
|
|
* @return array |
1288
|
|
|
*/ |
1289
|
|
|
public function describeTable($tableName) |
1290
|
|
|
{ |
1291
|
|
|
$options = $this->getOptions(); |
1292
|
|
|
|
1293
|
|
|
// mysql specific |
1294
|
|
|
$sql = sprintf( |
1295
|
|
|
"SELECT * |
1296
|
|
|
FROM information_schema.tables |
1297
|
|
|
WHERE table_schema = '%s' |
1298
|
|
|
AND table_name = '%s'", |
1299
|
|
|
$options['name'], |
1300
|
|
|
$tableName |
1301
|
|
|
); |
1302
|
|
|
|
1303
|
|
|
return $this->fetchRow($sql); |
1304
|
|
|
} |
1305
|
|
|
|
1306
|
|
|
/** |
1307
|
|
|
* Returns MySQL column types (inherited and MySQL specified). |
1308
|
|
|
* |
1309
|
|
|
* @return array |
1310
|
|
|
*/ |
1311
|
|
|
public function getColumnTypes() |
1312
|
|
|
{ |
1313
|
|
|
return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']); |
1314
|
|
|
} |
1315
|
|
|
|
1316
|
|
|
/** |
1317
|
|
|
* @inheritDoc |
1318
|
|
|
*/ |
1319
|
|
View Code Duplication |
public function getDecoratedConnection() |
|
|
|
|
1320
|
|
|
{ |
1321
|
|
|
$options = $this->getOptions(); |
1322
|
|
|
$options = [ |
1323
|
|
|
'username' => $options['user'], |
1324
|
|
|
'password' => $options['pass'], |
1325
|
|
|
'database' => $options['name'], |
1326
|
|
|
'quoteIdentifiers' => true, |
1327
|
|
|
] + $options; |
1328
|
|
|
|
1329
|
|
|
$driver = new MysqlDriver($options); |
1330
|
|
|
$driver->setConnection($this->connection); |
|
|
|
|
1331
|
|
|
|
1332
|
|
|
return new Connection(['driver' => $driver] + $options); |
1333
|
|
|
} |
1334
|
|
|
} |
1335
|
|
|
|
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.