Completed
Pull Request — master (#3143)
by Alessandro
17:39
created

SqlitePlatform::getSetTransactionIsolationSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

913
            $newTable->addOption('alter', /** @scrutinizer ignore-type */ true);
Loading history...
914
915 241
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
916
            //$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...
917 241
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
918 241
            $sql[] = $this->getDropTableSQL($fromTable);
919
920 241
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
921 241
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
922 241
            $sql[] = $this->getDropTableSQL($dataTable);
923
924 241
            if ($diff->newName && $diff->newName != $diff->name) {
925 57
                $renamedTable = $diff->getNewName();
926 57
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
927
            }
928
929 241
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
930
        }
931
932 241
        return array_merge($sql, $tableSql, $columnSql);
933
    }
934
935
    /**
936
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
937
     *
938
     * @return array|bool
939
     */
940 304
    private function getSimpleAlterTableSQL(TableDiff $diff)
941
    {
942
        // Suppress changes on integer type autoincrement columns.
943 304
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
944 131
            if ( ! $columnDiff->fromColumn instanceof Column ||
945 55
                ! $columnDiff->column instanceof Column ||
946 55
                ! $columnDiff->column->getAutoincrement() ||
947 131
                ! $columnDiff->column->getType() instanceof Types\IntegerType
948
            ) {
949 126
                continue;
950
            }
951
952 5
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
953 1
                unset($diff->changedColumns[$oldColumnName]);
954
955 1
                continue;
956
            }
957
958 4
            $fromColumnType = $columnDiff->fromColumn->getType();
959
960 4
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
961 4
                unset($diff->changedColumns[$oldColumnName]);
962
            }
963
        }
964
965 304
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
966 209
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
967 121
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
968 304
                || ! empty($diff->renamedIndexes)
969
        ) {
970 241
            return false;
971
        }
972
973 63
        $table = new Table($diff->name);
974
975 63
        $sql = [];
976 63
        $tableSql = [];
977 63
        $columnSql = [];
978
979 63
        foreach ($diff->addedColumns as $column) {
980 57
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
981
                continue;
982
            }
983
984 57
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
985 57
            $type = $field['type'];
986
            switch (true) {
987 57
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
988 38
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
989 38
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
990 19
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
991 38
                    return false;
992
            }
993
994 19
            $field['name'] = $column->getQuotedName($this);
995 19
            if ($type instanceof Types\StringType && $field['length'] === null) {
996 19
                $field['length'] = 255;
997
            }
998
999 19
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
1000
        }
1001
1002 25
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
1003 25
            if ($diff->newName !== false) {
1004 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

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