Failed Conditions
Push — master ( ff1501...ac0e13 )
by Sergei
22s queued 17s
created

SqlitePlatform::getForUpdateSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Schema\Column;
9
use Doctrine\DBAL\Schema\Constraint;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Schema\Identifier;
12
use Doctrine\DBAL\Schema\Index;
13
use Doctrine\DBAL\Schema\Table;
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\TransactionIsolationLevel;
16
use Doctrine\DBAL\Types;
17
use InvalidArgumentException;
18
use function array_merge;
19
use function array_unique;
20
use function array_values;
21
use function implode;
22
use function sprintf;
23
use function sqrt;
24
use function str_replace;
25
use function strpos;
26
use function strtolower;
27
use function trim;
28
29
/**
30
 * The SqlitePlatform class describes the specifics and dialects of the SQLite
31
 * database platform.
32
 *
33
 * @todo   Rename: SQLitePlatform
34
 */
35
class SqlitePlatform extends AbstractPlatform
36
{
37
    public function getRegexpExpression() : string
38
    {
39
        return 'REGEXP';
40
    }
41
42
    public function getNowExpression(string $type = 'timestamp') : string
43
    {
44
        switch ($type) {
45
            case 'time':
46
                return 'time(\'now\')';
47
48
            case 'date':
49
                return 'date(\'now\')';
50
51
            case 'timestamp':
52
            default:
53
                return 'datetime(\'now\')';
54
        }
55
    }
56
57
    public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED, ?string $char = null) : string
58
    {
59
        switch ($mode) {
60
            case TrimMode::UNSPECIFIED:
61
            case TrimMode::BOTH:
62
                $trimFn = 'TRIM';
63
                break;
64
65
            case TrimMode::LEADING:
66
                $trimFn = 'LTRIM';
67
                break;
68
69
            case TrimMode::TRAILING:
70
                $trimFn = 'RTRIM';
71
                break;
72
73
            default:
74
                throw new InvalidArgumentException(
75
                    sprintf(
76
                        'The value of $mode is expected to be one of the TrimMode constants, %d given.',
77
                        $mode
78
                    )
79
                );
80
        }
81
82
        $arguments = [$str];
83
84
        if ($char !== null) {
85
            $arguments[] = $char;
86
        }
87
88
        return sprintf('%s(%s)', $trimFn, implode(', ', $arguments));
89
    }
90
91
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
92
    {
93
        if ($length === null) {
94
            return sprintf('SUBSTR(%s, %s)', $string, $start);
95
        }
96
97
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
98
    }
99
100
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
101
    {
102
        if ($start === null) {
103
            return sprintf('LOCATE(%s, %s)', $string, $substring);
104
        }
105
106
        return sprintf('LOCATE(%s, %s, %s)', $string, $substring, $start);
107
    }
108
109
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
110
    {
111
        switch ($unit) {
112
            case DateIntervalUnit::WEEK:
113
                $interval = $this->multiplyInterval($interval, 7);
114
                $unit     = DateIntervalUnit::DAY;
115
                break;
116
117
            case DateIntervalUnit::QUARTER:
118
                $interval = $this->multiplyInterval($interval, 3);
119
                $unit     = DateIntervalUnit::MONTH;
120
                break;
121
        }
122
123
        return 'DATETIME(' . $date . ',' . $this->getConcatExpression(
124
            $this->quoteStringLiteral($operator),
125
            $interval,
126
            $this->quoteStringLiteral(' ' . $unit)
127
        ) . ')';
128
    }
129
130
    public function getDateDiffExpression(string $date1, string $date2) : string
131
    {
132
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     *
138
     * The SQLite platform doesn't support the concept of a database, therefore, it always returns an empty string
139
     * as an indicator of an implicitly selected database.
140
     *
141
     * @see \Doctrine\DBAL\Connection::getDatabase()
142
     */
143
    public function getCurrentDatabaseExpression() : string
144
    {
145
        return "''";
146
    }
147
148
    protected function _getTransactionIsolationLevelSQL(int $level) : string
149
    {
150
        switch ($level) {
151
            case TransactionIsolationLevel::READ_UNCOMMITTED:
152
                return '0';
153
154
            case TransactionIsolationLevel::READ_COMMITTED:
155
            case TransactionIsolationLevel::REPEATABLE_READ:
156
            case TransactionIsolationLevel::SERIALIZABLE:
157
                return '1';
158
159
            default:
160
                return parent::_getTransactionIsolationLevelSQL($level);
161
        }
162
    }
163
164
    public function getSetTransactionIsolationSQL(int $level) : string
165
    {
166
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
167
    }
168
169
    public function prefersIdentityColumns() : bool
170
    {
171
        return true;
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
178
    {
179
        return 'BOOLEAN';
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
186
    {
187
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
188
    }
189
190
    /**
191
     * {@inheritDoc}
192
     */
193
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
194
    {
195
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
196
        if (! empty($columnDef['autoincrement'])) {
197
            return $this->getIntegerTypeDeclarationSQL($columnDef);
198
        }
199
200
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
201
    }
202
203
    /**
204
     * @param array<string, mixed> $field
205
     */
206
    public function getTinyIntTypeDeclarationSql(array $field) : string
207
    {
208
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
209
        if (! empty($field['autoincrement'])) {
210
            return $this->getIntegerTypeDeclarationSQL($field);
211
        }
212
213
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
214
    }
215
216
    /**
217
     * {@inheritDoc}
218
     */
219
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
220
    {
221
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
222
        if (! empty($columnDef['autoincrement'])) {
223
            return $this->getIntegerTypeDeclarationSQL($columnDef);
224
        }
225
226
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
227
    }
228
229
    /**
230
     * @param array<string, mixed> $field
231
     */
232
    public function getMediumIntTypeDeclarationSql(array $field) : string
233
    {
234
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
235
        if (! empty($field['autoincrement'])) {
236
            return $this->getIntegerTypeDeclarationSQL($field);
237
        }
238
239
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
240
    }
241
242
    /**
243
     * {@inheritDoc}
244
     */
245
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
246
    {
247
        return 'DATETIME';
248
    }
249
250
    /**
251
     * {@inheritDoc}
252
     */
253
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
254
    {
255
        return 'DATE';
256
    }
257
258
    /**
259
     * {@inheritDoc}
260
     */
261
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
262
    {
263
        return 'TIME';
264
    }
265
266
    /**
267
     * {@inheritDoc}
268
     */
269
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
270
    {
271
        // sqlite autoincrement is only possible for the primary key
272
        if (! empty($columnDef['autoincrement'])) {
273
            return ' PRIMARY KEY AUTOINCREMENT';
274
        }
275
276
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
277
    }
278
279
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
280
    {
281
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
282
            $foreignKey->getQuotedLocalColumns($this),
283
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
284
            $foreignKey->getQuotedForeignColumns($this),
285
            $foreignKey->getName(),
286
            $foreignKey->getOptions()
287
        ));
288
    }
289
290
    /**
291
     * {@inheritDoc}
292
     */
293
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
294
    {
295
        $tableName   = str_replace('.', '__', $tableName);
296
        $queryFields = $this->getColumnDeclarationListSQL($columns);
297
298
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
299
            foreach ($options['uniqueConstraints'] as $name => $definition) {
300
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
301
            }
302
        }
303
304
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
305
306
        if (isset($options['foreignKeys'])) {
307
            foreach ($options['foreignKeys'] as $foreignKey) {
308
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
309
            }
310
        }
311
312
        $tableComment = '';
313
        if (isset($options['comment'])) {
314
            $comment = trim($options['comment'], " '");
315
316
            $tableComment = $this->getInlineTableCommentSQL($comment);
317
        }
318
319
        $query = ['CREATE TABLE ' . $tableName . ' ' . $tableComment . '(' . $queryFields . ')'];
320
321
        if (isset($options['alter']) && $options['alter'] === true) {
322
            return $query;
323
        }
324
325
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
326
            foreach ($options['indexes'] as $indexDef) {
327
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
328
            }
329
        }
330
331
        if (isset($options['unique']) && ! empty($options['unique'])) {
332
            foreach ($options['unique'] as $indexDef) {
333
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
334
            }
335
        }
336
337
        return $query;
338
    }
339
340
    /**
341
     * Generate a PRIMARY KEY definition if no autoincrement value is used
342
     *
343
     * @param mixed[][] $columns
344
     * @param mixed[]   $options
345
     */
346
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
347
    {
348
        if (empty($options['primary'])) {
349
            return '';
350
        }
351
352
        $keyColumns = array_unique(array_values($options['primary']));
353
354
        foreach ($keyColumns as $keyColumn) {
355
            foreach ($columns as $column) {
356
                if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) {
357
                    return '';
358
                }
359
            }
360
        }
361
362
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
363
    }
364
365
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
366
    {
367
        return 'BLOB';
368
    }
369
370
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
371
    {
372
        $sql = 'VARCHAR';
373
374
        if ($length !== null) {
375
            $sql .= sprintf('(%d)', $length);
376
        }
377
378
        return $sql;
379
    }
380
381
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
382
    {
383
        return 'BLOB';
384
    }
385
386
    /**
387
     * {@inheritDoc}
388
     */
389
    public function getClobTypeDeclarationSQL(array $field) : string
390
    {
391
        return 'CLOB';
392
    }
393
394
    public function getListTableConstraintsSQL(string $table) : string
395
    {
396
        $table = str_replace('.', '__', $table);
397
398
        return sprintf(
399
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
400
            $this->quoteStringLiteral($table)
401
        );
402
    }
403
404
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
405
    {
406
        $table = str_replace('.', '__', $table);
407
408
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
409
    }
410
411
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
412
    {
413
        $table = str_replace('.', '__', $table);
414
415
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
416
    }
417
418
    public function getListTablesSQL() : string
419
    {
420
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
421
             . 'UNION ALL SELECT name FROM sqlite_temp_master '
422
             . "WHERE type = 'table' ORDER BY name";
423
    }
424
425
    public function getListViewsSQL(string $database) : string
426
    {
427
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
428
    }
429
430
    public function getCreateViewSQL(string $name, string $sql) : string
431
    {
432
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
433
    }
434
435
    public function getDropViewSQL(string $name) : string
436
    {
437
        return 'DROP VIEW ' . $name;
438
    }
439
440
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
441
    {
442
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
443
444
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
445
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
446
447
        return $query;
448
    }
449
450
    public function supportsIdentityColumns() : bool
451
    {
452
        return true;
453
    }
454
455
    public function supportsColumnCollation() : bool
456
    {
457
        return true;
458
    }
459
460
    public function supportsInlineColumnComments() : bool
461
    {
462
        return true;
463
    }
464
465
    public function getName() : string
466
    {
467
        return 'sqlite';
468
    }
469
470
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
471
    {
472
        $tableIdentifier = new Identifier($tableName);
473
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
474
475
        return 'DELETE FROM ' . $tableName;
476
    }
477
478
    /**
479
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
480
     *
481
     * @param int|float $value
482
     */
483
    public static function udfSqrt($value) : float
484
    {
485
        return sqrt($value);
486
    }
487
488
    /**
489
     * User-defined function for Sqlite that implements MOD(a, b).
490
     */
491
    public static function udfMod(int $a, int $b) : int
492
    {
493
        return $a % $b;
494
    }
495
496
    public static function udfLocate(string $str, string $substr, int $offset = 0) : int
497
    {
498
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
499
        // So we have to make them compatible if an offset is given.
500
        if ($offset > 0) {
501
            $offset -= 1;
502
        }
503
504
        $pos = strpos($str, $substr, $offset);
505
506
        if ($pos !== false) {
507
            return $pos + 1;
508
        }
509
510
        return 0;
511
    }
512
513
    public function getForUpdateSQL() : string
514
    {
515
        return '';
516
    }
517
518
    public function getInlineColumnCommentSQL(?string $comment) : string
519
    {
520
        if ($comment === null || $comment === '') {
521
            return '';
522
        }
523
524
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
525
    }
526
527
    private function getInlineTableCommentSQL(string $comment) : string
528
    {
529
        return $this->getInlineColumnCommentSQL($comment);
530
    }
531
532
    protected function initializeDoctrineTypeMappings() : void
533
    {
534
        $this->doctrineTypeMapping = [
535
            'bigint'           => 'bigint',
536
            'bigserial'        => 'bigint',
537
            'blob'             => 'blob',
538
            'boolean'          => 'boolean',
539
            'char'             => 'string',
540
            'clob'             => 'text',
541
            'date'             => 'date',
542
            'datetime'         => 'datetime',
543
            'decimal'          => 'decimal',
544
            'double'           => 'float',
545
            'double precision' => 'float',
546
            'float'            => 'float',
547
            'image'            => 'string',
548
            'int'              => 'integer',
549
            'integer'          => 'integer',
550
            'longtext'         => 'text',
551
            'longvarchar'      => 'string',
552
            'mediumint'        => 'integer',
553
            'mediumtext'       => 'text',
554
            'ntext'            => 'string',
555
            'numeric'          => 'decimal',
556
            'nvarchar'         => 'string',
557
            'real'             => 'float',
558
            'serial'           => 'integer',
559
            'smallint'         => 'smallint',
560
            'string'           => 'string',
561
            'text'             => 'text',
562
            'time'             => 'time',
563
            'timestamp'        => 'datetime',
564
            'tinyint'          => 'boolean',
565
            'tinytext'         => 'text',
566
            'varchar'          => 'string',
567
            'varchar2'         => 'string',
568
        ];
569
    }
570
571
    protected function getReservedKeywordsClass() : string
572
    {
573
        return Keywords\SQLiteKeywords::class;
574
    }
575
576
    /**
577
     * {@inheritDoc}
578
     */
579
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
580
    {
581
        if (! $diff->fromTable instanceof Table) {
582
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
583
        }
584
585
        $sql = [];
586
        foreach ($diff->fromTable->getIndexes() as $index) {
587
            if ($index->isPrimary()) {
588
                continue;
589
            }
590
591
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
592
        }
593
594
        return $sql;
595
    }
596
597
    /**
598
     * {@inheritDoc}
599
     */
600
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
601
    {
602
        if ($diff->fromTable === null) {
603
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
604
        }
605
606
        $sql       = [];
607
        $tableName = $diff->getNewName();
608
609
        if ($tableName === null) {
610
            $tableName = $diff->getName($this);
611
        }
612
613
        foreach ($this->getIndexesInAlteredTable($diff, $diff->fromTable) as $index) {
614
            if ($index->isPrimary()) {
615
                continue;
616
            }
617
618
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
619
        }
620
621
        return $sql;
622
    }
623
624
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
625
    {
626
        if ($limit === null && $offset > 0) {
627
            $limit = -1;
628
        }
629
630
        return parent::doModifyLimitQuery($query, $limit, $offset);
631
    }
632
633
    /**
634
     * {@inheritDoc}
635
     */
636
    public function getBlobTypeDeclarationSQL(array $field) : string
637
    {
638
        return 'BLOB';
639
    }
640
641
    public function getTemporaryTableName(string $tableName) : string
642
    {
643
        $tableName = str_replace('.', '__', $tableName);
644
645
        return $tableName;
646
    }
647
648
    /**
649
     * {@inheritDoc}
650
     *
651
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
652
     * into the default database.
653
     *
654
     * This hack is implemented to be able to use SQLite as testdriver when
655
     * using schema supporting databases.
656
     */
657
    public function canEmulateSchemas() : bool
658
    {
659
        return true;
660
    }
661
662
    public function supportsForeignKeyConstraints() : bool
663
    {
664
        return false;
665
    }
666
667
    /**
668
     * {@inheritDoc}
669
     */
670
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
671
    {
672
        throw new DBALException('Sqlite platform does not support alter primary key.');
673
    }
674
675
    /**
676
     * {@inheritdoc}
677
     */
678
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string
679
    {
680
        throw new DBALException('Sqlite platform does not support alter foreign key.');
681
    }
682
683
    /**
684
     * {@inheritdoc}
685
     */
686
    public function getDropForeignKeySQL($foreignKey, $table) : string
687
    {
688
        throw new DBALException('Sqlite platform does not support alter foreign key.');
689
    }
690
691
    /**
692
     * {@inheritDoc}
693
     */
694
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
695
    {
696
        throw new DBALException('Sqlite platform does not support alter constraint.');
697
    }
698
699
    /**
700
     * {@inheritDoc}
701
     */
702
    public function getCreateTableSQL(Table $table, int $createFlags = self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS) : array
703
    {
704
        return parent::getCreateTableSQL($table, $createFlags);
705
    }
706
707
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
708
    {
709
        $table = str_replace('.', '__', $table);
710
711
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
712
    }
713
714
    /**
715
     * {@inheritDoc}
716
     */
717
    public function getAlterTableSQL(TableDiff $diff) : array
718
    {
719
        $sql = $this->getSimpleAlterTableSQL($diff);
720
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
721
            return $sql;
722
        }
723
724
        $fromTable = $diff->fromTable;
725
        if ($fromTable === null) {
726
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
727
        }
728
729
        $table = clone $fromTable;
730
731
        $columns        = [];
732
        $oldColumnNames = [];
733
        $newColumnNames = [];
734
        $columnSql      = [];
735
736
        foreach ($table->getColumns() as $columnName => $column) {
737
            $columnName                  = strtolower($columnName);
738
            $columns[$columnName]        = $column;
739
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
740
        }
741
742
        foreach ($diff->removedColumns as $columnName => $column) {
743
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
744
                continue;
745
            }
746
747
            $columnName = strtolower($columnName);
748
            if (! isset($columns[$columnName])) {
749
                continue;
750
            }
751
752
            unset(
753
                $columns[$columnName],
754
                $oldColumnNames[$columnName],
755
                $newColumnNames[$columnName]
756
            );
757
        }
758
759
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
760
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
761
                continue;
762
            }
763
764
            $oldColumnName = strtolower($oldColumnName);
765
            if (isset($columns[$oldColumnName])) {
766
                unset($columns[$oldColumnName]);
767
            }
768
769
            $columns[strtolower($column->getName())] = $column;
770
771
            if (! isset($newColumnNames[$oldColumnName])) {
772
                continue;
773
            }
774
775
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
776
        }
777
778
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
779
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
780
                continue;
781
            }
782
783
            if (isset($columns[$oldColumnName])) {
784
                unset($columns[$oldColumnName]);
785
            }
786
787
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
788
789
            if (! isset($newColumnNames[$oldColumnName])) {
790
                continue;
791
            }
792
793
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
794
        }
795
796
        foreach ($diff->addedColumns as $columnName => $column) {
797
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
798
                continue;
799
            }
800
801
            $columns[strtolower($columnName)] = $column;
802
        }
803
804
        $sql      = [];
805
        $tableSql = [];
806
807
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
808
            $dataTable = new Table('__temp__' . $table->getName());
809
810
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff, $fromTable), [], $this->getForeignKeysInAlteredTable($diff, $fromTable), $table->getOptions());
811
            $newTable->addOption('alter', true);
812
813
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
814
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
815
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
816
            $sql[] = $this->getDropTableSQL($fromTable);
817
818
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
819
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
820
            $sql[] = $this->getDropTableSQL($dataTable);
821
822
            $newName = $diff->getNewName();
823
824
            if ($newName !== null) {
825
                $sql[] = sprintf(
826
                    'ALTER TABLE %s RENAME TO %s',
827
                    $newTable->getQuotedName($this),
828
                    $newName->getQuotedName($this)
829
                );
830
            }
831
832
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
833
        }
834
835
        return array_merge($sql, $tableSql, $columnSql);
836
    }
837
838
    /**
839
     * @return string[]|false
840
     */
841
    private function getSimpleAlterTableSQL(TableDiff $diff)
842
    {
843
        // Suppress changes on integer type autoincrement columns.
844
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
845
            if (! $columnDiff->fromColumn instanceof Column ||
846
                ! $columnDiff->column instanceof Column ||
847
                ! $columnDiff->column->getAutoincrement() ||
848
                ! $columnDiff->column->getType() instanceof Types\IntegerType
849
            ) {
850
                continue;
851
            }
852
853
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
854
                unset($diff->changedColumns[$oldColumnName]);
855
856
                continue;
857
            }
858
859
            $fromColumnType = $columnDiff->fromColumn->getType();
860
861
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
862
                continue;
863
            }
864
865
            unset($diff->changedColumns[$oldColumnName]);
866
        }
867
868
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
869
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
870
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
871
                || ! empty($diff->renamedIndexes)
872
        ) {
873
            return false;
874
        }
875
876
        $table = new Table($diff->name);
877
878
        $sql       = [];
879
        $tableSql  = [];
880
        $columnSql = [];
881
882
        foreach ($diff->addedColumns as $column) {
883
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
884
                continue;
885
            }
886
887
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
888
            $type  = $field['type'];
889
            switch (true) {
890
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
891
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
892
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
893
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
894
                    return false;
895
            }
896
897
            $field['name'] = $column->getQuotedName($this);
898
            if ($type instanceof Types\StringType && $field['length'] === null) {
899
                $field['length'] = 255;
900
            }
901
902
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
903
        }
904
905
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
906
            if ($diff->newName !== null) {
907
                $newTable = new Identifier($diff->newName);
908
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
909
            }
910
        }
911
912
        return array_merge($sql, $tableSql, $columnSql);
913
    }
914
915
    /**
916
     * @return string[]
917
     */
918
    private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable) : array
919
    {
920
        $columns = [];
921
922
        foreach ($fromTable->getColumns() as $columnName => $column) {
923
            $columns[strtolower($columnName)] = $column->getName();
924
        }
925
926
        foreach ($diff->removedColumns as $columnName => $column) {
927
            $columnName = strtolower($columnName);
928
            if (! isset($columns[$columnName])) {
929
                continue;
930
            }
931
932
            unset($columns[$columnName]);
933
        }
934
935
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
936
            $columnName                          = $column->getName();
937
            $columns[strtolower($oldColumnName)] = $columnName;
938
            $columns[strtolower($columnName)]    = $columnName;
939
        }
940
941
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
942
            $columnName                          = $columnDiff->column->getName();
943
            $columns[strtolower($oldColumnName)] = $columnName;
944
            $columns[strtolower($columnName)]    = $columnName;
945
        }
946
947
        foreach ($diff->addedColumns as $column) {
948
            $columnName                       = $column->getName();
949
            $columns[strtolower($columnName)] = $columnName;
950
        }
951
952
        return $columns;
953
    }
954
955
    /**
956
     * @return Index[]
957
     */
958
    private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable) : array
959
    {
960
        $indexes     = $fromTable->getIndexes();
961
        $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable);
962
963
        foreach ($indexes as $key => $index) {
964
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
965
                if (strtolower($key) !== strtolower($oldIndexName)) {
966
                    continue;
967
                }
968
969
                unset($indexes[$key]);
970
            }
971
972
            $changed      = false;
973
            $indexColumns = [];
974
            foreach ($index->getColumns() as $columnName) {
975
                $normalizedColumnName = strtolower($columnName);
976
                if (! isset($columnNames[$normalizedColumnName])) {
977
                    unset($indexes[$key]);
978
                    continue 2;
979
                }
980
981
                $indexColumns[] = $columnNames[$normalizedColumnName];
982
                if ($columnName === $columnNames[$normalizedColumnName]) {
983
                    continue;
984
                }
985
986
                $changed = true;
987
            }
988
989
            if (! $changed) {
990
                continue;
991
            }
992
993
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
994
        }
995
996
        foreach ($diff->removedIndexes as $index) {
997
            $indexName = $index->getName();
998
999
            if ($indexName === '') {
1000
                continue;
1001
            }
1002
1003
            unset($indexes[strtolower($indexName)]);
1004
        }
1005
1006
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1007
            $indexName = $index->getName();
1008
1009
            if ($indexName !== '') {
1010
                $indexes[strtolower($indexName)] = $index;
1011
            } else {
1012
                $indexes[] = $index;
1013
            }
1014
        }
1015
1016
        return $indexes;
1017
    }
1018
1019
    /**
1020
     * @return ForeignKeyConstraint[]
1021
     */
1022
    private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable) : array
1023
    {
1024
        $foreignKeys = $fromTable->getForeignKeys();
1025
        $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable);
1026
1027
        foreach ($foreignKeys as $key => $constraint) {
1028
            $changed      = false;
1029
            $localColumns = [];
1030
            foreach ($constraint->getLocalColumns() as $columnName) {
1031
                $normalizedColumnName = strtolower($columnName);
1032
                if (! isset($columnNames[$normalizedColumnName])) {
1033
                    unset($foreignKeys[$key]);
1034
                    continue 2;
1035
                }
1036
1037
                $localColumns[] = $columnNames[$normalizedColumnName];
1038
                if ($columnName === $columnNames[$normalizedColumnName]) {
1039
                    continue;
1040
                }
1041
1042
                $changed = true;
1043
            }
1044
1045
            if (! $changed) {
1046
                continue;
1047
            }
1048
1049
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1050
        }
1051
1052
        foreach ($diff->removedForeignKeys as $constraint) {
1053
            if (! $constraint instanceof ForeignKeyConstraint) {
1054
                $constraint = new Identifier($constraint);
1055
            }
1056
1057
            $constraintName = $constraint->getName();
1058
1059
            if ($constraintName === '') {
1060
                continue;
1061
            }
1062
1063
            unset($foreignKeys[strtolower($constraintName)]);
1064
        }
1065
1066
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1067
            $constraintName = $constraint->getName();
1068
1069
            if ($constraintName !== '') {
1070
                $foreignKeys[strtolower($constraintName)] = $constraint;
1071
            } else {
1072
                $foreignKeys[] = $constraint;
1073
            }
1074
        }
1075
1076
        return $foreignKeys;
1077
    }
1078
1079
    /**
1080
     * @return Index[]
1081
     */
1082
    private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable) : array
1083
    {
1084
        $primaryIndex = [];
1085
1086
        foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) {
1087
            if (! $index->isPrimary()) {
1088
                continue;
1089
            }
1090
1091
            $primaryIndex = [$index->getName() => $index];
1092
        }
1093
1094
        return $primaryIndex;
1095
    }
1096
}
1097