Failed Conditions
Pull Request — develop (#3348)
by Sergei
65:23
created

MySqlPlatform::getDatabaseNameSql()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

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