Failed Conditions
Pull Request — develop (#3348)
by Sergei
125:02 queued 59:58
created

SqlitePlatform   F

Complexity

Total Complexity 206

Size/Duplication

Total Lines 1138
Duplicated Lines 0 %

Test Coverage

Coverage 91.03%

Importance

Changes 0
Metric Value
wmc 206
eloc 426
dl 0
loc 1138
ccs 396
cts 435
cp 0.9103
rs 2
c 0
b 0
f 0

67 Methods

Rating   Name   Duplication   Size   Complexity  
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 getBinaryDefaultLength() 0 3 1
A getForUpdateSql() 0 3 1
A getNonAutoincrementPrimaryKeyDefinition() 0 15 4
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A getListTableColumnsSQL() 0 5 1
A getRegexpExpression() 0 3 1
A getMediumIntTypeDeclarationSql() 0 8 2
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 getNowExpression() 0 10 4
A getCreateViewSQL() 0 3 1
A getIntegerTypeDeclarationSQL() 0 3 1
C _getCreateTableSQL() 0 38 14
A getBooleanTypeDeclarationSQL() 0 3 1
A getSmallIntTypeDeclarationSQL() 0 8 2
A getForeignKeyDeclarationSQL() 0 8 1
A supportsColumnCollation() 0 3 1
A getTruncateTableSQL() 0 6 1
A getBinaryMaxLength() 0 3 1
A prefersIdentityColumns() 0 3 1
A udfMod() 0 3 1
A getVarcharTypeDeclarationSQLSnippet() 0 5 4
A getAdvancedForeignKeyOptionsSQL() 0 8 5
A getDateDiffExpression() 0 3 1
A getTimeTypeDeclarationSQL() 0 3 1
A getSetTransactionIsolationSQL() 0 3 1
A _getCommonIntegerTypeDeclarationSQL() 0 8 3
A supportsIdentityColumns() 0 3 1
A getListTableConstraintsSQL() 0 7 1
A getBigIntTypeDeclarationSQL() 0 8 2
A getDropViewSQL() 0 3 1
A supportsInlineColumnComments() 0 3 1
A getCreateTableSQL() 0 3 1
A getBlobTypeDeclarationSQL() 0 3 1
A getReservedKeywordsClass() 0 3 1
A getInlineColumnCommentSQL() 0 7 3
A getPreAlterTableIndexForeignKeySQL() 0 16 4
A getPostAlterTableIndexForeignKeySQL() 0 22 5
A initializeDoctrineTypeMappings() 0 36 1
A getCreateConstraintSQL() 0 3 1
A supportsForeignKeyConstraints() 0 3 1
A getTemporaryTableName() 0 5 1
B getColumnNamesInAlteredTable() 0 35 7
A getListTableForeignKeysSQL() 0 5 1
D getSimpleAlterTableSQL() 0 72 35
A doModifyLimitQuery() 0 7 3
A getCreatePrimaryKeySQL() 0 3 1
F getAlterTableSQL() 0 119 19
A getDropForeignKeySQL() 0 3 1
A _getTransactionIsolationLevelSQL() 0 11 5
A canEmulateSchemas() 0 3 1
A getCreateForeignKeySQL() 0 3 1
B getTrimExpression() 0 32 6
A getPrimaryIndexInAlteredTable() 0 13 3
B getForeignKeysInAlteredTable() 0 55 11
A getSubstringExpression() 0 7 2
C getIndexesInAlteredTable() 0 59 12
A getLocateExpression() 0 7 2
A getDateArithmeticIntervalExpression() 0 19 3

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 InvalidArgumentException;
16
use function array_merge;
17
use function array_unique;
18
use function array_values;
19
use function implode;
20
use function sprintf;
21
use function sqrt;
22
use function str_replace;
23
use function strpos;
24
use function strtolower;
25
26
/**
27
 * The SqlitePlatform class describes the specifics and dialects of the SQLite
28
 * database platform.
29
 *
30
 * @todo   Rename: SQLitePlatform
31
 */
32
class SqlitePlatform extends AbstractPlatform
33
{
34
    /**
35
     * {@inheritDoc}
36
     */
37
    public function getRegexpExpression() : string
38 1801
    {
39
        return 'REGEXP';
40 1801
    }
41
42
    /**
43
     * {@inheritDoc}
44
     */
45
    public function getNowExpression($type = 'timestamp') : string
46
    {
47
        switch ($type) {
48
            case 'time':
49
                return 'time(\'now\')';
50
            case 'date':
51
                return 'date(\'now\')';
52
            case 'timestamp':
53
            default:
54
                return 'datetime(\'now\')';
55
        }
56
    }
57
58
    /**
59
     * {@inheritDoc}
60
     */
61
    public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED, ?string $char = null) : string
62 253
    {
63
        switch ($mode) {
64 253
            case TrimMode::UNSPECIFIED:
65
            case TrimMode::BOTH:
66 253
                $trimFn = 'TRIM';
67
                break;
68 252
69 252
            case TrimMode::LEADING:
70
                $trimFn = 'LTRIM';
71
                break;
72 251
73 251
            case TrimMode::TRAILING:
74
                $trimFn = 'RTRIM';
75
                break;
76 253
77
            default:
78
                throw new InvalidArgumentException(
79 253
                    sprintf(
80
                        'The value of $mode is expected to be one of the TrimMode constants, %d given',
81
                        $mode
82
                    )
83
                );
84
        }
85
86
        $arguments = [$str];
87 1801
88
        if ($char !== null) {
89 1801
            $arguments[] = $char;
90 1801
        }
91
92
        return sprintf('%s(%s)', $trimFn, implode(', ', $arguments));
93 1801
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
99 226
    {
100
        if ($length === null) {
101 226
            return sprintf('SUBSTR(%s, %s)', $string, $start);
102 226
        }
103
104
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
105 226
    }
106
107
    /**
108
     * {@inheritDoc}
109
     */
110
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
111 1403
    {
112
        if ($start === null) {
113 1403
            return sprintf('LOCATE(%s, %s)', $string, $substring);
114
        }
115
116
        return sprintf('LOCATE(%s, %s, %s)', $string, $substring, $start);
117 228
    }
118
119
    /**
120 1403
     * {@inheritdoc}
121
     */
122 228
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
123 228
    {
124 228
        switch ($unit) {
125
            case DateIntervalUnit::WEEK:
126
                $interval = $this->multiplyInterval($interval, 7);
127 228
                $unit     = DateIntervalUnit::DAY;
128 228
                break;
129 228
130
            case DateIntervalUnit::QUARTER:
131
                $interval = $this->multiplyInterval($interval, 3);
132 1403
                $unit     = DateIntervalUnit::MONTH;
133 1378
                break;
134
        }
135
136 1403
        return 'DATETIME(' . $date . ',' . $this->getConcatExpression(
137
            $this->quoteStringLiteral($operator),
138
            $interval,
139
            $this->quoteStringLiteral(' ' . $unit)
140
        ) . ')';
141
    }
142
143 201
    /**
144
     * {@inheritDoc}
145 201
     */
146
    public function getDateDiffExpression(string $date1, string $date2) : string
147
    {
148
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
149
    }
150
151 1777
    /**
152
     * {@inheritDoc}
153 1
     */
154 1776
    protected function _getTransactionIsolationLevelSQL(int $level) : string
155 1777
    {
156 1776
        switch ($level) {
157 1776
            case TransactionIsolationLevel::READ_UNCOMMITTED:
158 1776
                return 0;
159 1777
            case TransactionIsolationLevel::READ_COMMITTED:
160
            case TransactionIsolationLevel::REPEATABLE_READ:
161
            case TransactionIsolationLevel::SERIALIZABLE:
162
                return 1;
163
            default:
164
                return parent::_getTransactionIsolationLevelSQL($level);
165
        }
166
    }
167
168 1777
    /**
169
     * {@inheritDoc}
170 1777
     */
171
    public function getSetTransactionIsolationSQL(int $level) : string
172
    {
173
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
174
    }
175
176 1762
    /**
177
     * {@inheritDoc}
178 1762
     */
179
    public function prefersIdentityColumns() : bool
180
    {
181
        return true;
182
    }
183
184 965
    /**
185
     * {@inheritDoc}
186 965
     */
187
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
188
    {
189
        return 'BOOLEAN';
190
    }
191
192 1941
    /**
193
     * {@inheritDoc}
194 1941
     */
195
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
196
    {
197
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
198
    }
199
200 1733
    /**
201
     * {@inheritDoc}
202
     */
203 1733
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
204 1733
    {
205
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
206
        if (! empty($columnDef['autoincrement'])) {
207 1673
            return $this->getIntegerTypeDeclarationSQL($columnDef);
208
        }
209
210
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
211
    }
212
213 1706
    /**
214
     * {@inheritDoc}
215
     */
216 1706
    public function getTinyIntTypeDeclarationSql(array $field) : string
217 1706
    {
218
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
219
        if (! empty($field['autoincrement'])) {
220 1705
            return $this->getIntegerTypeDeclarationSQL($field);
221
        }
222
223
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
224
    }
225
226 1783
    /**
227
     * {@inheritDoc}
228
     */
229 1783
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
230 1783
    {
231
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
232
        if (! empty($columnDef['autoincrement'])) {
233 1760
            return $this->getIntegerTypeDeclarationSQL($columnDef);
234
        }
235
236
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
237
    }
238
239 1657
    /**
240
     * {@inheritDoc}
241
     */
242 1657
    public function getMediumIntTypeDeclarationSql(array $field) : string
243 1657
    {
244
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
245
        if (! empty($field['autoincrement'])) {
246 1657
            return $this->getIntegerTypeDeclarationSQL($field);
247
        }
248
249
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
250
    }
251
252 254
    /**
253
     * {@inheritDoc}
254 254
     */
255
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
256
    {
257
        return 'DATETIME';
258
    }
259
260 227
    /**
261
     * {@inheritDoc}
262 227
     */
263
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
264
    {
265
        return 'DATE';
266
    }
267
268 164
    /**
269
     * {@inheritDoc}
270 164
     */
271
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
272
    {
273
        return 'TIME';
274
    }
275
276 1941
    /**
277
     * {@inheritDoc}
278
     */
279 1941
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
280 1881
    {
281
        // sqlite autoincrement is only possible for the primary key
282
        if (! empty($columnDef['autoincrement'])) {
283 1915
            return ' PRIMARY KEY AUTOINCREMENT';
284
        }
285
286
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
287
    }
288
289 1495
    /**
290
     * {@inheritDoc}
291 1495
     */
292 1495
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
293 1495
    {
294 1495
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
295 1495
            $foreignKey->getQuotedLocalColumns($this),
296 1495
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
297
            $foreignKey->getQuotedForeignColumns($this),
298
            $foreignKey->getName(),
299
            $foreignKey->getOptions()
300
        ));
301
    }
302
303 1714
    /**
304
     * {@inheritDoc}
305 1714
     */
306 1714
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
307
    {
308 1714
        $tableName   = str_replace('.', '__', $tableName);
309
        $queryFields = $this->getColumnDeclarationListSQL($columns);
310
311
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
312
            foreach ($options['uniqueConstraints'] as $name => $definition) {
313
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
314 1714
            }
315
        }
316 1714
317 1713
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
318 1495
319
        if (isset($options['foreignKeys'])) {
320
            foreach ($options['foreignKeys'] as $foreignKey) {
321
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
322 1714
            }
323
        }
324 1714
325 1479
        $query = ['CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'];
326
327
        if (isset($options['alter']) && $options['alter'] === true) {
328 1702
            return $query;
329 1558
        }
330 1558
331
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
332
            foreach ($options['indexes'] as $indexDef) {
333
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
334 1702
            }
335
        }
336
337
        if (isset($options['unique']) && ! empty($options['unique'])) {
338
            foreach ($options['unique'] as $indexDef) {
339
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
340 1702
            }
341
        }
342
343
        return $query;
344
    }
345
346
    /**
347
     * Generate a PRIMARY KEY definition if no autoincrement value is used
348
     *
349 1714
     * @param mixed[][] $columns
350
     * @param mixed[]   $options
351 1714
     */
352 1317
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
353
    {
354
        if (empty($options['primary'])) {
355 1700
            return '';
356
        }
357 1700
358 1700
        $keyColumns = array_unique(array_values($options['primary']));
359 1654
360
        foreach ($keyColumns as $keyColumn) {
361
            if (! empty($columns[$keyColumn]['autoincrement'])) {
362
                return '';
363 1605
            }
364
        }
365
366
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
367
    }
368
369 1811
    /**
370
     * {@inheritDoc}
371 1811
     */
372 1743
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length, bool $fixed) : string
373 1811
    {
374
        return $fixed
375
            ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
376
            : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
377
    }
378
379 1464
    /**
380
     * {@inheritdoc}
381 1464
     */
382
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length, bool $fixed) : string
383
    {
384
        return 'BLOB';
385
    }
386
387 505
    /**
388
     * {@inheritdoc}
389 505
     */
390
    public function getBinaryMaxLength() : int
391
    {
392
        return 0;
393
    }
394
395 1465
    /**
396
     * {@inheritdoc}
397 1465
     */
398
    public function getBinaryDefaultLength() : int
399
    {
400
        return 0;
401
    }
402
403 746
    /**
404
     * {@inheritDoc}
405 746
     */
406
    public function getClobTypeDeclarationSQL(array $field) : string
407
    {
408
        return 'CLOB';
409
    }
410
411 1297
    /**
412
     * {@inheritDoc}
413 1297
     */
414
    public function getListTableConstraintsSQL(string $table) : string
415 1297
    {
416 1
        $table = str_replace('.', '__', $table);
417 1297
418
        return sprintf(
419
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
420
            $this->quoteStringLiteral($table)
421
        );
422
    }
423
424 1405
    /**
425
     * {@inheritDoc}
426 1405
     */
427
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
428 1405
    {
429
        $table = str_replace('.', '__', $table);
430
431
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
432
    }
433
434 1382
    /**
435
     * {@inheritDoc}
436 1382
     */
437
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
438 1382
    {
439
        $table = str_replace('.', '__', $table);
440
441
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
442
    }
443
444 304
    /**
445
     * {@inheritDoc}
446
     */
447
    public function getListTablesSQL() : string
448 304
    {
449
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
450
             . 'UNION ALL SELECT name FROM sqlite_temp_master '
451
             . "WHERE type = 'table' ORDER BY name";
452
    }
453
454 155
    /**
455
     * {@inheritDoc}
456 155
     */
457
    public function getListViewsSQL(?string $database) : string
458
    {
459
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
460
    }
461
462 155
    /**
463
     * {@inheritDoc}
464 155
     */
465
    public function getCreateViewSQL(string $name, string $sql) : string
466
    {
467
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
468
    }
469
470 155
    /**
471
     * {@inheritDoc}
472 155
     */
473
    public function getDropViewSQL(string $name) : string
474
    {
475
        return 'DROP VIEW ' . $name;
476
    }
477
478 1495
    /**
479
     * {@inheritDoc}
480 1495
     */
481
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
482 1495
    {
483 1495
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
484
485 1495
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
486
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
487
488
        return $query;
489
    }
490
491 154
    /**
492
     * {@inheritDoc}
493 154
     */
494
    public function supportsIdentityColumns() : bool
495
    {
496
        return true;
497
    }
498
499 1306
    /**
500
     * {@inheritDoc}
501 1306
     */
502
    public function supportsColumnCollation() : bool
503
    {
504
        return true;
505
    }
506
507 1718
    /**
508
     * {@inheritDoc}
509 1718
     */
510
    public function supportsInlineColumnComments() : bool
511
    {
512
        return true;
513
    }
514
515 2014
    /**
516
     * {@inheritDoc}
517 2014
     */
518
    public function getName() : string
519
    {
520
        return 'sqlite';
521
    }
522
523 792
    /**
524
     * {@inheritDoc}
525 792
     */
526 792
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
527
    {
528 792
        $tableIdentifier = new Identifier($tableName);
529
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
530
531
        return 'DELETE FROM ' . $tableName;
532
    }
533
534
    /**
535
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
536
     *
537
     * @param int|float $value
538
     */
539
    public static function udfSqrt($value) : float
540
    {
541
        return sqrt($value);
542
    }
543
544
    /**
545
     * User-defined function for Sqlite that implements MOD(a, b).
546
     */
547
    public static function udfMod(int $a, int $b) : int
548
    {
549
        return $a % $b;
550
    }
551
552
    public static function udfLocate(string $str, string $substr, int $offset = 0) : int
553
    {
554
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
555
        // So we have to make them compatible if an offset is given.
556
        if ($offset > 0) {
557
            $offset -= 1;
558
        }
559
560
        $pos = strpos($str, $substr, $offset);
561
562
        if ($pos !== false) {
563 226
            return $pos + 1;
564
        }
565
566
        return 0;
567 226
    }
568 226
569
    /**
570
     * {@inheritDoc}
571 226
     */
572
    public function getForUpdateSql() : string
573 226
    {
574 226
        return '';
575
    }
576
577 226
    /**
578
     * {@inheritDoc}
579
     */
580
    public function getInlineColumnCommentSQL(?string $comment) : string
581
    {
582
        if ($comment === null || $comment === '') {
583
            return '';
584
        }
585
586
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
587
    }
588
589
    /**
590
     * {@inheritDoc}
591 572
     */
592
    protected function initializeDoctrineTypeMappings() : void
593 572
    {
594
        $this->doctrineTypeMapping = [
595
            'bigint'           => 'bigint',
596
            'bigserial'        => 'bigint',
597
            'blob'             => 'blob',
598
            'boolean'          => 'boolean',
599 1201
            'char'             => 'string',
600
            'clob'             => 'text',
601 1201
            'date'             => 'date',
602
            'datetime'         => 'datetime',
603
            'decimal'          => 'decimal',
604
            'double'           => 'float',
605
            'double precision' => 'float',
606
            'float'            => 'float',
607
            'image'            => 'string',
608
            'int'              => 'integer',
609
            'integer'          => 'integer',
610
            'longtext'         => 'text',
611
            'longvarchar'      => 'string',
612
            'mediumint'        => 'integer',
613
            'mediumtext'       => 'text',
614
            'ntext'            => 'string',
615
            'numeric'          => 'decimal',
616
            'nvarchar'         => 'string',
617
            'real'             => 'float',
618
            'serial'           => 'integer',
619
            'smallint'         => 'smallint',
620
            'string'           => 'string',
621
            'text'             => 'text',
622
            'time'             => 'time',
623
            'timestamp'        => 'datetime',
624
            'tinyint'          => 'boolean',
625
            'tinytext'         => 'text',
626
            'varchar'          => 'string',
627
            'varchar2'         => 'string',
628
        ];
629
    }
630
631
    /**
632
     * {@inheritDoc}
633
     */
634
    protected function getReservedKeywordsClass() : string
635
    {
636 1201
        return Keywords\SQLiteKeywords::class;
637
    }
638
639
    /**
640
     * {@inheritDoc}
641 1728
     */
642
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
643 1728
    {
644
        if (! $diff->fromTable instanceof Table) {
645
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
646
        }
647
648
        $sql = [];
649 1479
        foreach ($diff->fromTable->getIndexes() as $index) {
650
            if ($index->isPrimary()) {
651 1479
                continue;
652
            }
653
654
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
655 1479
        }
656 1479
657 1473
        return $sql;
658 1471
    }
659
660
    /**
661 1470
     * {@inheritDoc}
662
     */
663
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
664 1479
    {
665
        if (! $diff->fromTable instanceof Table) {
666
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
667
        }
668
669
        $sql       = [];
670 1479
        $tableName = $diff->getNewName();
671
672 1479
        if ($tableName === false) {
673
            $tableName = $diff->getName($this);
674
        }
675
676 1479
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
677 1479
            if ($index->isPrimary()) {
678
                continue;
679 1479
            }
680 901
681
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
682
        }
683 1479
684 1473
        return $sql;
685 1471
    }
686
687
    /**
688 1472
     * {@inheritDoc}
689
     */
690
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
691 1479
    {
692
        if ($limit === null && $offset > 0) {
693
            $limit = -1;
694
        }
695
696
        return parent::doModifyLimitQuery($query, $limit, $offset);
697 1692
    }
698
699 1692
    /**
700 1666
     * {@inheritDoc}
701
     */
702
    public function getBlobTypeDeclarationSQL(array $field) : string
703 1692
    {
704
        return 'BLOB';
705
    }
706
707
    /**
708
     * {@inheritDoc}
709 262
     */
710
    public function getTemporaryTableName(string $tableName) : string
711 262
    {
712
        $tableName = str_replace('.', '__', $tableName);
713
714
        return $tableName;
715
    }
716
717 111
    /**
718
     * {@inheritDoc}
719 111
     *
720
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
721 111
     * into the default database.
722
     *
723
     * This hack is implemented to be able to use SQLite as testdriver when
724
     * using schema supporting databases.
725
     */
726
    public function canEmulateSchemas() : bool
727
    {
728
        return true;
729
    }
730
731
    /**
732
     * {@inheritDoc}
733
     */
734
    public function supportsForeignKeyConstraints() : bool
735
    {
736
        return false;
737
    }
738
739
    /**
740
     * {@inheritDoc}
741 1566
     */
742
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
743 1566
    {
744
        throw new DBALException('Sqlite platform does not support alter primary key.');
745
    }
746
747
    /**
748
     * {@inheritdoc}
749
     */
750
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string
751
    {
752
        throw new DBALException('Sqlite platform does not support alter foreign key.');
753
    }
754
755
    /**
756
     * {@inheritdoc}
757 1586
     */
758
    public function getDropForeignKeySQL($foreignKey, $table) : string
759 1586
    {
760
        throw new DBALException('Sqlite platform does not support alter foreign key.');
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
767
    {
768
        throw new DBALException('Sqlite platform does not support alter constraint.');
769
    }
770
771
    /**
772
     * {@inheritDoc}
773 1561
     */
774
    public function getCreateTableSQL(Table $table, int $createFlags = self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS) : array
775 1561
    {
776
        return parent::getCreateTableSQL($table, $createFlags);
777
    }
778
779
    /**
780
     * {@inheritDoc}
781 1715
     */
782
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
783 1715
    {
784
        $table = str_replace('.', '__', $table);
785 1715
786
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
787
    }
788
789
    /**
790
     * {@inheritDoc}
791 1374
     */
792
    public function getAlterTableSQL(TableDiff $diff) : array
793 1374
    {
794
        $sql = $this->getSimpleAlterTableSQL($diff);
795 1374
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
796
            return $sql;
797
        }
798
799
        $fromTable = $diff->fromTable;
800
        if (! $fromTable instanceof Table) {
801 1596
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
802
        }
803 1596
804 1596
        $table = clone $fromTable;
805 1582
806
        $columns        = [];
807
        $oldColumnNames = [];
808 1550
        $newColumnNames = [];
809 1550
        $columnSql      = [];
810 1442
811
        foreach ($table->getColumns() as $columnName => $column) {
812
            $columnName                  = strtolower($columnName);
813 1479
            $columns[$columnName]        = $column;
814
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
815 1479
        }
816 1479
817 1479
        foreach ($diff->removedColumns as $columnName => $column) {
818 1479
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
819
                continue;
820 1479
            }
821 1478
822 1478
            $columnName = strtolower($columnName);
823 1478
            if (! isset($columns[$columnName])) {
824
                continue;
825
            }
826 1479
827 1471
            unset(
828
                $columns[$columnName],
829
                $oldColumnNames[$columnName],
830
                $newColumnNames[$columnName]
831 1471
            );
832 1471
        }
833
834
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
835
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
836
                continue;
837 1471
            }
838 1471
839 1471
            $oldColumnName = strtolower($oldColumnName);
840
            if (isset($columns[$oldColumnName])) {
841
                unset($columns[$oldColumnName]);
842
            }
843 1479
844 1373
            $columns[strtolower($column->getName())] = $column;
845
846
            if (! isset($newColumnNames[$oldColumnName])) {
847
                continue;
848 1373
            }
849 1373
850 1373
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
851
        }
852
853 1373
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
854
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
855 1373
                continue;
856
            }
857
858
            if (isset($columns[$oldColumnName])) {
859 1373
                unset($columns[$oldColumnName]);
860
            }
861
862 1479
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
863 940
864
            if (! isset($newColumnNames[$oldColumnName])) {
865
                continue;
866
            }
867 940
868 939
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
869
        }
870
871 940
        foreach ($diff->addedColumns as $columnName => $column) {
872
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
873 940
                continue;
874 553
            }
875
876
            $columns[strtolower($columnName)] = $column;
877 939
        }
878
879
        $sql      = [];
880 1479
        $tableSql = [];
881 941
882
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
883
            $dataTable = new Table('__temp__' . $table->getName());
884
885 941
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), [], $this->getForeignKeysInAlteredTable($diff), $table->getOptions());
886
            $newTable->addOption('alter', true);
887
888 1479
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
889 1479
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
890
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
891 1479
            $sql[] = $this->getDropTableSQL($fromTable);
892 1479
893
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
894 1479
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
895 1479
            $sql[] = $this->getDropTableSQL($dataTable);
896
897 1479
            $newName = $diff->getNewName();
898
899 1479
            if ($newName !== false) {
900 1479
                $sql[] = sprintf(
901
                    'ALTER TABLE %s RENAME TO %s',
902 1479
                    $newTable->getQuotedName($this),
903 1479
                    $newName->getQuotedName($this)
904 1479
                );
905
            }
906 1479
907
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
908 1479
        }
909 1371
910 3
        return array_merge($sql, $tableSql, $columnSql);
911 1371
    }
912 1371
913
    /**
914
     * @return string[]|false
915
     */
916 1479
    private function getSimpleAlterTableSQL(TableDiff $diff)
917
    {
918
        // Suppress changes on integer type autoincrement columns.
919 1479
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
920
            if (! $columnDiff->fromColumn instanceof Column ||
921
                ! $columnDiff->column instanceof Column ||
922
                ! $columnDiff->column->getAutoincrement() ||
923
                ! $columnDiff->column->getType() instanceof Types\IntegerType
924
            ) {
925 1596
                continue;
926
            }
927
928 1596
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
929 960
                unset($diff->changedColumns[$oldColumnName]);
930 956
931 956
                continue;
932 960
            }
933
934 940
            $fromColumnType = $columnDiff->fromColumn->getType();
935
936
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
937 172
                continue;
938 169
            }
939
940 169
            unset($diff->changedColumns[$oldColumnName]);
941
        }
942
943 172
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
944
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
945 172
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
946
                || ! empty($diff->renamedIndexes)
947
        ) {
948
            return false;
949 172
        }
950
951
        $table = new Table($diff->name);
952 1596
953 1591
        $sql       = [];
954 1587
        $tableSql  = [];
955 1596
        $columnSql = [];
956
957 1479
        foreach ($diff->addedColumns as $column) {
958
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
959
                continue;
960 1584
            }
961
962 1584
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
963 1584
            $type  = $field['type'];
964 1584
            switch (true) {
965
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
966 1584
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
967 1467
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
968
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
969
                    return false;
970
            }
971 1467
972 1467
            $field['name'] = $column->getQuotedName($this);
973
            if ($type instanceof Types\StringType && $field['length'] === null) {
974 1467
                $field['length'] = 255;
975 1466
            }
976 1466
977 1465
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
978 1442
        }
979
980
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
981 1465
            if ($diff->newName !== false) {
982 1465
                $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

982
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
983 1465
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
984
            }
985
        }
986 1465
987
        return array_merge($sql, $tableSql, $columnSql);
988
    }
989 1582
990 1582
    /**
991 178
     * @return string[]
992 178
     */
993
    private function getColumnNamesInAlteredTable(TableDiff $diff) : array
994
    {
995
        $columns = [];
996 1582
997
        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

997
        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...
998
            $columns[strtolower($columnName)] = $column->getName();
999
        }
1000
1001
        foreach ($diff->removedColumns as $columnName => $column) {
1002 1479
            $columnName = strtolower($columnName);
1003
            if (! isset($columns[$columnName])) {
1004 1479
                continue;
1005
            }
1006 1479
1007 1478
            unset($columns[$columnName]);
1008
        }
1009
1010 1479
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1011 1471
            $columnName                          = $column->getName();
1012 1471
            $columns[strtolower($oldColumnName)] = $columnName;
1013
            $columns[strtolower($columnName)]    = $columnName;
1014
        }
1015
1016 1471
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1017
            $columnName                          = $columnDiff->column->getName();
1018
            $columns[strtolower($oldColumnName)] = $columnName;
1019 1479
            $columns[strtolower($columnName)]    = $columnName;
1020 1373
        }
1021 1373
1022 1373
        foreach ($diff->addedColumns as $column) {
1023
            $columnName                       = $column->getName();
1024
            $columns[strtolower($columnName)] = $columnName;
1025 1479
        }
1026 940
1027 940
        return $columns;
1028 940
    }
1029
1030
    /**
1031 1479
     * @return Index[]
1032 941
     */
1033 941
    private function getIndexesInAlteredTable(TableDiff $diff) : array
1034
    {
1035
        $indexes     = $diff->fromTable->getIndexes();
1036 1479
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1037
1038
        foreach ($indexes as $key => $index) {
1039
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1040
                if (strtolower($key) !== strtolower($oldIndexName)) {
1041
                    continue;
1042 1479
                }
1043
1044 1479
                unset($indexes[$key]);
1045 1479
            }
1046
1047 1479
            $changed      = false;
1048 1473
            $indexColumns = [];
1049 619
            foreach ($index->getColumns() as $columnName) {
1050 619
                $normalizedColumnName = strtolower($columnName);
1051
                if (! isset($columnNames[$normalizedColumnName])) {
1052
                    unset($indexes[$key]);
1053 341
                    continue 2;
1054
                }
1055
1056 1473
                $indexColumns[] = $columnNames[$normalizedColumnName];
1057 1473
                if ($columnName === $columnNames[$normalizedColumnName]) {
1058 1473
                    continue;
1059 1473
                }
1060 1473
1061 1369
                $changed = true;
1062 1369
            }
1063
1064 1473
            if (! $changed) {
1065 1473
                continue;
1066 1374
            }
1067
1068
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1069
        }
1070
1071 1473
        foreach ($diff->removedIndexes as $index) {
1072 1473
            $indexName = $index->getName();
1073
1074
            if ($indexName === null) {
1075 1369
                continue;
1076
            }
1077
1078 1479
            unset($indexes[strtolower($indexName)]);
1079 1468
        }
1080 1468
1081
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1082
            $indexName = $index->getName();
1083
1084 1468
            if ($indexName !== null) {
1085
                $indexes[strtolower($indexName)] = $index;
1086
            } else {
1087 1479
                $indexes[] = $index;
1088 619
            }
1089 619
        }
1090 619
1091
        return $indexes;
1092 3
    }
1093
1094
    /**
1095
     * @return ForeignKeyConstraint[]
1096 1479
     */
1097
    private function getForeignKeysInAlteredTable(TableDiff $diff) : array
1098
    {
1099
        $foreignKeys = $diff->fromTable->getForeignKeys();
1100
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1101
1102 1479
        foreach ($foreignKeys as $key => $constraint) {
1103
            $changed      = false;
1104 1479
            $localColumns = [];
1105 1479
            foreach ($constraint->getLocalColumns() as $columnName) {
1106
                $normalizedColumnName = strtolower($columnName);
1107 1479
                if (! isset($columnNames[$normalizedColumnName])) {
1108 1371
                    unset($foreignKeys[$key]);
1109 1371
                    continue 2;
1110 1371
                }
1111 1371
1112 1371
                $localColumns[] = $columnNames[$normalizedColumnName];
1113 1369
                if ($columnName === $columnNames[$normalizedColumnName]) {
1114 1369
                    continue;
1115
                }
1116 1371
1117 1371
                $changed = true;
1118 1371
            }
1119
1120
            if (! $changed) {
1121
                continue;
1122
            }
1123 1371
1124 1371
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1125
        }
1126
1127 1369
        foreach ($diff->removedForeignKeys as $constraint) {
1128
            if (! $constraint instanceof ForeignKeyConstraint) {
1129
                $constraint = new Identifier($constraint);
1130 1479
            }
1131 241
1132
            $constraintName = $constraint->getName();
1133
1134
            if ($constraintName === null) {
1135 241
                continue;
1136 241
            }
1137
1138
            unset($foreignKeys[strtolower($constraintName)]);
1139
        }
1140 241
1141
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1142
            $constraintName = $constraint->getName();
1143 1479
1144 387
            if ($constraintName !== null) {
1145 387
                $foreignKeys[strtolower($constraintName)] = $constraint;
1146 241
            } else {
1147
                $foreignKeys[] = $constraint;
1148 157
            }
1149
        }
1150
1151
        return $foreignKeys;
1152 1479
    }
1153
1154
    /**
1155
     * @return Index[]
1156
     */
1157
    private function getPrimaryIndexInAlteredTable(TableDiff $diff) : array
1158 1479
    {
1159
        $primaryIndex = [];
1160 1479
1161
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1162 1479
            if (! $index->isPrimary()) {
1163 1473
                continue;
1164 1472
            }
1165
1166
            $primaryIndex = [$index->getName() => $index];
1167 1471
        }
1168
1169
        return $primaryIndex;
1170 1479
    }
1171
}
1172