Failed Conditions
Push — master ( 01c22b...e42c1f )
by Marco
79:13 queued 10s
created

SqlitePlatform::getForeignKeysInAlteredTable()   B

Complexity

Conditions 11
Paths 120

Size

Total Lines 55
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11.0359

Importance

Changes 0
Metric Value
eloc 31
dl 0
loc 55
ccs 28
cts 30
cp 0.9333
rs 7.15
c 0
b 0
f 0
cc 11
nc 120
nop 1
crap 11.0359

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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