Completed
Pull Request — master (#3512)
by David
19:31 queued 22s
created

SqlitePlatform::getInlineTableCommentSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

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

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