Completed
Push — develop ( dcb0ff...425513 )
by Sergei
23s queued 13s
created

DB2Platform::getCurrentDatabaseExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\Platforms\Exception\NotSupported;
8
use Doctrine\DBAL\Schema\ColumnDiff;
9
use Doctrine\DBAL\Schema\Identifier;
10
use Doctrine\DBAL\Schema\Index;
11
use Doctrine\DBAL\Schema\TableDiff;
12
use Doctrine\DBAL\Types\Type;
13
use Doctrine\DBAL\Types\Types;
14
use function array_merge;
15
use function count;
16
use function current;
17
use function explode;
18
use function implode;
19
use function sprintf;
20
use function strpos;
21
use function strtoupper;
22
23
class DB2Platform extends AbstractPlatform
24
{
25
    /**
26
     * {@inheritDoc}
27
     */
28 2113
    public function getBlobTypeDeclarationSQL(array $field) : string
29
    {
30
        // todo blob(n) with $field['length'];
31 2113
        return 'BLOB(1M)';
32
    }
33
34
    /**
35
     * {@inheritDoc}
36
     */
37 2006
    public function initializeDoctrineTypeMappings() : void
38
    {
39 2006
        $this->doctrineTypeMapping = [
40
            'bigint'    => 'bigint',
41
            'binary'    => 'binary',
42
            'blob'      => 'blob',
43
            'character' => 'string',
44
            'clob'      => 'text',
45
            'date'      => 'date',
46
            'decimal'   => 'decimal',
47
            'double'    => 'float',
48
            'integer'   => 'integer',
49
            'real'      => 'float',
50
            'smallint'  => 'smallint',
51
            'time'      => 'time',
52
            'timestamp' => 'datetime',
53
            'varbinary' => 'binary',
54
            'varchar'   => 'string',
55
        ];
56 2006
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 2225
    public function isCommentedDoctrineType(Type $doctrineType) : bool
62
    {
63 2225
        if ($doctrineType->getName() === Types::BOOLEAN) {
64
            // We require a commented boolean type in order to distinguish between boolean and smallint
65
            // as both (have to) map to the same native type.
66 1329
            return true;
67
        }
68
69 2224
        return parent::isCommentedDoctrineType($doctrineType);
70
    }
71
72
    /**
73
     * {@inheritDoc}
74
     */
75 533
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
76
    {
77 533
        return $this->getCharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     */
83 1733
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
84
    {
85 1733
        return $this->getVarcharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
86
    }
87
88
    /**
89
     * {@inheritDoc}
90
     */
91 2115
    public function getClobTypeDeclarationSQL(array $field) : string
92
    {
93
        // todo clob(n) with $field['length'];
94 2115
        return 'CLOB(1M)';
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 2202
    public function getName() : string
101
    {
102 2202
        return 'db2';
103
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 2026
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
109
    {
110 2026
        return 'SMALLINT';
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 2196
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
117
    {
118 2196
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
119
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124 1963
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
125
    {
126 1963
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
127
    }
128
129
    /**
130
     * {@inheritDoc}
131
     */
132 2003
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
133
    {
134 2003
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140 2196
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
141
    {
142 2196
        $autoinc = '';
143 2196
        if (! empty($columnDef['autoincrement'])) {
144 2063
            $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
145
        }
146
147 2196
        return $autoinc;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 1154
    public function getBitAndComparisonExpression(string $value1, string $value2) : string
154
    {
155 1154
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 218
    public function getBitOrComparisonExpression(string $value1, string $value2) : string
162
    {
163 218
        return 'BITOR(' . $value1 . ', ' . $value2 . ')';
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 2005
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
170
    {
171
        switch ($unit) {
172 2005
            case DateIntervalUnit::WEEK:
173 1989
                $interval = $this->multiplyInterval($interval, 7);
174 1989
                $unit     = DateIntervalUnit::DAY;
175 1989
                break;
176
177 2005
            case DateIntervalUnit::QUARTER:
178 1981
                $interval = $this->multiplyInterval($interval, 3);
179 1981
                $unit     = DateIntervalUnit::MONTH;
180 1981
                break;
181
        }
182
183 2005
        return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 1952
    public function getDateDiffExpression(string $date1, string $date2) : string
190
    {
191 1952
        return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197 2102
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
198
    {
199 2102
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
200 1926
            return 'TIMESTAMP(0) WITH DEFAULT';
201
        }
202
203 2102
        return 'TIMESTAMP(0)';
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 2046
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
210
    {
211 2046
        return 'DATE';
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 2045
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
218
    {
219 2045
        return 'TIME';
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225 2015
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
226
    {
227 2015
        $tableIdentifier = new Identifier($tableName);
228
229 2015
        return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE';
230
    }
231
232
    /**
233
     * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited.
234
     */
235 1522
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
236
    {
237 1522
        $table = $this->quoteStringLiteral($table);
238
239
        // We do the funky subquery and join syscat.columns.default this crazy way because
240
        // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB,
241
        // it wants shorter stuff like a varchar.
242
        return "
243
        SELECT
244
          cols.default,
245
          subq.*
246
        FROM (
247
               SELECT DISTINCT
248
                 c.tabschema,
249
                 c.tabname,
250
                 c.colname,
251
                 c.colno,
252
                 c.typename,
253
                 c.nulls,
254
                 c.length,
255
                 c.scale,
256
                 c.identity,
257
                 tc.type AS tabconsttype,
258
                 c.remarks AS comment,
259
                 k.colseq,
260
                 CASE
261
                 WHEN c.generated = 'D' THEN 1
262
                 ELSE 0
263
                 END     AS autoincrement
264
               FROM syscat.columns c
265
                 LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
266
                     ON (k.tabschema = tc.tabschema
267
                         AND k.tabname = tc.tabname
268
                         AND tc.type = 'P'))
269
                   ON (c.tabschema = k.tabschema
270
                       AND c.tabname = k.tabname
271
                       AND c.colname = k.colname)
272 1522
               WHERE UPPER(c.tabname) = UPPER(" . $table . ')
273
               ORDER BY c.colno
274
             ) subq
275
          JOIN syscat.columns cols
276
            ON subq.tabschema = cols.tabschema
277
               AND subq.tabname = cols.tabname
278
               AND subq.colno = cols.colno
279
        ORDER BY subq.colno
280
        ';
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286 264
    public function getListTablesSQL() : string
287
    {
288 264
        return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 162
    public function getListViewsSQL(string $database) : string
295
    {
296 162
        return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS';
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302 178
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
303
    {
304 178
        $table = $this->quoteStringLiteral($table);
305
306
        return "SELECT   idx.INDNAME AS key_name,
307
                         idxcol.COLNAME AS column_name,
308
                         CASE
309
                             WHEN idx.UNIQUERULE = 'P' THEN 1
310
                             ELSE 0
311
                         END AS primary,
312
                         CASE
313
                             WHEN idx.UNIQUERULE = 'D' THEN 1
314
                             ELSE 0
315
                         END AS non_unique
316
                FROM     SYSCAT.INDEXES AS idx
317
                JOIN     SYSCAT.INDEXCOLUSE AS idxcol
318
                ON       idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME
319 178
                WHERE    idx.TABNAME = UPPER(" . $table . ')
320
                ORDER BY idxcol.COLSEQ ASC';
321
    }
322
323
    /**
324
     * {@inheritDoc}
325
     */
326 178
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
327
    {
328 178
        $table = $this->quoteStringLiteral($table);
329
330
        return "SELECT   fkcol.COLNAME AS local_column,
331
                         fk.REFTABNAME AS foreign_table,
332
                         pkcol.COLNAME AS foreign_column,
333
                         fk.CONSTNAME AS index_name,
334
                         CASE
335
                             WHEN fk.UPDATERULE = 'R' THEN 'RESTRICT'
336
                             ELSE NULL
337
                         END AS on_update,
338
                         CASE
339
                             WHEN fk.DELETERULE = 'C' THEN 'CASCADE'
340
                             WHEN fk.DELETERULE = 'N' THEN 'SET NULL'
341
                             WHEN fk.DELETERULE = 'R' THEN 'RESTRICT'
342
                             ELSE NULL
343
                         END AS on_delete
344
                FROM     SYSCAT.REFERENCES AS fk
345
                JOIN     SYSCAT.KEYCOLUSE AS fkcol
346
                ON       fk.CONSTNAME = fkcol.CONSTNAME
347
                AND      fk.TABSCHEMA = fkcol.TABSCHEMA
348
                AND      fk.TABNAME = fkcol.TABNAME
349
                JOIN     SYSCAT.KEYCOLUSE AS pkcol
350
                ON       fk.REFKEYNAME = pkcol.CONSTNAME
351
                AND      fk.REFTABSCHEMA = pkcol.TABSCHEMA
352
                AND      fk.REFTABNAME = pkcol.TABNAME
353 178
                WHERE    fk.TABNAME = UPPER(" . $table . ')
354
                ORDER BY fkcol.COLSEQ ASC';
355
    }
356
357
    /**
358
     * {@inheritDoc}
359
     */
360 1963
    public function getCreateViewSQL(string $name, string $sql) : string
361
    {
362 1963
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
363
    }
364
365
    /**
366
     * {@inheritDoc}
367
     */
368 1963
    public function getDropViewSQL(string $name) : string
369
    {
370 1963
        return 'DROP VIEW ' . $name;
371
    }
372
373
    /**
374
     * {@inheritDoc}
375
     */
376 1876
    public function getCreateDatabaseSQL(string $database) : string
377
    {
378 1876
        return 'CREATE DATABASE ' . $database;
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     */
384 1876
    public function getDropDatabaseSQL(string $database) : string
385
    {
386 1876
        return 'DROP DATABASE ' . $database;
387
    }
388
389
    /**
390
     * {@inheritDoc}
391
     */
392 265
    public function supportsCreateDropDatabase() : bool
393
    {
394 265
        return false;
395
    }
396
397
    /**
398
     * {@inheritDoc}
399
     */
400 1
    public function supportsReleaseSavepoints() : bool
401
    {
402 1
        return false;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408 2197
    public function supportsCommentOnStatement() : bool
409
    {
410 2197
        return true;
411
    }
412
413
    /**
414
     * {@inheritDoc}
415
     */
416 1951
    public function getCurrentDateSQL() : string
417
    {
418 1951
        return 'CURRENT DATE';
419
    }
420
421
    /**
422
     * {@inheritDoc}
423
     */
424 1949
    public function getCurrentTimeSQL() : string
425
    {
426 1949
        return 'CURRENT TIME';
427
    }
428
429
    /**
430
     * {@inheritDoc}
431
     */
432 1949
    public function getCurrentTimestampSQL() : string
433
    {
434 1949
        return 'CURRENT TIMESTAMP';
435
    }
436
437
    /**
438
     * {@inheritDoc}
439
     */
440 526
    public function getIndexDeclarationSQL(string $name, Index $index) : string
441
    {
442
        // Index declaration in statements like CREATE TABLE is not supported.
443 526
        throw NotSupported::new(__METHOD__);
444
    }
445
446
    /**
447
     * {@inheritDoc}
448
     */
449 2196
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
450
    {
451 2196
        $indexes = [];
452 2196
        if (isset($options['indexes'])) {
453 2196
            $indexes = $options['indexes'];
454
        }
455 2196
        $options['indexes'] = [];
456
457 2196
        $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
458
459 2196
        foreach ($indexes as $definition) {
460 2094
            $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
461
        }
462
463 2196
        return $sqls;
464
    }
465
466
    /**
467
     * {@inheritDoc}
468
     */
469 1795
    public function getAlterTableSQL(TableDiff $diff) : array
470
    {
471 1795
        $sql         = [];
472 1795
        $columnSql   = [];
473 1795
        $commentsSQL = [];
474
475 1795
        $queryParts = [];
476 1795
        foreach ($diff->addedColumns as $column) {
477 1079
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
478
                continue;
479
            }
480
481 1079
            $columnDef = $column->toArray();
482 1079
            $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
483
484
            // Adding non-nullable columns to a table requires a default value to be specified.
485 1079
            if (! empty($columnDef['notnull']) &&
486 1079
                ! isset($columnDef['default']) &&
487 1079
                empty($columnDef['autoincrement'])
488
            ) {
489 1030
                $queryPart .= ' WITH DEFAULT';
490
            }
491
492 1079
            $queryParts[] = $queryPart;
493
494 1079
            $comment = $this->getColumnComment($column);
495
496 1079
            if ($comment === null || $comment === '') {
497 1078
                continue;
498
            }
499
500 851
            $commentsSQL[] = $this->getCommentOnColumnSQL(
501 851
                $diff->getName($this)->getQuotedName($this),
502 851
                $column->getQuotedName($this),
503 1
                $comment
504
            );
505
        }
506
507 1795
        foreach ($diff->removedColumns as $column) {
508 1078
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
509
                continue;
510
            }
511
512 1078
            $queryParts[] =  'DROP COLUMN ' . $column->getQuotedName($this);
513
        }
514
515 1795
        foreach ($diff->changedColumns as $columnDiff) {
516 1782
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
517
                continue;
518
            }
519
520 1782
            if ($columnDiff->hasChanged('comment')) {
521 974
                $commentsSQL[] = $this->getCommentOnColumnSQL(
522 974
                    $diff->getName($this)->getQuotedName($this),
523 974
                    $columnDiff->column->getQuotedName($this),
524 974
                    $this->getColumnComment($columnDiff->column)
525
                );
526
527 974
                if (count($columnDiff->changedProperties) === 1) {
528 974
                    continue;
529
                }
530
            }
531
532 1778
            $this->gatherAlterColumnSQL($diff->getName($this), $columnDiff, $sql, $queryParts);
533
        }
534
535 1795
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
536 1027
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
537
                continue;
538
            }
539
540 1027
            $oldColumnName = new Identifier($oldColumnName);
541
542 1027
            $queryParts[] =  'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
543 1027
                ' TO ' . $column->getQuotedName($this);
544
        }
545
546 1795
        $tableSql = [];
547
548 1795
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
549 1795
            if (count($queryParts) > 0) {
550 1789
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(' ', $queryParts);
551
            }
552
553
            // Some table alteration operations require a table reorganization.
554 1795
            if (! empty($diff->removedColumns) || ! empty($diff->changedColumns)) {
555 1788
                $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')";
556
            }
557
558 1795
            $sql = array_merge($sql, $commentsSQL);
559
560 1795
            $newName = $diff->getNewName();
561
562 1795
            if ($newName !== null) {
563 952
                $sql[] = sprintf(
564 2
                    'RENAME TABLE %s TO %s',
565 952
                    $diff->getName($this)->getQuotedName($this),
566 952
                    $newName->getQuotedName($this)
567
                );
568
            }
569
570 1795
            $sql = array_merge(
571 1795
                $this->getPreAlterTableIndexForeignKeySQL($diff),
572 1795
                $sql,
573 1795
                $this->getPostAlterTableIndexForeignKeySQL($diff)
574
            );
575
        }
576
577 1795
        return array_merge($sql, $tableSql, $columnSql);
578
    }
579
580
    /**
581
     * Gathers the table alteration SQL for a given column diff.
582
     *
583
     * @param Identifier $table      The table to gather the SQL for.
584
     * @param ColumnDiff $columnDiff The column diff to evaluate.
585
     * @param string[]   $sql        The sequence of table alteration statements to fill.
586
     * @param mixed[]    $queryParts The sequence of column alteration clauses to fill.
587
     */
588 1778
    private function gatherAlterColumnSQL(Identifier $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts) : void
589
    {
590 1778
        $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
591
592 1778
        if (empty($alterColumnClauses)) {
593 1452
            return;
594
        }
595
596
        // If we have a single column alteration, we can append the clause to the main query.
597 1776
        if (count($alterColumnClauses) === 1) {
598 1775
            $queryParts[] = current($alterColumnClauses);
599
600 1775
            return;
601
        }
602
603
        // We have multiple alterations for the same column,
604
        // so we need to trigger a complete ALTER TABLE statement
605
        // for each ALTER COLUMN clause.
606 951
        foreach ($alterColumnClauses as $alterColumnClause) {
607 951
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
608
        }
609 951
    }
610
611
    /**
612
     * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff.
613
     *
614
     * @param ColumnDiff $columnDiff The column diff to evaluate.
615
     *
616
     * @return string[]
617
     */
618 1778
    private function getAlterColumnClausesSQL(ColumnDiff $columnDiff) : array
619
    {
620 1778
        $column = $columnDiff->column->toArray();
621
622 1778
        $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this);
623
624 1778
        if ($column['columnDefinition']) {
625 1676
            return [$alterClause . ' ' . $column['columnDefinition']];
626
        }
627
628 1753
        $clauses = [];
629
630 1753
        if ($columnDiff->hasChanged('type') ||
631 1750
            $columnDiff->hasChanged('length') ||
632 1749
            $columnDiff->hasChanged('precision') ||
633 1748
            $columnDiff->hasChanged('scale') ||
634 1753
            $columnDiff->hasChanged('fixed')
635
        ) {
636 1746
            $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
637
        }
638
639 1753
        if ($columnDiff->hasChanged('notnull')) {
640 1529
            $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
641
        }
642
643 1753
        if ($columnDiff->hasChanged('default')) {
644 1571
            if (isset($column['default'])) {
645 1570
                $defaultClause = $this->getDefaultValueDeclarationSQL($column);
646
647 1570
                if ($defaultClause) {
648 1570
                    $clauses[] = $alterClause . ' SET' . $defaultClause;
649
                }
650
            } else {
651 1520
                $clauses[] = $alterClause . ' DROP DEFAULT';
652
            }
653
        }
654
655 1753
        return $clauses;
656
    }
657
658
    /**
659
     * {@inheritDoc}
660
     */
661 1795
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
662
    {
663 1795
        $sql   = [];
664 1795
        $table = $diff->getName($this)->getQuotedName($this);
665
666 1795
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
667 163
            foreach ($diff->addedIndexes as $addKey => $addIndex) {
668
                if ($remIndex->getColumns() === $addIndex->getColumns()) {
669
                    if ($remIndex->isPrimary()) {
670
                        $sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
671
                    } elseif ($remIndex->isUnique()) {
672
                        $sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this);
673
                    } else {
674
                        $sql[] = $this->getDropIndexSQL($remIndex, $table);
675
                    }
676
677
                    $sql[] = $this->getCreateIndexSQL($addIndex, $table);
678
679
                    unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]);
680
681
                    break;
682
                }
683
            }
684
        }
685
686 1795
        $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
687
688 1795
        return $sql;
689
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694 504
    protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName) : array
695
    {
696 504
        if (strpos($tableName, '.') !== false) {
697 277
            [$schema]     = explode('.', $tableName);
698 277
            $oldIndexName = $schema . '.' . $oldIndexName;
699
        }
700
701 504
        return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
702
    }
703
704
    /**
705
     * {@inheritDoc}
706
     */
707 2206
    public function getDefaultValueDeclarationSQL(array $field) : string
708
    {
709 2206
        if (! empty($field['autoincrement'])) {
710 1607
            return '';
711
        }
712
713 2205
        if (isset($field['version']) && $field['version']) {
714
            if ((string) $field['type'] !== 'DateTime') {
715
                $field['default'] = '1';
716
            }
717
        }
718
719 2205
        return parent::getDefaultValueDeclarationSQL($field);
720
    }
721
722
    /**
723
     * {@inheritDoc}
724
     */
725 84
    public function getEmptyIdentityInsertSQL(string $tableName, string $identifierColumnName) : string
726
    {
727 84
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
728
    }
729
730
    /**
731
     * {@inheritDoc}
732
     */
733 1917
    public function getCreateTemporaryTableSnippetSQL() : string
734
    {
735 1917
        return 'DECLARE GLOBAL TEMPORARY TABLE';
736
    }
737
738
    /**
739
     * {@inheritDoc}
740
     */
741 116
    public function getTemporaryTableName(string $tableName) : string
742
    {
743 116
        return 'SESSION.' . $tableName;
744
    }
745
746
    /**
747
     * {@inheritDoc}
748
     */
749 1943
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
750
    {
751 1943
        $where = [];
752
753 1943
        if ($offset > 0) {
754 1942
            $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1);
755
        }
756
757 1943
        if ($limit !== null) {
758 1942
            $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit);
759
        }
760
761 1943
        if (empty($where)) {
762 1937
            return $query;
763
        }
764
765
        // Todo OVER() needs ORDER BY data!
766 1942
        return sprintf(
767 1
            'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
768 1942
            $query,
769 1942
            implode(' AND ', $where)
770
        );
771
    }
772
773
    /**
774
     * {@inheritDoc}
775
     */
776 1973
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
777
    {
778 1973
        if ($start === null) {
779 1973
            return sprintf('LOCATE(%s, %s)', $substring, $string);
780
        }
781
782 1973
        return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start);
783
    }
784
785
    /**
786
     * {@inheritDoc}
787
     */
788 1972
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
789
    {
790 1972
        if ($length === null) {
791 1972
            return sprintf('SUBSTR(%s, %s)', $string, $start);
792
        }
793
794 1971
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
795
    }
796
797
    /**
798
     * {@inheritDoc}
799
     */
800 162
    public function getCurrentDatabaseExpression() : string
801
    {
802 162
        return 'CURRENT_USER';
803
    }
804
805
    /**
806
     * {@inheritDoc}
807
     */
808 1793
    public function supportsIdentityColumns() : bool
809
    {
810 1793
        return true;
811
    }
812
813
    /**
814
     * {@inheritDoc}
815
     */
816
    public function prefersIdentityColumns() : bool
817
    {
818 1751
        return true;
819
    }
820 1751
821
    /**
822
     * {@inheritDoc}
823
     *
824
     * DB2 returns all column names in SQL result sets in uppercase.
825
     */
826 1871
    public function getSQLResultCasing(string $column) : string
827
    {
828 1871
        return strtoupper($column);
829
    }
830
831
    /**
832
     * {@inheritDoc}
833
     */
834 256
    public function getForUpdateSQL() : string
835
    {
836 256
        return ' WITH RR USE AND KEEP UPDATE LOCKS';
837
    }
838
839
    /**
840
     * {@inheritDoc}
841
     */
842
    public function getDummySelectSQL(string $expression = '1') : string
843
    {
844
        return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression);
845
    }
846 259
847
    /**
848 259
     * {@inheritDoc}
849
     *
850
     * DB2 supports savepoints, but they work semantically different than on other vendor platforms.
851
     *
852
     * TODO: We have to investigate how to get DB2 up and running with savepoints.
853
     */
854 2236
    public function supportsSavepoints() : bool
855
    {
856 2236
        return false;
857
    }
858
859
    /**
860
     * {@inheritDoc}
861
     */
862
    protected function getReservedKeywordsClass() : string
863
    {
864
        return Keywords\DB2Keywords::class;
865
    }
866
}
867