Completed
Pull Request — master (#3212)
by Sergei
49:12 queued 45:02
created

SqlitePlatform::getGuidExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

820
    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...
821
    {
822 18
        $table = str_replace('.', '__', $table);
823 18
        $table = $this->quoteStringLiteral($table);
824
825 18
        return "PRAGMA foreign_key_list($table)";
826
    }
827
828
    /**
829
     * {@inheritDoc}
830
     */
831 270
    public function getAlterTableSQL(TableDiff $diff)
832
    {
833 270
        $sql = $this->getSimpleAlterTableSQL($diff);
834 270
        if (false !== $sql) {
0 ignored issues
show
introduced by
The condition false !== $sql is always false.
Loading history...
835 18
            return $sql;
836
        }
837
838 252
        $fromTable = $diff->fromTable;
839 252
        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:121.
Loading history...
840 36
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
841
        }
842
843 216
        $table = clone $fromTable;
844
845 216
        $columns = [];
846 216
        $oldColumnNames = [];
847 216
        $newColumnNames = [];
848 216
        $columnSql = [];
849
850 216
        foreach ($table->getColumns() as $columnName => $column) {
851 198
            $columnName = strtolower($columnName);
852 198
            $columns[$columnName] = $column;
853 198
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
854
        }
855
856 216
        foreach ($diff->removedColumns as $columnName => $column) {
857 72
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
858
                continue;
859
            }
860
861 72
            $columnName = strtolower($columnName);
862 72
            if (isset($columns[$columnName])) {
863
                unset(
864 72
                    $columns[$columnName],
865 72
                    $oldColumnNames[$columnName],
866 72
                    $newColumnNames[$columnName]
867
                );
868
            }
869
        }
870
871 216
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
872 90
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
873
                continue;
874
            }
875
876 90
            $oldColumnName = strtolower($oldColumnName);
877 90
            if (isset($columns[$oldColumnName])) {
878 90
                unset($columns[$oldColumnName]);
879
            }
880
881 90
            $columns[strtolower($column->getName())] = $column;
882
883 90
            if (isset($newColumnNames[$oldColumnName])) {
884 90
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
885
            }
886
        }
887
888 216
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
889 108
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
890
                continue;
891
            }
892
893 108
            if (isset($columns[$oldColumnName])) {
894 90
                unset($columns[$oldColumnName]);
895
            }
896
897 108
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
898
899 108
            if (isset($newColumnNames[$oldColumnName])) {
900 108
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
901
            }
902
        }
903
904 216
        foreach ($diff->addedColumns as $columnName => $column) {
905 54
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
906
                continue;
907
            }
908
909 54
            $columns[strtolower($columnName)] = $column;
910
        }
911
912 216
        $sql = [];
913 216
        $tableSql = [];
914 216
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
915 216
            $dataTable = new Table('__temp__'.$table->getName());
916
917 216
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
918 216
            $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

918
            $newTable->addOption('alter', /** @scrutinizer ignore-type */ true);
Loading history...
919
920 216
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
921
            //$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...
922 216
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
923 216
            $sql[] = $this->getDropTableSQL($fromTable);
924
925 216
            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
926 216
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
927 216
            $sql[] = $this->getDropTableSQL($dataTable);
928
929 216
            if ($diff->newName && $diff->newName != $diff->name) {
930 54
                $renamedTable = $diff->getNewName();
931 54
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
932
            }
933
934 216
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
935
        }
936
937 216
        return array_merge($sql, $tableSql, $columnSql);
938
    }
939
940
    /**
941
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
942
     *
943
     * @return string[]|false
944
     */
945 270
    private function getSimpleAlterTableSQL(TableDiff $diff)
946
    {
947
        // Suppress changes on integer type autoincrement columns.
948 270
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
949 108
            if ( ! $columnDiff->fromColumn instanceof Column ||
950 36
                ! $columnDiff->column instanceof Column ||
951 36
                ! $columnDiff->column->getAutoincrement() ||
952 108
                ! $columnDiff->column->getType() instanceof Types\IntegerType
953
            ) {
954 108
                continue;
955
            }
956
957
            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
958
                unset($diff->changedColumns[$oldColumnName]);
959
960
                continue;
961
            }
962
963
            $fromColumnType = $columnDiff->fromColumn->getType();
964
965
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
966
                unset($diff->changedColumns[$oldColumnName]);
967
            }
968
        }
969
970 270
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
971 180
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
972 108
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
973 270
                || ! empty($diff->renamedIndexes)
974
        ) {
975 216
            return false;
976
        }
977
978 54
        $table = new Table($diff->name);
979
980 54
        $sql = [];
981 54
        $tableSql = [];
982 54
        $columnSql = [];
983
984 54
        foreach ($diff->addedColumns as $column) {
985 54
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
986
                continue;
987
            }
988
989 54
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
990 54
            $type = $field['type'];
991
            switch (true) {
992 54
                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...
993 36
                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...
994 36
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
995 18
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
996 36
                    return false;
997
            }
998
999 18
            $field['name'] = $column->getQuotedName($this);
1000 18
            if ($type instanceof Types\StringType && $field['length'] === null) {
1001 18
                $field['length'] = 255;
1002
            }
1003
1004 18
            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
1005
        }
1006
1007 18
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
1008 18
            if ($diff->newName !== false) {
1009
                $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

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