Completed
Push — master ( 9c1a2f...fffa9a )
by mark
02:04 queued 11s
created

MysqlAdapter::quoteTableName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Db\Adapter;
9
10
use Cake\Database\Connection;
11
use Cake\Database\Driver\Mysql as MysqlDriver;
12
use InvalidArgumentException;
13
use PDO;
14
use Phinx\Db\Table\Column;
15
use Phinx\Db\Table\ForeignKey;
16
use Phinx\Db\Table\Index;
17
use Phinx\Db\Table\Table;
18
use Phinx\Db\Util\AlterInstructions;
19
use Phinx\Util\Literal;
20
use RuntimeException;
21
22
/**
23
 * Phinx MySQL Adapter.
24
 *
25
 * @author Rob Morgan <[email protected]>
26
 */
27
class MysqlAdapter extends PdoAdapter
28
{
29
    protected $signedColumnTypes = [
30
        'integer' => true,
31
        'smallinteger' => true,
32
        'biginteger' => true,
33
        'float' => true,
34
        'decimal' => true,
35
        'double' => true,
36
        'boolean' => true,
37
    ];
38
39
    const TEXT_TINY = 255;
40
    const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
41
    const TEXT_REGULAR = 65535;
42
    const TEXT_MEDIUM = 16777215;
43
    const TEXT_LONG = 4294967295;
44
45
    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
46
    const BLOB_TINY = 255;
47
    const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
48
    const BLOB_REGULAR = 65535;
49
    const BLOB_MEDIUM = 16777215;
50
    const BLOB_LONG = 4294967295;
51
52
    const INT_TINY = 255;
53
    const INT_SMALL = 65535;
54
    const INT_MEDIUM = 16777215;
55
    const INT_REGULAR = 4294967295;
56
    const INT_BIG = 18446744073709551615;
57
58
    const BIT = 64;
59
60
    const TYPE_YEAR = 'year';
61
62
    /**
63
     * {@inheritDoc}
64
     *
65
     * @throws \RuntimeException
66
     * @throws \InvalidArgumentException
67
     *
68
     * @return void
69
     */
70 80
    public function connect()
71
    {
72 80
        if ($this->connection === null) {
73 80
            if (!class_exists('PDO') || !in_array('mysql', PDO::getAvailableDrivers(), true)) {
74
                // @codeCoverageIgnoreStart
75
                throw new RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
76
                // @codeCoverageIgnoreEnd
77
            }
78
79 80
            $options = $this->getOptions();
80 80
81
            $dsn = 'mysql:';
82 80
83
            if (!empty($options['unix_socket'])) {
84 80
                // use socket connection
85
                $dsn .= 'unix_socket=' . $options['unix_socket'];
86
            } else {
87
                // use network connection
88
                $dsn .= 'host=' . $options['host'];
89 80
                if (!empty($options['port'])) {
90 80
                    $dsn .= ';port=' . $options['port'];
91 80
                }
92 80
            }
93
94
            $dsn .= ';dbname=' . $options['name'];
95 80
96
            // charset support
97
            if (!empty($options['charset'])) {
98 80
                $dsn .= ';charset=' . $options['charset'];
99
            }
100
101
            $driverOptions = [];
102 80
103
            // use custom data fetch mode
104 View Code Duplication
            if (!empty($options['fetch_mode'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
105
                $driverOptions[PDO::ATTR_DEFAULT_FETCH_MODE] = constant('\PDO::FETCH_' . strtoupper($options['fetch_mode']));
106 80
            }
107 80
108
            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
109
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
110 80 View Code Duplication
            foreach ($options as $key => $option) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
111
                if (strpos($key, 'mysql_attr_') === 0) {
112
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
113 80
                }
114 80
            }
115 1
116 1
            $db = $this->createPdoConnection($dsn, $options['user'], $options['pass'], $driverOptions);
117 1
118 1
            $this->setConnection($db);
119
        }
120
    }
121 80
122 80
    /**
123 80
     * {@inheritDoc}
124
     *
125
     * @return void
126
     */
127
    public function disconnect()
128 81
    {
129
        $this->connection = null;
130 81
    }
131 81
132
    /**
133
     * @inheritDoc
134
     */
135
    public function hasTransactions()
136 6
    {
137
        return true;
138 6
    }
139
140
    /**
141
     * {@inheritDoc}
142
     *
143
     * @return void
144 6
     */
145
    public function beginTransaction()
146 6
    {
147 6
        $this->execute('START TRANSACTION');
148
    }
149
150
    /**
151
     * {@inheritDoc}
152 6
     *
153
     * @return void
154 6
     */
155 6
    public function commitTransaction()
156
    {
157
        $this->execute('COMMIT');
158
    }
159
160 1
    /**
161
     * {@inheritDoc}
162 1
     *
163 1
     * @return void
164
     */
165
    public function rollbackTransaction()
166
    {
167
        $this->execute('ROLLBACK');
168 112
    }
169
170 112
    /**
171
     * @inheritDoc
172
     */
173
    public function quoteTableName($tableName)
174
    {
175
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
176 112
    }
177
178 112
    /**
179
     * @inheritDoc
180
     */
181
    public function quoteColumnName($columnName)
182
    {
183
        return '`' . str_replace('`', '``', $columnName) . '`';
184 82
    }
185
186 82
    /**
187
     * @inheritDoc
188 82
     */
189
    public function hasTable($tableName)
190
    {
191 82
        if ($this->hasCreatedTable($tableName)) {
192 82
            return true;
193
        }
194 82
195
        if (strpos($tableName, '.') !== false) {
196 82
            list($schema, $table) = explode('.', $tableName);
197
            $exists = $this->hasTableWithSchema($schema, $table);
198
            // Only break here on success, because it is possible for table names to contain a dot.
199
            if ($exists) {
200
                return true;
201
            }
202 82
        }
203
204
        $options = $this->getOptions();
205
206 82
        return $this->hasTableWithSchema($options['name'], $tableName);
207
    }
208 82
209 82
    /**
210
     * @param string $schema The table schema
211
     * @param string $tableName The table name
212 82
     *
213 82
     * @return bool
214 68
     */
215 68
    private function hasTableWithSchema($schema, $tableName)
216 68
    {
217 68
        $result = $this->fetchRow(sprintf(
218 68
            "SELECT TABLE_NAME
219
            FROM INFORMATION_SCHEMA.TABLES
220 68
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
221 68
            $schema,
222 82
            $tableName
223
        ));
224 2
225 2
        return !empty($result);
226 2
    }
227 2
    /**
228
     * {@inheritDoc}
229 2
     *
230 2
     * @return void
231 2
     */
232
    public function createTable(Table $table, array $columns = [], array $indexes = [])
233
    {
234
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
235
        $defaultOptions = [
236 82
            'engine' => 'InnoDB',
237 82
            'collation' => 'utf8_general_ci',
238 82
        ];
239 82
240
        $options = array_merge(
241
            $defaultOptions,
242 82
            array_intersect_key($this->getOptions(), $defaultOptions),
243 82
            $table->getOptions()
244 82
        );
245 82
246 82
        // Add the default primary key
247 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
248
            $options['id'] = 'id';
249 82
        }
250 2
251 2
        if (isset($options['id']) && is_string($options['id'])) {
252
            // Handle id => "field_name" to support AUTO_INCREMENT
253 82
            $column = new Column();
254 82
            $column->setName($options['id'])
255 82
                   ->setType('integer')
256 82
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
257 82
                   ->setIdentity(true);
258
259
            array_unshift($columns, $column);
260 82
            if (isset($options['primary_key']) && (array)$options['id'] !== (array)$options['primary_key']) {
261 82
                throw new InvalidArgumentException('You cannot enable an auto incrementing ID field and a primary key');
262 82
            }
263 82
            $options['primary_key'] = $options['id'];
264 81
        }
265 82
266
        // TODO - process table options like collation etc
267
268 2
        // process table engine (default to InnoDB)
269 2
        $optionsStr = 'ENGINE = InnoDB';
270 2
        if (isset($options['engine'])) {
271 2
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
272 2
        }
273 2
274 2
        // process table collation
275 2
        if (isset($options['collation'])) {
276 2
            $charset = explode('_', $options['collation']);
277 2
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
278 82
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
279 82
        }
280 1
281
        // set the table comment
282
        if (isset($options['comment'])) {
283
            $optionsStr .= sprintf(' COMMENT=%s ', $this->getConnection()->quote($options['comment']));
284 82
        }
285 82
286 10
        // set the table row format
287 82
        if (isset($options['row_format'])) {
288
            $optionsStr .= sprintf(' ROW_FORMAT=%s ', $options['row_format']);
289
        }
290 82
291 82
        $sql = 'CREATE TABLE ';
292 2
        $sql .= $this->quoteTableName($table->getName()) . ' (';
293 82
        foreach ($columns as $column) {
294
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
295 82
        }
296 82
297
        // set the primary key(s)
298 View Code Duplication
        if (isset($options['primary_key'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
299 82
            $sql = rtrim($sql);
300 82
            $sql .= ' PRIMARY KEY (';
301
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
302
                $sql .= $this->quoteColumnName($options['primary_key']);
303
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
304
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
305 5
            }
306
            $sql .= ')';
307 5
        } else {
308 5
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
309
        }
310
311
        // set the indexes
312
        foreach ($indexes as $index) {
313 5
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
314
        }
315 5
316 5
        $sql .= ') ' . $optionsStr;
317
        $sql = rtrim($sql);
318
319
        // execute the sql
320
        $this->execute($sql);
321 1
322
        $this->addCreatedTable($table->getName());
323 1
    }
324 1
325 1
    /**
326 1
     * {@inheritDoc}
327
     *
328 1
     * @throws \InvalidArgumentException
329 1
     */
330
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
331
    {
332
        $instructions = new AlterInstructions();
333
334 12
        // Drop the existing primary key
335
        $primaryKey = $this->getPrimaryKey($table->getName());
336 12
        if (!empty($primaryKey['columns'])) {
337 12
            $instructions->addAlter('DROP PRIMARY KEY');
338 12
        }
339 12
340
        // Add the primary key(s)
341 12
        if (!empty($newColumns)) {
342 12
            $sql = 'ADD PRIMARY KEY (';
343 12
            if (is_string($newColumns)) { // handle primary_key => 'id'
344 12
                $sql .= $this->quoteColumnName($newColumns);
345 12
            } elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
346 12
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
347
            } else {
348 12
                throw new InvalidArgumentException(sprintf(
349 12
                    'Invalid value for primary key: %s',
350 12
                    json_encode($newColumns)
351
                ));
352 12
            }
353 3
            $sql .= ')';
354 3
            $instructions->addAlter($sql);
355
        }
356 12
357 12
        return $instructions;
358
    }
359 12
360
    /**
361
     * @inheritDoc
362
     */
363
    protected function getChangeCommentInstructions(Table $table, $newComment)
364
    {
365 79
        $instructions = new AlterInstructions();
366
367 79
        // passing 'null' is to remove table comment
368 79
        $newComment = ($newComment !== null)
369 79
            ? $newComment
370 77
            : '';
371
        $sql = sprintf(' COMMENT=%s ', $this->getConnection()->quote($newComment));
372 77
        $instructions->addAlter($sql);
373
374 21
        return $instructions;
375
    }
376
377
    /**
378
     * @inheritDoc
379
     */
380 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
396 18
    {
397
        $this->removeCreatedTable($tableName);
398 18
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
399 18
400 18
        return new AlterInstructions([], [$sql]);
401 18
    }
402 18
403 18
    /**
404
     * {@inheritDoc}
405 18
     *
406 2
     * @return void
407 2
     */
408
    public function truncateTable($tableName)
409 18
    {
410 18
        $sql = sprintf(
411
            'TRUNCATE TABLE %s',
412
            $this->quoteTableName($tableName)
413
        );
414
415 7
        $this->execute($sql);
416
    }
417 7
418 7
    /**
419 7
     * @inheritDoc
420 5
     */
421 5
    public function getColumns($tableName)
422 5
    {
423 1
        $columns = [];
424 1
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
425 5
        foreach ($rows as $columnInfo) {
426
            $phinxType = $this->getPhinxType($columnInfo['Type']);
427 5
428 5
            $column = new Column();
429 5
            $column->setName($columnInfo['Field'])
430 5
                   ->setNull($columnInfo['Null'] !== 'NO')
431 5
                   ->setDefault($columnInfo['Default'])
432 5
                   ->setType($phinxType['name'])
433
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
434 5
                   ->setLimit($phinxType['limit'])
435 5
                   ->setScale($phinxType['scale']);
436 5
437
            if ($columnInfo['Extra'] === 'auto_increment') {
438 6
                $column->setIdentity(true);
439
            }
440 2
441
            if (isset($phinxType['values'])) {
442
                $column->setValues($phinxType['values']);
443 2
            }
444
445
            $columns[] = $column;
446
        }
447
448
        return $columns;
449 5
    }
450
451 5
    /**
452 5
     * @inheritDoc
453 5
     */
454 5 View Code Duplication
    public function hasColumn($tableName, $columnName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
455 5
    {
456 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
457 5
        foreach ($rows as $column) {
458 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
459
                return true;
460 5
            }
461 5
        }
462 5
463
        return false;
464
    }
465
466
    /**
467 5
     * @inheritDoc
468
     */
469 5
    protected function getAddColumnInstructions(Table $table, Column $column)
470 5
    {
471 5
        $alter = sprintf(
472 5
            'ADD %s %s',
473 5
            $this->quoteColumnName($column->getName()),
474 5
            $this->getColumnSqlDefinition($column)
475 5
        );
476 5
477
        if ($column->getAfter()) {
478
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
479
        }
480
481
        return new AlterInstructions([$alter]);
482
    }
483
484 19
    /**
485
     * {@inheritDoc}
486 19
     *
487 19
     * @throws \InvalidArgumentException
488 19
     */
489 18
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
490 18
    {
491 18
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
492 18
493 19
        foreach ($rows as $row) {
494 19
            if (strcasecmp($row['Field'], $columnName) === 0) {
495
                $null = ($row['Null'] === 'NO') ? 'NOT NULL' : 'NULL';
496
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
497
                $extra = ' ' . strtoupper($row['Extra']);
498
                if (($row['Default'] !== null)) {
499
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
500 14
                }
501
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
502 14
503 6
                $alter = sprintf(
504 6
                    'CHANGE COLUMN %s %s %s',
505
                    $this->quoteColumnName($columnName),
506 14
                    $this->quoteColumnName($newColumnName),
507 14
                    $definition
508
                );
509 14
510 14
                return new AlterInstructions([$alter]);
511 12
            }
512
        }
513 13
514
        throw new InvalidArgumentException(sprintf(
515 11
            "The specified column doesn't exist: " .
516
            $columnName
517
        ));
518
    }
519
520
    /**
521 1
     * @inheritDoc
522
     */
523 1
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
524
    {
525 1
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
526 1
        $alter = sprintf(
527 1
            'CHANGE %s %s %s%s',
528
            $this->quoteColumnName($columnName),
529 1
            $this->quoteColumnName($newColumn->getName()),
530
            $this->getColumnSqlDefinition($newColumn),
531
            $after
532
        );
533
534
        return new AlterInstructions([$alter]);
535
    }
536
537 4
    /**
538
     * @inheritDoc
539 4
     */
540 4
    protected function getDropColumnInstructions($tableName, $columnName)
541 4
    {
542 4
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
543 4
544 4
        return new AlterInstructions([$alter]);
545 4
    }
546 4
547
    /**
548
     * Get an array of indexes from a particular table.
549
     *
550
     * @param string $tableName Table Name
551 3
     *
552
     * @return array
553 3
     */
554 2
    protected function getIndexes($tableName)
555 2
    {
556
        $indexes = [];
557 3
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
558 3 View Code Duplication
        foreach ($rows as $row) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
559
            if (!isset($indexes[$row['Key_name']])) {
560 3
                $indexes[$row['Key_name']] = ['columns' => []];
561 3
            }
562 3
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
563 3
        }
564 3
565 3
        return $indexes;
566 3
    }
567 3
568 3
    /**
569 3
     * @inheritDoc
570
     */
571 3 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
572 1
    {
573
        if (is_string($columns)) {
574
            $columns = [$columns]; // str to array
575
        }
576
577 2
        $columns = array_map('strtolower', $columns);
578
        $indexes = $this->getIndexes($tableName);
579 2
580
        foreach ($indexes as $index) {
581 2
            if ($columns == $index['columns']) {
582
                return true;
583 2
            }
584 2
        }
585 2
586 2
        return false;
587 2
    }
588 2
589 2
    /**
590 2
     * @inheritDoc
591 2
     */
592 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
593 2
    {
594
        $indexes = $this->getIndexes($tableName);
595
596
        foreach ($indexes as $name => $index) {
597
            if ($name === $indexName) {
598
                return true;
599 21
            }
600
        }
601 21
602 5
        return false;
603 5
    }
604 21
605 21
    /**
606 6
     * @inheritDoc
607 4
     */
608
    protected function getAddIndexInstructions(Table $table, Index $index)
609 4
    {
610
        $instructions = new AlterInstructions();
611 15
612 12
        if ($index->getType() === Index::FULLTEXT) {
613 10
            // Must be executed separately
614
            // SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time
615 11
            $alter = sprintf(
616 11
                'ALTER TABLE %s ADD %s',
617
                $this->quoteTableName($table->getName()),
618
                $this->getIndexSqlDefinition($index)
619
            );
620
621
            $instructions->addPostStep($alter);
622
        } else {
623
            $alter = sprintf(
624
                'ADD %s',
625
                $this->getIndexSqlDefinition($index)
626 22
            );
627
628 22
            $instructions->addAlter($alter);
629 22
        }
630
631
        return $instructions;
632
    }
633
634
    /**
635
     * {@inheritDoc}
636
     *
637
     * @throws \InvalidArgumentException
638
     */
639
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
640 22
    {
641
        if (is_string($columns)) {
642 22
            $columns = [$columns]; // str to array
643 22
        }
644 19
645 19
        $indexes = $this->getIndexes($tableName);
646 19
        $columns = array_map('strtolower', $columns);
647 19
648 22 View Code Duplication
        foreach ($indexes as $indexName => $index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
649 22
            if ($columns == $index['columns']) {
650
                return new AlterInstructions([sprintf(
651
                    'DROP INDEX %s',
652
                    $this->quoteColumnName($indexName)
653
                )]);
654
            }
655 15
        }
656
657 15
        throw new InvalidArgumentException(sprintf(
658 15
            "The specified index on columns '%s' does not exist",
659 15
            implode(',', $columns)
660 15
        ));
661 15
    }
662 15
663 15
    /**
664 15
     * {@inheritDoc}
665
     *
666
     * @throws \InvalidArgumentException
667
     */
668
    protected function getDropIndexByNameInstructions($tableName, $indexName)
669 8
    {
670
        $indexes = $this->getIndexes($tableName);
671 8
672 3 View Code Duplication
        foreach ($indexes as $name => $index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
673 3
            if ($name === $indexName) {
674
                return new AlterInstructions([sprintf(
675
                    'DROP INDEX %s',
676 8
                    $this->quoteColumnName($indexName)
677 8
                )]);
678 8
            }
679 8
        }
680 8
681
        throw new InvalidArgumentException(sprintf(
682 8
            "The specified index name '%s' does not exist",
683 8
            $indexName
684 8
        ));
685
    }
686 7
687 7
    /**
688
     * @inheritDoc
689
     */
690 View Code Duplication
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
691
    {
692
        $primaryKey = $this->getPrimaryKey($tableName);
693
694
        if (empty($primaryKey['constraint'])) {
695 7
            return false;
696 7
        }
697
698 7
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
699 7
            return ($primaryKey['constraint'] === $constraint);
700 7
        } else {
701 7
            if (is_string($columns)) {
702 7
                $columns = [$columns]; // str to array
703
            }
704 7
            $missingColumns = array_diff($columns, $primaryKey['columns']);
705
706
            return empty($missingColumns);
707
        }
708
    }
709 96
710
    /**
711
     * Get the primary key from a particular table.
712 96
     *
713 87
     * @param string $tableName Table Name
714
     *
715 96
     * @return array
716 4
     */
717 View Code Duplication
    public function getPrimaryKey($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
718 96
    {
719 9
        $rows = $this->fetchAll(sprintf(
720
            "SELECT
721
                k.constraint_name,
722 6
                k.column_name
723 6
            FROM information_schema.table_constraints t
724 6
            JOIN information_schema.key_column_usage k
725 6
                USING(constraint_name,table_name)
726 6
            WHERE t.constraint_type='PRIMARY KEY'
727 6
                AND t.table_name='%s'",
728 6
            $tableName
729 6
        ));
730
731 5
        $primaryKey = [
732
            'columns' => [],
733 5
        ];
734
        foreach ($rows as $row) {
735 95
            $primaryKey['constraint'] = $row['constraint_name'];
736 5
            $primaryKey['columns'][] = $row['column_name'];
737
        }
738 95
739 3
        return $primaryKey;
740
    }
741 95
742 1
    /**
743
     * @inheritDoc
744
     */
745 1
    public function hasForeignKey($tableName, $columns, $constraint = null)
746 1
    {
747 1
        if (is_string($columns)) {
748 1
            $columns = [$columns]; // str to array
749 1
        }
750 1
        $foreignKeys = $this->getForeignKeys($tableName);
751 1
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
752 1
            if (isset($foreignKeys[$constraint])) {
753
                return !empty($foreignKeys[$constraint]);
754 1
            }
755
756 1
            return false;
757
        } else {
758 95
            foreach ($foreignKeys as $key) {
759 82
                if ($columns == $key['columns']) {
760
                    return true;
761
                }
762 6
            }
763 6
764 6
            return false;
765 6
        }
766 6
    }
767 6
768
    /**
769 6
     * Get an array of foreign keys from a particular table.
770 6
     *
771 6
     * @param string $tableName Table Name
772 6
     *
773 6
     * @return array
774 6
     */
775 6
    protected function getForeignKeys($tableName)
776 2
    {
777 2 View Code Duplication
        if (strpos($tableName, '.') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
778 6
            list($schema, $tableName) = explode('.', $tableName);
779
        }
780 5
781 82
        $foreignKeys = [];
782 76
        $rows = $this->fetchAll(sprintf(
783 76
            "SELECT
784 82
              CONSTRAINT_NAME,
785
              CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS TABLE_NAME,
786 86
              COLUMN_NAME,
787 82
              CONCAT(REFERENCED_TABLE_SCHEMA, '.', REFERENCED_TABLE_NAME) AS REFERENCED_TABLE_NAME,
788
              REFERENCED_COLUMN_NAME
789 86
            FROM information_schema.KEY_COLUMN_USAGE
790 7
            WHERE REFERENCED_TABLE_NAME IS NOT NULL
791
              AND TABLE_SCHEMA = %s
792 84
              AND TABLE_NAME = '%s'
793 5
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
794
            empty($schema) ? 'DATABASE()' : "'$schema'",
795 83
            $tableName
796 7
        ));
797 View Code Duplication
        foreach ($rows as $row) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
798 83
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
799 80
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
800
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
801 83
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
802 4
        }
803
804 83
        return $foreignKeys;
805 4
    }
806
807 83
    /**
808 80
     * @inheritDoc
809
     */
810 10 View Code Duplication
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
811 2
    {
812
        $alter = sprintf(
813 10
            'ADD %s',
814 10
            $this->getForeignKeySqlDefinition($foreignKey)
815 10
        );
816 10
817 5
        return new AlterInstructions([$alter]);
818 8
    }
819 5
820
    /**
821 6
     * @inheritDoc
822 4
     */
823
    protected function getDropForeignKeyInstructions($tableName, $constraint)
824 2
    {
825
        $alter = sprintf(
826
            'DROP FOREIGN KEY %s',
827
            $constraint
828
        );
829
830 2
        return new AlterInstructions([$alter]);
831
    }
832
833 2
    /**
834 2
     * {@inheritDoc}
835 2
     *
836
     * @throws \InvalidArgumentException
837
     */
838
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
839
    {
840
        $instructions = new AlterInstructions();
841
842 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
843
            $rows = $this->fetchAll(sprintf(
844
                "SELECT
845
                    CONSTRAINT_NAME
846 17
                  FROM information_schema.KEY_COLUMN_USAGE
847
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
848 17
                    AND REFERENCED_TABLE_NAME IS NOT NULL
849 17
                    AND TABLE_NAME = '%s'
850 1
                    AND COLUMN_NAME = '%s'
851
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
852 16
                $tableName,
853 16
                $column
854 16
            ));
855 16
856 14
            foreach ($rows as $row) {
857 14
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
858 16
            }
859 4
        }
860 4
861 16
        if (empty($instructions->getAlterParts())) {
862 3
            throw new InvalidArgumentException(sprintf(
863 3
                "Not foreign key on columns '%s' exist",
864 3
                implode(',', $columns)
865
            ));
866 16
        }
867 6
868 6
        return $instructions;
869 3
    }
870 3
871 6
    /**
872 16
     * {@inheritDoc}
873 5
     *
874 5
     * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException
875 1
     */
876 1
    public function getSqlType($type, $limit = null)
877 5
    {
878 2
        switch ($type) {
879 2
            case static::PHINX_TYPE_FLOAT:
880 5
            case static::PHINX_TYPE_DOUBLE:
881 16
            case static::PHINX_TYPE_DECIMAL:
882 2
            case static::PHINX_TYPE_DATE:
883 2
            case static::PHINX_TYPE_ENUM:
884 2
            case static::PHINX_TYPE_SET:
885 16
            case static::PHINX_TYPE_JSON:
886 2
            // Geospatial database types
887 2
            case static::PHINX_TYPE_GEOMETRY:
888 2
            case static::PHINX_TYPE_POINT:
889 16
            case static::PHINX_TYPE_LINESTRING:
890 2
            case static::PHINX_TYPE_POLYGON:
891 2
                return ['name' => $type];
892 2
            case static::PHINX_TYPE_DATETIME:
893 16
            case static::PHINX_TYPE_TIMESTAMP:
894 15
            case static::PHINX_TYPE_TIME:
895 15
                return ['name' => $type, 'limit' => $limit];
896 12
            case static::PHINX_TYPE_STRING:
897 12
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
898 15
            case static::PHINX_TYPE_CHAR:
899 11
                return ['name' => 'char', 'limit' => $limit ?: 255];
900 6 View Code Duplication
            case static::PHINX_TYPE_TEXT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
901 4
                if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
902 4
                    $sizes = [
903 6
                        // Order matters! Size must always be tested from longest to shortest!
904 6
                        'longtext' => static::TEXT_LONG,
905 10
                        'mediumtext' => static::TEXT_MEDIUM,
906 2
                        'text' => static::TEXT_REGULAR,
907 2
                        'tinytext' => static::TEXT_SMALL,
908 10
                    ];
909 1
                    foreach ($sizes as $name => $length) {
910 1
                        if ($limit >= $length) {
911 1
                            return ['name' => $name];
912 10
                        }
913 1
                    }
914 1
                }
915 1
916 10
                return ['name' => 'text'];
917 1
            case static::PHINX_TYPE_BINARY:
918 1
                return ['name' => 'binary', 'limit' => $limit ?: 255];
919 1
            case static::PHINX_TYPE_VARBINARY:
920 10
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
921 2 View Code Duplication
            case static::PHINX_TYPE_BLOB:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
922 2
                if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
923 2
                    $sizes = [
924 9
                        // Order matters! Size must always be tested from longest to shortest!
925 2
                        'longblob' => static::BLOB_LONG,
926 2
                        'mediumblob' => static::BLOB_MEDIUM,
927 2
                        'blob' => static::BLOB_REGULAR,
928 8
                        'tinyblob' => static::BLOB_SMALL,
929 2
                    ];
930 2
                    foreach ($sizes as $name => $length) {
931 2
                        if ($limit >= $length) {
932
                            return ['name' => $name];
933
                        }
934
                    }
935 16
                }
936
937
                return ['name' => 'blob'];
938 15
            case static::PHINX_TYPE_BIT:
939 15
                return ['name' => 'bit', 'limit' => $limit ?: 64];
940
            case static::PHINX_TYPE_SMALL_INTEGER:
941 15
                return ['name' => 'smallint', 'limit' => $limit ?: 6];
942
            case static::PHINX_TYPE_INTEGER:
943 15
                if ($limit && $limit >= static::INT_TINY) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
944 3
                    $sizes = [
945 3
                        // Order matters! Size must always be tested from longest to shortest!
946
                        'bigint' => static::INT_BIG,
947 15
                        'int' => static::INT_REGULAR,
948
                        'mediumint' => static::INT_MEDIUM,
949
                        'smallint' => static::INT_SMALL,
950
                        'tinyint' => static::INT_TINY,
951
                    ];
952
                    $limits = [
953
                        'smallint' => 6,
954 83
                        'int' => 11,
955
                        'bigint' => 20,
956 83
                    ];
957
                    foreach ($sizes as $name => $length) {
958 83
                        if ($limit >= $length) {
959 1
                            $def = ['name' => $name];
960 1
                            if (isset($limits[$name])) {
961 82
                                $def['limit'] = $limits[$name];
962
                            }
963 83
964
                            return $def;
965
                        }
966
                    }
967
                } elseif (!$limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
968 4
                    $limit = 11;
969
                }
970 4
971 4
                return ['name' => 'int', 'limit' => $limit];
972 4
            case static::PHINX_TYPE_BIG_INTEGER:
973
                return ['name' => 'bigint', 'limit' => 20];
974 4
            case static::PHINX_TYPE_BOOLEAN:
975 4
                return ['name' => 'tinyint', 'limit' => 1];
976
            case static::PHINX_TYPE_UUID:
977 4
                return ['name' => 'char', 'limit' => 36];
978 3
            case static::TYPE_YEAR:
979 3
                if (!$limit || in_array($limit, [2, 4])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
980
                    $limit = 4;
981 3
                }
982
983 3
                return ['name' => 'year', 'limit' => $limit];
984
            default:
985
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by MySQL.');
986
        }
987
    }
988
989 81
    /**
990
     * Returns Phinx type by SQL type
991 81
     *
992 81
     * @internal param string $sqlType SQL type
993
     *
994
     * @param string $sqlTypeDef
995
     *
996
     * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException
997
     *
998
     * @return array Phinx type
999
     */
1000 89
    public function getPhinxType($sqlTypeDef)
1001
    {
1002 89
        $matches = [];
1003
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
1004 89
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by MySQL.');
1005 89
        } else {
1006 89
            $limit = null;
1007 2
            $scale = null;
1008 89
            $type = $matches[1];
1009 86
            if (count($matches) > 2) {
1010 86
                $limit = $matches[3] ? (int)$matches[3] : null;
1011 89
            }
1012 5
            if (count($matches) > 4) {
1013 5
                $scale = (int)$matches[5];
1014 89
            }
1015 89 View Code Duplication
            if ($type === 'tinyint' && $limit === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1016 89
                $type = static::PHINX_TYPE_BOOLEAN;
1017 89
                $limit = null;
1018 89
            }
1019 89
            switch ($type) {
1020
                case 'varchar':
1021 89
                    $type = static::PHINX_TYPE_STRING;
1022 2
                    if ($limit === 255) {
1023 2
                        $limit = null;
1024
                    }
1025 89
                    break;
1026 1
                case 'char':
1027 1
                    $type = static::PHINX_TYPE_CHAR;
1028
                    if ($limit === 255) {
1029 89
                        $limit = null;
1030
                    }
1031
                    if ($limit === 36) {
1032
                        $type = static::PHINX_TYPE_UUID;
1033
                    }
1034
                    break;
1035
                case 'tinyint':
1036
                    $type = static::PHINX_TYPE_INTEGER;
1037
                    $limit = static::INT_TINY;
1038 16
                    break;
1039
                case 'smallint':
1040 16
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1041 16
                    $limit = static::INT_SMALL;
1042 16
                    break;
1043 2
                case 'mediumint':
1044 2
                    $type = static::PHINX_TYPE_INTEGER;
1045
                    $limit = static::INT_MEDIUM;
1046 16
                    break;
1047 5
                case 'int':
1048 5
                    $type = static::PHINX_TYPE_INTEGER;
1049
                    if ($limit === 11) {
1050 16
                        $limit = null;
1051 1
                    }
1052 1
                    break;
1053
                case 'bigint':
1054 16
                    if ($limit === 20) {
1055
                        $limit = null;
1056 16
                    }
1057 5
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1058 5
                    break;
1059
                case 'bit':
1060 16
                    $type = static::PHINX_TYPE_BIT;
1061
                    if ($limit === 64) {
1062 16
                        $limit = null;
1063
                    }
1064
                    break;
1065
                case 'blob':
1066
                    $type = static::PHINX_TYPE_BINARY;
1067
                    break;
1068
                case 'tinyblob':
1069
                    $type = static::PHINX_TYPE_BINARY;
1070
                    $limit = static::BLOB_TINY;
1071 17
                    break;
1072
                case 'mediumblob':
1073 17
                    $type = static::PHINX_TYPE_BINARY;
1074 17
                    $limit = static::BLOB_MEDIUM;
1075 5
                    break;
1076 5
                case 'longblob':
1077 17
                    $type = static::PHINX_TYPE_BINARY;
1078 17
                    $limit = static::BLOB_LONG;
1079 17
                    break;
1080 17
                case 'tinytext':
1081 17
                    $type = static::PHINX_TYPE_TEXT;
1082 17
                    $limit = static::TEXT_TINY;
1083 17
                    break;
1084 17
                case 'mediumtext':
1085 17
                    $type = static::PHINX_TYPE_TEXT;
1086 17
                    $limit = static::TEXT_MEDIUM;
1087 17
                    break;
1088 2
                case 'longtext':
1089 2
                    $type = static::PHINX_TYPE_TEXT;
1090 17
                    $limit = static::TEXT_LONG;
1091 2
                    break;
1092 2
            }
1093 17
1094
            try {
1095
                // Call this to check if parsed type is supported.
1096
                $this->getSqlType($type, $limit);
1097
            } catch (UnsupportedColumnTypeException $e) {
1098
                $type = Literal::from($type);
1099
            }
1100
1101
            $phinxType = [
1102 2
                'name' => $type,
1103
                'limit' => $limit,
1104 2
                'scale' => $scale,
1105
            ];
1106
1107 2
            if ($type === static::PHINX_TYPE_ENUM || $type === static::PHINX_TYPE_SET) {
1108
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1109
            }
1110
1111 2
            return $phinxType;
1112 2
        }
1113
    }
1114 2
1115
    /**
1116 2
     * {@inheritDoc}
1117
     *
1118
     * @return void
1119
     */
1120
    public function createDatabase($name, $options = [])
1121
    {
1122
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1123 85
1124 View Code Duplication
        if (isset($options['collation'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
1125 85
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1126
        } else {
1127
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1128
        }
1129
    }
1130
1131
    /**
1132
     * @inheritDoc
1133
     */
1134
    public function hasDatabase($name)
1135
    {
1136
        $rows = $this->fetchAll(
1137
            sprintf(
1138
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1139
                $name
1140
            )
1141
        );
1142
1143
        foreach ($rows as $row) {
1144
            if (!empty($row)) {
1145
                return true;
1146
            }
1147
        }
1148
1149
        return false;
1150
    }
1151
1152
    /**
1153
     * {@inheritDoc}
1154
     *
1155
     * @return void
1156
     */
1157
    public function dropDatabase($name)
1158
    {
1159
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1160
    }
1161
1162
    /**
1163
     * Gets the MySQL Column Definition for a Column object.
1164
     *
1165
     * @param \Phinx\Db\Table\Column $column Column
1166
     *
1167
     * @return string
1168
     */
1169
    protected function getColumnSqlDefinition(Column $column)
1170
    {
1171
        if ($column->getType() instanceof Literal) {
1172
            $def = (string)$column->getType();
1173
        } else {
1174
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1175
            $def = strtoupper($sqlType['name']);
1176
        }
1177
        if ($column->getPrecision() && $column->getScale()) {
1178
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1179
        } elseif (isset($sqlType['limit'])) {
1180
            $def .= '(' . $sqlType['limit'] . ')';
1181
        }
1182
        if (($values = $column->getValues()) && is_array($values)) {
1183
            $def .= "('" . implode("', '", $values) . "')";
1184
        }
1185
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1186
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1187
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1188
        $def .= $column->isNull() ? ' NULL' : ' NOT NULL';
1189
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1190
        $def .= $this->getDefaultValueDefinition($column->getDefault(), $column->getType());
0 ignored issues
show
Bug introduced by
It seems like $column->getType() targeting Phinx\Db\Table\Column::getType() can also be of type object<Phinx\Util\Literal>; however, Phinx\Db\Adapter\PdoAdap...efaultValueDefinition() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1191
1192
        if ($column->getComment()) {
1193
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1194
        }
1195
1196
        if ($column->getUpdate()) {
1197
            $def .= ' ON UPDATE ' . $column->getUpdate();
1198
        }
1199
1200
        return $def;
1201
    }
1202
1203
    /**
1204
     * Gets the MySQL Index Definition for an Index object.
1205
     *
1206
     * @param \Phinx\Db\Table\Index $index Index
1207
     *
1208
     * @return string
1209
     */
1210
    protected function getIndexSqlDefinition(Index $index)
1211
    {
1212
        $def = '';
1213
        $limit = '';
1214
1215
        if ($index->getType() === Index::UNIQUE) {
1216
            $def .= ' UNIQUE';
1217
        }
1218
1219
        if ($index->getType() === Index::FULLTEXT) {
1220
            $def .= ' FULLTEXT';
1221
        }
1222
1223
        $def .= ' KEY';
1224
1225
        if (is_string($index->getName())) {
1226
            $def .= ' `' . $index->getName() . '`';
1227
        }
1228
1229
        if (!is_array($index->getLimit())) {
1230
            if ($index->getLimit()) {
1231
                $limit = '(' . $index->getLimit() . ')';
1232
            }
1233
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1234
        } else {
1235
            $columns = $index->getColumns();
1236
            $limits = $index->getLimit();
1237
            $def .= ' (';
1238
            foreach ($columns as $column) {
1239
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1240
                $def .= '`' . $column . '`' . $limit . ', ';
1241
            }
1242
            $def = rtrim($def, ', ');
1243
            $def .= ' )';
1244
        }
1245
1246
        return $def;
1247
    }
1248
1249
    /**
1250
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1251
     *
1252
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1253
     *
1254
     * @return string
1255
     */
1256 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
1257
    {
1258
        $def = '';
1259
        if ($foreignKey->getConstraint()) {
1260
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
0 ignored issues
show
Bug introduced by
It seems like $foreignKey->getConstraint() targeting Phinx\Db\Table\ForeignKey::getConstraint() can also be of type boolean; however, Phinx\Db\Adapter\MysqlAdapter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1261
        }
1262
        $columnNames = [];
1263
        foreach ($foreignKey->getColumns() as $column) {
1264
            $columnNames[] = $this->quoteColumnName($column);
1265
        }
1266
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1267
        $refColumnNames = [];
1268
        foreach ($foreignKey->getReferencedColumns() as $column) {
1269
            $refColumnNames[] = $this->quoteColumnName($column);
1270
        }
1271
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1272
        if ($foreignKey->getOnDelete()) {
1273
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1274
        }
1275
        if ($foreignKey->getOnUpdate()) {
1276
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1277
        }
1278
1279
        return $def;
1280
    }
1281
1282
    /**
1283
     * Describes a database table. This is a MySQL adapter specific method.
1284
     *
1285
     * @param string $tableName Table name
1286
     *
1287
     * @return array
1288
     */
1289
    public function describeTable($tableName)
1290
    {
1291
        $options = $this->getOptions();
1292
1293
        // mysql specific
1294
        $sql = sprintf(
1295
            "SELECT *
1296
             FROM information_schema.tables
1297
             WHERE table_schema = '%s'
1298
             AND table_name = '%s'",
1299
            $options['name'],
1300
            $tableName
1301
        );
1302
1303
        return $this->fetchRow($sql);
1304
    }
1305
1306
    /**
1307
     * Returns MySQL column types (inherited and MySQL specified).
1308
     *
1309
     * @return array
1310
     */
1311
    public function getColumnTypes()
1312
    {
1313
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1314
    }
1315
1316
    /**
1317
     * @inheritDoc
1318
     */
1319 View Code Duplication
    public function getDecoratedConnection()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
1320
    {
1321
        $options = $this->getOptions();
1322
        $options = [
1323
            'username' => $options['user'],
1324
            'password' => $options['pass'],
1325
            'database' => $options['name'],
1326
            'quoteIdentifiers' => true,
1327
        ] + $options;
1328
1329
        $driver = new MysqlDriver($options);
1330
        $driver->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can be null; however, setConnection() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1331
1332
        return new Connection(['driver' => $driver] + $options);
1333
    }
1334
}
1335