Completed
Pull Request — master (#3512)
by David
16:25
created

SqlitePlatform::_getCreateTableSQL()   C

Complexity

Conditions 15
Paths 40

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 16.0422

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 45
ccs 20
cts 24
cp 0.8333
rs 5.9166
c 0
b 0
f 0
cc 15
nc 40
nop 3
crap 16.0422

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

1014
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1015 183
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1016
            }
1017
        }
1018
1019 1649
        return array_merge($sql, $tableSql, $columnSql);
1020
    }
1021
1022
    /**
1023
     * @return string[]
1024
     */
1025 1553
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1026
    {
1027 1553
        $columns = [];
1028
1029 1553
        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

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