Failed Conditions
Pull Request — 3.0.x (#3980)
by Guilherme
30:17 queued 13s
created

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

826
    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...
827
    {
828 23
        $table = str_replace('.', '__', $table);
829
830 23
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
831
    }
832
833
    /**
834
     * {@inheritDoc}
835
     */
836 349
    public function getAlterTableSQL(TableDiff $diff)
837
    {
838 349
        $sql = $this->getSimpleAlterTableSQL($diff);
839 349
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
840 28
            return $sql;
841
        }
842
843 321
        $fromTable = $diff->fromTable;
844 321
        if (! $fromTable instanceof Table) {
845 44
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
846
        }
847
848 277
        $table = clone $fromTable;
849
850 277
        $columns        = [];
851 277
        $oldColumnNames = [];
852 277
        $newColumnNames = [];
853 277
        $columnSql      = [];
854
855 277
        foreach ($table->getColumns() as $columnName => $column) {
856 255
            $columnName                  = strtolower($columnName);
857 255
            $columns[$columnName]        = $column;
858 255
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
859
        }
860
861 277
        foreach ($diff->removedColumns as $columnName => $column) {
862 89
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
863
                continue;
864
            }
865
866 89
            $columnName = strtolower($columnName);
867 89
            if (! isset($columns[$columnName])) {
868
                continue;
869
            }
870
871
            unset(
872 89
                $columns[$columnName],
873 89
                $oldColumnNames[$columnName],
874 89
                $newColumnNames[$columnName]
875
            );
876
        }
877
878 277
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
879 110
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
880
                continue;
881
            }
882
883 110
            $oldColumnName = strtolower($oldColumnName);
884 110
            if (isset($columns[$oldColumnName])) {
885 110
                unset($columns[$oldColumnName]);
886
            }
887
888 110
            $columns[strtolower($column->getName())] = $column;
889
890 110
            if (! isset($newColumnNames[$oldColumnName])) {
891
                continue;
892
            }
893
894 110
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
895
        }
896
897 277
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
898 144
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
899
                continue;
900
            }
901
902 144
            if (isset($columns[$oldColumnName])) {
903 122
                unset($columns[$oldColumnName]);
904
            }
905
906 144
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
907
908 144
            if (! isset($newColumnNames[$oldColumnName])) {
909 22
                continue;
910
            }
911
912 122
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
913
        }
914
915 277
        foreach ($diff->addedColumns as $columnName => $column) {
916 67
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
917
                continue;
918
            }
919
920 67
            $columns[strtolower($columnName)] = $column;
921
        }
922
923 277
        $sql      = [];
924 277
        $tableSql = [];
925 277
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
926 277
            $dataTable = new Table('__temp__' . $table->getName());
927
928 277
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), [], $this->getForeignKeysInAlteredTable($diff), $table->getOptions());
929 277
            $newTable->addOption('alter', true);
930
931 277
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
932
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
933 277
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
934 277
            $sql[] = $this->getDropTableSQL($fromTable);
935
936 277
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
937 277
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
938 277
            $sql[] = $this->getDropTableSQL($dataTable);
939
940 277
            $newName = $diff->getNewName();
941
942 277
            if ($newName !== false) {
943 66
                $sql[] = sprintf(
944 3
                    'ALTER TABLE %s RENAME TO %s',
945 66
                    $newTable->getQuotedName($this),
946 66
                    $newName->getQuotedName($this)
947
                );
948
            }
949
950 277
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
951
        }
952
953 277
        return array_merge($sql, $tableSql, $columnSql);
954
    }
955
956
    /**
957
     * @return string[]|false
958
     */
959 349
    private function getSimpleAlterTableSQL(TableDiff $diff)
960
    {
961
        // Suppress changes on integer type autoincrement columns.
962 349
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
963 149
            if ($columnDiff->fromColumn === null ||
964 145
                ! $columnDiff->column->getAutoincrement() ||
965 149
                ! $columnDiff->column->getType() instanceof Types\IntegerType
966
            ) {
967 144
                continue;
968
            }
969
970 5
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
971 1
                unset($diff->changedColumns[$oldColumnName]);
972
973 1
                continue;
974
            }
975
976 4
            $fromColumnType = $columnDiff->fromColumn->getType();
977
978 4
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
979
                continue;
980
            }
981
982 4
            unset($diff->changedColumns[$oldColumnName]);
983
        }
984
985 349
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
986 344
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
987 340
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
988 349
                || ! empty($diff->renamedIndexes)
989
        ) {
990 277
            return false;
991
        }
992
993 72
        $table = new Table($diff->name);
994
995 72
        $sql       = [];
996 72
        $tableSql  = [];
997 72
        $columnSql = [];
998
999 72
        foreach ($diff->addedColumns as $column) {
1000 66
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
1001
                continue;
1002
            }
1003
1004 66
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
1005 66
            $type  = $field['type'];
1006
            switch (true) {
1007 66
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
1008 44
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
1009 44
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
1010 22
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
1011 44
                    return false;
1012
            }
1013
1014 22
            $field['name'] = $column->getQuotedName($this);
1015 22
            if ($type instanceof Types\StringType && $field['length'] === null) {
1016 22
                $field['length'] = 255;
1017
            }
1018
1019 22
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
1020
        }
1021
1022 28
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
1023 28
            if ($diff->newName !== false) {
1024 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

1024
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1025 1
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1026
            }
1027
        }
1028
1029 28
        return array_merge($sql, $tableSql, $columnSql);
1030
    }
1031
1032
    /**
1033
     * @return string[]
1034
     */
1035 277
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1036
    {
1037 277
        $columns = [];
1038
1039 277
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
0 ignored issues
show
Bug introduced by
The method getColumns() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1039
        foreach ($diff->fromTable->/** @scrutinizer ignore-call */ getColumns() as $columnName => $column) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1040 255
            $columns[strtolower($columnName)] = $column->getName();
1041
        }
1042
1043 277
        foreach ($diff->removedColumns as $columnName => $column) {
1044 89
            $columnName = strtolower($columnName);
1045 89
            if (! isset($columns[$columnName])) {
1046
                continue;
1047
            }
1048
1049 89
            unset($columns[$columnName]);
1050
        }
1051
1052 277
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1053 110
            $columnName                          = $column->getName();
1054 110
            $columns[strtolower($oldColumnName)] = $columnName;
1055 110
            $columns[strtolower($columnName)]    = $columnName;
1056
        }
1057
1058 277
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1059 144
            $columnName                          = $columnDiff->column->getName();
1060 144
            $columns[strtolower($oldColumnName)] = $columnName;
1061 144
            $columns[strtolower($columnName)]    = $columnName;
1062
        }
1063
1064 277
        foreach ($diff->addedColumns as $column) {
1065 67
            $columnName                       = $column->getName();
1066 67
            $columns[strtolower($columnName)] = $columnName;
1067
        }
1068
1069 277
        return $columns;
1070
    }
1071
1072
    /**
1073
     * @return Index[]
1074
     */
1075 277
    private function getIndexesInAlteredTable(TableDiff $diff)
1076
    {
1077 277
        $indexes     = $diff->fromTable->getIndexes();
1078 277
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1079
1080 277
        foreach ($indexes as $key => $index) {
1081 135
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1082 67
                if (strtolower($key) !== strtolower($oldIndexName)) {
1083 67
                    continue;
1084
                }
1085
1086 23
                unset($indexes[$key]);
1087
            }
1088
1089 135
            $changed      = false;
1090 135
            $indexColumns = [];
1091 135
            foreach ($index->getColumns() as $columnName) {
1092 135
                $normalizedColumnName = strtolower($columnName);
1093 135
                if (! isset($columnNames[$normalizedColumnName])) {
1094 22
                    unset($indexes[$key]);
1095 22
                    continue 2;
1096
                }
1097
1098 135
                $indexColumns[] = $columnNames[$normalizedColumnName];
1099 135
                if ($columnName === $columnNames[$normalizedColumnName]) {
1100 135
                    continue;
1101
                }
1102
1103 22
                $changed = true;
1104
            }
1105
1106 135
            if (! $changed) {
1107 135
                continue;
1108
            }
1109
1110 22
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1111
        }
1112
1113 277
        foreach ($diff->removedIndexes as $index) {
1114 23
            $indexName = strtolower($index->getName());
1115 23
            if (strlen($indexName) === 0 || ! isset($indexes[$indexName])) {
1116
                continue;
1117
            }
1118
1119 23
            unset($indexes[$indexName]);
1120
        }
1121
1122 277
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1123 67
            $indexName = strtolower($index->getName());
1124 67
            if (strlen($indexName) > 0) {
1125 67
                $indexes[$indexName] = $index;
1126
            } else {
1127
                $indexes[] = $index;
1128
            }
1129
        }
1130
1131 277
        return $indexes;
1132
    }
1133
1134
    /**
1135
     * @return ForeignKeyConstraint[]
1136
     */
1137 277
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1138
    {
1139 277
        $foreignKeys = $diff->fromTable->getForeignKeys();
1140 277
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1141
1142 277
        foreach ($foreignKeys as $key => $constraint) {
1143 66
            $changed      = false;
1144 66
            $localColumns = [];
1145 66
            foreach ($constraint->getLocalColumns() as $columnName) {
1146 66
                $normalizedColumnName = strtolower($columnName);
1147 66
                if (! isset($columnNames[$normalizedColumnName])) {
1148 22
                    unset($foreignKeys[$key]);
1149 22
                    continue 2;
1150
                }
1151
1152 66
                $localColumns[] = $columnNames[$normalizedColumnName];
1153 66
                if ($columnName === $columnNames[$normalizedColumnName]) {
1154 66
                    continue;
1155
                }
1156
1157 22
                $changed = true;
1158
            }
1159
1160 66
            if (! $changed) {
1161 66
                continue;
1162
            }
1163
1164 22
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1165
        }
1166
1167 277
        foreach ($diff->removedForeignKeys as $constraint) {
1168 22
            if (! $constraint instanceof ForeignKeyConstraint) {
1169
                $constraint = new Identifier($constraint);
1170
            }
1171
1172 22
            $constraintName = strtolower($constraint->getName());
1173 22
            if (strlen($constraintName) === 0 || ! isset($foreignKeys[$constraintName])) {
1174
                continue;
1175
            }
1176
1177 22
            unset($foreignKeys[$constraintName]);
1178
        }
1179
1180 277
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1181 23
            $constraintName = strtolower($constraint->getName());
1182 23
            if (strlen($constraintName) > 0) {
1183 22
                $foreignKeys[$constraintName] = $constraint;
1184
            } else {
1185 1
                $foreignKeys[] = $constraint;
1186
            }
1187
        }
1188
1189 277
        return $foreignKeys;
1190
    }
1191
1192
    /**
1193
     * @return Index[]
1194
     */
1195 277
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1196
    {
1197 277
        $primaryIndex = [];
1198
1199 277
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1200 135
            if (! $index->isPrimary()) {
1201 111
                continue;
1202
            }
1203
1204 91
            $primaryIndex = [$index->getName() => $index];
1205
        }
1206
1207 277
        return $primaryIndex;
1208
    }
1209
}
1210