Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\Platforms;
4
 
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Exception;
7
use Doctrine\DBAL\Schema\AbstractAsset;
8
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
9
use Doctrine\DBAL\Schema\Identifier;
10
use Doctrine\DBAL\Schema\Index;
11
use Doctrine\DBAL\Schema\MySQLSchemaManager;
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Schema\TableDiff;
14
use Doctrine\DBAL\TransactionIsolationLevel;
15
use Doctrine\DBAL\Types\BlobType;
16
use Doctrine\DBAL\Types\TextType;
17
use Doctrine\Deprecations\Deprecation;
18
use InvalidArgumentException;
19
 
20
use function array_diff_key;
21
use function array_merge;
22
use function array_unique;
23
use function array_values;
24
use function count;
25
use function func_get_arg;
26
use function func_get_args;
27
use function func_num_args;
28
use function implode;
29
use function in_array;
30
use function is_numeric;
31
use function is_string;
32
use function sprintf;
33
use function str_replace;
34
use function strcasecmp;
35
use function strtolower;
36
use function strtoupper;
37
use function trim;
38
 
39
/**
40
 * Provides the base implementation for the lowest versions of supported MySQL-like database platforms.
41
 */
42
abstract class AbstractMySQLPlatform extends AbstractPlatform
43
{
44
    public const LENGTH_LIMIT_TINYTEXT   = 255;
45
    public const LENGTH_LIMIT_TEXT       = 65535;
46
    public const LENGTH_LIMIT_MEDIUMTEXT = 16777215;
47
 
48
    public const LENGTH_LIMIT_TINYBLOB   = 255;
49
    public const LENGTH_LIMIT_BLOB       = 65535;
50
    public const LENGTH_LIMIT_MEDIUMBLOB = 16777215;
51
 
52
    /**
53
     * {@inheritDoc}
54
     */
55
    protected function doModifyLimitQuery($query, $limit, $offset)
56
    {
57
        if ($limit !== null) {
58
            $query .= sprintf(' LIMIT %d', $limit);
59
 
60
            if ($offset > 0) {
61
                $query .= sprintf(' OFFSET %d', $offset);
62
            }
63
        } elseif ($offset > 0) {
64
            // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible
65
            $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset);
66
        }
67
 
68
        return $query;
69
    }
70
 
71
    /**
72
     * {@inheritDoc}
73
     *
74
     * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead.
75
     */
76
    public function getIdentifierQuoteCharacter()
77
    {
78
        Deprecation::triggerIfCalledFromOutside(
79
            'doctrine/dbal',
80
            'https://github.com/doctrine/dbal/pull/5388',
81
            'AbstractMySQLPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.',
82
        );
83
 
84
        return '`';
85
    }
86
 
87
    /**
88
     * {@inheritDoc}
89
     */
90
    public function getRegexpExpression()
91
    {
92
        return 'RLIKE';
93
    }
94
 
95
    /**
96
     * {@inheritDoc}
97
     */
98
    public function getLocateExpression($str, $substr, $startPos = false)
99
    {
100
        if ($startPos === false) {
101
            return 'LOCATE(' . $substr . ', ' . $str . ')';
102
        }
103
 
104
        return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')';
105
    }
106
 
107
    /**
108
     * {@inheritDoc}
109
     */
110
    public function getConcatExpression()
111
    {
112
        return sprintf('CONCAT(%s)', implode(', ', func_get_args()));
113
    }
114
 
115
    /**
116
     * {@inheritdoc}
117
     */
118
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
119
    {
120
        $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB';
121
 
122
        return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')';
123
    }
124
 
125
    /**
126
     * {@inheritDoc}
127
     */
128
    public function getDateDiffExpression($date1, $date2)
129
    {
130
        return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')';
131
    }
132
 
133
    public function getCurrentDatabaseExpression(): string
134
    {
135
        return 'DATABASE()';
136
    }
137
 
138
    /**
139
     * {@inheritDoc}
140
     */
141
    public function getLengthExpression($column)
142
    {
143
        return 'CHAR_LENGTH(' . $column . ')';
144
    }
145
 
146
    /**
147
     * {@inheritDoc}
148
     *
149
     * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
150
     */
151
    public function getListDatabasesSQL()
152
    {
153
        return 'SHOW DATABASES';
154
    }
155
 
156
    /**
157
     * @deprecated
158
     *
159
     * {@inheritDoc}
160
     */
161
    public function getListTableConstraintsSQL($table)
162
    {
163
        return 'SHOW INDEX FROM ' . $table;
164
    }
165
 
166
    /**
167
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
168
     *
169
     * {@inheritDoc}
170
     *
171
     * Two approaches to listing the table indexes. The information_schema is
172
     * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table".
173
     */
174
    public function getListTableIndexesSQL($table, $database = null)
175
    {
176
        if ($database !== null) {
177
            return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' .
178
                   ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' .
179
                   ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $this->quoteStringLiteral($table) .
180
                   ' AND TABLE_SCHEMA = ' . $this->quoteStringLiteral($database) .
181
                   ' ORDER BY SEQ_IN_INDEX ASC';
182
        }
183
 
184
        return 'SHOW INDEX FROM ' . $table;
185
    }
186
 
187
    /**
188
     * {@inheritDoc}
189
     *
190
     * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
191
     */
192
    public function getListViewsSQL($database)
193
    {
194
        return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database);
195
    }
196
 
197
    /**
198
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
199
     *
200
     * @param string      $table
201
     * @param string|null $database
202
     *
203
     * @return string
204
     */
205
    public function getListTableForeignKeysSQL($table, $database = null)
206
    {
207
        // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition
208
        // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
209
        // caused by https://bugs.mysql.com/bug.php?id=81347
210
        return 'SELECT k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, ' .
211
               'k.REFERENCED_COLUMN_NAME /*!50116 , c.UPDATE_RULE, c.DELETE_RULE */ ' .
212
               'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k /*!50116 ' .
213
               'INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS c ON ' .
214
               'c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND ' .
215
               'c.TABLE_NAME = k.TABLE_NAME */ ' .
216
               'WHERE k.TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ' .
217
               'AND k.TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' /*!50116 ' .
218
               'AND c.CONSTRAINT_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' */' .
219
               'ORDER BY k.ORDINAL_POSITION';
220
    }
221
 
222
    /**
223
     * {@inheritDoc}
224
     */
225
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/)
226
    {
227
        if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) {
228
            Deprecation::trigger(
229
                'doctrine/dbal',
230
                'https://github.com/doctrine/dbal/issues/3263',
231
                'Relying on the default string column length on MySQL is deprecated'
232
                    . ', specify the length explicitly.',
233
            );
234
        }
235
 
236
        return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)')
237
                : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
238
    }
239
 
240
    /**
241
     * {@inheritdoc}
242
     */
243
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/)
244
    {
245
        if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) {
246
            Deprecation::trigger(
247
                'doctrine/dbal',
248
                'https://github.com/doctrine/dbal/issues/3263',
249
                'Relying on the default binary column length on MySQL is deprecated'
250
                . ', specify the length explicitly.',
251
            );
252
        }
253
 
254
        return $fixed
255
            ? 'BINARY(' . ($length > 0 ? $length : 255) . ')'
256
            : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')';
257
    }
258
 
259
    /**
260
     * Gets the SQL snippet used to declare a CLOB column type.
261
     *     TINYTEXT   : 2 ^  8 - 1 = 255
262
     *     TEXT       : 2 ^ 16 - 1 = 65535
263
     *     MEDIUMTEXT : 2 ^ 24 - 1 = 16777215
264
     *     LONGTEXT   : 2 ^ 32 - 1 = 4294967295
265
     *
266
     * {@inheritDoc}
267
     */
268
    public function getClobTypeDeclarationSQL(array $column)
269
    {
270
        if (! empty($column['length']) && is_numeric($column['length'])) {
271
            $length = $column['length'];
272
 
273
            if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
274
                return 'TINYTEXT';
275
            }
276
 
277
            if ($length <= static::LENGTH_LIMIT_TEXT) {
278
                return 'TEXT';
279
            }
280
 
281
            if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) {
282
                return 'MEDIUMTEXT';
283
            }
284
        }
285
 
286
        return 'LONGTEXT';
287
    }
288
 
289
    /**
290
     * {@inheritDoc}
291
     */
292
    public function getDateTimeTypeDeclarationSQL(array $column)
293
    {
294
        if (isset($column['version']) && $column['version'] === true) {
295
            return 'TIMESTAMP';
296
        }
297
 
298
        return 'DATETIME';
299
    }
300
 
301
    /**
302
     * {@inheritDoc}
303
     */
304
    public function getDateTypeDeclarationSQL(array $column)
305
    {
306
        return 'DATE';
307
    }
308
 
309
    /**
310
     * {@inheritDoc}
311
     */
312
    public function getTimeTypeDeclarationSQL(array $column)
313
    {
314
        return 'TIME';
315
    }
316
 
317
    /**
318
     * {@inheritDoc}
319
     */
320
    public function getBooleanTypeDeclarationSQL(array $column)
321
    {
322
        return 'TINYINT(1)';
323
    }
324
 
325
    /**
326
     * {@inheritDoc}
327
     *
328
     * @deprecated
329
     *
330
     * MySQL prefers "autoincrement" identity columns since sequences can only
331
     * be emulated with a table.
332
     */
333
    public function prefersIdentityColumns()
334
    {
335
        Deprecation::trigger(
336
            'doctrine/dbal',
337
            'https://github.com/doctrine/dbal/pull/1519',
338
            'AbstractMySQLPlatform::prefersIdentityColumns() is deprecated.',
339
        );
340
 
341
        return true;
342
    }
343
 
344
    /**
345
     * {@inheritDoc}
346
     *
347
     * MySQL supports this through AUTO_INCREMENT columns.
348
     */
349
    public function supportsIdentityColumns()
350
    {
351
        return true;
352
    }
353
 
354
    /**
355
     * {@inheritDoc}
356
     *
357
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
358
     */
359
    public function supportsInlineColumnComments()
360
    {
361
        return true;
362
    }
363
 
364
    /**
365
     * {@inheritDoc}
366
     *
367
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
368
     */
369
    public function supportsColumnCollation()
370
    {
371
        return true;
372
    }
373
 
374
    /**
375
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
376
     *
377
     * {@inheritDoc}
378
     */
379
    public function getListTablesSQL()
380
    {
381
        return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'";
382
    }
383
 
384
    /**
385
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
386
     *
387
     * {@inheritDoc}
388
     */
389
    public function getListTableColumnsSQL($table, $database = null)
390
    {
391
        return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' .
392
               'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' .
393
               'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' .
394
               'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) .
395
               ' AND TABLE_NAME = ' . $this->quoteStringLiteral($table) .
396
               ' ORDER BY ORDINAL_POSITION ASC';
397
    }
398
 
399
    /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */
400
    public function getListTableMetadataSQL(string $table, ?string $database = null): string
401
    {
402
        return sprintf(
403
            <<<'SQL'
404
SELECT t.ENGINE,
405
       t.AUTO_INCREMENT,
406
       t.TABLE_COMMENT,
407
       t.CREATE_OPTIONS,
408
       t.TABLE_COLLATION,
409
       ccsa.CHARACTER_SET_NAME
410
FROM information_schema.TABLES t
411
    INNER JOIN information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` ccsa
412
        ON ccsa.COLLATION_NAME = t.TABLE_COLLATION
413
WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s
414
SQL
415
            ,
416
            $this->getDatabaseNameSQL($database),
417
            $this->quoteStringLiteral($table),
418
        );
419
    }
420
 
421
    /**
422
     * {@inheritDoc}
423
     */
424
    public function getCreateTablesSQL(array $tables): array
425
    {
426
        $sql = [];
427
 
428
        foreach ($tables as $table) {
429
            $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table));
430
        }
431
 
432
        foreach ($tables as $table) {
433
            if (! $table->hasOption('engine') || $this->engineSupportsForeignKeys($table->getOption('engine'))) {
434
                foreach ($table->getForeignKeys() as $foreignKey) {
435
                    $sql[] = $this->getCreateForeignKeySQL(
436
                        $foreignKey,
437
                        $table->getQuotedName($this),
438
                    );
439
                }
440
            } elseif (count($table->getForeignKeys()) > 0) {
441
                Deprecation::trigger(
442
                    'doctrine/dbal',
443
                    'https://github.com/doctrine/dbal/pull/5414',
444
                    'Relying on the DBAL not generating DDL for foreign keys on MySQL engines'
445
                        . ' other than InnoDB is deprecated.'
446
                        . ' Define foreign key constraints only if they are necessary.',
447
                );
448
            }
449
        }
450
 
451
        return $sql;
452
    }
453
 
454
    /**
455
     * {@inheritDoc}
456
     */
457
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
458
    {
459
        $queryFields = $this->getColumnDeclarationListSQL($columns);
460
 
461
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
462
            foreach ($options['uniqueConstraints'] as $constraintName => $definition) {
463
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition);
464
            }
465
        }
466
 
467
        // add all indexes
468
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
469
            foreach ($options['indexes'] as $indexName => $definition) {
470
                $queryFields .= ', ' . $this->getIndexDeclarationSQL($indexName, $definition);
471
            }
472
        }
473
 
474
        // attach all primary keys
475
        if (isset($options['primary']) && ! empty($options['primary'])) {
476
            $keyColumns   = array_unique(array_values($options['primary']));
477
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
478
        }
479
 
480
        $query = 'CREATE ';
481
 
482
        if (! empty($options['temporary'])) {
483
            $query .= 'TEMPORARY ';
484
        }
485
 
486
        $query .= 'TABLE ' . $name . ' (' . $queryFields . ') ';
487
        $query .= $this->buildTableOptions($options);
488
        $query .= $this->buildPartitionOptions($options);
489
 
490
        $sql = [$query];
491
 
492
        // Propagate foreign key constraints only for InnoDB.
493
        if (isset($options['foreignKeys'])) {
494
            if (! isset($options['engine']) || $this->engineSupportsForeignKeys($options['engine'])) {
495
                foreach ($options['foreignKeys'] as $definition) {
496
                    $sql[] = $this->getCreateForeignKeySQL($definition, $name);
497
                }
498
            } elseif (count($options['foreignKeys']) > 0) {
499
                Deprecation::trigger(
500
                    'doctrine/dbal',
501
                    'https://github.com/doctrine/dbal/pull/5414',
502
                    'Relying on the DBAL not generating DDL for foreign keys on MySQL engines'
503
                    . ' other than InnoDB is deprecated.'
504
                    . ' Define foreign key constraints only if they are necessary.',
505
                );
506
            }
507
        }
508
 
509
        return $sql;
510
    }
511
 
512
    /**
513
     * {@inheritdoc}
514
     *
515
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
516
     */
517
    public function getDefaultValueDeclarationSQL($column)
518
    {
519
        // Unset the default value if the given column definition does not allow default values.
520
        if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) {
521
            $column['default'] = null;
522
        }
523
 
524
        return parent::getDefaultValueDeclarationSQL($column);
525
    }
526
 
527
    /**
528
     * Build SQL for table options
529
     *
530
     * @param mixed[] $options
531
     */
532
    private function buildTableOptions(array $options): string
533
    {
534
        if (isset($options['table_options'])) {
535
            return $options['table_options'];
536
        }
537
 
538
        $tableOptions = [];
539
 
540
        // Charset
541
        if (! isset($options['charset'])) {
542
            $options['charset'] = 'utf8';
543
        }
544
 
545
        $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
546
 
547
        if (isset($options['collate'])) {
548
            Deprecation::trigger(
549
                'doctrine/dbal',
550
                'https://github.com/doctrine/dbal/issues/5214',
551
                'The "collate" option is deprecated in favor of "collation" and will be removed in 4.0.',
552
            );
553
            $options['collation'] = $options['collate'];
554
        }
555
 
556
        // Collation
557
        if (! isset($options['collation'])) {
558
            $options['collation'] = $options['charset'] . '_unicode_ci';
559
        }
560
 
561
        $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']);
562
 
563
        // Engine
564
        if (! isset($options['engine'])) {
565
            $options['engine'] = 'InnoDB';
566
        }
567
 
568
        $tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
569
 
570
        // Auto increment
571
        if (isset($options['auto_increment'])) {
572
            $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
573
        }
574
 
575
        // Comment
576
        if (isset($options['comment'])) {
577
            $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment']));
578
        }
579
 
580
        // Row format
581
        if (isset($options['row_format'])) {
582
            $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
583
        }
584
 
585
        return implode(' ', $tableOptions);
586
    }
587
 
588
    /**
589
     * Build SQL for partition options.
590
     *
591
     * @param mixed[] $options
592
     */
593
    private function buildPartitionOptions(array $options): string
594
    {
595
        return isset($options['partition_options'])
596
            ? ' ' . $options['partition_options']
597
            : '';
598
    }
599
 
600
    private function engineSupportsForeignKeys(string $engine): bool
601
    {
602
        return strcasecmp(trim($engine), 'InnoDB') === 0;
603
    }
604
 
605
    /**
606
     * {@inheritDoc}
607
     */
608
    public function getAlterTableSQL(TableDiff $diff)
609
    {
610
        $columnSql  = [];
611
        $queryParts = [];
612
        $newName    = $diff->getNewName();
613
 
614
        if ($newName !== false) {
615
            Deprecation::trigger(
616
                'doctrine/dbal',
617
                'https://github.com/doctrine/dbal/pull/5663',
618
                'Generation of SQL that renames a table using %s is deprecated. Use getRenameTableSQL() instead.',
619
                __METHOD__,
620
            );
621
 
622
            $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this);
623
        }
624
 
625
        foreach ($diff->getAddedColumns() as $column) {
626
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
627
                continue;
628
            }
629
 
630
            $columnProperties = array_merge($column->toArray(), [
631
                'comment' => $this->getColumnComment($column),
632
            ]);
633
 
634
            $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL(
635
                $column->getQuotedName($this),
636
                $columnProperties,
637
            );
638
        }
639
 
640
        foreach ($diff->getDroppedColumns() as $column) {
641
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
642
                continue;
643
            }
644
 
645
            $queryParts[] =  'DROP ' . $column->getQuotedName($this);
646
        }
647
 
648
        foreach ($diff->getModifiedColumns() as $columnDiff) {
649
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
650
                continue;
651
            }
652
 
653
            $newColumn = $columnDiff->getNewColumn();
654
 
655
            $newColumnProperties = array_merge($newColumn->toArray(), [
656
                'comment' => $this->getColumnComment($newColumn),
657
            ]);
658
 
659
            $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName();
660
 
661
            $queryParts[] =  'CHANGE ' . $oldColumn->getQuotedName($this) . ' '
662
                . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties);
663
        }
664
 
665
        foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
666
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
667
                continue;
668
            }
669
 
670
            $oldColumnName = new Identifier($oldColumnName);
671
 
672
            $columnProperties = array_merge($column->toArray(), [
673
                'comment' => $this->getColumnComment($column),
674
            ]);
675
 
676
            $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' '
677
                . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties);
678
        }
679
 
680
        $addedIndexes    = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes());
681
        $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes());
682
        $diffModified    = false;
683
 
684
        if (isset($addedIndexes['primary'])) {
685
            $keyColumns   = array_unique(array_values($addedIndexes['primary']->getColumns()));
686
            $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
687
            unset($addedIndexes['primary']);
688
            $diffModified = true;
689
        } elseif (isset($modifiedIndexes['primary'])) {
690
            $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns());
691
 
692
            // Necessary in case the new primary key includes a new auto_increment column
693
            foreach ($modifiedIndexes['primary']->getColumns() as $columnName) {
694
                if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) {
695
                    $keyColumns   = array_unique(array_values($modifiedIndexes['primary']->getColumns()));
696
                    $queryParts[] = 'DROP PRIMARY KEY';
697
                    $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
698
                    unset($modifiedIndexes['primary']);
699
                    $diffModified = true;
700
                    break;
701
                }
702
            }
703
        }
704
 
705
        if ($diffModified) {
706
            $diff = new TableDiff(
707
                $diff->name,
708
                $diff->getAddedColumns(),
709
                $diff->getModifiedColumns(),
710
                $diff->getDroppedColumns(),
711
                array_values($addedIndexes),
712
                array_values($modifiedIndexes),
713
                $diff->getDroppedIndexes(),
714
                $diff->getOldTable(),
715
                $diff->getAddedForeignKeys(),
716
                $diff->getModifiedForeignKeys(),
717
                $diff->getDroppedForeignKeys(),
718
                $diff->getRenamedColumns(),
719
                $diff->getRenamedIndexes(),
720
            );
721
        }
722
 
723
        $sql      = [];
724
        $tableSql = [];
725
 
726
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
727
            if (count($queryParts) > 0) {
728
                $sql[] = 'ALTER TABLE ' . ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this) . ' '
729
                    . implode(', ', $queryParts);
730
            }
731
 
732
            $sql = array_merge(
733
                $this->getPreAlterTableIndexForeignKeySQL($diff),
734
                $sql,
735
                $this->getPostAlterTableIndexForeignKeySQL($diff),
736
            );
737
        }
738
 
739
        return array_merge($sql, $tableSql, $columnSql);
740
    }
741
 
742
    /**
743
     * {@inheritDoc}
744
     */
745
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
746
    {
747
        $sql = [];
748
 
749
        $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this);
750
 
751
        foreach ($diff->getModifiedIndexes() as $changedIndex) {
752
            $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex));
753
        }
754
 
755
        foreach ($diff->getDroppedIndexes() as $droppedIndex) {
756
            $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex));
757
 
758
            foreach ($diff->getAddedIndexes() as $addedIndex) {
759
                if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) {
760
                    continue;
761
                }
762
 
763
                $indexClause = 'INDEX ' . $addedIndex->getName();
764
 
765
                if ($addedIndex->isPrimary()) {
766
                    $indexClause = 'PRIMARY KEY';
767
                } elseif ($addedIndex->isUnique()) {
768
                    $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName();
769
                }
770
 
771
                $query  = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', ';
772
                $query .= 'ADD ' . $indexClause;
773
                $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addedIndex) . ')';
774
 
775
                $sql[] = $query;
776
 
777
                $diff->unsetAddedIndex($addedIndex);
778
                $diff->unsetDroppedIndex($droppedIndex);
779
 
780
                break;
781
            }
782
        }
783
 
784
        $engine = 'INNODB';
785
 
786
        $table = $diff->getOldTable();
787
 
788
        if ($table !== null && $table->hasOption('engine')) {
789
            $engine = strtoupper(trim($table->getOption('engine')));
790
        }
791
 
792
        // Suppress foreign key constraint propagation on non-supporting engines.
793
        if ($engine !== 'INNODB') {
794
            $diff->addedForeignKeys   = [];
795
            $diff->changedForeignKeys = [];
796
            $diff->removedForeignKeys = [];
797
        }
798
 
799
        $sql = array_merge(
800
            $sql,
801
            $this->getPreAlterTableAlterIndexForeignKeySQL($diff),
802
            parent::getPreAlterTableIndexForeignKeySQL($diff),
803
            $this->getPreAlterTableRenameIndexForeignKeySQL($diff),
804
        );
805
 
806
        return $sql;
807
    }
808
 
809
    /**
810
     * @return string[]
811
     *
812
     * @throws Exception
813
     */
814
    private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array
815
    {
816
        if (! $index->isPrimary()) {
817
            return [];
818
        }
819
 
820
        $table = $diff->getOldTable();
821
 
822
        if ($table === null) {
823
            return [];
824
        }
825
 
826
        $sql = [];
827
 
828
        $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this);
829
 
830
        // Dropping primary keys requires to unset autoincrement attribute on the particular column first.
831
        foreach ($index->getColumns() as $columnName) {
832
            if (! $table->hasColumn($columnName)) {
833
                continue;
834
            }
835
 
836
            $column = $table->getColumn($columnName);
837
 
838
            if ($column->getAutoincrement() !== true) {
839
                continue;
840
            }
841
 
842
            $column->setAutoincrement(false);
843
 
844
            $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' .
845
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
846
 
847
            // original autoincrement information might be needed later on by other parts of the table alteration
848
            $column->setAutoincrement(true);
849
        }
850
 
851
        return $sql;
852
    }
853
 
854
    /**
855
     * @param TableDiff $diff The table diff to gather the SQL for.
856
     *
857
     * @return string[]
858
     *
859
     * @throws Exception
860
     */
861
    private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array
862
    {
863
        $table = $diff->getOldTable();
864
 
865
        if ($table === null) {
866
            return [];
867
        }
868
 
869
        $primaryKey = $table->getPrimaryKey();
870
 
871
        if ($primaryKey === null) {
872
            return [];
873
        }
874
 
875
        $primaryKeyColumns = [];
876
 
877
        foreach ($primaryKey->getColumns() as $columnName) {
878
            if (! $table->hasColumn($columnName)) {
879
                continue;
880
            }
881
 
882
            $primaryKeyColumns[] = $table->getColumn($columnName);
883
        }
884
 
885
        if (count($primaryKeyColumns) === 0) {
886
            return [];
887
        }
888
 
889
        $sql = [];
890
 
891
        $tableNameSQL = $table->getQuotedName($this);
892
 
893
        foreach ($diff->getModifiedIndexes() as $changedIndex) {
894
            // Changed primary key
895
            if (! $changedIndex->isPrimary()) {
896
                continue;
897
            }
898
 
899
            foreach ($primaryKeyColumns as $column) {
900
                // Check if an autoincrement column was dropped from the primary key.
901
                if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) {
902
                    continue;
903
                }
904
 
905
                // The autoincrement attribute needs to be removed from the dropped column
906
                // before we can drop and recreate the primary key.
907
                $column->setAutoincrement(false);
908
 
909
                $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' .
910
                    $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
911
 
912
                // Restore the autoincrement attribute as it might be needed later on
913
                // by other parts of the table alteration.
914
                $column->setAutoincrement(true);
915
            }
916
        }
917
 
918
        return $sql;
919
    }
920
 
921
    /**
922
     * @param TableDiff $diff The table diff to gather the SQL for.
923
     *
924
     * @return string[]
925
     */
926
    protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
927
    {
928
        $sql = [];
929
 
930
        $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this);
931
 
932
        foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
933
            if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) {
934
                continue;
935
            }
936
 
937
            $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL);
938
        }
939
 
940
        return $sql;
941
    }
942
 
943
    /**
944
     * Returns the remaining foreign key constraints that require one of the renamed indexes.
945
     *
946
     * "Remaining" here refers to the diff between the foreign keys currently defined in the associated
947
     * table and the foreign keys to be removed.
948
     *
949
     * @param TableDiff $diff The table diff to evaluate.
950
     *
951
     * @return ForeignKeyConstraint[]
952
     */
953
    private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array
954
    {
955
        if (count($diff->getRenamedIndexes()) === 0) {
956
            return [];
957
        }
958
 
959
        $table = $diff->getOldTable();
960
 
961
        if ($table === null) {
962
            return [];
963
        }
964
 
965
        $foreignKeys = [];
966
        /** @var ForeignKeyConstraint[] $remainingForeignKeys */
967
        $remainingForeignKeys = array_diff_key(
968
            $table->getForeignKeys(),
969
            $diff->getDroppedForeignKeys(),
970
        );
971
 
972
        foreach ($remainingForeignKeys as $foreignKey) {
973
            foreach ($diff->getRenamedIndexes() as $index) {
974
                if ($foreignKey->intersectsIndexColumns($index)) {
975
                    $foreignKeys[] = $foreignKey;
976
 
977
                    break;
978
                }
979
            }
980
        }
981
 
982
        return $foreignKeys;
983
    }
984
 
985
    /**
986
     * {@inheritdoc}
987
     */
988
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
989
    {
990
        return array_merge(
991
            parent::getPostAlterTableIndexForeignKeySQL($diff),
992
            $this->getPostAlterTableRenameIndexForeignKeySQL($diff),
993
        );
994
    }
995
 
996
    /**
997
     * @param TableDiff $diff The table diff to gather the SQL for.
998
     *
999
     * @return string[]
1000
     */
1001
    protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
1002
    {
1003
        $sql     = [];
1004
        $newName = $diff->getNewName();
1005
 
1006
        if ($newName !== false) {
1007
            $tableNameSQL = $newName->getQuotedName($this);
1008
        } else {
1009
            $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this);
1010
        }
1011
 
1012
        foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) {
1013
            if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) {
1014
                continue;
1015
            }
1016
 
1017
            $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL);
1018
        }
1019
 
1020
        return $sql;
1021
    }
1022
 
1023
    /**
1024
     * {@inheritDoc}
1025
     */
1026
    protected function getCreateIndexSQLFlags(Index $index)
1027
    {
1028
        $type = '';
1029
        if ($index->isUnique()) {
1030
            $type .= 'UNIQUE ';
1031
        } elseif ($index->hasFlag('fulltext')) {
1032
            $type .= 'FULLTEXT ';
1033
        } elseif ($index->hasFlag('spatial')) {
1034
            $type .= 'SPATIAL ';
1035
        }
1036
 
1037
        return $type;
1038
    }
1039
 
1040
    /**
1041
     * {@inheritDoc}
1042
     */
1043
    public function getIntegerTypeDeclarationSQL(array $column)
1044
    {
1045
        return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
1046
    }
1047
 
1048
    /**
1049
     * {@inheritDoc}
1050
     */
1051
    public function getBigIntTypeDeclarationSQL(array $column)
1052
    {
1053
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
1054
    }
1055
 
1056
    /**
1057
     * {@inheritDoc}
1058
     */
1059
    public function getSmallIntTypeDeclarationSQL(array $column)
1060
    {
1061
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
1062
    }
1063
 
1064
    /**
1065
     * {@inheritdoc}
1066
     */
1067
    public function getFloatDeclarationSQL(array $column)
1068
    {
1069
        return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column);
1070
    }
1071
 
1072
    /**
1073
     * {@inheritdoc}
1074
     */
1075
    public function getDecimalTypeDeclarationSQL(array $column)
1076
    {
1077
        return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column);
1078
    }
1079
 
1080
    /**
1081
     * Get unsigned declaration for a column.
1082
     *
1083
     * @param mixed[] $columnDef
1084
     */
1085
    private function getUnsignedDeclaration(array $columnDef): string
1086
    {
1087
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
1088
    }
1089
 
1090
    /**
1091
     * {@inheritDoc}
1092
     */
1093
    protected function _getCommonIntegerTypeDeclarationSQL(array $column)
1094
    {
1095
        $autoinc = '';
1096
        if (! empty($column['autoincrement'])) {
1097
            $autoinc = ' AUTO_INCREMENT';
1098
        }
1099
 
1100
        return $this->getUnsignedDeclaration($column) . $autoinc;
1101
    }
1102
 
1103
    /**
1104
     * {@inheritDoc}
1105
     *
1106
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1107
     */
1108
    public function getColumnCharsetDeclarationSQL($charset)
1109
    {
1110
        return 'CHARACTER SET ' . $charset;
1111
    }
1112
 
1113
    /**
1114
     * {@inheritDoc}
1115
     *
1116
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1117
     */
1118
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
1119
    {
1120
        $query = '';
1121
        if ($foreignKey->hasOption('match')) {
1122
            $query .= ' MATCH ' . $foreignKey->getOption('match');
1123
        }
1124
 
1125
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
1126
 
1127
        return $query;
1128
    }
1129
 
1130
    /**
1131
     * {@inheritDoc}
1132
     */
1133
    public function getDropIndexSQL($index, $table = null)
1134
    {
1135
        if ($index instanceof Index) {
1136
            Deprecation::trigger(
1137
                'doctrine/dbal',
1138
                'https://github.com/doctrine/dbal/issues/4798',
1139
                'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.',
1140
                __METHOD__,
1141
            );
1142
 
1143
            $indexName = $index->getQuotedName($this);
1144
        } elseif (is_string($index)) {
1145
            $indexName = $index;
1146
        } else {
1147
            throw new InvalidArgumentException(
1148
                __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.',
1149
            );
1150
        }
1151
 
1152
        if ($table instanceof Table) {
1153
            Deprecation::trigger(
1154
                'doctrine/dbal',
1155
                'https://github.com/doctrine/dbal/issues/4798',
1156
                'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.',
1157
                __METHOD__,
1158
            );
1159
 
1160
            $table = $table->getQuotedName($this);
1161
        } elseif (! is_string($table)) {
1162
            throw new InvalidArgumentException(
1163
                __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.',
1164
            );
1165
        }
1166
 
1167
        if ($index instanceof Index && $index->isPrimary()) {
1168
            // MySQL primary keys are always named "PRIMARY",
1169
            // so we cannot use them in statements because of them being keyword.
1170
            return $this->getDropPrimaryKeySQL($table);
1171
        }
1172
 
1173
        return 'DROP INDEX ' . $indexName . ' ON ' . $table;
1174
    }
1175
 
1176
    /**
1177
     * @param string $table
1178
     *
1179
     * @return string
1180
     */
1181
    protected function getDropPrimaryKeySQL($table)
1182
    {
1183
        return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
1184
    }
1185
 
1186
    /**
1187
     * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19.
1188
     *
1189
     * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html
1190
     */
1191
    public function getDropUniqueConstraintSQL(string $name, string $tableName): string
1192
    {
1193
        return $this->getDropIndexSQL($name, $tableName);
1194
    }
1195
 
1196
    /**
1197
     * {@inheritDoc}
1198
     */
1199
    public function getSetTransactionIsolationSQL($level)
1200
    {
1201
        return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
1202
    }
1203
 
1204
    /**
1205
     * {@inheritDoc}
1206
     */
1207
    public function getName()
1208
    {
1209
        Deprecation::triggerIfCalledFromOutside(
1210
            'doctrine/dbal',
1211
            'https://github.com/doctrine/dbal/issues/4749',
1212
            'AbstractMySQLPlatform::getName() is deprecated. Identify platforms by their class.',
1213
        );
1214
 
1215
        return 'mysql';
1216
    }
1217
 
1218
    /**
1219
     * {@inheritDoc}
1220
     */
1221
    public function getReadLockSQL()
1222
    {
1223
        return 'LOCK IN SHARE MODE';
1224
    }
1225
 
1226
    /**
1227
     * {@inheritDoc}
1228
     */
1229
    protected function initializeDoctrineTypeMappings()
1230
    {
1231
        $this->doctrineTypeMapping = [
1232
            'bigint'     => 'bigint',
1233
            'binary'     => 'binary',
1234
            'blob'       => 'blob',
1235
            'char'       => 'string',
1236
            'date'       => 'date',
1237
            'datetime'   => 'datetime',
1238
            'decimal'    => 'decimal',
1239
            'double'     => 'float',
1240
            'float'      => 'float',
1241
            'int'        => 'integer',
1242
            'integer'    => 'integer',
1243
            'longblob'   => 'blob',
1244
            'longtext'   => 'text',
1245
            'mediumblob' => 'blob',
1246
            'mediumint'  => 'integer',
1247
            'mediumtext' => 'text',
1248
            'numeric'    => 'decimal',
1249
            'real'       => 'float',
1250
            'set'        => 'simple_array',
1251
            'smallint'   => 'smallint',
1252
            'string'     => 'string',
1253
            'text'       => 'text',
1254
            'time'       => 'time',
1255
            'timestamp'  => 'datetime',
1256
            'tinyblob'   => 'blob',
1257
            'tinyint'    => 'boolean',
1258
            'tinytext'   => 'text',
1259
            'varbinary'  => 'binary',
1260
            'varchar'    => 'string',
1261
            'year'       => 'date',
1262
        ];
1263
    }
1264
 
1265
    /**
1266
     * {@inheritDoc}
1267
     *
1268
     * @deprecated
1269
     */
1270
    public function getVarcharMaxLength()
1271
    {
1272
        Deprecation::triggerIfCalledFromOutside(
1273
            'doctrine/dbal',
1274
            'https://github.com/doctrine/dbal/issues/3263',
1275
            'AbstractMySQLPlatform::getVarcharMaxLength() is deprecated.',
1276
        );
1277
 
1278
        return 65535;
1279
    }
1280
 
1281
    /**
1282
     * {@inheritdoc}
1283
     *
1284
     * @deprecated
1285
     */
1286
    public function getBinaryMaxLength()
1287
    {
1288
        Deprecation::triggerIfCalledFromOutside(
1289
            'doctrine/dbal',
1290
            'https://github.com/doctrine/dbal/issues/3263',
1291
            'AbstractMySQLPlatform::getBinaryMaxLength() is deprecated.',
1292
        );
1293
 
1294
        return 65535;
1295
    }
1296
 
1297
    /**
1298
     * {@inheritDoc}
1299
     *
1300
     * @deprecated Implement {@see createReservedKeywordsList()} instead.
1301
     */
1302
    protected function getReservedKeywordsClass()
1303
    {
1304
        Deprecation::triggerIfCalledFromOutside(
1305
            'doctrine/dbal',
1306
            'https://github.com/doctrine/dbal/issues/4510',
1307
            'AbstractMySQLPlatform::getReservedKeywordsClass() is deprecated,'
1308
                . ' use AbstractMySQLPlatform::createReservedKeywordsList() instead.',
1309
        );
1310
 
1311
        return Keywords\MySQLKeywords::class;
1312
    }
1313
 
1314
    /**
1315
     * {@inheritDoc}
1316
     *
1317
     * MySQL commits a transaction implicitly when DROP TABLE is executed, however not
1318
     * if DROP TEMPORARY TABLE is executed.
1319
     */
1320
    public function getDropTemporaryTableSQL($table)
1321
    {
1322
        if ($table instanceof Table) {
1323
            Deprecation::trigger(
1324
                'doctrine/dbal',
1325
                'https://github.com/doctrine/dbal/issues/4798',
1326
                'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.',
1327
                __METHOD__,
1328
            );
1329
 
1330
            $table = $table->getQuotedName($this);
1331
        } elseif (! is_string($table)) {
1332
            throw new InvalidArgumentException(
1333
                __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.',
1334
            );
1335
        }
1336
 
1337
        return 'DROP TEMPORARY TABLE ' . $table;
1338
    }
1339
 
1340
    /**
1341
     * Gets the SQL Snippet used to declare a BLOB column type.
1342
     *     TINYBLOB   : 2 ^  8 - 1 = 255
1343
     *     BLOB       : 2 ^ 16 - 1 = 65535
1344
     *     MEDIUMBLOB : 2 ^ 24 - 1 = 16777215
1345
     *     LONGBLOB   : 2 ^ 32 - 1 = 4294967295
1346
     *
1347
     * {@inheritDoc}
1348
     */
1349
    public function getBlobTypeDeclarationSQL(array $column)
1350
    {
1351
        if (! empty($column['length']) && is_numeric($column['length'])) {
1352
            $length = $column['length'];
1353
 
1354
            if ($length <= static::LENGTH_LIMIT_TINYBLOB) {
1355
                return 'TINYBLOB';
1356
            }
1357
 
1358
            if ($length <= static::LENGTH_LIMIT_BLOB) {
1359
                return 'BLOB';
1360
            }
1361
 
1362
            if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) {
1363
                return 'MEDIUMBLOB';
1364
            }
1365
        }
1366
 
1367
        return 'LONGBLOB';
1368
    }
1369
 
1370
    /**
1371
     * {@inheritdoc}
1372
     */
1373
    public function quoteStringLiteral($str)
1374
    {
1375
        $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped
1376
 
1377
        return parent::quoteStringLiteral($str);
1378
    }
1379
 
1380
    /**
1381
     * {@inheritdoc}
1382
     */
1383
    public function getDefaultTransactionIsolationLevel()
1384
    {
1385
        return TransactionIsolationLevel::REPEATABLE_READ;
1386
    }
1387
 
1388
    public function supportsColumnLengthIndexes(): bool
1389
    {
1390
        return true;
1391
    }
1392
 
1393
    private function getDatabaseNameSQL(?string $databaseName): string
1394
    {
1395
        if ($databaseName !== null) {
1396
            return $this->quoteStringLiteral($databaseName);
1397
        }
1398
 
1399
        return $this->getCurrentDatabaseExpression();
1400
    }
1401
 
1402
    public function createSchemaManager(Connection $connection): MySQLSchemaManager
1403
    {
1404
        return new MySQLSchemaManager($connection, $this);
1405
    }
1406
 
1407
    /**
1408
     * @param list<T> $assets
1409
     *
1410
     * @return array<string,T>
1411
     *
1412
     * @template T of AbstractAsset
1413
     */
1414
    private function indexAssetsByLowerCaseName(array $assets): array
1415
    {
1416
        $result = [];
1417
 
1418
        foreach ($assets as $asset) {
1419
            $result[strtolower($asset->getName())] = $asset;
1420
        }
1421
 
1422
        return $result;
1423
    }
1424
}