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