Failed Conditions
Push — drop-deprecated ( d77510...d983c4 )
by Michael
40:59 queued 38:23
created

SqlitePlatform   F

Complexity

Total Complexity 206

Size/Duplication

Total Lines 1146
Duplicated Lines 0 %

Test Coverage

Coverage 76.87%

Importance

Changes 0
Metric Value
wmc 206
eloc 425
dl 0
loc 1146
ccs 339
cts 441
cp 0.7687
rs 2
c 0
b 0
f 0

67 Methods

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

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

997
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
998
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
999
            }
1000
        }
1001
1002 1380
        return array_merge($sql, $tableSql, $columnSql);
1003
    }
1004
1005
    /**
1006
     * @return string[]
1007
     */
1008 1288
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1009
    {
1010 1288
        $columns = [];
1011
1012 1288
        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

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