Failed Conditions
Push — master ( ea4232...3b6e69 )
by Grégoire
17:30 queued 17:24
created

supportsCreateDropForeignKeyConstraints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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