Completed
Pull Request — master (#3512)
by David
61:26
created

DB2Platform::gatherAlterColumnSQL()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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