Completed
Pull Request — develop (#3515)
by Sergei
20:18
created

MySqlPlatform::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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