Completed
Push — master ( 9728d9...4d9a08 )
by Sergei
27s queued 16s
created

DB2Platform::_getCreateTableSQL()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 15
rs 10
c 0
b 0
f 0
ccs 9
cts 9
cp 1
cc 3
nc 4
nop 3
crap 3
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 35
    public function getBlobTypeDeclarationSQL(array $field) : string
29
    {
30
        // todo blob(n) with $field['length'];
31 35
        return 'BLOB(1M)';
32
    }
33
34
    /**
35
     * {@inheritDoc}
36
     */
37 136
    public function initializeDoctrineTypeMappings() : void
38
    {
39 136
        $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 136
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 1236
    public function isCommentedDoctrineType(Type $doctrineType) : bool
62
    {
63 1236
        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 44
            return true;
67
        }
68
69 1207
        return parent::isCommentedDoctrineType($doctrineType);
70
    }
71
72
    /**
73
     * {@inheritDoc}
74
     */
75 55
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
76
    {
77 55
        return $this->getCharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     */
83 55
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
84
    {
85 55
        return $this->getVarcharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA';
86
    }
87
88
    /**
89
     * {@inheritDoc}
90
     */
91 126
    public function getClobTypeDeclarationSQL(array $field) : string
92
    {
93
        // todo clob(n) with $field['length'];
94 126
        return 'CLOB(1M)';
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 189
    public function getName() : string
101
    {
102 189
        return 'db2';
103
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 71
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
109
    {
110 71
        return 'SMALLINT';
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 443
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
117
    {
118 443
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
119
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124 42
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
125
    {
126 42
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
127
    }
128
129
    /**
130
     * {@inheritDoc}
131
     */
132 28
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
133
    {
134 28
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140 443
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
141
    {
142 443
        $autoinc = '';
143 443
        if (! empty($columnDef['autoincrement'])) {
144 74
            $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
145
        }
146
147 443
        return $autoinc;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 28
    public function getBitAndComparisonExpression(string $value1, string $value2) : string
154
    {
155 28
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 28
    public function getBitOrComparisonExpression(string $value1, string $value2) : string
162
    {
163 28
        return 'BITOR(' . $value1 . ', ' . $value2 . ')';
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 75
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
170
    {
171
        switch ($unit) {
172 75
            case DateIntervalUnit::WEEK:
173 33
                $interval = $this->multiplyInterval($interval, 7);
174 33
                $unit     = DateIntervalUnit::DAY;
175 33
                break;
176
177 69
            case DateIntervalUnit::QUARTER:
178 33
                $interval = $this->multiplyInterval($interval, 3);
179 33
                $unit     = DateIntervalUnit::MONTH;
180 33
                break;
181
        }
182
183 75
        return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 30
    public function getDateDiffExpression(string $date1, string $date2) : string
190
    {
191 30
        return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197 50
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
198
    {
199 50
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
200 27
            return 'TIMESTAMP(0) WITH DEFAULT';
201
        }
202
203 50
        return 'TIMESTAMP(0)';
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 46
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
210
    {
211 46
        return 'DATE';
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 46
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
218
    {
219 46
        return 'TIME';
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225 62
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
226
    {
227 62
        $tableIdentifier = new Identifier($tableName);
228
229 62
        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 76
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
236
    {
237 76
        $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 76
               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 10
    public function getListTablesSQL() : string
287
    {
288 10
        return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 1
    public function getListViewsSQL(string $database) : string
295
    {
296 1
        return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS';
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302 70
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
303
    {
304 70
        $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 70
                WHERE    idx.TABNAME = UPPER(" . $table . ')
320
                ORDER BY idxcol.COLSEQ ASC';
321
    }
322
323
    /**
324
     * {@inheritDoc}
325
     */
326 68
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
327
    {
328 68
        $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 68
                WHERE    fk.TABNAME = UPPER(" . $table . ')
354
                ORDER BY fkcol.COLSEQ ASC';
355
    }
356
357
    /**
358
     * {@inheritDoc}
359
     */
360 28
    public function getCreateViewSQL(string $name, string $sql) : string
361
    {
362 28
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
363
    }
364
365
    /**
366
     * {@inheritDoc}
367
     */
368 28
    public function getDropViewSQL(string $name) : string
369
    {
370 28
        return 'DROP VIEW ' . $name;
371
    }
372
373
    /**
374
     * {@inheritDoc}
375
     */
376 27
    public function getCreateDatabaseSQL(string $database) : string
377
    {
378 27
        return 'CREATE DATABASE ' . $database;
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     */
384 27
    public function getDropDatabaseSQL(string $database) : string
385
    {
386 27
        return 'DROP DATABASE ' . $database;
387
    }
388
389
    /**
390
     * {@inheritDoc}
391
     */
392 27
    public function supportsCreateDropDatabase() : bool
393
    {
394 27
        return false;
395
    }
396
397
    /**
398
     * {@inheritDoc}
399
     */
400 27
    public function supportsReleaseSavepoints() : bool
401
    {
402 27
        return false;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408 480
    public function supportsCommentOnStatement() : bool
409
    {
410 480
        return true;
411
    }
412
413
    /**
414
     * {@inheritDoc}
415
     */
416 55
    public function getCurrentDateSQL() : string
417
    {
418 55
        return 'CURRENT DATE';
419
    }
420
421
    /**
422
     * {@inheritDoc}
423
     */
424 28
    public function getCurrentTimeSQL() : string
425
    {
426 28
        return 'CURRENT TIME';
427
    }
428
429
    /**
430
     * {@inheritDoc}
431
     */
432 55
    public function getCurrentTimestampSQL() : string
433
    {
434 55
        return 'CURRENT TIMESTAMP';
435
    }
436
437
    /**
438
     * {@inheritDoc}
439
     */
440 27
    public function getIndexDeclarationSQL(string $name, Index $index) : string
441
    {
442
        // Index declaration in statements like CREATE TABLE is not supported.
443 27
        throw NotSupported::new(__METHOD__);
444
    }
445
446
    /**
447
     * {@inheritDoc}
448
     */
449 453
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
450
    {
451 453
        $indexes = [];
452 453
        if (isset($options['indexes'])) {
453 453
            $indexes = $options['indexes'];
454
        }
455 453
        $options['indexes'] = [];
456
457 453
        $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
458
459 453
        foreach ($indexes as $definition) {
460 115
            $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
461
        }
462
463 453
        return $sqls;
464
    }
465
466
    /**
467
     * {@inheritDoc}
468
     */
469 663
    public function getAlterTableSQL(TableDiff $diff) : array
470
    {
471 663
        $sql         = [];
472 663
        $columnSql   = [];
473 663
        $commentsSQL = [];
474
475 663
        $queryParts = [];
476 663
        foreach ($diff->addedColumns as $column) {
477 109
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
478
                continue;
479
            }
480
481 109
            $columnDef = $column->toArray();
482 109
            $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 109
            if (! empty($columnDef['notnull']) &&
486 109
                ! isset($columnDef['default']) &&
487 109
                empty($columnDef['autoincrement'])
488
            ) {
489 82
                $queryPart .= ' WITH DEFAULT';
490
            }
491
492 109
            $queryParts[] = $queryPart;
493
494 109
            $comment = $this->getColumnComment($column);
495
496 109
            if ($comment === null || $comment === '') {
497 82
                continue;
498
            }
499
500 27
            $commentsSQL[] = $this->getCommentOnColumnSQL(
501 27
                $diff->getName($this)->getQuotedName($this),
502 27
                $column->getQuotedName($this),
503
                $comment
504
            );
505
        }
506
507 663
        foreach ($diff->removedColumns as $column) {
508 82
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
509
                continue;
510
            }
511
512 82
            $queryParts[] =  'DROP COLUMN ' . $column->getQuotedName($this);
513
        }
514
515 663
        foreach ($diff->changedColumns as $columnDiff) {
516 471
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
517
                continue;
518
            }
519
520 471
            if ($columnDiff->hasChanged('comment')) {
521 37
                $commentsSQL[] = $this->getCommentOnColumnSQL(
522 37
                    $diff->getName($this)->getQuotedName($this),
523 37
                    $columnDiff->column->getQuotedName($this),
524 37
                    $this->getColumnComment($columnDiff->column)
525
                );
526
527 37
                if (count($columnDiff->changedProperties) === 1) {
528 37
                    continue;
529
                }
530
            }
531
532 434
            $this->gatherAlterColumnSQL($diff->getName($this), $columnDiff, $sql, $queryParts);
533
        }
534
535 663
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
536 109
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
537
                continue;
538
            }
539
540 109
            $oldColumnName = new Identifier($oldColumnName);
541
542 109
            $queryParts[] =  'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
543 109
                ' TO ' . $column->getQuotedName($this);
544
        }
545
546 663
        $tableSql = [];
547
548 663
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
549 663
            if (count($queryParts) > 0) {
550 490
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(' ', $queryParts);
551
            }
552
553
            // Some table alteration operations require a table reorganization.
554 663
            if (! empty($diff->removedColumns) || ! empty($diff->changedColumns)) {
555 472
                $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')";
556
            }
557
558 663
            $sql = array_merge($sql, $commentsSQL);
559
560 663
            $newName = $diff->getNewName();
561
562 663
            if ($newName !== null) {
563 54
                $sql[] = sprintf(
564 12
                    'RENAME TABLE %s TO %s',
565 54
                    $diff->getName($this)->getQuotedName($this),
566 54
                    $newName->getQuotedName($this)
567
                );
568
            }
569
570 663
            $sql = array_merge(
571 663
                $this->getPreAlterTableIndexForeignKeySQL($diff),
572 663
                $sql,
573 663
                $this->getPostAlterTableIndexForeignKeySQL($diff)
574
            );
575
        }
576
577 663
        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 434
    private function gatherAlterColumnSQL(Identifier $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts) : void
589
    {
590 434
        $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
591
592 434
        if (empty($alterColumnClauses)) {
593 54
            return;
594
        }
595
596
        // If we have a single column alteration, we can append the clause to the main query.
597 380
        if (count($alterColumnClauses) === 1) {
598 353
            $queryParts[] = current($alterColumnClauses);
599
600 353
            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 27
        foreach ($alterColumnClauses as $alterColumnClause) {
607 27
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
608
        }
609 27
    }
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 434
    private function getAlterColumnClausesSQL(ColumnDiff $columnDiff) : array
619
    {
620 434
        $column = $columnDiff->column->toArray();
621
622 434
        $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this);
623
624 434
        if ($column['columnDefinition']) {
625 27
            return [$alterClause . ' ' . $column['columnDefinition']];
626
        }
627
628 407
        $clauses = [];
629
630 407
        if ($columnDiff->hasChanged('type') ||
631 389
            $columnDiff->hasChanged('length') ||
632 383
            $columnDiff->hasChanged('precision') ||
633 377
            $columnDiff->hasChanged('scale') ||
634 407
            $columnDiff->hasChanged('fixed')
635
        ) {
636 217
            $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
637
        }
638
639 407
        if ($columnDiff->hasChanged('notnull')) {
640 108
            $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
641
        }
642
643 407
        if ($columnDiff->hasChanged('default')) {
644 109
            if (isset($column['default'])) {
645 82
                $defaultClause = $this->getDefaultValueDeclarationSQL($column);
646
647 82
                if ($defaultClause) {
648 82
                    $clauses[] = $alterClause . ' SET' . $defaultClause;
649
                }
650
            } else {
651 28
                $clauses[] = $alterClause . ' DROP DEFAULT';
652
            }
653
        }
654
655 407
        return $clauses;
656
    }
657
658
    /**
659
     * {@inheritDoc}
660
     */
661 663
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) : array
662
    {
663 663
        $sql   = [];
664 663
        $table = $diff->getName($this)->getQuotedName($this);
665
666 663
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
667 1
            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 663
        $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
687
688 663
        return $sql;
689
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694 137
    protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName) : array
695
    {
696 137
        if (strpos($tableName, '.') !== false) {
697 54
            [$schema]     = explode('.', $tableName);
698 54
            $oldIndexName = $schema . '.' . $oldIndexName;
699
        }
700
701 137
        return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
702
    }
703
704
    /**
705
     * {@inheritDoc}
706
     */
707 723
    public function getDefaultValueDeclarationSQL(array $field) : string
708
    {
709 723
        if (! empty($field['autoincrement'])) {
710 74
            return '';
711
        }
712
713 695
        if (isset($field['version']) && $field['version']) {
714
            if ((string) $field['type'] !== 'DateTime') {
715
                $field['default'] = '1';
716
            }
717
        }
718
719 695
        return parent::getDefaultValueDeclarationSQL($field);
720
    }
721
722
    /**
723
     * {@inheritDoc}
724
     */
725 1
    public function getEmptyIdentityInsertSQL(string $tableName, string $identifierColumnName) : string
726
    {
727 1
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
728
    }
729
730
    /**
731
     * {@inheritDoc}
732
     */
733 29
    public function getCreateTemporaryTableSnippetSQL() : string
734
    {
735 29
        return 'DECLARE GLOBAL TEMPORARY TABLE';
736
    }
737
738
    /**
739
     * {@inheritDoc}
740
     */
741 2
    public function getTemporaryTableName(string $tableName) : string
742
    {
743 2
        return 'SESSION.' . $tableName;
744
    }
745
746
    /**
747
     * {@inheritDoc}
748
     */
749 62
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
750
    {
751 62
        $where = [];
752
753 62
        if ($offset > 0) {
754 34
            $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1);
755
        }
756
757 62
        if ($limit !== null) {
758 34
            $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit);
759
        }
760
761 62
        if (empty($where)) {
762 55
            return $query;
763
        }
764
765
        // Todo OVER() needs ORDER BY data!
766 34
        return sprintf(
767 6
            'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
768 34
            $query,
769 34
            implode(' AND ', $where)
770
        );
771
    }
772
773
    /**
774
     * {@inheritDoc}
775
     */
776 28
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
777
    {
778 28
        if ($start === null) {
779 28
            return sprintf('LOCATE(%s, %s)', $substring, $string);
780
        }
781
782 28
        return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start);
783
    }
784
785
    /**
786
     * {@inheritDoc}
787
     */
788 30
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
789
    {
790 30
        if ($length === null) {
791 28
            return sprintf('SUBSTR(%s, %s)', $string, $start);
792
        }
793
794 29
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
795
    }
796
797
    /**
798
     * {@inheritDoc}
799
     */
800 56
    public function getCurrentDatabaseExpression() : string
801
    {
802 56
        return 'CURRENT_USER';
803
    }
804
805
    /**
806
     * {@inheritDoc}
807
     */
808 30
    public function supportsIdentityColumns() : bool
809
    {
810 30
        return true;
811
    }
812
813
    /**
814
     * {@inheritDoc}
815
     */
816 28
    public function prefersIdentityColumns() : bool
817
    {
818 28
        return true;
819
    }
820
821
    /**
822
     * {@inheritDoc}
823
     *
824
     * DB2 returns all column names in SQL result sets in uppercase.
825
     */
826 27
    public function getSQLResultCasing(string $column) : string
827
    {
828 27
        return strtoupper($column);
829
    }
830
831
    /**
832
     * {@inheritDoc}
833
     */
834 29
    public function getForUpdateSQL() : string
835
    {
836 29
        return ' WITH RR USE AND KEEP UPDATE LOCKS';
837
    }
838
839
    /**
840
     * {@inheritDoc}
841
     */
842 72
    public function getDummySelectSQL(string $expression = '1') : string
843
    {
844 72
        return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression);
845
    }
846
847
    /**
848
     * {@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 31
    public function supportsSavepoints() : bool
855
    {
856 31
        return false;
857
    }
858
859
    /**
860
     * {@inheritDoc}
861
     */
862 1412
    protected function getReservedKeywordsClass() : string
863
    {
864 1412
        return Keywords\DB2Keywords::class;
865
    }
866
867 39
    public function getListTableCommentsSQL(string $table) : string
868
    {
869 39
        return sprintf(
870
            <<<'SQL'
871
SELECT REMARKS
872
  FROM SYSIBM.SYSTABLES
873
  WHERE NAME = UPPER( %s )
874
SQL
875
            ,
876 39
            $this->quoteStringLiteral($table)
877
        );
878
    }
879
}
880