Completed
Push — develop ( de019a...a640b8 )
by Marco
19s queued 13s
created

SqlitePlatform::getTrimExpression()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

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

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

1006
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1007 1478
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1008
            }
1009
        }
1010 1479
1011 1471
        return array_merge($sql, $tableSql, $columnSql);
1012 1471
    }
1013
1014
    /**
1015
     * @return string[]
1016 1471
     */
1017
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1018
    {
1019 1479
        $columns = [];
1020 1373
1021 1373
        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

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