Failed Conditions
Pull Request — 2.10 (#3762)
by Benjamin
09:16
created

SqlitePlatform::udfMod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

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