Passed
Push — master ( d48ea9...7021bd )
by Sergei
24:21
created

getDateArithmeticIntervalExpression()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

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

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

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

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