Completed
Pull Request — master (#1624)
by
unknown
01:46
created

MysqlAdapter::getChangeCommentInstructions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Mysql as MysqlDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx MySQL Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 */
45
class MysqlAdapter extends PdoAdapter implements AdapterInterface
46
{
47
    protected $signedColumnTypes = [
48
        'integer' => true,
49
        'biginteger' => true,
50
        'float' => true,
51
        'decimal' => true,
52
        'double' => true,
53
        'boolean' => true,
54
    ];
55
56
    const TEXT_TINY = 255;
57
    const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
58
    const TEXT_REGULAR = 65535;
59
    const TEXT_MEDIUM = 16777215;
60
    const TEXT_LONG = 4294967295;
61
62
    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
63
    const BLOB_TINY = 255;
64
    const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
65
    const BLOB_REGULAR = 65535;
66
    const BLOB_MEDIUM = 16777215;
67
    const BLOB_LONG = 4294967295;
68
69
    const INT_TINY = 255;
70 80
    const INT_SMALL = 65535;
71
    const INT_MEDIUM = 16777215;
72 80
    const INT_REGULAR = 4294967295;
73 80
    const INT_BIG = 18446744073709551615;
74
75
    const BIT = 64;
76
77
    const TYPE_YEAR = 'year';
78
79 80
    /**
80 80
     * {@inheritdoc}
81
     */
82 80
    public function connect()
83
    {
84 80
        if ($this->connection === null) {
85
            if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
86
                // @codeCoverageIgnoreStart
87
                throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
88
                // @codeCoverageIgnoreEnd
89 80
            }
90 80
91 80
            $db = null;
92 80
            $options = $this->getOptions();
93
94
            $dsn = 'mysql:';
95 80
96
            if (!empty($options['unix_socket'])) {
97
                // use socket connection
98 80
                $dsn .= 'unix_socket=' . $options['unix_socket'];
99
            } else {
100
                // use network connection
101
                $dsn .= 'host=' . $options['host'];
102 80
                if (!empty($options['port'])) {
103
                    $dsn .= ';port=' . $options['port'];
104
                }
105
            }
106 80
107 80
            $dsn .= ';dbname=' . $options['name'];
108
109
            // charset support
110 80
            if (!empty($options['charset'])) {
111
                $dsn .= ';charset=' . $options['charset'];
112
            }
113 80
114 80
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
115 1
116 1
            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
117 1
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
118 1 View Code Duplication
            foreach ($options as $key => $option) {
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...
119
                if (strpos($key, 'mysql_attr_') === 0) {
120
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
121 80
                }
122 80
            }
123 80
124
            try {
125
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
126
            } catch (\PDOException $exception) {
127
                throw new \InvalidArgumentException(sprintf(
128 81
                    'There was a problem connecting to the database: %s',
129
                    $exception->getMessage()
130 81
                ));
131 81
            }
132
133
            $this->setConnection($db);
134
        }
135
    }
136 6
137
    /**
138 6
     * {@inheritdoc}
139
     */
140
    public function disconnect()
141
    {
142
        $this->connection = null;
143
    }
144 6
145
    /**
146 6
     * {@inheritdoc}
147 6
     */
148
    public function hasTransactions()
149
    {
150
        return true;
151
    }
152 6
153
    /**
154 6
     * {@inheritdoc}
155 6
     */
156
    public function beginTransaction()
157
    {
158
        $this->execute('START TRANSACTION');
159
    }
160 1
161
    /**
162 1
     * {@inheritdoc}
163 1
     */
164
    public function commitTransaction()
165
    {
166
        $this->execute('COMMIT');
167
    }
168 112
169
    /**
170 112
     * {@inheritdoc}
171
     */
172
    public function rollbackTransaction()
173
    {
174
        $this->execute('ROLLBACK');
175
    }
176 112
177
    /**
178 112
     * {@inheritdoc}
179
     */
180
    public function quoteTableName($tableName)
181
    {
182
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
183
    }
184 82
185
    /**
186 82
     * {@inheritdoc}
187
     */
188 82
    public function quoteColumnName($columnName)
189
    {
190
        return '`' . str_replace('`', '``', $columnName) . '`';
191 82
    }
192 82
193
    /**
194 82
     * {@inheritdoc}
195
     */
196 82
    public function hasTable($tableName)
197
    {
198
        if ($this->hasCreatedTable($tableName)) {
199
            return true;
200
        }
201
202 82
        if (strpos($tableName, '.') !== false) {
203
            list($schema, $table) = explode('.', $tableName);
204
            $exists = $this->queryTableExists($schema, $table);
205
            // Only break here on success, because it is possible for table names to contain a dot.
206 82
            if (!empty($exists)) {
207
                return true;
208 82
            }
209 82
        }
210
211
        $options = $this->getOptions();
212 82
213 82
        $exists = $this->queryTableExists($options['name'], $tableName);
214 68
215 68
        return !empty($exists);
216 68
    }
217 68
218 68
    /**
219
     * @param string $schema The table schema
220 68
     * @param string $tableName The table name
221 68
     *
222 82
     * @return array|mixed
223
     */
224 2
    private function queryTableExists($schema, $tableName)
225 2
    {
226 2
        return $this->fetchRow(sprintf(
227 2
            "SELECT TABLE_NAME
228
            FROM INFORMATION_SCHEMA.TABLES
229 2
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
230 2
            $schema,
231 2
            $tableName
232
        ));
233
    }
234
    /**
235
     * {@inheritdoc}
236 82
     */
237 82
    public function createTable(Table $table, array $columns = [], array $indexes = [])
238 82
    {
239 82
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
240
        $defaultOptions = [
241
            'engine' => 'InnoDB',
242 82
            'collation' => 'utf8_general_ci'
243 82
        ];
244 82
245 82
        $options = array_merge(
246 82
            $defaultOptions,
247
            array_intersect_key($this->getOptions(), $defaultOptions),
248
            $table->getOptions()
249 82
        );
250 2
251 2
        // Add the default primary key
252 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
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...
253 82
            $options['id'] = 'id';
254 82
        }
255 82
256 82
        if (isset($options['id']) && is_string($options['id'])) {
257 82
            // Handle id => "field_name" to support AUTO_INCREMENT
258
            $column = new Column();
259
            $column->setName($options['id'])
260 82
                   ->setType('integer')
261 82
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
262 82
                   ->setIdentity(true);
263 82
264 81
            array_unshift($columns, $column);
265 82
            $options['primary_key'] = $options['id'];
266
        }
267
268 2
        // TODO - process table options like collation etc
269 2
270 2
        // process table engine (default to InnoDB)
271 2
        $optionsStr = 'ENGINE = InnoDB';
272 2
        if (isset($options['engine'])) {
273 2
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
274 2
        }
275 2
276 2
        // process table collation
277 2
        if (isset($options['collation'])) {
278 82
            $charset = explode('_', $options['collation']);
279 82
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
280 1
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
281
        }
282
283
        // set the table comment
284 82
        if (isset($options['comment'])) {
285 82
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
286 10
        }
287 82
288
        // set the table row format
289
        if (isset($options['row_format'])) {
290 82
            $optionsStr .= sprintf(" ROW_FORMAT=%s ", $options['row_format']);
291 82
        }
292 2
293 82
        $sql = 'CREATE TABLE ';
294
        $sql .= $this->quoteTableName($table->getName()) . ' (';
295 82
        foreach ($columns as $column) {
296 82
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
297
        }
298
299 82
        // set the primary key(s)
300 82 View Code Duplication
        if (isset($options['primary_key'])) {
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...
301
            $sql = rtrim($sql);
302
            $sql .= ' PRIMARY KEY (';
303
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
304
                $sql .= $this->quoteColumnName($options['primary_key']);
305 5
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
306
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
307 5
            }
308 5
            $sql .= ')';
309
        } else {
310
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
311
        }
312
313 5
        // set the indexes
314
        foreach ($indexes as $index) {
315 5
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
316 5
        }
317
318
        $sql .= ') ' . $optionsStr;
319
        $sql = rtrim($sql);
320
321 1
        // execute the sql
322
        $this->execute($sql);
323 1
324 1
        $this->addCreatedTable($table->getName());
325 1
    }
326 1
327
    /**
328 1
     * {@inheritdoc}
329 1
     */
330
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
331
    {
332
        $instructions = new AlterInstructions();
333
334 12
        // Drop the existing primary key
335
        $primaryKey = $this->getPrimaryKey($table->getName());
336 12
        if (!empty($primaryKey['columns'])) {
337 12
            $instructions->addAlter('DROP PRIMARY KEY');
338 12
        }
339 12
340
        // Add the primary key(s)
341 12
        if (!empty($newColumns)) {
342 12
            $sql = 'ADD PRIMARY KEY (';
343 12
            if (is_string($newColumns)) { // handle primary_key => 'id'
344 12
                $sql .= $this->quoteColumnName($newColumns);
345 12
            } elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
346 12
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
347
            } else {
348 12
                throw new \InvalidArgumentException(sprintf(
349 12
                    "Invalid value for primary key: %s",
350 12
                    json_encode($newColumns)
351
                ));
352 12
            }
353 3
            $sql .= ')';
354 3
            $instructions->addAlter($sql);
355
        }
356 12
357 12
        return $instructions;
358
    }
359 12
360
    /**
361
     * {@inheritdoc}
362
     */
363
    protected function getChangeCommentInstructions(Table $table, $newComment)
364
    {
365 79
        $instructions = new AlterInstructions();
366
367 79
        // passing 'null' is to remove table comment
368 79
        $newComment = ($newComment !== null)
369 79
            ? $newComment
370 77
            : '';
371
        $sql = sprintf(" COMMENT=%s ", $this->getConnection()->quote($newComment));
372 77
        $instructions->addAlter($sql);
373
374 21
        return $instructions;
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     */
380 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381
    {
382
        $this->updateCreatedTableName($tableName, $newTableName);
383 95
        $sql = sprintf(
384
            'RENAME TABLE %s TO %s',
385 95
            $this->quoteTableName($tableName),
386 10
            $this->quoteTableName($newTableName)
387 95
        );
388 79
389 79
        return new AlterInstructions([], [$sql]);
390 95
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395 View Code Duplication
    protected function getDropTableInstructions($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
396 18
    {
397
        $this->removeCreatedTable($tableName);
398 18
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
399 18
400 18
        return new AlterInstructions([], [$sql]);
401 18
    }
402 18
403 18
    /**
404
     * {@inheritdoc}
405 18
     */
406 2
    public function truncateTable($tableName)
407 2
    {
408
        $sql = sprintf(
409 18
            'TRUNCATE TABLE %s',
410 18
            $this->quoteTableName($tableName)
411
        );
412
413
        $this->execute($sql);
414
    }
415 7
416
    /**
417 7
     * {@inheritdoc}
418 7
     */
419 7
    public function getColumns($tableName)
420 5
    {
421 5
        $columns = [];
422 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
423 1
        foreach ($rows as $columnInfo) {
424 1
            $phinxType = $this->getPhinxType($columnInfo['Type']);
425 5
426
            $column = new Column();
427 5
            $column->setName($columnInfo['Field'])
428 5
                   ->setNull($columnInfo['Null'] !== 'NO')
429 5
                   ->setDefault($columnInfo['Default'])
430 5
                   ->setType($phinxType['name'])
431 5
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
432 5
                   ->setLimit($phinxType['limit'])
433
                   ->setScale($phinxType['scale']);
434 5
435 5
            if ($columnInfo['Extra'] === 'auto_increment') {
436 5
                $column->setIdentity(true);
437
            }
438 6
439
            if (isset($phinxType['values'])) {
440 2
                $column->setValues($phinxType['values']);
441
            }
442
443 2
            $columns[] = $column;
444
        }
445
446
        return $columns;
447
    }
448
449 5
    /**
450
     * {@inheritdoc}
451 5
     */
452 5 View Code Duplication
    public function hasColumn($tableName, $columnName)
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...
453 5
    {
454 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
455 5
        foreach ($rows as $column) {
456 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
457 5
                return true;
458 5
            }
459
        }
460 5
461 5
        return false;
462 5
    }
463
464
    /**
465
     * {@inheritdoc}
466
     */
467 5
    protected function getAddColumnInstructions(Table $table, Column $column)
468
    {
469 5
        $alter = sprintf(
470 5
            'ADD %s %s',
471 5
            $this->quoteColumnName($column->getName()),
472 5
            $this->getColumnSqlDefinition($column)
473 5
        );
474 5
475 5
        if ($column->getAfter()) {
476 5
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
477
        }
478
479
        return new AlterInstructions([$alter]);
480
    }
481
482
    /**
483
     * {@inheritdoc}
484 19
     */
485
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
486 19
    {
487 19
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
488 19
489 18
        foreach ($rows as $row) {
490 18
            if (strcasecmp($row['Field'], $columnName) === 0) {
491 18
                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
492 18
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
493 19
                $extra = ' ' . strtoupper($row['Extra']);
494 19
                if (!is_null($row['Default'])) {
495
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
496
                }
497
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
498
499
                $alter = sprintf(
500 14
                    'CHANGE COLUMN %s %s %s',
501
                    $this->quoteColumnName($columnName),
502 14
                    $this->quoteColumnName($newColumnName),
503 6
                    $definition
504 6
                );
505
506 14
                return new AlterInstructions([$alter]);
507 14
            }
508
        }
509 14
510 14
        throw new \InvalidArgumentException(sprintf(
511 12
            'The specified column doesn\'t exist: ' .
512
            $columnName
513 13
        ));
514
    }
515 11
516
    /**
517
     * {@inheritdoc}
518
     */
519
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
520
    {
521 1
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
522
        $alter = sprintf(
523 1
            'CHANGE %s %s %s%s',
524
            $this->quoteColumnName($columnName),
525 1
            $this->quoteColumnName($newColumn->getName()),
526 1
            $this->getColumnSqlDefinition($newColumn),
527 1
            $after
528
        );
529 1
530
        return new AlterInstructions([$alter]);
531
    }
532
533
    /**
534
     * {@inheritdoc}
535
     */
536
    protected function getDropColumnInstructions($tableName, $columnName)
537 4
    {
538
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
539 4
540 4
        return new AlterInstructions([$alter]);
541 4
    }
542 4
543 4
    /**
544 4
     * Get an array of indexes from a particular table.
545 4
     *
546 4
     * @param string $tableName Table Name
547
     * @return array
548
     */
549
    protected function getIndexes($tableName)
550
    {
551 3
        $indexes = [];
552
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
553 3 View Code Duplication
        foreach ($rows as $row) {
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...
554 2
            if (!isset($indexes[$row['Key_name']])) {
555 2
                $indexes[$row['Key_name']] = ['columns' => []];
556
            }
557 3
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
558 3
        }
559
560 3
        return $indexes;
561 3
    }
562 3
563 3
    /**
564 3
     * {@inheritdoc}
565 3
     */
566 3 View Code Duplication
    public function hasIndex($tableName, $columns)
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...
567 3
    {
568 3
        if (is_string($columns)) {
569 3
            $columns = [$columns]; // str to array
570
        }
571 3
572 1
        $columns = array_map('strtolower', $columns);
573
        $indexes = $this->getIndexes($tableName);
574
575
        foreach ($indexes as $index) {
576
            if ($columns == $index['columns']) {
577 2
                return true;
578
            }
579 2
        }
580
581 2
        return false;
582
    }
583 2
584 2
    /**
585 2
     * {@inheritdoc}
586 2
     */
587 2 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
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...
588 2
    {
589 2
        $indexes = $this->getIndexes($tableName);
590 2
591 2
        foreach ($indexes as $name => $index) {
592
            if ($name === $indexName) {
593 2
                return true;
594
            }
595
        }
596
597
        return false;
598
    }
599 21
600
    /**
601 21
     * {@inheritdoc}
602 5
     */
603 5
    protected function getAddIndexInstructions(Table $table, Index $index)
604 21
    {
605 21
        $instructions = new AlterInstructions();
606 6
607 4
        if ($index->getType() == Index::FULLTEXT) {
608
            // Must be executed separately
609 4
            // SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time
610
            $alter = sprintf(
611 15
                'ALTER TABLE %s ADD %s',
612 12
                $this->quoteTableName($table->getName()),
613 10
                $this->getIndexSqlDefinition($index)
614
            );
615 11
616 11
            $instructions->addPostStep($alter);
617
        } else {
618
            $alter = sprintf(
619
                'ADD %s',
620
                $this->getIndexSqlDefinition($index)
621
            );
622
623
            $instructions->addAlter($alter);
624
        }
625
626 22
        return $instructions;
627
    }
628 22
629 22
    /**
630
     * {@inheritdoc}
631
     */
632
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
633
    {
634
        if (is_string($columns)) {
635
            $columns = [$columns]; // str to array
636
        }
637
638
        $indexes = $this->getIndexes($tableName);
639
        $columns = array_map('strtolower', $columns);
640 22
641 View Code Duplication
        foreach ($indexes as $indexName => $index) {
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...
642 22
            if ($columns == $index['columns']) {
643 22
                return new AlterInstructions([sprintf(
644 19
                    'DROP INDEX %s',
645 19
                    $this->quoteColumnName($indexName)
646 19
                )]);
647 19
            }
648 22
        }
649 22
650
        throw new \InvalidArgumentException(sprintf(
651
            "The specified index on columns '%s' does not exist",
652
            implode(',', $columns)
653
        ));
654
    }
655 15
656
    /**
657 15
     * {@inheritdoc}
658 15
     */
659 15
    protected function getDropIndexByNameInstructions($tableName, $indexName)
660 15
    {
661 15
662 15
        $indexes = $this->getIndexes($tableName);
663 15
664 15 View Code Duplication
        foreach ($indexes as $name => $index) {
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...
665
            if ($name === $indexName) {
666
                return new AlterInstructions([sprintf(
667
                    'DROP INDEX %s',
668
                    $this->quoteColumnName($indexName)
669 8
                )]);
670
            }
671 8
        }
672 3
673 3
        throw new \InvalidArgumentException(sprintf(
674
            "The specified index name '%s' does not exist",
675
            $indexName
676 8
        ));
677 8
    }
678 8
679 8
    /**
680 8
     * {@inheritdoc}
681
     */
682 8 View Code Duplication
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
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...
683 8
    {
684 8
        $primaryKey = $this->getPrimaryKey($tableName);
685
686 7
        if (empty($primaryKey['constraint'])) {
687 7
            return false;
688
        }
689
690
        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...
691
            return ($primaryKey['constraint'] === $constraint);
692
        } else {
693
            if (is_string($columns)) {
694
                $columns = [$columns]; // str to array
695 7
            }
696 7
            $missingColumns = array_diff($columns, $primaryKey['columns']);
697
698 7
            return empty($missingColumns);
699 7
        }
700 7
    }
701 7
702 7
    /**
703
     * Get the primary key from a particular table.
704 7
     *
705
     * @param string $tableName Table Name
706
     * @return array
707
     */
708 View Code Duplication
    public function getPrimaryKey($tableName)
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...
709 96
    {
710
        $rows = $this->fetchAll(sprintf(
711
            "SELECT
712 96
                k.constraint_name,
713 87
                k.column_name
714
            FROM information_schema.table_constraints t
715 96
            JOIN information_schema.key_column_usage k
716 4
                USING(constraint_name,table_name)
717
            WHERE t.constraint_type='PRIMARY KEY'
718 96
                AND t.table_name='%s'",
719 9
            $tableName
720
        ));
721
722 6
        $primaryKey = [
723 6
            'columns' => [],
724 6
        ];
725 6
        foreach ($rows as $row) {
726 6
            $primaryKey['constraint'] = $row['constraint_name'];
727 6
            $primaryKey['columns'][] = $row['column_name'];
728 6
        }
729 6
730
        return $primaryKey;
731 5
    }
732
733 5
    /**
734
     * {@inheritdoc}
735 95
     */
736 5 View Code Duplication
    public function hasForeignKey($tableName, $columns, $constraint = null)
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
        if (is_string($columns)) {
739 3
            $columns = [$columns]; // str to array
740
        }
741 95
        $foreignKeys = $this->getForeignKeys($tableName);
742 1
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
743
            if (isset($foreignKeys[$constraint])) {
744
                return !empty($foreignKeys[$constraint]);
745 1
            }
746 1
747 1
            return false;
748 1
        } else {
749 1
            foreach ($foreignKeys as $key) {
750 1
                if ($columns == $key['columns']) {
751 1
                    return true;
752 1
                }
753
            }
754 1
755
            return false;
756 1
        }
757
    }
758 95
759 82
    /**
760
     * Get an array of foreign keys from a particular table.
761
     *
762 6
     * @param string $tableName Table Name
763 6
     * @return array
764 6
     */
765 6
    protected function getForeignKeys($tableName)
766 6
    {
767 6 View Code Duplication
        if (strpos($tableName, '.') !== false) {
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...
768
            list($schema, $tableName) = explode('.', $tableName);
769 6
        }
770 6
771 6
        $foreignKeys = [];
772 6
        $rows = $this->fetchAll(sprintf(
773 6
            "SELECT
774 6
              CONSTRAINT_NAME,
775 6
              CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS TABLE_NAME,
776 2
              COLUMN_NAME,
777 2
              CONCAT(REFERENCED_TABLE_SCHEMA, '.', REFERENCED_TABLE_NAME) AS REFERENCED_TABLE_NAME,
778 6
              REFERENCED_COLUMN_NAME
779
            FROM information_schema.KEY_COLUMN_USAGE
780 5
            WHERE REFERENCED_TABLE_NAME IS NOT NULL
781 82
              AND TABLE_SCHEMA = %s
782 76
              AND TABLE_NAME = '%s'
783 76
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
784 82
            empty($schema) ? 'DATABASE()' : "'$schema'",
785
            $tableName
786 86
        ));
787 82 View Code Duplication
        foreach ($rows as $row) {
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...
788
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
789 86
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
790 7
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
791
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
792 84
        }
793 5
794
        return $foreignKeys;
795 83
    }
796 7
797
    /**
798 83
     * {@inheritdoc}
799 80
     */
800 View Code Duplication
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
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...
801 83
    {
802 4
        $alter = sprintf(
803
            'ADD %s',
804 83
            $this->getForeignKeySqlDefinition($foreignKey)
805 4
        );
806
807 83
        return new AlterInstructions([$alter]);
808 80
    }
809
810 10
    /**
811 2
     * {@inheritdoc}
812
     */
813 10
    protected function getDropForeignKeyInstructions($tableName, $constraint)
814 10
    {
815 10
        $alter = sprintf(
816 10
            'DROP FOREIGN KEY %s',
817 5
            $constraint
818 8
        );
819 5
820
        return new AlterInstructions([$alter]);
821 6
    }
822 4
823
    /**
824 2
     * {@inheritdoc}
825
     */
826
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
827
    {
828
        $instructions = new AlterInstructions();
829
830 2 View Code Duplication
        foreach ($columns as $column) {
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...
831
            $rows = $this->fetchAll(sprintf(
832
                "SELECT
833 2
                    CONSTRAINT_NAME
834 2
                  FROM information_schema.KEY_COLUMN_USAGE
835 2
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
836
                    AND REFERENCED_TABLE_NAME IS NOT NULL
837
                    AND TABLE_NAME = '%s'
838
                    AND COLUMN_NAME = '%s'
839
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
840
                $tableName,
841
                $column
842
            ));
843
844
            foreach ($rows as $row) {
845
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
846 17
            }
847
        }
848 17
849 17
        if (empty($instructions->getAlterParts())) {
850 1
            throw new \InvalidArgumentException(sprintf(
851
                "Not foreign key on columns '%s' exist",
852 16
                implode(',', $columns)
853 16
            ));
854 16
        }
855 16
856 14
        return $instructions;
857 14
    }
858 16
859 4
    /**
860 4
     * {@inheritdoc}
861 16
     */
862 3
    public function getSqlType($type, $limit = null)
863 3
    {
864 3
        switch ($type) {
865
            case static::PHINX_TYPE_FLOAT:
866 16
            case static::PHINX_TYPE_DOUBLE:
867 6
            case static::PHINX_TYPE_DECIMAL:
868 6
            case static::PHINX_TYPE_DATE:
869 3
            case static::PHINX_TYPE_ENUM:
870 3
            case static::PHINX_TYPE_SET:
871 6
            case static::PHINX_TYPE_JSON:
872 16
            // Geospatial database types
873 5
            case static::PHINX_TYPE_GEOMETRY:
874 5
            case static::PHINX_TYPE_POINT:
875 1
            case static::PHINX_TYPE_LINESTRING:
876 1
            case static::PHINX_TYPE_POLYGON:
877 5
                return ['name' => $type];
878 2
            case static::PHINX_TYPE_DATETIME:
879 2
            case static::PHINX_TYPE_TIMESTAMP:
880 5
            case static::PHINX_TYPE_TIME:
881 16
                return ['name' => $type, 'limit' => $limit];
882 2
            case static::PHINX_TYPE_STRING:
883 2
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
884 2
            case static::PHINX_TYPE_CHAR:
885 16
                return ['name' => 'char', 'limit' => $limit ?: 255];
886 2 View Code Duplication
            case static::PHINX_TYPE_TEXT:
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...
887 2
                if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
888 2
                    $sizes = [
889 16
                        // Order matters! Size must always be tested from longest to shortest!
890 2
                        'longtext' => static::TEXT_LONG,
891 2
                        'mediumtext' => static::TEXT_MEDIUM,
892 2
                        'text' => static::TEXT_REGULAR,
893 16
                        'tinytext' => static::TEXT_SMALL,
894 15
                    ];
895 15
                    foreach ($sizes as $name => $length) {
896 12
                        if ($limit >= $length) {
897 12
                            return ['name' => $name];
898 15
                        }
899 11
                    }
900 6
                }
901 4
902 4
                return ['name' => 'text'];
903 6
            case static::PHINX_TYPE_BINARY:
904 6
                return ['name' => 'binary', 'limit' => $limit ?: 255];
905 10
            case static::PHINX_TYPE_VARBINARY:
906 2
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
907 2 View Code Duplication
            case static::PHINX_TYPE_BLOB:
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...
908 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...
909 1
                    $sizes = [
910 1
                        // Order matters! Size must always be tested from longest to shortest!
911 1
                        'longblob' => static::BLOB_LONG,
912 10
                        'mediumblob' => static::BLOB_MEDIUM,
913 1
                        'blob' => static::BLOB_REGULAR,
914 1
                        'tinyblob' => static::BLOB_SMALL,
915 1
                    ];
916 10
                    foreach ($sizes as $name => $length) {
917 1
                        if ($limit >= $length) {
918 1
                            return ['name' => $name];
919 1
                        }
920 10
                    }
921 2
                }
922 2
923 2
                return ['name' => 'blob'];
924 9
            case static::PHINX_TYPE_BIT:
925 2
                return ['name' => 'bit', 'limit' => $limit ?: 64];
926 2
            case static::PHINX_TYPE_SMALL_INTEGER:
927 2
                return ['name' => 'smallint', 'limit' => $limit ?: 6];
928 8
            case static::PHINX_TYPE_INTEGER:
929 2
                if ($limit && $limit >= static::INT_TINY) {
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...
930 2
                    $sizes = [
931 2
                        // Order matters! Size must always be tested from longest to shortest!
932
                        'bigint' => static::INT_BIG,
933
                        'int' => static::INT_REGULAR,
934
                        'mediumint' => static::INT_MEDIUM,
935 16
                        'smallint' => static::INT_SMALL,
936
                        'tinyint' => static::INT_TINY,
937
                    ];
938 15
                    $limits = [
939 15
                        'smallint' => 6,
940
                        'int' => 11,
941 15
                        'bigint' => 20,
942
                    ];
943 15
                    foreach ($sizes as $name => $length) {
944 3
                        if ($limit >= $length) {
945 3
                            $def = ['name' => $name];
946
                            if (isset($limits[$name])) {
947 15
                                $def['limit'] = $limits[$name];
948
                            }
949
950
                            return $def;
951
                        }
952
                    }
953
                } elseif (!$limit) {
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...
954 83
                    $limit = 11;
955
                }
956 83
957
                return ['name' => 'int', 'limit' => $limit];
958 83
            case static::PHINX_TYPE_BIG_INTEGER:
959 1
                return ['name' => 'bigint', 'limit' => 20];
960 1
            case static::PHINX_TYPE_BOOLEAN:
961 82
                return ['name' => 'tinyint', 'limit' => 1];
962
            case static::PHINX_TYPE_UUID:
963 83
                return ['name' => 'char', 'limit' => 36];
964
            case static::TYPE_YEAR:
965
                if (!$limit || in_array($limit, [2, 4])) {
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...
966
                    $limit = 4;
967
                }
968 4
969
                return ['name' => 'year', 'limit' => $limit];
970 4
            default:
971 4
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by MySQL.');
972 4
        }
973
    }
974 4
975 4
    /**
976
     * Returns Phinx type by SQL type
977 4
     *
978 3
     * @param string $sqlTypeDef
979 3
     * @throws UnsupportedColumnTypeException
980
     * @internal param string $sqlType SQL type
981 3
     * @return array Phinx type
982
     */
983 3
    public function getPhinxType($sqlTypeDef)
984
    {
985
        $matches = [];
986
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
987
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by MySQL.');
988
        } else {
989 81
            $limit = null;
990
            $scale = null;
991 81
            $type = $matches[1];
992 81
            if (count($matches) > 2) {
993
                $limit = $matches[3] ? (int)$matches[3] : null;
994
            }
995
            if (count($matches) > 4) {
996
                $scale = (int)$matches[5];
997
            }
998 View Code Duplication
            if ($type === 'tinyint' && $limit === 1) {
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...
999
                $type = static::PHINX_TYPE_BOOLEAN;
1000 89
                $limit = null;
1001
            }
1002 89
            switch ($type) {
1003
                case 'varchar':
1004 89
                    $type = static::PHINX_TYPE_STRING;
1005 89
                    if ($limit === 255) {
1006 89
                        $limit = null;
1007 2
                    }
1008 89
                    break;
1009 86
                case 'char':
1010 86
                    $type = static::PHINX_TYPE_CHAR;
1011 89
                    if ($limit === 255) {
1012 5
                        $limit = null;
1013 5
                    }
1014 89
                    if ($limit === 36) {
1015 89
                        $type = static::PHINX_TYPE_UUID;
1016 89
                    }
1017 89
                    break;
1018 89
                case 'tinyint':
1019 89
                    $type = static::PHINX_TYPE_INTEGER;
1020
                    $limit = static::INT_TINY;
1021 89
                    break;
1022 2
                case 'smallint':
1023 2
                    $type = static::PHINX_TYPE_SMALL_INTEGER;
1024
                    $limit = static::INT_SMALL;
1025 89
                    break;
1026 1
                case 'mediumint':
1027 1
                    $type = static::PHINX_TYPE_INTEGER;
1028
                    $limit = static::INT_MEDIUM;
1029 89
                    break;
1030
                case 'int':
1031
                    $type = static::PHINX_TYPE_INTEGER;
1032
                    if ($limit === 11) {
1033
                        $limit = null;
1034
                    }
1035
                    break;
1036
                case 'bigint':
1037
                    if ($limit === 20) {
1038 16
                        $limit = null;
1039
                    }
1040 16
                    $type = static::PHINX_TYPE_BIG_INTEGER;
1041 16
                    break;
1042 16
                case 'bit':
1043 2
                    $type = static::PHINX_TYPE_BIT;
1044 2
                    if ($limit === 64) {
1045
                        $limit = null;
1046 16
                    }
1047 5
                    break;
1048 5
                case 'blob':
1049
                    $type = static::PHINX_TYPE_BINARY;
1050 16
                    break;
1051 1
                case 'tinyblob':
1052 1
                    $type = static::PHINX_TYPE_BINARY;
1053
                    $limit = static::BLOB_TINY;
1054 16
                    break;
1055
                case 'mediumblob':
1056 16
                    $type = static::PHINX_TYPE_BINARY;
1057 5
                    $limit = static::BLOB_MEDIUM;
1058 5
                    break;
1059
                case 'longblob':
1060 16
                    $type = static::PHINX_TYPE_BINARY;
1061
                    $limit = static::BLOB_LONG;
1062 16
                    break;
1063
                case 'tinytext':
1064
                    $type = static::PHINX_TYPE_TEXT;
1065
                    $limit = static::TEXT_TINY;
1066
                    break;
1067
                case 'mediumtext':
1068
                    $type = static::PHINX_TYPE_TEXT;
1069
                    $limit = static::TEXT_MEDIUM;
1070
                    break;
1071 17
                case 'longtext':
1072
                    $type = static::PHINX_TYPE_TEXT;
1073 17
                    $limit = static::TEXT_LONG;
1074 17
                    break;
1075 5
            }
1076 5
1077 17
            try {
1078 17
                // Call this to check if parsed type is supported.
1079 17
                $this->getSqlType($type, $limit);
1080 17
            } catch (UnsupportedColumnTypeException $e) {
1081 17
                $type = Literal::from($type);
1082 17
            }
1083 17
1084 17
            $phinxType = [
1085 17
                'name' => $type,
1086 17
                'limit' => $limit,
1087 17
                'scale' => $scale
1088 2
            ];
1089 2
1090 17
            if (static::PHINX_TYPE_ENUM == $type || static::PHINX_TYPE_SET == $type) {
1091 2
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
1092 2
            }
1093 17
1094
            return $phinxType;
1095
        }
1096
    }
1097
1098
    /**
1099
     * {@inheritdoc}
1100
     */
1101
    public function createDatabase($name, $options = [])
1102 2
    {
1103
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
1104 2
1105 View Code Duplication
        if (isset($options['collation'])) {
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...
1106
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
1107 2
        } else {
1108
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1109
        }
1110
    }
1111 2
1112 2
    /**
1113
     * {@inheritdoc}
1114 2
     */
1115
    public function hasDatabase($name)
1116 2
    {
1117
        $rows = $this->fetchAll(
1118
            sprintf(
1119
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1120
                $name
1121
            )
1122
        );
1123 85
1124
        foreach ($rows as $row) {
1125 85
            if (!empty($row)) {
1126
                return true;
1127
            }
1128
        }
1129
1130
        return false;
1131
    }
1132
1133
    /**
1134
     * {@inheritdoc}
1135
     */
1136
    public function dropDatabase($name)
1137
    {
1138
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1139
    }
1140
1141
    /**
1142
     * Gets the MySQL Column Definition for a Column object.
1143
     *
1144
     * @param \Phinx\Db\Table\Column $column Column
1145
     * @return string
1146
     */
1147
    protected function getColumnSqlDefinition(Column $column)
1148
    {
1149
        if ($column->getType() instanceof Literal) {
1150
            $def = (string)$column->getType();
1151
        } else {
1152
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1153
            $def = strtoupper($sqlType['name']);
1154
        }
1155
        if ($column->getPrecision() && $column->getScale()) {
1156
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1157
        } elseif (isset($sqlType['limit'])) {
1158
            $def .= '(' . $sqlType['limit'] . ')';
1159
        }
1160
        if (($values = $column->getValues()) && is_array($values)) {
1161
            $def .= "('" . implode("', '", $values) . "')";
1162
        }
1163
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1164
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1165
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
1166
        $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
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...
1167
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1168
        $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...
1169
1170
        if ($column->getComment()) {
1171
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1172
        }
1173
1174
        if ($column->getUpdate()) {
1175
            $def .= ' ON UPDATE ' . $column->getUpdate();
1176
        }
1177
1178
        return $def;
1179
    }
1180
1181
    /**
1182
     * Gets the MySQL Index Definition for an Index object.
1183
     *
1184
     * @param \Phinx\Db\Table\Index $index Index
1185
     * @return string
1186
     */
1187
    protected function getIndexSqlDefinition(Index $index)
1188
    {
1189
        $def = '';
1190
        $limit = '';
1191
1192
        if ($index->getType() == Index::UNIQUE) {
1193
            $def .= ' UNIQUE';
1194
        }
1195
1196
        if ($index->getType() == Index::FULLTEXT) {
1197
            $def .= ' FULLTEXT';
1198
        }
1199
1200
        $def .= ' KEY';
1201
1202
        if (is_string($index->getName())) {
1203
            $def .= ' `' . $index->getName() . '`';
1204
        }
1205
1206
        if (!is_array($index->getLimit())) {
1207
            if ($index->getLimit()) {
1208
                $limit = '(' . $index->getLimit() . ')';
1209
            }
1210
            $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
1211
        } else {
1212
            $columns = $index->getColumns();
1213
            $limits = $index->getLimit();
1214
            $def .= ' (';
1215
            foreach ($columns as $column) {
1216
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1217
                $def .= '`' . $column . '`' . $limit . ', ';
1218
            }
1219
            $def = rtrim($def, ', ');
1220
            $def .= ' )';
1221
        }
1222
1223
        return $def;
1224
    }
1225
1226
    /**
1227
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1228
     *
1229
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1230
     * @return string
1231
     */
1232 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
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...
1233
    {
1234
        $def = '';
1235
        if ($foreignKey->getConstraint()) {
1236
            $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...
1237
        }
1238
        $columnNames = [];
1239
        foreach ($foreignKey->getColumns() as $column) {
1240
            $columnNames[] = $this->quoteColumnName($column);
1241
        }
1242
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1243
        $refColumnNames = [];
1244
        foreach ($foreignKey->getReferencedColumns() as $column) {
1245
            $refColumnNames[] = $this->quoteColumnName($column);
1246
        }
1247
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1248
        if ($foreignKey->getOnDelete()) {
1249
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1250
        }
1251
        if ($foreignKey->getOnUpdate()) {
1252
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1253
        }
1254
1255
        return $def;
1256
    }
1257
1258
    /**
1259
     * Describes a database table. This is a MySQL adapter specific method.
1260
     *
1261
     * @param string $tableName Table name
1262
     * @return array
1263
     */
1264
    public function describeTable($tableName)
1265
    {
1266
        $options = $this->getOptions();
1267
1268
        // mysql specific
1269
        $sql = sprintf(
1270
            "SELECT *
1271
             FROM information_schema.tables
1272
             WHERE table_schema = '%s'
1273
             AND table_name = '%s'",
1274
            $options['name'],
1275
            $tableName
1276
        );
1277
1278
        return $this->fetchRow($sql);
1279
    }
1280
1281
    /**
1282
     * Returns MySQL column types (inherited and MySQL specified).
1283
     * @return array
1284
     */
1285
    public function getColumnTypes()
1286
    {
1287
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
1288
    }
1289
1290
    /**
1291
     * {@inheritDoc}
1292
     *
1293
     */
1294 View Code Duplication
    public function getDecoratedConnection()
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...
1295
    {
1296
        $options = $this->getOptions();
1297
        $options = [
1298
            'username' => $options['user'],
1299
            'password' => $options['pass'],
1300
            'database' => $options['name'],
1301
            'quoteIdentifiers' => true,
1302
        ] + $options;
1303
1304
        $driver = new MysqlDriver($options);
1305
        if (method_exists($driver, 'setConnection')) {
1306
            $driver->setConnection($this->connection);
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...
1307
        } else {
1308
            $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...
1309
        }
1310
1311
        return new Connection(['driver' => $driver] + $options);
1312
    }
1313
}
1314