Completed
Push — develop ( a59880...a5109c )
by Sergei
112:22 queued 47:20
created

SqlitePlatform::getGuidExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

789
    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...
790
    {
791
        $table = str_replace('.', '__', $table);
792
        $table = $this->quoteStringLiteral($table);
793
794
        return "PRAGMA foreign_key_list($table)";
795
    }
796
797
    /**
798
     * {@inheritDoc}
799
     */
800
    public function getAlterTableSQL(TableDiff $diff)
801
    {
802
        $sql = $this->getSimpleAlterTableSQL($diff);
803
        if (false !== $sql) {
0 ignored issues
show
introduced by
The condition false !== $sql is always false.
Loading history...
804
            return $sql;
805
        }
806
807
        $fromTable = $diff->fromTable;
808
        if ( ! $fromTable instanceof Table) {
0 ignored issues
show
introduced by
$fromTable is always a sub-type of Doctrine\DBAL\Schema\Table. If $fromTable can have other possible types, add them to lib/Doctrine/DBAL/Schema/TableDiff.php:104.
Loading history...
809
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
810
        }
811
812
        $table = clone $fromTable;
813
814
        $columns = [];
815
        $oldColumnNames = [];
816
        $newColumnNames = [];
817
        $columnSql = [];
818
819
        foreach ($table->getColumns() as $columnName => $column) {
820
            $columnName = strtolower($columnName);
821
            $columns[$columnName] = $column;
822
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
823
        }
824
825
        foreach ($diff->removedColumns as $columnName => $column) {
826
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
827
                continue;
828
            }
829
830
            $columnName = strtolower($columnName);
831
            if (isset($columns[$columnName])) {
832
                unset(
833
                    $columns[$columnName],
834
                    $oldColumnNames[$columnName],
835
                    $newColumnNames[$columnName]
836
                );
837
            }
838
        }
839
840
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
841
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
842
                continue;
843
            }
844
845
            $oldColumnName = strtolower($oldColumnName);
846
            if (isset($columns[$oldColumnName])) {
847
                unset($columns[$oldColumnName]);
848
            }
849
850
            $columns[strtolower($column->getName())] = $column;
851
852
            if (isset($newColumnNames[$oldColumnName])) {
853
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
854
            }
855
        }
856
857
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
858
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
859
                continue;
860
            }
861
862
            if (isset($columns[$oldColumnName])) {
863
                unset($columns[$oldColumnName]);
864
            }
865
866
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
867
868
            if (isset($newColumnNames[$oldColumnName])) {
869
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
870
            }
871
        }
872
873
        foreach ($diff->addedColumns as $columnName => $column) {
874
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
875
                continue;
876
            }
877
878
            $columns[strtolower($columnName)] = $column;
879
        }
880
881
        $sql = [];
882
        $tableSql = [];
883
884
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
885
            $dataTable = new Table('__temp__'.$table->getName());
886
887
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), array(), $this->getForeignKeysInAlteredTable($diff), $table->getOptions());
888
            $newTable->addOption('alter', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of Doctrine\DBAL\Schema\Table::addOption(). ( Ignorable by Annotation )

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

888
            $newTable->addOption('alter', /** @scrutinizer ignore-type */ true);
Loading history...
889
890
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
891
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
892
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
893
            $sql[] = $this->getDropTableSQL($fromTable);
894
895
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
896
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
897
            $sql[] = $this->getDropTableSQL($dataTable);
898
899
            if ($diff->newName && $diff->newName != $diff->name) {
900
                $renamedTable = $diff->getNewName();
901
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
902
            }
903
904
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
905
        }
906
907
        return array_merge($sql, $tableSql, $columnSql);
908
    }
909
910
    /**
911
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
912
     *
913
     * @return string[]|false
914
     */
915
    private function getSimpleAlterTableSQL(TableDiff $diff)
916
    {
917
        // Suppress changes on integer type autoincrement columns.
918
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
919
            if ( ! $columnDiff->fromColumn instanceof Column ||
920
                ! $columnDiff->column instanceof Column ||
921
                ! $columnDiff->column->getAutoincrement() ||
922
                ! $columnDiff->column->getType() instanceof Types\IntegerType
923
            ) {
924
                continue;
925
            }
926
927
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
928
                unset($diff->changedColumns[$oldColumnName]);
929
930
                continue;
931
            }
932
933
            $fromColumnType = $columnDiff->fromColumn->getType();
934
935
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
936
                unset($diff->changedColumns[$oldColumnName]);
937
            }
938
        }
939
940
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
941
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
942
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
943
                || ! empty($diff->renamedIndexes)
944
        ) {
945
            return false;
946
        }
947
948
        $table = new Table($diff->name);
949
950
        $sql = [];
951
        $tableSql = [];
952
        $columnSql = [];
953
954
        foreach ($diff->addedColumns as $column) {
955
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
956
                continue;
957
            }
958
959
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
960
            $type = $field['type'];
961
            switch (true) {
962
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
963
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
964
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
965
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
966
                    return false;
967
            }
968
969
            $field['name'] = $column->getQuotedName($this);
970
            if ($type instanceof Types\StringType && $field['length'] === null) {
971
                $field['length'] = 255;
972
            }
973
974
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
975
        }
976
977
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
978
            if ($diff->newName !== false) {
979
                $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

979
                $newTable = new Identifier(/** @scrutinizer ignore-type */ $diff->newName);
Loading history...
980
                $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
981
            }
982
        }
983
984
        return array_merge($sql, $tableSql, $columnSql);
985
    }
986
987
    /**
988
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
989
     *
990
     * @return array
991
     */
992
    private function getColumnNamesInAlteredTable(TableDiff $diff)
993
    {
994
        $columns = [];
995
996
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
997
            $columns[strtolower($columnName)] = $column->getName();
998
        }
999
1000
        foreach ($diff->removedColumns as $columnName => $column) {
1001
            $columnName = strtolower($columnName);
1002
            if (isset($columns[$columnName])) {
1003
                unset($columns[$columnName]);
1004
            }
1005
        }
1006
1007
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
1008
            $columnName = $column->getName();
1009
            $columns[strtolower($oldColumnName)] = $columnName;
1010
            $columns[strtolower($columnName)] = $columnName;
1011
        }
1012
1013
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
1014
            $columnName = $columnDiff->column->getName();
1015
            $columns[strtolower($oldColumnName)] = $columnName;
1016
            $columns[strtolower($columnName)] = $columnName;
1017
        }
1018
1019
        foreach ($diff->addedColumns as $columnName => $column) {
1020
            $columns[strtolower($columnName)] = $columnName;
1021
        }
1022
1023
        return $columns;
1024
    }
1025
1026
    /**
1027
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1028
     *
1029
     * @return \Doctrine\DBAL\Schema\Index[]
1030
     */
1031
    private function getIndexesInAlteredTable(TableDiff $diff)
1032
    {
1033
        $indexes = $diff->fromTable->getIndexes();
1034
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1035
1036
        foreach ($indexes as $key => $index) {
1037
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
1038
                if (strtolower($key) === strtolower($oldIndexName)) {
1039
                    unset($indexes[$key]);
1040
                }
1041
            }
1042
1043
            $changed = false;
1044
            $indexColumns = [];
1045
            foreach ($index->getColumns() as $columnName) {
1046
                $normalizedColumnName = strtolower($columnName);
1047
                if ( ! isset($columnNames[$normalizedColumnName])) {
1048
                    unset($indexes[$key]);
1049
                    continue 2;
1050
                } else {
1051
                    $indexColumns[] = $columnNames[$normalizedColumnName];
1052
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1053
                        $changed = true;
1054
                    }
1055
                }
1056
            }
1057
1058
            if ($changed) {
1059
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1060
            }
1061
        }
1062
1063
        foreach ($diff->removedIndexes as $index) {
1064
            $indexName = strtolower($index->getName());
1065
            if (strlen($indexName) && isset($indexes[$indexName])) {
1066
                unset($indexes[$indexName]);
1067
            }
1068
        }
1069
1070
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1071
            $indexName = strtolower($index->getName());
1072
            if (strlen($indexName)) {
1073
                $indexes[$indexName] = $index;
1074
            } else {
1075
                $indexes[] = $index;
1076
            }
1077
        }
1078
1079
        return $indexes;
1080
    }
1081
1082
    /**
1083
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1084
     *
1085
     * @return array
1086
     */
1087
    private function getForeignKeysInAlteredTable(TableDiff $diff)
1088
    {
1089
        $foreignKeys = $diff->fromTable->getForeignKeys();
1090
        $columnNames = $this->getColumnNamesInAlteredTable($diff);
1091
1092
        foreach ($foreignKeys as $key => $constraint) {
1093
            $changed = false;
1094
            $localColumns = [];
1095
            foreach ($constraint->getLocalColumns() as $columnName) {
1096
                $normalizedColumnName = strtolower($columnName);
1097
                if ( ! isset($columnNames[$normalizedColumnName])) {
1098
                    unset($foreignKeys[$key]);
1099
                    continue 2;
1100
                } else {
1101
                    $localColumns[] = $columnNames[$normalizedColumnName];
1102
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
1103
                        $changed = true;
1104
                    }
1105
                }
1106
            }
1107
1108
            if ($changed) {
1109
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1110
            }
1111
        }
1112
1113
        foreach ($diff->removedForeignKeys as $constraint) {
1114
            $constraintName = strtolower($constraint->getName());
1115
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
1116
                unset($foreignKeys[$constraintName]);
1117
            }
1118
        }
1119
1120
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1121
            $constraintName = strtolower($constraint->getName());
1122
            if (strlen($constraintName)) {
1123
                $foreignKeys[$constraintName] = $constraint;
1124
            } else {
1125
                $foreignKeys[] = $constraint;
1126
            }
1127
        }
1128
1129
        return $foreignKeys;
1130
    }
1131
1132
    /**
1133
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
1134
     *
1135
     * @return array
1136
     */
1137
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
1138
    {
1139
        $primaryIndex = [];
1140
1141
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1142
            if ($index->isPrimary()) {
1143
                $primaryIndex = [$index->getName() => $index];
1144
            }
1145
        }
1146
1147
        return $primaryIndex;
1148
    }
1149
}
1150