Completed
Push — develop ( 0c4aa7...b62acb )
by Sergei
55s queued 14s
created

SqlitePlatform::supportsForeignKeyConstraints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
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
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
    /**
37
     * {@inheritDoc}
38
     */
39 1
    public function getRegexpExpression() : string
40
    {
41 1
        return 'REGEXP';
42
    }
43
44
    /**
45
     * {@inheritDoc}
46
     */
47
    public function getNowExpression(string $type = 'timestamp') : string
48
    {
49
        switch ($type) {
50
            case 'time':
51
                return 'time(\'now\')';
52
            case 'date':
53
                return 'date(\'now\')';
54
            case 'timestamp':
55
            default:
56
                return 'datetime(\'now\')';
57
        }
58
    }
59
60
    /**
61
     * {@inheritDoc}
62
     */
63 270
    public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED, ?string $char = null) : string
64
    {
65 270
        switch ($mode) {
66
            case TrimMode::UNSPECIFIED:
67
            case TrimMode::BOTH:
68 270
                $trimFn = 'TRIM';
69 270
                break;
70
71
            case TrimMode::LEADING:
72
                $trimFn = 'LTRIM';
73
                break;
74
75
            case TrimMode::TRAILING:
76
                $trimFn = 'RTRIM';
77
                break;
78
79
            default:
80 266
                throw new InvalidArgumentException(
81 266
                    sprintf(
82
                        'The value of $mode is expected to be one of the TrimMode constants, %d given.',
83 266
                        $mode
84
                    )
85
                );
86
        }
87
88 270
        $arguments = [$str];
89
90 270
        if ($char !== null) {
91 269
            $arguments[] = $char;
92
        }
93
94 270
        return sprintf('%s(%s)', $trimFn, implode(', ', $arguments));
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 232
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
101
    {
102 232
        if ($length === null) {
103 232
            return sprintf('SUBSTR(%s, %s)', $string, $start);
104
        }
105
106 231
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112 232
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
113
    {
114 232
        if ($start === null) {
115 232
            return sprintf('LOCATE(%s, %s)', $string, $substring);
116
        }
117
118 232
        return sprintf('LOCATE(%s, %s, %s)', $string, $substring, $start);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124 1587
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
125
    {
126
        switch ($unit) {
127 1587
            case DateIntervalUnit::WEEK:
128 249
                $interval = $this->multiplyInterval($interval, 7);
129 249
                $unit     = DateIntervalUnit::DAY;
130 249
                break;
131
132 1587
            case DateIntervalUnit::QUARTER:
133 241
                $interval = $this->multiplyInterval($interval, 3);
134 241
                $unit     = DateIntervalUnit::MONTH;
135 241
                break;
136
        }
137
138 1587
        return 'DATETIME(' . $date . ',' . $this->getConcatExpression(
139 1587
            $this->quoteStringLiteral($operator),
140 2
            $interval,
141 1587
            $this->quoteStringLiteral(' ' . $unit)
142 1587
        ) . ')';
143
    }
144
145
    /**
146
     * {@inheritDoc}
147
     */
148 206
    public function getDateDiffExpression(string $date1, string $date2) : string
149
    {
150 206
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
151
    }
152
153
    /**
154
     * {@inheritDoc}
155
     */
156 1851
    protected function _getTransactionIsolationLevelSQL(int $level) : string
157
    {
158
        switch ($level) {
159 1851
            case TransactionIsolationLevel::READ_UNCOMMITTED:
160 1851
                return '0';
161 1851
            case TransactionIsolationLevel::READ_COMMITTED:
162 1851
            case TransactionIsolationLevel::REPEATABLE_READ:
163 1851
            case TransactionIsolationLevel::SERIALIZABLE:
164 1851
                return '1';
165
            default:
166
                return parent::_getTransactionIsolationLevelSQL($level);
167
        }
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     */
173 1851
    public function getSetTransactionIsolationSQL(int $level) : string
174
    {
175 1851
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
176
    }
177
178
    /**
179
     * {@inheritDoc}
180
     */
181 1834
    public function prefersIdentityColumns() : bool
182
    {
183 1834
        return true;
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189 1121
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
190
    {
191 1121
        return 'BOOLEAN';
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197 2030
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
198
    {
199 2030
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 1795
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
206
    {
207
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
208 1795
        if (! empty($columnDef['autoincrement'])) {
209 1795
            return $this->getIntegerTypeDeclarationSQL($columnDef);
210
        }
211
212 1740
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 1777
    public function getTinyIntTypeDeclarationSql(array $field) : string
219
    {
220
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
221 1777
        if (! empty($field['autoincrement'])) {
222 1777
            return $this->getIntegerTypeDeclarationSQL($field);
223
        }
224
225 1776
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 1847
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
232
    {
233
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
234 1847
        if (! empty($columnDef['autoincrement'])) {
235 1847
            return $this->getIntegerTypeDeclarationSQL($columnDef);
236
        }
237
238 1824
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 1
    public function getMediumIntTypeDeclarationSql(array $field) : string
245
    {
246
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
247 1
        if (! empty($field['autoincrement'])) {
248 1
            return $this->getIntegerTypeDeclarationSQL($field);
249
        }
250
251 1
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
252
    }
253
254
    /**
255
     * {@inheritDoc}
256
     */
257 271
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
258
    {
259 271
        return 'DATETIME';
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     */
265 233
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
266
    {
267 233
        return 'DATE';
268
    }
269
270
    /**
271
     * {@inheritDoc}
272
     */
273 203
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
274
    {
275 203
        return 'TIME';
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281 2030
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
282
    {
283
        // sqlite autoincrement is only possible for the primary key
284 2030
        if (! empty($columnDef['autoincrement'])) {
285 1958
            return ' PRIMARY KEY AUTOINCREMENT';
286
        }
287
288 2003
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 1571
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
295
    {
296 1571
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
297 1571
            $foreignKey->getQuotedLocalColumns($this),
298 1571
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
299 1571
            $foreignKey->getQuotedForeignColumns($this),
300 1571
            $foreignKey->getName(),
301 1571
            $foreignKey->getOptions()
302
        ));
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308 1817
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
309
    {
310 1817
        $tableName   = str_replace('.', '__', $tableName);
311 1817
        $queryFields = $this->getColumnDeclarationListSQL($columns);
312
313 1817
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
314
            foreach ($options['uniqueConstraints'] as $name => $definition) {
315
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
316
            }
317
        }
318
319 1817
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
320
321 1817
        if (isset($options['foreignKeys'])) {
322 1816
            foreach ($options['foreignKeys'] as $foreignKey) {
323 1571
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
324
            }
325
        }
326
327 1817
        $query = ['CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'];
328
329 1817
        if (isset($options['alter']) && $options['alter'] === true) {
330 1554
            return $query;
331
        }
332
333 1805
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
334 1644
            foreach ($options['indexes'] as $indexDef) {
335 1644
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
336
            }
337
        }
338
339 1805
        if (isset($options['unique']) && ! empty($options['unique'])) {
340
            foreach ($options['unique'] as $indexDef) {
341
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
342
            }
343
        }
344
345 1805
        return $query;
346
    }
347
348
    /**
349
     * Generate a PRIMARY KEY definition if no autoincrement value is used
350
     *
351
     * @param mixed[][] $columns
352
     * @param mixed[]   $options
353
     */
354 1817
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
355
    {
356 1817
        if (empty($options['primary'])) {
357 1488
            return '';
358
        }
359
360 1803
        $keyColumns = array_unique(array_values($options['primary']));
361
362 1803
        foreach ($keyColumns as $keyColumn) {
363 1803
            foreach ($columns as $column) {
364 1803
                if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) {
365 1745
                    return '';
366
                }
367
            }
368
        }
369
370 1704
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
371
    }
372
373
    /**
374
     * {@inheritdoc}
375
     */
376 771
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
377
    {
378 771
        return 'BLOB';
379
    }
380
381
    /**
382
     * {@inheritdoc}
383
     */
384 1774
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
385
    {
386 1774
        $sql = 'VARCHAR';
387
388 1774
        if ($length !== null) {
389 1773
            $sql .= sprintf('(%d)', $length);
390
        }
391
392 1774
        return $sql;
393
    }
394
395
    /**
396
     * {@inheritDoc}
397
     */
398 723
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
399
    {
400 723
        return 'BLOB';
401
    }
402
403
    /**
404
     * {@inheritDoc}
405
     */
406 784
    public function getClobTypeDeclarationSQL(array $field) : string
407
    {
408 784
        return 'CLOB';
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     */
414 1426
    public function getListTableConstraintsSQL(string $table) : string
415
    {
416 1426
        $table = str_replace('.', '__', $table);
417
418 1426
        return sprintf(
419 1
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
420 1426
            $this->quoteStringLiteral($table)
421
        );
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427 1532
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
428
    {
429 1532
        $table = str_replace('.', '__', $table);
430
431 1532
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
432
    }
433
434
    /**
435
     * {@inheritDoc}
436
     */
437 260
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
438
    {
439 260
        $table = str_replace('.', '__', $table);
440
441 260
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
442
    }
443
444
    /**
445
     * {@inheritDoc}
446
     */
447 313
    public function getListTablesSQL() : string
448
    {
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 313
             . "WHERE type = 'table' ORDER BY name";
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457 149
    public function getListViewsSQL(string $database) : string
458
    {
459 149
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
460
    }
461
462
    /**
463
     * {@inheritDoc}
464
     */
465 149
    public function getCreateViewSQL(string $name, string $sql) : string
466
    {
467 149
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
468
    }
469
470
    /**
471
     * {@inheritDoc}
472
     */
473 149
    public function getDropViewSQL(string $name) : string
474
    {
475 149
        return 'DROP VIEW ' . $name;
476
    }
477
478
    /**
479
     * {@inheritDoc}
480
     */
481 1571
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
482
    {
483 1571
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
484
485 1571
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
486 1571
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
487
488 1571
        return $query;
489
    }
490
491
    /**
492
     * {@inheritDoc}
493
     */
494 148
    public function supportsIdentityColumns() : bool
495
    {
496 148
        return true;
497
    }
498
499
    /**
500
     * {@inheritDoc}
501
     */
502 1469
    public function supportsColumnCollation() : bool
503
    {
504 1469
        return true;
505
    }
506
507
    /**
508
     * {@inheritDoc}
509
     */
510 1821
    public function supportsInlineColumnComments() : bool
511
    {
512 1821
        return true;
513
    }
514
515
    /**
516
     * {@inheritDoc}
517
     */
518 1499
    public function getName() : string
519
    {
520 1499
        return 'sqlite';
521
    }
522
523
    /**
524
     * {@inheritDoc}
525
     */
526 942
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
527
    {
528 942
        $tableIdentifier = new Identifier($tableName);
529 942
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
530
531 942
        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 232
    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 232
        if ($offset > 0) {
557 232
            $offset -= 1;
558
        }
559
560 232
        $pos = strpos($str, $substr, $offset);
561
562 232
        if ($pos !== false) {
563 232
            return $pos + 1;
564
        }
565
566 232
        return 0;
567
    }
568
569
    /**
570
     * {@inheritDoc}
571
     */
572
    public function getForUpdateSql() : string
573
    {
574
        return '';
575
    }
576
577
    /**
578
     * {@inheritDoc}
579
     */
580 584
    public function getInlineColumnCommentSQL(?string $comment) : string
581
    {
582 584
        if ($comment === null || $comment === '') {
583 301
            return '';
584
        }
585
586 583
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
587
    }
588
589
    /**
590
     * {@inheritDoc}
591
     */
592 1367
    protected function initializeDoctrineTypeMappings() : void
593
    {
594 1367
        $this->doctrineTypeMapping = [
595
            'bigint'           => 'bigint',
596
            'bigserial'        => 'bigint',
597
            'blob'             => 'blob',
598
            'boolean'          => 'boolean',
599
            'char'             => 'string',
600
            'clob'             => 'text',
601
            '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 1367
    }
630
631
    /**
632
     * {@inheritDoc}
633
     */
634 1831
    protected function getReservedKeywordsClass() : string
635
    {
636 1831
        return Keywords\SQLiteKeywords::class;
637
    }
638
639
    /**
640
     * {@inheritDoc}
641
     */
642 1554
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
643
    {
644 1554
        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 1554
        $sql = [];
649 1554
        foreach ($diff->fromTable->getIndexes() as $index) {
650 1548
            if ($index->isPrimary()) {
651 1546
                continue;
652
            }
653
654 1545
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
655
        }
656
657 1554
        return $sql;
658
    }
659
660
    /**
661
     * {@inheritDoc}
662
     */
663 1554
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
664
    {
665 1554
        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 1554
        $sql       = [];
670 1554
        $tableName = $diff->getNewName();
671
672 1554
        if ($tableName === null) {
673 1047
            $tableName = $diff->getName($this);
674
        }
675
676 1554
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
677 1548
            if ($index->isPrimary()) {
678 1546
                continue;
679
            }
680
681 1547
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
682
        }
683
684 1554
        return $sql;
685
    }
686
687
    /**
688
     * {@inheritDoc}
689
     */
690 1785
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
691
    {
692 1785
        if ($limit === null && $offset > 0) {
693 1758
            $limit = -1;
694
        }
695
696 1785
        return parent::doModifyLimitQuery($query, $limit, $offset);
697
    }
698
699
    /**
700
     * {@inheritDoc}
701
     */
702 279
    public function getBlobTypeDeclarationSQL(array $field) : string
703
    {
704 279
        return 'BLOB';
705
    }
706
707
    /**
708
     * {@inheritDoc}
709
     */
710 110
    public function getTemporaryTableName(string $tableName) : string
711
    {
712 110
        $tableName = str_replace('.', '__', $tableName);
713
714 110
        return $tableName;
715
    }
716
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 1652
    public function supportsForeignKeyConstraints() : bool
735
    {
736 1652
        return false;
737
    }
738
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 1677
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string
751
    {
752 1677
        throw new DBALException('Sqlite platform does not support alter foreign key.');
753
    }
754
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 1651
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
767
    {
768 1651
        throw new DBALException('Sqlite platform does not support alter constraint.');
769
    }
770
771
    /**
772
     * {@inheritDoc}
773
     */
774 1818
    public function getCreateTableSQL(Table $table, int $createFlags = self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS) : array
775
    {
776 1818
        return parent::getCreateTableSQL($table, $createFlags);
777
    }
778
779
    /**
780
     * {@inheritDoc}
781
     */
782 172
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
783
    {
784 172
        $table = str_replace('.', '__', $table);
785
786 172
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
787
    }
788
789
    /**
790
     * {@inheritDoc}
791
     */
792 1675
    public function getAlterTableSQL(TableDiff $diff) : array
793
    {
794 1675
        $sql = $this->getSimpleAlterTableSQL($diff);
795 1675
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
796 1661
            return $sql;
797
        }
798
799 1628
        $fromTable = $diff->fromTable;
800 1628
        if (! $fromTable instanceof Table) {
801 1527
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
802
        }
803
804 1554
        $table = clone $fromTable;
805
806 1554
        $columns        = [];
807 1554
        $oldColumnNames = [];
808 1554
        $newColumnNames = [];
809 1554
        $columnSql      = [];
810
811 1554
        foreach ($table->getColumns() as $columnName => $column) {
812 1553
            $columnName                  = strtolower($columnName);
813 1553
            $columns[$columnName]        = $column;
814 1553
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
815
        }
816
817 1554
        foreach ($diff->removedColumns as $columnName => $column) {
818 1546
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
819
                continue;
820
            }
821
822 1546
            $columnName = strtolower($columnName);
823 1546
            if (! isset($columns[$columnName])) {
824
                continue;
825
            }
826
827
            unset(
828 1546
                $columns[$columnName],
829 1546
                $oldColumnNames[$columnName],
830 1546
                $newColumnNames[$columnName]
831
            );
832
        }
833
834 1554
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
835 1455
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
836
                continue;
837
            }
838
839 1455
            $oldColumnName = strtolower($oldColumnName);
840 1455
            if (isset($columns[$oldColumnName])) {
841 1455
                unset($columns[$oldColumnName]);
842
            }
843
844 1455
            $columns[strtolower($column->getName())] = $column;
845
846 1455
            if (! isset($newColumnNames[$oldColumnName])) {
847
                continue;
848
            }
849
850 1455
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
851
        }
852
853 1554
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
854 1088
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
855
                continue;
856
            }
857
858 1088
            if (isset($columns[$oldColumnName])) {
859 1087
                unset($columns[$oldColumnName]);
860
            }
861
862 1088
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
863
864 1088
            if (! isset($newColumnNames[$oldColumnName])) {
865 701
                continue;
866
            }
867
868 1087
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
869
        }
870
871 1554
        foreach ($diff->addedColumns as $columnName => $column) {
872 1089
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
873
                continue;
874
            }
875
876 1089
            $columns[strtolower($columnName)] = $column;
877
        }
878
879 1554
        $sql      = [];
880 1554
        $tableSql = [];
881
882 1554
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
883 1554
            $dataTable = new Table('__temp__' . $table->getName());
884
885 1554
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), [], $this->getForeignKeysInAlteredTable($diff), $table->getOptions());
886 1554
            $newTable->addOption('alter', true);
887
888 1554
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
889
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
890 1554
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
891 1554
            $sql[] = $this->getDropTableSQL($fromTable);
892
893 1554
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
894 1554
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
895 1554
            $sql[] = $this->getDropTableSQL($dataTable);
896
897 1554
            $newName = $diff->getNewName();
898
899 1554
            if ($newName !== null) {
900 1453
                $sql[] = sprintf(
901 3
                    'ALTER TABLE %s RENAME TO %s',
902 1453
                    $newTable->getQuotedName($this),
903 1453
                    $newName->getQuotedName($this)
904
                );
905
            }
906
907 1554
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
908
        }
909
910 1554
        return array_merge($sql, $tableSql, $columnSql);
911
    }
912
913
    /**
914
     * @return string[]|false
915
     */
916 1675
    private function getSimpleAlterTableSQL(TableDiff $diff)
917
    {
918
        // Suppress changes on integer type autoincrement columns.
919 1675
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
920 1108
            if (! $columnDiff->fromColumn instanceof Column ||
921 1104
                ! $columnDiff->column instanceof Column ||
922 1104
                ! $columnDiff->column->getAutoincrement() ||
923 1108
                ! $columnDiff->column->getType() instanceof Types\IntegerType
924
            ) {
925 1088
                continue;
926
            }
927
928 166
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
929 163
                unset($diff->changedColumns[$oldColumnName]);
930
931 163
                continue;
932
            }
933
934 166
            $fromColumnType = $columnDiff->fromColumn->getType();
935
936 166
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
937
                continue;
938
            }
939
940 166
            unset($diff->changedColumns[$oldColumnName]);
941
        }
942
943 1675
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
944 1670
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
945 1666
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
946 1675
                || ! empty($diff->renamedIndexes)
947
        ) {
948 1554
            return false;
949
        }
950
951 1663
        $table = new Table($diff->name);
952
953 1663
        $sql       = [];
954 1663
        $tableSql  = [];
955 1663
        $columnSql = [];
956
957 1663
        foreach ($diff->addedColumns as $column) {
958 1553
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
959
                continue;
960
            }
961
962 1553
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
963 1553
            $type  = $field['type'];
964
            switch (true) {
965 1553
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
966 1552
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
967 1552
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
968 1551
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
969 1527
                    return false;
970
            }
971
972 1551
            $field['name'] = $column->getQuotedName($this);
973 1551
            if ($type instanceof Types\StringType && $field['length'] === null) {
974 1551
                $field['length'] = 255;
975
            }
976
977 1551
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
978
        }
979
980 1661
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
981 1661
            if ($diff->newName !== null) {
982 172
                $newTable = new Identifier($diff->newName);
983 172
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
984
            }
985
        }
986
987 1661
        return array_merge($sql, $tableSql, $columnSql);
988
    }
989
990
    /**
991
     * @return string[]
992
     */
993 1554
    private function getColumnNamesInAlteredTable(TableDiff $diff) : array
994
    {
995 1554
        $columns = [];
996
997 1554
        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 1553
            $columns[strtolower($columnName)] = $column->getName();
999
        }
1000
1001 1554
        foreach ($diff->removedColumns as $columnName => $column) {
1002 1546
            $columnName = strtolower($columnName);
1003 1546
            if (! isset($columns[$columnName])) {
1004
                continue;
1005
            }
1006
1007 1546
            unset($columns[$columnName]);
1008
        }
1009
1010 1554
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1011 1455
            $columnName                          = $column->getName();
1012 1455
            $columns[strtolower($oldColumnName)] = $columnName;
1013 1455
            $columns[strtolower($columnName)]    = $columnName;
1014
        }
1015
1016 1554
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1017 1088
            $columnName                          = $columnDiff->column->getName();
1018 1088
            $columns[strtolower($oldColumnName)] = $columnName;
1019 1088
            $columns[strtolower($columnName)]    = $columnName;
1020
        }
1021
1022 1554
        foreach ($diff->addedColumns as $column) {
1023 1089
            $columnName                       = $column->getName();
1024 1089
            $columns[strtolower($columnName)] = $columnName;
1025
        }
1026
1027 1554
        return $columns;
1028
    }
1029
1030
    /**
1031
     * @return Index[]
1032
     */
1033 1554
    private function getIndexesInAlteredTable(TableDiff $diff) : array
1034
    {
1035 1554
        $indexes     = $diff->fromTable->getIndexes();
1036 1554
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1037
1038 1554
        foreach ($indexes as $key => $index) {
1039 1548
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1040 633
                if (strtolower($key) !== strtolower($oldIndexName)) {
1041 633
                    continue;
1042
                }
1043
1044 343
                unset($indexes[$key]);
1045
            }
1046
1047 1548
            $changed      = false;
1048 1548
            $indexColumns = [];
1049 1548
            foreach ($index->getColumns() as $columnName) {
1050 1548
                $normalizedColumnName = strtolower($columnName);
1051 1548
                if (! isset($columnNames[$normalizedColumnName])) {
1052 1451
                    unset($indexes[$key]);
1053 1451
                    continue 2;
1054
                }
1055
1056 1548
                $indexColumns[] = $columnNames[$normalizedColumnName];
1057 1548
                if ($columnName === $columnNames[$normalizedColumnName]) {
1058 1548
                    continue;
1059
                }
1060
1061 1451
                $changed = true;
1062
            }
1063
1064 1548
            if (! $changed) {
1065 1548
                continue;
1066
            }
1067
1068 1451
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1069
        }
1070
1071 1554
        foreach ($diff->removedIndexes as $index) {
1072 1543
            $indexName = $index->getName();
1073
1074 1543
            if ($indexName === '') {
1075
                continue;
1076
            }
1077
1078 1543
            unset($indexes[strtolower($indexName)]);
1079
        }
1080
1081 1554
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1082 633
            $indexName = $index->getName();
1083
1084 633
            if ($indexName !== '') {
1085 633
                $indexes[strtolower($indexName)] = $index;
1086
            } else {
1087 3
                $indexes[] = $index;
1088
            }
1089
        }
1090
1091 1554
        return $indexes;
1092
    }
1093
1094
    /**
1095
     * @return ForeignKeyConstraint[]
1096
     */
1097 1554
    private function getForeignKeysInAlteredTable(TableDiff $diff) : array
1098
    {
1099 1554
        $foreignKeys = $diff->fromTable->getForeignKeys();
1100 1554
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1101
1102 1554
        foreach ($foreignKeys as $key => $constraint) {
1103 1453
            $changed      = false;
1104 1453
            $localColumns = [];
1105 1453
            foreach ($constraint->getLocalColumns() as $columnName) {
1106 1453
                $normalizedColumnName = strtolower($columnName);
1107 1453
                if (! isset($columnNames[$normalizedColumnName])) {
1108 1451
                    unset($foreignKeys[$key]);
1109 1451
                    continue 2;
1110
                }
1111
1112 1453
                $localColumns[] = $columnNames[$normalizedColumnName];
1113 1453
                if ($columnName === $columnNames[$normalizedColumnName]) {
1114 1453
                    continue;
1115
                }
1116
1117 1451
                $changed = true;
1118
            }
1119
1120 1453
            if (! $changed) {
1121 1453
                continue;
1122
            }
1123
1124 1451
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1125
        }
1126
1127 1554
        foreach ($diff->removedForeignKeys as $constraint) {
1128 251
            if (! $constraint instanceof ForeignKeyConstraint) {
1129
                $constraint = new Identifier($constraint);
1130
            }
1131
1132 251
            $constraintName = $constraint->getName();
1133
1134 251
            if ($constraintName === '') {
1135
                continue;
1136
            }
1137
1138 251
            unset($foreignKeys[strtolower($constraintName)]);
1139
        }
1140
1141 1554
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1142 391
            $constraintName = $constraint->getName();
1143
1144 391
            if ($constraintName !== '') {
1145 251
                $foreignKeys[strtolower($constraintName)] = $constraint;
1146
            } else {
1147 151
                $foreignKeys[] = $constraint;
1148
            }
1149
        }
1150
1151 1554
        return $foreignKeys;
1152
    }
1153
1154
    /**
1155
     * @return Index[]
1156
     */
1157 1554
    private function getPrimaryIndexInAlteredTable(TableDiff $diff) : array
1158
    {
1159 1554
        $primaryIndex = [];
1160
1161 1554
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1162 1548
            if (! $index->isPrimary()) {
1163 1547
                continue;
1164
            }
1165
1166 1546
            $primaryIndex = [$index->getName() => $index];
1167
        }
1168
1169 1554
        return $primaryIndex;
1170
    }
1171
}
1172