Completed
Pull Request — 2.10.x (#3936)
by Asmir
65:42
created

SqlitePlatform::supportsNamedPrimaryConstraints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

1031
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1032
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1033 1369
            }
1034 1353
        }
1035 1353
1036
        return array_merge($sql, $tableSql, $columnSql);
1037
    }
1038
1039 1353
    /**
1040
     * @return string[]
1041
     */
1042 1369
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1043 1252
    {
1044 1252
        $columns = [];
1045 1252
1046
        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

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