Completed
Pull Request — master (#1904)
by
unknown
02:37 queued 01:08
created

MysqlAdapter::getForeignKeys()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 23
cts 23
cp 1
rs 9.424
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Db\Adapter;
9
10
use Cake\Database\Connection;
11
use Cake\Database\Driver\Mysql as MysqlDriver;
12
use InvalidArgumentException;
13
use PDO;
14
use Phinx\Db\Table\Column;
15
use Phinx\Db\Table\ForeignKey;
16
use Phinx\Db\Table\Index;
17
use Phinx\Db\Table\Table;
18
use Phinx\Db\Util\AlterInstructions;
19
use Phinx\Util\Literal;
20
use RuntimeException;
21
22
/**
23
 * Phinx MySQL Adapter.
24
 *
25
 * @author Rob Morgan <[email protected]>
26
 */
27
class MysqlAdapter extends PdoAdapter
28
{
29
    /**
30
     * @var string[]
31
     */
32
    protected static $specificColumnTypes = [
33
        self::PHINX_TYPE_ENUM,
34
        self::PHINX_TYPE_SET,
35
        self::PHINX_TYPE_YEAR,
36
        self::PHINX_TYPE_JSON,
37
        self::PHINX_TYPE_BINARYUUID,
38
        self::PHINX_TYPE_TINYBLOB,
39
        self::PHINX_TYPE_MEDIUMBLOB,
40
        self::PHINX_TYPE_LONGBLOB,
41
    ];
42
43
    /**
44
     * @var bool[]
45
     */
46
    protected $signedColumnTypes = [
47
        self::PHINX_TYPE_INTEGER => true,
48
        self::PHINX_TYPE_TINY_INTEGER => true,
49
        self::PHINX_TYPE_SMALL_INTEGER => true,
50
        self::PHINX_TYPE_BIG_INTEGER => true,
51
        self::PHINX_TYPE_FLOAT => true,
52
        self::PHINX_TYPE_DECIMAL => true,
53
        self::PHINX_TYPE_DOUBLE => true,
54
        self::PHINX_TYPE_BOOLEAN => true,
55
    ];
56
57
    public const TEXT_TINY = 255;
58
    public const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
59
    public const TEXT_REGULAR = 65535;
60
    public const TEXT_MEDIUM = 16777215;
61
    public const TEXT_LONG = 4294967295;
62
63
    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
64
    public const BLOB_TINY = 255;
65
    public const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
66
    public const BLOB_REGULAR = 65535;
67
    public const BLOB_MEDIUM = 16777215;
68
    public const BLOB_LONG = 4294967295;
69
70 80
    public const INT_TINY = 255;
71
    public const INT_SMALL = 65535;
72 80
    public const INT_MEDIUM = 16777215;
73 80
    public const INT_REGULAR = 4294967295;
74
    public const INT_BIG = 18446744073709551615;
75
76
    public const BIT = 64;
77
78
    public const TYPE_YEAR = 'year';
79 80
80 80
    /**
81
     * {@inheritDoc}
82 80
     *
83
     * @throws \RuntimeException
84 80
     * @throws \InvalidArgumentException
85
     *
86
     * @return void
87
     */
88
    public function connect()
89 80
    {
90 80
        if ($this->connection === null) {
91 80
            if (!class_exists('PDO') || !in_array('mysql', PDO::getAvailableDrivers(), true)) {
92 80
                // @codeCoverageIgnoreStart
93
                throw new RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
94
                // @codeCoverageIgnoreEnd
95 80
            }
96
97
            $options = $this->getOptions();
98 80
99
            $dsn = 'mysql:';
100
101
            if (!empty($options['unix_socket'])) {
102 80
                // use socket connection
103
                $dsn .= 'unix_socket=' . $options['unix_socket'];
104
            } else {
105
                // use network connection
106 80
                $dsn .= 'host=' . $options['host'];
107 80
                if (!empty($options['port'])) {
108
                    $dsn .= ';port=' . $options['port'];
109
                }
110 80
            }
111
112
            $dsn .= ';dbname=' . $options['name'];
113 80
114 80
            // charset support
115 1
            if (!empty($options['charset'])) {
116 1
                $dsn .= ';charset=' . $options['charset'];
117 1
            }
118 1
119
            $driverOptions = [];
120
121 80
            // use custom data fetch mode
122 80
            if (!empty($options['fetch_mode'])) {
123 80
                $driverOptions[PDO::ATTR_DEFAULT_FETCH_MODE] = constant('\PDO::FETCH_' . strtoupper($options['fetch_mode']));
124
            }
125
126
            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
127
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
128 81
            foreach ($options as $key => $option) {
129
                if (strpos($key, 'mysql_attr_') === 0) {
130 81
                    $pdoConstant = '\PDO::' . strtoupper($key);
131 81
                    if (!defined($pdoConstant)) {
132
                        throw new \UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')');
133
                    }
134
                    $driverOptions[constant($pdoConstant)] = $option;
135
                }
136 6
            }
137
138 6
            $db = $this->createPdoConnection($dsn, $options['user'] ?? null, $options['pass'] ?? null, $driverOptions);
139
140
            $this->setConnection($db);
141
        }
142
    }
143
144 6
    /**
145
     * @inheritDoc
146 6
     */
147 6
    public function disconnect()
148
    {
149
        $this->connection = null;
150
    }
151
152 6
    /**
153
     * @inheritDoc
154 6
     */
155 6
    public function hasTransactions()
156
    {
157
        return true;
158
    }
159
160 1
    /**
161
     * @inheritDoc
162 1
     */
163 1
    public function beginTransaction()
164
    {
165
        $this->execute('START TRANSACTION');
166
    }
167
168 112
    /**
169
     * @inheritDoc
170 112
     */
171
    public function commitTransaction()
172
    {
173
        $this->execute('COMMIT');
174
    }
175
176 112
    /**
177
     * @inheritDoc
178 112
     */
179
    public function rollbackTransaction()
180
    {
181
        $this->execute('ROLLBACK');
182
    }
183
184 82
    /**
185
     * @inheritDoc
186 82
     */
187
    public function quoteTableName($tableName)
188 82
    {
189
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
190
    }
191 82
192 82
    /**
193
     * @inheritDoc
194 82
     */
195
    public function quoteColumnName($columnName)
196 82
    {
197
        return '`' . str_replace('`', '``', $columnName) . '`';
198
    }
199
200
    /**
201
     * @inheritDoc
202 82
     */
203
    public function hasTable($tableName)
204
    {
205
        if ($this->hasCreatedTable($tableName)) {
206 82
            return true;
207
        }
208 82
209 82
        if (strpos($tableName, '.') !== false) {
210
            [$schema, $table] = explode('.', $tableName);
0 ignored issues
show
Bug introduced by
The variable $schema does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $table does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
211
            $exists = $this->hasTableWithSchema($schema, $table);
212 82
            // Only break here on success, because it is possible for table names to contain a dot.
213 82
            if ($exists) {
214 68
                return true;
215 68
            }
216 68
        }
217 68
218 68
        $options = $this->getOptions();
219
220 68
        return $this->hasTableWithSchema($options['name'], $tableName);
221 68
    }
222 82
223
    /**
224 2
     * @param string $schema The table schema
225 2
     * @param string $tableName The table name
226 2
     *
227 2
     * @return bool
228
     */
229 2
    protected function hasTableWithSchema($schema, $tableName)
230 2
    {
231 2
        $result = $this->fetchRow(sprintf(
232
            "SELECT TABLE_NAME
233
            FROM INFORMATION_SCHEMA.TABLES
234
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
235
            $schema,
236 82
            $tableName
237 82
        ));
238 82
239 82
        return !empty($result);
240
    }
241
242 82
    /**
243 82
     * @inheritDoc
244 82
     */
245 82
    public function createTable(Table $table, array $columns = [], array $indexes = [])
246 82
    {
247
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
248
        $defaultOptions = [
249 82
            'engine' => 'InnoDB',
250 2
            'collation' => 'utf8_general_ci',
251 2
        ];
252
253 82
        $options = array_merge(
254 82
            $defaultOptions,
255 82
            array_intersect_key($this->getOptions(), $defaultOptions),
256 82
            $table->getOptions()
257 82
        );
258
259
        // Add the default primary key
260 82
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
261 82
            $options['id'] = 'id';
262 82
        }
263 82
264 81
        if (isset($options['id']) && is_string($options['id'])) {
265 82
            // Handle id => "field_name" to support AUTO_INCREMENT
266
            $column = new Column();
267
            $column->setName($options['id'])
268 2
                   ->setType('integer')
269 2
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
270 2
                   ->setIdentity(true);
271 2
272 2
            array_unshift($columns, $column);
273 2
            if (isset($options['primary_key']) && (array)$options['id'] !== (array)$options['primary_key']) {
274 2
                throw new InvalidArgumentException('You cannot enable an auto incrementing ID field and a primary key');
275 2
            }
276 2
            $options['primary_key'] = $options['id'];
277 2
        }
278 82
279 82
        // open: process table options like collation etc
280 1
281
        // process table engine (default to InnoDB)
282
        $optionsStr = 'ENGINE = InnoDB';
283
        if (isset($options['engine'])) {
284 82
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
285 82
        }
286 10
287 82
        // process table collation
288
        if (isset($options['collation'])) {
289
            $charset = explode('_', $options['collation']);
290 82
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
291 82
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
292 2
        }
293 82
294
        // set the table comment
295 82
        if (isset($options['comment'])) {
296 82
            $optionsStr .= sprintf(' COMMENT=%s ', $this->getConnection()->quote($options['comment']));
297
        }
298
299 82
        // set the table row format
300 82
        if (isset($options['row_format'])) {
301
            $optionsStr .= sprintf(' ROW_FORMAT=%s ', $options['row_format']);
302
        }
303
304
        $sql = 'CREATE TABLE ';
305 5
        $sql .= $this->quoteTableName($table->getName()) . ' (';
306
        foreach ($columns as $column) {
307 5
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
308 5
        }
309
310
        // set the primary key(s)
311
        if (isset($options['primary_key'])) {
312
            $sql = rtrim($sql);
313 5
            $sql .= ' PRIMARY KEY (';
314
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
315 5
                $sql .= $this->quoteColumnName($options['primary_key']);
316 5
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
317
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
318
            }
319
            $sql .= ')';
320
        } else {
321 1
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
322
        }
323 1
324 1
        // set the indexes
325 1
        foreach ($indexes as $index) {
326 1
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
327
        }
328 1
329 1
        $sql .= ') ' . $optionsStr;
330
        $sql = rtrim($sql);
331
332
        // execute the sql
333
        $this->execute($sql);
334 12
335
        $this->addCreatedTable($table->getName());
336 12
    }
337 12
338 12
    /**
339 12
     * {@inheritDoc}
340
     *
341 12
     * @throws \InvalidArgumentException
342 12
     */
343 12
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
344 12
    {
345 12
        $instructions = new AlterInstructions();
346 12
347
        // Drop the existing primary key
348 12
        $primaryKey = $this->getPrimaryKey($table->getName());
349 12
        if (!empty($primaryKey['columns'])) {
350 12
            $instructions->addAlter('DROP PRIMARY KEY');
351
        }
352 12
353 3
        // Add the primary key(s)
354 3
        if (!empty($newColumns)) {
355
            $sql = 'ADD PRIMARY KEY (';
356 12
            if (is_string($newColumns)) { // handle primary_key => 'id'
357 12
                $sql .= $this->quoteColumnName($newColumns);
358
            } elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
359 12
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
360
            } else {
361
                throw new InvalidArgumentException(sprintf(
362
                    'Invalid value for primary key: %s',
363
                    json_encode($newColumns)
364
                ));
365 79
            }
366
            $sql .= ')';
367 79
            $instructions->addAlter($sql);
368 79
        }
369 79
370 77
        return $instructions;
371
    }
372 77
373
    /**
374 21
     * @inheritDoc
375
     */
376
    protected function getChangeCommentInstructions(Table $table, $newComment)
377
    {
378
        $instructions = new AlterInstructions();
379
380
        // passing 'null' is to remove table comment
381
        $newComment = ($newComment !== null)
382
            ? $newComment
383 95
            : '';
384
        $sql = sprintf(' COMMENT=%s ', $this->getConnection()->quote($newComment));
385 95
        $instructions->addAlter($sql);
386 10
387 95
        return $instructions;
388 79
    }
389 79
390 95
    /**
391
     * @inheritDoc
392
     */
393
    protected function getRenameTableInstructions($tableName, $newTableName)
394
    {
395
        $this->updateCreatedTableName($tableName, $newTableName);
396 18
        $sql = sprintf(
397
            'RENAME TABLE %s TO %s',
398 18
            $this->quoteTableName($tableName),
399 18
            $this->quoteTableName($newTableName)
400 18
        );
401 18
402 18
        return new AlterInstructions([], [$sql]);
403 18
    }
404
405 18
    /**
406 2
     * @inheritDoc
407 2
     */
408
    protected function getDropTableInstructions($tableName)
409 18
    {
410 18
        $this->removeCreatedTable($tableName);
411
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
412
413
        return new AlterInstructions([], [$sql]);
414
    }
415 7
416
    /**
417 7
     * @inheritDoc
418 7
     */
419 7
    public function truncateTable($tableName)
420 5
    {
421 5
        $sql = sprintf(
422 5
            'TRUNCATE TABLE %s',
423 1
            $this->quoteTableName($tableName)
424 1
        );
425 5
426
        $this->execute($sql);
427 5
    }
428 5
429 5
    /**
430 5
     * @inheritDoc
431 5
     */
432 5
    public function getColumns($tableName)
433
    {
434 5
        $columns = [];
435 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
436 5
        foreach ($rows as $columnInfo) {
437
            $phinxType = $this->getPhinxType($columnInfo['Type']);
438 6
439
            $column = new Column();
440 2
            $column->setName($columnInfo['Field'])
441
                   ->setNull($columnInfo['Null'] !== 'NO')
442
                   ->setDefault($columnInfo['Default'])
443 2
                   ->setType($phinxType['name'])
444
                   ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false)
445
                   ->setLimit($phinxType['limit'])
446
                   ->setScale($phinxType['scale']);
447
448
            if ($columnInfo['Extra'] === 'auto_increment') {
449 5
                $column->setIdentity(true);
450
            }
451 5
452 5
            if (isset($phinxType['values'])) {
453 5
                $column->setValues($phinxType['values']);
454 5
            }
455 5
456 5
            $columns[] = $column;
457 5
        }
458 5
459
        return $columns;
460 5
    }
461 5
462 5
    /**
463
     * @inheritDoc
464
     */
465
    public function hasColumn($tableName, $columnName)
466
    {
467 5
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
468
        foreach ($rows as $column) {
469 5
            if (strcasecmp($column['Field'], $columnName) === 0) {
470 5
                return true;
471 5
            }
472 5
        }
473 5
474 5
        return false;
475 5
    }
476 5
477
    /**
478
     * @inheritDoc
479
     */
480
    protected function getAddColumnInstructions(Table $table, Column $column)
481
    {
482
        $alter = sprintf(
483
            'ADD %s %s',
484 19
            $this->quoteColumnName($column->getName()),
485
            $this->getColumnSqlDefinition($column)
486 19
        );
487 19
488 19
        if ($column->getAfter()) {
489 18
            $alter .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
490 18
        }
491 18
492 18
        return new AlterInstructions([$alter]);
493 19
    }
494 19
495
    /**
496
     * {@inheritDoc}
497
     *
498
     * @throws \InvalidArgumentException
499
     */
500 14
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
501
    {
502 14
        $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
503 6
504 6
        foreach ($rows as $row) {
505
            if (strcasecmp($row['Field'], $columnName) === 0) {
506 14
                $null = ($row['Null'] === 'NO') ? 'NOT NULL' : 'NULL';
507 14
                $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
508
                $extra = ' ' . strtoupper($row['Extra']);
509 14
                if (($row['Default'] !== null)) {
510 14
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
511 12
                }
512
                $definition = $row['Type'] . ' ' . $null . $extra . $comment;
513 13
514
                $alter = sprintf(
515 11
                    'CHANGE COLUMN %s %s %s',
516
                    $this->quoteColumnName($columnName),
517
                    $this->quoteColumnName($newColumnName),
518
                    $definition
519
                );
520
521 1
                return new AlterInstructions([$alter]);
522
            }
523 1
        }
524
525 1
        throw new InvalidArgumentException(sprintf(
526 1
            "The specified column doesn't exist: " .
527 1
            $columnName
528
        ));
529 1
    }
530
531
    /**
532
     * @inheritDoc
533
     */
534
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
535
    {
536
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
537 4
        $alter = sprintf(
538
            'CHANGE %s %s %s%s',
539 4
            $this->quoteColumnName($columnName),
540 4
            $this->quoteColumnName($newColumn->getName()),
541 4
            $this->getColumnSqlDefinition($newColumn),
542 4
            $after
543 4
        );
544 4
545 4
        return new AlterInstructions([$alter]);
546 4
    }
547
548
    /**
549
     * @inheritDoc
550
     */
551 3
    protected function getDropColumnInstructions($tableName, $columnName)
552
    {
553 3
        $alter = sprintf('DROP COLUMN %s', $this->quoteColumnName($columnName));
554 2
555 2
        return new AlterInstructions([$alter]);
556
    }
557 3
558 3
    /**
559
     * Get an array of indexes from a particular table.
560 3
     *
561 3
     * @param string $tableName Table name
562 3
     *
563 3
     * @return array
564 3
     */
565 3
    protected function getIndexes($tableName)
566 3
    {
567 3
        $indexes = [];
568 3
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
569 3
        foreach ($rows as $row) {
570
            if (!isset($indexes[$row['Key_name']])) {
571 3
                $indexes[$row['Key_name']] = ['columns' => []];
572 1
            }
573
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
574
        }
575
576
        return $indexes;
577 2
    }
578
579 2
    /**
580
     * @inheritDoc
581 2
     */
582
    public function hasIndex($tableName, $columns)
583 2
    {
584 2
        if (is_string($columns)) {
585 2
            $columns = [$columns]; // str to array
586 2
        }
587 2
588 2
        $columns = array_map('strtolower', $columns);
589 2
        $indexes = $this->getIndexes($tableName);
590 2
591 2
        foreach ($indexes as $index) {
592
            if ($columns == $index['columns']) {
593 2
                return true;
594
            }
595
        }
596
597
        return false;
598
    }
599 21
600
    /**
601 21
     * @inheritDoc
602 5
     */
603 5
    public function hasIndexByName($tableName, $indexName)
604 21
    {
605 21
        $indexes = $this->getIndexes($tableName);
606 6
607 4
        foreach ($indexes as $name => $index) {
608
            if ($name === $indexName) {
609 4
                return true;
610
            }
611 15
        }
612 12
613 10
        return false;
614
    }
615 11
616 11
    /**
617
     * @inheritDoc
618
     */
619
    protected function getAddIndexInstructions(Table $table, Index $index)
620
    {
621
        $instructions = new AlterInstructions();
622
623
        if ($index->getType() === Index::FULLTEXT) {
624
            // Must be executed separately
625
            // SQLSTATE[HY000]: General error: 1795 InnoDB presently supports one FULLTEXT index creation at a time
626 22
            $alter = sprintf(
627
                'ALTER TABLE %s ADD %s',
628 22
                $this->quoteTableName($table->getName()),
629 22
                $this->getIndexSqlDefinition($index)
630
            );
631
632
            $instructions->addPostStep($alter);
633
        } else {
634
            $alter = sprintf(
635
                'ADD %s',
636
                $this->getIndexSqlDefinition($index)
637
            );
638
639
            $instructions->addAlter($alter);
640 22
        }
641
642 22
        return $instructions;
643 22
    }
644 19
645 19
    /**
646 19
     * {@inheritDoc}
647 19
     *
648 22
     * @throws \InvalidArgumentException
649 22
     */
650
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
651
    {
652
        if (is_string($columns)) {
653
            $columns = [$columns]; // str to array
654
        }
655 15
656
        $indexes = $this->getIndexes($tableName);
657 15
        $columns = array_map('strtolower', $columns);
658 15
659 15
        foreach ($indexes as $indexName => $index) {
660 15
            if ($columns == $index['columns']) {
661 15
                return new AlterInstructions([sprintf(
662 15
                    'DROP INDEX %s',
663 15
                    $this->quoteColumnName($indexName)
664 15
                )]);
665
            }
666
        }
667
668
        throw new InvalidArgumentException(sprintf(
669 8
            "The specified index on columns '%s' does not exist",
670
            implode(',', $columns)
671 8
        ));
672 3
    }
673 3
674
    /**
675
     * {@inheritDoc}
676 8
     *
677 8
     * @throws \InvalidArgumentException
678 8
     */
679 8
    protected function getDropIndexByNameInstructions($tableName, $indexName)
680 8
    {
681
        $indexes = $this->getIndexes($tableName);
682 8
683 8
        foreach ($indexes as $name => $index) {
684 8
            if ($name === $indexName) {
685
                return new AlterInstructions([sprintf(
686 7
                    'DROP INDEX %s',
687 7
                    $this->quoteColumnName($indexName)
688
                )]);
689
            }
690
        }
691
692
        throw new InvalidArgumentException(sprintf(
693
            "The specified index name '%s' does not exist",
694
            $indexName
695 7
        ));
696 7
    }
697
698 7
    /**
699 7
     * @inheritDoc
700 7
     */
701 7
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
702 7
    {
703
        $primaryKey = $this->getPrimaryKey($tableName);
704 7
705
        if (empty($primaryKey['constraint'])) {
706
            return false;
707
        }
708
709 96
        if ($constraint) {
710
            return ($primaryKey['constraint'] === $constraint);
711
        } else {
712 96
            if (is_string($columns)) {
713 87
                $columns = [$columns]; // str to array
714
            }
715 96
            $missingColumns = array_diff($columns, $primaryKey['columns']);
716 4
717
            return empty($missingColumns);
718 96
        }
719 9
    }
720
721
    /**
722 6
     * Get the primary key from a particular table.
723 6
     *
724 6
     * @param string $tableName Table name
725 6
     *
726 6
     * @return array
727 6
     */
728 6
    public function getPrimaryKey($tableName)
729 6
    {
730
        $options = $this->getOptions();
731 5
        $rows = $this->fetchAll(sprintf(
732
            "SELECT
733 5
                k.CONSTRAINT_NAME,
734
                k.COLUMN_NAME
735 95
            FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS t
736 5
            JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
737
                USING(CONSTRAINT_NAME,TABLE_NAME)
738 95
            WHERE t.CONSTRAINT_TYPE='PRIMARY KEY'
739 3
                AND t.TABLE_SCHEMA='%s'
740
                AND t.TABLE_NAME='%s'",
741 95
            $options['name'],
742 1
            $tableName
743
        ));
744
745 1
        $primaryKey = [
746 1
            'columns' => [],
747 1
        ];
748 1
        foreach ($rows as $row) {
749 1
            $primaryKey['constraint'] = $row['CONSTRAINT_NAME'];
750 1
            $primaryKey['columns'][] = $row['COLUMN_NAME'];
751 1
        }
752 1
753
        return $primaryKey;
754 1
    }
755
756 1
    /**
757
     * @inheritDoc
758 95
     */
759 82
    public function hasForeignKey($tableName, $columns, $constraint = null)
760
    {
761
        if (is_string($columns)) {
762 6
            $columns = [$columns]; // str to array
763 6
        }
764 6
        $foreignKeys = $this->getForeignKeys($tableName);
765 6
        if ($constraint) {
766 6
            if (isset($foreignKeys[$constraint])) {
767 6
                return !empty($foreignKeys[$constraint]);
768
            }
769 6
770 6
            return false;
771 6
        }
772 6
773 6
        foreach ($foreignKeys as $key) {
774 6
            if ($columns == $key['columns']) {
775 6
                return true;
776 2
            }
777 2
        }
778 6
779
        return false;
780 5
    }
781 82
782 76
    /**
783 76
     * Get an array of foreign keys from a particular table.
784 82
     *
785
     * @param string $tableName Table name
786 86
     *
787 82
     * @return array
788
     */
789 86
    protected function getForeignKeys($tableName)
790 7
    {
791
        if (strpos($tableName, '.') !== false) {
792 84
            [$schema, $tableName] = explode('.', $tableName);
0 ignored issues
show
Bug introduced by
The variable $schema does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
793 5
        }
794
795 83
        $foreignKeys = [];
796 7
        $rows = $this->fetchAll(sprintf(
797
            "SELECT
798 83
              CONSTRAINT_NAME,
799 80
              CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS TABLE_NAME,
800
              COLUMN_NAME,
801 83
              CONCAT(REFERENCED_TABLE_SCHEMA, '.', REFERENCED_TABLE_NAME) AS REFERENCED_TABLE_NAME,
802 4
              REFERENCED_COLUMN_NAME
803
            FROM information_schema.KEY_COLUMN_USAGE
804 83
            WHERE REFERENCED_TABLE_NAME IS NOT NULL
805 4
              AND TABLE_SCHEMA = %s
806
              AND TABLE_NAME = '%s'
807 83
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
808 80
            empty($schema) ? 'DATABASE()' : "'$schema'",
809
            $tableName
810 10
        ));
811 2
        foreach ($rows as $row) {
812
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
813 10
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
814 10
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
815 10
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
816 10
        }
817 5
818 8
        return $foreignKeys;
819 5
    }
820
821 6
    /**
822 4
     * @inheritDoc
823
     */
824 2
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
825
    {
826
        $alter = sprintf(
827
            'ADD %s',
828
            $this->getForeignKeySqlDefinition($foreignKey)
829
        );
830 2
831
        return new AlterInstructions([$alter]);
832
    }
833 2
834 2
    /**
835 2
     * @inheritDoc
836
     */
837
    protected function getDropForeignKeyInstructions($tableName, $constraint)
838
    {
839
        $alter = sprintf(
840
            'DROP FOREIGN KEY %s',
841
            $constraint
842
        );
843
844
        return new AlterInstructions([$alter]);
845
    }
846 17
847
    /**
848 17
     * {@inheritDoc}
849 17
     *
850 1
     * @throws \InvalidArgumentException
851
     */
852 16
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
853 16
    {
854 16
        $instructions = new AlterInstructions();
855 16
856 14
        foreach ($columns as $column) {
857 14
            $rows = $this->fetchAll(sprintf(
858 16
                "SELECT
859 4
                    CONSTRAINT_NAME
860 4
                  FROM information_schema.KEY_COLUMN_USAGE
861 16
                  WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
862 3
                    AND REFERENCED_TABLE_NAME IS NOT NULL
863 3
                    AND TABLE_NAME = '%s'
864 3
                    AND COLUMN_NAME = '%s'
865
                  ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
866 16
                $tableName,
867 6
                $column
868 6
            ));
869 3
870 3
            foreach ($rows as $row) {
871 6
                $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME']));
872 16
            }
873 5
        }
874 5
875 1
        if (empty($instructions->getAlterParts())) {
876 1
            throw new InvalidArgumentException(sprintf(
877 5
                "Not foreign key on columns '%s' exist",
878 2
                implode(',', $columns)
879 2
            ));
880 5
        }
881 16
882 2
        return $instructions;
883 2
    }
884 2
885 16
    /**
886 2
     * {@inheritDoc}
887 2
     *
888 2
     * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException
889 16
     */
890 2
    public function getSqlType($type, $limit = null)
891 2
    {
892 2
        switch ($type) {
893 16
            case static::PHINX_TYPE_FLOAT:
894 15
            case static::PHINX_TYPE_DOUBLE:
895 15
            case static::PHINX_TYPE_DECIMAL:
896 12
            case static::PHINX_TYPE_DATE:
897 12
            case static::PHINX_TYPE_ENUM:
898 15
            case static::PHINX_TYPE_SET:
899 11
            case static::PHINX_TYPE_JSON:
900 6
            // Geospatial database types
901 4
            case static::PHINX_TYPE_GEOMETRY:
902 4
            case static::PHINX_TYPE_POINT:
903 6
            case static::PHINX_TYPE_LINESTRING:
904 6
            case static::PHINX_TYPE_POLYGON:
905 10
                return ['name' => $type];
906 2
            case static::PHINX_TYPE_DATETIME:
907 2
            case static::PHINX_TYPE_TIMESTAMP:
908 10
            case static::PHINX_TYPE_TIME:
909 1
                return ['name' => $type, 'limit' => $limit];
910 1
            case static::PHINX_TYPE_STRING:
911 1
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
912 10
            case static::PHINX_TYPE_CHAR:
913 1
                return ['name' => 'char', 'limit' => $limit ?: 255];
914 1
            case static::PHINX_TYPE_TEXT:
915 1
                if ($limit) {
916 10
                    $sizes = [
917 1
                        // Order matters! Size must always be tested from longest to shortest!
918 1
                        'longtext' => static::TEXT_LONG,
919 1
                        'mediumtext' => static::TEXT_MEDIUM,
920 10
                        'text' => static::TEXT_REGULAR,
921 2
                        'tinytext' => static::TEXT_SMALL,
922 2
                    ];
923 2
                    foreach ($sizes as $name => $length) {
924 9
                        if ($limit >= $length) {
925 2
                            return ['name' => $name];
926 2
                        }
927 2
                    }
928 8
                }
929 2
930 2
                return ['name' => 'text'];
931 2
            case static::PHINX_TYPE_BINARY:
932
                if ($limit === null) {
933
                    $limit = 255;
934
                }
935 16
936
                if ($limit > 255) {
937
                    return $this->getSqlType(static::PHINX_TYPE_BLOB, $limit);
938 15
                }
939 15
940
                return ['name' => 'binary', 'limit' => $limit];
941 15
            case static::PHINX_TYPE_BINARYUUID:
942
                return ['name' => 'binary', 'limit' => 16];
943 15
            case static::PHINX_TYPE_VARBINARY:
944 3
                if ($limit === null) {
945 3
                    $limit = 255;
946
                }
947 15
948
                if ($limit > 255) {
949
                    return $this->getSqlType(static::PHINX_TYPE_BLOB, $limit);
950
                }
951
952
                return ['name' => 'varbinary', 'limit' => $limit];
953
            case static::PHINX_TYPE_BLOB:
954 83
                if ($limit !== null) {
955
                    // Rework this part as the choosen types were always UNDER the required length
956 83
                    $sizes = [
957
                        'tinyblob' => static::BLOB_SMALL,
958 83
                        'blob' => static::BLOB_REGULAR,
959 1
                        'mediumblob' => static::BLOB_MEDIUM,
960 1
                    ];
961 82
962
                    foreach ($sizes as $name => $length) {
963 83
                        if ($limit <= $length) {
964
                            return ['name' => $name];
965
                        }
966
                    }
967
968 4
                    // For more length requirement, the longblob is used
969
                    return ['name' => 'longblob'];
970 4
                }
971 4
972 4
                // If not limit is provided, fallback on blob
973
                return ['name' => 'blob'];
974 4
            case static::PHINX_TYPE_TINYBLOB:
975 4
                // Automatically reprocess blob type to ensure that correct blob subtype is selected given provided limit
976
                return $this->getSqlType(static::PHINX_TYPE_BLOB, $limit ?: static::BLOB_TINY);
977 4
            case static::PHINX_TYPE_MEDIUMBLOB:
978 3
                // Automatically reprocess blob type to ensure that correct blob subtype is selected given provided limit
979 3
                return $this->getSqlType(static::PHINX_TYPE_BLOB, $limit ?: static::BLOB_MEDIUM);
980
            case static::PHINX_TYPE_LONGBLOB:
981 3
                // Automatically reprocess blob type to ensure that correct blob subtype is selected given provided limit
982
                return $this->getSqlType(static::PHINX_TYPE_BLOB, $limit ?: static::BLOB_LONG);
983 3
            case static::PHINX_TYPE_BIT:
984
                return ['name' => 'bit', 'limit' => $limit ?: 64];
985
            case static::PHINX_TYPE_SMALL_INTEGER:
986
                return ['name' => 'smallint', 'limit' => $limit ?: 6];
987
            case static::PHINX_TYPE_TINY_INTEGER:
988
                return ['name' => 'tinyint', 'limit' => $limit ?: 4];
989 81
            case static::PHINX_TYPE_INTEGER:
990
                if ($limit && $limit >= static::INT_TINY) {
991 81
                    $sizes = [
992 81
                        // Order matters! Size must always be tested from longest to shortest!
993
                        'bigint' => static::INT_BIG,
994
                        'int' => static::INT_REGULAR,
995
                        'mediumint' => static::INT_MEDIUM,
996
                        'smallint' => static::INT_SMALL,
997
                        'tinyint' => static::INT_TINY,
998
                    ];
999
                    $limits = [
1000 89
                        'tinyint' => 4,
1001
                        'smallint' => 6,
1002 89
                        'int' => 11,
1003
                        'bigint' => 20,
1004 89
                    ];
1005 89
                    foreach ($sizes as $name => $length) {
1006 89
                        if ($limit >= $length) {
1007 2
                            $def = ['name' => $name];
1008 89
                            if (isset($limits[$name])) {
1009 86
                                $def['limit'] = $limits[$name];
1010 86
                            }
1011 89
1012 5
                            return $def;
1013 5
                        }
1014 89
                    }
1015 89
                } elseif (!$limit) {
1016 89
                    $limit = 11;
1017 89
                }
1018 89
1019 89
                return ['name' => 'int', 'limit' => $limit];
1020
            case static::PHINX_TYPE_BIG_INTEGER:
1021 89
                return ['name' => 'bigint', 'limit' => $limit ?: 20];
1022 2
            case static::PHINX_TYPE_BOOLEAN:
1023 2
                return ['name' => 'tinyint', 'limit' => 1];
1024
            case static::PHINX_TYPE_UUID:
1025 89
                return ['name' => 'char', 'limit' => 36];
1026 1
            case static::PHINX_TYPE_YEAR:
1027 1
                if (!$limit || in_array($limit, [2, 4])) {
1028
                    $limit = 4;
1029 89
                }
1030
1031
                return ['name' => 'year', 'limit' => $limit];
1032
            default:
1033
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by MySQL.');
1034
        }
1035
    }
1036
1037
    /**
1038 16
     * Returns Phinx type by SQL type
1039
     *
1040 16
     * @internal param string $sqlType SQL type
1041 16
     *
1042 16
     * @param string $sqlTypeDef SQL Type definition
1043 2
     *
1044 2
     * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException
1045
     *
1046 16
     * @return array Phinx type
1047 5
     */
1048 5
    public function getPhinxType($sqlTypeDef)
1049
    {
1050 16
        $matches = [];
1051 1
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
1052 1
            throw new UnsupportedColumnTypeException('Column type "' . $sqlTypeDef . '" is not supported by MySQL.');
1053
        }
1054 16
1055
        $limit = null;
1056 16
        $scale = null;
1057 5
        $type = $matches[1];
1058 5
        if (count($matches) > 2) {
1059
            $limit = $matches[3] ? (int)$matches[3] : null;
1060 16
        }
1061
        if (count($matches) > 4) {
1062 16
            $scale = (int)$matches[5];
1063
        }
1064
        if ($type === 'tinyint' && $limit === 1) {
1065
            $type = static::PHINX_TYPE_BOOLEAN;
1066
            $limit = null;
1067
        }
1068
        switch ($type) {
1069
            case 'varchar':
1070
                $type = static::PHINX_TYPE_STRING;
1071 17
                if ($limit === 255) {
1072
                    $limit = null;
1073 17
                }
1074 17
                break;
1075 5
            case 'char':
1076 5
                $type = static::PHINX_TYPE_CHAR;
1077 17
                if ($limit === 255) {
1078 17
                    $limit = null;
1079 17
                }
1080 17
                if ($limit === 36) {
1081 17
                    $type = static::PHINX_TYPE_UUID;
1082 17
                }
1083 17
                break;
1084 17
            case 'tinyint':
1085 17
                $type = static::PHINX_TYPE_TINY_INTEGER;
1086 17
                $limit = static::INT_TINY;
1087 17
                break;
1088 2
            case 'smallint':
1089 2
                $type = static::PHINX_TYPE_SMALL_INTEGER;
1090 17
                $limit = static::INT_SMALL;
1091 2
                break;
1092 2
            case 'mediumint':
1093 17
                $type = static::PHINX_TYPE_INTEGER;
1094
                $limit = static::INT_MEDIUM;
1095
                break;
1096
            case 'int':
1097
                $type = static::PHINX_TYPE_INTEGER;
1098
                if ($limit === 11) {
1099
                    $limit = null;
1100
                }
1101
                break;
1102 2
            case 'bigint':
1103
                if ($limit === 20) {
1104 2
                    $limit = null;
1105
                }
1106
                $type = static::PHINX_TYPE_BIG_INTEGER;
1107 2
                break;
1108
            case 'bit':
1109
                $type = static::PHINX_TYPE_BIT;
1110
                if ($limit === 64) {
1111 2
                    $limit = null;
1112 2
                }
1113
                break;
1114 2
            case 'blob':
1115
                $type = static::PHINX_TYPE_BLOB;
1116 2
                $limit = static::BLOB_REGULAR;
1117
                break;
1118
            case 'tinyblob':
1119
                $type = static::PHINX_TYPE_TINYBLOB;
1120
                $limit = static::BLOB_TINY;
1121
                break;
1122
            case 'mediumblob':
1123 85
                $type = static::PHINX_TYPE_MEDIUMBLOB;
1124
                $limit = static::BLOB_MEDIUM;
1125 85
                break;
1126
            case 'longblob':
1127
                $type = static::PHINX_TYPE_LONGBLOB;
1128
                $limit = static::BLOB_LONG;
1129
                break;
1130
            case 'tinytext':
1131
                $type = static::PHINX_TYPE_TEXT;
1132
                $limit = static::TEXT_TINY;
1133
                break;
1134
            case 'mediumtext':
1135
                $type = static::PHINX_TYPE_TEXT;
1136
                $limit = static::TEXT_MEDIUM;
1137
                break;
1138
            case 'longtext':
1139
                $type = static::PHINX_TYPE_TEXT;
1140
                $limit = static::TEXT_LONG;
1141
                break;
1142
            case 'binary':
1143
                if ($limit === null) {
1144
                    $limit = 255;
1145
                }
1146
1147
                if ($limit > 255) {
1148
                    $type = static::PHINX_TYPE_BLOB;
1149
                    break;
1150
                }
1151
1152
                if ($limit === 16) {
1153
                    $type = static::PHINX_TYPE_BINARYUUID;
1154
                }
1155
                break;
1156
        }
1157
1158
        try {
1159
            // Call this to check if parsed type is supported.
1160
            $this->getSqlType($type, $limit);
1161
        } catch (UnsupportedColumnTypeException $e) {
1162
            $type = Literal::from($type);
1163
        }
1164
1165
        $phinxType = [
1166
            'name' => $type,
1167
            'limit' => $limit,
1168
            'scale' => $scale,
1169
        ];
1170
1171
        if ($type === static::PHINX_TYPE_ENUM || $type === static::PHINX_TYPE_SET) {
1172
            $values = trim($matches[6], "()");
1173
            $phinxType['values'] = [];
1174
            $opened = false;
1175
            $escaped = false;
1176
            $wasEscaped = false;
1177
            $value = '';
1178
            $valuesLength = strlen($values);
1179
            for ($i = 0; $i < $valuesLength; $i++) {
1180
                $char = $values[$i];
1181
                if ($char === "'" && !$opened) {
1182
                    $opened = true;
1183
                } elseif (
1184
                    !$escaped
1185
                    && ($i + 1) < $valuesLength
1186
                    && (
1187
                        $char === "'" && $values[$i + 1] === "'"
1188
                        || $char === "\\" && $values[$i + 1] === "\\"
1189
                    )
1190
                ) {
1191
                    $escaped = true;
1192
                } elseif ($char === "'" && $opened && !$escaped) {
1193
                    $phinxType['values'][] = $value;
1194
                    $value = '';
1195
                    $opened = false;
1196
                } elseif (($char === "'" || $char === "\\") && $opened && $escaped) {
1197
                    $value .= $char;
1198
                    $escaped = false;
1199
                    $wasEscaped = true;
1200
                } elseif ($opened) {
1201
                    if ($values[$i - 1] === "\\" && !$wasEscaped) {
1202
                        if ($char === 'n') {
1203
                            $char = "\n";
1204
                        } elseif ($char === 'r') {
1205
                            $char = "\r";
1206
                        } elseif ($char === 't') {
1207
                            $char = "\t";
1208
                        }
1209
                        if ($values[$i] !== $char) {
1210
                            $value = substr($value, 0, strlen($value) - 1);
1211
                        }
1212
                    }
1213
                    $value .= $char;
1214
                    $wasEscaped = false;
1215
                }
1216
            }
1217
        }
1218
1219
        return $phinxType;
1220
    }
1221
1222
    /**
1223
     * @inheritDoc
1224
     */
1225
    public function createDatabase($name, $options = [])
1226
    {
1227
        $charset = $options['charset'] ?? 'utf8';
1228
1229
        if (isset($options['collation'])) {
1230
            $this->execute(sprintf(
1231
                'CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`',
1232
                $name,
1233
                $charset,
1234
                $options['collation']
1235
            ));
1236
        } else {
1237
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
1238
        }
1239
    }
1240
1241
    /**
1242
     * @inheritDoc
1243
     */
1244
    public function hasDatabase($name)
1245
    {
1246
        $rows = $this->fetchAll(
1247
            sprintf(
1248
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
1249
                $name
1250
            )
1251
        );
1252
1253
        foreach ($rows as $row) {
1254
            if (!empty($row)) {
1255
                return true;
1256
            }
1257
        }
1258
1259
        return false;
1260
    }
1261
1262
    /**
1263
     * @inheritDoc
1264
     */
1265
    public function dropDatabase($name)
1266
    {
1267
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
1268
        $this->createdTables = [];
1269
    }
1270
1271
    /**
1272
     * Gets the MySQL Column Definition for a Column object.
1273
     *
1274
     * @param \Phinx\Db\Table\Column $column Column
1275
     *
1276
     * @return string
1277
     */
1278
    protected function getColumnSqlDefinition(Column $column)
1279
    {
1280
        if ($column->getType() instanceof Literal) {
1281
            $def = (string)$column->getType();
1282
        } else {
1283
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1284
            $def = strtoupper($sqlType['name']);
1285
        }
1286
        if ($column->getPrecision() && $column->getScale()) {
1287
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1288
        } elseif (isset($sqlType['limit'])) {
1289
            $def .= '(' . $sqlType['limit'] . ')';
1290
        }
1291
        if (($values = $column->getValues()) && is_array($values)) {
1292
            $def .= "(" . implode(", ", array_map(function ($value) {
1293
                // we special case NULL as it's not actually allowed an enum value,
1294
                // and we want MySQL to issue an error on the create statement, but
1295
                // quote coerces it to an empty string, which will not error
1296
                return $value === null ? 'NULL' : $this->getConnection()->quote($value);
1297
            }, $values)) . ")";
1298
        }
1299
1300
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
1301
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
1302
        $def .= !$column->isSigned() && isset($this->signedColumnTypes[$column->getType()]) ? ' unsigned' : '';
1303
        $def .= $column->isNull() ? ' NULL' : ' NOT NULL';
1304
1305
        if (
1306
            version_compare($this->getAttribute(\PDO::ATTR_SERVER_VERSION), '8') > -1
1307
            && in_array($column->getType(), [
1308
                static::PHINX_TYPE_GEOMETRY,
1309
                static::PHINX_TYPE_POINT,
1310
                static::PHINX_TYPE_LINESTRING,
1311
                static::PHINX_TYPE_POLYGON,
1312
            ])
1313
            && !is_null($column->getSrid())
1314
        ) {
1315
            $def .= " SRID {$column->getSrid()}";
1316
        }
1317
1318
        $def .= $column->isIdentity() ? ' AUTO_INCREMENT' : '';
1319
        $def .= $this->getDefaultValueDefinition($column->getDefault(), $column->getType());
1320
1321
        if ($column->getComment()) {
1322
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
1323
        }
1324
1325
        if ($column->getUpdate()) {
1326
            $def .= ' ON UPDATE ' . $column->getUpdate();
1327
        }
1328
1329
        return $def;
1330
    }
1331
1332
    /**
1333
     * Gets the MySQL Index Definition for an Index object.
1334
     *
1335
     * @param \Phinx\Db\Table\Index $index Index
1336
     *
1337
     * @return string
1338
     */
1339
    protected function getIndexSqlDefinition(Index $index)
1340
    {
1341
        $def = '';
1342
        $limit = '';
1343
1344
        if ($index->getType() === Index::UNIQUE) {
1345
            $def .= ' UNIQUE';
1346
        }
1347
1348
        if ($index->getType() === Index::FULLTEXT) {
1349
            $def .= ' FULLTEXT';
1350
        }
1351
1352
        $def .= ' KEY';
1353
1354
        if (is_string($index->getName())) {
1355
            $def .= ' `' . $index->getName() . '`';
1356
        }
1357
1358
        $columnNames = $index->getColumns();
1359
        $order = $index->getOrder() ?? [];
1360
        $columnNames = array_map(function ($columnName) use ($order) {
1361
            $ret = '`' . $columnName . '`';
1362
            if (isset($order[$columnName])) {
1363
                $ret .= ' ' . $order[$columnName];
1364
            }
1365
1366
            return $ret;
1367
        }, $columnNames);
1368
1369
        if (!is_array($index->getLimit())) {
1370
            if ($index->getLimit()) {
1371
                $limit = '(' . $index->getLimit() . ')';
1372
            }
1373
            $def .= ' (' . implode(',', $columnNames) . $limit . ')';
1374
        } else {
1375
            $columns = $index->getColumns();
1376
            $limits = $index->getLimit();
1377
            $def .= ' (';
1378
            foreach ($columns as $column) {
1379
                $limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
1380
                $columnSort = isset($order[$column]) ?? '';
1381
                $def .= '`' . $column . '`' . $limit . ' ' . $columnSort . ', ';
1382
            }
1383
            $def = rtrim($def, ', ');
1384
            $def .= ' )';
1385
        }
1386
1387
        return $def;
1388
    }
1389
1390
    /**
1391
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
1392
     *
1393
     * @param \Phinx\Db\Table\ForeignKey $foreignKey Foreign key
1394
     *
1395
     * @return string
1396
     */
1397
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
1398
    {
1399
        $def = '';
1400
        if ($foreignKey->getConstraint()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $foreignKey->getConstraint() 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...
1401
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
1402
        }
1403
        $columnNames = [];
1404
        foreach ($foreignKey->getColumns() as $column) {
1405
            $columnNames[] = $this->quoteColumnName($column);
1406
        }
1407
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
1408
        $refColumnNames = [];
1409
        foreach ($foreignKey->getReferencedColumns() as $column) {
1410
            $refColumnNames[] = $this->quoteColumnName($column);
1411
        }
1412
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
1413
        if ($foreignKey->getOnDelete()) {
1414
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
1415
        }
1416
        if ($foreignKey->getOnUpdate()) {
1417
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
1418
        }
1419
1420
        return $def;
1421
    }
1422
1423
    /**
1424
     * Describes a database table. This is a MySQL adapter specific method.
1425
     *
1426
     * @param string $tableName Table name
1427
     *
1428
     * @return array
1429
     */
1430
    public function describeTable($tableName)
1431
    {
1432
        $options = $this->getOptions();
1433
1434
        // mysql specific
1435
        $sql = sprintf(
1436
            "SELECT *
1437
             FROM information_schema.tables
1438
             WHERE table_schema = '%s'
1439
             AND table_name = '%s'",
1440
            $options['name'],
1441
            $tableName
1442
        );
1443
1444
        return $this->fetchRow($sql);
1445
    }
1446
1447
    /**
1448
     * Returns MySQL column types (inherited and MySQL specified).
1449
     *
1450
     * @return string[]
1451
     */
1452
    public function getColumnTypes()
1453
    {
1454
        return array_merge(parent::getColumnTypes(), static::$specificColumnTypes);
1455
    }
1456
1457
    /**
1458
     * @inheritDoc
1459
     */
1460
    public function getDecoratedConnection()
1461
    {
1462
        $options = $this->getOptions();
1463
        $options = [
1464
            'username' => $options['user'] ?? null,
1465
            'password' => $options['pass'] ?? null,
1466
            'database' => $options['name'],
1467
            'quoteIdentifiers' => true,
1468
        ] + $options;
1469
1470
        $driver = new MysqlDriver($options);
1471
        $driver->setConnection($this->connection);
1472
1473
        return new Connection(['driver' => $driver] + $options);
1474
    }
1475
}
1476