Completed
Push — develop ( a59880...a5109c )
by Sergei
112:22 queued 47:20
created

MySqlPlatform::getGuidExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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