Failed Conditions
Push — master ( 46069b...f76bf5 )
by Marco
16s queued 12s
created

MySqlPlatform   F

Complexity

Total Complexity 175

Size/Duplication

Total Lines 1100
Duplicated Lines 0 %

Test Coverage

Coverage 85.22%

Importance

Changes 0
Metric Value
wmc 175
c 0
b 0
f 0
dl 0
loc 1100
rs 0.6314
ccs 323
cts 379
cp 0.8522

65 Methods

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