Completed
Pull Request — master (#3143)
by Alessandro
17:39
created

MySqlPlatform::getDropViewSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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