Failed Conditions
Pull Request — develop (#3348)
by Sergei
22:47
created

SqlitePlatform::getForeignKeysInAlteredTable()   B

Complexity

Conditions 11
Paths 120

Size

Total Lines 55
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 11.0324

Importance

Changes 0
Metric Value
eloc 31
dl 0
loc 55
ccs 29
cts 31
cp 0.9355
rs 7.15
c 0
b 0
f 0
cc 11
nc 120
nop 1
crap 11.0324

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

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

984
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
985 177
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
986
            }
987
        }
988
989 1558
        return array_merge($sql, $tableSql, $columnSql);
990
    }
991
992
    /**
993
     * @return string[]
994
     */
995 1455
    private function getColumnNamesInAlteredTable(TableDiff $diff) : array
996
    {
997 1455
        $columns = [];
998
999 1455
        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

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