Completed
Push — master ( e33650...15989c )
by José
02:42
created

MysqlAdapter::getChangePrimaryKeyInstructions()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 29
c 0
b 0
f 0
ccs 13
cts 13
cp 1
rs 9.1448
cc 5
nc 8
nop 2
crap 5
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
        if (isset($options['collation'])) {
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 getChangePrimaryKeyInstructions(Table $table, $newColumns)
305 5
    {
306
        $instructions = new AlterInstructions();
307 5
308 5
        // Drop the existing primary key
309
        $primaryKey = $this->getPrimaryKey($table->getName());
310
        if (!empty($primaryKey['columns'])) {
311
            $instructions->addAlter('DROP PRIMARY KEY');
312
        }
313 5
314
        // Add the primary key(s)
315 5
        if (!empty($newColumns)) {
316 5
            $sql = 'ADD PRIMARY KEY (';
317
            if (is_string($newColumns)) { // handle primary_key => 'id'
318
                $sql .= $this->quoteColumnName($newColumns);
319
            } elseif (is_array($newColumns)) { // 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...
320
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
321 1
            } else {
322
                throw new \InvalidArgumentException(sprintf(
323 1
                    "Invalid value for primary key: %s",
324 1
                    json_encode($newColumns)
325 1
                ));
326 1
            }
327
            $sql .= ')';
328 1
            $instructions->addAlter($sql);
329 1
        }
330
331
        return $instructions;
332
    }
333
334 12
    /**
335
     * {@inheritdoc}
336 12
     */
337 12
    protected function getChangeCommentInstructions(Table $table, $newComment)
338 12
    {
339 12
        $instructions = new AlterInstructions();
340
341 12
        // passing 'null' is to remove table comment
342 12
        $newComment = ($newComment !== null)
343 12
            ? $newComment
344 12
            : '';
345 12
        $sql = sprintf(" COMMENT=%s ", $this->getConnection()->quote($newComment));
346 12
        $instructions->addAlter($sql);
347
348 12
        return $instructions;
349 12
    }
350 12
351
    /**
352 12
     * {@inheritdoc}
353 3
     */
354 3 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...
355
    {
356 12
        $sql = sprintf(
357 12
            'RENAME TABLE %s TO %s',
358
            $this->quoteTableName($tableName),
359 12
            $this->quoteTableName($newTableName)
360
        );
361
362
        return new AlterInstructions([], [$sql]);
363
    }
364
365 79
    /**
366
     * {@inheritdoc}
367 79
     */
368 79
    protected function getDropTableInstructions($tableName)
369 79
    {
370 77
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
371
372 77
        return new AlterInstructions([], [$sql]);
373
    }
374 21
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function truncateTable($tableName)
379
    {
380
        $sql = sprintf(
381
            'TRUNCATE TABLE %s',
382
            $this->quoteTableName($tableName)
383 95
        );
384
385 95
        $this->execute($sql);
386 10
    }
387 95
388 79
    /**
389 79
     * {@inheritdoc}
390 95
     */
391
    public function getColumns($tableName)
392
    {
393
        $columns = [];
394
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
395
        foreach ($rows as $columnInfo) {
396 18
            $phinxType = $this->getPhinxType($columnInfo['Type']);
397
398 18
            $column = new Column();
399 18
            $column->setName($columnInfo['Field'])
400 18
                   ->setNull($columnInfo['Null'] !== 'NO')
401 18
                   ->setDefault($columnInfo['Default'])
402 18
                   ->setType($phinxType['name'])
403 18
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
404
                   ->setLimit($phinxType['limit']);
405 18
406 2
            if ($columnInfo['Extra'] === 'auto_increment') {
407 2
                $column->setIdentity(true);
408
            }
409 18
410 18
            if (isset($phinxType['values'])) {
411
                $column->setValues($phinxType['values']);
412
            }
413
414
            $columns[] = $column;
415 7
        }
416
417 7
        return $columns;
418 7
    }
419 7
420 5
    /**
421 5
     * {@inheritdoc}
422 5
     */
423 1 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...
424 1
    {
425 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
426
        foreach ($rows as $column) {
427 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
428 5
                return true;
429 5
            }
430 5
        }
431 5
432 5
        return false;
433
    }
434 5
435 5
    /**
436 5
     * {@inheritdoc}
437
     */
438 6
    protected function getAddColumnInstructions(Table $table, Column $column)
439
    {
440 2
        $alter = sprintf(
441
            'ADD %s %s',
442
            $this->quoteColumnName($column->getName()),
443 2
            $this->getColumnSqlDefinition($column)
444
        );
445
446
        if ($column->getAfter()) {
447
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
448
        }
449 5
450
        return new AlterInstructions([$alter]);
451 5
    }
452 5
453 5
    /**
454 5
     * {@inheritdoc}
455 5
     */
456 5
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
457 5
    {
458 5
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
459
460 5
        foreach ($rows as $row) {
461 5
            if (strcasecmp($row['Field'], $columnName) === 0) {
462 5
                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
463
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
464
                $extra = ' ' . strtoupper($row['Extra']);
465
                if (!is_null($row['Default'])) {
466
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
467 5
                }
468
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
469 5
470 5
                $alter = sprintf(
471 5
                    'CHANGE COLUMN %s %s %s',
472 5
                    $this->quoteColumnName($columnName),
473 5
                    $this->quoteColumnName($newColumnName),
474 5
                    $definition
475 5
                );
476 5
477
                return new AlterInstructions([$alter]);
478
            }
479
        }
480
481
        throw new \InvalidArgumentException(sprintf(
482
            'The specified column doesn\'t exist: ' .
483
            $columnName
484 19
        ));
485
    }
486 19
487 19
    /**
488 19
     * {@inheritdoc}
489 18
     */
490 18
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
491 18
    {
492 18
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
493 19
        $alter = sprintf(
494 19
            'CHANGE %s %s %s%s',
495
            $this->quoteColumnName($columnName),
496
            $this->quoteColumnName($newColumn->getName()),
497
            $this->getColumnSqlDefinition($newColumn),
498
            $after
499
        );
500 14
501
        return new AlterInstructions([$alter]);
502 14
    }
503 6
504 6
    /**
505
     * {@inheritdoc}
506 14
     */
507 14
    protected function getDropColumnInstructions($tableName, $columnName)
508
    {
509 14
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
510 14
511 12
        return new AlterInstructions([$alter]);
512
    }
513 13
514
    /**
515 11
     * Get an array of indexes from a particular table.
516
     *
517
     * @param string $tableName Table Name
518
     * @return array
519
     */
520
    protected function getIndexes($tableName)
521 1
    {
522
        $indexes = [];
523 1
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
524 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...
525 1
            if (!isset($indexes[$row['Key_name']])) {
526 1
                $indexes[$row['Key_name']] = ['columns' => []];
527 1
            }
528
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
529 1
        }
530
531
        return $indexes;
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537 4 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...
538
    {
539 4
        if (is_string($columns)) {
540 4
            $columns = [$columns]; // str to array
541 4
        }
542 4
543 4
        $columns = array_map('strtolower', $columns);
544 4
        $indexes = $this->getIndexes($tableName);
545 4
546 4
        foreach ($indexes as $index) {
547
            if ($columns == $index['columns']) {
548
                return true;
549
            }
550
        }
551 3
552
        return false;
553 3
    }
554 2
555 2
    /**
556
     * {@inheritdoc}
557 3
     */
558 3 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...
559
    {
560 3
        $indexes = $this->getIndexes($tableName);
561 3
562 3
        foreach ($indexes as $name => $index) {
563 3
            if ($name === $indexName) {
564 3
                return true;
565 3
            }
566 3
        }
567 3
568 3
        return false;
569 3
    }
570
571 3
    /**
572 1
     * {@inheritdoc}
573
     */
574
    protected function getAddIndexInstructions(Table $table, Index $index)
575
    {
576
        $instructions = new AlterInstructions();
577 2
578
        if ($index->getType() == Index::FULLTEXT) {
579 2
            // Must be executed separately
580
            // SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time
581 2
            $alter = sprintf(
582
                'ALTER TABLE %s ADD %s',
583 2
                $this->quoteTableName($table->getName()),
584 2
                $this->getIndexSqlDefinition($index)
585 2
            );
586 2
587 2
            $instructions->addPostStep($alter);
588 2
        } else {
589 2
            $alter = sprintf(
590 2
                'ADD %s',
591 2
                $this->getIndexSqlDefinition($index)
592
            );
593 2
594
            $instructions->addAlter($alter);
595
        }
596
597
        return $instructions;
598
    }
599 21
600
    /**
601 21
     * {@inheritdoc}
602 5
     */
603 5
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
604 21
    {
605 21
        if (is_string($columns)) {
606 6
            $columns = [$columns]; // str to array
607 4
        }
608
609 4
        $indexes = $this->getIndexes($tableName);
610
        $columns = array_map('strtolower', $columns);
611 15
612 12 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...
613 10
            if ($columns == $index['columns']) {
614
                return new AlterInstructions([sprintf(
615 11
                    'DROP INDEX %s',
616 11
                    $this->quoteColumnName($indexName)
617
                )]);
618
            }
619
        }
620
621
        throw new \InvalidArgumentException(sprintf(
622
            "The specified index on columns '%s' does not exist",
623
            implode(',', $columns)
624
        ));
625
    }
626 22
627
    /**
628 22
     * {@inheritdoc}
629 22
     */
630
    protected function getDropIndexByNameInstructions($tableName, $indexName)
631
    {
632
633
        $indexes = $this->getIndexes($tableName);
634
635 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...
636
            if ($name === $indexName) {
637
                return new AlterInstructions([sprintf(
638
                    'DROP INDEX %s',
639
                    $this->quoteColumnName($indexName)
640 22
                )]);
641
            }
642 22
        }
643 22
644 19
        throw new \InvalidArgumentException(sprintf(
645 19
            "The specified index name '%s' does not exist",
646 19
            $indexName
647 19
        ));
648 22
    }
649 22
650
    /**
651
     * {@inheritdoc}
652
     */
653 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...
654
    {
655 15
        $primaryKey = $this->getPrimaryKey($tableName);
656
657 15
        if (empty($primaryKey['constraint'])) {
658 15
            return false;
659 15
        }
660 15
661 15
        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...
662 15
            return ($primaryKey['constraint'] === $constraint);
663 15
        } else {
664 15
            if (is_string($columns)) {
665
                $columns = [$columns]; // str to array
666
            }
667
            $missingColumns = array_diff($columns, $primaryKey['columns']);
668
669 8
            return empty($missingColumns);
670
        }
671 8
    }
672 3
673 3
    /**
674
     * Get the primary key from a particular table.
675
     *
676 8
     * @param string $tableName Table Name
677 8
     * @return array
678 8
     */
679 8 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...
680 8
    {
681
        $rows = $this->fetchAll(sprintf(
682 8
            "SELECT
683 8
                k.constraint_name,
684 8
                k.column_name
685
            FROM information_schema.table_constraints t
686 7
            JOIN information_schema.key_column_usage k
687 7
                USING(constraint_name,table_name)
688
            WHERE t.constraint_type='PRIMARY KEY'
689
                AND t.table_name='%s'",
690
            $tableName
691
        ));
692
693
        $primaryKey = [
694
            'columns' => [],
695 7
        ];
696 7
        foreach ($rows as $row) {
697
            $primaryKey['constraint'] = $row['constraint_name'];
698 7
            $primaryKey['columns'][] = $row['column_name'];
699 7
        }
700 7
701 7
        return $primaryKey;
702 7
    }
703
704 7
    /**
705
     * {@inheritdoc}
706
     */
707 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...
708
    {
709 96
        if (is_string($columns)) {
710
            $columns = [$columns]; // str to array
711
        }
712 96
        $foreignKeys = $this->getForeignKeys($tableName);
713 87
        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...
714
            if (isset($foreignKeys[$constraint])) {
715 96
                return !empty($foreignKeys[$constraint]);
716 4
            }
717
718 96
            return false;
719 9
        } else {
720
            foreach ($foreignKeys as $key) {
721
                if ($columns == $key['columns']) {
722 6
                    return true;
723 6
                }
724 6
            }
725 6
726 6
            return false;
727 6
        }
728 6
    }
729 6
730
    /**
731 5
     * Get an array of foreign keys from a particular table.
732
     *
733 5
     * @param string $tableName Table Name
734
     * @return array
735 95
     */
736 5 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...
737
    {
738 95
        $foreignKeys = [];
739 3
        $rows = $this->fetchAll(sprintf(
740
            "SELECT
741 95
              CONSTRAINT_NAME,
742 1
              TABLE_NAME,
743
              COLUMN_NAME,
744
              REFERENCED_TABLE_NAME,
745 1
              REFERENCED_COLUMN_NAME
746 1
            FROM information_schema.KEY_COLUMN_USAGE
747 1
            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
748 1
              AND REFERENCED_TABLE_NAME IS NOT NULL
749 1
              AND TABLE_NAME = '%s'
750 1
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
751 1
            $tableName
752 1
        ));
753
        foreach ($rows as $row) {
754 1
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
755
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
756 1
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
757
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
758 95
        }
759 82
760
        return $foreignKeys;
761
    }
762 6
763 6
    /**
764 6
     * {@inheritdoc}
765 6
     */
766 6 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...
767 6
    {
768
        $alter = sprintf(
769 6
            'ADD %s',
770 6
            $this->getForeignKeySqlDefinition($foreignKey)
771 6
        );
772 6
773 6
        return new AlterInstructions([$alter]);
774 6
    }
775 6
776 2
    /**
777 2
     * {@inheritdoc}
778 6
     */
779
    protected function getDropForeignKeyInstructions($tableName, $constraint)
780 5
    {
781 82
        $alter = sprintf(
782 76
            'DROP FOREIGN KEY %s',
783 76
            $constraint
784 82
        );
785
786 86
        return new AlterInstructions([$alter]);
787 82
    }
788
789 86
    /**
790 7
     * {@inheritdoc}
791
     */
792 84
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
793 5
    {
794
        $instructions = new AlterInstructions();
795 83
796 7 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...
797
            $rows = $this->fetchAll(sprintf(
798 83
                "SELECT
799 80
                    CONSTRAINT_NAME
800
                  FROM information_schema.KEY_COLUMN_USAGE
801 83
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
802 4
                    AND REFERENCED_TABLE_NAME IS NOT NULL
803
                    AND TABLE_NAME = '%s'
804 83
                    AND COLUMN_NAME = '%s'
805 4
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
806
                $tableName,
807 83
                $column
808 80
            ));
809
810 10
            foreach ($rows as $row) {
811 2
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
812
            }
813 10
        }
814 10
815 10
        if (empty($instructions->getAlterParts())) {
816 10
            throw new \InvalidArgumentException(sprintf(
817 5
                "Not foreign key on columns '%s' exist",
818 8
                implode(',', $columns)
819 5
            ));
820
        }
821 6
822 4
        return $instructions;
823
    }
824 2
825
    /**
826
     * {@inheritdoc}
827
     */
828
    public function getSqlType($type, $limit = null)
829
    {
830 2
        switch ($type) {
831
            case static::PHINX_TYPE_FLOAT:
832
            case static::PHINX_TYPE_DECIMAL:
833 2
            case static::PHINX_TYPE_DATE:
834 2
            case static::PHINX_TYPE_ENUM:
835 2
            case static::PHINX_TYPE_SET:
836
            case static::PHINX_TYPE_JSON:
837
            // Geospatial database types
838
            case static::PHINX_TYPE_GEOMETRY:
839
            case static::PHINX_TYPE_POINT:
840
            case static::PHINX_TYPE_LINESTRING:
841
            case static::PHINX_TYPE_POLYGON:
842
                return ['name' => $type];
843
            case static::PHINX_TYPE_DATETIME:
844
            case static::PHINX_TYPE_TIMESTAMP:
845
            case static::PHINX_TYPE_TIME:
846 17
                return ['name' => $type, 'limit' => $limit];
847
            case static::PHINX_TYPE_STRING:
848 17
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
849 17
            case static::PHINX_TYPE_CHAR:
850 1
                return ['name' => 'char', 'limit' => $limit ?: 255];
851 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...
852 16
                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...
853 16
                    $sizes = [
854 16
                        // Order matters! Size must always be tested from longest to shortest!
855 16
                        'longtext' => static::TEXT_LONG,
856 14
                        'mediumtext' => static::TEXT_MEDIUM,
857 14
                        'text' => static::TEXT_REGULAR,
858 16
                        'tinytext' => static::TEXT_SMALL,
859 4
                    ];
860 4
                    foreach ($sizes as $name => $length) {
861 16
                        if ($limit >= $length) {
862 3
                            return ['name' => $name];
863 3
                        }
864 3
                    }
865
                }
866 16
867 6
                return ['name' => 'text'];
868 6
            case static::PHINX_TYPE_BINARY:
869 3
                return ['name' => 'binary', 'limit' => $limit ?: 255];
870 3
            case static::PHINX_TYPE_VARBINARY:
871 6
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
872 16 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...
873 5
                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...
874 5
                    $sizes = [
875 1
                        // Order matters! Size must always be tested from longest to shortest!
876 1
                        'longblob' => static::BLOB_LONG,
877 5
                        'mediumblob' => static::BLOB_MEDIUM,
878 2
                        'blob' => static::BLOB_REGULAR,
879 2
                        'tinyblob' => static::BLOB_SMALL,
880 5
                    ];
881 16
                    foreach ($sizes as $name => $length) {
882 2
                        if ($limit >= $length) {
883 2
                            return ['name' => $name];
884 2
                        }
885 16
                    }
886 2
                }
887 2
888 2
                return ['name' => 'blob'];
889 16
            case static::PHINX_TYPE_BIT:
890 2
                return ['name' => 'bit', 'limit' => $limit ?: 64];
891 2
            case static::PHINX_TYPE_INTEGER:
892 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...
893 16
                    $sizes = [
894 15
                        // Order matters! Size must always be tested from longest to shortest!
895 15
                        'bigint' => static::INT_BIG,
896 12
                        'int' => static::INT_REGULAR,
897 12
                        'mediumint' => static::INT_MEDIUM,
898 15
                        'smallint' => static::INT_SMALL,
899 11
                        'tinyint' => static::INT_TINY,
900 6
                    ];
901 4
                    $limits = [
902 4
                        'int' => 11,
903 6
                        'bigint' => 20,
904 6
                    ];
905 10
                    foreach ($sizes as $name => $length) {
906 2
                        if ($limit >= $length) {
907 2
                            $def = ['name' => $name];
908 10
                            if (isset($limits[$name])) {
909 1
                                $def['limit'] = $limits[$name];
910 1
                            }
911 1
912 10
                            return $def;
913 1
                        }
914 1
                    }
915 1
                } 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...
916 10
                    $limit = 11;
917 1
                }
918 1
919 1
                return ['name' => 'int', 'limit' => $limit];
920 10
            case static::PHINX_TYPE_BIG_INTEGER:
921 2
                return ['name' => 'bigint', 'limit' => 20];
922 2
            case static::PHINX_TYPE_BOOLEAN:
923 2
                return ['name' => 'tinyint', 'limit' => 1];
924 9
            case static::PHINX_TYPE_UUID:
925 2
                return ['name' => 'char', 'limit' => 36];
926 2
            case static::TYPE_YEAR:
927 2
                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...
928 8
                    $limit = 4;
929 2
                }
930 2
931 2
                return ['name' => 'year', 'limit' => $limit];
932
            default:
933
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
934
        }
935 16
    }
936
937
    /**
938 15
     * Returns Phinx type by SQL type
939 15
     *
940
     * @param string $sqlTypeDef
941 15
     * @throws \RuntimeException
942
     * @internal param string $sqlType SQL type
943 15
     * @returns string Phinx type
944 3
     */
945 3
    public function getPhinxType($sqlTypeDef)
946
    {
947 15
        $matches = [];
948
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
949
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
950
        } else {
951
            $limit = null;
952
            $precision = null;
953
            $type = $matches[1];
954 83
            if (count($matches) > 2) {
955
                $limit = $matches[3] ? (int)$matches[3] : null;
956 83
            }
957
            if (count($matches) > 4) {
958 83
                $precision = (int)$matches[5];
959 1
            }
960 1 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...
961 82
                $type = static::PHINX_TYPE_BOOLEAN;
962
                $limit = null;
963 83
            }
964
            switch ($type) {
965
                case 'varchar':
966
                    $type = static::PHINX_TYPE_STRING;
967
                    if ($limit === 255) {
968 4
                        $limit = null;
969
                    }
970 4
                    break;
971 4 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...
972 4
                    $type = static::PHINX_TYPE_CHAR;
973
                    if ($limit === 255) {
974 4
                        $limit = null;
975 4
                    }
976
                    if ($limit === 36) {
977 4
                        $type = static::PHINX_TYPE_UUID;
978 3
                    }
979 3
                    break;
980
                case 'tinyint':
981 3
                    $type = static::PHINX_TYPE_INTEGER;
982
                    $limit = static::INT_TINY;
983 3
                    break;
984
                case 'smallint':
985
                    $type = static::PHINX_TYPE_INTEGER;
986
                    $limit = static::INT_SMALL;
987
                    break;
988
                case 'mediumint':
989 81
                    $type = static::PHINX_TYPE_INTEGER;
990
                    $limit = static::INT_MEDIUM;
991 81
                    break;
992 81
                case 'int':
993
                    $type = static::PHINX_TYPE_INTEGER;
994
                    if ($limit === 11) {
995
                        $limit = null;
996
                    }
997
                    break;
998
                case 'bigint':
999
                    if ($limit === 20) {
1000 89
                        $limit = null;
1001
                    }
1002 89
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1003
                    break;
1004 89
                case 'bit':
1005 89
                    $type = static::PHINX_TYPE_BIT;
1006 89
                    if ($limit === 64) {
1007 2
                        $limit = null;
1008 89
                    }
1009 86
                    break;
1010 86
                case 'blob':
1011 89
                    $type = static::PHINX_TYPE_BINARY;
1012 5
                    break;
1013 5
                case 'tinyblob':
1014 89
                    $type = static::PHINX_TYPE_BINARY;
1015 89
                    $limit = static::BLOB_TINY;
1016 89
                    break;
1017 89
                case 'mediumblob':
1018 89
                    $type = static::PHINX_TYPE_BINARY;
1019 89
                    $limit = static::BLOB_MEDIUM;
1020
                    break;
1021 89
                case 'longblob':
1022 2
                    $type = static::PHINX_TYPE_BINARY;
1023 2
                    $limit = static::BLOB_LONG;
1024
                    break;
1025 89
                case 'tinytext':
1026 1
                    $type = static::PHINX_TYPE_TEXT;
1027 1
                    $limit = static::TEXT_TINY;
1028
                    break;
1029 89
                case 'mediumtext':
1030
                    $type = static::PHINX_TYPE_TEXT;
1031
                    $limit = static::TEXT_MEDIUM;
1032
                    break;
1033
                case 'longtext':
1034
                    $type = static::PHINX_TYPE_TEXT;
1035
                    $limit = static::TEXT_LONG;
1036
                    break;
1037
            }
1038 16
1039
            // Call this to check if parsed type is supported.
1040 16
            $this->getSqlType($type, $limit);
1041 16
1042 16
            $phinxType = [
1043 2
                'name' => $type,
1044 2
                'limit' => $limit,
1045
                'precision' => $precision
1046 16
            ];
1047 5
1048 5
            if (static::PHINX_TYPE_ENUM == $type) {
1049
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1050 16
            }
1051 1
1052 1
            return $phinxType;
1053
        }
1054 16
    }
1055
1056 16
    /**
1057 5
     * {@inheritdoc}
1058 5
     */
1059
    public function createDatabase($name, $options = [])
1060 16
    {
1061
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1062 16
1063 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...
1064
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1065
        } else {
1066
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1067
        }
1068
    }
1069
1070
    /**
1071 17
     * {@inheritdoc}
1072
     */
1073 17
    public function hasDatabase($name)
1074 17
    {
1075 5
        $rows = $this->fetchAll(
1076 5
            sprintf(
1077 17
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1078 17
                $name
1079 17
            )
1080 17
        );
1081 17
1082 17
        foreach ($rows as $row) {
1083 17
            if (!empty($row)) {
1084 17
                return true;
1085 17
            }
1086 17
        }
1087 17
1088 2
        return false;
1089 2
    }
1090 17
1091 2
    /**
1092 2
     * {@inheritdoc}
1093 17
     */
1094
    public function dropDatabase($name)
1095
    {
1096
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1097
    }
1098
1099
    /**
1100
     * Gets the MySQL Column Definition for a Column object.
1101
     *
1102 2
     * @param \Phinx\Db\Table\Column $column Column
1103
     * @return string
1104 2
     */
1105
    protected function getColumnSqlDefinition(Column $column)
1106
    {
1107 2
        if ($column->getType() instanceof Literal) {
1108
            $def = (string)$column->getType();
1109
        } else {
1110
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1111 2
            $def = strtoupper($sqlType['name']);
1112 2
        }
1113
        if ($column->getPrecision() && $column->getScale()) {
1114 2
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1115
        } elseif (isset($sqlType['limit'])) {
1116 2
            $def .= '(' . $sqlType['limit'] . ')';
1117
        }
1118 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...
1119
            $def .= "('" . implode("', '", $values) . "')";
1120
        }
1121
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1122
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1123 85
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1124
        $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...
1125 85
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1126
        $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...
1127
1128
        if ($column->getComment()) {
1129
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1130
        }
1131
1132
        if ($column->getUpdate()) {
1133
            $def .= ' ON UPDATE ' . $column->getUpdate();
1134
        }
1135
1136
        return $def;
1137
    }
1138
1139
    /**
1140
     * Gets the MySQL Index Definition for an Index object.
1141
     *
1142
     * @param \Phinx\Db\Table\Index $index Index
1143
     * @return string
1144
     */
1145
    protected function getIndexSqlDefinition(Index $index)
1146
    {
1147
        $def = '';
1148
        $limit = '';
1149
1150
        if ($index->getType() == Index::UNIQUE) {
1151
            $def .= ' UNIQUE';
1152
        }
1153
1154
        if ($index->getType() == Index::FULLTEXT) {
1155
            $def .= ' FULLTEXT';
1156
        }
1157
1158
        $def .= ' KEY';
1159
1160
        if (is_string($index->getName())) {
1161
            $def .= ' `' . $index->getName() . '`';
1162
        }
1163
1164
        if (!is_array($index->getLimit())) {
1165
            if ($index->getLimit()) {
1166
                $limit = '(' . $index->getLimit() . ')';
1167
            }
1168
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1169
        } else {
1170
            $columns = $index->getColumns();
1171
            $limits = $index->getLimit();
1172
            $def .= ' (';
1173
            foreach ($columns as $column) {
1174
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1175
                $def .= '`' . $column . '`' . $limit . ', ';
1176
            }
1177
            $def = rtrim($def, ', ');
1178
            $def .= ' )';
1179
        }
1180
1181
        return $def;
1182
    }
1183
1184
    /**
1185
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1186
     *
1187
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1188
     * @return string
1189
     */
1190 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...
1191
    {
1192
        $def = '';
1193
        if ($foreignKey->getConstraint()) {
1194
            $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...
1195
        }
1196
        $columnNames = [];
1197
        foreach ($foreignKey->getColumns() as $column) {
1198
            $columnNames[] = $this->quoteColumnName($column);
1199
        }
1200
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1201
        $refColumnNames = [];
1202
        foreach ($foreignKey->getReferencedColumns() as $column) {
1203
            $refColumnNames[] = $this->quoteColumnName($column);
1204
        }
1205
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1206
        if ($foreignKey->getOnDelete()) {
1207
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1208
        }
1209
        if ($foreignKey->getOnUpdate()) {
1210
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1211
        }
1212
1213
        return $def;
1214
    }
1215
1216
    /**
1217
     * Describes a database table. This is a MySQL adapter specific method.
1218
     *
1219
     * @param string $tableName Table name
1220
     * @return array
1221
     */
1222 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...
1223
    {
1224
        $options = $this->getOptions();
1225
1226
        // mysql specific
1227
        $sql = sprintf(
1228
            "SELECT *
1229
             FROM information_schema.tables
1230
             WHERE table_schema = '%s'
1231
             AND table_name = '%s'",
1232
            $options['name'],
1233
            $tableName
1234
        );
1235
1236
        return $this->fetchRow($sql);
1237
    }
1238
1239
    /**
1240
     * Returns MySQL column types (inherited and MySQL specified).
1241
     * @return array
1242
     */
1243
    public function getColumnTypes()
1244
    {
1245
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1246
    }
1247
1248
    /**
1249
     * {@inheritDoc}
1250
     *
1251
     */
1252 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...
1253
    {
1254
        $options = $this->getOptions();
1255
        $options = [
1256
            'username' => $options['user'],
1257
            'password' => $options['pass'],
1258
            'database' => $options['name'],
1259
            'quoteIdentifiers' => true,
1260
        ] + $options;
1261
1262
        $driver = new MysqlDriver($options);
1263
        if (method_exists($driver, 'setConnection')) {
1264
            $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...
1265
        } else {
1266
            $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...
1267
        }
1268
1269
        return new Connection(['driver' => $driver] + $options);
1270
    }
1271
}
1272