Completed
Push — master ( 3e2bee...8fa2d7 )
by Sergei
15:16 queued 07:05
created

SqlitePlatform::getListTableConstraintsSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 19
    public function getRegexpExpression()
60
    {
61 19
        return 'REGEXP';
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     *
67
     * @deprecated Use application-generated UUIDs instead
68
     */
69 2
    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 2
            . "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 36
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
97
    {
98 36
        $trimChar = ($char != false) ? (', ' . $char) : '';
99
100
        switch ($pos) {
101 36
            case TrimMode::LEADING:
102 9
                $trimFn = 'LTRIM';
103 9
                break;
104
105 27
            case TrimMode::TRAILING:
106 9
                $trimFn = 'RTRIM';
107 9
                break;
108
109
            default:
110 18
                $trimFn = 'TRIM';
111
        }
112
113 36
        return $trimFn . '(' . $str . $trimChar . ')';
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     *
119
     * SQLite only supports the 2 parameter variant of this function
120
     */
121 19
    public function getSubstringExpression($value, $position, $length = null)
122
    {
123 19
        if ($length !== null) {
124 19
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
125
        }
126
127 19
        return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133 1
    public function getLocateExpression($str, $substr, $startPos = false)
134
    {
135 1
        if ($startPos == false) {
136 1
            return 'LOCATE('.$str.', '.$substr.')';
137
        }
138
139 1
        return 'LOCATE('.$str.', '.$substr.', '.$startPos.')';
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 40
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
146
    {
147 2
        switch ($unit) {
148 38
            case DateIntervalUnit::SECOND:
149 38
            case DateIntervalUnit::MINUTE:
150 38
            case DateIntervalUnit::HOUR:
151 1
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
152
153
            default:
154 2
                switch ($unit) {
155 38
                    case DateIntervalUnit::WEEK:
156 1
                        $interval *= 7;
157 1
                        $unit      = DateIntervalUnit::DAY;
158 1
                        break;
159
160 38
                    case DateIntervalUnit::QUARTER:
161 1
                        $interval *= 3;
162 1
                        $unit      = DateIntervalUnit::MONTH;
163 1
                        break;
164
                }
165
166 40
                if (! is_numeric($interval)) {
167 20
                    $interval = "' || " . $interval . " || '";
168
                }
169
170 40
                return "DATE(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
171
        }
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177 3
    public function getDateDiffExpression($date1, $date2)
178
    {
179 3
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185 19
    protected function _getTransactionIsolationLevelSQL($level)
186
    {
187 1
        switch ($level) {
188 18
            case TransactionIsolationLevel::READ_UNCOMMITTED:
189 19
                return 0;
190 18
            case TransactionIsolationLevel::READ_COMMITTED:
191 18
            case TransactionIsolationLevel::REPEATABLE_READ:
192 18
            case TransactionIsolationLevel::SERIALIZABLE:
193 19
                return 1;
194
            default:
195
                return parent::_getTransactionIsolationLevelSQL($level);
196
        }
197
    }
198
199
    /**
200
     * {@inheritDoc}
201
     */
202 19
    public function getSetTransactionIsolationSQL($level)
203
    {
204 19
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
205
    }
206
207
    /**
208
     * {@inheritDoc}
209
     */
210 20
    public function prefersIdentityColumns()
211
    {
212 20
        return true;
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 34
    public function getBooleanTypeDeclarationSQL(array $field)
219
    {
220 34
        return 'BOOLEAN';
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226 563
    public function getIntegerTypeDeclarationSQL(array $field)
227
    {
228 563
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 36
    public function getBigIntTypeDeclarationSQL(array $field)
235
    {
236
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
237 36
        if ( ! empty($field['autoincrement'])) {
238 21
            return $this->getIntegerTypeDeclarationSQL($field);
239
        }
240
241 34
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 38
    public function getTinyIntTypeDeclarationSql(array $field)
248
    {
249
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
250 38
        if ( ! empty($field['autoincrement'])) {
251 38
            return $this->getIntegerTypeDeclarationSQL($field);
252
        }
253
254 19
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 22
    public function getSmallIntTypeDeclarationSQL(array $field)
261
    {
262
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
263 22
        if ( ! empty($field['autoincrement'])) {
264 21
            return $this->getIntegerTypeDeclarationSQL($field);
265
        }
266
267 20
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
268
    }
269
270
    /**
271
     * {@inheritDoc}
272
     */
273 19
    public function getMediumIntTypeDeclarationSql(array $field)
274
    {
275
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
276 19
        if ( ! empty($field['autoincrement'])) {
277 19
            return $this->getIntegerTypeDeclarationSQL($field);
278
        }
279
280 19
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286 22
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
287
    {
288 22
        return 'DATETIME';
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 19
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
295
    {
296 19
        return 'DATE';
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302 18
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
303
    {
304 18
        return 'TIME';
305
    }
306
307
    /**
308
     * {@inheritDoc}
309
     */
310 563
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
311
    {
312
        // sqlite autoincrement is only possible for the primary key
313 563
        if ( ! empty($columnDef['autoincrement'])) {
314 202
            return ' PRIMARY KEY AUTOINCREMENT';
315
        }
316
317 495
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
318
    }
319
320
    /**
321
     * {@inheritDoc}
322
     */
323 96
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
324
    {
325 96
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
326 96
            $foreignKey->getQuotedLocalColumns($this),
327 96
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
328 96
            $foreignKey->getQuotedForeignColumns($this),
329 96
            $foreignKey->getName(),
330 96
            $foreignKey->getOptions()
331
        ));
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337 627
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
338
    {
339 627
        $name = str_replace('.', '__', $name);
340 627
        $queryFields = $this->getColumnDeclarationListSQL($columns);
341
342 627
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
343
            foreach ($options['uniqueConstraints'] as $name => $definition) {
0 ignored issues
show
introduced by
$name is overwriting one of the parameters of this function.
Loading history...
344
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
345
            }
346
        }
347
348 627
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
349
350 627
        if (isset($options['foreignKeys'])) {
351 608
            foreach ($options['foreignKeys'] as $foreignKey) {
352 96
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
353
            }
354
        }
355
356 627
        $query = ['CREATE TABLE ' . $name . ' (' . $queryFields . ')'];
357
358 627
        if (isset($options['alter']) && true === $options['alter']) {
359 241
            return $query;
360
        }
361
362 399
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
363 80
            foreach ($options['indexes'] as $indexDef) {
364 80
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
365
            }
366
        }
367
368 399
        if (isset($options['unique']) && ! empty($options['unique'])) {
369
            foreach ($options['unique'] as $indexDef) {
370
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
371
            }
372
        }
373
374 399
        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 627
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
384
    {
385 627
        if (empty($options['primary'])) {
386 310
            return '';
387
        }
388
389 317
        $keyColumns = array_unique(array_values($options['primary']));
390
391 317
        foreach ($keyColumns as $keyColumn) {
392 317
            if (isset($columns[$keyColumn]['autoincrement']) && ! empty($columns[$keyColumn]['autoincrement'])) {
393 317
                return '';
394
            }
395
        }
396
397 231
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
398
    }
399
400
    /**
401
     * {@inheritDoc}
402
     */
403 370
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
404
    {
405 370
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
406 370
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412 20
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
413
    {
414 20
        return 'BLOB';
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420 40
    public function getBinaryMaxLength()
421
    {
422 40
        return 0;
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428 39
    public function getBinaryDefaultLength()
429
    {
430 39
        return 0;
431
    }
432
433
    /**
434
     * {@inheritDoc}
435
     */
436 66
    public function getClobTypeDeclarationSQL(array $field)
437
    {
438 66
        return 'CLOB';
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444 19
    public function getListTableConstraintsSQL($table)
445
    {
446 19
        $table = str_replace('.', '__', $table);
447 19
        $table = $this->quoteStringLiteral($table);
448
449 19
        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 95
    public function getListTableColumnsSQL($table, $currentDatabase = null)
456
    {
457 95
        $table = str_replace('.', '__', $table);
458 95
        $table = $this->quoteStringLiteral($table);
459
460 95
        return "PRAGMA table_info($table)";
461
    }
462
463
    /**
464
     * {@inheritDoc}
465
     */
466 88
    public function getListTableIndexesSQL($table, $currentDatabase = null)
467
    {
468 88
        $table = str_replace('.', '__', $table);
469 88
        $table = $this->quoteStringLiteral($table);
470
471 88
        return "PRAGMA index_list($table)";
472
    }
473
474
    /**
475
     * {@inheritDoc}
476
     */
477 120
    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 120
             . "WHERE type = 'table' ORDER BY name";
482
    }
483
484
    /**
485
     * {@inheritDoc}
486
     */
487 1
    public function getListViewsSQL($database)
488
    {
489 1
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
490
    }
491
492
    /**
493
     * {@inheritDoc}
494
     */
495 1
    public function getCreateViewSQL($name, $sql)
496
    {
497 1
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
498
    }
499
500
    /**
501
     * {@inheritDoc}
502
     */
503 1
    public function getDropViewSQL($name)
504
    {
505 1
        return 'DROP VIEW '. $name;
506
    }
507
508
    /**
509
     * {@inheritDoc}
510
     */
511 96
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
512
    {
513 96
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
514
515 96
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
516 96
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
517
518 96
        return $query;
519
    }
520
521
    /**
522
     * {@inheritDoc}
523
     */
524 3
    public function supportsIdentityColumns()
525
    {
526 3
        return true;
527
    }
528
529
    /**
530
     * {@inheritDoc}
531
     */
532 58
    public function supportsColumnCollation()
533
    {
534 58
        return true;
535
    }
536
537
    /**
538
     * {@inheritDoc}
539
     */
540 703
    public function supportsInlineColumnComments()
541
    {
542 703
        return true;
543
    }
544
545
    /**
546
     * {@inheritDoc}
547
     */
548 126
    public function getName()
549
    {
550 126
        return 'sqlite';
551
    }
552
553
    /**
554
     * {@inheritDoc}
555
     */
556 27
    public function getTruncateTableSQL($tableName, $cascade = false)
557
    {
558 27
        $tableIdentifier = new Identifier($tableName);
559 27
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
560
561 27
        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 1
    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 1
        if ($offset > 0) {
601 1
            $offset -= 1;
602
        }
603
604 1
        $pos = strpos($str, $substr, $offset);
605
606 1
        if ($pos !== false) {
607 1
            return $pos + 1;
608
        }
609
610 1
        return 0;
611
    }
612
613
    /**
614
     * {@inheritDoc}
615
     */
616
    public function getForUpdateSql()
617
    {
618
        return '';
619
    }
620
621
    /**
622
     * {@inheritDoc}
623
     */
624 146
    public function getInlineColumnCommentSQL($comment)
625
    {
626 146
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
627
    }
628
629
    /**
630
     * {@inheritDoc}
631
     */
632 115
    protected function initializeDoctrineTypeMappings()
633
    {
634 115
        $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 115
    }
669
670
    /**
671
     * {@inheritDoc}
672
     */
673 770
    protected function getReservedKeywordsClass()
674
    {
675 770
        return Keywords\SQLiteKeywords::class;
676
    }
677
678
    /**
679
     * {@inheritDoc}
680
     */
681 241
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
682
    {
683 241
        if ( ! $diff->fromTable instanceof Table) {
0 ignored issues
show
introduced by
$diff->fromTable is always a sub-type of Doctrine\DBAL\Schema\Table. If $diff->fromTable can have other possible types, add them to lib/Doctrine/DBAL/Schema/TableDiff.php:121.
Loading history...
684
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
685
        }
686
687 241
        $sql = [];
688 241
        foreach ($diff->fromTable->getIndexes() as $index) {
689 117
            if ( ! $index->isPrimary()) {
690 117
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
691
            }
692
        }
693
694 241
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 241
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
701
    {
702 241
        if ( ! $diff->fromTable instanceof Table) {
0 ignored issues
show
introduced by
$diff->fromTable is always a sub-type of Doctrine\DBAL\Schema\Table. If $diff->fromTable can have other possible types, add them to lib/Doctrine/DBAL/Schema/TableDiff.php:121.
Loading history...
703
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
704
        }
705
706 241
        $sql = [];
707 241
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
708 241
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
709 117
            if ($index->isPrimary()) {
710 79
                continue;
711
            }
712
713 96
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
714
        }
715
716 241
        return $sql;
717
    }
718
719
    /**
720
     * {@inheritDoc}
721
     */
722 84
    protected function doModifyLimitQuery($query, $limit, $offset)
723
    {
724 84
        if ($limit === null && $offset > 0) {
725 20
            return $query . ' LIMIT -1 OFFSET ' . $offset;
726
        }
727
728 65
        return parent::doModifyLimitQuery($query, $limit, $offset);
729
    }
730
731
    /**
732
     * {@inheritDoc}
733
     */
734 25
    public function getBlobTypeDeclarationSQL(array $field)
735
    {
736 25
        return 'BLOB';
737
    }
738
739
    /**
740
     * {@inheritDoc}
741
     */
742 2
    public function getTemporaryTableName($tableName)
743
    {
744 2
        $tableName = str_replace('.', '__', $tableName);
745
746 2
        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 198
    public function supportsForeignKeyConstraints()
767
    {
768 198
        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 38
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
783
    {
784 38
        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 19
    public function getCreateConstraintSQL(Constraint $constraint, $table)
799
    {
800 19
        throw new DBALException('Sqlite platform does not support alter constraint.');
801
    }
802
803
    /**
804
     * {@inheritDoc}
805
     */
806 646
    public function getCreateTableSQL(Table $table, $createFlags = null)
807
    {
808 646
        $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS;
809
810 646
        return parent::getCreateTableSQL($table, $createFlags);
811
    }
812
813
    /**
814
     * {@inheritDoc}
815
     */
816 20
    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

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

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

914
            $newTable->addOption('alter', /** @scrutinizer ignore-type */ true);
Loading history...
915
916 241
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
917
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
918 241
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
919 241
            $sql[] = $this->getDropTableSQL($fromTable);
920
921 241
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
922 241
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
923 241
            $sql[] = $this->getDropTableSQL($dataTable);
924
925 241
            if ($diff->newName && $diff->newName != $diff->name) {
926 57
                $renamedTable = $diff->getNewName();
927 57
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
928
            }
929
930 241
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
931
        }
932
933 241
        return array_merge($sql, $tableSql, $columnSql);
934
    }
935
936
    /**
937
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
938
     *
939
     * @return string[]|false
940
     */
941 304
    private function getSimpleAlterTableSQL(TableDiff $diff)
942
    {
943
        // Suppress changes on integer type autoincrement columns.
944 304
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
945 131
            if ( ! $columnDiff->fromColumn instanceof Column ||
946 55
                ! $columnDiff->column instanceof Column ||
947 55
                ! $columnDiff->column->getAutoincrement() ||
948 131
                ! $columnDiff->column->getType() instanceof Types\IntegerType
949
            ) {
950 126
                continue;
951
            }
952
953 5
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
954 1
                unset($diff->changedColumns[$oldColumnName]);
955
956 1
                continue;
957
            }
958
959 4
            $fromColumnType = $columnDiff->fromColumn->getType();
960
961 4
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
962 4
                unset($diff->changedColumns[$oldColumnName]);
963
            }
964
        }
965
966 304
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
967 209
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
968 121
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
969 304
                || ! empty($diff->renamedIndexes)
970
        ) {
971 241
            return false;
972
        }
973
974 63
        $table = new Table($diff->name);
975
976 63
        $sql = [];
977 63
        $tableSql = [];
978 63
        $columnSql = [];
979
980 63
        foreach ($diff->addedColumns as $column) {
981 57
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
982
                continue;
983
            }
984
985 57
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
986 57
            $type = $field['type'];
987
            switch (true) {
988 57
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
989 38
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
990 38
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
991 19
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
992 38
                    return false;
993
            }
994
995 19
            $field['name'] = $column->getQuotedName($this);
996 19
            if ($type instanceof Types\StringType && $field['length'] === null) {
997 19
                $field['length'] = 255;
998
            }
999
1000 19
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
1001
        }
1002
1003 25
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
1004 25
            if ($diff->newName !== false) {
1005 1
                $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

1005
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1006 1
                $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
1007
            }
1008
        }
1009
1010 25
        return array_merge($sql, $tableSql, $columnSql);
1011
    }
1012
1013
    /**
1014
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1015
     *
1016
     * @return array
1017
     */
1018 241
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1019
    {
1020 241
        $columns = [];
1021
1022 241
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
1023 222
            $columns[strtolower($columnName)] = $column->getName();
1024
        }
1025
1026 241
        foreach ($diff->removedColumns as $columnName => $column) {
1027 77
            $columnName = strtolower($columnName);
1028 77
            if (isset($columns[$columnName])) {
1029 77
                unset($columns[$columnName]);
1030
            }
1031
        }
1032
1033 241
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1034 95
            $columnName = $column->getName();
1035 95
            $columns[strtolower($oldColumnName)] = $columnName;
1036 95
            $columns[strtolower($columnName)] = $columnName;
1037
        }
1038
1039 241
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1040 126
            $columnName = $columnDiff->column->getName();
1041 126
            $columns[strtolower($oldColumnName)] = $columnName;
1042 126
            $columns[strtolower($columnName)] = $columnName;
1043
        }
1044
1045 241
        foreach ($diff->addedColumns as $columnName => $column) {
1046 58
            $columns[strtolower($columnName)] = $columnName;
1047
        }
1048
1049 241
        return $columns;
1050
    }
1051
1052
    /**
1053
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1054
     *
1055
     * @return \Doctrine\DBAL\Schema\Index[]
1056
     */
1057 241
    private function getIndexesInAlteredTable(TableDiff $diff)
1058
    {
1059 241
        $indexes = $diff->fromTable->getIndexes();
1060 241
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1061
1062 241
        foreach ($indexes as $key => $index) {
1063 117
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1064 58
                if (strtolower($key) === strtolower($oldIndexName)) {
1065 58
                    unset($indexes[$key]);
1066
                }
1067
            }
1068
1069 117
            $changed = false;
1070 117
            $indexColumns = [];
1071 117
            foreach ($index->getColumns() as $columnName) {
1072 117
                $normalizedColumnName = strtolower($columnName);
1073 117
                if ( ! isset($columnNames[$normalizedColumnName])) {
1074 19
                    unset($indexes[$key]);
1075 19
                    continue 2;
1076
                } else {
1077 117
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1078 117
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1079 117
                        $changed = true;
1080
                    }
1081
                }
1082
            }
1083
1084 117
            if ($changed) {
1085 117
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1086
            }
1087
        }
1088
1089 241
        foreach ($diff->removedIndexes as $index) {
1090 20
            $indexName = strtolower($index->getName());
1091 20
            if (strlen($indexName) && isset($indexes[$indexName])) {
1092 20
                unset($indexes[$indexName]);
1093
            }
1094
        }
1095
1096 241
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1097 58
            $indexName = strtolower($index->getName());
1098 58
            if (strlen($indexName)) {
1099 58
                $indexes[$indexName] = $index;
1100
            } else {
1101 58
                $indexes[] = $index;
1102
            }
1103
        }
1104
1105 241
        return $indexes;
1106
    }
1107
1108
    /**
1109
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1110
     *
1111
     * @return array
1112
     */
1113 241
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1114
    {
1115 241
        $foreignKeys = $diff->fromTable->getForeignKeys();
1116 241
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1117
1118 241
        foreach ($foreignKeys as $key => $constraint) {
1119 57
            $changed = false;
1120 57
            $localColumns = [];
1121 57
            foreach ($constraint->getLocalColumns() as $columnName) {
1122 57
                $normalizedColumnName = strtolower($columnName);
1123 57
                if ( ! isset($columnNames[$normalizedColumnName])) {
1124 19
                    unset($foreignKeys[$key]);
1125 19
                    continue 2;
1126
                } else {
1127 57
                    $localColumns[] = $columnNames[$normalizedColumnName];
1128 57
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1129 57
                        $changed = true;
1130
                    }
1131
                }
1132
            }
1133
1134 57
            if ($changed) {
1135 57
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1136
            }
1137
        }
1138
1139 241
        foreach ($diff->removedForeignKeys as $constraint) {
1140 19
            $constraintName = strtolower($constraint->getName());
1141 19
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
1142 19
                unset($foreignKeys[$constraintName]);
1143
            }
1144
        }
1145
1146 241
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1147 20
            $constraintName = strtolower($constraint->getName());
1148 20
            if (strlen($constraintName)) {
1149 19
                $foreignKeys[$constraintName] = $constraint;
1150
            } else {
1151 20
                $foreignKeys[] = $constraint;
1152
            }
1153
        }
1154
1155 241
        return $foreignKeys;
1156
    }
1157
1158
    /**
1159
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1160
     *
1161
     * @return array
1162
     */
1163 241
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1164
    {
1165 241
        $primaryIndex = [];
1166
1167 241
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1168 117
            if ($index->isPrimary()) {
1169 117
                $primaryIndex = [$index->getName() => $index];
1170
            }
1171
        }
1172
1173 241
        return $primaryIndex;
1174
    }
1175
}
1176