Failed Conditions
Push — master ( ac0e13...24dbc4 )
by Sergei
22s queued 15s
created

SqlitePlatform::getTinyIntTypeDeclarationSQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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