Failed Conditions
Pull Request — master (#3133)
by Michael
25:15 queued 21:18
created

SqlitePlatform::getTrimExpression()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 10
cts 10
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 6
nop 3
crap 4
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
        switch ($unit) {
146 39
            case DateIntervalUnit::SECOND:
147 39
            case DateIntervalUnit::MINUTE:
148 39
            case DateIntervalUnit::HOUR:
149 1
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
150
151
            default:
152
                switch ($unit) {
153 39
                    case DateIntervalUnit::WEEK:
154 1
                        $interval *= 7;
155 1
                        $unit      = DateIntervalUnit::DAY;
156 1
                        break;
157
158 39
                    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
        switch ($level) {
186 19
            case TransactionIsolationLevel::READ_UNCOMMITTED:
187 19
                return 0;
188 19
            case TransactionIsolationLevel::READ_COMMITTED:
189 19
            case TransactionIsolationLevel::REPEATABLE_READ:
190 19
            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 560
    public function getIntegerTypeDeclarationSQL(array $field)
225
    {
226 560
        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 560
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
309
    {
310
        // sqlite autoincrement is implicit for integer PKs, but not when the field is unsigned
311 560
        if ( ! empty($columnDef['autoincrement'])) {
312 201
            return '';
313
        }
314
315 493
        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 624
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
336
    {
337 624
        $name = str_replace('.', '__', $name);
338 624
        $queryFields = $this->getColumnDeclarationListSQL($columns);
339
340 624
        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 624
        if (isset($options['primary']) && ! empty($options['primary'])) {
347 314
            $keyColumns = array_unique(array_values($options['primary']));
348 314
            $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
349
        }
350
351 624
        if (isset($options['foreignKeys'])) {
352 605
            foreach ($options['foreignKeys'] as $foreignKey) {
353 96
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
354
            }
355
        }
356
357 624
        $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.
Loading history...
358
359 624
        if (isset($options['alter']) && true === $options['alter']) {
360 241
            return $query;
361
        }
362
363 396
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
364 80
            foreach ($options['indexes'] as $indexDef) {
365 80
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
366
            }
367
        }
368
369 396
        if (isset($options['unique']) && ! empty($options['unique'])) {
370
            foreach ($options['unique'] as $indexDef) {
371
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
372
            }
373
        }
374
375 396
        return $query;
376
    }
377
378
    /**
379
     * {@inheritDoc}
380
     */
381 369
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
382
    {
383 369
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
384 369
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390 20
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
391
    {
392 20
        return 'BLOB';
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398 40
    public function getBinaryMaxLength()
399
    {
400 40
        return 0;
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     */
406 39
    public function getBinaryDefaultLength()
407
    {
408 39
        return 0;
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     */
414 65
    public function getClobTypeDeclarationSQL(array $field)
415
    {
416 65
        return 'CLOB';
417
    }
418
419
    /**
420
     * {@inheritDoc}
421
     */
422 19
    public function getListTableConstraintsSQL($table)
423
    {
424 19
        $table = str_replace('.', '__', $table);
425 19
        $table = $this->quoteStringLiteral($table);
426
427 19
        return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = $table AND sql NOT NULL ORDER BY name";
428
    }
429
430
    /**
431
     * {@inheritDoc}
432
     */
433 95
    public function getListTableColumnsSQL($table, $currentDatabase = null)
434
    {
435 95
        $table = str_replace('.', '__', $table);
436 95
        $table = $this->quoteStringLiteral($table);
437
438 95
        return "PRAGMA table_info($table)";
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444 88
    public function getListTableIndexesSQL($table, $currentDatabase = null)
445
    {
446 88
        $table = str_replace('.', '__', $table);
447 88
        $table = $this->quoteStringLiteral($table);
448
449 88
        return "PRAGMA index_list($table)";
450
    }
451
452
    /**
453
     * {@inheritDoc}
454
     */
455 118
    public function getListTablesSQL()
456
    {
457
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
458
             . "UNION ALL SELECT name FROM sqlite_temp_master "
459 118
             . "WHERE type = 'table' ORDER BY name";
460
    }
461
462
    /**
463
     * {@inheritDoc}
464
     */
465 1
    public function getListViewsSQL($database)
466
    {
467 1
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
468
    }
469
470
    /**
471
     * {@inheritDoc}
472
     */
473 1
    public function getCreateViewSQL($name, $sql)
474
    {
475 1
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
476
    }
477
478
    /**
479
     * {@inheritDoc}
480
     */
481 1
    public function getDropViewSQL($name)
482
    {
483 1
        return 'DROP VIEW '. $name;
484
    }
485
486
    /**
487
     * {@inheritDoc}
488
     */
489 96
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
490
    {
491 96
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
492
493 96
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
494 96
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
495
496 96
        return $query;
497
    }
498
499
    /**
500
     * {@inheritDoc}
501
     */
502 3
    public function supportsIdentityColumns()
503
    {
504 3
        return true;
505
    }
506
507
    /**
508
     * {@inheritDoc}
509
     */
510 58
    public function supportsColumnCollation()
511
    {
512 58
        return true;
513
    }
514
515
    /**
516
     * {@inheritDoc}
517
     */
518 700
    public function supportsInlineColumnComments()
519
    {
520 700
        return true;
521
    }
522
523
    /**
524
     * {@inheritDoc}
525
     */
526 124
    public function getName()
527
    {
528 124
        return 'sqlite';
529
    }
530
531
    /**
532
     * {@inheritDoc}
533
     */
534 27
    public function getTruncateTableSQL($tableName, $cascade = false)
535
    {
536 27
        $tableIdentifier = new Identifier($tableName);
537 27
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
538
539 27
        return 'DELETE FROM ' . $tableName;
540
    }
541
542
    /**
543
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
544
     *
545
     * @param int|float $value
546
     *
547
     * @return float
548
     */
549
    public static function udfSqrt($value)
550
    {
551
        return sqrt($value);
552
    }
553
554
    /**
555
     * User-defined function for Sqlite that implements MOD(a, b).
556
     *
557
     * @param int $a
558
     * @param int $b
559
     *
560
     * @return int
561
     */
562
    public static function udfMod($a, $b)
563
    {
564
        return ($a % $b);
565
    }
566
567
    /**
568
     * @param string $str
569
     * @param string $substr
570
     * @param int    $offset
571
     *
572
     * @return int
573
     */
574 1
    public static function udfLocate($str, $substr, $offset = 0)
575
    {
576
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
577
        // So we have to make them compatible if an offset is given.
578 1
        if ($offset > 0) {
579 1
            $offset -= 1;
580
        }
581
582 1
        $pos = strpos($str, $substr, $offset);
583
584 1
        if ($pos !== false) {
585 1
            return $pos + 1;
586
        }
587
588 1
        return 0;
589
    }
590
591
    /**
592
     * {@inheritDoc}
593
     */
594
    public function getForUpdateSql()
595
    {
596
        return '';
597
    }
598
599
    /**
600
     * {@inheritDoc}
601
     */
602 146
    public function getInlineColumnCommentSQL($comment)
603
    {
604 146
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
605
    }
606
607
    /**
608
     * {@inheritDoc}
609
     */
610 115
    protected function initializeDoctrineTypeMappings()
611
    {
612 115
        $this->doctrineTypeMapping = [
613
            'boolean'          => 'boolean',
614
            'tinyint'          => 'boolean',
615
            'smallint'         => 'smallint',
616
            'mediumint'        => 'integer',
617
            'int'              => 'integer',
618
            'integer'          => 'integer',
619
            'serial'           => 'integer',
620
            'bigint'           => 'bigint',
621
            'bigserial'        => 'bigint',
622
            'clob'             => 'text',
623
            'tinytext'         => 'text',
624
            'mediumtext'       => 'text',
625
            'longtext'         => 'text',
626
            'text'             => 'text',
627
            'varchar'          => 'string',
628
            'longvarchar'      => 'string',
629
            'varchar2'         => 'string',
630
            'nvarchar'         => 'string',
631
            'image'            => 'string',
632
            'ntext'            => 'string',
633
            'char'             => 'string',
634
            'date'             => 'date',
635
            'datetime'         => 'datetime',
636
            'timestamp'        => 'datetime',
637
            'time'             => 'time',
638
            'float'            => 'float',
639
            'double'           => 'float',
640
            'double precision' => 'float',
641
            'real'             => 'float',
642
            'decimal'          => 'decimal',
643
            'numeric'          => 'decimal',
644
            'blob'             => 'blob',
645
        ];
646 115
    }
647
648
    /**
649
     * {@inheritDoc}
650
     */
651 770
    protected function getReservedKeywordsClass()
652
    {
653 770
        return Keywords\SQLiteKeywords::class;
654
    }
655
656
    /**
657
     * {@inheritDoc}
658
     */
659 241
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
660
    {
661 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...
662
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
663
        }
664
665 241
        $sql = [];
666 241
        foreach ($diff->fromTable->getIndexes() as $index) {
667 117
            if ( ! $index->isPrimary()) {
668 117
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
669
            }
670
        }
671
672 241
        return $sql;
673
    }
674
675
    /**
676
     * {@inheritDoc}
677
     */
678 241
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
679
    {
680 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...
681
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
682
        }
683
684 241
        $sql = [];
685 241
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
686 241
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
687 117
            if ($index->isPrimary()) {
688 79
                continue;
689
            }
690
691 96
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
692
        }
693
694 241
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 65
    protected function doModifyLimitQuery($query, $limit, $offset)
701
    {
702 65
        if (null === $limit && null !== $offset) {
703 21
            return $query . ' LIMIT -1 OFFSET ' . $offset;
704
        }
705
706 45
        return parent::doModifyLimitQuery($query, $limit, $offset);
707
    }
708
709
    /**
710
     * {@inheritDoc}
711
     */
712 25
    public function getBlobTypeDeclarationSQL(array $field)
713
    {
714 25
        return 'BLOB';
715
    }
716
717
    /**
718
     * {@inheritDoc}
719
     */
720 2
    public function getTemporaryTableName($tableName)
721
    {
722 2
        $tableName = str_replace('.', '__', $tableName);
723
724 2
        return $tableName;
725
    }
726
727
    /**
728
     * {@inheritDoc}
729
     *
730
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
731
     * into the default database.
732
     *
733
     * This hack is implemented to be able to use SQLite as testdriver when
734
     * using schema supporting databases.
735
     */
736
    public function canEmulateSchemas()
737
    {
738
        return true;
739
    }
740
741
    /**
742
     * {@inheritDoc}
743
     */
744 198
    public function supportsForeignKeyConstraints()
745
    {
746 198
        return false;
747
    }
748
749
    /**
750
     * {@inheritDoc}
751
     */
752
    public function getCreatePrimaryKeySQL(Index $index, $table)
753
    {
754
        throw new DBALException('Sqlite platform does not support alter primary key.');
755
    }
756
757
    /**
758
     * {@inheritdoc}
759
     */
760 38
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
761
    {
762 38
        throw new DBALException('Sqlite platform does not support alter foreign key.');
763
    }
764
765
    /**
766
     * {@inheritdoc}
767
     */
768
    public function getDropForeignKeySQL($foreignKey, $table)
769
    {
770
        throw new DBALException('Sqlite platform does not support alter foreign key.');
771
    }
772
773
    /**
774
     * {@inheritDoc}
775
     */
776 19
    public function getCreateConstraintSQL(Constraint $constraint, $table)
777
    {
778 19
        throw new DBALException('Sqlite platform does not support alter constraint.');
779
    }
780
781
    /**
782
     * {@inheritDoc}
783
     */
784 643
    public function getCreateTableSQL(Table $table, $createFlags = null)
785
    {
786 643
        $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags;
787
788 643
        return parent::getCreateTableSQL($table, $createFlags);
789
    }
790
791
    /**
792
     * {@inheritDoc}
793
     */
794 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

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

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

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