Completed
Pull Request — 2.11.x (#3935)
by Sergei
87:20 queued 83:15
created

SqlitePlatform::getColumnNamesInAlteredTable()   B

Complexity

Conditions 7
Paths 48

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7.0052

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 35
ccs 20
cts 21
cp 0.9524
rs 8.6666
c 0
b 0
f 0
cc 7
nc 48
nop 1
crap 7.0052
1
<?php
2
3
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\Constraint;
8
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
9
use Doctrine\DBAL\Schema\Identifier;
10
use Doctrine\DBAL\Schema\Index;
11
use Doctrine\DBAL\Schema\Table;
12
use Doctrine\DBAL\Schema\TableDiff;
13
use Doctrine\DBAL\TransactionIsolationLevel;
14
use Doctrine\DBAL\Types;
15
use function array_merge;
16
use function array_unique;
17
use function array_values;
18
use function implode;
19
use function is_numeric;
20
use function sprintf;
21
use function sqrt;
22
use function str_replace;
23
use function strlen;
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
    /**
37
     * {@inheritDoc}
38
     */
39 1680
    public function getRegexpExpression()
40
    {
41 1680
        return 'REGEXP';
42
    }
43
44
    /**
45
     * {@inheritDoc}
46
     *
47
     * @deprecated Use application-generated UUIDs instead
48
     */
49 109
    public function getGuidExpression()
50
    {
51
        return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || "
52
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || "
53
            . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || "
54 109
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))";
55
    }
56
57
    /**
58
     * @param string $type
59
     *
60
     * @return string
61
     */
62
    public function getNowExpression($type = 'timestamp')
63
    {
64
        switch ($type) {
65
            case 'time':
66
                return 'time(\'now\')';
67
            case 'date':
68
                return 'date(\'now\')';
69
            case 'timestamp':
70
            default:
71
                return 'datetime(\'now\')';
72
        }
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78 255
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
79
    {
80 255
        $trimChar = $char !== false ? (', ' . $char) : '';
81
82 255
        switch ($pos) {
83
            case TrimMode::LEADING:
84 254
                $trimFn = 'LTRIM';
85 254
                break;
86
87
            case TrimMode::TRAILING:
88 253
                $trimFn = 'RTRIM';
89 253
                break;
90
91
            default:
92 255
                $trimFn = 'TRIM';
93
        }
94
95 255
        return $trimFn . '(' . $str . $trimChar . ')';
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     *
101
     * SQLite only supports the 2 parameter variant of this function
102
     */
103 1680
    public function getSubstringExpression($value, $position, $length = null)
104
    {
105 1680
        if ($length !== null) {
106 1680
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
107
        }
108
109 1680
        return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
110
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 241
    public function getLocateExpression($str, $substr, $startPos = false)
116
    {
117 241
        if ($startPos === false) {
118 241
            return 'LOCATE(' . $str . ', ' . $substr . ')';
119
        }
120
121 241
        return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')';
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 1323
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
128
    {
129 1323
        switch ($unit) {
130
            case DateIntervalUnit::SECOND:
131
            case DateIntervalUnit::MINUTE:
132
            case DateIntervalUnit::HOUR:
133 243
                return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')";
134
135
            default:
136 1323
                switch ($unit) {
137
                    case DateIntervalUnit::WEEK:
138 243
                        $interval *= 7;
139 243
                        $unit      = DateIntervalUnit::DAY;
140 243
                        break;
141
142
                    case DateIntervalUnit::QUARTER:
143 243
                        $interval *= 3;
144 243
                        $unit      = DateIntervalUnit::MONTH;
145 243
                        break;
146
                }
147
148 1323
                if (! is_numeric($interval)) {
149 1321
                    $interval = "' || " . $interval . " || '";
150
                }
151
152 1323
                return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')";
153
        }
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 217
    public function getDateDiffExpression($date1, $date2)
160
    {
161 217
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167 1657
    protected function _getTransactionIsolationLevelSQL($level)
168
    {
169 1
        switch ($level) {
170 1656
            case TransactionIsolationLevel::READ_UNCOMMITTED:
171 1657
                return '0';
172 1656
            case TransactionIsolationLevel::READ_COMMITTED:
173 1656
            case TransactionIsolationLevel::REPEATABLE_READ:
174 1656
            case TransactionIsolationLevel::SERIALIZABLE:
175 1657
                return '1';
176
            default:
177
                return parent::_getTransactionIsolationLevelSQL($level);
178
        }
179
    }
180
181
    /**
182
     * {@inheritDoc}
183
     */
184 1657
    public function getSetTransactionIsolationSQL($level)
185
    {
186 1657
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     */
192 1643
    public function prefersIdentityColumns()
193
    {
194 1643
        return true;
195
    }
196
197
    /**
198
     * {@inheritDoc}
199
     */
200 922
    public function getBooleanTypeDeclarationSQL(array $field)
201
    {
202 922
        return 'BOOLEAN';
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208 1828
    public function getIntegerTypeDeclarationSQL(array $field)
209
    {
210 1828
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 1622
    public function getBigIntTypeDeclarationSQL(array $field)
217
    {
218
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
219 1622
        if (! empty($field['autoincrement'])) {
220 1622
            return $this->getIntegerTypeDeclarationSQL($field);
221
        }
222
223 1559
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
224
    }
225
226
    /**
227
     * @param array<string, mixed> $field
228
     *
229
     * @return string
230
     */
231 1589
    public function getTinyIntTypeDeclarationSql(array $field)
232
    {
233
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
234 1589
        if (! empty($field['autoincrement'])) {
235 1589
            return $this->getIntegerTypeDeclarationSQL($field);
236
        }
237
238 1588
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 1670
    public function getSmallIntTypeDeclarationSQL(array $field)
245
    {
246
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
247 1670
        if (! empty($field['autoincrement'])) {
248 1670
            return $this->getIntegerTypeDeclarationSQL($field);
249
        }
250
251 1647
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
252
    }
253
254
    /**
255
     * @param array<string, mixed> $field
256
     *
257
     * @return string
258
     */
259 1
    public function getMediumIntTypeDeclarationSql(array $field)
260
    {
261
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
262 1
        if (! empty($field['autoincrement'])) {
263 1
            return $this->getIntegerTypeDeclarationSQL($field);
264
        }
265
266 1
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 256
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
273
    {
274 256
        return 'DATETIME';
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280 242
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
281
    {
282 242
        return 'DATE';
283
    }
284
285
    /**
286
     * {@inheritDoc}
287
     */
288 214
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
289
    {
290 214
        return 'TIME';
291
    }
292
293
    /**
294
     * {@inheritDoc}
295
     */
296 1828
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
297
    {
298
        // sqlite autoincrement is only possible for the primary key
299 1828
        if (! empty($columnDef['autoincrement'])) {
300 1781
            return ' PRIMARY KEY AUTOINCREMENT';
301
        }
302
303 1803
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
304
    }
305
306
    /**
307
     * {@inheritDoc}
308
     */
309 1372
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
310
    {
311 1372
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
312 1372
            $foreignKey->getQuotedLocalColumns($this),
313 1372
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
314 1372
            $foreignKey->getQuotedForeignColumns($this),
315 1372
            $foreignKey->getName(),
316 1372
            $foreignKey->getOptions()
317
        ));
318
    }
319
320
    /**
321
     * {@inheritDoc}
322
     */
323 1589
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
324
    {
325 1589
        $name        = str_replace('.', '__', $name);
326 1589
        $queryFields = $this->getColumnDeclarationListSQL($columns);
327
328 1589
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
329
            foreach ($options['uniqueConstraints'] as $name => $definition) {
0 ignored issues
show
introduced by
$name is overwriting one of the parameters of this function.
Loading history...
330
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
331
            }
332
        }
333
334 1589
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
335
336 1589
        if (isset($options['foreignKeys'])) {
337 1588
            foreach ($options['foreignKeys'] as $foreignKey) {
338 1372
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
339
            }
340
        }
341
342 1589
        $tableComment = '';
343 1589
        if (isset($options['comment'])) {
344 131
            $comment = trim($options['comment'], " '");
345
346 131
            $tableComment = $this->getInlineTableCommentSQL($comment);
347
        }
348
349 1589
        $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')'];
350
351 1589
        if (isset($options['alter']) && $options['alter'] === true) {
352 1357
            return $query;
353
        }
354
355 1577
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
356 1449
            foreach ($options['indexes'] as $indexDef) {
357 1449
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
358
            }
359
        }
360
361 1577
        if (isset($options['unique']) && ! empty($options['unique'])) {
362
            foreach ($options['unique'] as $indexDef) {
363
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
364
            }
365
        }
366
367 1577
        return $query;
368
    }
369
370
    /**
371
     * Generate a PRIMARY KEY definition if no autoincrement value is used
372
     *
373
     * @param mixed[][] $columns
374
     * @param mixed[]   $options
375
     */
376 1589
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
377
    {
378 1589
        if (empty($options['primary'])) {
379 1263
            return '';
380
        }
381
382 1575
        $keyColumns = array_unique(array_values($options['primary']));
383
384 1575
        foreach ($keyColumns as $keyColumn) {
385 1575
            if (! empty($columns[$keyColumn]['autoincrement'])) {
386 1533
                return '';
387
            }
388
        }
389
390 1484
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
391
    }
392
393
    /**
394
     * {@inheritDoc}
395
     */
396 1702
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
397
    {
398 1702
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
399 1702
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
400
    }
401
402
    /**
403
     * {@inheritdoc}
404
     */
405 1343
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
406
    {
407 1343
        return 'BLOB';
408
    }
409
410
    /**
411
     * {@inheritdoc}
412
     */
413 1344
    public function getBinaryMaxLength()
414
    {
415 1344
        return 0;
416
    }
417
418
    /**
419
     * {@inheritdoc}
420
     */
421 1344
    public function getBinaryDefaultLength()
422
    {
423 1344
        return 0;
424
    }
425
426
    /**
427
     * {@inheritDoc}
428
     */
429 706
    public function getClobTypeDeclarationSQL(array $field)
430
    {
431 706
        return 'CLOB';
432
    }
433
434
    /**
435
     * {@inheritDoc}
436
     */
437 1174
    public function getListTableConstraintsSQL($table)
438
    {
439 1174
        $table = str_replace('.', '__', $table);
440
441 1174
        return sprintf(
442 1
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
443 1174
            $this->quoteStringLiteral($table)
444
        );
445
    }
446
447
    /**
448
     * {@inheritDoc}
449
     */
450 1299
    public function getListTableColumnsSQL($table, $currentDatabase = null)
451
    {
452 1299
        $table = str_replace('.', '__', $table);
453
454 1299
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
455
    }
456
457
    /**
458
     * {@inheritDoc}
459
     */
460 265
    public function getListTableIndexesSQL($table, $currentDatabase = null)
461
    {
462 265
        $table = str_replace('.', '__', $table);
463
464 265
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
465
    }
466
467
    /**
468
     * {@inheritDoc}
469
     */
470 316
    public function getListTablesSQL()
471
    {
472
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
473
             . 'UNION ALL SELECT name FROM sqlite_temp_master '
474 316
             . "WHERE type = 'table' ORDER BY name";
475
    }
476
477
    /**
478
     * {@inheritDoc}
479
     */
480 156
    public function getListViewsSQL($database)
481
    {
482 156
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
483
    }
484
485
    /**
486
     * {@inheritDoc}
487
     */
488 156
    public function getCreateViewSQL($name, $sql)
489
    {
490 156
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
491
    }
492
493
    /**
494
     * {@inheritDoc}
495
     */
496 156
    public function getDropViewSQL($name)
497
    {
498 156
        return 'DROP VIEW ' . $name;
499
    }
500
501
    /**
502
     * {@inheritDoc}
503
     */
504 1372
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
505
    {
506 1372
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
507
508 1372
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
509 1372
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
510
511 1372
        return $query;
512
    }
513
514
    /**
515
     * {@inheritDoc}
516
     */
517 155
    public function supportsIdentityColumns()
518
    {
519 155
        return true;
520
    }
521
522
    /**
523
     * {@inheritDoc}
524
     */
525 1236
    public function supportsColumnCollation()
526
    {
527 1236
        return true;
528
    }
529
530
    /**
531
     * {@inheritDoc}
532
     */
533 1593
    public function supportsInlineColumnComments()
534
    {
535 1593
        return true;
536
    }
537
538
    /**
539
     * {@inheritDoc}
540
     */
541 1897
    public function getName()
542
    {
543 1897
        return 'sqlite';
544
    }
545
546
    /**
547
     * {@inheritDoc}
548
     */
549 761
    public function getTruncateTableSQL($tableName, $cascade = false)
550
    {
551 761
        $tableIdentifier = new Identifier($tableName);
552 761
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
553
554 761
        return 'DELETE FROM ' . $tableName;
555
    }
556
557
    /**
558
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
559
     *
560
     * @param int|float $value
561
     *
562
     * @return float
563
     */
564
    public static function udfSqrt($value)
565
    {
566
        return sqrt($value);
567
    }
568
569
    /**
570
     * User-defined function for Sqlite that implements MOD(a, b).
571
     *
572
     * @param int $a
573
     * @param int $b
574
     *
575
     * @return int
576
     */
577
    public static function udfMod($a, $b)
578
    {
579
        return $a % $b;
580
    }
581
582
    /**
583
     * @param string $str
584
     * @param string $substr
585
     * @param int    $offset
586
     *
587
     * @return int
588
     */
589 241
    public static function udfLocate($str, $substr, $offset = 0)
590
    {
591
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
592
        // So we have to make them compatible if an offset is given.
593 241
        if ($offset > 0) {
594 241
            $offset -= 1;
595
        }
596
597 241
        $pos = strpos($str, $substr, $offset);
598
599 241
        if ($pos !== false) {
600 241
            return $pos + 1;
601
        }
602
603 241
        return 0;
604
    }
605
606
    /**
607
     * {@inheritDoc}
608
     */
609
    public function getForUpdateSQL()
610
    {
611
        return '';
612
    }
613
614
    /**
615
     * {@inheritDoc}
616
     */
617 533
    public function getInlineColumnCommentSQL($comment)
618
    {
619 533
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
620
    }
621
622 131
    private function getInlineTableCommentSQL(string $comment) : string
623
    {
624 131
        return $this->getInlineColumnCommentSQL($comment);
625
    }
626
627
    /**
628
     * {@inheritDoc}
629
     */
630 1148
    protected function initializeDoctrineTypeMappings()
631
    {
632 1148
        $this->doctrineTypeMapping = [
633
            'boolean'          => 'boolean',
634
            'tinyint'          => 'boolean',
635
            'smallint'         => 'smallint',
636
            'mediumint'        => 'integer',
637
            'int'              => 'integer',
638
            'integer'          => 'integer',
639
            'serial'           => 'integer',
640
            'bigint'           => 'bigint',
641
            'bigserial'        => 'bigint',
642
            'clob'             => 'text',
643
            'tinytext'         => 'text',
644
            'mediumtext'       => 'text',
645
            'longtext'         => 'text',
646
            'text'             => 'text',
647
            'varchar'          => 'string',
648
            'longvarchar'      => 'string',
649
            'varchar2'         => 'string',
650
            'nvarchar'         => 'string',
651
            'image'            => 'string',
652
            'ntext'            => 'string',
653
            'char'             => 'string',
654
            'date'             => 'date',
655
            'datetime'         => 'datetime',
656
            'timestamp'        => 'datetime',
657
            'time'             => 'time',
658
            'float'            => 'float',
659
            'double'           => 'float',
660
            'double precision' => 'float',
661
            'real'             => 'float',
662
            'decimal'          => 'decimal',
663
            'numeric'          => 'decimal',
664
            'blob'             => 'blob',
665
        ];
666 1148
    }
667
668
    /**
669
     * {@inheritDoc}
670
     */
671 1603
    protected function getReservedKeywordsClass()
672
    {
673 1603
        return Keywords\SQLiteKeywords::class;
674
    }
675
676
    /**
677
     * {@inheritDoc}
678
     */
679 1357
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
680
    {
681 1357
        if (! $diff->fromTable instanceof Table) {
682
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
683
        }
684
685 1357
        $sql = [];
686 1357
        foreach ($diff->fromTable->getIndexes() as $index) {
687 1351
            if ($index->isPrimary()) {
688 1349
                continue;
689
            }
690
691 1348
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
692
        }
693
694 1357
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 1357
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
701
    {
702 1357
        if (! $diff->fromTable instanceof Table) {
703
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
704
        }
705
706 1357
        $sql       = [];
707 1357
        $tableName = $diff->getNewName();
708
709 1357
        if ($tableName === false) {
710 848
            $tableName = $diff->getName($this);
711
        }
712
713 1357
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
714 1351
            if ($index->isPrimary()) {
715 1349
                continue;
716
            }
717
718 1350
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
719
        }
720
721 1357
        return $sql;
722
    }
723
724
    /**
725
     * {@inheritDoc}
726
     */
727 1600
    protected function doModifyLimitQuery($query, $limit, $offset)
728
    {
729 1600
        if ($limit === null && $offset > 0) {
730 1553
            return $query . ' LIMIT -1 OFFSET ' . $offset;
731
        }
732
733 1599
        return parent::doModifyLimitQuery($query, $limit, $offset);
734
    }
735
736
    /**
737
     * {@inheritDoc}
738
     */
739 1432
    public function getBlobTypeDeclarationSQL(array $field)
740
    {
741 1432
        return 'BLOB';
742
    }
743
744
    /**
745
     * {@inheritDoc}
746
     */
747 111
    public function getTemporaryTableName($tableName)
748
    {
749 111
        $tableName = str_replace('.', '__', $tableName);
750
751 111
        return $tableName;
752
    }
753
754
    /**
755
     * {@inheritDoc}
756
     *
757
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
758
     * into the default database.
759
     *
760
     * This hack is implemented to be able to use SQLite as testdriver when
761
     * using schema supporting databases.
762
     */
763
    public function canEmulateSchemas()
764
    {
765
        return true;
766
    }
767
768
    /**
769
     * {@inheritDoc}
770
     */
771 1457
    public function supportsForeignKeyConstraints()
772
    {
773 1457
        return false;
774
    }
775
776
    /**
777
     * {@inheritDoc}
778
     */
779
    public function getCreatePrimaryKeySQL(Index $index, $table)
780
    {
781
        throw new DBALException('Sqlite platform does not support alter primary key.');
782
    }
783
784
    /**
785
     * {@inheritdoc}
786
     */
787 1474
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
788
    {
789 1474
        throw new DBALException('Sqlite platform does not support alter foreign key.');
790
    }
791
792
    /**
793
     * {@inheritdoc}
794
     */
795
    public function getDropForeignKeySQL($foreignKey, $table)
796
    {
797
        throw new DBALException('Sqlite platform does not support alter foreign key.');
798
    }
799
800
    /**
801
     * {@inheritDoc}
802
     */
803 1450
    public function getCreateConstraintSQL(Constraint $constraint, $table)
804
    {
805 1450
        throw new DBALException('Sqlite platform does not support alter constraint.');
806
    }
807
808
    /**
809
     * {@inheritDoc}
810
     */
811 1590
    public function getCreateTableSQL(Table $table, $createFlags = null)
812
    {
813 1590
        $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS;
814
815 1590
        return parent::getCreateTableSQL($table, $createFlags);
816
    }
817
818
    /**
819
     * @param string      $table
820
     * @param string|null $database
821
     *
822
     * @return string
823
     */
824 179
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed. ( Ignorable by Annotation )

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

824
    public function getListTableForeignKeysSQL($table, /** @scrutinizer ignore-unused */ $database = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
825
    {
826 179
        $table = str_replace('.', '__', $table);
827
828 179
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
829
    }
830
831
    /**
832
     * {@inheritDoc}
833
     */
834 1470
    public function getAlterTableSQL(TableDiff $diff)
835
    {
836 1470
        $sql = $this->getSimpleAlterTableSQL($diff);
837 1470
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
838 1456
            return $sql;
839
        }
840
841 1425
        $fromTable = $diff->fromTable;
842 1425
        if (! $fromTable instanceof Table) {
843 1313
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
844
        }
845
846 1357
        $table = clone $fromTable;
847
848 1357
        $columns        = [];
849 1357
        $oldColumnNames = [];
850 1357
        $newColumnNames = [];
851 1357
        $columnSql      = [];
852
853 1357
        foreach ($table->getColumns() as $columnName => $column) {
854 1356
            $columnName                  = strtolower($columnName);
855 1356
            $columns[$columnName]        = $column;
856 1356
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
857
        }
858
859 1357
        foreach ($diff->removedColumns as $columnName => $column) {
860 1349
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
861
                continue;
862
            }
863
864 1349
            $columnName = strtolower($columnName);
865 1349
            if (! isset($columns[$columnName])) {
866
                continue;
867
            }
868
869
            unset(
870 1349
                $columns[$columnName],
871 1349
                $oldColumnNames[$columnName],
872 1349
                $newColumnNames[$columnName]
873
            );
874
        }
875
876 1357
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
877 1247
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
878
                continue;
879
            }
880
881 1247
            $oldColumnName = strtolower($oldColumnName);
882 1247
            if (isset($columns[$oldColumnName])) {
883 1247
                unset($columns[$oldColumnName]);
884
            }
885
886 1247
            $columns[strtolower($column->getName())] = $column;
887
888 1247
            if (! isset($newColumnNames[$oldColumnName])) {
889
                continue;
890
            }
891
892 1247
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
893
        }
894
895 1357
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
896 885
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
897
                continue;
898
            }
899
900 885
            if (isset($columns[$oldColumnName])) {
901 884
                unset($columns[$oldColumnName]);
902
            }
903
904 885
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
905
906 885
            if (! isset($newColumnNames[$oldColumnName])) {
907 507
                continue;
908
            }
909
910 884
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
911
        }
912
913 1357
        foreach ($diff->addedColumns as $columnName => $column) {
914 886
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
915
                continue;
916
            }
917
918 886
            $columns[strtolower($columnName)] = $column;
919
        }
920
921 1357
        $sql      = [];
922 1357
        $tableSql = [];
923 1357
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
924 1357
            $dataTable = new Table('__temp__' . $table->getName());
925
926 1357
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
927 1357
            $newTable->addOption('alter', true);
928
929 1357
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
930
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
931 1357
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
932 1357
            $sql[] = $this->getDropTableSQL($fromTable);
933
934 1357
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
935 1357
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
936 1357
            $sql[] = $this->getDropTableSQL($dataTable);
937
938 1357
            $newName = $diff->getNewName();
939
940 1357
            if ($newName !== false) {
941 1245
                $sql[] = sprintf(
942 3
                    'ALTER TABLE %s RENAME TO %s',
943 1245
                    $newTable->getQuotedName($this),
944 1245
                    $newName->getQuotedName($this)
945
                );
946
            }
947
948 1357
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
949
        }
950
951 1357
        return array_merge($sql, $tableSql, $columnSql);
952
    }
953
954
    /**
955
     * @return string[]|false
956
     */
957 1470
    private function getSimpleAlterTableSQL(TableDiff $diff)
958
    {
959
        // Suppress changes on integer type autoincrement columns.
960 1470
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
961 905
            if (! $columnDiff->fromColumn instanceof Column ||
962 901
                ! $columnDiff->column instanceof Column ||
963 901
                ! $columnDiff->column->getAutoincrement() ||
964 905
                ! $columnDiff->column->getType() instanceof Types\IntegerType
965
            ) {
966 885
                continue;
967
            }
968
969 173
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
970 170
                unset($diff->changedColumns[$oldColumnName]);
971
972 170
                continue;
973
            }
974
975 173
            $fromColumnType = $columnDiff->fromColumn->getType();
976
977 173
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
978
                continue;
979
            }
980
981 173
            unset($diff->changedColumns[$oldColumnName]);
982
        }
983
984 1470
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
985 1465
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
986 1461
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
987 1470
                || ! empty($diff->renamedIndexes)
988
        ) {
989 1357
            return false;
990
        }
991
992 1458
        $table = new Table($diff->name);
993
994 1458
        $sql       = [];
995 1458
        $tableSql  = [];
996 1458
        $columnSql = [];
997
998 1458
        foreach ($diff->addedColumns as $column) {
999 1337
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
1000
                continue;
1001
            }
1002
1003 1337
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
1004 1337
            $type  = $field['type'];
1005
            switch (true) {
1006 1337
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
1007 1336
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
1008 1336
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
1009 1335
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
1010 1313
                    return false;
1011
            }
1012
1013 1335
            $field['name'] = $column->getQuotedName($this);
1014 1335
            if ($type instanceof Types\StringType && $field['length'] === null) {
1015 1335
                $field['length'] = 255;
1016
            }
1017
1018 1335
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
1019
        }
1020
1021 1456
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
1022 1456
            if ($diff->newName !== false) {
1023 179
                $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

1023
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1024 179
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1025
            }
1026
        }
1027
1028 1456
        return array_merge($sql, $tableSql, $columnSql);
1029
    }
1030
1031
    /**
1032
     * @return string[]
1033
     */
1034 1357
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1035
    {
1036 1357
        $columns = [];
1037
1038 1357
        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

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