Completed
Push — 2.11.x ( 0a2e2f...a8544c )
by Grégoire
23s queued 16s
created

SqlitePlatform   F

Complexity

Total Complexity 212

Size/Duplication

Total Lines 1183
Duplicated Lines 0 %

Test Coverage

Coverage 90.6%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 212
eloc 427
dl 0
loc 1183
ccs 405
cts 447
cp 0.906
rs 2
c 4
b 0
f 0

70 Methods

Rating   Name   Duplication   Size   Complexity  
A getDateDiffExpression() 0 3 1
A getRegexpExpression() 0 3 1
A getGuidExpression() 0 6 1
A getIntegerTypeDeclarationSQL() 0 3 1
A getBooleanTypeDeclarationSQL() 0 3 1
A prefersIdentityColumns() 0 3 1
A getSetTransactionIsolationSQL() 0 3 1
A getBigIntTypeDeclarationSQL() 0 8 2
A getNowExpression() 0 12 4
A getCreateTableSQL() 0 5 1
A getCreateConstraintSQL() 0 3 1
B getColumnNamesInAlteredTable() 0 35 7
A getListTableForeignKeysSQL() 0 5 1
D getSimpleAlterTableSQL() 0 72 35
A supportsCreateDropForeignKeyConstraints() 0 3 1
A getPrimaryIndexInAlteredTable() 0 13 3
C getForeignKeysInAlteredTable() 0 53 12
C getIndexesInAlteredTable() 0 57 13
A getCreatePrimaryKeySQL() 0 3 1
F getAlterTableSQL() 0 118 19
A getDropForeignKeySQL() 0 3 1
A getCreateForeignKeySQL() 0 3 1
A getListViewsSQL() 0 3 1
A getName() 0 3 1
A getTinyIntTypeDeclarationSql() 0 8 2
A udfSqrt() 0 3 1
A udfLocate() 0 15 3
A getBlobTypeDeclarationSQL() 0 3 1
A getTrimExpression() 0 18 4
A getBinaryDefaultLength() 0 3 1
A supportsForeignKeyConstraints() 0 3 1
A getNonAutoincrementPrimaryKeyDefinition() 0 15 4
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A getReservedKeywordsClass() 0 3 1
A getInlineColumnCommentSQL() 0 3 1
A getForUpdateSQL() 0 3 1
A getListTableColumnsSQL() 0 5 1
A getMediumIntTypeDeclarationSql() 0 8 2
A getTemporaryTableName() 0 5 1
A getDateTypeDeclarationSQL() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 3 1
A getListTablesSQL() 0 5 1
A getClobTypeDeclarationSQL() 0 3 1
A getListTableIndexesSQL() 0 5 1
A getPreAlterTableIndexForeignKeySQL() 0 16 4
A getCreateViewSQL() 0 3 1
C _getCreateTableSQL() 0 45 15
A getSmallIntTypeDeclarationSQL() 0 8 2
A getForeignKeyDeclarationSQL() 0 8 1
A supportsColumnCollation() 0 3 1
A getInlineTableCommentSQL() 0 3 1
A getSubstringExpression() 0 7 2
A doModifyLimitQuery() 0 7 3
A getTruncateTableSQL() 0 6 1
A getBinaryMaxLength() 0 3 1
A getLocateExpression() 0 7 2
A udfMod() 0 3 1
B getDateArithmeticIntervalExpression() 0 26 7
A getVarcharTypeDeclarationSQLSnippet() 0 4 4
A getAdvancedForeignKeyOptionsSQL() 0 8 5
A _getTransactionIsolationLevelSQL() 0 13 5
A getTimeTypeDeclarationSQL() 0 3 1
A canEmulateSchemas() 0 3 1
A getPostAlterTableIndexForeignKeySQL() 0 22 5
A initializeDoctrineTypeMappings() 0 35 1
A _getCommonIntegerTypeDeclarationSQL() 0 8 3
A supportsIdentityColumns() 0 3 1
A getListTableConstraintsSQL() 0 7 1
A getDropViewSQL() 0 3 1
A supportsInlineColumnComments() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SqlitePlatform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SqlitePlatform, and based on these observations, apply Extract Interface, too.

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

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

1033
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1034 185
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1035
            }
1036
        }
1037
1038 1440
        return array_merge($sql, $tableSql, $columnSql);
1039
    }
1040
1041
    /**
1042
     * @return string[]
1043
     */
1044 1340
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1045
    {
1046 1340
        $columns = [];
1047
1048 1340
        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

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