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

MysqlAdapter::getPrimaryKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24

Duplication

Lines 23
Ratio 95.83 %

Code Coverage

Tests 17
CRAP Score 2.0006

Importance

Changes 0
Metric Value
dl 23
loc 24
ccs 17
cts 18
cp 0.9444
rs 9.536
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.0006
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
        $instructions = new AlterInstructions();
624
625
        if ($index->getType() == Index::FULLTEXT) {
626 22
            // Must be executed separately
627
            // SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time
628 22
            $alter = sprintf(
629 22
                'ALTER TABLE %s ADD %s',
630
                $this->quoteTableName($table->getName()),
631
                $this->getIndexSqlDefinition($index)
632
            );
633
634
            $instructions->addPostStep($alter);
635
        } else {
636
            $alter = sprintf(
637
                'ADD %s',
638
                $this->getIndexSqlDefinition($index)
639
            );
640 22
641
            $instructions->addAlter($alter);
642 22
        }
643 22
644 19
        return $instructions;
645 19
    }
646 19
647 19
    /**
648 22
     * {@inheritdoc}
649 22
     */
650
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
651
    {
652
        if (is_string($columns)) {
653
            $columns = [$columns]; // str to array
654
        }
655 15
656
        $indexes = $this->getIndexes($tableName);
657 15
        $columns = array_map('strtolower', $columns);
658 15
659 15 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...
660 15
            if ($columns == $index['columns']) {
661 15
                return new AlterInstructions([sprintf(
662 15
                    'DROP INDEX %s',
663 15
                    $this->quoteColumnName($indexName)
664 15
                )]);
665
            }
666
        }
667
668
        throw new \InvalidArgumentException(sprintf(
669 8
            "The specified index on columns '%s' does not exist",
670
            implode(',', $columns)
671 8
        ));
672 3
    }
673 3
674
    /**
675
     * {@inheritdoc}
676 8
     */
677 8
    protected function getDropIndexByNameInstructions($tableName, $indexName)
678 8
    {
679 8
680 8
        $indexes = $this->getIndexes($tableName);
681
682 8 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...
683 8
            if ($name === $indexName) {
684 8
                return new AlterInstructions([sprintf(
685
                    'DROP INDEX %s',
686 7
                    $this->quoteColumnName($indexName)
687 7
                )]);
688
            }
689
        }
690
691
        throw new \InvalidArgumentException(sprintf(
692
            "The specified index name '%s' does not exist",
693
            $indexName
694
        ));
695 7
    }
696 7
697
    /**
698 7
     * {@inheritdoc}
699 7
     */
700 7 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...
701 7
    {
702 7
        $primaryKey = $this->getPrimaryKey($tableName);
703
704 7
        if (empty($primaryKey['constraint'])) {
705
            return false;
706
        }
707
708
        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...
709 96
            return ($primaryKey['constraint'] === $constraint);
710
        } else {
711
            if (is_string($columns)) {
712 96
                $columns = [$columns]; // str to array
713 87
            }
714
            $missingColumns = array_diff($columns, $primaryKey['columns']);
715 96
716 4
            return empty($missingColumns);
717
        }
718 96
    }
719 9
720
    /**
721
     * Get the primary key from a particular table.
722 6
     *
723 6
     * @param string $tableName Table Name
724 6
     * @return array
725 6
     */
726 6 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...
727 6
    {
728 6
        $rows = $this->fetchAll(sprintf(
729 6
            "SELECT
730
                k.constraint_name,
731 5
                k.column_name
732
            FROM information_schema.table_constraints t
733 5
            JOIN information_schema.key_column_usage k
734
                USING(constraint_name,table_name)
735 95
            WHERE t.constraint_type='PRIMARY KEY'
736 5
                AND t.table_name='%s'",
737
            $tableName
738 95
        ));
739 3
740
        $primaryKey = [
741 95
            'columns' => [],
742 1
        ];
743
        foreach ($rows as $row) {
744
            $primaryKey['constraint'] = $row['constraint_name'];
745 1
            $primaryKey['columns'][] = $row['column_name'];
746 1
        }
747 1
748 1
        return $primaryKey;
749 1
    }
750 1
751 1
    /**
752 1
     * {@inheritdoc}
753
     */
754 1 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...
755
    {
756 1
        if (is_string($columns)) {
757
            $columns = [$columns]; // str to array
758 95
        }
759 82
        $foreignKeys = $this->getForeignKeys($tableName);
760
        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...
761
            if (isset($foreignKeys[$constraint])) {
762 6
                return !empty($foreignKeys[$constraint]);
763 6
            }
764 6
765 6
            return false;
766 6
        } else {
767 6
            foreach ($foreignKeys as $key) {
768
                if ($columns == $key['columns']) {
769 6
                    return true;
770 6
                }
771 6
            }
772 6
773 6
            return false;
774 6
        }
775 6
    }
776 2
777 2
    /**
778 6
     * Get an array of foreign keys from a particular table.
779
     *
780 5
     * @param string $tableName Table Name
781 82
     * @return array
782 76
     */
783 76 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...
784 82
    {
785
        $foreignKeys = [];
786 86
        $rows = $this->fetchAll(sprintf(
787 82
            "SELECT
788
              CONSTRAINT_NAME,
789 86
              TABLE_NAME,
790 7
              COLUMN_NAME,
791
              REFERENCED_TABLE_NAME,
792 84
              REFERENCED_COLUMN_NAME
793 5
            FROM information_schema.KEY_COLUMN_USAGE
794
            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
795 83
              AND REFERENCED_TABLE_NAME IS NOT NULL
796 7
              AND TABLE_NAME = '%s'
797
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
798 83
            $tableName
799 80
        ));
800
        foreach ($rows as $row) {
801 83
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
802 4
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
803
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
804 83
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
805 4
        }
806
807 83
        return $foreignKeys;
808 80
    }
809
810 10
    /**
811 2
     * {@inheritdoc}
812
     */
813 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...
814 10
    {
815 10
        $alter = sprintf(
816 10
            'ADD %s',
817 5
            $this->getForeignKeySqlDefinition($foreignKey)
818 8
        );
819 5
820
        return new AlterInstructions([$alter]);
821 6
    }
822 4
823
    /**
824 2
     * {@inheritdoc}
825
     */
826
    protected function getDropForeignKeyInstructions($tableName, $constraint)
827
    {
828
        $alter = sprintf(
829
            'DROP FOREIGN KEY %s',
830 2
            $constraint
831
        );
832
833 2
        return new AlterInstructions([$alter]);
834 2
    }
835 2
836
    /**
837
     * {@inheritdoc}
838
     */
839
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
840
    {
841
        $instructions = new AlterInstructions();
842
843 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...
844
            $rows = $this->fetchAll(sprintf(
845
                "SELECT
846 17
                    CONSTRAINT_NAME
847
                  FROM information_schema.KEY_COLUMN_USAGE
848 17
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
849 17
                    AND REFERENCED_TABLE_NAME IS NOT NULL
850 1
                    AND TABLE_NAME = '%s'
851
                    AND COLUMN_NAME = '%s'
852 16
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
853 16
                $tableName,
854 16
                $column
855 16
            ));
856 14
857 14
            foreach ($rows as $row) {
858 16
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
859 4
            }
860 4
        }
861 16
862 3
        if (empty($instructions->getAlterParts())) {
863 3
            throw new \InvalidArgumentException(sprintf(
864 3
                "Not foreign key on columns '%s' exist",
865
                implode(',', $columns)
866 16
            ));
867 6
        }
868 6
869 3
        return $instructions;
870 3
    }
871 6
872 16
    /**
873 5
     * {@inheritdoc}
874 5
     */
875 1
    public function getSqlType($type, $limit = null)
876 1
    {
877 5
        switch ($type) {
878 2
            case static::PHINX_TYPE_FLOAT:
879 2
            case static::PHINX_TYPE_DECIMAL:
880 5
            case static::PHINX_TYPE_DATE:
881 16
            case static::PHINX_TYPE_ENUM:
882 2
            case static::PHINX_TYPE_SET:
883 2
            case static::PHINX_TYPE_JSON:
884 2
            // Geospatial database types
885 16
            case static::PHINX_TYPE_GEOMETRY:
886 2
            case static::PHINX_TYPE_POINT:
887 2
            case static::PHINX_TYPE_LINESTRING:
888 2
            case static::PHINX_TYPE_POLYGON:
889 16
                return ['name' => $type];
890 2
            case static::PHINX_TYPE_DATETIME:
891 2
            case static::PHINX_TYPE_TIMESTAMP:
892 2
            case static::PHINX_TYPE_TIME:
893 16
                return ['name' => $type, 'limit' => $limit];
894 15
            case static::PHINX_TYPE_STRING:
895 15
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
896 12
            case static::PHINX_TYPE_CHAR:
897 12
                return ['name' => 'char', 'limit' => $limit ?: 255];
898 15 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...
899 11
                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...
900 6
                    $sizes = [
901 4
                        // Order matters! Size must always be tested from longest to shortest!
902 4
                        'longtext' => static::TEXT_LONG,
903 6
                        'mediumtext' => static::TEXT_MEDIUM,
904 6
                        'text' => static::TEXT_REGULAR,
905 10
                        'tinytext' => static::TEXT_SMALL,
906 2
                    ];
907 2
                    foreach ($sizes as $name => $length) {
908 10
                        if ($limit >= $length) {
909 1
                            return ['name' => $name];
910 1
                        }
911 1
                    }
912 10
                }
913 1
914 1
                return ['name' => 'text'];
915 1
            case static::PHINX_TYPE_BINARY:
916 10
                return ['name' => 'binary', 'limit' => $limit ?: 255];
917 1
            case static::PHINX_TYPE_VARBINARY:
918 1
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
919 1 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...
920 10
                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...
921 2
                    $sizes = [
922 2
                        // Order matters! Size must always be tested from longest to shortest!
923 2
                        'longblob' => static::BLOB_LONG,
924 9
                        'mediumblob' => static::BLOB_MEDIUM,
925 2
                        'blob' => static::BLOB_REGULAR,
926 2
                        'tinyblob' => static::BLOB_SMALL,
927 2
                    ];
928 8
                    foreach ($sizes as $name => $length) {
929 2
                        if ($limit >= $length) {
930 2
                            return ['name' => $name];
931 2
                        }
932
                    }
933
                }
934
935 16
                return ['name' => 'blob'];
936
            case static::PHINX_TYPE_BIT:
937
                return ['name' => 'bit', 'limit' => $limit ?: 64];
938 15
            case static::PHINX_TYPE_INTEGER:
939 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...
940
                    $sizes = [
941 15
                        // Order matters! Size must always be tested from longest to shortest!
942
                        'bigint' => static::INT_BIG,
943 15
                        'int' => static::INT_REGULAR,
944 3
                        'mediumint' => static::INT_MEDIUM,
945 3
                        'smallint' => static::INT_SMALL,
946
                        'tinyint' => static::INT_TINY,
947 15
                    ];
948
                    $limits = [
949
                        'int' => 11,
950
                        'bigint' => 20,
951
                    ];
952
                    foreach ($sizes as $name => $length) {
953
                        if ($limit >= $length) {
954 83
                            $def = ['name' => $name];
955
                            if (isset($limits[$name])) {
956 83
                                $def['limit'] = $limits[$name];
957
                            }
958 83
959 1
                            return $def;
960 1
                        }
961 82
                    }
962
                } 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...
963 83
                    $limit = 11;
964
                }
965
966
                return ['name' => 'int', 'limit' => $limit];
967
            case static::PHINX_TYPE_BIG_INTEGER:
968 4
                return ['name' => 'bigint', 'limit' => 20];
969
            case static::PHINX_TYPE_BOOLEAN:
970 4
                return ['name' => 'tinyint', 'limit' => 1];
971 4
            case static::PHINX_TYPE_UUID:
972 4
                return ['name' => 'char', 'limit' => 36];
973
            case static::TYPE_YEAR:
974 4
                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...
975 4
                    $limit = 4;
976
                }
977 4
978 3
                return ['name' => 'year', 'limit' => $limit];
979 3
            default:
980
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
981 3
        }
982
    }
983 3
984
    /**
985
     * Returns Phinx type by SQL type
986
     *
987
     * @param string $sqlTypeDef
988
     * @throws \RuntimeException
989 81
     * @internal param string $sqlType SQL type
990
     * @returns string Phinx type
991 81
     */
992 81
    public function getPhinxType($sqlTypeDef)
993
    {
994
        $matches = [];
995
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
996
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
997
        } else {
998
            $limit = null;
999
            $precision = null;
1000 89
            $type = $matches[1];
1001
            if (count($matches) > 2) {
1002 89
                $limit = $matches[3] ? (int)$matches[3] : null;
1003
            }
1004 89
            if (count($matches) > 4) {
1005 89
                $precision = (int)$matches[5];
1006 89
            }
1007 2 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...
1008 89
                $type = static::PHINX_TYPE_BOOLEAN;
1009 86
                $limit = null;
1010 86
            }
1011 89
            switch ($type) {
1012 5
                case 'varchar':
1013 5
                    $type = static::PHINX_TYPE_STRING;
1014 89
                    if ($limit === 255) {
1015 89
                        $limit = null;
1016 89
                    }
1017 89
                    break;
1018 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...
1019 89
                    $type = static::PHINX_TYPE_CHAR;
1020
                    if ($limit === 255) {
1021 89
                        $limit = null;
1022 2
                    }
1023 2
                    if ($limit === 36) {
1024
                        $type = static::PHINX_TYPE_UUID;
1025 89
                    }
1026 1
                    break;
1027 1
                case 'tinyint':
1028
                    $type = static::PHINX_TYPE_INTEGER;
1029 89
                    $limit = static::INT_TINY;
1030
                    break;
1031
                case 'smallint':
1032
                    $type = static::PHINX_TYPE_INTEGER;
1033
                    $limit = static::INT_SMALL;
1034
                    break;
1035
                case 'mediumint':
1036
                    $type = static::PHINX_TYPE_INTEGER;
1037
                    $limit = static::INT_MEDIUM;
1038 16
                    break;
1039
                case 'int':
1040 16
                    $type = static::PHINX_TYPE_INTEGER;
1041 16
                    if ($limit === 11) {
1042 16
                        $limit = null;
1043 2
                    }
1044 2
                    break;
1045
                case 'bigint':
1046 16
                    if ($limit === 20) {
1047 5
                        $limit = null;
1048 5
                    }
1049
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1050 16
                    break;
1051 1
                case 'bit':
1052 1
                    $type = static::PHINX_TYPE_BIT;
1053
                    if ($limit === 64) {
1054 16
                        $limit = null;
1055
                    }
1056 16
                    break;
1057 5
                case 'blob':
1058 5
                    $type = static::PHINX_TYPE_BINARY;
1059
                    break;
1060 16
                case 'tinyblob':
1061
                    $type = static::PHINX_TYPE_BINARY;
1062 16
                    $limit = static::BLOB_TINY;
1063
                    break;
1064
                case 'mediumblob':
1065
                    $type = static::PHINX_TYPE_BINARY;
1066
                    $limit = static::BLOB_MEDIUM;
1067
                    break;
1068
                case 'longblob':
1069
                    $type = static::PHINX_TYPE_BINARY;
1070
                    $limit = static::BLOB_LONG;
1071 17
                    break;
1072
                case 'tinytext':
1073 17
                    $type = static::PHINX_TYPE_TEXT;
1074 17
                    $limit = static::TEXT_TINY;
1075 5
                    break;
1076 5
                case 'mediumtext':
1077 17
                    $type = static::PHINX_TYPE_TEXT;
1078 17
                    $limit = static::TEXT_MEDIUM;
1079 17
                    break;
1080 17
                case 'longtext':
1081 17
                    $type = static::PHINX_TYPE_TEXT;
1082 17
                    $limit = static::TEXT_LONG;
1083 17
                    break;
1084 17
            }
1085 17
1086 17
            // Call this to check if parsed type is supported.
1087 17
            $this->getSqlType($type, $limit);
1088 2
1089 2
            $phinxType = [
1090 17
                'name' => $type,
1091 2
                'limit' => $limit,
1092 2
                'precision' => $precision
1093 17
            ];
1094
1095
            if (static::PHINX_TYPE_ENUM == $type) {
1096
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1097
            }
1098
1099
            return $phinxType;
1100
        }
1101
    }
1102 2
1103
    /**
1104 2
     * {@inheritdoc}
1105
     */
1106
    public function createDatabase($name, $options = [])
1107 2
    {
1108
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1109
1110 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...
1111 2
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1112 2
        } else {
1113
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1114 2
        }
1115
    }
1116 2
1117
    /**
1118
     * {@inheritdoc}
1119
     */
1120
    public function hasDatabase($name)
1121
    {
1122
        $rows = $this->fetchAll(
1123 85
            sprintf(
1124
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1125 85
                $name
1126
            )
1127
        );
1128
1129
        foreach ($rows as $row) {
1130
            if (!empty($row)) {
1131
                return true;
1132
            }
1133
        }
1134
1135
        return false;
1136
    }
1137
1138
    /**
1139
     * {@inheritdoc}
1140
     */
1141
    public function dropDatabase($name)
1142
    {
1143
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1144
    }
1145
1146
    /**
1147
     * Gets the MySQL Column Definition for a Column object.
1148
     *
1149
     * @param \Phinx\Db\Table\Column $column Column
1150
     * @return string
1151
     */
1152
    protected function getColumnSqlDefinition(Column $column)
1153
    {
1154
        if ($column->getType() instanceof Literal) {
1155
            $def = (string)$column->getType();
1156
        } else {
1157
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1158
            $def = strtoupper($sqlType['name']);
1159
        }
1160
        if ($column->getPrecision() && $column->getScale()) {
1161
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1162
        } elseif (isset($sqlType['limit'])) {
1163
            $def .= '(' . $sqlType['limit'] . ')';
1164
        }
1165 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...
1166
            $def .= "('" . implode("', '", $values) . "')";
1167
        }
1168
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1169
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1170
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1171
        $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...
1172
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1173
        $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...
1174
1175
        if ($column->getComment()) {
1176
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1177
        }
1178
1179
        if ($column->getUpdate()) {
1180
            $def .= ' ON UPDATE ' . $column->getUpdate();
1181
        }
1182
1183
        return $def;
1184
    }
1185
1186
    /**
1187
     * Gets the MySQL Index Definition for an Index object.
1188
     *
1189
     * @param \Phinx\Db\Table\Index $index Index
1190
     * @return string
1191
     */
1192
    protected function getIndexSqlDefinition(Index $index)
1193
    {
1194
        $def = '';
1195
        $limit = '';
1196
1197
        if ($index->getType() == Index::UNIQUE) {
1198
            $def .= ' UNIQUE';
1199
        }
1200
1201
        if ($index->getType() == Index::FULLTEXT) {
1202
            $def .= ' FULLTEXT';
1203
        }
1204
1205
        $def .= ' KEY';
1206
1207
        if (is_string($index->getName())) {
1208
            $def .= ' `' . $index->getName() . '`';
1209
        }
1210
1211
        if (!is_array($index->getLimit())) {
1212
            if ($index->getLimit()) {
1213
                $limit = '(' . $index->getLimit() . ')';
1214
            }
1215
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1216
        } else {
1217
            $columns = $index->getColumns();
1218
            $limits = $index->getLimit();
1219
            $def .= ' (';
1220
            foreach ($columns as $column) {
1221
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1222
                $def .= '`' . $column . '`' . $limit . ', ';
1223
            }
1224
            $def = rtrim($def, ', ');
1225
            $def .= ' )';
1226
        }
1227
1228
        return $def;
1229
    }
1230
1231
    /**
1232
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1233
     *
1234
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1235
     * @return string
1236
     */
1237 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...
1238
    {
1239
        $def = '';
1240
        if ($foreignKey->getConstraint()) {
1241
            $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...
1242
        }
1243
        $columnNames = [];
1244
        foreach ($foreignKey->getColumns() as $column) {
1245
            $columnNames[] = $this->quoteColumnName($column);
1246
        }
1247
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1248
        $refColumnNames = [];
1249
        foreach ($foreignKey->getReferencedColumns() as $column) {
1250
            $refColumnNames[] = $this->quoteColumnName($column);
1251
        }
1252
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1253
        if ($foreignKey->getOnDelete()) {
1254
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1255
        }
1256
        if ($foreignKey->getOnUpdate()) {
1257
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1258
        }
1259
1260
        return $def;
1261
    }
1262
1263
    /**
1264
     * Describes a database table. This is a MySQL adapter specific method.
1265
     *
1266
     * @param string $tableName Table name
1267
     * @return array
1268
     */
1269 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...
1270
    {
1271
        $options = $this->getOptions();
1272
1273
        // mysql specific
1274
        $sql = sprintf(
1275
            "SELECT *
1276
             FROM information_schema.tables
1277
             WHERE table_schema = '%s'
1278
             AND table_name = '%s'",
1279
            $options['name'],
1280
            $tableName
1281
        );
1282
1283
        return $this->fetchRow($sql);
1284
    }
1285
1286
    /**
1287
     * Returns MySQL column types (inherited and MySQL specified).
1288
     * @return array
1289
     */
1290
    public function getColumnTypes()
1291
    {
1292
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1293
    }
1294
1295
    /**
1296
     * {@inheritDoc}
1297
     *
1298
     */
1299 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...
1300
    {
1301
        $options = $this->getOptions();
1302
        $options = [
1303
            'username' => $options['user'],
1304
            'password' => $options['pass'],
1305
            'database' => $options['name'],
1306
            'quoteIdentifiers' => true,
1307
        ] + $options;
1308
1309
        $driver = new MysqlDriver($options);
1310
        if (method_exists($driver, 'setConnection')) {
1311
            $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...
1312
        } else {
1313
            $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...
1314
        }
1315
1316
        return new Connection(['driver' => $driver] + $options);
1317
    }
1318
}
1319