Passed
Pull Request — master (#3070)
by Sergei
07:45
created

MySqlPlatform::getDefaultValueDeclarationSQL()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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