Completed
Pull Request — master (#1393)
by
unknown
03:12
created

MysqlAdapter::getChangeTableInstructions()   F

Complexity

Conditions 20
Paths 444

Size

Total Lines 93

Duplication

Lines 5
Ratio 5.38 %

Code Coverage

Tests 48
CRAP Score 20

Importance

Changes 0
Metric Value
dl 5
loc 93
ccs 48
cts 48
cp 1
rs 0.7722
c 0
b 0
f 0
cc 20
nc 444
nop 2
crap 20

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
48
    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true];
49
50
    const TEXT_TINY = 255;
51
    const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
52
    const TEXT_REGULAR = 65535;
53
    const TEXT_MEDIUM = 16777215;
54
    const TEXT_LONG = 4294967295;
55
56
    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
57
    const BLOB_TINY = 255;
58
    const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
59
    const BLOB_REGULAR = 65535;
60
    const BLOB_MEDIUM = 16777215;
61
    const BLOB_LONG = 4294967295;
62
63
    const INT_TINY = 255;
64
    const INT_SMALL = 65535;
65
    const INT_MEDIUM = 16777215;
66
    const INT_REGULAR = 4294967295;
67
    const INT_BIG = 18446744073709551615;
68
69
    const BIT = 64;
70 80
71
    const TYPE_YEAR = 'year';
72 80
73 80
    /**
74
     * {@inheritdoc}
75
     */
76
    public function connect()
77
    {
78
        if ($this->connection === null) {
79 80
            if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
80 80
                // @codeCoverageIgnoreStart
81
                throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
82 80
                // @codeCoverageIgnoreEnd
83
            }
84 80
85
            $db = null;
86
            $options = $this->getOptions();
87
88
            $dsn = 'mysql:';
89 80
90 80
            if (!empty($options['unix_socket'])) {
91 80
                // use socket connection
92 80
                $dsn .= 'unix_socket=' . $options['unix_socket'];
93
            } else {
94
                // use network connection
95 80
                $dsn .= 'host=' . $options['host'];
96
                if (!empty($options['port'])) {
97
                    $dsn .= ';port=' . $options['port'];
98 80
                }
99
            }
100
101
            $dsn .= ';dbname=' . $options['name'];
102 80
103
            // charset support
104
            if (!empty($options['charset'])) {
105
                $dsn .= ';charset=' . $options['charset'];
106 80
            }
107 80
108
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
109
110 80
            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
111
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
112 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...
113 80
                if (strpos($key, 'mysql_attr_') === 0) {
114 80
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
115 1
                }
116 1
            }
117 1
118 1
            try {
119
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
120
            } catch (\PDOException $exception) {
121 80
                throw new \InvalidArgumentException(sprintf(
122 80
                    'There was a problem connecting to the database: %s',
123 80
                    $exception->getMessage()
124
                ));
125
            }
126
127
            $this->setConnection($db);
128 81
        }
129
    }
130 81
131 81
    /**
132
     * {@inheritdoc}
133
     */
134
    public function disconnect()
135
    {
136 6
        $this->connection = null;
137
    }
138 6
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function hasTransactions()
143
    {
144 6
        return true;
145
    }
146 6
147 6
    /**
148
     * {@inheritdoc}
149
     */
150
    public function beginTransaction()
151
    {
152 6
        $this->execute('START TRANSACTION');
153
    }
154 6
155 6
    /**
156
     * {@inheritdoc}
157
     */
158
    public function commitTransaction()
159
    {
160 1
        $this->execute('COMMIT');
161
    }
162 1
163 1
    /**
164
     * {@inheritdoc}
165
     */
166
    public function rollbackTransaction()
167
    {
168 112
        $this->execute('ROLLBACK');
169
    }
170 112
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function quoteTableName($tableName)
175
    {
176 112
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
177
    }
178 112
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function quoteColumnName($columnName)
183
    {
184 82
        return '`' . str_replace('`', '``', $columnName) . '`';
185
    }
186 82
187
    /**
188 82
     * {@inheritdoc}
189
     */
190 View Code Duplication
    public function hasTable($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...
191 82
    {
192 82
        $options = $this->getOptions();
193
194 82
        $exists = $this->fetchRow(sprintf(
195
            "SELECT TABLE_NAME
196 82
            FROM INFORMATION_SCHEMA.TABLES
197
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
198
            $options['name'],
199
            $tableName
200
        ));
201
202 82
        return !empty($exists);
203
    }
204
205
    /**
206 82
     * {@inheritdoc}
207
     */
208 82
    public function createTable(Table $table, array $columns = [], array $indexes = [])
209 82
    {
210
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
211
        $defaultOptions = [
212 82
            'engine' => 'InnoDB',
213 82
            'collation' => 'utf8_general_ci'
214 68
        ];
215 68
216 68
        $options = array_merge(
217 68
            $defaultOptions,
218 68
            array_intersect_key($this->getOptions(), $defaultOptions),
219
            $table->getOptions()
220 68
        );
221 68
222 82
        // Add the default primary key
223
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
224 2
            $column = new Column();
225 2
            $column->setName('id')
226 2
                   ->setType('integer')
227 2
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
228
                   ->setIdentity(true);
229 2
230 2
            array_unshift($columns, $column);
231 2
            $options['primary_key'] = 'id';
232
        } elseif (isset($options['id']) && is_string($options['id'])) {
233
            // Handle id => "field_name" to support AUTO_INCREMENT
234
            $column = new Column();
235
            $column->setName($options['id'])
236 82
                   ->setType('integer')
237 82
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
238 82
                   ->setIdentity(true);
239 82
240
            array_unshift($columns, $column);
241
            $options['primary_key'] = $options['id'];
242 82
        }
243 82
244 82
        // TODO - process table options like collation etc
245 82
246 82
        // process table engine (default to InnoDB)
247
        $optionsStr = 'ENGINE = InnoDB';
248
        if (isset($options['engine'])) {
249 82
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
250 2
        }
251 2
252
        // process table collation
253 82 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...
254 82
            $charset = explode('_', $options['collation']);
255 82
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
256 82
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
257 82
        }
258
259
        // set the table comment
260 82
        if (isset($options['comment'])) {
261 82
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
262 82
        }
263 82
264 81
        // set the table row format
265 82
        if (isset($options['row_format'])) {
266
            $optionsStr .= sprintf(" ROW_FORMAT=%s ", $options['row_format']);
267
        }
268 2
269 2
        $sql = 'CREATE TABLE ';
270 2
        $sql .= $this->quoteTableName($table->getName()) . ' (';
271 2
        foreach ($columns as $column) {
272 2
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
273 2
        }
274 2
275 2
        // set the primary key(s)
276 2 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...
277 2
            $sql = rtrim($sql);
278 82
            $sql .= ' PRIMARY KEY (';
279 82
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
280 1
                $sql .= $this->quoteColumnName($options['primary_key']);
281
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
282
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
283
            }
284 82
            $sql .= ')';
285 82
        } else {
286 10
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
287 82
        }
288
289
        // set the indexes
290 82
        foreach ($indexes as $index) {
291 82
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
292 2
        }
293 82
294
        $sql .= ') ' . $optionsStr;
295 82
        $sql = rtrim($sql) . ';';
296 82
297
        // execute the sql
298
        $this->execute($sql);
299 82
    }
300 82
301
    /**
302
     * {@inheritdoc}
303
     */
304
    protected function getChangeTableInstructions(Table $table, array $newOptions)
305 5
    {
306
        $instructions = new AlterInstructions();
307 5
308 5
        // Drop the existing primary key
309
        $primaryKey = $this->getPrimaryKey($table->getName());
310
        if ((isset($newOptions['id']) || isset($newOptions['primary_key']))
311
            && !empty($primaryKey['columns'])) {
312
            foreach ($primaryKey['columns'] as $column) {
313 5
                $sql = sprintf(
314
                    'MODIFY %s INT NOT NULL',
315 5
                    $this->quoteColumnName($column)
316 5
                );
317
                $instructions->addAlter($sql);
318
            }
319
            $instructions->addAlter('DROP PRIMARY KEY');
320
        }
321 1
322
        // Set the default primary key and add associated column
323 1
        if (isset($newOptions['id']) && $newOptions['id'] !== false) {
324 1
            if ($newOptions['id'] === true) {
325 1
                $newOptions['primary_key'] = 'id';
326 1
            } elseif (is_string($newOptions['id'])) {
327
                // Handle id => "field_name" to support AUTO_INCREMENT
328 1
                $newOptions['primary_key'] = $newOptions['id'];
329 1
            } else {
330
                throw new \InvalidArgumentException(sprintf(
331
                    "Invalid value for option 'id': %s",
332
                    json_encode($newOptions['id'])
333
                ));
334 12
            }
335
336 12
            if ($this->hasColumn($table->getName(), $newOptions['primary_key'])) {
337 12
                throw new \RuntimeException(sprintf(
338 12
                    "Tried to create primary key column %s for table %s, but that column already exists",
339 12
                    $this->quoteColumnName($newOptions['primary_key']),
340
                    $this->quoteTableName($table->getName())
341 12
                ));
342 12
            }
343 12
344 12
            $column = new Column();
345 12
            $column
346 12
                ->setName($newOptions['primary_key'])
347
                ->setType('integer')
348 12
                ->setSigned(isset($newOptions['signed']) ? $newOptions['signed'] : true)
349 12
                ->setIdentity(true);
350 12
            $instructions->merge($this->getAddColumnInstructions($table, $column));
351
        }
352 12
353 3
        // Add the primary key(s)
354 3
        if (isset($newOptions['primary_key']) && $newOptions['primary_key'] !== false) {
355
            $sql = 'ADD PRIMARY KEY (';
356 12
            if (is_string($newOptions['primary_key'])) { // handle primary_key => 'id'
357 12
                $sql .= $this->quoteColumnName($newOptions['primary_key']);
358
            } elseif (is_array($newOptions['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
359 12
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newOptions['primary_key']));
360
            } else {
361
                throw new \InvalidArgumentException(sprintf(
362
                    "Invalid value for option 'primary_key': %s",
363
                    json_encode($newOptions['primary_key'])
364
                ));
365 79
            }
366
            $sql .= ')';
367 79
            $instructions->addAlter($sql);
368 79
        }
369 79
370 77
        // process table engine (default to InnoDB)
371
        $optionsStr = 'ENGINE = InnoDB';
372 77
        if (isset($newOptions['engine'])) {
373
            $optionsStr = sprintf('ENGINE = %s', $newOptions['engine']);
374 21
        }
375
        // process table collation
376 View Code Duplication
        if (isset($newOptions['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...
377
            $charset = explode('_', $newOptions['collation']);
378
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
379
            $optionsStr .= sprintf(' COLLATE %s', $newOptions['collation']);
380
        }
381
        // set the table comment
382
        if (array_key_exists('comment', $newOptions)) {
383 95
            // passing 'null' is to remove table comment
384
            $newComment = ($newOptions['comment'] !== null)
385 95
                ? $newOptions['comment']
386 10
                : '';
387 95
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($newComment));
388 79
        }
389 79
        // set the table row format
390 95
        if (isset($newOptions['row_format'])) {
391
            $optionsStr .= sprintf(" ROW_FORMAT=%s ", $newOptions['row_format']);
392
        }
393
        $instructions->addAlter($optionsStr);
394
395
        return $instructions;
396 18
    }
397
398 18
    /**
399 18
     * {@inheritdoc}
400 18
     */
401 18 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...
402 18
    {
403 18
        $sql = sprintf(
404
            'RENAME TABLE %s TO %s',
405 18
            $this->quoteTableName($tableName),
406 2
            $this->quoteTableName($newTableName)
407 2
        );
408
409 18
        return new AlterInstructions([], [$sql]);
410 18
    }
411
412
    /**
413
     * {@inheritdoc}
414
     */
415 7
    protected function getDropTableInstructions($tableName)
416
    {
417 7
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
418 7
419 7
        return new AlterInstructions([], [$sql]);
420 5
    }
421 5
422 5
    /**
423 1
     * {@inheritdoc}
424 1
     */
425 5
    public function truncateTable($tableName)
426
    {
427 5
        $sql = sprintf(
428 5
            'TRUNCATE TABLE %s',
429 5
            $this->quoteTableName($tableName)
430 5
        );
431 5
432 5
        $this->execute($sql);
433
    }
434 5
435 5
    /**
436 5
     * {@inheritdoc}
437
     */
438 6
    public function getColumns($tableName)
439
    {
440 2
        $columns = [];
441
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
442
        foreach ($rows as $columnInfo) {
443 2
            $phinxType = $this->getPhinxType($columnInfo['Type']);
444
445
            $column = new Column();
446
            $column->setName($columnInfo['Field'])
447
                   ->setNull($columnInfo['Null'] !== 'NO')
448
                   ->setDefault($columnInfo['Default'])
449 5
                   ->setType($phinxType['name'])
450
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
451 5
                   ->setLimit($phinxType['limit']);
452 5
453 5
            if ($columnInfo['Extra'] === 'auto_increment') {
454 5
                $column->setIdentity(true);
455 5
            }
456 5
457 5
            if (isset($phinxType['values'])) {
458 5
                $column->setValues($phinxType['values']);
459
            }
460 5
461 5
            $columns[] = $column;
462 5
        }
463
464
        return $columns;
465
    }
466
467 5
    /**
468
     * {@inheritdoc}
469 5
     */
470 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...
471 5
    {
472 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
473 5
        foreach ($rows as $column) {
474 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
475 5
                return true;
476 5
            }
477
        }
478
479
        return false;
480
    }
481
482
    /**
483
     * {@inheritdoc}
484 19
     */
485
    protected function getAddColumnInstructions(Table $table, Column $column)
486 19
    {
487 19
        $alter = sprintf(
488 19
            'ADD %s %s',
489 18
            $this->quoteColumnName($column->getName()),
490 18
            $this->getColumnSqlDefinition($column)
491 18
        );
492 18
493 19
        if ($column->getAfter()) {
494 19
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
495
        }
496
497
        return new AlterInstructions([$alter]);
498
    }
499
500 14
    /**
501
     * {@inheritdoc}
502 14
     */
503 6
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
504 6
    {
505
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
506 14
507 14
        foreach ($rows as $row) {
508
            if (strcasecmp($row['Field'], $columnName) === 0) {
509 14
                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
510 14
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
511 12
                $extra = ' ' . strtoupper($row['Extra']);
512
                if (!is_null($row['Default'])) {
513 13
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
514
                }
515 11
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
516
517
                $alter = sprintf(
518
                    'CHANGE COLUMN %s %s %s',
519
                    $this->quoteColumnName($columnName),
520
                    $this->quoteColumnName($newColumnName),
521 1
                    $definition
522
                );
523 1
524
                return new AlterInstructions([$alter]);
525 1
            }
526 1
        }
527 1
528
        throw new \InvalidArgumentException(sprintf(
529 1
            'The specified column doesn\'t exist: ' .
530
            $columnName
531
        ));
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537 4
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
538
    {
539 4
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
540 4
        $alter = sprintf(
541 4
            'CHANGE %s %s %s%s',
542 4
            $this->quoteColumnName($columnName),
543 4
            $this->quoteColumnName($newColumn->getName()),
544 4
            $this->getColumnSqlDefinition($newColumn),
545 4
            $after
546 4
        );
547
548
        return new AlterInstructions([$alter]);
549
    }
550
551 3
    /**
552
     * {@inheritdoc}
553 3
     */
554 2
    protected function getDropColumnInstructions($tableName, $columnName)
555 2
    {
556
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
557 3
558 3
        return new AlterInstructions([$alter]);
559
    }
560 3
561 3
    /**
562 3
     * Get an array of indexes from a particular table.
563 3
     *
564 3
     * @param string $tableName Table Name
565 3
     * @return array
566 3
     */
567 3
    protected function getIndexes($tableName)
568 3
    {
569 3
        $indexes = [];
570
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
571 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...
572 1
            if (!isset($indexes[$row['Key_name']])) {
573
                $indexes[$row['Key_name']] = ['columns' => []];
574
            }
575
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
576
        }
577 2
578
        return $indexes;
579 2
    }
580
581 2
    /**
582
     * {@inheritdoc}
583 2
     */
584 2 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...
585 2
    {
586 2
        if (is_string($columns)) {
587 2
            $columns = [$columns]; // str to array
588 2
        }
589 2
590 2
        $columns = array_map('strtolower', $columns);
591 2
        $indexes = $this->getIndexes($tableName);
592
593 2
        foreach ($indexes as $index) {
594
            if ($columns == $index['columns']) {
595
                return true;
596
            }
597
        }
598
599 21
        return false;
600
    }
601 21
602 5
    /**
603 5
     * {@inheritdoc}
604 21
     */
605 21 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...
606 6
    {
607 4
        $indexes = $this->getIndexes($tableName);
608
609 4
        foreach ($indexes as $name => $index) {
610
            if ($name === $indexName) {
611 15
                return true;
612 12
            }
613 10
        }
614
615 11
        return false;
616 11
    }
617
618
    /**
619
     * {@inheritdoc}
620
     */
621
    protected function getAddIndexInstructions(Table $table, Index $index)
622
    {
623
        $alter = sprintf(
624
            'ADD %s',
625
            $this->getIndexSqlDefinition($index)
626 22
        );
627
628 22
        return new AlterInstructions([$alter]);
629 22
    }
630
631
    /**
632
     * {@inheritdoc}
633
     */
634
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
635
    {
636
        if (is_string($columns)) {
637
            $columns = [$columns]; // str to array
638
        }
639
640 22
        $indexes = $this->getIndexes($tableName);
641
        $columns = array_map('strtolower', $columns);
642 22
643 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...
644 19
            if ($columns == $index['columns']) {
645 19
                return new AlterInstructions([sprintf(
646 19
                    'DROP INDEX %s',
647 19
                    $this->quoteColumnName($indexName)
648 22
                )]);
649 22
            }
650
        }
651
652
        throw new \InvalidArgumentException(sprintf(
653
            "The specified index on columns '%s' does not exist",
654
            implode(',', $columns)
655 15
        ));
656
    }
657 15
658 15
    /**
659 15
     * {@inheritdoc}
660 15
     */
661 15
    protected function getDropIndexByNameInstructions($tableName, $indexName)
662 15
    {
663 15
664 15
        $indexes = $this->getIndexes($tableName);
665
666 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...
667
            if ($name === $indexName) {
668
                return new AlterInstructions([sprintf(
669 8
                    'DROP INDEX %s',
670
                    $this->quoteColumnName($indexName)
671 8
                )]);
672 3
            }
673 3
        }
674
675
        throw new \InvalidArgumentException(sprintf(
676 8
            "The specified index name '%s' does not exist",
677 8
            $indexName
678 8
        ));
679 8
    }
680 8
681
    /**
682 8
     * {@inheritdoc}
683 8
     */
684 8 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...
685
    {
686 7
        $primaryKey = $this->getPrimaryKey($tableName);
687 7
688
        if (empty($primaryKey['constraint'])) {
689
            return false;
690
        }
691
692
        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...
693
            return ($primaryKey['constraint'] === $constraint);
694
        } else {
695 7
            if (is_string($columns)) {
696 7
                $columns = [$columns]; // str to array
697
            }
698 7
            $missingColumns = array_diff($columns, $primaryKey['columns']);
699 7
700 7
            return empty($missingColumns);
701 7
        }
702 7
    }
703
704 7
    /**
705
     * Get the primary key from a particular table.
706
     *
707
     * @param string $tableName Table Name
708
     * @return array
709 96
     */
710 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...
711
    {
712 96
        $rows = $this->fetchAll(sprintf(
713 87
            "SELECT
714
                k.constraint_name,
715 96
                k.column_name
716 4
            FROM information_schema.table_constraints t
717
            JOIN information_schema.key_column_usage k
718 96
                USING(constraint_name,table_name)
719 9
            WHERE t.constraint_type='PRIMARY KEY'
720
                AND t.table_name='%s'",
721
            $tableName
722 6
        ));
723 6
724 6
        $primaryKey = [
725 6
            'columns' => [],
726 6
        ];
727 6
        foreach ($rows as $row) {
728 6
            $primaryKey['constraint'] = $row['constraint_name'];
729 6
            $primaryKey['columns'][] = $row['column_name'];
730
        }
731 5
732
        return $primaryKey;
733 5
    }
734
735 95
    /**
736 5
     * {@inheritdoc}
737
     */
738 95 View Code Duplication
    public function hasForeignKey($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...
739 3
    {
740
        if (is_string($columns)) {
741 95
            $columns = [$columns]; // str to array
742 1
        }
743
        $foreignKeys = $this->getForeignKeys($tableName);
744
        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...
745 1
            if (isset($foreignKeys[$constraint])) {
746 1
                return !empty($foreignKeys[$constraint]);
747 1
            }
748 1
749 1
            return false;
750 1
        } else {
751 1
            foreach ($foreignKeys as $key) {
752 1
                if ($columns == $key['columns']) {
753
                    return true;
754 1
                }
755
            }
756 1
757
            return false;
758 95
        }
759 82
    }
760
761
    /**
762 6
     * Get an array of foreign keys from a particular table.
763 6
     *
764 6
     * @param string $tableName Table Name
765 6
     * @return array
766 6
     */
767 6 View Code Duplication
    protected function getForeignKeys($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...
768
    {
769 6
        $foreignKeys = [];
770 6
        $rows = $this->fetchAll(sprintf(
771 6
            "SELECT
772 6
              CONSTRAINT_NAME,
773 6
              TABLE_NAME,
774 6
              COLUMN_NAME,
775 6
              REFERENCED_TABLE_NAME,
776 2
              REFERENCED_COLUMN_NAME
777 2
            FROM information_schema.KEY_COLUMN_USAGE
778 6
            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
779
              AND REFERENCED_TABLE_NAME IS NOT NULL
780 5
              AND TABLE_NAME = '%s'
781 82
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
782 76
            $tableName
783 76
        ));
784 82
        foreach ($rows as $row) {
785
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
786 86
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
787 82
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
788
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
789 86
        }
790 7
791
        return $foreignKeys;
792 84
    }
793 5
794
    /**
795 83
     * {@inheritdoc}
796 7
     */
797 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...
798 83
    {
799 80
        $alter = sprintf(
800
            'ADD %s',
801 83
            $this->getForeignKeySqlDefinition($foreignKey)
802 4
        );
803
804 83
        return new AlterInstructions([$alter]);
805 4
    }
806
807 83
    /**
808 80
     * {@inheritdoc}
809
     */
810 10
    protected function getDropForeignKeyInstructions($tableName, $constraint)
811 2
    {
812
        $alter = sprintf(
813 10
            'DROP FOREIGN KEY %s',
814 10
            $constraint
815 10
        );
816 10
817 5
        return new AlterInstructions([$alter]);
818 8
    }
819 5
820
    /**
821 6
     * {@inheritdoc}
822 4
     */
823
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
824 2
    {
825
        $instructions = new AlterInstructions();
826
827 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...
828
            $rows = $this->fetchAll(sprintf(
829
                "SELECT
830 2
                    CONSTRAINT_NAME
831
                  FROM information_schema.KEY_COLUMN_USAGE
832
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
833 2
                    AND REFERENCED_TABLE_NAME IS NOT NULL
834 2
                    AND TABLE_NAME = '%s'
835 2
                    AND COLUMN_NAME = '%s'
836
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
837
                $tableName,
838
                $column
839
            ));
840
841
            foreach ($rows as $row) {
842
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
843
            }
844
        }
845
846 17
        if (empty($instructions->getAlterParts())) {
847
            throw new \InvalidArgumentException(sprintf(
848 17
                "Not foreign key on columns '%s' exist",
849 17
                implode(',', $columns)
850 1
            ));
851
        }
852 16
853 16
        return $instructions;
854 16
    }
855 16
856 14
    /**
857 14
     * {@inheritdoc}
858 16
     */
859 4
    public function getSqlType($type, $limit = null)
860 4
    {
861 16
        switch ($type) {
862 3
            case static::PHINX_TYPE_FLOAT:
863 3
            case static::PHINX_TYPE_DECIMAL:
864 3
            case static::PHINX_TYPE_DATE:
865
            case static::PHINX_TYPE_ENUM:
866 16
            case static::PHINX_TYPE_SET:
867 6
            case static::PHINX_TYPE_JSON:
868 6
            // Geospatial database types
869 3
            case static::PHINX_TYPE_GEOMETRY:
870 3
            case static::PHINX_TYPE_POINT:
871 6
            case static::PHINX_TYPE_LINESTRING:
872 16
            case static::PHINX_TYPE_POLYGON:
873 5
                return ['name' => $type];
874 5
            case static::PHINX_TYPE_DATETIME:
875 1
            case static::PHINX_TYPE_TIMESTAMP:
876 1
            case static::PHINX_TYPE_TIME:
877 5
                return ['name' => $type, 'limit' => $limit];
878 2
            case static::PHINX_TYPE_STRING:
879 2
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
880 5
            case static::PHINX_TYPE_CHAR:
881 16
                return ['name' => 'char', 'limit' => $limit ?: 255];
882 2 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...
883 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...
884 2
                    $sizes = [
885 16
                        // Order matters! Size must always be tested from longest to shortest!
886 2
                        'longtext' => static::TEXT_LONG,
887 2
                        'mediumtext' => static::TEXT_MEDIUM,
888 2
                        'text' => static::TEXT_REGULAR,
889 16
                        'tinytext' => static::TEXT_SMALL,
890 2
                    ];
891 2
                    foreach ($sizes as $name => $length) {
892 2
                        if ($limit >= $length) {
893 16
                            return ['name' => $name];
894 15
                        }
895 15
                    }
896 12
                }
897 12
898 15
                return ['name' => 'text'];
899 11
            case static::PHINX_TYPE_BINARY:
900 6
                return ['name' => 'binary', 'limit' => $limit ?: 255];
901 4
            case static::PHINX_TYPE_VARBINARY:
902 4
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
903 6 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...
904 6
                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...
905 10
                    $sizes = [
906 2
                        // Order matters! Size must always be tested from longest to shortest!
907 2
                        'longblob' => static::BLOB_LONG,
908 10
                        'mediumblob' => static::BLOB_MEDIUM,
909 1
                        'blob' => static::BLOB_REGULAR,
910 1
                        'tinyblob' => static::BLOB_SMALL,
911 1
                    ];
912 10
                    foreach ($sizes as $name => $length) {
913 1
                        if ($limit >= $length) {
914 1
                            return ['name' => $name];
915 1
                        }
916 10
                    }
917 1
                }
918 1
919 1
                return ['name' => 'blob'];
920 10
            case static::PHINX_TYPE_BIT:
921 2
                return ['name' => 'bit', 'limit' => $limit ?: 64];
922 2
            case static::PHINX_TYPE_INTEGER:
923 2
                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...
924 9
                    $sizes = [
925 2
                        // Order matters! Size must always be tested from longest to shortest!
926 2
                        'bigint' => static::INT_BIG,
927 2
                        'int' => static::INT_REGULAR,
928 8
                        'mediumint' => static::INT_MEDIUM,
929 2
                        'smallint' => static::INT_SMALL,
930 2
                        'tinyint' => static::INT_TINY,
931 2
                    ];
932
                    $limits = [
933
                        'int' => 11,
934
                        'bigint' => 20,
935 16
                    ];
936
                    foreach ($sizes as $name => $length) {
937
                        if ($limit >= $length) {
938 15
                            $def = ['name' => $name];
939 15
                            if (isset($limits[$name])) {
940
                                $def['limit'] = $limits[$name];
941 15
                            }
942
943 15
                            return $def;
944 3
                        }
945 3
                    }
946
                } 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...
947 15
                    $limit = 11;
948
                }
949
950
                return ['name' => 'int', 'limit' => $limit];
951
            case static::PHINX_TYPE_BIG_INTEGER:
952
                return ['name' => 'bigint', 'limit' => 20];
953
            case static::PHINX_TYPE_BOOLEAN:
954 83
                return ['name' => 'tinyint', 'limit' => 1];
955
            case static::PHINX_TYPE_UUID:
956 83
                return ['name' => 'char', 'limit' => 36];
957
            case static::TYPE_YEAR:
958 83
                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...
959 1
                    $limit = 4;
960 1
                }
961 82
962
                return ['name' => 'year', 'limit' => $limit];
963 83
            default:
964
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
965
        }
966
    }
967
968 4
    /**
969
     * Returns Phinx type by SQL type
970 4
     *
971 4
     * @param string $sqlTypeDef
972 4
     * @throws \RuntimeException
973
     * @internal param string $sqlType SQL type
974 4
     * @returns string Phinx type
975 4
     */
976
    public function getPhinxType($sqlTypeDef)
977 4
    {
978 3
        $matches = [];
979 3
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
980
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
981 3
        } else {
982
            $limit = null;
983 3
            $precision = null;
984
            $type = $matches[1];
985
            if (count($matches) > 2) {
986
                $limit = $matches[3] ? (int)$matches[3] : null;
987
            }
988
            if (count($matches) > 4) {
989 81
                $precision = (int)$matches[5];
990
            }
991 81 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...
992 81
                $type = static::PHINX_TYPE_BOOLEAN;
993
                $limit = null;
994
            }
995
            switch ($type) {
996
                case 'varchar':
997
                    $type = static::PHINX_TYPE_STRING;
998
                    if ($limit === 255) {
999
                        $limit = null;
1000 89
                    }
1001
                    break;
1002 89 View Code Duplication
                case 'char':
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...
1003
                    $type = static::PHINX_TYPE_CHAR;
1004 89
                    if ($limit === 255) {
1005 89
                        $limit = null;
1006 89
                    }
1007 2
                    if ($limit === 36) {
1008 89
                        $type = static::PHINX_TYPE_UUID;
1009 86
                    }
1010 86
                    break;
1011 89
                case 'tinyint':
1012 5
                    $type = static::PHINX_TYPE_INTEGER;
1013 5
                    $limit = static::INT_TINY;
1014 89
                    break;
1015 89
                case 'smallint':
1016 89
                    $type = static::PHINX_TYPE_INTEGER;
1017 89
                    $limit = static::INT_SMALL;
1018 89
                    break;
1019 89
                case 'mediumint':
1020
                    $type = static::PHINX_TYPE_INTEGER;
1021 89
                    $limit = static::INT_MEDIUM;
1022 2
                    break;
1023 2
                case 'int':
1024
                    $type = static::PHINX_TYPE_INTEGER;
1025 89
                    if ($limit === 11) {
1026 1
                        $limit = null;
1027 1
                    }
1028
                    break;
1029 89
                case 'bigint':
1030
                    if ($limit === 20) {
1031
                        $limit = null;
1032
                    }
1033
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1034
                    break;
1035
                case 'bit':
1036
                    $type = static::PHINX_TYPE_BIT;
1037
                    if ($limit === 64) {
1038 16
                        $limit = null;
1039
                    }
1040 16
                    break;
1041 16
                case 'blob':
1042 16
                    $type = static::PHINX_TYPE_BINARY;
1043 2
                    break;
1044 2
                case 'tinyblob':
1045
                    $type = static::PHINX_TYPE_BINARY;
1046 16
                    $limit = static::BLOB_TINY;
1047 5
                    break;
1048 5
                case 'mediumblob':
1049
                    $type = static::PHINX_TYPE_BINARY;
1050 16
                    $limit = static::BLOB_MEDIUM;
1051 1
                    break;
1052 1
                case 'longblob':
1053
                    $type = static::PHINX_TYPE_BINARY;
1054 16
                    $limit = static::BLOB_LONG;
1055
                    break;
1056 16
                case 'tinytext':
1057 5
                    $type = static::PHINX_TYPE_TEXT;
1058 5
                    $limit = static::TEXT_TINY;
1059
                    break;
1060 16
                case 'mediumtext':
1061
                    $type = static::PHINX_TYPE_TEXT;
1062 16
                    $limit = static::TEXT_MEDIUM;
1063
                    break;
1064
                case 'longtext':
1065
                    $type = static::PHINX_TYPE_TEXT;
1066
                    $limit = static::TEXT_LONG;
1067
                    break;
1068
            }
1069
1070
            // Call this to check if parsed type is supported.
1071 17
            $this->getSqlType($type, $limit);
1072
1073 17
            $phinxType = [
1074 17
                'name' => $type,
1075 5
                'limit' => $limit,
1076 5
                'precision' => $precision
1077 17
            ];
1078 17
1079 17
            if (static::PHINX_TYPE_ENUM == $type) {
1080 17
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1081 17
            }
1082 17
1083 17
            return $phinxType;
1084 17
        }
1085 17
    }
1086 17
1087 17
    /**
1088 2
     * {@inheritdoc}
1089 2
     */
1090 17
    public function createDatabase($name, $options = [])
1091 2
    {
1092 2
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1093 17
1094 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...
1095
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1096
        } else {
1097
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1098
        }
1099
    }
1100
1101
    /**
1102 2
     * {@inheritdoc}
1103
     */
1104 2
    public function hasDatabase($name)
1105
    {
1106
        $rows = $this->fetchAll(
1107 2
            sprintf(
1108
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1109
                $name
1110
            )
1111 2
        );
1112 2
1113
        foreach ($rows as $row) {
1114 2
            if (!empty($row)) {
1115
                return true;
1116 2
            }
1117
        }
1118
1119
        return false;
1120
    }
1121
1122
    /**
1123 85
     * {@inheritdoc}
1124
     */
1125 85
    public function dropDatabase($name)
1126
    {
1127
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1128
    }
1129
1130
    /**
1131
     * Gets the MySQL Column Definition for a Column object.
1132
     *
1133
     * @param \Phinx\Db\Table\Column $column Column
1134
     * @return string
1135
     */
1136
    protected function getColumnSqlDefinition(Column $column)
1137
    {
1138
        if ($column->getType() instanceof Literal) {
1139
            $def = (string)$column->getType();
1140
        } else {
1141
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1142
            $def = strtoupper($sqlType['name']);
1143
        }
1144
        if ($column->getPrecision() && $column->getScale()) {
1145
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1146
        } elseif (isset($sqlType['limit'])) {
1147
            $def .= '(' . $sqlType['limit'] . ')';
1148
        }
1149 View Code Duplication
        if (($values = $column->getValues()) && is_array($values)) {
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...
1150
            $def .= "('" . implode("', '", $values) . "')";
1151
        }
1152
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1153
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1154
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1155
        $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1156
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1157
        $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...
1158
1159
        if ($column->getComment()) {
1160
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1161
        }
1162
1163
        if ($column->getUpdate()) {
1164
            $def .= ' ON UPDATE ' . $column->getUpdate();
1165
        }
1166
1167
        return $def;
1168
    }
1169
1170
    /**
1171
     * Gets the MySQL Index Definition for an Index object.
1172
     *
1173
     * @param \Phinx\Db\Table\Index $index Index
1174
     * @return string
1175
     */
1176
    protected function getIndexSqlDefinition(Index $index)
1177
    {
1178
        $def = '';
1179
        $limit = '';
1180
1181
        if ($index->getType() == Index::UNIQUE) {
1182
            $def .= ' UNIQUE';
1183
        }
1184
1185
        if ($index->getType() == Index::FULLTEXT) {
1186
            $def .= ' FULLTEXT';
1187
        }
1188
1189
        $def .= ' KEY';
1190
1191
        if (is_string($index->getName())) {
1192
            $def .= ' `' . $index->getName() . '`';
1193
        }
1194
1195
        if (!is_array($index->getLimit())) {
1196
            if ($index->getLimit()) {
1197
                $limit = '(' . $index->getLimit() . ')';
1198
            }
1199
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1200
        } else {
1201
            $columns = $index->getColumns();
1202
            $limits = $index->getLimit();
1203
            $def .= ' (';
1204
            foreach ($columns as $column) {
1205
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1206
                $def .= '`' . $column . '`' . $limit . ', ';
1207
            }
1208
            $def = rtrim($def, ', ');
1209
            $def .= ' )';
1210
        }
1211
1212
        return $def;
1213
    }
1214
1215
    /**
1216
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1217
     *
1218
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1219
     * @return string
1220
     */
1221 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...
1222
    {
1223
        $def = '';
1224
        if ($foreignKey->getConstraint()) {
1225
            $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...
1226
        }
1227
        $columnNames = [];
1228
        foreach ($foreignKey->getColumns() as $column) {
1229
            $columnNames[] = $this->quoteColumnName($column);
1230
        }
1231
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1232
        $refColumnNames = [];
1233
        foreach ($foreignKey->getReferencedColumns() as $column) {
1234
            $refColumnNames[] = $this->quoteColumnName($column);
1235
        }
1236
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1237
        if ($foreignKey->getOnDelete()) {
1238
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1239
        }
1240
        if ($foreignKey->getOnUpdate()) {
1241
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1242
        }
1243
1244
        return $def;
1245
    }
1246
1247
    /**
1248
     * Describes a database table. This is a MySQL adapter specific method.
1249
     *
1250
     * @param string $tableName Table name
1251
     * @return array
1252
     */
1253 View Code Duplication
    public function describeTable($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...
1254
    {
1255
        $options = $this->getOptions();
1256
1257
        // mysql specific
1258
        $sql = sprintf(
1259
            "SELECT *
1260
             FROM information_schema.tables
1261
             WHERE table_schema = '%s'
1262
             AND table_name = '%s'",
1263
            $options['name'],
1264
            $tableName
1265
        );
1266
1267
        return $this->fetchRow($sql);
1268
    }
1269
1270
    /**
1271
     * Returns MySQL column types (inherited and MySQL specified).
1272
     * @return array
1273
     */
1274
    public function getColumnTypes()
1275
    {
1276
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1277
    }
1278
1279
    /**
1280
     * {@inheritDoc}
1281
     *
1282
     */
1283 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...
1284
    {
1285
        $options = $this->getOptions();
1286
        $options = [
1287
            'username' => $options['user'],
1288
            'password' => $options['pass'],
1289
            'database' => $options['name'],
1290
            'quoteIdentifiers' => true,
1291
        ] + $options;
1292
1293
        $driver = new MysqlDriver($options);
1294
        if (method_exists($driver, 'setConnection')) {
1295
            $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...
1296
        } else {
1297
            $driver->connection($this->connection);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Database\Driver::connection() has been deprecated with message: 3.6.0 Use getConnection()/setConnection() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1298
        }
1299
1300
        return new Connection(['driver' => $driver] + $options);
1301
    }
1302
}
1303