SqlitePlatform::udfSqrt()   A
last analyzed

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
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Schema\Constraint;
9
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10
use Doctrine\DBAL\Schema\Identifier;
11
use Doctrine\DBAL\Schema\Index;
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Schema\TableDiff;
14
use Doctrine\DBAL\TransactionIsolationLevel;
15
use Doctrine\DBAL\Types;
16
use InvalidArgumentException;
17
use function array_merge;
18
use function array_unique;
19
use function array_values;
20
use function implode;
21
use function sprintf;
22
use function sqrt;
23
use function str_replace;
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 22
    public function getRegexpExpression() : string
37
    {
38 22
        return 'REGEXP';
39
    }
40
41
    public function getNowExpression(string $type = 'timestamp') : string
42
    {
43
        switch ($type) {
44
            case 'time':
45
                return 'time(\'now\')';
46
47
            case 'date':
48
                return 'date(\'now\')';
49
50
            case 'timestamp':
51
            default:
52
                return 'datetime(\'now\')';
53
        }
54
    }
55
56 37
    public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED, ?string $char = null) : string
57
    {
58 37
        switch ($mode) {
59
            case TrimMode::UNSPECIFIED:
60
            case TrimMode::BOTH:
61 18
                $trimFn = 'TRIM';
62 18
                break;
63
64
            case TrimMode::LEADING:
65 9
                $trimFn = 'LTRIM';
66 9
                break;
67
68
            case TrimMode::TRAILING:
69 9
                $trimFn = 'RTRIM';
70 9
                break;
71
72
            default:
73 1
                throw new InvalidArgumentException(
74 1
                    sprintf(
75
                        'The value of $mode is expected to be one of the TrimMode constants, %d given.',
76 1
                        $mode
77
                    )
78
                );
79
        }
80
81 36
        $arguments = [$str];
82
83 36
        if ($char !== null) {
84 28
            $arguments[] = $char;
85
        }
86
87 36
        return sprintf('%s(%s)', $trimFn, implode(', ', $arguments));
88
    }
89
90 25
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
91
    {
92 25
        if ($length === null) {
93 23
            return sprintf('SUBSTR(%s, %s)', $string, $start);
94
        }
95
96 24
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
97
    }
98
99 1
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
100
    {
101 1
        if ($start === null) {
102 1
            return sprintf('LOCATE(%s, %s)', $string, $substring);
103
        }
104
105 1
        return sprintf('LOCATE(%s, %s, %s)', $string, $substring, $start);
106
    }
107
108 93
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
109
    {
110
        switch ($unit) {
111 93
            case DateIntervalUnit::WEEK:
112 6
                $interval = $this->multiplyInterval($interval, 7);
113 6
                $unit     = DateIntervalUnit::DAY;
114 6
                break;
115
116 87
            case DateIntervalUnit::QUARTER:
117 6
                $interval = $this->multiplyInterval($interval, 3);
118 6
                $unit     = DateIntervalUnit::MONTH;
119 6
                break;
120
        }
121
122 93
        return 'DATETIME(' . $date . ',' . $this->getConcatExpression(
123 93
            $this->quoteStringLiteral($operator),
124
            $interval,
125 93
            $this->quoteStringLiteral(' ' . $unit)
126 93
        ) . ')';
127
    }
128
129 3
    public function getDateDiffExpression(string $date1, string $date2) : string
130
    {
131 3
        return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
132
    }
133
134
    /**
135
     * {@inheritDoc}
136
     *
137
     * The SQLite platform doesn't support the concept of a database, therefore, it always returns an empty string
138
     * as an indicator of an implicitly selected database.
139
     *
140
     * @see \Doctrine\DBAL\Connection::getDatabase()
141
     */
142 139
    public function getCurrentDatabaseExpression() : string
143
    {
144 139
        return "''";
145
    }
146
147 22
    protected function _getTransactionIsolationLevelSQL(int $level) : string
148
    {
149
        switch ($level) {
150 22
            case TransactionIsolationLevel::READ_UNCOMMITTED:
151 22
                return '0';
152
153 22
            case TransactionIsolationLevel::READ_COMMITTED:
154 22
            case TransactionIsolationLevel::REPEATABLE_READ:
155 22
            case TransactionIsolationLevel::SERIALIZABLE:
156 22
                return '1';
157
158
            default:
159
                return parent::_getTransactionIsolationLevelSQL($level);
160
        }
161
    }
162
163 22
    public function getSetTransactionIsolationSQL(int $level) : string
164
    {
165 22
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
166
    }
167
168 23
    public function prefersIdentityColumns() : bool
169
    {
170 23
        return true;
171
    }
172
173
    /**
174
     * {@inheritDoc}
175
     */
176 38
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
177
    {
178 38
        return 'BOOLEAN';
179
    }
180
181
    /**
182
     * {@inheritDoc}
183
     */
184 640
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
185
    {
186 640
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     */
192 39
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
193
    {
194
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
195 39
        if (! empty($columnDef['autoincrement'])) {
196 24
            return $this->getIntegerTypeDeclarationSQL($columnDef);
197
        }
198
199 37
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
200
    }
201
202
    /**
203
     * @param array<string, mixed> $field
204
     */
205 44
    public function getTinyIntTypeDeclarationSQL(array $field) : string
206
    {
207
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
208 44
        if (! empty($field['autoincrement'])) {
209 44
            return $this->getIntegerTypeDeclarationSQL($field);
210
        }
211
212 22
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 25
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
219
    {
220
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
221 25
        if (! empty($columnDef['autoincrement'])) {
222 24
            return $this->getIntegerTypeDeclarationSQL($columnDef);
223
        }
224
225 23
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
226
    }
227
228
    /**
229
     * @param array<string, mixed> $field
230
     */
231 22
    public function getMediumIntTypeDeclarationSQL(array $field) : string
232
    {
233
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
234 22
        if (! empty($field['autoincrement'])) {
235 22
            return $this->getIntegerTypeDeclarationSQL($field);
236
        }
237
238 22
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 23
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
245
    {
246 23
        return 'DATETIME';
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     */
252 20
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
253
    {
254 20
        return 'DATE';
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 19
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
261
    {
262 19
        return 'TIME';
263
    }
264
265
    /**
266
     * {@inheritDoc}
267
     */
268 640
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
269
    {
270
        // sqlite autoincrement is only possible for the primary key
271 640
        if (! empty($columnDef['autoincrement'])) {
272 231
            return ' PRIMARY KEY AUTOINCREMENT';
273
        }
274
275 563
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
276
    }
277
278 123
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
279
    {
280 123
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
281 123
            $foreignKey->getQuotedLocalColumns($this),
282 123
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
283 123
            $foreignKey->getQuotedForeignColumns($this),
284 123
            $foreignKey->getName(),
285 123
            $foreignKey->getOptions()
286
        ));
287
    }
288
289
    /**
290
     * {@inheritDoc}
291
     */
292 716
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
293
    {
294 716
        $tableName   = str_replace('.', '__', $tableName);
295 716
        $queryFields = $this->getColumnDeclarationListSQL($columns);
296
297 716
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
298
            foreach ($options['uniqueConstraints'] as $constraintName => $definition) {
299
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition);
300
            }
301
        }
302
303 716
        $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
304
305 716
        if (isset($options['foreignKeys'])) {
306 716
            foreach ($options['foreignKeys'] as $foreignKey) {
307 123
                $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
308
            }
309
        }
310
311 716
        $tableComment = '';
312 716
        if (isset($options['comment'])) {
313 1
            $comment = trim($options['comment'], " '");
314
315 1
            $tableComment = $this->getInlineTableCommentSQL($comment);
316
        }
317
318 716
        $query = ['CREATE TABLE ' . $tableName . ' ' . $tableComment . '(' . $queryFields . ')'];
319
320 716
        if (isset($options['alter']) && $options['alter'] === true) {
321 276
            return $query;
322
        }
323
324 452
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
325 102
            foreach ($options['indexes'] as $indexDef) {
326 102
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
327
            }
328
        }
329
330 452
        if (isset($options['unique']) && ! empty($options['unique'])) {
331
            foreach ($options['unique'] as $indexDef) {
332
                $query[] = $this->getCreateIndexSQL($indexDef, $tableName);
333
            }
334
        }
335
336 452
        return $query;
337
    }
338
339
    /**
340
     * Generate a PRIMARY KEY definition if no autoincrement value is used
341
     *
342
     * @param mixed[][] $columns
343
     * @param mixed[]   $options
344
     */
345 716
    private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
346
    {
347 716
        if (empty($options['primary'])) {
348 354
            return '';
349
        }
350
351 365
        $keyColumns = array_unique(array_values($options['primary']));
352
353 365
        foreach ($keyColumns as $keyColumn) {
354 365
            foreach ($columns as $column) {
355 365
                if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) {
356 99
                    return '';
357
                }
358
            }
359
        }
360
361 270
        return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
362
    }
363
364 46
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
365
    {
366 46
        return 'BLOB';
367
    }
368
369 401
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
370
    {
371 401
        $sql = 'VARCHAR';
372
373 401
        if ($length !== null) {
374 379
            $sql .= sprintf('(%d)', $length);
375
        }
376
377 401
        return $sql;
378
    }
379
380 46
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
381
    {
382 46
        return 'BLOB';
383
    }
384
385
    /**
386
     * {@inheritDoc}
387
     */
388 68
    public function getClobTypeDeclarationSQL(array $field) : string
389
    {
390 68
        return 'CLOB';
391
    }
392
393 22
    public function getListTableConstraintsSQL(string $table) : string
394
    {
395 22
        $table = str_replace('.', '__', $table);
396
397 22
        return sprintf(
398 1
            "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
399 22
            $this->quoteStringLiteral($table)
400
        );
401
    }
402
403 131
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
404
    {
405 131
        $table = str_replace('.', '__', $table);
406
407 131
        return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
408
    }
409
410 129
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
411
    {
412 129
        $table = str_replace('.', '__', $table);
413
414 129
        return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
415
    }
416
417 77
    public function getListTablesSQL() : string
418
    {
419
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
420
             . 'UNION ALL SELECT name FROM sqlite_temp_master '
421 77
             . "WHERE type = 'table' ORDER BY name";
422
    }
423
424 1
    public function getListViewsSQL(string $database) : string
425
    {
426 1
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
427
    }
428
429 1
    public function getCreateViewSQL(string $name, string $sql) : string
430
    {
431 1
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
432
    }
433
434 1
    public function getDropViewSQL(string $name) : string
435
    {
436 1
        return 'DROP VIEW ' . $name;
437
    }
438
439 123
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
440
    {
441 123
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
442
443 123
        $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
444 123
        $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
445
446 123
        return $query;
447
    }
448
449 3
    public function supportsIdentityColumns() : bool
450
    {
451 3
        return true;
452
    }
453
454 69
    public function supportsColumnCollation() : bool
455
    {
456 69
        return true;
457
    }
458
459 804
    public function supportsInlineColumnComments() : bool
460
    {
461 804
        return true;
462
    }
463
464 102
    public function getName() : string
465
    {
466 102
        return 'sqlite';
467
    }
468
469 31
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
470
    {
471 31
        $tableIdentifier = new Identifier($tableName);
472 31
        $tableName       = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
473
474 31
        return 'DELETE FROM ' . $tableName;
475
    }
476
477
    /**
478
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
479
     *
480
     * @param int|float $value
481
     */
482
    public static function udfSqrt($value) : float
483
    {
484
        return sqrt($value);
485
    }
486
487
    /**
488
     * User-defined function for Sqlite that implements MOD(a, b).
489
     */
490
    public static function udfMod(int $a, int $b) : int
491
    {
492
        return $a % $b;
493
    }
494
495 1
    public static function udfLocate(string $str, string $substr, int $offset = 0) : int
496
    {
497
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
498
        // So we have to make them compatible if an offset is given.
499 1
        if ($offset > 0) {
500 1
            $offset -= 1;
501
        }
502
503 1
        $pos = strpos($str, $substr, $offset);
504
505 1
        if ($pos !== false) {
506 1
            return $pos + 1;
507
        }
508
509 1
        return 0;
510
    }
511
512
    public function getForUpdateSQL() : string
513
    {
514
        return '';
515
    }
516
517 155
    public function getInlineColumnCommentSQL(string $comment) : string
518
    {
519 155
        if ($comment === '') {
520 22
            return '';
521
        }
522
523 133
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
524
    }
525
526 1
    private function getInlineTableCommentSQL(string $comment) : string
527
    {
528 1
        return $this->getInlineColumnCommentSQL($comment);
529
    }
530
531 133
    protected function initializeDoctrineTypeMappings() : void
532
    {
533 133
        $this->doctrineTypeMapping = [
534
            'bigint'           => 'bigint',
535
            'bigserial'        => 'bigint',
536
            'blob'             => 'blob',
537
            'boolean'          => 'boolean',
538
            'char'             => 'string',
539
            'clob'             => 'text',
540
            'date'             => 'date',
541
            'datetime'         => 'datetime',
542
            'decimal'          => 'decimal',
543
            'double'           => 'float',
544
            'double precision' => 'float',
545
            'float'            => 'float',
546
            'image'            => 'string',
547
            'int'              => 'integer',
548
            'integer'          => 'integer',
549
            'longtext'         => 'text',
550
            'longvarchar'      => 'string',
551
            'mediumint'        => 'integer',
552
            'mediumtext'       => 'text',
553
            'ntext'            => 'string',
554
            'numeric'          => 'decimal',
555
            'nvarchar'         => 'string',
556
            'real'             => 'float',
557
            'serial'           => 'integer',
558
            'smallint'         => 'smallint',
559
            'string'           => 'string',
560
            'text'             => 'text',
561
            'time'             => 'time',
562
            'timestamp'        => 'datetime',
563
            'tinyint'          => 'boolean',
564
            'tinytext'         => 'text',
565
            'varchar'          => 'string',
566
            'varchar2'         => 'string',
567
        ];
568 133
    }
569
570 889
    protected function getReservedKeywordsClass() : string
571
    {
572 889
        return Keywords\SQLiteKeywords::class;
573
    }
574
575
    /**
576
     * {@inheritDoc}
577
     */
578 276
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
579
    {
580 276
        if (! $diff->fromTable instanceof Table) {
581
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
582
        }
583
584 276
        $sql = [];
585 276
        foreach ($diff->fromTable->getIndexes() as $index) {
586 138
            if ($index->isPrimary()) {
587 93
                continue;
588
            }
589
590 69
            $sql[] = $this->getDropIndexSQL($index, $diff->name);
591
        }
592
593 276
        return $sql;
594
    }
595
596
    /**
597
     * {@inheritDoc}
598
     */
599 276
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) : array
600
    {
601 276
        if ($diff->fromTable === null) {
602
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
603
        }
604
605 276
        $sql       = [];
606 276
        $tableName = $diff->getNewName();
607
608 276
        if ($tableName === null) {
609 210
            $tableName = $diff->getName($this);
610
        }
611
612 276
        foreach ($this->getIndexesInAlteredTable($diff, $diff->fromTable) as $index) {
613 138
            if ($index->isPrimary()) {
614 93
                continue;
615
            }
616
617 113
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
618
        }
619
620 276
        return $sql;
621
    }
622
623 96
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
624
    {
625 96
        if ($limit === null && $offset > 0) {
626 23
            $limit = -1;
627
        }
628
629 96
        return parent::doModifyLimitQuery($query, $limit, $offset);
630
    }
631
632
    /**
633
     * {@inheritDoc}
634
     */
635 8
    public function getBlobTypeDeclarationSQL(array $field) : string
636
    {
637 8
        return 'BLOB';
638
    }
639
640 2
    public function getTemporaryTableName(string $tableName) : string
641
    {
642 2
        $tableName = str_replace('.', '__', $tableName);
643
644 2
        return $tableName;
645
    }
646
647
    /**
648
     * {@inheritDoc}
649
     *
650
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
651
     * into the default database.
652
     *
653
     * This hack is implemented to be able to use SQLite as testdriver when
654
     * using schema supporting databases.
655
     */
656
    public function canEmulateSchemas() : bool
657
    {
658
        return true;
659
    }
660
661 240
    public function supportsForeignKeyConstraints() : bool
662
    {
663 240
        return true;
664
    }
665
666 24
    public function supportsCreateDropForeignKeyConstraints() : bool
667
    {
668 24
        return false;
669
    }
670
671
    /**
672
     * {@inheritDoc}
673
     */
674
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
675
    {
676
        throw new DBALException('Sqlite platform does not support alter primary key.');
677
    }
678
679
    /**
680
     * {@inheritdoc}
681
     */
682 22
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) : string
683
    {
684 22
        throw new DBALException('Sqlite platform does not support alter foreign key, the table must be fully recreated using getAlterTableSQL.');
685
    }
686
687
    /**
688
     * {@inheritdoc}
689
     */
690
    public function getDropForeignKeySQL($foreignKey, $table) : string
691
    {
692
        throw new DBALException('Sqlite platform does not support alter foreign key, the table must be fully recreated using getAlterTableSQL.');
693
    }
694
695
    /**
696
     * {@inheritDoc}
697
     */
698
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
699
    {
700
        throw new DBALException('Sqlite platform does not support alter constraint.');
701
    }
702
703
    /**
704
     * {@inheritDoc}
705
     *
706
     * @param int $createFlags
707
     */
708 738
    public function getCreateTableSQL(Table $table, int $createFlags = self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS) : array
709
    {
710 738
        return parent::getCreateTableSQL($table, $createFlags);
711
    }
712
713 126
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
714
    {
715 126
        $table = str_replace('.', '__', $table);
716
717 126
        return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
718
    }
719
720
    /**
721
     * {@inheritDoc}
722
     */
723 348
    public function getAlterTableSQL(TableDiff $diff) : array
724
    {
725 348
        $sql = $this->getSimpleAlterTableSQL($diff);
726 348
        if ($sql !== false) {
0 ignored issues
show
introduced by
The condition $sql !== false is always false.
Loading history...
727 28
            return $sql;
728
        }
729
730 320
        $fromTable = $diff->fromTable;
731 320
        if ($fromTable === null) {
732 44
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema.');
733
        }
734
735 276
        $table = clone $fromTable;
736
737 276
        $columns        = [];
738 276
        $oldColumnNames = [];
739 276
        $newColumnNames = [];
740 276
        $columnSql      = [];
741
742 276
        foreach ($table->getColumns() as $columnName => $column) {
743 254
            $columnName                  = strtolower($columnName);
744 254
            $columns[$columnName]        = $column;
745 254
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
746
        }
747
748 276
        foreach ($diff->removedColumns as $columnName => $column) {
749 89
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
750
                continue;
751
            }
752
753 89
            $columnName = strtolower($columnName);
754 89
            if (! isset($columns[$columnName])) {
755
                continue;
756
            }
757
758
            unset(
759 89
                $columns[$columnName],
760 89
                $oldColumnNames[$columnName],
761 89
                $newColumnNames[$columnName]
762
            );
763
        }
764
765 276
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
766 111
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
767
                continue;
768
            }
769
770 111
            $oldColumnName = strtolower($oldColumnName);
771 111
            if (isset($columns[$oldColumnName])) {
772 111
                unset($columns[$oldColumnName]);
773
            }
774
775 111
            $columns[strtolower($column->getName())] = $column;
776
777 111
            if (! isset($newColumnNames[$oldColumnName])) {
778
                continue;
779
            }
780
781 111
            $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
782
        }
783
784 276
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
785 139
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
786
                continue;
787
            }
788
789 139
            if (isset($columns[$oldColumnName])) {
790 117
                unset($columns[$oldColumnName]);
791
            }
792
793 139
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
794
795 139
            if (! isset($newColumnNames[$oldColumnName])) {
796 22
                continue;
797
            }
798
799 117
            $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
800
        }
801
802 276
        foreach ($diff->addedColumns as $columnName => $column) {
803 67
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
804
                continue;
805
            }
806
807 67
            $columns[strtolower($columnName)] = $column;
808
        }
809
810 276
        $sql      = [];
811 276
        $tableSql = [];
812
813 276
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
814 276
            $dataTable = new Table('__temp__' . $table->getName());
815
816 276
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff, $fromTable), [], $this->getForeignKeysInAlteredTable($diff, $fromTable), $table->getOptions());
817 276
            $newTable->addOption('alter', true);
818
819 276
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
820
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
821 276
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
822 276
            $sql[] = $this->getDropTableSQL($fromTable);
823
824 276
            $sql   = array_merge($sql, $this->getCreateTableSQL($newTable));
825 276
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
826 276
            $sql[] = $this->getDropTableSQL($dataTable);
827
828 276
            $newName = $diff->getNewName();
829
830 276
            if ($newName !== null) {
831 66
                $sql[] = sprintf(
832 3
                    'ALTER TABLE %s RENAME TO %s',
833 66
                    $newTable->getQuotedName($this),
834 66
                    $newName->getQuotedName($this)
835
                );
836
            }
837
838 276
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
839
        }
840
841 276
        return array_merge($sql, $tableSql, $columnSql);
842
    }
843
844
    /**
845
     * @return string[]|false
846
     */
847 348
    private function getSimpleAlterTableSQL(TableDiff $diff)
848
    {
849
        // Suppress changes on integer type autoincrement columns.
850 348
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
851 144
            if ($columnDiff->fromColumn === null ||
852 140
                ! $columnDiff->column->getAutoincrement() ||
853 144
                ! $columnDiff->column->getType() instanceof Types\IntegerType
854
            ) {
855 139
                continue;
856
            }
857
858 5
            if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
859 1
                unset($diff->changedColumns[$oldColumnName]);
860
861 1
                continue;
862
            }
863
864 4
            $fromColumnType = $columnDiff->fromColumn->getType();
865
866 4
            if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
867
                continue;
868
            }
869
870 4
            unset($diff->changedColumns[$oldColumnName]);
871
        }
872
873 348
        if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
874 343
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
875 339
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
876 348
                || ! empty($diff->renamedIndexes)
877
        ) {
878 276
            return false;
879
        }
880
881 72
        $table = new Table($diff->name);
882
883 72
        $sql       = [];
884 72
        $tableSql  = [];
885 72
        $columnSql = [];
886
887 72
        foreach ($diff->addedColumns as $column) {
888 66
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
889
                continue;
890
            }
891
892 66
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
893 66
            $type  = $field['type'];
894
            switch (true) {
895 66
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
896 44
                case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
897 44
                case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
898 22
                case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
899 44
                    return false;
900
            }
901
902 22
            $field['name'] = $column->getQuotedName($this);
903 22
            if ($type instanceof Types\StringType && $field['length'] === null) {
904 22
                $field['length'] = 255;
905
            }
906
907 22
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
908
        }
909
910 28
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
911 28
            if ($diff->newName !== null) {
912 1
                $newTable = new Identifier($diff->newName);
913 1
                $sql[]    = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
914
            }
915
        }
916
917 28
        return array_merge($sql, $tableSql, $columnSql);
918
    }
919
920
    /**
921
     * @return string[]
922
     */
923 276
    private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable) : array
924
    {
925 276
        $columns = [];
926
927 276
        foreach ($fromTable->getColumns() as $columnName => $column) {
928 254
            $columns[strtolower($columnName)] = $column->getName();
929
        }
930
931 276
        foreach ($diff->removedColumns as $columnName => $column) {
932 89
            $columnName = strtolower($columnName);
933 89
            if (! isset($columns[$columnName])) {
934
                continue;
935
            }
936
937 89
            unset($columns[$columnName]);
938
        }
939
940 276
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
941 111
            $columnName                          = $column->getName();
942 111
            $columns[strtolower($oldColumnName)] = $columnName;
943 111
            $columns[strtolower($columnName)]    = $columnName;
944
        }
945
946 276
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
947 139
            $columnName                          = $columnDiff->column->getName();
948 139
            $columns[strtolower($oldColumnName)] = $columnName;
949 139
            $columns[strtolower($columnName)]    = $columnName;
950
        }
951
952 276
        foreach ($diff->addedColumns as $column) {
953 67
            $columnName                       = $column->getName();
954 67
            $columns[strtolower($columnName)] = $columnName;
955
        }
956
957 276
        return $columns;
958
    }
959
960
    /**
961
     * @return Index[]
962
     */
963 276
    private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable) : array
964
    {
965 276
        $indexes     = $fromTable->getIndexes();
966 276
        $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable);
967
968 276
        foreach ($indexes as $key => $index) {
969 138
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
970 68
                if (strtolower($key) !== strtolower($oldIndexName)) {
971 67
                    continue;
972
                }
973
974 24
                unset($indexes[$key]);
975
            }
976
977 138
            $changed      = false;
978 138
            $indexColumns = [];
979 138
            foreach ($index->getColumns() as $columnName) {
980 138
                $normalizedColumnName = strtolower($columnName);
981 138
                if (! isset($columnNames[$normalizedColumnName])) {
982 22
                    unset($indexes[$key]);
983 22
                    continue 2;
984
                }
985
986 138
                $indexColumns[] = $columnNames[$normalizedColumnName];
987 138
                if ($columnName === $columnNames[$normalizedColumnName]) {
988 138
                    continue;
989
                }
990
991 23
                $changed = true;
992
            }
993
994 138
            if (! $changed) {
995 138
                continue;
996
            }
997
998 23
            $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
999
        }
1000
1001 276
        foreach ($diff->removedIndexes as $index) {
1002 23
            $indexName = $index->getName();
1003
1004 23
            if ($indexName === '') {
1005
                continue;
1006
            }
1007
1008 23
            unset($indexes[strtolower($indexName)]);
1009
        }
1010
1011 276
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1012 69
            $indexName = $index->getName();
1013
1014 69
            if ($indexName !== '') {
1015 69
                $indexes[strtolower($indexName)] = $index;
1016
            } else {
1017
                $indexes[] = $index;
1018
            }
1019
        }
1020
1021 276
        return $indexes;
1022
    }
1023
1024
    /**
1025
     * @return ForeignKeyConstraint[]
1026
     */
1027 276
    private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable) : array
1028
    {
1029 276
        $foreignKeys = $fromTable->getForeignKeys();
1030 276
        $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable);
1031
1032 276
        foreach ($foreignKeys as $key => $constraint) {
1033 68
            $changed      = false;
1034 68
            $localColumns = [];
1035 68
            foreach ($constraint->getLocalColumns() as $columnName) {
1036 68
                $normalizedColumnName = strtolower($columnName);
1037 68
                if (! isset($columnNames[$normalizedColumnName])) {
1038 22
                    unset($foreignKeys[$key]);
1039 22
                    continue 2;
1040
                }
1041
1042 68
                $localColumns[] = $columnNames[$normalizedColumnName];
1043 68
                if ($columnName === $columnNames[$normalizedColumnName]) {
1044 67
                    continue;
1045
                }
1046
1047 23
                $changed = true;
1048
            }
1049
1050 68
            if (! $changed) {
1051 67
                continue;
1052
            }
1053
1054 23
            $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
1055
        }
1056
1057 276
        foreach ($diff->removedForeignKeys as $constraint) {
1058 23
            $constraintName = $constraint->getName();
1059
1060 23
            if ($constraintName === '') {
1061
                continue;
1062
            }
1063
1064 23
            unset($foreignKeys[strtolower($constraintName)]);
1065
        }
1066
1067 276
        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
1068 26
            $constraintName = $constraint->getName();
1069
1070 26
            if ($constraintName !== '') {
1071 25
                $foreignKeys[strtolower($constraintName)] = $constraint;
1072
            } else {
1073 1
                $foreignKeys[] = $constraint;
1074
            }
1075
        }
1076
1077 276
        return $foreignKeys;
1078
    }
1079
1080
    /**
1081
     * @return Index[]
1082
     */
1083 276
    private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable) : array
1084
    {
1085 276
        $primaryIndex = [];
1086
1087 276
        foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) {
1088 138
            if (! $index->isPrimary()) {
1089 113
                continue;
1090
            }
1091
1092 93
            $primaryIndex = [$index->getName() => $index];
1093
        }
1094
1095 276
        return $primaryIndex;
1096
    }
1097
}
1098