Completed
Push — develop ( dcb0ff...425513 )
by Sergei
23s queued 13s
created

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