Failed Conditions
Pull Request — develop (#3348)
by Sergei
65:20
created

SqlitePlatform::getForeignKeysInAlteredTable()   B

Complexity

Conditions 9
Paths 48

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.0076

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

982
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
983
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
984
            }
985
        }
986 29
987
        return array_merge($sql, $tableSql, $columnSql);
988
    }
989
990
    /**
991
     * @return string[]
992 289
     */
993
    private function getColumnNamesInAlteredTable(TableDiff $diff) : array
994 289
    {
995
        $columns = [];
996 289
997 266
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
0 ignored issues
show
Bug introduced by
The method getColumns() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

997
        foreach ($diff->fromTable->/** @scrutinizer ignore-call */ getColumns() as $columnName => $column) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
998
            $columns[strtolower($columnName)] = $column->getName();
999
        }
1000 289
1001 93
        foreach ($diff->removedColumns as $columnName => $column) {
1002 93
            $columnName = strtolower($columnName);
1003
            if (! isset($columns[$columnName])) {
1004
                continue;
1005
            }
1006 93
1007
            unset($columns[$columnName]);
1008
        }
1009 289
1010 115
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1011 115
            $columnName                          = $column->getName();
1012 115
            $columns[strtolower($oldColumnName)] = $columnName;
1013
            $columns[strtolower($columnName)]    = $columnName;
1014
        }
1015 289
1016 150
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1017 150
            $columnName                          = $columnDiff->column->getName();
1018 150
            $columns[strtolower($oldColumnName)] = $columnName;
1019
            $columns[strtolower($columnName)]    = $columnName;
1020
        }
1021 289
1022 70
        foreach ($diff->addedColumns as $column) {
1023
            $columnName                       = $column->getName();
1024
            $columns[strtolower($columnName)] = $columnName;
1025 289
        }
1026
1027
        return $columns;
1028
    }
1029
1030
    /**
1031 289
     * @return Index[]
1032
     */
1033 289
    private function getIndexesInAlteredTable(TableDiff $diff) : array
1034 289
    {
1035
        $indexes     = $diff->fromTable->getIndexes();
1036 289
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1037 141
1038 70
        foreach ($indexes as $key => $index) {
1039 70
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1040
                if (strtolower($key) !== strtolower($oldIndexName)) {
1041
                    continue;
1042 24
                }
1043
1044
                unset($indexes[$key]);
1045 141
            }
1046 141
1047 141
            $changed      = false;
1048 141
            $indexColumns = [];
1049 141
            foreach ($index->getColumns() as $columnName) {
1050 23
                $normalizedColumnName = strtolower($columnName);
1051 23
                if (! isset($columnNames[$normalizedColumnName])) {
1052
                    unset($indexes[$key]);
1053 141
                    continue 2;
1054 141
                } else {
1055 141
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1056
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1057
                        $changed = true;
1058
                    }
1059
                }
1060 141
            }
1061 141
1062
            if (! $changed) {
1063
                continue;
1064 23
            }
1065
1066
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1067 289
        }
1068 24
1069 24
        foreach ($diff->removedIndexes as $index) {
1070
            unset($indexes[strtolower($index->getName())]);
1071
        }
1072
1073 24
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1074
            $indexes[strtolower($index->getName())] = $index;
1075
        }
1076 289
1077 70
        return $indexes;
1078 70
    }
1079 70
1080
    /**
1081 70
     * @return ForeignKeyConstraint[]
1082
     */
1083
    private function getForeignKeysInAlteredTable(TableDiff $diff) : array
1084
    {
1085 289
        $foreignKeys = $diff->fromTable->getForeignKeys();
1086
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1087
1088
        foreach ($foreignKeys as $key => $constraint) {
1089
            $changed      = false;
1090
            $localColumns = [];
1091 289
            foreach ($constraint->getLocalColumns() as $columnName) {
1092
                $normalizedColumnName = strtolower($columnName);
1093 289
                if (! isset($columnNames[$normalizedColumnName])) {
1094 289
                    unset($foreignKeys[$key]);
1095
                    continue 2;
1096 289
                } else {
1097 69
                    $localColumns[] = $columnNames[$normalizedColumnName];
1098 69
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1099 69
                        $changed = true;
1100 69
                    }
1101 69
                }
1102 23
            }
1103 23
1104
            if (! $changed) {
1105 69
                continue;
1106 69
            }
1107 69
1108
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1109
        }
1110
1111
        foreach ($diff->removedForeignKeys as $constraint) {
1112 69
            if (! $constraint instanceof ForeignKeyConstraint) {
1113 69
                $constraint = new Identifier($constraint);
1114
            }
1115
1116 23
            unset($foreignKeys[strtolower($constraint->getName())]);
1117
        }
1118
1119 289
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1120 23
            $foreignKeys[strtolower($constraint->getName())] = $constraint;
1121 23
        }
1122
1123
        return $foreignKeys;
1124
    }
1125 23
1126
    /**
1127
     * @return Index[]
1128 289
     */
1129 24
    private function getPrimaryIndexInAlteredTable(TableDiff $diff) : array
1130 24
    {
1131 23
        $primaryIndex = [];
1132
1133 24
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1134
            if (! $index->isPrimary()) {
1135
                continue;
1136
            }
1137 289
1138
            $primaryIndex = [$index->getName() => $index];
1139
        }
1140
1141
        return $primaryIndex;
1142
    }
1143
}
1144