Failed Conditions
Push — travis-db2 ( e87e37...2c4097 )
by Michael
03:43
created

getDateArithmeticIntervalExpression()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 9
nop 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Platforms;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Schema\Column;
24
use Doctrine\DBAL\Schema\Constraint;
25
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
26
use Doctrine\DBAL\Schema\Identifier;
27
use Doctrine\DBAL\Schema\Index;
28
use Doctrine\DBAL\Schema\Table;
29
use Doctrine\DBAL\Schema\TableDiff;
30
use Doctrine\DBAL\TransactionIsolationLevel;
31
use Doctrine\DBAL\Types;
32
use function array_merge;
33
use function array_unique;
34
use function array_values;
35
use function implode;
36
use function is_numeric;
37
use function sprintf;
38
use function sqrt;
39
use function str_replace;
40
use function strlen;
41
use function strpos;
42
use function strtolower;
43
44
/**
45
 * The SqlitePlatform class describes the specifics and dialects of the SQLite
46
 * database platform.
47
 *
48
 * @since  2.0
49
 * @author Roman Borschel <[email protected]>
50
 * @author Benjamin Eberlei <[email protected]>
51
 * @author Martin Hasoň <[email protected]>
52
 * @todo   Rename: SQLitePlatform
53
 */
54
class SqlitePlatform extends AbstractPlatform
55
{
56
    /**
57
     * {@inheritDoc}
58
     */
59
    public function getRegexpExpression()
60
    {
61
        return 'REGEXP';
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     */
67
    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
            . "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
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
95
    {
96
        $trimChar = ($char != false) ? (', ' . $char) : '';
97
98
        switch ($pos) {
99
            case TrimMode::LEADING:
100
                $trimFn = 'LTRIM';
101
                break;
102
103
            case TrimMode::TRAILING:
104
                $trimFn = 'RTRIM';
105
                break;
106
107
            default:
108
                $trimFn = 'TRIM';
109
        }
110
111
        return $trimFn . '(' . $str . $trimChar . ')';
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     *
117
     * SQLite only supports the 2 parameter variant of this function
118
     */
119
    public function getSubstringExpression($value, $position, $length = null)
120
    {
121
        if ($length !== null) {
122
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
123
        }
124
125
        return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
126
    }
127
128
    /**
129
     * {@inheritDoc}
130
     */
131
    public function getLocateExpression($str, $substr, $startPos = false)
132
    {
133
        if ($startPos == false) {
134
            return 'LOCATE('.$str.', '.$substr.')';
135
        }
136
137
        return 'LOCATE('.$str.', '.$substr.', '.$startPos.')';
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
144
    {
145
        switch ($unit) {
146
            case DateIntervalUnit::SECOND:
147
            case DateIntervalUnit::MINUTE:
148
            case DateIntervalUnit::HOUR:
149
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
150
151
            default:
152
                switch ($unit) {
153
                    case DateIntervalUnit::WEEK:
154
                        $interval *= 7;
155
                        $unit      = DateIntervalUnit::DAY;
156
                        break;
157
158
                    case DateIntervalUnit::QUARTER:
159
                        $interval *= 3;
160
                        $unit      = DateIntervalUnit::MONTH;
161
                        break;
162
                }
163
164
                if (! is_numeric($interval)) {
165
                    $interval = "' || " . $interval . " || '";
166
                }
167
168
                return "DATE(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
169
        }
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175
    public function getDateDiffExpression($date1, $date2)
176
    {
177
        return 'ROUND(JULIANDAY('.$date1 . ')-JULIANDAY('.$date2.'))';
178
    }
179
180
    /**
181
     * {@inheritDoc}
182
     */
183
    protected function _getTransactionIsolationLevelSQL($level)
184
    {
185
        switch ($level) {
186
            case TransactionIsolationLevel::READ_UNCOMMITTED:
187
                return 0;
188
            case TransactionIsolationLevel::READ_COMMITTED:
189
            case TransactionIsolationLevel::REPEATABLE_READ:
190
            case TransactionIsolationLevel::SERIALIZABLE:
191
                return 1;
192
            default:
193
                return parent::_getTransactionIsolationLevelSQL($level);
194
        }
195
    }
196
197
    /**
198
     * {@inheritDoc}
199
     */
200
    public function getSetTransactionIsolationSQL($level)
201
    {
202
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208
    public function prefersIdentityColumns()
209
    {
210
        return true;
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216
    public function getBooleanTypeDeclarationSQL(array $field)
217
    {
218
        return 'BOOLEAN';
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224
    public function getIntegerTypeDeclarationSQL(array $field)
225
    {
226
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232
    public function getBigIntTypeDeclarationSQL(array $field)
233
    {
234
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
235
        if ( ! empty($field['autoincrement'])) {
236
            return $this->getIntegerTypeDeclarationSQL($field);
237
        }
238
239
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
240
    }
241
242
    /**
243
     * {@inheritDoc}
244
     */
245
    public function getTinyIntTypeDeclarationSql(array $field)
246
    {
247
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
248
        if ( ! empty($field['autoincrement'])) {
249
            return $this->getIntegerTypeDeclarationSQL($field);
250
        }
251
252
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258
    public function getSmallIntTypeDeclarationSQL(array $field)
259
    {
260
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
261
        if ( ! empty($field['autoincrement'])) {
262
            return $this->getIntegerTypeDeclarationSQL($field);
263
        }
264
265
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     */
271
    public function getMediumIntTypeDeclarationSql(array $field)
272
    {
273
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
274
        if ( ! empty($field['autoincrement'])) {
275
            return $this->getIntegerTypeDeclarationSQL($field);
276
        }
277
278
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
279
    }
280
281
    /**
282
     * {@inheritDoc}
283
     */
284
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
285
    {
286
        return 'DATETIME';
287
    }
288
289
    /**
290
     * {@inheritDoc}
291
     */
292
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
293
    {
294
        return 'DATE';
295
    }
296
297
    /**
298
     * {@inheritDoc}
299
     */
300
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
301
    {
302
        return 'TIME';
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
309
    {
310
        // sqlite autoincrement is implicit for integer PKs, but not when the field is unsigned
311
        if ( ! empty($columnDef['autoincrement'])) {
312
            return '';
313
        }
314
315
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
322
    {
323
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
324
            $foreignKey->getQuotedLocalColumns($this),
325
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
326
            $foreignKey->getQuotedForeignColumns($this),
327
            $foreignKey->getName(),
328
            $foreignKey->getOptions()
329
        ));
330
    }
331
332
    /**
333
     * {@inheritDoc}
334
     */
335
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
336
    {
337
        $name = str_replace('.', '__', $name);
338
        $queryFields = $this->getColumnDeclarationListSQL($columns);
339
340
        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
        if (isset($options['primary']) && ! empty($options['primary'])) {
347
            $keyColumns = array_unique(array_values($options['primary']));
348
            $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
349
        }
350
351
        if (isset($options['foreignKeys'])) {
352
            foreach ($options['foreignKeys'] as $foreignKey) {
353
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
354
            }
355
        }
356
357
        $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
        if (isset($options['alter']) && true === $options['alter']) {
360
            return $query;
361
        }
362
363
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
364
            foreach ($options['indexes'] as $indexDef) {
365
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
366
            }
367
        }
368
369
        if (isset($options['unique']) && ! empty($options['unique'])) {
370
            foreach ($options['unique'] as $indexDef) {
371
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
372
            }
373
        }
374
375
        return $query;
376
    }
377
378
    /**
379
     * {@inheritDoc}
380
     */
381
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
382
    {
383
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
384
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
391
    {
392
        return 'BLOB';
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398
    public function getBinaryMaxLength()
399
    {
400
        return 0;
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     */
406
    public function getBinaryDefaultLength()
407
    {
408
        return 0;
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     */
414
    public function getClobTypeDeclarationSQL(array $field)
415
    {
416
        return 'CLOB';
417
    }
418
419
    /**
420
     * {@inheritDoc}
421
     */
422
    public function getListTableConstraintsSQL($table)
423
    {
424
        $table = str_replace('.', '__', $table);
425
        $table = $this->quoteStringLiteral($table);
426
427
        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
    public function getListTableColumnsSQL($table, $currentDatabase = null)
434
    {
435
        $table = str_replace('.', '__', $table);
436
        $table = $this->quoteStringLiteral($table);
437
438
        return "PRAGMA table_info($table)";
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444
    public function getListTableIndexesSQL($table, $currentDatabase = null)
445
    {
446
        $table = str_replace('.', '__', $table);
447
        $table = $this->quoteStringLiteral($table);
448
449
        return "PRAGMA index_list($table)";
450
    }
451
452
    /**
453
     * {@inheritDoc}
454
     */
455
    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
             . "WHERE type = 'table' ORDER BY name";
460
    }
461
462
    /**
463
     * {@inheritDoc}
464
     */
465
    public function getListViewsSQL($database)
466
    {
467
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
468
    }
469
470
    /**
471
     * {@inheritDoc}
472
     */
473
    public function getCreateViewSQL($name, $sql)
474
    {
475
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
476
    }
477
478
    /**
479
     * {@inheritDoc}
480
     */
481
    public function getDropViewSQL($name)
482
    {
483
        return 'DROP VIEW '. $name;
484
    }
485
486
    /**
487
     * {@inheritDoc}
488
     */
489
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
490
    {
491
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
492
493
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
494
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
495
496
        return $query;
497
    }
498
499
    /**
500
     * {@inheritDoc}
501
     */
502
    public function supportsIdentityColumns()
503
    {
504
        return true;
505
    }
506
507
    /**
508
     * {@inheritDoc}
509
     */
510
    public function supportsColumnCollation()
511
    {
512
        return true;
513
    }
514
515
    /**
516
     * {@inheritDoc}
517
     */
518
    public function supportsInlineColumnComments()
519
    {
520
        return true;
521
    }
522
523
    /**
524
     * {@inheritDoc}
525
     */
526
    public function getName()
527
    {
528
        return 'sqlite';
529
    }
530
531
    /**
532
     * {@inheritDoc}
533
     */
534
    public function getTruncateTableSQL($tableName, $cascade = false)
535
    {
536
        $tableIdentifier = new Identifier($tableName);
537
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
538
539
        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
    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
        if ($offset > 0) {
579
            $offset -= 1;
580
        }
581
582
        $pos = strpos($str, $substr, $offset);
583
584
        if ($pos !== false) {
585
            return $pos + 1;
586
        }
587
588
        return 0;
589
    }
590
591
    /**
592
     * {@inheritDoc}
593
     */
594
    public function getForUpdateSql()
595
    {
596
        return '';
597
    }
598
599
    /**
600
     * {@inheritDoc}
601
     */
602
    public function getInlineColumnCommentSQL($comment)
603
    {
604
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
605
    }
606
607
    /**
608
     * {@inheritDoc}
609
     */
610
    protected function initializeDoctrineTypeMappings()
611
    {
612
        $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
    }
647
648
    /**
649
     * {@inheritDoc}
650
     */
651
    protected function getReservedKeywordsClass()
652
    {
653
        return Keywords\SQLiteKeywords::class;
654
    }
655
656
    /**
657
     * {@inheritDoc}
658
     */
659
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
660
    {
661
        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
        $sql = [];
666
        foreach ($diff->fromTable->getIndexes() as $index) {
667
            if ( ! $index->isPrimary()) {
668
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
669
            }
670
        }
671
672
        return $sql;
673
    }
674
675
    /**
676
     * {@inheritDoc}
677
     */
678
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
679
    {
680
        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
        $sql = [];
685
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
686
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
687
            if ($index->isPrimary()) {
688
                continue;
689
            }
690
691
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
692
        }
693
694
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700
    protected function doModifyLimitQuery($query, $limit, $offset)
701
    {
702
        if (null === $limit && null !== $offset) {
703
            return $query . ' LIMIT -1 OFFSET ' . $offset;
704
        }
705
706
        return parent::doModifyLimitQuery($query, $limit, $offset);
707
    }
708
709
    /**
710
     * {@inheritDoc}
711
     */
712
    public function getBlobTypeDeclarationSQL(array $field)
713
    {
714
        return 'BLOB';
715
    }
716
717
    /**
718
     * {@inheritDoc}
719
     */
720
    public function getTemporaryTableName($tableName)
721
    {
722
        $tableName = str_replace('.', '__', $tableName);
723
724
        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
    public function supportsForeignKeyConstraints()
745
    {
746
        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
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
761
    {
762
        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
    public function getCreateConstraintSQL(Constraint $constraint, $table)
777
    {
778
        throw new DBALException('Sqlite platform does not support alter constraint.');
779
    }
780
781
    /**
782
     * {@inheritDoc}
783
     */
784
    public function getCreateTableSQL(Table $table, $createFlags = null)
785
    {
786
        $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags;
787
788
        return parent::getCreateTableSQL($table, $createFlags);
789
    }
790
791
    /**
792
     * {@inheritDoc}
793
     */
794
    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
        $table = str_replace('.', '__', $table);
797
        $table = $this->quoteStringLiteral($table);
798
799
        return "PRAGMA foreign_key_list($table)";
800
    }
801
802
    /**
803
     * {@inheritDoc}
804
     */
805
    public function getAlterTableSQL(TableDiff $diff)
806
    {
807
        $sql = $this->getSimpleAlterTableSQL($diff);
808
        if (false !== $sql) {
809
            return $sql;
810
        }
811
812
        $fromTable = $diff->fromTable;
813
        if ( ! $fromTable instanceof Table) {
0 ignored issues
show
introduced by
$fromTable is always a sub-type of Doctrine\DBAL\Schema\Table.
Loading history...
814
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
815
        }
816
817
        $table = clone $fromTable;
818
819
        $columns = [];
820
        $oldColumnNames = [];
821
        $newColumnNames = [];
822
        $columnSql = [];
823
824
        foreach ($table->getColumns() as $columnName => $column) {
825
            $columnName = strtolower($columnName);
826
            $columns[$columnName] = $column;
827
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
828
        }
829
830
        foreach ($diff->removedColumns as $columnName => $column) {
831
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
832
                continue;
833
            }
834
835
            $columnName = strtolower($columnName);
836
            if (isset($columns[$columnName])) {
837
                unset(
838
                    $columns[$columnName],
839
                    $oldColumnNames[$columnName],
840
                    $newColumnNames[$columnName]
841
                );
842
            }
843
        }
844
845
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
846
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
847
                continue;
848
            }
849
850
            $oldColumnName = strtolower($oldColumnName);
851
            if (isset($columns[$oldColumnName])) {
852
                unset($columns[$oldColumnName]);
853
            }
854
855
            $columns[strtolower($column->getName())] = $column;
856
857
            if (isset($newColumnNames[$oldColumnName])) {
858
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
859
            }
860
        }
861
862
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
863
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
864
                continue;
865
            }
866
867
            if (isset($columns[$oldColumnName])) {
868
                unset($columns[$oldColumnName]);
869
            }
870
871
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
872
873
            if (isset($newColumnNames[$oldColumnName])) {
874
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
875
            }
876
        }
877
878
        foreach ($diff->addedColumns as $columnName => $column) {
879
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
880
                continue;
881
            }
882
883
            $columns[strtolower($columnName)] = $column;
884
        }
885
886
        $sql = [];
887
        $tableSql = [];
888
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
889
            $dataTable = new Table('__temp__'.$table->getName());
890
891
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
892
            $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
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
895
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
896
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
897
            $sql[] = $this->getDropTableSQL($fromTable);
898
899
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
900
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
901
            $sql[] = $this->getDropTableSQL($dataTable);
902
903
            if ($diff->newName && $diff->newName != $diff->name) {
904
                $renamedTable = $diff->getNewName();
905
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
906
            }
907
908
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
909
        }
910
911
        return array_merge($sql, $tableSql, $columnSql);
912
    }
913
914
    /**
915
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
916
     *
917
     * @return array|bool
918
     */
919
    private function getSimpleAlterTableSQL(TableDiff $diff)
920
    {
921
        // Suppress changes on integer type autoincrement columns.
922
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
923
            if ( ! $columnDiff->fromColumn instanceof Column ||
924
                ! $columnDiff->column instanceof Column ||
925
                ! $columnDiff->column->getAutoincrement() ||
926
                ! $columnDiff->column->getType() instanceof Types\IntegerType
927
            ) {
928
                continue;
929
            }
930
931
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
932
                unset($diff->changedColumns[$oldColumnName]);
933
934
                continue;
935
            }
936
937
            $fromColumnType = $columnDiff->fromColumn->getType();
938
939
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
940
                unset($diff->changedColumns[$oldColumnName]);
941
            }
942
        }
943
944
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
945
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
946
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
947
                || ! empty($diff->renamedIndexes)
948
        ) {
949
            return false;
950
        }
951
952
        $table = new Table($diff->name);
953
954
        $sql = [];
955
        $tableSql = [];
956
        $columnSql = [];
957
958
        foreach ($diff->addedColumns as $column) {
959
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
960
                continue;
961
            }
962
963
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
964
            $type = $field['type'];
965
            switch (true) {
966
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
967
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
968
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
969
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
970
                    return false;
971
            }
972
973
            $field['name'] = $column->getQuotedName($this);
974
            if ($type instanceof Types\StringType && $field['length'] === null) {
975
                $field['length'] = 255;
976
            }
977
978
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
979
        }
980
981
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
982
            if ($diff->newName !== false) {
983
                $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
                $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
985
            }
986
        }
987
988
        return array_merge($sql, $tableSql, $columnSql);
989
    }
990
991
    /**
992
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
993
     *
994
     * @return array
995
     */
996
    private function getColumnNamesInAlteredTable(TableDiff $diff)
997
    {
998
        $columns = [];
999
1000
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
1001
            $columns[strtolower($columnName)] = $column->getName();
1002
        }
1003
1004
        foreach ($diff->removedColumns as $columnName => $column) {
1005
            $columnName = strtolower($columnName);
1006
            if (isset($columns[$columnName])) {
1007
                unset($columns[$columnName]);
1008
            }
1009
        }
1010
1011
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1012
            $columnName = $column->getName();
1013
            $columns[strtolower($oldColumnName)] = $columnName;
1014
            $columns[strtolower($columnName)] = $columnName;
1015
        }
1016
1017
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1018
            $columnName = $columnDiff->column->getName();
1019
            $columns[strtolower($oldColumnName)] = $columnName;
1020
            $columns[strtolower($columnName)] = $columnName;
1021
        }
1022
1023
        foreach ($diff->addedColumns as $columnName => $column) {
1024
            $columns[strtolower($columnName)] = $columnName;
1025
        }
1026
1027
        return $columns;
1028
    }
1029
1030
    /**
1031
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1032
     *
1033
     * @return \Doctrine\DBAL\Schema\Index[]
1034
     */
1035
    private function getIndexesInAlteredTable(TableDiff $diff)
1036
    {
1037
        $indexes = $diff->fromTable->getIndexes();
1038
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1039
1040
        foreach ($indexes as $key => $index) {
1041
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1042
                if (strtolower($key) === strtolower($oldIndexName)) {
1043
                    unset($indexes[$key]);
1044
                }
1045
            }
1046
1047
            $changed = false;
1048
            $indexColumns = [];
1049
            foreach ($index->getColumns() as $columnName) {
1050
                $normalizedColumnName = strtolower($columnName);
1051
                if ( ! isset($columnNames[$normalizedColumnName])) {
1052
                    unset($indexes[$key]);
1053
                    continue 2;
1054
                } else {
1055
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1056
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1057
                        $changed = true;
1058
                    }
1059
                }
1060
            }
1061
1062
            if ($changed) {
1063
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1064
            }
1065
        }
1066
1067
        foreach ($diff->removedIndexes as $index) {
1068
            $indexName = strtolower($index->getName());
1069
            if (strlen($indexName) && isset($indexes[$indexName])) {
1070
                unset($indexes[$indexName]);
1071
            }
1072
        }
1073
1074
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1075
            $indexName = strtolower($index->getName());
1076
            if (strlen($indexName)) {
1077
                $indexes[$indexName] = $index;
1078
            } else {
1079
                $indexes[] = $index;
1080
            }
1081
        }
1082
1083
        return $indexes;
1084
    }
1085
1086
    /**
1087
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1088
     *
1089
     * @return array
1090
     */
1091
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1092
    {
1093
        $foreignKeys = $diff->fromTable->getForeignKeys();
1094
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1095
1096
        foreach ($foreignKeys as $key => $constraint) {
1097
            $changed = false;
1098
            $localColumns = [];
1099
            foreach ($constraint->getLocalColumns() as $columnName) {
1100
                $normalizedColumnName = strtolower($columnName);
1101
                if ( ! isset($columnNames[$normalizedColumnName])) {
1102
                    unset($foreignKeys[$key]);
1103
                    continue 2;
1104
                } else {
1105
                    $localColumns[] = $columnNames[$normalizedColumnName];
1106
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1107
                        $changed = true;
1108
                    }
1109
                }
1110
            }
1111
1112
            if ($changed) {
1113
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1114
            }
1115
        }
1116
1117
        foreach ($diff->removedForeignKeys as $constraint) {
1118
            $constraintName = strtolower($constraint->getName());
1119
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
1120
                unset($foreignKeys[$constraintName]);
1121
            }
1122
        }
1123
1124
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1125
            $constraintName = strtolower($constraint->getName());
1126
            if (strlen($constraintName)) {
1127
                $foreignKeys[$constraintName] = $constraint;
1128
            } else {
1129
                $foreignKeys[] = $constraint;
1130
            }
1131
        }
1132
1133
        return $foreignKeys;
1134
    }
1135
1136
    /**
1137
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1138
     *
1139
     * @return array
1140
     */
1141
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1142
    {
1143
        $primaryIndex = [];
1144
1145
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1146
            if ($index->isPrimary()) {
1147
                $primaryIndex = [$index->getName() => $index];
1148
            }
1149
        }
1150
1151
        return $primaryIndex;
1152
    }
1153
}
1154