Completed
Push — master ( af81c1...f3339a )
by Sergei
21s queued 15s
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.0119

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 10
cts 11
cp 0.9091
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 6
nop 3
crap 4.0119
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 18
    public function getRegexpExpression()
60
    {
61 18
        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 18
    public function getSubstringExpression($value, $position, $length = null)
120
    {
121 18
        if ($length !== null) {
122 18
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
123
        }
124
125 18
        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 37
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
144
    {
145 2
        switch ($unit) {
146 35
            case DateIntervalUnit::SECOND:
147 35
            case DateIntervalUnit::MINUTE:
148 35
            case DateIntervalUnit::HOUR:
149 1
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
150
151
            default:
152 2
                switch ($unit) {
153 35
                    case DateIntervalUnit::WEEK:
154 1
                        $interval *= 7;
155 1
                        $unit      = DateIntervalUnit::DAY;
156 1
                        break;
157
158 35
                    case DateIntervalUnit::QUARTER:
159 1
                        $interval *= 3;
160 1
                        $unit      = DateIntervalUnit::MONTH;
161 1
                        break;
162
                }
163
164 37
                if (! is_numeric($interval)) {
165 18
                    $interval = "' || " . $interval . " || '";
166
                }
167
168 37
                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 18
    protected function _getTransactionIsolationLevelSQL($level)
184
    {
185 1
        switch ($level) {
186 17
            case TransactionIsolationLevel::READ_UNCOMMITTED:
187 18
                return 0;
188 17
            case TransactionIsolationLevel::READ_COMMITTED:
189 17
            case TransactionIsolationLevel::REPEATABLE_READ:
190 17
            case TransactionIsolationLevel::SERIALIZABLE:
191 18
                return 1;
192
            default:
193
                return parent::_getTransactionIsolationLevelSQL($level);
194
        }
195
    }
196
197
    /**
198
     * {@inheritDoc}
199
     */
200 18
    public function getSetTransactionIsolationSQL($level)
201
    {
202 18
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208 19
    public function prefersIdentityColumns()
209
    {
210 19
        return true;
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 33
    public function getBooleanTypeDeclarationSQL(array $field)
217
    {
218 33
        return 'BOOLEAN';
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 537
    public function getIntegerTypeDeclarationSQL(array $field)
225
    {
226 537
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 35
    public function getBigIntTypeDeclarationSQL(array $field)
233
    {
234
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
235 35
        if ( ! empty($field['autoincrement'])) {
236 20
            return $this->getIntegerTypeDeclarationSQL($field);
237
        }
238
239 33
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
240
    }
241
242
    /**
243
     * {@inheritDoc}
244
     */
245 36
    public function getTinyIntTypeDeclarationSql(array $field)
246
    {
247
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
248 36
        if ( ! empty($field['autoincrement'])) {
249 36
            return $this->getIntegerTypeDeclarationSQL($field);
250
        }
251
252 18
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258 21
    public function getSmallIntTypeDeclarationSQL(array $field)
259
    {
260
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
261 21
        if ( ! empty($field['autoincrement'])) {
262 20
            return $this->getIntegerTypeDeclarationSQL($field);
263
        }
264
265 19
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     */
271 18
    public function getMediumIntTypeDeclarationSql(array $field)
272
    {
273
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
274 18
        if ( ! empty($field['autoincrement'])) {
275 18
            return $this->getIntegerTypeDeclarationSQL($field);
276
        }
277
278 18
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
279
    }
280
281
    /**
282
     * {@inheritDoc}
283
     */
284 19
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
285
    {
286 19
        return 'DATETIME';
287
    }
288
289
    /**
290
     * {@inheritDoc}
291
     */
292 21
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
293
    {
294 21
        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 537
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
309
    {
310
        // sqlite autoincrement is implicit for integer PKs, but not when the field is unsigned
311 537
        if ( ! empty($columnDef['autoincrement'])) {
312 192
            return '';
313
        }
314
315 473
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321 91
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
322
    {
323 91
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
324 91
            $foreignKey->getQuotedLocalColumns($this),
325 91
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
326 91
            $foreignKey->getQuotedForeignColumns($this),
327 91
            $foreignKey->getName(),
328 91
            $foreignKey->getOptions()
329
        ));
330
    }
331
332
    /**
333
     * {@inheritDoc}
334
     */
335 597
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
336
    {
337 597
        $name = str_replace('.', '__', $name);
338 597
        $queryFields = $this->getColumnDeclarationListSQL($columns);
339
340 597
        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 597
        if (isset($options['primary']) && ! empty($options['primary'])) {
347 301
            $keyColumns = array_unique(array_values($options['primary']));
348 301
            $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
349
        }
350
351 597
        if (isset($options['foreignKeys'])) {
352 579
            foreach ($options['foreignKeys'] as $foreignKey) {
353 91
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
354
            }
355
        }
356
357 597
        $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 597
        if (isset($options['alter']) && true === $options['alter']) {
360 229
            return $query;
361
        }
362
363 381
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
364 76
            foreach ($options['indexes'] as $indexDef) {
365 76
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
366
            }
367
        }
368
369 381
        if (isset($options['unique']) && ! empty($options['unique'])) {
370
            foreach ($options['unique'] as $indexDef) {
371
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
372
            }
373
        }
374
375 381
        return $query;
376
    }
377
378
    /**
379
     * {@inheritDoc}
380
     */
381 353
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
382
    {
383 353
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
384 353
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390 19
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
391
    {
392 19
        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 37
    public function getBinaryDefaultLength()
407
    {
408 37
        return 0;
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     */
414 64
    public function getClobTypeDeclarationSQL(array $field)
415
    {
416 64
        return 'CLOB';
417
    }
418
419
    /**
420
     * {@inheritDoc}
421
     */
422 18
    public function getListTableConstraintsSQL($table)
423
    {
424 18
        $table = str_replace('.', '__', $table);
425 18
        $table = $this->quoteStringLiteral($table);
426
427 18
        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 92
    public function getListTableColumnsSQL($table, $currentDatabase = null)
434
    {
435 92
        $table = str_replace('.', '__', $table);
436 92
        $table = $this->quoteStringLiteral($table);
437
438 92
        return "PRAGMA table_info($table)";
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444 85
    public function getListTableIndexesSQL($table, $currentDatabase = null)
445
    {
446 85
        $table = str_replace('.', '__', $table);
447 85
        $table = $this->quoteStringLiteral($table);
448
449 85
        return "PRAGMA index_list($table)";
450
    }
451
452
    /**
453
     * {@inheritDoc}
454
     */
455 115
    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 115
             . "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 91
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
490
    {
491 91
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
492
493 91
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
494 91
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
495
496 91
        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 55
    public function supportsColumnCollation()
511
    {
512 55
        return true;
513
    }
514
515
    /**
516
     * {@inheritDoc}
517
     */
518 669
    public function supportsInlineColumnComments()
519
    {
520 669
        return true;
521
    }
522
523
    /**
524
     * {@inheritDoc}
525
     */
526 121
    public function getName()
527
    {
528 121
        return 'sqlite';
529
    }
530
531
    /**
532
     * {@inheritDoc}
533
     */
534 29
    public function getTruncateTableSQL($tableName, $cascade = false)
535
    {
536 29
        $tableIdentifier = new Identifier($tableName);
537 29
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
538
539 29
        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 140
    public function getInlineColumnCommentSQL($comment)
603
    {
604 140
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
605
    }
606
607
    /**
608
     * {@inheritDoc}
609
     */
610 109
    protected function initializeDoctrineTypeMappings()
611
    {
612 109
        $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 109
    }
647
648
    /**
649
     * {@inheritDoc}
650
     */
651 730
    protected function getReservedKeywordsClass()
652
    {
653 730
        return Keywords\SQLiteKeywords::class;
654
    }
655
656
    /**
657
     * {@inheritDoc}
658
     */
659 229
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
660
    {
661 229
        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 229
        $sql = [];
666 229
        foreach ($diff->fromTable->getIndexes() as $index) {
667 111
            if ( ! $index->isPrimary()) {
668 111
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
669
            }
670
        }
671
672 229
        return $sql;
673
    }
674
675
    /**
676
     * {@inheritDoc}
677
     */
678 229
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
679
    {
680 229
        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 229
        $sql = [];
685 229
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
686 229
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
687 111
            if ($index->isPrimary()) {
688 75
                continue;
689
            }
690
691 91
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
692
        }
693
694 229
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 62
    protected function doModifyLimitQuery($query, $limit, $offset)
701
    {
702 62
        if (null === $limit && null !== $offset) {
703 20
            return $query . ' LIMIT -1 OFFSET ' . $offset;
704
        }
705
706 43
        return parent::doModifyLimitQuery($query, $limit, $offset);
707
    }
708
709
    /**
710
     * {@inheritDoc}
711
     */
712 23
    public function getBlobTypeDeclarationSQL(array $field)
713
    {
714 23
        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 189
    public function supportsForeignKeyConstraints()
745
    {
746 189
        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 36
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
761
    {
762 36
        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 18
    public function getCreateConstraintSQL(Constraint $constraint, $table)
777
    {
778 18
        throw new DBALException('Sqlite platform does not support alter constraint.');
779
    }
780
781
    /**
782
     * {@inheritDoc}
783
     */
784 615
    public function getCreateTableSQL(Table $table, $createFlags = null)
785
    {
786 615
        $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags;
787
788 615
        return parent::getCreateTableSQL($table, $createFlags);
789
    }
790
791
    /**
792
     * {@inheritDoc}
793
     */
794 19
    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 19
        $table = str_replace('.', '__', $table);
797 19
        $table = $this->quoteStringLiteral($table);
798
799 19
        return "PRAGMA foreign_key_list($table)";
800
    }
801
802
    /**
803
     * {@inheritDoc}
804
     */
805 289
    public function getAlterTableSQL(TableDiff $diff)
806
    {
807 289
        $sql = $this->getSimpleAlterTableSQL($diff);
808 289
        if (false !== $sql) {
809 24
            return $sql;
810
        }
811
812 265
        $fromTable = $diff->fromTable;
813 265
        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 36
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
815
        }
816
817 229
        $table = clone $fromTable;
818
819 229
        $columns = [];
820 229
        $oldColumnNames = [];
821 229
        $newColumnNames = [];
822 229
        $columnSql = [];
823
824 229
        foreach ($table->getColumns() as $columnName => $column) {
825 211
            $columnName = strtolower($columnName);
826 211
            $columns[$columnName] = $column;
827 211
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
828
        }
829
830 229
        foreach ($diff->removedColumns as $columnName => $column) {
831 73
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
832
                continue;
833
            }
834
835 73
            $columnName = strtolower($columnName);
836 73
            if (isset($columns[$columnName])) {
837
                unset(
838 73
                    $columns[$columnName],
839 73
                    $oldColumnNames[$columnName],
840 73
                    $newColumnNames[$columnName]
841
                );
842
            }
843
        }
844
845 229
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
846 90
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
847
                continue;
848
            }
849
850 90
            $oldColumnName = strtolower($oldColumnName);
851 90
            if (isset($columns[$oldColumnName])) {
852 90
                unset($columns[$oldColumnName]);
853
            }
854
855 90
            $columns[strtolower($column->getName())] = $column;
856
857 90
            if (isset($newColumnNames[$oldColumnName])) {
858 90
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
859
            }
860
        }
861
862 229
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
863 120
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
864
                continue;
865
            }
866
867 120
            if (isset($columns[$oldColumnName])) {
868 102
                unset($columns[$oldColumnName]);
869
            }
870
871 120
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
872
873 120
            if (isset($newColumnNames[$oldColumnName])) {
874 120
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
875
            }
876
        }
877
878 229
        foreach ($diff->addedColumns as $columnName => $column) {
879 55
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
880
                continue;
881
            }
882
883 55
            $columns[strtolower($columnName)] = $column;
884
        }
885
886 229
        $sql = [];
887 229
        $tableSql = [];
888 229
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
889 229
            $dataTable = new Table('__temp__'.$table->getName());
890
891 229
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
892 229
            $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 229
            $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 229
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
897 229
            $sql[] = $this->getDropTableSQL($fromTable);
898
899 229
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
900 229
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
901 229
            $sql[] = $this->getDropTableSQL($dataTable);
902
903 229
            if ($diff->newName && $diff->newName != $diff->name) {
904 54
                $renamedTable = $diff->getNewName();
905 54
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
906
            }
907
908 229
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
909
        }
910
911 229
        return array_merge($sql, $tableSql, $columnSql);
912
    }
913
914
    /**
915
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
916
     *
917
     * @return array|bool
918
     */
919 289
    private function getSimpleAlterTableSQL(TableDiff $diff)
920
    {
921
        // Suppress changes on integer type autoincrement columns.
922 289
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
923 125
            if ( ! $columnDiff->fromColumn instanceof Column ||
924 53
                ! $columnDiff->column instanceof Column ||
925 53
                ! $columnDiff->column->getAutoincrement() ||
926 125
                ! $columnDiff->column->getType() instanceof Types\IntegerType
927
            ) {
928 120
                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 289
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
945 199
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
946 115
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
947 289
                || ! empty($diff->renamedIndexes)
948
        ) {
949 229
            return false;
950
        }
951
952 60
        $table = new Table($diff->name);
953
954 60
        $sql = [];
955 60
        $tableSql = [];
956 60
        $columnSql = [];
957
958 60
        foreach ($diff->addedColumns as $column) {
959 54
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
960
                continue;
961
            }
962
963 54
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
964 54
            $type = $field['type'];
965
            switch (true) {
966 54
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
967 36
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
968 36
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
969 18
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
970 36
                    return false;
971
            }
972
973 18
            $field['name'] = $column->getQuotedName($this);
974 18
            if ($type instanceof Types\StringType && $field['length'] === null) {
975 18
                $field['length'] = 255;
976
            }
977
978 18
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
979
        }
980
981 24
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
982 24
            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 24
        return array_merge($sql, $tableSql, $columnSql);
989
    }
990
991
    /**
992
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
993
     *
994
     * @return array
995
     */
996 229
    private function getColumnNamesInAlteredTable(TableDiff $diff)
997
    {
998 229
        $columns = [];
999
1000 229
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
1001 211
            $columns[strtolower($columnName)] = $column->getName();
1002
        }
1003
1004 229
        foreach ($diff->removedColumns as $columnName => $column) {
1005 73
            $columnName = strtolower($columnName);
1006 73
            if (isset($columns[$columnName])) {
1007 73
                unset($columns[$columnName]);
1008
            }
1009
        }
1010
1011 229
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1012 90
            $columnName = $column->getName();
1013 90
            $columns[strtolower($oldColumnName)] = $columnName;
1014 90
            $columns[strtolower($columnName)] = $columnName;
1015
        }
1016
1017 229
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1018 120
            $columnName = $columnDiff->column->getName();
1019 120
            $columns[strtolower($oldColumnName)] = $columnName;
1020 120
            $columns[strtolower($columnName)] = $columnName;
1021
        }
1022
1023 229
        foreach ($diff->addedColumns as $columnName => $column) {
1024 55
            $columns[strtolower($columnName)] = $columnName;
1025
        }
1026
1027 229
        return $columns;
1028
    }
1029
1030
    /**
1031
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1032
     *
1033
     * @return \Doctrine\DBAL\Schema\Index[]
1034
     */
1035 229
    private function getIndexesInAlteredTable(TableDiff $diff)
1036
    {
1037 229
        $indexes = $diff->fromTable->getIndexes();
1038 229
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1039
1040 229
        foreach ($indexes as $key => $index) {
1041 111
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1042 55
                if (strtolower($key) === strtolower($oldIndexName)) {
1043 55
                    unset($indexes[$key]);
1044
                }
1045
            }
1046
1047 111
            $changed = false;
1048 111
            $indexColumns = [];
1049 111
            foreach ($index->getColumns() as $columnName) {
1050 111
                $normalizedColumnName = strtolower($columnName);
1051 111
                if ( ! isset($columnNames[$normalizedColumnName])) {
1052 18
                    unset($indexes[$key]);
1053 18
                    continue 2;
1054
                } else {
1055 111
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1056 111
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1057 111
                        $changed = true;
1058
                    }
1059
                }
1060
            }
1061
1062 111
            if ($changed) {
1063 111
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1064
            }
1065
        }
1066
1067 229
        foreach ($diff->removedIndexes as $index) {
1068 19
            $indexName = strtolower($index->getName());
1069 19
            if (strlen($indexName) && isset($indexes[$indexName])) {
1070 19
                unset($indexes[$indexName]);
1071
            }
1072
        }
1073
1074 229
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1075 55
            $indexName = strtolower($index->getName());
1076 55
            if (strlen($indexName)) {
1077 55
                $indexes[$indexName] = $index;
1078
            } else {
1079 55
                $indexes[] = $index;
1080
            }
1081
        }
1082
1083 229
        return $indexes;
1084
    }
1085
1086
    /**
1087
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1088
     *
1089
     * @return array
1090
     */
1091 229
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1092
    {
1093 229
        $foreignKeys = $diff->fromTable->getForeignKeys();
1094 229
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1095
1096 229
        foreach ($foreignKeys as $key => $constraint) {
1097 54
            $changed = false;
1098 54
            $localColumns = [];
1099 54
            foreach ($constraint->getLocalColumns() as $columnName) {
1100 54
                $normalizedColumnName = strtolower($columnName);
1101 54
                if ( ! isset($columnNames[$normalizedColumnName])) {
1102 18
                    unset($foreignKeys[$key]);
1103 18
                    continue 2;
1104
                } else {
1105 54
                    $localColumns[] = $columnNames[$normalizedColumnName];
1106 54
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1107 54
                        $changed = true;
1108
                    }
1109
                }
1110
            }
1111
1112 54
            if ($changed) {
1113 54
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1114
            }
1115
        }
1116
1117 229
        foreach ($diff->removedForeignKeys as $constraint) {
1118 18
            $constraintName = strtolower($constraint->getName());
1119 18
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
1120 18
                unset($foreignKeys[$constraintName]);
1121
            }
1122
        }
1123
1124 229
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1125 19
            $constraintName = strtolower($constraint->getName());
1126 19
            if (strlen($constraintName)) {
1127 18
                $foreignKeys[$constraintName] = $constraint;
1128
            } else {
1129 19
                $foreignKeys[] = $constraint;
1130
            }
1131
        }
1132
1133 229
        return $foreignKeys;
1134
    }
1135
1136
    /**
1137
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1138
     *
1139
     * @return array
1140
     */
1141 229
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1142
    {
1143 229
        $primaryIndex = [];
1144
1145 229
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1146 111
            if ($index->isPrimary()) {
1147 111
                $primaryIndex = [$index->getName() => $index];
1148
            }
1149
        }
1150
1151 229
        return $primaryIndex;
1152
    }
1153
}
1154