MySqlPlatform::getAlterTableSQL()   F
last analyzed

Complexity

Conditions 21
Paths 3240

Size

Total Lines 94
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 21.1448

Importance

Changes 0
Metric Value
eloc 57
dl 0
loc 94
ccs 54
cts 58
cp 0.931
rs 0
c 0
b 0
f 0
cc 21
nc 3240
nop 1
crap 21.1448

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Identifier;
9
use Doctrine\DBAL\Schema\Index;
10
use Doctrine\DBAL\Schema\Table;
11
use Doctrine\DBAL\Schema\TableDiff;
12
use Doctrine\DBAL\TransactionIsolationLevel;
13
use Doctrine\DBAL\Types\BlobType;
14
use Doctrine\DBAL\Types\TextType;
15
use InvalidArgumentException;
16
use function array_diff_key;
17
use function array_merge;
18
use function array_unique;
19
use function array_values;
20
use function count;
21
use function implode;
22
use function in_array;
23
use function is_numeric;
24
use function is_string;
25
use function sprintf;
26
use function str_replace;
27
use function strtoupper;
28
use function trim;
29
30
/**
31
 * The MySqlPlatform provides the behavior, features and SQL dialect of the
32
 * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that
33
 * uses the InnoDB storage engine.
34
 *
35
 * @todo   Rename: MySQLPlatform
36
 */
37
class MySqlPlatform extends AbstractPlatform
38
{
39
    public const LENGTH_LIMIT_TINYTEXT   = 255;
40
    public const LENGTH_LIMIT_TEXT       = 65535;
41
    public const LENGTH_LIMIT_MEDIUMTEXT = 16777215;
42
43
    public const LENGTH_LIMIT_TINYBLOB   = 255;
44
    public const LENGTH_LIMIT_BLOB       = 65535;
45
    public const LENGTH_LIMIT_MEDIUMBLOB = 16777215;
46
47 360
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
48
    {
49 360
        if ($limit !== null) {
50 216
            $query .= ' LIMIT ' . $limit;
51
52 216
            if ($offset > 0) {
53 216
                $query .= ' OFFSET ' . $offset;
54
            }
55 156
        } elseif ($offset > 0) {
56
            // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible
57 78
            $query .= ' LIMIT 18446744073709551615 OFFSET ' . $offset;
58
        }
59
60 360
        return $query;
61
    }
62
63 4500
    public function getIdentifierQuoteCharacter() : string
64
    {
65 4500
        return '`';
66
    }
67
68 66
    public function getRegexpExpression() : string
69
    {
70 66
        return 'RLIKE';
71
    }
72
73 12
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
74
    {
75 12
        if ($start === null) {
76 12
            return sprintf('LOCATE(%s, %s)', $substring, $string);
77
        }
78
79 12
        return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start);
80
    }
81
82 66
    public function getConcatExpression(string ...$string) : string
83
    {
84 66
        return sprintf('CONCAT(%s)', implode(', ', $string));
85
    }
86
87 576
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
88
    {
89 576
        $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB';
90
91 576
        return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')';
92
    }
93
94 36
    public function getDateDiffExpression(string $date1, string $date2) : string
95
    {
96 36
        return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')';
97
    }
98
99 1022
    public function getCurrentDatabaseExpression() : string
100
    {
101 1022
        return 'DATABASE()';
102
    }
103
104 90
    public function getListDatabasesSQL() : string
105
    {
106 90
        return 'SHOW DATABASES';
107
    }
108
109
    public function getListTableConstraintsSQL(string $table) : string
110
    {
111
        return 'SHOW INDEX FROM ' . $table;
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     *
117
     * Two approaches to listing the table indexes. The information_schema is
118
     * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table".
119
     */
120 928
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
121
    {
122 928
        if ($currentDatabase !== null) {
123 928
            $currentDatabase = $this->quoteStringLiteral($currentDatabase);
124 928
            $table           = $this->quoteStringLiteral($table);
125
126
            return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' .
127
                   ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' .
128 928
                   ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $table .
129 928
                   ' AND TABLE_SCHEMA = ' . $currentDatabase .
130 928
                   ' ORDER BY SEQ_IN_INDEX ASC';
131
        }
132
133
        return 'SHOW INDEX FROM ' . $table;
134
    }
135
136 78
    public function getListViewsSQL(string $database) : string
137
    {
138 78
        return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database);
139
    }
140
141 946
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
142
    {
143 946
        $table = $this->quoteStringLiteral($table);
144
145
        $sql = 'SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ' .
146
               'k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ' .
147
               'FROM information_schema.key_column_usage k /*!50116 ' .
148
               'INNER JOIN information_schema.referential_constraints c ON ' .
149
               '  c.constraint_name = k.constraint_name AND ' .
150 946
               '  c.table_name = ' . $table . ' */ WHERE k.table_name = ' . $table;
151
152 946
        $databaseNameSql = $this->getDatabaseNameSql($database);
153
154 946
        $sql .= ' AND k.table_schema = ' . $databaseNameSql . ' /*!50116 AND c.constraint_schema = ' . $databaseNameSql . ' */';
155 946
        $sql .= ' AND k.`REFERENCED_COLUMN_NAME` is not NULL';
156
157 946
        return $sql;
158
    }
159
160 12
    public function getCreateViewSQL(string $name, string $sql) : string
161
    {
162 12
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
163
    }
164
165 12
    public function getDropViewSQL(string $name) : string
166
    {
167 12
        return 'DROP VIEW ' . $name;
168
    }
169
170
    /**
171
     * Gets the SQL snippet used to declare a CLOB column type.
172
     *     TINYTEXT   : 2 ^  8 - 1 = 255
173
     *     TEXT       : 2 ^ 16 - 1 = 65535
174
     *     MEDIUMTEXT : 2 ^ 24 - 1 = 16777215
175
     *     LONGTEXT   : 2 ^ 32 - 1 = 4294967295
176
     *
177
     * {@inheritDoc}
178
     */
179 924
    public function getClobTypeDeclarationSQL(array $field) : string
180
    {
181 924
        if (! empty($field['length']) && is_numeric($field['length'])) {
182 100
            $length = $field['length'];
183
184 100
            if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
185 78
                return 'TINYTEXT';
186
            }
187
188 100
            if ($length <= static::LENGTH_LIMIT_TEXT) {
189 100
                return 'TEXT';
190
            }
191
192 78
            if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) {
193 78
                return 'MEDIUMTEXT';
194
            }
195
        }
196
197 902
        return 'LONGTEXT';
198
    }
199
200
    /**
201
     * {@inheritDoc}
202
     */
203 370
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
204
    {
205 370
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
206 66
            return 'TIMESTAMP';
207
        }
208
209 370
        return 'DATETIME';
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     */
215 232
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
216
    {
217 232
        return 'DATE';
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223 220
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
224
    {
225 220
        return 'TIME';
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 258
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
232
    {
233 258
        return 'TINYINT(1)';
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     *
239
     * MySql prefers "autoincrement" identity columns since sequences can only
240
     * be emulated with a table.
241
     */
242 78
    public function prefersIdentityColumns() : bool
243
    {
244 78
        return true;
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     *
250
     * MySql supports this through AUTO_INCREMENT columns.
251
     */
252 102
    public function supportsIdentityColumns() : bool
253
    {
254 102
        return true;
255
    }
256
257 4148
    public function supportsInlineColumnComments() : bool
258
    {
259 4148
        return true;
260
    }
261
262 66
    public function supportsColumnCollation() : bool
263
    {
264 66
        return true;
265
    }
266
267 132
    public function getListTablesSQL() : string
268
    {
269 132
        return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'";
270
    }
271
272 976
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
273
    {
274
        return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' .
275
               'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' .
276
               'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' .
277 976
               'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $this->getDatabaseNameSql($database) . ' ' .
278 976
               'AND TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ORDER BY ORDINAL_POSITION';
279
    }
280
281 712
    public function getListTableMetadataSQL(string $table, ?string $database = null) : string
282
    {
283 712
        return sprintf(
284
            <<<'SQL'
285
SELECT ENGINE, AUTO_INCREMENT, TABLE_COLLATION, TABLE_COMMENT, CREATE_OPTIONS
286
FROM information_schema.TABLES
287
WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s
288
SQL
289
            ,
290 712
            $database !== null ? $this->quoteStringLiteral($database) : 'DATABASE()',
291 712
            $this->quoteStringLiteral($table)
292
        );
293
    }
294
295 102
    public function getCreateDatabaseSQL(string $database) : string
296
    {
297 102
        return 'CREATE DATABASE ' . $database;
298
    }
299
300 102
    public function getDropDatabaseSQL(string $database) : string
301
    {
302 102
        return 'DROP DATABASE ' . $database;
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308 2948
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
309
    {
310 2948
        $queryFields = $this->getColumnDeclarationListSQL($columns);
311
312 2948
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
313
            foreach ($options['uniqueConstraints'] as $name => $definition) {
314
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
315
            }
316
        }
317
318
        // add all indexes
319 2948
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
320 668
            foreach ($options['indexes'] as $index => $definition) {
321 668
                $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
322
            }
323
        }
324
325
        // attach all primary keys
326 2948
        if (isset($options['primary']) && ! empty($options['primary'])) {
327 1566
            $keyColumns   = array_unique(array_values($options['primary']));
328 1566
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
329
        }
330
331 2948
        $query = 'CREATE ';
332
333 2948
        if (! empty($options['temporary'])) {
334
            $query .= 'TEMPORARY ';
335
        }
336
337 2948
        $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') ';
338 2948
        $query .= $this->buildTableOptions($options);
339 2948
        $query .= $this->buildPartitionOptions($options);
340
341 2948
        $sql    = [$query];
342 2948
        $engine = 'INNODB';
343
344 2948
        if (isset($options['engine'])) {
345 222
            $engine = strtoupper(trim($options['engine']));
346
        }
347
348
        // Propagate foreign key constraints only for InnoDB.
349 2948
        if (isset($options['foreignKeys']) && $engine === 'INNODB') {
350 1926
            foreach ((array) $options['foreignKeys'] as $definition) {
351 228
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
352
            }
353
        }
354
355 2948
        return $sql;
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361 4214
    public function getDefaultValueDeclarationSQL(array $field) : string
362
    {
363
        // Unset the default value if the given field definition does not allow default values.
364 4214
        if ($field['type'] instanceof TextType || $field['type'] instanceof BlobType) {
365 766
            $field['default'] = null;
366
        }
367
368 4214
        return parent::getDefaultValueDeclarationSQL($field);
369
    }
370
371
    /**
372
     * Build SQL for table options
373
     *
374
     * @param mixed[] $options
375
     */
376 2948
    private function buildTableOptions(array $options) : string
377
    {
378 2948
        if (isset($options['table_options'])) {
379
            return $options['table_options'];
380
        }
381
382 2948
        $tableOptions = [];
383
384
        // Charset
385 2948
        if (! isset($options['charset'])) {
386 2936
            $options['charset'] = 'utf8';
387
        }
388
389 2948
        $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
390
391
        // Collate
392 2948
        if (! isset($options['collate'])) {
393 2936
            $options['collate'] = $options['charset'] . '_unicode_ci';
394
        }
395
396 2948
        $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collate']);
397
398
        // Engine
399 2948
        if (! isset($options['engine'])) {
400 2726
            $options['engine'] = 'InnoDB';
401
        }
402
403 2948
        $tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
404
405
        // Auto increment
406 2948
        if (isset($options['auto_increment'])) {
407
            $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
408
        }
409
410
        // Comment
411 2948
        if (isset($options['comment'])) {
412 12
            $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment']));
413
        }
414
415
        // Row format
416 2948
        if (isset($options['row_format'])) {
417
            $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
418
        }
419
420 2948
        return implode(' ', $tableOptions);
421
    }
422
423
    /**
424
     * Build SQL for partition options.
425
     *
426
     * @param mixed[] $options
427
     */
428 2948
    private function buildPartitionOptions(array $options) : string
429
    {
430 2948
        return isset($options['partition_options'])
431
            ? ' ' . $options['partition_options']
432 2948
            : '';
433
    }
434
435
    /**
436
     * {@inheritDoc}
437
     */
438 1938
    public function getAlterTableSQL(TableDiff $diff) : array
439
    {
440 1938
        $columnSql  = [];
441 1938
        $queryParts = [];
442 1938
        $newName    = $diff->getNewName();
443
444 1938
        if ($newName !== null) {
445 132
            $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this);
446
        }
447
448 1938
        foreach ($diff->addedColumns as $column) {
449 420
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
450
                continue;
451
            }
452
453 420
            $columnArray            = $column->toArray();
454 420
            $columnArray['comment'] = $this->getColumnComment($column);
455 420
            $queryParts[]           = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
456
        }
457
458 1938
        foreach ($diff->removedColumns as $column) {
459 210
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
460
                continue;
461
            }
462
463 210
            $queryParts[] =  'DROP ' . $column->getQuotedName($this);
464
        }
465
466 1938
        foreach ($diff->changedColumns as $columnDiff) {
467 622
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
468
                continue;
469
            }
470
471 622
            $column      = $columnDiff->column;
472 622
            $columnArray = $column->toArray();
473
474
            // Don't propagate default value changes for unsupported column types.
475 622
            if ($columnDiff->hasChanged('default') &&
476 622
                count($columnDiff->changedProperties) === 1 &&
477 622
                ($columnArray['type'] instanceof TextType || $columnArray['type'] instanceof BlobType)
478
            ) {
479 52
                continue;
480
            }
481
482 570
            $columnArray['comment'] = $this->getColumnComment($column);
483 570
            $queryParts[]           =  'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' '
484 570
                    . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
485
        }
486
487 1938
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
488 276
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
489
                continue;
490
            }
491
492 276
            $oldColumnName          = new Identifier($oldColumnName);
493 276
            $columnArray            = $column->toArray();
494 276
            $columnArray['comment'] = $this->getColumnComment($column);
495 276
            $queryParts[]           =  'CHANGE ' . $oldColumnName->getQuotedName($this) . ' '
496 276
                    . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
497
        }
498
499 1938
        if (isset($diff->addedIndexes['primary'])) {
500 244
            $keyColumns   = array_unique(array_values($diff->addedIndexes['primary']->getColumns()));
501 244
            $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
502 244
            unset($diff->addedIndexes['primary']);
503 1694
        } elseif (isset($diff->changedIndexes['primary'])) {
504
            // Necessary in case the new primary key includes a new auto_increment column
505 298
            foreach ($diff->changedIndexes['primary']->getColumns() as $columnName) {
506 298
                if (isset($diff->addedColumns[$columnName]) && $diff->addedColumns[$columnName]->getAutoincrement()) {
507 12
                    $keyColumns   = array_unique(array_values($diff->changedIndexes['primary']->getColumns()));
508 12
                    $queryParts[] = 'DROP PRIMARY KEY';
509 12
                    $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
510 12
                    unset($diff->changedIndexes['primary']);
511 12
                    break;
512
                }
513
            }
514
        }
515
516 1938
        $sql      = [];
517 1938
        $tableSql = [];
518
519 1938
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
520 1938
            if (count($queryParts) > 0) {
521 1048
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(', ', $queryParts);
522
            }
523
524 1938
            $sql = array_merge(
525 1938
                $this->getPreAlterTableIndexForeignKeySQL($diff),
526 1938
                $sql,
527 1938
                $this->getPostAlterTableIndexForeignKeySQL($diff)
528
            );
529
        }
530
531 1938
        return array_merge($sql, $tableSql, $columnSql);
532
    }
533
534
    /**
535
     * {@inheritDoc}
536
     */
537 1938
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
538
    {
539 1938
        $sql   = [];
540 1938
        $table = $diff->getName($this)->getQuotedName($this);
541
542 1938
        foreach ($diff->changedIndexes as $changedIndex) {
543 376
            $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex));
544
        }
545
546 1938
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
547 234
            $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $remIndex));
548
549 234
            foreach ($diff->addedIndexes as $addKey => $addIndex) {
550 66
                if ($remIndex->getColumns() !== $addIndex->getColumns()) {
551
                    continue;
552
                }
553
554 66
                $indexClause = 'INDEX ' . $addIndex->getName();
555
556 66
                if ($addIndex->isPrimary()) {
557
                    $indexClause = 'PRIMARY KEY';
558 66
                } elseif ($addIndex->isUnique()) {
559 66
                    $indexClause = 'UNIQUE INDEX ' . $addIndex->getName();
560
                }
561
562 66
                $query  = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $remIndex->getName() . ', ';
563 66
                $query .= 'ADD ' . $indexClause;
564 66
                $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex) . ')';
565
566 66
                $sql[] = $query;
567
568 66
                unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]);
569
570 66
                break;
571
            }
572
        }
573
574 1938
        $engine = 'INNODB';
575
576 1938
        if ($diff->fromTable instanceof Table && $diff->fromTable->hasOption('engine')) {
577 90
            $engine = strtoupper(trim($diff->fromTable->getOption('engine')));
578
        }
579
580
        // Suppress foreign key constraint propagation on non-supporting engines.
581 1938
        if ($engine !== 'INNODB') {
582 66
            $diff->addedForeignKeys   = [];
583 66
            $diff->changedForeignKeys = [];
584 66
            $diff->removedForeignKeys = [];
585
        }
586
587 1938
        $sql = array_merge(
588 1938
            $sql,
589 1938
            $this->getPreAlterTableAlterIndexForeignKeySQL($diff),
590 1938
            parent::getPreAlterTableIndexForeignKeySQL($diff),
591 1938
            $this->getPreAlterTableRenameIndexForeignKeySQL($diff)
592
        );
593
594 1938
        return $sql;
595
    }
596
597
    /**
598
     * @return string[]
599
     */
600 598
    private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index) : array
601
    {
602 598
        $sql = [];
603
604 598
        if (! $index->isPrimary() || ! $diff->fromTable instanceof Table) {
605 234
            return $sql;
606
        }
607
608 364
        $tableName = $diff->getName($this)->getQuotedName($this);
609
610
        // Dropping primary keys requires to unset autoincrement attribute on the particular column first.
611 364
        foreach ($index->getColumns() as $columnName) {
612 364
            if (! $diff->fromTable->hasColumn($columnName)) {
613 66
                continue;
614
            }
615
616 364
            $column = $diff->fromTable->getColumn($columnName);
617
618 364
            if (! $column->getAutoincrement()) {
619 298
                continue;
620
            }
621
622 210
            $column->setAutoincrement(false);
623
624 210
            $sql[] = 'ALTER TABLE ' . $tableName . ' MODIFY ' .
625 210
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
626
627
            // original autoincrement information might be needed later on by other parts of the table alteration
628 210
            $column->setAutoincrement(true);
629
        }
630
631 364
        return $sql;
632
    }
633
634
    /**
635
     * @param TableDiff $diff The table diff to gather the SQL for.
636
     *
637
     * @return string[]
638
     */
639 1938
    private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff) : array
640
    {
641 1938
        $sql   = [];
642 1938
        $table = $diff->getName($this)->getQuotedName($this);
643
644 1938
        foreach ($diff->changedIndexes as $changedIndex) {
645
            // Changed primary key
646 376
            if (! $changedIndex->isPrimary() || ! ($diff->fromTable instanceof Table)) {
647 90
                continue;
648
            }
649
650 286
            foreach ($diff->fromTable->getPrimaryKeyColumns() as $columnName) {
651 286
                $column = $diff->fromTable->getColumn($columnName);
652
653
                // Check if an autoincrement column was dropped from the primary key.
654 286
                if (! $column->getAutoincrement() || in_array($columnName, $changedIndex->getColumns(), true)) {
655 220
                    continue;
656
                }
657
658
                // The autoincrement attribute needs to be removed from the dropped column
659
                // before we can drop and recreate the primary key.
660 66
                $column->setAutoincrement(false);
661
662 66
                $sql[] = 'ALTER TABLE ' . $table . ' MODIFY ' .
663 66
                    $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
664
665
                // Restore the autoincrement attribute as it might be needed later on
666
                // by other parts of the table alteration.
667 66
                $column->setAutoincrement(true);
668
            }
669
        }
670
671 1938
        return $sql;
672
    }
673
674
    /**
675
     * @param TableDiff $diff The table diff to gather the SQL for.
676
     *
677
     * @return string[]
678
     */
679 1298
    protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) : array
680
    {
681 1298
        $sql       = [];
682 1298
        $tableName = $diff->getName($this)->getQuotedName($this);
683
684 1298
        foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
685 52
            if (in_array($foreignKey, $diff->changedForeignKeys, true)) {
686
                continue;
687
            }
688
689 52
            $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
690
        }
691
692 1298
        return $sql;
693
    }
694
695
    /**
696
     * Returns the remaining foreign key constraints that require one of the renamed indexes.
697
     *
698
     * "Remaining" here refers to the diff between the foreign keys currently defined in the associated
699
     * table and the foreign keys to be removed.
700
     *
701
     * @param TableDiff $diff The table diff to evaluate.
702
     *
703
     * @return ForeignKeyConstraint[]
704
     */
705 1298
    private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff) : array
706
    {
707 1298
        if (empty($diff->renamedIndexes) || ! $diff->fromTable instanceof Table) {
708 1070
            return [];
709
        }
710
711 236
        $foreignKeys = [];
712
        /** @var ForeignKeyConstraint[] $remainingForeignKeys */
713 236
        $remainingForeignKeys = array_diff_key(
714 236
            $diff->fromTable->getForeignKeys(),
715 236
            $diff->removedForeignKeys
716
        );
717
718 236
        foreach ($remainingForeignKeys as $foreignKey) {
719 52
            foreach ($diff->renamedIndexes as $index) {
720 52
                if ($foreignKey->intersectsIndexColumns($index)) {
721 52
                    $foreignKeys[] = $foreignKey;
722
723 52
                    break;
724
                }
725
            }
726
        }
727
728 236
        return $foreignKeys;
729
    }
730
731
    /**
732
     * {@inheritdoc}
733
     */
734 1938
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
735
    {
736 1938
        return array_merge(
737 1938
            parent::getPostAlterTableIndexForeignKeySQL($diff),
738 1938
            $this->getPostAlterTableRenameIndexForeignKeySQL($diff)
739
        );
740
    }
741
742
    /**
743
     * @param TableDiff $diff The table diff to gather the SQL for.
744
     *
745
     * @return string[]
746
     */
747 1298
    protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) : array
748
    {
749 1298
        $sql     = [];
750 1298
        $newName = $diff->getNewName();
751
752 1298
        if ($newName !== null) {
753 88
            $tableName = $newName->getQuotedName($this);
754
        } else {
755 1210
            $tableName = $diff->getName($this)->getQuotedName($this);
756
        }
757
758 1298
        foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
759 52
            if (in_array($foreignKey, $diff->changedForeignKeys, true)) {
760
                continue;
761
            }
762
763 52
            $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
764
        }
765
766 1298
        return $sql;
767
    }
768
769 1230
    protected function getCreateIndexSQLFlags(Index $index) : string
770
    {
771 1230
        $type = '';
772 1230
        if ($index->isUnique()) {
773 246
            $type .= 'UNIQUE ';
774 1008
        } elseif ($index->hasFlag('fulltext')) {
775 78
            $type .= 'FULLTEXT ';
776 930
        } elseif ($index->hasFlag('spatial')) {
777 78
            $type .= 'SPATIAL ';
778
        }
779
780 1230
        return $type;
781
    }
782
783
    /**
784
     * {@inheritDoc}
785
     */
786 2952
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
787
    {
788 2952
        return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
789
    }
790
791
    /**
792
     * {@inheritDoc}
793
     */
794 180
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
795
    {
796 180
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
797
    }
798
799
    /**
800
     * {@inheritDoc}
801
     */
802 12
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
803
    {
804 12
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
805
    }
806
807
    /**
808
     * {@inheritdoc}
809
     */
810 588
    public function getFloatDeclarationSQL(array $fieldDeclaration) : string
811
    {
812 588
        return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($fieldDeclaration);
813
    }
814
815
    /**
816
     * {@inheritdoc}
817
     */
818 636
    public function getDecimalTypeDeclarationSQL(array $columnDef) : string
819
    {
820 636
        return parent::getDecimalTypeDeclarationSQL($columnDef) . $this->getUnsignedDeclaration($columnDef);
821
    }
822
823
    /**
824
     * Get unsigned declaration for a column.
825
     *
826
     * @param mixed[] $columnDef
827
     */
828 3768
    private function getUnsignedDeclaration(array $columnDef) : string
829
    {
830 3768
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
831
    }
832
833
    /**
834
     * {@inheritDoc}
835
     */
836 2952
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
837
    {
838 2952
        $autoinc = '';
839 2952
        if (! empty($columnDef['autoincrement'])) {
840 474
            $autoinc = ' AUTO_INCREMENT';
841
        }
842
843 2952
        return $this->getUnsignedDeclaration($columnDef) . $autoinc;
844
    }
845
846 102
    public function getColumnCharsetDeclarationSQL(string $charset) : string
847
    {
848 102
        return 'CHARACTER SET ' . $charset;
849
    }
850
851 3014
    public function getColumnCollationDeclarationSQL(string $collation) : string
852
    {
853 3014
        return 'COLLATE ' . $this->quoteSingleIdentifier($collation);
854
    }
855
856 552
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
857
    {
858 552
        $query = '';
859 552
        if ($foreignKey->hasOption('match')) {
860
            $query .= ' MATCH ' . $foreignKey->getOption('match');
861
        }
862
863 552
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
864
865 552
        return $query;
866
    }
867
868
    /**
869
     * {@inheritDoc}
870
     */
871 772
    public function getDropIndexSQL($index, $table = null) : string
872
    {
873 772
        if ($index instanceof Index) {
874 532
            $indexName = $index->getQuotedName($this);
875 248
        } elseif (is_string($index)) {
876 248
            $indexName = $index;
877
        } else {
878
            throw new InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
879
        }
880
881 772
        if ($table instanceof Table) {
882 12
            $table = $table->getQuotedName($this);
883 760
        } elseif (! is_string($table)) {
884
            throw new InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
885
        }
886
887 772
        if ($index instanceof Index && $index->isPrimary()) {
888
            // mysql primary keys are always named "PRIMARY",
889
            // so we cannot use them in statements because of them being keyword.
890 430
            return $this->getDropPrimaryKeySQL($table);
891
        }
892
893 342
        return 'DROP INDEX ' . $indexName . ' ON ' . $table;
894
    }
895
896 430
    protected function getDropPrimaryKeySQL(string $table) : string
897
    {
898 430
        return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
899
    }
900
901 66
    public function getSetTransactionIsolationSQL(int $level) : string
902
    {
903 66
        return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
904
    }
905
906 1204
    public function getName() : string
907
    {
908 1204
        return 'mysql';
909
    }
910
911
    public function getReadLockSQL() : string
912
    {
913
        return 'LOCK IN SHARE MODE';
914
    }
915
916 398
    protected function initializeDoctrineTypeMappings() : void
917
    {
918 398
        $this->doctrineTypeMapping = [
919
            'bigint'     => 'bigint',
920
            'binary'     => 'binary',
921
            'blob'       => 'blob',
922
            'char'       => 'string',
923
            'date'       => 'date',
924
            'datetime'   => 'datetime',
925
            'decimal'    => 'decimal',
926
            'double'     => 'float',
927
            'float'      => 'float',
928
            'int'        => 'integer',
929
            'integer'    => 'integer',
930
            'longblob'   => 'blob',
931
            'longtext'   => 'text',
932
            'mediumblob' => 'blob',
933
            'mediumint'  => 'integer',
934
            'mediumtext' => 'text',
935
            'numeric'    => 'decimal',
936
            'real'       => 'float',
937
            'set'        => 'simple_array',
938
            'smallint'   => 'smallint',
939
            'string'     => 'string',
940
            'text'       => 'text',
941
            'time'       => 'time',
942
            'timestamp'  => 'datetime',
943
            'tinyblob'   => 'blob',
944
            'tinyint'    => 'boolean',
945
            'tinytext'   => 'text',
946
            'varbinary'  => 'binary',
947
            'varchar'    => 'string',
948
            'year'       => 'date',
949
        ];
950 398
    }
951
952 1422
    protected function getReservedKeywordsClass() : string
953
    {
954 1422
        return Keywords\MySQLKeywords::class;
955
    }
956
957
    /**
958
     * {@inheritDoc}
959
     *
960
     * MySQL commits a transaction implicitly when DROP TABLE is executed, however not
961
     * if DROP TEMPORARY TABLE is executed.
962
     */
963 24
    public function getDropTemporaryTableSQL($table) : string
964
    {
965 24
        if ($table instanceof Table) {
966
            $table = $table->getQuotedName($this);
967 24
        } elseif (! is_string($table)) {
968
            throw new InvalidArgumentException('getDropTemporaryTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
969
        }
970
971 24
        return 'DROP TEMPORARY TABLE ' . $table;
972
    }
973
974
    /**
975
     * Gets the SQL Snippet used to declare a BLOB column type.
976
     *     TINYBLOB   : 2 ^  8 - 1 = 255
977
     *     BLOB       : 2 ^ 16 - 1 = 65535
978
     *     MEDIUMBLOB : 2 ^ 24 - 1 = 16777215
979
     *     LONGBLOB   : 2 ^ 32 - 1 = 4294967295
980
     *
981
     * {@inheritDoc}
982
     */
983 226
    public function getBlobTypeDeclarationSQL(array $field) : string
984
    {
985 226
        if (! empty($field['length']) && is_numeric($field['length'])) {
986 90
            $length = $field['length'];
987
988 90
            if ($length <= static::LENGTH_LIMIT_TINYBLOB) {
989 78
                return 'TINYBLOB';
990
            }
991
992 90
            if ($length <= static::LENGTH_LIMIT_BLOB) {
993 78
                return 'BLOB';
994
            }
995
996 90
            if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) {
997 78
                return 'MEDIUMBLOB';
998
            }
999
        }
1000
1001 226
        return 'LONGBLOB';
1002
    }
1003
1004 2710
    public function quoteStringLiteral(string $str) : string
1005
    {
1006 2710
        $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped aswell.
1007
1008 2710
        return parent::quoteStringLiteral($str);
1009
    }
1010
1011 22
    public function getDefaultTransactionIsolationLevel() : int
1012
    {
1013 22
        return TransactionIsolationLevel::REPEATABLE_READ;
1014
    }
1015
1016 3046
    public function supportsColumnLengthIndexes() : bool
1017
    {
1018 3046
        return true;
1019
    }
1020
1021
    /**
1022
     * Returns an SQL expression representing the given database name or current database name
1023
     *
1024
     * @param string|null $database Database name
1025
     */
1026 1210
    private function getDatabaseNameSql(?string $database) : string
1027
    {
1028 1210
        if ($database === null) {
1029 198
            return 'DATABASE()';
1030
        }
1031
1032 1078
        return $this->quoteStringLiteral($database);
1033
    }
1034
}
1035