Completed
Push — develop ( dcb0ff...425513 )
by Sergei
23s queued 13s
created

SqlitePlatform::getCurrentDatabaseExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 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
     * The SQLite platform doesn't support the concept of a database, therefore, it always returns an empty string
157
     * as an indicator of an implicitly selected database.
158
     *
159 1851
     * @see \Doctrine\DBAL\Connection::getDatabase()
160 1851
     */
161 1851
    public function getCurrentDatabaseExpression() : string
162 1851
    {
163 1851
        return "''";
164 1851
    }
165
166
    /**
167
     * {@inheritDoc}
168
     */
169
    protected function _getTransactionIsolationLevelSQL(int $level) : string
170
    {
171
        switch ($level) {
172
            case TransactionIsolationLevel::READ_UNCOMMITTED:
173 1851
                return '0';
174
            case TransactionIsolationLevel::READ_COMMITTED:
175 1851
            case TransactionIsolationLevel::REPEATABLE_READ:
176
            case TransactionIsolationLevel::SERIALIZABLE:
177
                return '1';
178
            default:
179
                return parent::_getTransactionIsolationLevelSQL($level);
180
        }
181 1834
    }
182
183 1834
    /**
184
     * {@inheritDoc}
185
     */
186
    public function getSetTransactionIsolationSQL(int $level) : string
187
    {
188
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
189 1121
    }
190
191 1121
    /**
192
     * {@inheritDoc}
193
     */
194
    public function prefersIdentityColumns() : bool
195
    {
196
        return true;
197 2030
    }
198
199 2030
    /**
200
     * {@inheritDoc}
201
     */
202
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
203
    {
204
        return 'BOOLEAN';
205 1795
    }
206
207
    /**
208 1795
     * {@inheritDoc}
209 1795
     */
210
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
211
    {
212 1740
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 1777
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
219
    {
220
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
221 1777
        if (! empty($columnDef['autoincrement'])) {
222 1777
            return $this->getIntegerTypeDeclarationSQL($columnDef);
223
        }
224
225 1776
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 1847
    public function getTinyIntTypeDeclarationSql(array $field) : string
232
    {
233
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
234 1847
        if (! empty($field['autoincrement'])) {
235 1847
            return $this->getIntegerTypeDeclarationSQL($field);
236
        }
237
238 1824
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 1
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
245
    {
246
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
247 1
        if (! empty($columnDef['autoincrement'])) {
248 1
            return $this->getIntegerTypeDeclarationSQL($columnDef);
249
        }
250
251 1
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
252
    }
253
254
    /**
255
     * {@inheritDoc}
256
     */
257 271
    public function getMediumIntTypeDeclarationSql(array $field) : string
258
    {
259 271
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
260
        if (! empty($field['autoincrement'])) {
261
            return $this->getIntegerTypeDeclarationSQL($field);
262
        }
263
264
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
265 233
    }
266
267 233
    /**
268
     * {@inheritDoc}
269
     */
270
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
271
    {
272
        return 'DATETIME';
273 203
    }
274
275 203
    /**
276
     * {@inheritDoc}
277
     */
278
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
279
    {
280
        return 'DATE';
281 2030
    }
282
283
    /**
284 2030
     * {@inheritDoc}
285 1958
     */
286
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
287
    {
288 2003
        return 'TIME';
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 1571
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
295
    {
296 1571
        // sqlite autoincrement is only possible for the primary key
297 1571
        if (! empty($columnDef['autoincrement'])) {
298 1571
            return ' PRIMARY KEY AUTOINCREMENT';
299 1571
        }
300 1571
301 1571
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
308 1817
    {
309
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
310 1817
            $foreignKey->getQuotedLocalColumns($this),
311 1817
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
312
            $foreignKey->getQuotedForeignColumns($this),
313 1817
            $foreignKey->getName(),
314
            $foreignKey->getOptions()
315
        ));
316
    }
317
318
    /**
319 1817
     * {@inheritDoc}
320
     */
321 1817
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
322 1816
    {
323 1571
        $tableName   = str_replace('.', '__', $tableName);
324
        $queryFields = $this->getColumnDeclarationListSQL($columns);
325
326
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
327 1817
            foreach ($options['uniqueConstraints'] as $name => $definition) {
328
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
329 1817
            }
330 1554
        }
331
332
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
333 1805
334 1644
        if (isset($options['foreignKeys'])) {
335 1644
            foreach ($options['foreignKeys'] as $foreignKey) {
336
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
337
            }
338
        }
339 1805
340
        $query = ['CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'];
341
342
        if (isset($options['alter']) && $options['alter'] === true) {
343
            return $query;
344
        }
345 1805
346
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
347
            foreach ($options['indexes'] as $indexDef) {
348
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
349
            }
350
        }
351
352
        if (isset($options['unique']) && ! empty($options['unique'])) {
353
            foreach ($options['unique'] as $indexDef) {
354 1817
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
355
            }
356 1817
        }
357 1488
358
        return $query;
359
    }
360 1803
361
    /**
362 1803
     * Generate a PRIMARY KEY definition if no autoincrement value is used
363 1803
     *
364 1803
     * @param mixed[][] $columns
365 1745
     * @param mixed[]   $options
366
     */
367
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
368
    {
369
        if (empty($options['primary'])) {
370 1704
            return '';
371
        }
372
373
        $keyColumns = array_unique(array_values($options['primary']));
374
375
        foreach ($keyColumns as $keyColumn) {
376 771
            foreach ($columns as $column) {
377
                if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) {
378 771
                    return '';
379
                }
380
            }
381
        }
382
383
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
384 1774
    }
385
386 1774
    /**
387
     * {@inheritdoc}
388 1774
     */
389 1773
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
390
    {
391
        return 'BLOB';
392 1774
    }
393
394
    /**
395
     * {@inheritdoc}
396
     */
397
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
398 723
    {
399
        $sql = 'VARCHAR';
400 723
401
        if ($length !== null) {
402
            $sql .= sprintf('(%d)', $length);
403
        }
404
405
        return $sql;
406 784
    }
407
408 784
    /**
409
     * {@inheritDoc}
410
     */
411
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
412
    {
413
        return 'BLOB';
414 1426
    }
415
416 1426
    /**
417
     * {@inheritDoc}
418 1426
     */
419 1
    public function getClobTypeDeclarationSQL(array $field) : string
420 1426
    {
421
        return 'CLOB';
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427 1532
    public function getListTableConstraintsSQL(string $table) : string
428
    {
429 1532
        $table = str_replace('.', '__', $table);
430
431 1532
        return sprintf(
432
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
433
            $this->quoteStringLiteral($table)
434
        );
435
    }
436
437 260
    /**
438
     * {@inheritDoc}
439 260
     */
440
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
441 260
    {
442
        $table = str_replace('.', '__', $table);
443
444
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
445
    }
446
447 313
    /**
448
     * {@inheritDoc}
449
     */
450
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
451 313
    {
452
        $table = str_replace('.', '__', $table);
453
454
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
455
    }
456
457 149
    /**
458
     * {@inheritDoc}
459 149
     */
460
    public function getListTablesSQL() : string
461
    {
462
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
463
             . 'UNION ALL SELECT name FROM sqlite_temp_master '
464
             . "WHERE type = 'table' ORDER BY name";
465 149
    }
466
467 149
    /**
468
     * {@inheritDoc}
469
     */
470
    public function getListViewsSQL(string $database) : string
471
    {
472
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
473 149
    }
474
475 149
    /**
476
     * {@inheritDoc}
477
     */
478
    public function getCreateViewSQL(string $name, string $sql) : string
479
    {
480
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
481 1571
    }
482
483 1571
    /**
484
     * {@inheritDoc}
485 1571
     */
486 1571
    public function getDropViewSQL(string $name) : string
487
    {
488 1571
        return 'DROP VIEW ' . $name;
489
    }
490
491
    /**
492
     * {@inheritDoc}
493
     */
494 148
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
495
    {
496 148
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
497
498
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
499
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
500
501
        return $query;
502 1469
    }
503
504 1469
    /**
505
     * {@inheritDoc}
506
     */
507
    public function supportsIdentityColumns() : bool
508
    {
509
        return true;
510 1821
    }
511
512 1821
    /**
513
     * {@inheritDoc}
514
     */
515
    public function supportsColumnCollation() : bool
516
    {
517
        return true;
518 1499
    }
519
520 1499
    /**
521
     * {@inheritDoc}
522
     */
523
    public function supportsInlineColumnComments() : bool
524
    {
525
        return true;
526 942
    }
527
528 942
    /**
529 942
     * {@inheritDoc}
530
     */
531 942
    public function getName() : string
532
    {
533
        return 'sqlite';
534
    }
535
536
    /**
537
     * {@inheritDoc}
538
     */
539
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
540
    {
541
        $tableIdentifier = new Identifier($tableName);
542
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
543
544
        return 'DELETE FROM ' . $tableName;
545
    }
546
547
    /**
548
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
549
     *
550
     * @param int|float $value
551
     */
552 232
    public static function udfSqrt($value) : float
553
    {
554
        return sqrt($value);
555
    }
556 232
557 232
    /**
558
     * User-defined function for Sqlite that implements MOD(a, b).
559
     */
560 232
    public static function udfMod(int $a, int $b) : int
561
    {
562 232
        return $a % $b;
563 232
    }
564
565
    public static function udfLocate(string $str, string $substr, int $offset = 0) : int
566 232
    {
567
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
568
        // So we have to make them compatible if an offset is given.
569
        if ($offset > 0) {
570
            $offset -= 1;
571
        }
572
573
        $pos = strpos($str, $substr, $offset);
574
575
        if ($pos !== false) {
576
            return $pos + 1;
577
        }
578
579
        return 0;
580 584
    }
581
582 584
    /**
583 301
     * {@inheritDoc}
584
     */
585
    public function getForUpdateSql() : string
586 583
    {
587
        return '';
588
    }
589
590
    /**
591
     * {@inheritDoc}
592 1367
     */
593
    public function getInlineColumnCommentSQL(?string $comment) : string
594 1367
    {
595
        if ($comment === null || $comment === '') {
596
            return '';
597
        }
598
599
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
600
    }
601
602
    /**
603
     * {@inheritDoc}
604
     */
605
    protected function initializeDoctrineTypeMappings() : void
606
    {
607
        $this->doctrineTypeMapping = [
608
            'bigint'           => 'bigint',
609
            'bigserial'        => 'bigint',
610
            'blob'             => 'blob',
611
            'boolean'          => 'boolean',
612
            'char'             => 'string',
613
            'clob'             => 'text',
614
            'date'             => 'date',
615
            'datetime'         => 'datetime',
616
            'decimal'          => 'decimal',
617
            'double'           => 'float',
618
            'double precision' => 'float',
619
            'float'            => 'float',
620
            'image'            => 'string',
621
            'int'              => 'integer',
622
            'integer'          => 'integer',
623
            'longtext'         => 'text',
624
            'longvarchar'      => 'string',
625
            'mediumint'        => 'integer',
626
            'mediumtext'       => 'text',
627
            'ntext'            => 'string',
628
            'numeric'          => 'decimal',
629 1367
            'nvarchar'         => 'string',
630
            'real'             => 'float',
631
            'serial'           => 'integer',
632
            'smallint'         => 'smallint',
633
            'string'           => 'string',
634 1831
            'text'             => 'text',
635
            'time'             => 'time',
636 1831
            'timestamp'        => 'datetime',
637
            'tinyint'          => 'boolean',
638
            'tinytext'         => 'text',
639
            'varchar'          => 'string',
640
            'varchar2'         => 'string',
641
        ];
642 1554
    }
643
644 1554
    /**
645
     * {@inheritDoc}
646
     */
647
    protected function getReservedKeywordsClass() : string
648 1554
    {
649 1554
        return Keywords\SQLiteKeywords::class;
650 1548
    }
651 1546
652
    /**
653
     * {@inheritDoc}
654 1545
     */
655
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
656
    {
657 1554
        if (! $diff->fromTable instanceof Table) {
658
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
659
        }
660
661
        $sql = [];
662
        foreach ($diff->fromTable->getIndexes() as $index) {
663 1554
            if ($index->isPrimary()) {
664
                continue;
665 1554
            }
666
667
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
668
        }
669 1554
670 1554
        return $sql;
671
    }
672 1554
673 1047
    /**
674
     * {@inheritDoc}
675
     */
676 1554
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
677 1548
    {
678 1546
        if (! $diff->fromTable instanceof Table) {
679
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
680
        }
681 1547
682
        $sql       = [];
683
        $tableName = $diff->getNewName();
684 1554
685
        if ($tableName === null) {
686
            $tableName = $diff->getName($this);
687
        }
688
689
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
690 1785
            if ($index->isPrimary()) {
691
                continue;
692 1785
            }
693 1758
694
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
695
        }
696 1785
697
        return $sql;
698
    }
699
700
    /**
701
     * {@inheritDoc}
702 279
     */
703
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
704 279
    {
705
        if ($limit === null && $offset > 0) {
706
            $limit = -1;
707
        }
708
709
        return parent::doModifyLimitQuery($query, $limit, $offset);
710 110
    }
711
712 110
    /**
713
     * {@inheritDoc}
714 110
     */
715
    public function getBlobTypeDeclarationSQL(array $field) : string
716
    {
717
        return 'BLOB';
718
    }
719
720
    /**
721
     * {@inheritDoc}
722
     */
723
    public function getTemporaryTableName(string $tableName) : string
724
    {
725
        $tableName = str_replace('.', '__', $tableName);
726
727
        return $tableName;
728
    }
729
730
    /**
731
     * {@inheritDoc}
732
     *
733
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
734 1652
     * into the default database.
735
     *
736 1652
     * This hack is implemented to be able to use SQLite as testdriver when
737
     * using schema supporting databases.
738
     */
739
    public function canEmulateSchemas() : bool
740
    {
741
        return true;
742
    }
743
744
    /**
745
     * {@inheritDoc}
746
     */
747
    public function supportsForeignKeyConstraints() : bool
748
    {
749
        return false;
750 1677
    }
751
752 1677
    /**
753
     * {@inheritDoc}
754
     */
755
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
756
    {
757
        throw new DBALException('Sqlite platform does not support alter primary key.');
758
    }
759
760
    /**
761
     * {@inheritdoc}
762
     */
763
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string
764
    {
765
        throw new DBALException('Sqlite platform does not support alter foreign key.');
766 1651
    }
767
768 1651
    /**
769
     * {@inheritdoc}
770
     */
771
    public function getDropForeignKeySQL($foreignKey, $table) : string
772
    {
773
        throw new DBALException('Sqlite platform does not support alter foreign key.');
774 1818
    }
775
776 1818
    /**
777
     * {@inheritDoc}
778
     */
779
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
780
    {
781
        throw new DBALException('Sqlite platform does not support alter constraint.');
782 172
    }
783
784 172
    /**
785
     * {@inheritDoc}
786 172
     */
787
    public function getCreateTableSQL(Table $table, int $createFlags = self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS) : array
788
    {
789
        return parent::getCreateTableSQL($table, $createFlags);
790
    }
791
792 1675
    /**
793
     * {@inheritDoc}
794 1675
     */
795 1675
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
796 1661
    {
797
        $table = str_replace('.', '__', $table);
798
799 1628
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
800 1628
    }
801 1527
802
    /**
803
     * {@inheritDoc}
804 1554
     */
805
    public function getAlterTableSQL(TableDiff $diff) : array
806 1554
    {
807 1554
        $sql = $this->getSimpleAlterTableSQL($diff);
808 1554
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
809 1554
            return $sql;
810
        }
811 1554
812 1553
        $fromTable = $diff->fromTable;
813 1553
        if (! $fromTable instanceof Table) {
814 1553
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
815
        }
816
817 1554
        $table = clone $fromTable;
818 1546
819
        $columns        = [];
820
        $oldColumnNames = [];
821
        $newColumnNames = [];
822 1546
        $columnSql      = [];
823 1546
824
        foreach ($table->getColumns() as $columnName => $column) {
825
            $columnName                  = strtolower($columnName);
826
            $columns[$columnName]        = $column;
827
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
828 1546
        }
829 1546
830 1546
        foreach ($diff->removedColumns as $columnName => $column) {
831
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
832
                continue;
833
            }
834 1554
835 1455
            $columnName = strtolower($columnName);
836
            if (! isset($columns[$columnName])) {
837
                continue;
838
            }
839 1455
840 1455
            unset(
841 1455
                $columns[$columnName],
842
                $oldColumnNames[$columnName],
843
                $newColumnNames[$columnName]
844 1455
            );
845
        }
846 1455
847
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
848
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
849
                continue;
850 1455
            }
851
852
            $oldColumnName = strtolower($oldColumnName);
853 1554
            if (isset($columns[$oldColumnName])) {
854 1088
                unset($columns[$oldColumnName]);
855
            }
856
857
            $columns[strtolower($column->getName())] = $column;
858 1088
859 1087
            if (! isset($newColumnNames[$oldColumnName])) {
860
                continue;
861
            }
862 1088
863
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
864 1088
        }
865 701
866
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
867
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
868 1087
                continue;
869
            }
870
871 1554
            if (isset($columns[$oldColumnName])) {
872 1089
                unset($columns[$oldColumnName]);
873
            }
874
875
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
876 1089
877
            if (! isset($newColumnNames[$oldColumnName])) {
878
                continue;
879 1554
            }
880 1554
881
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
882 1554
        }
883 1554
884
        foreach ($diff->addedColumns as $columnName => $column) {
885 1554
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
886 1554
                continue;
887
            }
888 1554
889
            $columns[strtolower($columnName)] = $column;
890 1554
        }
891 1554
892
        $sql      = [];
893 1554
        $tableSql = [];
894 1554
895 1554
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
896
            $dataTable = new Table('__temp__' . $table->getName());
897 1554
898
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), [], $this->getForeignKeysInAlteredTable($diff), $table->getOptions());
899 1554
            $newTable->addOption('alter', true);
900 1453
901 3
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
902 1453
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
903 1453
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
904
            $sql[] = $this->getDropTableSQL($fromTable);
905
906
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
907 1554
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
908
            $sql[] = $this->getDropTableSQL($dataTable);
909
910 1554
            $newName = $diff->getNewName();
911
912
            if ($newName !== null) {
913
                $sql[] = sprintf(
914
                    'ALTER TABLE %s RENAME TO %s',
915
                    $newTable->getQuotedName($this),
916 1675
                    $newName->getQuotedName($this)
917
                );
918
            }
919 1675
920 1108
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
921 1104
        }
922 1104
923 1108
        return array_merge($sql, $tableSql, $columnSql);
924
    }
925 1088
926
    /**
927
     * @return string[]|false
928 166
     */
929 163
    private function getSimpleAlterTableSQL(TableDiff $diff)
930
    {
931 163
        // Suppress changes on integer type autoincrement columns.
932
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
933
            if (! $columnDiff->fromColumn instanceof Column ||
934 166
                ! $columnDiff->column instanceof Column ||
935
                ! $columnDiff->column->getAutoincrement() ||
936 166
                ! $columnDiff->column->getType() instanceof Types\IntegerType
937
            ) {
938
                continue;
939
            }
940 166
941
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
942
                unset($diff->changedColumns[$oldColumnName]);
943 1675
944 1670
                continue;
945 1666
            }
946 1675
947
            $fromColumnType = $columnDiff->fromColumn->getType();
948 1554
949
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
950
                continue;
951 1663
            }
952
953 1663
            unset($diff->changedColumns[$oldColumnName]);
954 1663
        }
955 1663
956
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
957 1663
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
958 1553
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
959
                || ! empty($diff->renamedIndexes)
960
        ) {
961
            return false;
962 1553
        }
963 1553
964
        $table = new Table($diff->name);
965 1553
966 1552
        $sql       = [];
967 1552
        $tableSql  = [];
968 1551
        $columnSql = [];
969 1527
970
        foreach ($diff->addedColumns as $column) {
971
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
972 1551
                continue;
973 1551
            }
974 1551
975
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
976
            $type  = $field['type'];
977 1551
            switch (true) {
978
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
979
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
980 1661
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
981 1661
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
982 172
                    return false;
983 172
            }
984
985
            $field['name'] = $column->getQuotedName($this);
986
            if ($type instanceof Types\StringType && $field['length'] === null) {
987 1661
                $field['length'] = 255;
988
            }
989
990
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
991
        }
992
993 1554
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
994
            if ($diff->newName !== null) {
995 1554
                $newTable = new Identifier($diff->newName);
996
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
997 1554
            }
998 1553
        }
999
1000
        return array_merge($sql, $tableSql, $columnSql);
1001 1554
    }
1002 1546
1003 1546
    /**
1004
     * @return string[]
1005
     */
1006
    private function getColumnNamesInAlteredTable(TableDiff $diff) : array
1007 1546
    {
1008
        $columns = [];
1009
1010 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

1010
        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...
1011 1455
            $columns[strtolower($columnName)] = $column->getName();
1012 1455
        }
1013 1455
1014
        foreach ($diff->removedColumns as $columnName => $column) {
1015
            $columnName = strtolower($columnName);
1016 1554
            if (! isset($columns[$columnName])) {
1017 1088
                continue;
1018 1088
            }
1019 1088
1020
            unset($columns[$columnName]);
1021
        }
1022 1554
1023 1089
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1024 1089
            $columnName                          = $column->getName();
1025
            $columns[strtolower($oldColumnName)] = $columnName;
1026
            $columns[strtolower($columnName)]    = $columnName;
1027 1554
        }
1028
1029
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1030
            $columnName                          = $columnDiff->column->getName();
1031
            $columns[strtolower($oldColumnName)] = $columnName;
1032
            $columns[strtolower($columnName)]    = $columnName;
1033 1554
        }
1034
1035 1554
        foreach ($diff->addedColumns as $column) {
1036 1554
            $columnName                       = $column->getName();
1037
            $columns[strtolower($columnName)] = $columnName;
1038 1554
        }
1039 1548
1040 633
        return $columns;
1041 633
    }
1042
1043
    /**
1044 343
     * @return Index[]
1045
     */
1046
    private function getIndexesInAlteredTable(TableDiff $diff) : array
1047 1548
    {
1048 1548
        $indexes     = $diff->fromTable->getIndexes();
1049 1548
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1050 1548
1051 1548
        foreach ($indexes as $key => $index) {
1052 1451
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1053 1451
                if (strtolower($key) !== strtolower($oldIndexName)) {
1054
                    continue;
1055
                }
1056 1548
1057 1548
                unset($indexes[$key]);
1058 1548
            }
1059
1060
            $changed      = false;
1061 1451
            $indexColumns = [];
1062
            foreach ($index->getColumns() as $columnName) {
1063
                $normalizedColumnName = strtolower($columnName);
1064 1548
                if (! isset($columnNames[$normalizedColumnName])) {
1065 1548
                    unset($indexes[$key]);
1066
                    continue 2;
1067
                }
1068 1451
1069
                $indexColumns[] = $columnNames[$normalizedColumnName];
1070
                if ($columnName === $columnNames[$normalizedColumnName]) {
1071 1554
                    continue;
1072 1543
                }
1073
1074 1543
                $changed = true;
1075
            }
1076
1077
            if (! $changed) {
1078 1543
                continue;
1079
            }
1080
1081 1554
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1082 633
        }
1083
1084 633
        foreach ($diff->removedIndexes as $index) {
1085 633
            $indexName = $index->getName();
1086
1087 3
            if ($indexName === '') {
1088
                continue;
1089
            }
1090
1091 1554
            unset($indexes[strtolower($indexName)]);
1092
        }
1093
1094
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1095
            $indexName = $index->getName();
1096
1097 1554
            if ($indexName !== '') {
1098
                $indexes[strtolower($indexName)] = $index;
1099 1554
            } else {
1100 1554
                $indexes[] = $index;
1101
            }
1102 1554
        }
1103 1453
1104 1453
        return $indexes;
1105 1453
    }
1106 1453
1107 1453
    /**
1108 1451
     * @return ForeignKeyConstraint[]
1109 1451
     */
1110
    private function getForeignKeysInAlteredTable(TableDiff $diff) : array
1111
    {
1112 1453
        $foreignKeys = $diff->fromTable->getForeignKeys();
1113 1453
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1114 1453
1115
        foreach ($foreignKeys as $key => $constraint) {
1116
            $changed      = false;
1117 1451
            $localColumns = [];
1118
            foreach ($constraint->getLocalColumns() as $columnName) {
1119
                $normalizedColumnName = strtolower($columnName);
1120 1453
                if (! isset($columnNames[$normalizedColumnName])) {
1121 1453
                    unset($foreignKeys[$key]);
1122
                    continue 2;
1123
                }
1124 1451
1125
                $localColumns[] = $columnNames[$normalizedColumnName];
1126
                if ($columnName === $columnNames[$normalizedColumnName]) {
1127 1554
                    continue;
1128 251
                }
1129
1130
                $changed = true;
1131
            }
1132 251
1133
            if (! $changed) {
1134 251
                continue;
1135
            }
1136
1137
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1138 251
        }
1139
1140
        foreach ($diff->removedForeignKeys as $constraint) {
1141 1554
            if (! $constraint instanceof ForeignKeyConstraint) {
1142 391
                $constraint = new Identifier($constraint);
1143
            }
1144 391
1145 251
            $constraintName = $constraint->getName();
1146
1147 151
            if ($constraintName === '') {
1148
                continue;
1149
            }
1150
1151 1554
            unset($foreignKeys[strtolower($constraintName)]);
1152
        }
1153
1154
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1155
            $constraintName = $constraint->getName();
1156
1157 1554
            if ($constraintName !== '') {
1158
                $foreignKeys[strtolower($constraintName)] = $constraint;
1159 1554
            } else {
1160
                $foreignKeys[] = $constraint;
1161 1554
            }
1162 1548
        }
1163 1547
1164
        return $foreignKeys;
1165
    }
1166 1546
1167
    /**
1168
     * @return Index[]
1169 1554
     */
1170
    private function getPrimaryIndexInAlteredTable(TableDiff $diff) : array
1171
    {
1172
        $primaryIndex = [];
1173
1174
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1175
            if (! $index->isPrimary()) {
1176
                continue;
1177
            }
1178
1179
            $primaryIndex = [$index->getName() => $index];
1180
        }
1181
1182
        return $primaryIndex;
1183
    }
1184
}
1185