Failed Conditions
Push — master ( 46069b...f76bf5 )
by Marco
16s queued 12s
created

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