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