SqlitePlatform.php 33.7 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17 18
 * <http://www.doctrine-project.org>.
 */
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 28 29
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
30
use Doctrine\DBAL\TransactionIsolationLevel;
31
use Doctrine\DBAL\Types;
32

33
/**
34 35
 * The SqlitePlatform class describes the specifics and dialects of the SQLite
 * database platform.
36
 *
Benjamin Morel's avatar
Benjamin Morel committed
37
 * @since  2.0
38
 * @author Roman Borschel <roman@code-factory.org>
39
 * @author Benjamin Eberlei <kontakt@beberlei.de>
40
 * @author Martin Hasoň <martin.hason@gmail.com>
Benjamin Morel's avatar
Benjamin Morel committed
41
 * @todo   Rename: SQLitePlatform
42
 */
43
class SqlitePlatform extends AbstractPlatform
44 45
{
    /**
46
     * {@inheritDoc}
47 48 49
     */
    public function getRegexpExpression()
    {
50
        return 'REGEXP';
51 52
    }

53 54 55 56 57 58 59 60 61 62 63
    /**
     * {@inheritDoc}
     */
    public function getGuidExpression()
    {
        return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || "
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || "
            . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || "
            . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))";
    }

64
    /**
65
     * {@inheritDoc}
66 67 68 69 70 71 72 73 74 75 76 77 78 79
     */
    public function getNowExpression($type = 'timestamp')
    {
        switch ($type) {
            case 'time':
                return 'time(\'now\')';
            case 'date':
                return 'date(\'now\')';
            case 'timestamp':
            default:
                return 'datetime(\'now\')';
        }
    }

80
    /**
81
     * {@inheritDoc}
82
     */
83
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
84 85 86
    {
        $trimChar = ($char != false) ? (', ' . $char) : '';

87
        switch ($pos) {
88
            case TrimMode::LEADING:
89 90 91
                $trimFn = 'LTRIM';
                break;

92
            case TrimMode::TRAILING:
93 94 95 96 97
                $trimFn = 'RTRIM';
                break;

            default:
                $trimFn = 'TRIM';
98 99 100 101 102
        }

        return $trimFn . '(' . $str . $trimChar . ')';
    }

103
    /**
104
     * {@inheritDoc}
105 106 107 108 109 110 111 112
     *
     * SQLite only supports the 2 parameter variant of this function
     */
    public function getSubstringExpression($value, $position, $length = null)
    {
        if ($length !== null) {
            return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
        }
113

114 115 116
        return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
    }

117
    /**
118
     * {@inheritDoc}
119 120 121 122 123 124
     */
    public function getLocateExpression($str, $substr, $startPos = false)
    {
        if ($startPos == false) {
            return 'LOCATE('.$str.', '.$substr.')';
        }
125 126

        return 'LOCATE('.$str.', '.$substr.', '.$startPos.')';
127 128
    }

129
    /**
130
     * {@inheritdoc}
131
     */
132
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
133
    {
134
        switch ($unit) {
135 136 137
            case DateIntervalUnit::SECOND:
            case DateIntervalUnit::MINUTE:
            case DateIntervalUnit::HOUR:
138
                return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
139

140 141
            default:
                switch ($unit) {
142
                    case DateIntervalUnit::WEEK:
143
                        $interval *= 7;
144
                        $unit      = DateIntervalUnit::DAY;
145 146
                        break;

147
                    case DateIntervalUnit::QUARTER:
148
                        $interval *= 3;
149
                        $unit      = DateIntervalUnit::MONTH;
150 151 152 153 154
                        break;
                }

                return "DATE(" . $date . ",'" . $operator . $interval . " " . $unit . "')";
        }
155 156
    }

157 158 159
    /**
     * {@inheritDoc}
     */
160
    public function getDateDiffExpression($date1, $date2)
161
    {
162
        return 'ROUND(JULIANDAY('.$date1 . ')-JULIANDAY('.$date2.'))';
163 164
    }

165 166 167
    /**
     * {@inheritDoc}
     */
168
    protected function _getTransactionIsolationLevelSQL($level)
romanb's avatar
romanb committed
169 170
    {
        switch ($level) {
171
            case TransactionIsolationLevel::READ_UNCOMMITTED:
romanb's avatar
romanb committed
172
                return 0;
173 174 175
            case TransactionIsolationLevel::READ_COMMITTED:
            case TransactionIsolationLevel::REPEATABLE_READ:
            case TransactionIsolationLevel::SERIALIZABLE:
romanb's avatar
romanb committed
176 177
                return 1;
            default:
178
                return parent::_getTransactionIsolationLevelSQL($level);
romanb's avatar
romanb committed
179 180
        }
    }
181

182 183 184
    /**
     * {@inheritDoc}
     */
185
    public function getSetTransactionIsolationSQL($level)
romanb's avatar
romanb committed
186
    {
187
        return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
romanb's avatar
romanb committed
188
    }
189

190
    /**
191
     * {@inheritDoc}
192
     */
193 194
    public function prefersIdentityColumns()
    {
195 196
        return true;
    }
197 198

    /**
199
     * {@inheritDoc}
200
     */
201
    public function getBooleanTypeDeclarationSQL(array $field)
202 203 204
    {
        return 'BOOLEAN';
    }
205

206
    /**
207
     * {@inheritDoc}
208
     */
209
    public function getIntegerTypeDeclarationSQL(array $field)
210
    {
211
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
212 213
    }

214
    /**
215
     * {@inheritDoc}
216
     */
217
    public function getBigIntTypeDeclarationSQL(array $field)
218
    {
219 220 221 222 223
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
        if ( ! empty($field['autoincrement'])) {
            return $this->getIntegerTypeDeclarationSQL($field);
        }

224
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
225 226
    }

227
    /**
228
     * {@inheritDoc}
229
     */
230 231
    public function getTinyIntTypeDeclarationSql(array $field)
    {
232 233 234 235 236
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
        if ( ! empty($field['autoincrement'])) {
            return $this->getIntegerTypeDeclarationSQL($field);
        }

237
        return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
238 239
    }

240
    /**
241
     * {@inheritDoc}
242
     */
243
    public function getSmallIntTypeDeclarationSQL(array $field)
244
    {
245 246 247 248 249
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
        if ( ! empty($field['autoincrement'])) {
            return $this->getIntegerTypeDeclarationSQL($field);
        }

250
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
251 252
    }

253
    /**
254
     * {@inheritDoc}
255
     */
256 257
    public function getMediumIntTypeDeclarationSql(array $field)
    {
258 259 260 261 262
        //  SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
        if ( ! empty($field['autoincrement'])) {
            return $this->getIntegerTypeDeclarationSQL($field);
        }

263
        return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
264 265
    }

266
    /**
267
     * {@inheritDoc}
268
     */
269
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
270 271 272
    {
        return 'DATETIME';
    }
273

274
    /**
275
     * {@inheritDoc}
276
     */
277
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
278 279 280
    {
        return 'DATE';
    }
281

282
    /**
283
     * {@inheritDoc}
284
     */
285
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
286 287 288 289
    {
        return 'TIME';
    }

290
    /**
291
     * {@inheritDoc}
292
     */
293
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
294
    {
295 296 297 298 299
        // sqlite autoincrement is implicit for integer PKs, but not when the field is unsigned
        if ( ! empty($columnDef['autoincrement'])) {
            return '';
        }

300
        return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
301 302
    }

303 304 305 306 307 308
    /**
     * {@inheritDoc}
     */
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
    {
        return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
309 310 311
            $foreignKey->getQuotedLocalColumns($this),
            str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
            $foreignKey->getQuotedForeignColumns($this),
312 313 314 315 316
            $foreignKey->getName(),
            $foreignKey->getOptions()
        ));
    }

317
    /**
318
     * {@inheritDoc}
319
     */
320
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
321
    {
322
        $name = str_replace('.', '__', $name);
323
        $queryFields = $this->getColumnDeclarationListSQL($columns);
324

325 326 327 328 329 330
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
            foreach ($options['uniqueConstraints'] as $name => $definition) {
                $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
            }
        }

331
        if (isset($options['primary']) && ! empty($options['primary'])) {
332
            $keyColumns = array_unique(array_values($options['primary']));
333 334 335
            $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
        }

336 337 338 339 340 341
        if (isset($options['foreignKeys'])) {
            foreach ($options['foreignKeys'] as $foreignKey) {
                $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
            }
        }

342
        $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
343

344 345
        if (isset($options['alter']) && true === $options['alter']) {
            return $query;
346
        }
347

348
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
Benjamin Morel's avatar
Benjamin Morel committed
349
            foreach ($options['indexes'] as $indexDef) {
350
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
351 352
            }
        }
353

354
        if (isset($options['unique']) && ! empty($options['unique'])) {
Benjamin Morel's avatar
Benjamin Morel committed
355
            foreach ($options['unique'] as $indexDef) {
356 357 358 359
                $query[] = $this->getCreateIndexSQL($indexDef, $name);
            }
        }

360 361 362 363
        return $query;
    }

    /**
364
     * {@inheritDoc}
365
     */
366
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
367 368 369 370
    {
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
    }
371

Steve Müller's avatar
Steve Müller committed
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
    /**
     * {@inheritdoc}
     */
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
    {
        return 'BLOB';
    }

    /**
     * {@inheritdoc}
     */
    public function getBinaryMaxLength()
    {
        return 0;
    }

    /**
     * {@inheritdoc}
     */
    public function getBinaryDefaultLength()
    {
        return 0;
    }

396 397 398
    /**
     * {@inheritDoc}
     */
399
    public function getClobTypeDeclarationSQL(array $field)
400 401 402
    {
        return 'CLOB';
    }
403

404 405 406
    /**
     * {@inheritDoc}
     */
407
    public function getListTableConstraintsSQL($table)
408
    {
409
        $table = str_replace('.', '__', $table);
410
        $table = $this->quoteStringLiteral($table);
411

412
        return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = $table AND sql NOT NULL ORDER BY name";
413 414
    }

415 416 417
    /**
     * {@inheritDoc}
     */
418
    public function getListTableColumnsSQL($table, $currentDatabase = null)
419
    {
420
        $table = str_replace('.', '__', $table);
421
        $table = $this->quoteStringLiteral($table);
422

423
        return "PRAGMA table_info($table)";
424 425
    }

426 427 428
    /**
     * {@inheritDoc}
     */
429
    public function getListTableIndexesSQL($table, $currentDatabase = null)
430
    {
431
        $table = str_replace('.', '__', $table);
432
        $table = $this->quoteStringLiteral($table);
433

434
        return "PRAGMA index_list($table)";
435 436
    }

437 438 439
    /**
     * {@inheritDoc}
     */
440
    public function getListTablesSQL()
441
    {
jsor's avatar
jsor committed
442
        return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
443 444 445 446
             . "UNION ALL SELECT name FROM sqlite_temp_master "
             . "WHERE type = 'table' ORDER BY name";
    }

447 448 449
    /**
     * {@inheritDoc}
     */
450
    public function getListViewsSQL($database)
451 452 453 454
    {
        return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
    }

455 456 457
    /**
     * {@inheritDoc}
     */
458
    public function getCreateViewSQL($name, $sql)
459 460 461 462
    {
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
    }

463 464 465
    /**
     * {@inheritDoc}
     */
466
    public function getDropViewSQL($name)
467 468 469 470
    {
        return 'DROP VIEW '. $name;
    }

471 472 473
    /**
     * {@inheritDoc}
     */
474 475 476 477
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
    {
        $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);

478 479
        $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE';
        $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE');
480 481 482 483

        return $query;
    }

484 485 486
    /**
     * {@inheritDoc}
     */
487 488 489 490 491
    public function supportsIdentityColumns()
    {
        return true;
    }

492 493 494 495 496 497 498 499
    /**
     * {@inheritDoc}
     */
    public function supportsColumnCollation()
    {
        return true;
    }

500 501 502 503 504 505 506 507
    /**
     * {@inheritDoc}
     */
    public function supportsInlineColumnComments()
    {
        return true;
    }

508
    /**
509
     * {@inheritDoc}
510 511 512 513 514
     */
    public function getName()
    {
        return 'sqlite';
    }
515 516

    /**
517
     * {@inheritDoc}
518
     */
519
    public function getTruncateTableSQL($tableName, $cascade = false)
520
    {
521 522
        $tableIdentifier = new Identifier($tableName);
        $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
523

524
        return 'DELETE FROM ' . $tableName;
525
    }
526 527

    /**
Benjamin Morel's avatar
Benjamin Morel committed
528
     * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
529
     *
530
     * @param int|float $value
531
     *
532 533
     * @return float
     */
534
    public static function udfSqrt($value)
535 536 537 538 539
    {
        return sqrt($value);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
540
     * User-defined function for Sqlite that implements MOD(a, b).
541
     *
542 543
     * @param int $a
     * @param int $b
544
     *
545
     * @return int
546
     */
547
    public static function udfMod($a, $b)
548 549 550
    {
        return ($a % $b);
    }
551 552

    /**
553 554 555
     * @param string $str
     * @param string $substr
     * @param int    $offset
556
     *
557
     * @return int
558
     */
559
    public static function udfLocate($str, $substr, $offset = 0)
560
    {
561 562 563
        // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
        // So we have to make them compatible if an offset is given.
        if ($offset > 0) {
Steve Müller's avatar
Steve Müller committed
564
            $offset -= 1;
565 566
        }

567
        $pos = strpos($str, $substr, $offset);
568

569
        if ($pos !== false) {
570
            return $pos + 1;
571
        }
572

573 574
        return 0;
    }
575

576 577 578
    /**
     * {@inheritDoc}
     */
579 580 581 582
    public function getForUpdateSql()
    {
        return '';
    }
583

584 585 586 587 588
    /**
     * {@inheritDoc}
     */
    public function getInlineColumnCommentSQL($comment)
    {
Steve Müller's avatar
Steve Müller committed
589
        return '--' . str_replace("\n", "\n--", $comment) . "\n";
590 591
    }

592 593 594
    /**
     * {@inheritDoc}
     */
595 596
    protected function initializeDoctrineTypeMappings()
    {
597
        $this->doctrineTypeMapping = [
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
            'boolean'          => 'boolean',
            'tinyint'          => 'boolean',
            'smallint'         => 'smallint',
            'mediumint'        => 'integer',
            'int'              => 'integer',
            'integer'          => 'integer',
            'serial'           => 'integer',
            'bigint'           => 'bigint',
            'bigserial'        => 'bigint',
            'clob'             => 'text',
            'tinytext'         => 'text',
            'mediumtext'       => 'text',
            'longtext'         => 'text',
            'text'             => 'text',
            'varchar'          => 'string',
613
            'longvarchar'      => 'string',
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
            'varchar2'         => 'string',
            'nvarchar'         => 'string',
            'image'            => 'string',
            'ntext'            => 'string',
            'char'             => 'string',
            'date'             => 'date',
            'datetime'         => 'datetime',
            'timestamp'        => 'datetime',
            'time'             => 'time',
            'float'            => 'float',
            'double'           => 'float',
            'double precision' => 'float',
            'real'             => 'float',
            'decimal'          => 'decimal',
            'numeric'          => 'decimal',
629
            'blob'             => 'blob',
630
        ];
631
    }
632

633 634 635
    /**
     * {@inheritDoc}
     */
636 637
    protected function getReservedKeywordsClass()
    {
638
        return Keywords\SQLiteKeywords::class;
639
    }
640

641 642 643 644 645 646 647 648 649
    /**
     * {@inheritDoc}
     */
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
    {
        if ( ! $diff->fromTable instanceof Table) {
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
        }

650
        $sql = [];
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
        foreach ($diff->fromTable->getIndexes() as $index) {
            if ( ! $index->isPrimary()) {
                $sql[] = $this->getDropIndexSQL($index, $diff->name);
            }
        }

        return $sql;
    }

    /**
     * {@inheritDoc}
     */
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
    {
        if ( ! $diff->fromTable instanceof Table) {
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
        }

669
        $sql = [];
670
        $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this);
Benjamin Morel's avatar
Benjamin Morel committed
671
        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
672 673 674 675
            if ($index->isPrimary()) {
                continue;
            }

676
            $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
677 678 679 680
        }

        return $sql;
    }
681 682 683 684 685 686 687 688 689 690 691 692

    /**
     * {@inheritDoc}
     */
    protected function doModifyLimitQuery($query, $limit, $offset)
    {
        if (null === $limit && null !== $offset) {
            return $query . ' LIMIT -1 OFFSET ' . $offset;
        }

        return parent::doModifyLimitQuery($query, $limit, $offset);
    }
693

694
    /**
695
     * {@inheritDoc}
696 697 698 699 700
     */
    public function getBlobTypeDeclarationSQL(array $field)
    {
        return 'BLOB';
    }
701

702 703 704
    /**
     * {@inheritDoc}
     */
705 706
    public function getTemporaryTableName($tableName)
    {
707
        $tableName = str_replace('.', '__', $tableName);
708

709 710
        return $tableName;
    }
711 712

    /**
713 714
     * {@inheritDoc}
     *
715 716 717 718 719 720 721 722 723 724
     * Sqlite Platform emulates schema by underscoring each dot and generating tables
     * into the default database.
     *
     * This hack is implemented to be able to use SQLite as testdriver when
     * using schema supporting databases.
     */
    public function canEmulateSchemas()
    {
        return true;
    }
725

726 727 728 729 730 731 732 733
    /**
     * {@inheritDoc}
     */
    public function supportsForeignKeyConstraints()
    {
        return false;
    }

734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
    /**
     * {@inheritDoc}
     */
    public function getCreatePrimaryKeySQL(Index $index, $table)
    {
        throw new DBALException('Sqlite platform does not support alter primary key.');
    }

    /**
     * {@inheritdoc}
     */
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
    {
        throw new DBALException('Sqlite platform does not support alter foreign key.');
    }

    /**
     * {@inheritdoc}
     */
    public function getDropForeignKeySQL($foreignKey, $table)
    {
        throw new DBALException('Sqlite platform does not support alter foreign key.');
    }

758 759 760 761 762 763 764
    /**
     * {@inheritDoc}
     */
    public function getCreateConstraintSQL(Constraint $constraint, $table)
    {
        throw new DBALException('Sqlite platform does not support alter constraint.');
    }
765 766 767 768 769 770 771 772 773 774

    /**
     * {@inheritDoc}
     */
    public function getCreateTableSQL(Table $table, $createFlags = null)
    {
        $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags;

        return parent::getCreateTableSQL($table, $createFlags);
    }
775

776 777 778 779 780 781
    /**
     * {@inheritDoc}
     */
    public function getListTableForeignKeysSQL($table, $database = null)
    {
        $table = str_replace('.', '__', $table);
782
        $table = $this->quoteStringLiteral($table);
783

784
        return "PRAGMA foreign_key_list($table)";
785 786 787 788 789 790 791
    }

    /**
     * {@inheritDoc}
     */
    public function getAlterTableSQL(TableDiff $diff)
    {
792 793 794 795 796
        $sql = $this->getSimpleAlterTableSQL($diff);
        if (false !== $sql) {
            return $sql;
        }

797 798 799 800 801 802 803
        $fromTable = $diff->fromTable;
        if ( ! $fromTable instanceof Table) {
            throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
        }

        $table = clone $fromTable;

804 805 806 807
        $columns = [];
        $oldColumnNames = [];
        $newColumnNames = [];
        $columnSql = [];
808 809 810 811 812 813 814

        foreach ($table->getColumns() as $columnName => $column) {
            $columnName = strtolower($columnName);
            $columns[$columnName] = $column;
            $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
        }

815 816 817 818 819
        foreach ($diff->removedColumns as $columnName => $column) {
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
                continue;
            }

820 821
            $columnName = strtolower($columnName);
            if (isset($columns[$columnName])) {
822 823 824 825 826
                unset(
                    $columns[$columnName],
                    $oldColumnNames[$columnName],
                    $newColumnNames[$columnName]
                );
827
            }
828 829 830 831 832 833 834
        }

        foreach ($diff->renamedColumns as $oldColumnName => $column) {
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
                continue;
            }

835 836 837 838 839 840 841 842 843 844
            $oldColumnName = strtolower($oldColumnName);
            if (isset($columns[$oldColumnName])) {
                unset($columns[$oldColumnName]);
            }

            $columns[strtolower($column->getName())] = $column;

            if (isset($newColumnNames[$oldColumnName])) {
                $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
            }
845 846 847 848 849 850 851
        }

        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
                continue;
            }

852 853
            if (isset($columns[$oldColumnName])) {
                unset($columns[$oldColumnName]);
854 855
            }

856
            $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
857

858 859
            if (isset($newColumnNames[$oldColumnName])) {
                $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
860 861 862
            }
        }

863 864 865 866
        foreach ($diff->addedColumns as $columnName => $column) {
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
                continue;
            }
867

868
            $columns[strtolower($columnName)] = $column;
869 870
        }

871 872
        $sql = [];
        $tableSql = [];
873
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
874 875
            $dataTable = new Table('__temp__'.$table->getName());

876
            $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
877
            $newTable->addOption('alter', true);
878

879 880 881
            $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
            //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
            $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
882
            $sql[] = $this->getDropTableSQL($fromTable);
883 884 885 886 887 888

            $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
            $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
            $sql[] = $this->getDropTableSQL($dataTable);

            if ($diff->newName && $diff->newName != $diff->name) {
889
                $renamedTable = $diff->getNewName();
890 891 892
                $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this);
            }

893 894
            $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
        }
895 896 897 898

        return array_merge($sql, $tableSql, $columnSql);
    }

Benjamin Morel's avatar
Benjamin Morel committed
899 900 901 902 903
    /**
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
     *
     * @return array|bool
     */
904 905
    private function getSimpleAlterTableSQL(TableDiff $diff)
    {
906 907 908 909 910
        // Suppress changes on integer type autoincrement columns.
        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
            if ( ! $columnDiff->fromColumn instanceof Column ||
                ! $columnDiff->column instanceof Column ||
                ! $columnDiff->column->getAutoincrement() ||
911
                ! $columnDiff->column->getType() instanceof Types\IntegerType
912 913 914 915 916 917 918 919 920 921
            ) {
                continue;
            }

            if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
                unset($diff->changedColumns[$oldColumnName]);

                continue;
            }

922
            $fromColumnType = $columnDiff->fromColumn->getType();
923

924
            if ($fromColumnType instanceof Types\SmallIntType || $fromColumnType instanceof Types\BigIntType) {
925 926 927 928
                unset($diff->changedColumns[$oldColumnName]);
            }
        }

929 930 931
        if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
                || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
                || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
932
                || ! empty($diff->renamedIndexes)
933 934 935 936 937 938
        ) {
            return false;
        }

        $table = new Table($diff->name);

939 940 941
        $sql = [];
        $tableSql = [];
        $columnSql = [];
942

Benjamin Morel's avatar
Benjamin Morel committed
943
        foreach ($diff->addedColumns as $column) {
944 945 946 947
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
                continue;
            }

948
            $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
949
            $type = $field['type'];
950 951
            switch (true) {
                case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
952 953 954
                case $type instanceof Types\DateTimeType && $field['default'] == $this->getCurrentTimestampSQL():
                case $type instanceof Types\DateType && $field['default'] == $this->getCurrentDateSQL():
                case $type instanceof Types\TimeType && $field['default'] == $this->getCurrentTimeSQL():
955 956 957 958
                    return false;
            }

            $field['name'] = $column->getQuotedName($this);
959
            if ($type instanceof Types\StringType && $field['length'] === null) {
960 961 962 963 964 965 966 967
                $field['length'] = 255;
            }

            $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field);
        }

        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
            if ($diff->newName !== false) {
968
                $newTable = new Identifier($diff->newName);
969 970 971
                $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
            }
        }
972 973 974 975

        return array_merge($sql, $tableSql, $columnSql);
    }

Benjamin Morel's avatar
Benjamin Morel committed
976 977 978 979 980
    /**
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
     *
     * @return array
     */
981
    private function getColumnNamesInAlteredTable(TableDiff $diff)
982
    {
983
        $columns = [];
984

985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        foreach ($diff->fromTable->getColumns() as $columnName => $column) {
            $columns[strtolower($columnName)] = $column->getName();
        }

        foreach ($diff->removedColumns as $columnName => $column) {
            $columnName = strtolower($columnName);
            if (isset($columns[$columnName])) {
                unset($columns[$columnName]);
            }
        }

        foreach ($diff->renamedColumns as $oldColumnName => $column) {
            $columnName = $column->getName();
            $columns[strtolower($oldColumnName)] = $columnName;
            $columns[strtolower($columnName)] = $columnName;
        }

        foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
            $columnName = $columnDiff->column->getName();
            $columns[strtolower($oldColumnName)] = $columnName;
            $columns[strtolower($columnName)] = $columnName;
        }

        foreach ($diff->addedColumns as $columnName => $column) {
            $columns[strtolower($columnName)] = $columnName;
        }

        return $columns;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1015 1016 1017 1018 1019
    /**
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
     *
     * @return \Doctrine\DBAL\Schema\Index[]
     */
1020 1021 1022 1023 1024 1025
    private function getIndexesInAlteredTable(TableDiff $diff)
    {
        $indexes = $diff->fromTable->getIndexes();
        $columnNames = $this->getColumnNamesInAlteredTable($diff);

        foreach ($indexes as $key => $index) {
1026 1027 1028 1029 1030 1031
            foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
                if (strtolower($key) === strtolower($oldIndexName)) {
                    unset($indexes[$key]);
                }
            }

1032
            $changed = false;
1033
            $indexColumns = [];
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
            foreach ($index->getColumns() as $columnName) {
                $normalizedColumnName = strtolower($columnName);
                if ( ! isset($columnNames[$normalizedColumnName])) {
                    unset($indexes[$key]);
                    continue 2;
                } else {
                    $indexColumns[] = $columnNames[$normalizedColumnName];
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
                        $changed = true;
                    }
                }
            }

            if ($changed) {
                $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
1049 1050 1051 1052
            }
        }

        foreach ($diff->removedIndexes as $index) {
1053 1054 1055
            $indexName = strtolower($index->getName());
            if (strlen($indexName) && isset($indexes[$indexName])) {
                unset($indexes[$indexName]);
1056 1057 1058
            }
        }

1059
        foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
1060 1061 1062 1063 1064
            $indexName = strtolower($index->getName());
            if (strlen($indexName)) {
                $indexes[$indexName] = $index;
            } else {
                $indexes[] = $index;
1065 1066 1067
            }
        }

1068 1069 1070
        return $indexes;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1071 1072 1073 1074 1075
    /**
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
     *
     * @return array
     */
1076 1077 1078 1079 1080 1081 1082
    private function getForeignKeysInAlteredTable(TableDiff $diff)
    {
        $foreignKeys = $diff->fromTable->getForeignKeys();
        $columnNames = $this->getColumnNamesInAlteredTable($diff);

        foreach ($foreignKeys as $key => $constraint) {
            $changed = false;
1083
            $localColumns = [];
1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
            foreach ($constraint->getLocalColumns() as $columnName) {
                $normalizedColumnName = strtolower($columnName);
                if ( ! isset($columnNames[$normalizedColumnName])) {
                    unset($foreignKeys[$key]);
                    continue 2;
                } else {
                    $localColumns[] = $columnNames[$normalizedColumnName];
                    if ($columnName !== $columnNames[$normalizedColumnName]) {
                        $changed = true;
                    }
                }
            }

            if ($changed) {
                $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
            }
        }

        foreach ($diff->removedForeignKeys as $constraint) {
            $constraintName = strtolower($constraint->getName());
            if (strlen($constraintName) && isset($foreignKeys[$constraintName])) {
                unset($foreignKeys[$constraintName]);
            }
        }

        foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
            $constraintName = strtolower($constraint->getName());
            if (strlen($constraintName)) {
                $foreignKeys[$constraintName] = $constraint;
            } else {
                $foreignKeys[] = $constraint;
            }
        }

        return $foreignKeys;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1121 1122 1123 1124 1125
    /**
     * @param \Doctrine\DBAL\Schema\TableDiff $diff
     *
     * @return array
     */
1126 1127
    private function getPrimaryIndexInAlteredTable(TableDiff $diff)
    {
1128
        $primaryIndex = [];
1129 1130

        foreach ($this->getIndexesInAlteredTable($diff) as $index) {
1131
            if ($index->isPrimary()) {
1132
                $primaryIndex = [$index->getName() => $index];
1133 1134 1135 1136 1137
            }
        }

        return $primaryIndex;
    }
1138
}