Completed
Pull Request — master (#3512)
by David
61:26
created

SqlitePlatform::getInlineTableCommentSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

815
    public function getListTableForeignKeysSQL($table, /** @scrutinizer ignore-unused */ $database = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1014
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
1015
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
1016 1563
            }
1017 1561
        }
1018
1019
        return array_merge($sql, $tableSql, $columnSql);
1020 1563
    }
1021 1547
1022 1547
    /**
1023
     * @return string[]
1024
     */
1025
    private function getColumnNamesInAlteredTable(TableDiff $diff)
1026 1547
    {
1027
        $columns = [];
1028
1029 1563
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
0 ignored issues
show
Bug introduced by
The method getColumns() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1029
        foreach ($diff->fromTable->/** @scrutinizer ignore-call */ getColumns() as $columnName => $column) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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