Passed
Pull Request — master (#3183)
by Sergei
15:27
created

MySqlPlatform   F

Complexity

Total Complexity 175

Size/Duplication

Total Lines 1096
Duplicated Lines 0 %

Test Coverage

Coverage 93.16%

Importance

Changes 0
Metric Value
wmc 175
dl 0
loc 1096
ccs 354
cts 380
cp 0.9316
rs 0.6568
c 0
b 0
f 0

65 Methods

Rating   Name   Duplication   Size   Complexity  
A getLocateExpression() 0 7 2
A getTimeTypeDeclarationSQL() 0 3 1
A getListTableForeignKeysSQL() 0 21 3
A prefersIdentityColumns() 0 3 1
A getCollationFieldDeclaration() 0 3 1
A getDateDiffExpression() 0 3 1
A getListTablesSQL() 0 3 1
A getBooleanTypeDeclarationSQL() 0 3 1
A doModifyLimitQuery() 0 14 4
A getListViewsSQL() 0 5 1
A getCreateViewSQL() 0 3 1
B getClobTypeDeclarationSQL() 0 19 6
A getRegexpExpression() 0 3 1
A getListTableIndexesSQL() 0 14 2
A getGuidExpression() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 7 3
A supportsInlineColumnComments() 0 3 1
A getDateTypeDeclarationSQL() 0 3 1
A getVarcharTypeDeclarationSQLSnippet() 0 4 4
A getListDatabasesSQL() 0 3 1
A getDropViewSQL() 0 3 1
A getConcatExpression() 0 5 1
A getDateArithmeticIntervalExpression() 0 5 2
A getIdentifierQuoteCharacter() 0 3 1
A getListTableConstraintsSQL() 0 3 1
A supportsIdentityColumns() 0 3 1
A supportsColumnCollation() 0 3 1
A getBinaryTypeDeclarationSQLSnippet() 0 3 4
A getDefaultValueDeclarationSQL() 0 8 3
A getSmallIntTypeDeclarationSQL() 0 3 1
A getBigIntTypeDeclarationSQL() 0 3 1
A getReadLockSQL() 0 3 1
A getDropDatabaseSQL() 0 3 1
A getDefaultTransactionIsolationLevel() 0 3 1
B getRemainingForeignKeyConstraintsRequiringRenamedIndexes() 0 24 6
B initializeDoctrineTypeMappings() 0 33 1
A getIntegerTypeDeclarationSQL() 0 3 1
B getPreAlterTableAlterPrimaryKeySQL() 0 30 6
A _getCommonIntegerTypeDeclarationSQL() 0 8 2
A getCreateIndexSQLFlags() 0 12 4
A buildPartitionOptions() 0 5 2
A getListTableColumnsSQL() 0 15 2
A getPostAlterTableIndexForeignKeySQL() 0 5 1
A getAdvancedForeignKeyOptionsSQL() 0 9 2
C getDropIndexSQL() 0 23 7
C getPreAlterTableIndexForeignKeySQL() 0 57 10
A getPreAlterTableRenameIndexForeignKeySQL() 0 12 3
A getPostAlterTableRenameIndexForeignKeySQL() 0 14 4
A getDropTemporaryTableSQL() 0 9 3
F getAlterTableSQL() 0 81 17
A getDropPrimaryKeySQL() 0 3 1
C buildTableOptions() 0 47 8
A getName() 0 3 1
A getBinaryMaxLength() 0 3 1
A getSetTransactionIsolationSQL() 0 3 1
A getFloatDeclarationSQL() 0 3 1
A getDecimalTypeDeclarationSQL() 0 3 1
A getUnsignedDeclaration() 0 3 2
A getVarcharMaxLength() 0 3 1
C _getCreateTableSQL() 0 48 14
A getReservedKeywordsClass() 0 3 1
A getCreateDatabaseSQL() 0 3 1
A quoteStringLiteral() 0 5 1
C getPreAlterTableAlterIndexForeignKeySQL() 0 29 7
B getBlobTypeDeclarationSQL() 0 19 6

How to fix   Complexity   

Complex Class

Complex classes like MySqlPlatform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MySqlPlatform, and based on these observations, apply Extract Interface, too.

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