SqlitePlatform::supportsForeignKeyConstraints()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Platforms;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Schema\Column;
24
use Doctrine\DBAL\Schema\Constraint;
25
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
26
use Doctrine\DBAL\Schema\Identifier;
27
use Doctrine\DBAL\Schema\Index;
28
use Doctrine\DBAL\Schema\Table;
29
use Doctrine\DBAL\Schema\TableDiff;
30
use Doctrine\DBAL\TransactionIsolationLevel;
31
use Doctrine\DBAL\Types;
32
use function array_merge;
33
use function array_unique;
34
use function array_values;
35
use function implode;
36
use function is_numeric;
37
use function sprintf;
38
use function sqrt;
39
use function str_replace;
40
use function strlen;
41
use function strpos;
42
use function strtolower;
43
44
/**
45
 * The SqlitePlatform class describes the specifics and dialects of the SQLite
46
 * database platform.
47
 *
48
 * @since  2.0
49
 * @author Roman Borschel <[email protected]>
50
 * @author Benjamin Eberlei <[email protected]>
51
 * @author Martin Hasoň <[email protected]>
52
 * @todo   Rename: SQLitePlatform
53
 */
54
class SqlitePlatform extends AbstractPlatform
55
{
56
    /**
57
     * {@inheritDoc}
58
     */
59
    public function getRegexpExpression()
60
    {
61
        return 'REGEXP';
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     *
67
     * @deprecated Use application-generated UUIDs instead
68
     */
69
    public function getGuidExpression()
70
    {
71
        return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || "
72
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || "
73
            . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || "
74
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))";
75
    }
76
77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function getNowExpression($type = 'timestamp')
81
    {
82
        switch ($type) {
83
            case 'time':
84
                return 'time(\'now\')';
85
            case 'date':
86
                return 'date(\'now\')';
87
            case 'timestamp':
88
            default:
89
                return 'datetime(\'now\')';
90
        }
91
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
97
    {
98
        $trimChar = ($char != false) ? (', ' . $char) : '';
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
99
100
        switch ($pos) {
101
            case TrimMode::LEADING:
102
                $trimFn = 'LTRIM';
103
                break;
104
105
            case TrimMode::TRAILING:
106
                $trimFn = 'RTRIM';
107
                break;
108
109
            default:
110
                $trimFn = 'TRIM';
111
        }
112
113
        return $trimFn . '(' . $str . $trimChar . ')';
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     *
119
     * SQLite only supports the 2 parameter variant of this function
120
     */
121 View Code Duplication
    public function getSubstringExpression($value, $position, $length = null)
122
    {
123
        if ($length !== null) {
124
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
125
        }
126
127
        return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133 View Code Duplication
    public function getLocateExpression($str, $substr, $startPos = false)
134
    {
135
        if ($startPos == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
136
            return 'LOCATE('.$str.', '.$substr.')';
137
        }
138
139
        return 'LOCATE('.$str.', '.$substr.', '.$startPos.')';
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
146
    {
147
        switch ($unit) {
148
            case DateIntervalUnit::SECOND:
149
            case DateIntervalUnit::MINUTE:
150
            case DateIntervalUnit::HOUR:
151
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
152
153
            default:
154 View Code Duplication
                switch ($unit) {
155
                    case DateIntervalUnit::WEEK:
156
                        $interval *= 7;
157
                        $unit      = DateIntervalUnit::DAY;
158
                        break;
159
160
                    case DateIntervalUnit::QUARTER:
161
                        $interval *= 3;
162
                        $unit      = DateIntervalUnit::MONTH;
163
                        break;
164
                }
165
166
                if (! is_numeric($interval)) {
167
                    $interval = "' || " . $interval . " || '";
168
                }
169
170
                return "DATE(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
171
        }
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177
    public function getDateDiffExpression($date1, $date2)
178
    {
179
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185 View Code Duplication
    protected function _getTransactionIsolationLevelSQL($level)
186
    {
187
        switch ($level) {
188
            case TransactionIsolationLevel::READ_UNCOMMITTED:
189
                return 0;
190
            case TransactionIsolationLevel::READ_COMMITTED:
191
            case TransactionIsolationLevel::REPEATABLE_READ:
192
            case TransactionIsolationLevel::SERIALIZABLE:
193
                return 1;
194
            default:
195
                return parent::_getTransactionIsolationLevelSQL($level);
196
        }
197
    }
198
199
    /**
200
     * {@inheritDoc}
201
     */
202
    public function getSetTransactionIsolationSQL($level)
203
    {
204
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
205
    }
206
207
    /**
208
     * {@inheritDoc}
209
     */
210
    public function prefersIdentityColumns()
211
    {
212
        return true;
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218
    public function getBooleanTypeDeclarationSQL(array $field)
219
    {
220
        return 'BOOLEAN';
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226
    public function getIntegerTypeDeclarationSQL(array $field)
227
    {
228
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 View Code Duplication
    public function getBigIntTypeDeclarationSQL(array $field)
235
    {
236
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
237
        if ( ! empty($field['autoincrement'])) {
238
            return $this->getIntegerTypeDeclarationSQL($field);
239
        }
240
241
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 View Code Duplication
    public function getTinyIntTypeDeclarationSql(array $field)
248
    {
249
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
250
        if ( ! empty($field['autoincrement'])) {
251
            return $this->getIntegerTypeDeclarationSQL($field);
252
        }
253
254
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 View Code Duplication
    public function getSmallIntTypeDeclarationSQL(array $field)
261
    {
262
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
263
        if ( ! empty($field['autoincrement'])) {
264
            return $this->getIntegerTypeDeclarationSQL($field);
265
        }
266
267
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
268
    }
269
270
    /**
271
     * {@inheritDoc}
272
     */
273 View Code Duplication
    public function getMediumIntTypeDeclarationSql(array $field)
274
    {
275
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
276
        if ( ! empty($field['autoincrement'])) {
277
            return $this->getIntegerTypeDeclarationSQL($field);
278
        }
279
280
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
287
    {
288
        return 'DATETIME';
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
295
    {
296
        return 'DATE';
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
303
    {
304
        return 'TIME';
305
    }
306
307
    /**
308
     * {@inheritDoc}
309
     */
310
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
311
    {
312
        // sqlite autoincrement is only possible for the primary key
313
        if ( ! empty($columnDef['autoincrement'])) {
314
            return ' PRIMARY KEY AUTOINCREMENT';
315
        }
316
317
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
318
    }
319
320
    /**
321
     * {@inheritDoc}
322
     */
323
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
324
    {
325
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
326
            $foreignKey->getQuotedLocalColumns($this),
327
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
328
            $foreignKey->getQuotedForeignColumns($this),
329
            $foreignKey->getName(),
330
            $foreignKey->getOptions()
331
        ));
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
338
    {
339
        $name = str_replace('.', '__', $name);
340
        $queryFields = $this->getColumnDeclarationListSQL($columns);
341
342
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
343
            foreach ($options['uniqueConstraints'] as $name => $definition) {
344
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
345
            }
346
        }
347
348
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
349
350
        if (isset($options['foreignKeys'])) {
351
            foreach ($options['foreignKeys'] as $foreignKey) {
352
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
353
            }
354
        }
355
356
        $query = ['CREATE TABLE ' . $name . ' (' . $queryFields . ')'];
357
358
        if (isset($options['alter']) && true === $options['alter']) {
359
            return $query;
360
        }
361
362 View Code Duplication
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
363
            foreach ($options['indexes'] as $indexDef) {
364
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
365
            }
366
        }
367
368 View Code Duplication
        if (isset($options['unique']) && ! empty($options['unique'])) {
369
            foreach ($options['unique'] as $indexDef) {
370
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
371
            }
372
        }
373
374
        return $query;
375
    }
376
377
    /**
378
     * Generate a PRIMARY KEY definition if no autoincrement value is used
379
     *
380
     * @param string[] $columns
381
     * @param mixed[]  $options
382
     */
383
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
384
    {
385
        if (empty($options['primary'])) {
386
            return '';
387
        }
388
389
        $keyColumns = array_unique(array_values($options['primary']));
390
391
        foreach ($keyColumns as $keyColumn) {
392
            if (isset($columns[$keyColumn]['autoincrement']) && ! empty($columns[$keyColumn]['autoincrement'])) {
393
                return '';
394
            }
395
        }
396
397
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
398
    }
399
400
    /**
401
     * {@inheritDoc}
402
     */
403
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
404
    {
405
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
406
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
413
    {
414
        return 'BLOB';
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420
    public function getBinaryMaxLength()
421
    {
422
        return 0;
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428
    public function getBinaryDefaultLength()
429
    {
430
        return 0;
431
    }
432
433
    /**
434
     * {@inheritDoc}
435
     */
436
    public function getClobTypeDeclarationSQL(array $field)
437
    {
438
        return 'CLOB';
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444
    public function getListTableConstraintsSQL($table)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
445
    {
446
        $table = str_replace('.', '__', $table);
447
        $table = $this->quoteStringLiteral($table);
448
449
        return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = $table AND sql NOT NULL ORDER BY name";
450
    }
451
452
    /**
453
     * {@inheritDoc}
454
     */
455 View Code Duplication
    public function getListTableColumnsSQL($table, $currentDatabase = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
456
    {
457
        $table = str_replace('.', '__', $table);
458
        $table = $this->quoteStringLiteral($table);
459
460
        return "PRAGMA table_info($table)";
461
    }
462
463
    /**
464
     * {@inheritDoc}
465
     */
466 View Code Duplication
    public function getListTableIndexesSQL($table, $currentDatabase = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
467
    {
468
        $table = str_replace('.', '__', $table);
469
        $table = $this->quoteStringLiteral($table);
470
471
        return "PRAGMA index_list($table)";
472
    }
473
474
    /**
475
     * {@inheritDoc}
476
     */
477
    public function getListTablesSQL()
478
    {
479
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
480
             . "UNION ALL SELECT name FROM sqlite_temp_master "
481
             . "WHERE type = 'table' ORDER BY name";
482
    }
483
484
    /**
485
     * {@inheritDoc}
486
     */
487
    public function getListViewsSQL($database)
488
    {
489
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
490
    }
491
492
    /**
493
     * {@inheritDoc}
494
     */
495
    public function getCreateViewSQL($name, $sql)
496
    {
497
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
498
    }
499
500
    /**
501
     * {@inheritDoc}
502
     */
503
    public function getDropViewSQL($name)
504
    {
505
        return 'DROP VIEW '. $name;
506
    }
507
508
    /**
509
     * {@inheritDoc}
510
     */
511
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
512
    {
513
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
514
515
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
516
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
517
518
        return $query;
519
    }
520
521
    /**
522
     * {@inheritDoc}
523
     */
524
    public function supportsIdentityColumns()
525
    {
526
        return true;
527
    }
528
529
    /**
530
     * {@inheritDoc}
531
     */
532
    public function supportsColumnCollation()
533
    {
534
        return true;
535
    }
536
537
    /**
538
     * {@inheritDoc}
539
     */
540
    public function supportsInlineColumnComments()
541
    {
542
        return true;
543
    }
544
545
    /**
546
     * {@inheritDoc}
547
     */
548
    public function getName()
549
    {
550
        return 'sqlite';
551
    }
552
553
    /**
554
     * {@inheritDoc}
555
     */
556
    public function getTruncateTableSQL($tableName, $cascade = false)
557
    {
558
        $tableIdentifier = new Identifier($tableName);
559
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
560
561
        return 'DELETE FROM ' . $tableName;
562
    }
563
564
    /**
565
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
566
     *
567
     * @param int|float $value
568
     *
569
     * @return float
570
     */
571
    public static function udfSqrt($value)
572
    {
573
        return sqrt($value);
574
    }
575
576
    /**
577
     * User-defined function for Sqlite that implements MOD(a, b).
578
     *
579
     * @param int $a
580
     * @param int $b
581
     *
582
     * @return int
583
     */
584
    public static function udfMod($a, $b)
585
    {
586
        return ($a % $b);
587
    }
588
589
    /**
590
     * @param string $str
591
     * @param string $substr
592
     * @param int    $offset
593
     *
594
     * @return int
595
     */
596
    public static function udfLocate($str, $substr, $offset = 0)
597
    {
598
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
599
        // So we have to make them compatible if an offset is given.
600
        if ($offset > 0) {
601
            $offset -= 1;
602
        }
603
604
        $pos = strpos($str, $substr, $offset);
605
606
        if ($pos !== false) {
607
            return $pos + 1;
608
        }
609
610
        return 0;
611
    }
612
613
    /**
614
     * {@inheritDoc}
615
     */
616
    public function getForUpdateSql()
617
    {
618
        return '';
619
    }
620
621
    /**
622
     * {@inheritDoc}
623
     */
624
    public function getInlineColumnCommentSQL($comment)
625
    {
626
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
627
    }
628
629
    /**
630
     * {@inheritDoc}
631
     */
632
    protected function initializeDoctrineTypeMappings()
633
    {
634
        $this->doctrineTypeMapping = [
635
            'boolean'          => 'boolean',
636
            'tinyint'          => 'boolean',
637
            'smallint'         => 'smallint',
638
            'mediumint'        => 'integer',
639
            'int'              => 'integer',
640
            'integer'          => 'integer',
641
            'serial'           => 'integer',
642
            'bigint'           => 'bigint',
643
            'bigserial'        => 'bigint',
644
            'clob'             => 'text',
645
            'tinytext'         => 'text',
646
            'mediumtext'       => 'text',
647
            'longtext'         => 'text',
648
            'text'             => 'text',
649
            'varchar'          => 'string',
650
            'longvarchar'      => 'string',
651
            'varchar2'         => 'string',
652
            'nvarchar'         => 'string',
653
            'image'            => 'string',
654
            'ntext'            => 'string',
655
            'char'             => 'string',
656
            'date'             => 'date',
657
            'datetime'         => 'datetime',
658
            'timestamp'        => 'datetime',
659
            'time'             => 'time',
660
            'float'            => 'float',
661
            'double'           => 'float',
662
            'double precision' => 'float',
663
            'real'             => 'float',
664
            'decimal'          => 'decimal',
665
            'numeric'          => 'decimal',
666
            'blob'             => 'blob',
667
        ];
668
    }
669
670
    /**
671
     * {@inheritDoc}
672
     */
673
    protected function getReservedKeywordsClass()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
674
    {
675
        return Keywords\SQLiteKeywords::class;
676
    }
677
678
    /**
679
     * {@inheritDoc}
680
     */
681
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
682
    {
683
        if ( ! $diff->fromTable instanceof Table) {
684
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
685
        }
686
687
        $sql = [];
688
        foreach ($diff->fromTable->getIndexes() as $index) {
689
            if ( ! $index->isPrimary()) {
690
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
691
            }
692
        }
693
694
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
701
    {
702
        if ( ! $diff->fromTable instanceof Table) {
703
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
704
        }
705
706
        $sql = [];
707
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
708
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
709
            if ($index->isPrimary()) {
710
                continue;
711
            }
712
713
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
714
        }
715
716
        return $sql;
717
    }
718
719
    /**
720
     * {@inheritDoc}
721
     */
722
    protected function doModifyLimitQuery($query, $limit, $offset)
723
    {
724
        if ($limit === null && $offset > 0) {
725
            return $query . ' LIMIT -1 OFFSET ' . $offset;
726
        }
727
728
        return parent::doModifyLimitQuery($query, $limit, $offset);
729
    }
730
731
    /**
732
     * {@inheritDoc}
733
     */
734
    public function getBlobTypeDeclarationSQL(array $field)
735
    {
736
        return 'BLOB';
737
    }
738
739
    /**
740
     * {@inheritDoc}
741
     */
742
    public function getTemporaryTableName($tableName)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
743
    {
744
        $tableName = str_replace('.', '__', $tableName);
745
746
        return $tableName;
747
    }
748
749
    /**
750
     * {@inheritDoc}
751
     *
752
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
753
     * into the default database.
754
     *
755
     * This hack is implemented to be able to use SQLite as testdriver when
756
     * using schema supporting databases.
757
     */
758
    public function canEmulateSchemas()
759
    {
760
        return true;
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766
    public function supportsForeignKeyConstraints()
767
    {
768
        return false;
769
    }
770
771
    /**
772
     * {@inheritDoc}
773
     */
774
    public function getCreatePrimaryKeySQL(Index $index, $table)
775
    {
776
        throw new DBALException('Sqlite platform does not support alter primary key.');
777
    }
778
779
    /**
780
     * {@inheritdoc}
781
     */
782
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
783
    {
784
        throw new DBALException('Sqlite platform does not support alter foreign key.');
785
    }
786
787
    /**
788
     * {@inheritdoc}
789
     */
790
    public function getDropForeignKeySQL($foreignKey, $table)
791
    {
792
        throw new DBALException('Sqlite platform does not support alter foreign key.');
793
    }
794
795
    /**
796
     * {@inheritDoc}
797
     */
798
    public function getCreateConstraintSQL(Constraint $constraint, $table)
799
    {
800
        throw new DBALException('Sqlite platform does not support alter constraint.');
801
    }
802
803
    /**
804
     * {@inheritDoc}
805
     */
806
    public function getCreateTableSQL(Table $table, $createFlags = null)
807
    {
808
        $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS;
809
810
        return parent::getCreateTableSQL($table, $createFlags);
811
    }
812
813
    /**
814
     * {@inheritDoc}
815
     */
816 View Code Duplication
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
Unused Code introduced by
The parameter $database is not used and could be removed.

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

Loading history...
817
    {
818
        $table = str_replace('.', '__', $table);
819
        $table = $this->quoteStringLiteral($table);
820
821
        return "PRAGMA foreign_key_list($table)";
822
    }
823
824
    /**
825
     * {@inheritDoc}
826
     */
827
    public function getAlterTableSQL(TableDiff $diff)
828
    {
829
        $sql = $this->getSimpleAlterTableSQL($diff);
830
        if (false !== $sql) {
831
            return $sql;
832
        }
833
834
        $fromTable = $diff->fromTable;
835
        if ( ! $fromTable instanceof Table) {
836
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
837
        }
838
839
        $table = clone $fromTable;
840
841
        $columns = [];
842
        $oldColumnNames = [];
843
        $newColumnNames = [];
844
        $columnSql = [];
845
846
        foreach ($table->getColumns() as $columnName => $column) {
847
            $columnName = strtolower($columnName);
848
            $columns[$columnName] = $column;
849
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
850
        }
851
852
        foreach ($diff->removedColumns as $columnName => $column) {
853
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
854
                continue;
855
            }
856
857
            $columnName = strtolower($columnName);
858
            if (isset($columns[$columnName])) {
859
                unset(
860
                    $columns[$columnName],
861
                    $oldColumnNames[$columnName],
862
                    $newColumnNames[$columnName]
863
                );
864
            }
865
        }
866
867
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
868
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
869
                continue;
870
            }
871
872
            $oldColumnName = strtolower($oldColumnName);
873
            if (isset($columns[$oldColumnName])) {
874
                unset($columns[$oldColumnName]);
875
            }
876
877
            $columns[strtolower($column->getName())] = $column;
878
879
            if (isset($newColumnNames[$oldColumnName])) {
880
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
881
            }
882
        }
883
884
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
885
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
886
                continue;
887
            }
888
889
            if (isset($columns[$oldColumnName])) {
890
                unset($columns[$oldColumnName]);
891
            }
892
893
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
894
895
            if (isset($newColumnNames[$oldColumnName])) {
896
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
897
            }
898
        }
899
900
        foreach ($diff->addedColumns as $columnName => $column) {
901
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
902
                continue;
903
            }
904
905
            $columns[strtolower($columnName)] = $column;
906
        }
907
908
        $sql = [];
909
        $tableSql = [];
910
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
911
            $dataTable = new Table('__temp__'.$table->getName());
912
913
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
914
            $newTable->addOption('alter', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
915
916
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
917
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
918
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
919
            $sql[] = $this->getDropTableSQL($fromTable);
920
921
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
922
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
923
            $sql[] = $this->getDropTableSQL($dataTable);
924
925
            if ($diff->newName && $diff->newName != $diff->name) {
926
                $renamedTable = $diff->getNewName();
927
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
928
            }
929
930
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
931
        }
932
933
        return array_merge($sql, $tableSql, $columnSql);
934
    }
935
936
    /**
937
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
938
     *
939
     * @return string[]|false
0 ignored issues
show
Documentation introduced by
Should the return type not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
940
     */
941
    private function getSimpleAlterTableSQL(TableDiff $diff)
942
    {
943
        // Suppress changes on integer type autoincrement columns.
944
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
945
            if ( ! $columnDiff->fromColumn instanceof Column ||
946
                ! $columnDiff->column instanceof Column ||
947
                ! $columnDiff->column->getAutoincrement() ||
948
                ! $columnDiff->column->getType() instanceof Types\IntegerType
949
            ) {
950
                continue;
951
            }
952
953
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
954
                unset($diff->changedColumns[$oldColumnName]);
955
956
                continue;
957
            }
958
959
            $fromColumnType = $columnDiff->fromColumn->getType();
960
961
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
962
                unset($diff->changedColumns[$oldColumnName]);
963
            }
964
        }
965
966
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
967
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
968
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
969
                || ! empty($diff->renamedIndexes)
970
        ) {
971
            return false;
972
        }
973
974
        $table = new Table($diff->name);
975
976
        $sql = [];
977
        $tableSql = [];
978
        $columnSql = [];
979
980
        foreach ($diff->addedColumns as $column) {
981
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
982
                continue;
983
            }
984
985
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
986
            $type = $field['type'];
987
            switch (true) {
988
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
989
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
990
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
991
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
992
                    return false;
993
            }
994
995
            $field['name'] = $column->getQuotedName($this);
996
            if ($type instanceof Types\StringType && $field['length'] === null) {
997
                $field['length'] = 255;
998
            }
999
1000
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
1001
        }
1002
1003
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
1004
            if ($diff->newName !== false) {
1005
                $newTable = new Identifier($diff->newName);
0 ignored issues
show
Bug introduced by
It seems like $diff->newName can also be of type boolean; however, Doctrine\DBAL\Schema\Identifier::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1006
                $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
1007
            }
1008
        }
1009
1010
        return array_merge($sql, $tableSql, $columnSql);
1011
    }
1012
1013
    /**
1014
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1015
     *
1016
     * @return array
1017
     */
1018
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1019
    {
1020
        $columns = [];
1021
1022
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
1023
            $columns[strtolower($columnName)] = $column->getName();
1024
        }
1025
1026
        foreach ($diff->removedColumns as $columnName => $column) {
1027
            $columnName = strtolower($columnName);
1028
            if (isset($columns[$columnName])) {
1029
                unset($columns[$columnName]);
1030
            }
1031
        }
1032
1033 View Code Duplication
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1034
            $columnName = $column->getName();
1035
            $columns[strtolower($oldColumnName)] = $columnName;
1036
            $columns[strtolower($columnName)] = $columnName;
1037
        }
1038
1039 View Code Duplication
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1040
            $columnName = $columnDiff->column->getName();
1041
            $columns[strtolower($oldColumnName)] = $columnName;
1042
            $columns[strtolower($columnName)] = $columnName;
1043
        }
1044
1045
        foreach ($diff->addedColumns as $columnName => $column) {
1046
            $columns[strtolower($columnName)] = $columnName;
1047
        }
1048
1049
        return $columns;
1050
    }
1051
1052
    /**
1053
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1054
     *
1055
     * @return \Doctrine\DBAL\Schema\Index[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array<*,Index>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1056
     */
1057
    private function getIndexesInAlteredTable(TableDiff $diff)
1058
    {
1059
        $indexes = $diff->fromTable->getIndexes();
1060
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1061
1062
        foreach ($indexes as $key => $index) {
1063
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1064
                if (strtolower($key) === strtolower($oldIndexName)) {
1065
                    unset($indexes[$key]);
1066
                }
1067
            }
1068
1069
            $changed = false;
1070
            $indexColumns = [];
1071 View Code Duplication
            foreach ($index->getColumns() as $columnName) {
1072
                $normalizedColumnName = strtolower($columnName);
1073
                if ( ! isset($columnNames[$normalizedColumnName])) {
1074
                    unset($indexes[$key]);
1075
                    continue 2;
1076
                } else {
1077
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1078
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1079
                        $changed = true;
1080
                    }
1081
                }
1082
            }
1083
1084
            if ($changed) {
1085
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1086
            }
1087
        }
1088
1089
        foreach ($diff->removedIndexes as $index) {
1090
            $indexName = strtolower($index->getName());
1091
            if (strlen($indexName) && isset($indexes[$indexName])) {
1092
                unset($indexes[$indexName]);
1093
            }
1094
        }
1095
1096
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1097
            $indexName = strtolower($index->getName());
1098
            if (strlen($indexName)) {
1099
                $indexes[$indexName] = $index;
1100
            } else {
1101
                $indexes[] = $index;
1102
            }
1103
        }
1104
1105
        return $indexes;
1106
    }
1107
1108
    /**
1109
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1110
     *
1111
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<*,ForeignKeyConstraint>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1112
     */
1113
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1114
    {
1115
        $foreignKeys = $diff->fromTable->getForeignKeys();
1116
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1117
1118
        foreach ($foreignKeys as $key => $constraint) {
1119
            $changed = false;
1120
            $localColumns = [];
1121 View Code Duplication
            foreach ($constraint->getLocalColumns() as $columnName) {
1122
                $normalizedColumnName = strtolower($columnName);
1123
                if ( ! isset($columnNames[$normalizedColumnName])) {
1124
                    unset($foreignKeys[$key]);
1125
                    continue 2;
1126
                } else {
1127
                    $localColumns[] = $columnNames[$normalizedColumnName];
1128
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1129
                        $changed = true;
1130
                    }
1131
                }
1132
            }
1133
1134
            if ($changed) {
1135
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1136
            }
1137
        }
1138
1139
        foreach ($diff->removedForeignKeys as $constraint) {
1140
            $constraintName = strtolower($constraint->getName());
1141
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
1142
                unset($foreignKeys[$constraintName]);
1143
            }
1144
        }
1145
1146
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1147
            $constraintName = strtolower($constraint->getName());
1148
            if (strlen($constraintName)) {
1149
                $foreignKeys[$constraintName] = $constraint;
1150
            } else {
1151
                $foreignKeys[] = $constraint;
1152
            }
1153
        }
1154
1155
        return $foreignKeys;
1156
    }
1157
1158
    /**
1159
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1160
     *
1161
     * @return array
1162
     */
1163
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1164
    {
1165
        $primaryIndex = [];
1166
1167
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1168
            if ($index->isPrimary()) {
1169
                $primaryIndex = [$index->getName() => $index];
1170
            }
1171
        }
1172
1173
        return $primaryIndex;
1174
    }
1175
}
1176