Completed
Pull Request — 2.10.x (#3936)
by Asmir
65:42
created

DB2Platform::supportsNamedPrimaryConstraints()   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
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
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 1909
    public function getCharMaxLength() : int
29
    {
30 1909
        return 254;
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 1623
    public function getBinaryMaxLength()
37
    {
38 1623
        return 32704;
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 1591
    public function getBinaryDefaultLength()
45
    {
46 1591
        return 1;
47
    }
48
49
    /**
50
     * {@inheritDoc}
51
     */
52 2024
    public function getVarcharTypeDeclarationSQL(array $field)
53
    {
54
        // for IBM DB2, the CHAR max length is less than VARCHAR default length
55 2024
        if (! isset($field['length']) && ! empty($field['fixed'])) {
56 1863
            $field['length'] = $this->getCharMaxLength();
57
        }
58
59 2024
        return parent::getVarcharTypeDeclarationSQL($field);
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65 1944
    public function getBlobTypeDeclarationSQL(array $field)
66
    {
67
        // todo blob(n) with $field['length'];
68 1944
        return 'BLOB(1M)';
69
    }
70
71
    /**
72
     * {@inheritDoc}
73
     */
74 1872
    public function initializeDoctrineTypeMappings()
75
    {
76 1872
        $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 1872
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 2090
    public function isCommentedDoctrineType(Type $doctrineType)
99
    {
100 2090
        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 1203
            return true;
104
        }
105
106 2088
        return parent::isCommentedDoctrineType($doctrineType);
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112 2026
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
113
    {
114 2026
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(254)')
115 2026
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 1619
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
122
    {
123 1619
        return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA';
124
    }
125
126
    /**
127
     * {@inheritDoc}
128
     */
129 1946
    public function getClobTypeDeclarationSQL(array $field)
130
    {
131
        // todo clob(n) with $field['length'];
132 1946
        return 'CLOB(1M)';
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     */
138 2028
    public function getName()
139
    {
140 2028
        return 'db2';
141
    }
142
143
    /**
144
     * {@inheritDoc}
145
     */
146 1887
    public function getBooleanTypeDeclarationSQL(array $columnDef)
147
    {
148 1887
        return 'SMALLINT';
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154 2030
    public function getIntegerTypeDeclarationSQL(array $columnDef)
155
    {
156 2030
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
157
    }
158
159
    /**
160
     * {@inheritDoc}
161
     */
162 1810
    public function getBigIntTypeDeclarationSQL(array $columnDef)
163
    {
164 1810
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 1863
    public function getSmallIntTypeDeclarationSQL(array $columnDef)
171
    {
172 1863
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
173
    }
174
175
    /**
176
     * {@inheritDoc}
177
     */
178 2030
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
179
    {
180 2030
        $autoinc = '';
181 2030
        if (! empty($columnDef['autoincrement'])) {
182 1925
            $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
183
        }
184
185 2030
        return $autoinc;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 1047
    public function getBitAndComparisonExpression($value1, $value2)
192
    {
193 1047
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 233
    public function getBitOrComparisonExpression($value1, $value2)
200
    {
201 233
        return 'BITOR(' . $value1 . ', ' . $value2 . ')';
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 1841
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
208
    {
209
        switch ($unit) {
210 1841
            case DateIntervalUnit::WEEK:
211 1841
                $interval *= 7;
212 1841
                $unit      = DateIntervalUnit::DAY;
213 1841
                break;
214
215 1841
            case DateIntervalUnit::QUARTER:
216 1841
                $interval *= 3;
217 1841
                $unit      = DateIntervalUnit::MONTH;
218 1841
                break;
219
        }
220
221 1841
        return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227 1821
    public function getDateDiffExpression($date1, $date2)
228
    {
229 1821
        return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     */
235 1930
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
236
    {
237 1930
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
238 1773
            return 'TIMESTAMP(0) WITH DEFAULT';
239
        }
240
241 1930
        return 'TIMESTAMP(0)';
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 1907
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
248
    {
249 1907
        return 'DATE';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 1906
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
256
    {
257 1906
        return 'TIME';
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 1881
    public function getTruncateTableSQL($tableName, $cascade = false)
264
    {
265 1881
        $tableIdentifier = new Identifier($tableName);
266
267 1881
        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 1402
    public function getListTableColumnsSQL($table, $database = null)
279
    {
280 1402
        $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 1402
               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 246
    public function getListTablesSQL()
330
    {
331 246
        return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337 175
    public function getListViewsSQL($database)
338
    {
339 175
        return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS';
340
    }
341
342
    /**
343
     * {@inheritDoc}
344
     */
345 192
    public function getListTableIndexesSQL($table, $currentDatabase = null)
346
    {
347 192
        $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 192
                WHERE    idx.TABNAME = UPPER(" . $table . ')
363
                ORDER BY idxcol.COLSEQ ASC';
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369 1380
    public function getListTableForeignKeysSQL($table)
370
    {
371 1380
        $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 1380
                WHERE    fk.TABNAME = UPPER(" . $table . ')
397
                ORDER BY fkcol.COLSEQ ASC';
398
    }
399
400
    /**
401
     * {@inheritDoc}
402
     */
403 1827
    public function getCreateViewSQL($name, $sql)
404
    {
405 1827
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
406
    }
407
408
    /**
409
     * {@inheritDoc}
410
     */
411 1827
    public function getDropViewSQL($name)
412
    {
413 1827
        return 'DROP VIEW ' . $name;
414
    }
415
416
    /**
417
     * {@inheritDoc}
418
     */
419 1727
    public function getCreateDatabaseSQL($database)
420
    {
421 1727
        return 'CREATE DATABASE ' . $database;
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427 1727
    public function getDropDatabaseSQL($database)
428
    {
429 1727
        return 'DROP DATABASE ' . $database;
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     */
435 248
    public function supportsNamedPrimaryConstraints()
436
    {
437 248
        return false;
438
    }
439
440
    /**
441
     * {@inheritDoc}
442
     */
443 2
    public function supportsCreateDropDatabase()
444
    {
445 2
        return false;
446
    }
447
448
    /**
449
     * {@inheritDoc}
450
     */
451 2032
    public function supportsReleaseSavepoints()
452
    {
453 2032
        return false;
454
    }
455
456
    /**
457
     * {@inheritdoc}
458
     */
459 1821
    public function supportsCommentOnStatement()
460
    {
461 1821
        return true;
462
    }
463
464
    /**
465
     * {@inheritDoc}
466
     */
467 1818
    public function getCurrentDateSQL()
468
    {
469 1818
        return 'CURRENT DATE';
470
    }
471
472
    /**
473
     * {@inheritDoc}
474
     */
475 1819
    public function getCurrentTimeSQL()
476
    {
477 1819
        return 'CURRENT TIME';
478
    }
479
480
    /**
481
     * {@inheritDoc}
482
     */
483 439
    public function getCurrentTimestampSQL()
484
    {
485
        return 'CURRENT TIMESTAMP';
486 439
    }
487
488
    /**
489
     * {@inheritDoc}
490
     */
491
    public function getIndexDeclarationSQL($name, Index $index)
492 2030
    {
493
        // Index declaration in statements like CREATE TABLE is not supported.
494 2030
        throw DBALException::notSupported(__METHOD__);
495 2030
    }
496 2030
497
    /**
498 2030
     * {@inheritDoc}
499
     */
500 2030
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
501
    {
502 2030
        $indexes = [];
503 1951
        if (isset($options['indexes'])) {
504
            $indexes = $options['indexes'];
505
        }
506 2030
        $options['indexes'] = [];
507
508
        $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
509
510
        foreach ($indexes as $definition) {
511
            $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
512 1676
        }
513
514 1676
        return $sqls;
515 1676
    }
516 1676
517
    /**
518 1676
     * {@inheritDoc}
519 1676
     */
520 976
    public function getAlterTableSQL(TableDiff $diff)
521
    {
522
        $sql         = [];
523
        $columnSql   = [];
524 976
        $commentsSQL = [];
525 976
526
        $queryParts = [];
527
        foreach ($diff->addedColumns as $column) {
528 976
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
529 976
                continue;
530 976
            }
531
532 930
            $columnDef = $column->toArray();
533
            $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
534
535 976
            // Adding non-nullable columns to a table requires a default value to be specified.
536
            if (! empty($columnDef['notnull']) &&
537 976
                ! isset($columnDef['default']) &&
538
                empty($columnDef['autoincrement'])
539 976
            ) {
540 974
                $queryPart .= ' WITH DEFAULT';
541
            }
542
543 738
            $queryParts[] = $queryPart;
544 738
545 738
            $comment = $this->getColumnComment($column);
546
547
            if ($comment === null || $comment === '') {
548
                continue;
549
            }
550 1676
551 974
            $commentsSQL[] = $this->getCommentOnColumnSQL(
552
                $diff->getName($this)->getQuotedName($this),
553
                $column->getQuotedName($this),
554
                $comment
555 974
            );
556
        }
557
558 1676
        foreach ($diff->removedColumns as $column) {
559 1656
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
560
                continue;
561
            }
562
563 1656
            $queryParts[] =  'DROP COLUMN ' . $column->getQuotedName($this);
564 876
        }
565 876
566 876
        foreach ($diff->changedColumns as $columnDiff) {
567 876
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
568
                continue;
569
            }
570 876
571 876
            if ($columnDiff->hasChanged('comment')) {
572
                $commentsSQL[] = $this->getCommentOnColumnSQL(
573
                    $diff->getName($this)->getQuotedName($this),
574
                    $columnDiff->column->getQuotedName($this),
575 1651
                    $this->getColumnComment($columnDiff->column)
576
                );
577
578 1676
                if (count($columnDiff->changedProperties) === 1) {
579 928
                    continue;
580
                }
581
            }
582
583 928
            $this->gatherAlterColumnSQL($diff->getName($this), $columnDiff, $sql, $queryParts);
584
        }
585 928
586 928
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
587
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
588
                continue;
589 1676
            }
590
591 1676
            $oldColumnName = new Identifier($oldColumnName);
592 1676
593 1664
            $queryParts[] =  'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
594
                ' TO ' . $column->getQuotedName($this);
595
        }
596
597 1676
        $tableSql = [];
598 1662
599
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
600
            if (count($queryParts) > 0) {
601 1676
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(' ', $queryParts);
602
            }
603 1676
604
            // Some table alteration operations require a table reorganization.
605 1676
            if (! empty($diff->removedColumns) || ! empty($diff->changedColumns)) {
606 832
                $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')";
607 4
            }
608 832
609 832
            $sql = array_merge($sql, $commentsSQL);
610
611
            $newName = $diff->getNewName();
612
613 1676
            if ($newName !== false) {
614 1676
                $sql[] = sprintf(
615 1676
                    'RENAME TABLE %s TO %s',
616 1676
                    $diff->getName($this)->getQuotedName($this),
617
                    $newName->getQuotedName($this)
618
                );
619
            }
620 1676
621
            $sql = array_merge(
622
                $this->getPreAlterTableIndexForeignKeySQL($diff),
623
                $sql,
624
                $this->getPostAlterTableIndexForeignKeySQL($diff)
625
            );
626
        }
627
628
        return array_merge($sql, $tableSql, $columnSql);
629
    }
630
631 1651
    /**
632
     * Gathers the table alteration SQL for a given column diff.
633 1651
     *
634
     * @param Identifier $table      The table to gather the SQL for.
635 1651
     * @param ColumnDiff $columnDiff The column diff to evaluate.
636 1315
     * @param string[]   $sql        The sequence of table alteration statements to fill.
637
     * @param mixed[]    $queryParts The sequence of column alteration clauses to fill.
638
     */
639
    private function gatherAlterColumnSQL(Identifier $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts) : void
640 1647
    {
641 1645
        $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
642
643 1645
        if (empty($alterColumnClauses)) {
644
            return;
645
        }
646
647
        // If we have a single column alteration, we can append the clause to the main query.
648
        if (count($alterColumnClauses) === 1) {
649 830
            $queryParts[] = current($alterColumnClauses);
650 830
651
            return;
652 830
        }
653
654
        // We have multiple alterations for the same column,
655
        // so we need to trigger a complete ALTER TABLE statement
656
        // for each ALTER COLUMN clause.
657
        foreach ($alterColumnClauses as $alterColumnClause) {
658
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
659
        }
660
    }
661 1651
662
    /**
663 1651
     * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff.
664
     *
665 1651
     * @param ColumnDiff $columnDiff The column diff to evaluate.
666
     *
667 1651
     * @return string[]
668 1520
     */
669
    private function getAlterColumnClausesSQL(ColumnDiff $columnDiff)
670
    {
671 1627
        $column = $columnDiff->column->toArray();
672
673 1627
        $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this);
674 1621
675 1619
        if ($column['columnDefinition']) {
676 1617
            return [$alterClause . ' ' . $column['columnDefinition']];
677 1627
        }
678
679 1613
        $clauses = [];
680
681
        if ($columnDiff->hasChanged('type') ||
682 1627
            $columnDiff->hasChanged('length') ||
683 1388
            $columnDiff->hasChanged('precision') ||
684
            $columnDiff->hasChanged('scale') ||
685
            $columnDiff->hasChanged('fixed')
686 1627
        ) {
687 1448
            $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
688 1446
        }
689
690 1446
        if ($columnDiff->hasChanged('notnull')) {
691 1446
            $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
692
        }
693
694 1398
        if ($columnDiff->hasChanged('default')) {
695
            if (isset($column['default'])) {
696
                $defaultClause = $this->getDefaultValueDeclarationSQL($column);
697
698 1627
                if ($defaultClause) {
699
                    $clauses[] = $alterClause . ' SET' . $defaultClause;
700
                }
701
            } else {
702
                $clauses[] = $alterClause . ' DROP DEFAULT';
703
            }
704 1676
        }
705
706 1676
        return $clauses;
707 1676
    }
708
709 1676
    /**
710 176
     * {@inheritDoc}
711
     */
712
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
713
    {
714
        $sql   = [];
715
        $table = $diff->getName($this)->getQuotedName($this);
716
717
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
718
            foreach ($diff->addedIndexes as $addKey => $addIndex) {
719
                if ($remIndex->getColumns() === $addIndex->getColumns()) {
720
                    if ($remIndex->isPrimary()) {
721
                        $sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY';
722
                    } elseif ($remIndex->isUnique()) {
723
                        $sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this);
724
                    } else {
725
                        $sql[] = $this->getDropIndexSQL($remIndex, $table);
726
                    }
727
728
                    $sql[] = $this->getCreateIndexSQL($addIndex, $table);
729 1676
730
                    unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]);
731 1676
732
                    break;
733
                }
734
            }
735
        }
736
737 494
        $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
738
739 494
        return $sql;
740 257
    }
741 257
742
    /**
743
     * {@inheritdoc}
744 494
     */
745
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
746
    {
747
        if (strpos($tableName, '.') !== false) {
748
            [$schema]     = explode('.', $tableName);
749
            $oldIndexName = $schema . '.' . $oldIndexName;
750 2050
        }
751
752 2050
        return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
753 1485
    }
754
755
    /**
756 2048
     * {@inheritDoc}
757
     */
758
    public function getDefaultValueDeclarationSQL($field)
759
    {
760
        if (! empty($field['autoincrement'])) {
761
            return '';
762 2048
        }
763
764
        if (isset($field['version']) && $field['version']) {
765
            if ((string) $field['type'] !== 'DateTime') {
766
                $field['default'] = '1';
767
            }
768 84
        }
769
770 84
        return parent::getDefaultValueDeclarationSQL($field);
771
    }
772
773
    /**
774
     * {@inheritDoc}
775
     */
776 1768
    public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
777
    {
778 1768
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
779
    }
780
781
    /**
782
     * {@inheritDoc}
783
     */
784 116
    public function getCreateTemporaryTableSnippetSQL()
785
    {
786 116
        return 'DECLARE GLOBAL TEMPORARY TABLE';
787
    }
788
789
    /**
790
     * {@inheritDoc}
791
     */
792 1815
    public function getTemporaryTableName($tableName)
793
    {
794 1815
        return 'SESSION.' . $tableName;
795
    }
796 1815
797 1813
    /**
798
     * {@inheritDoc}
799
     */
800 1815
    protected function doModifyLimitQuery($query, $limit, $offset = null)
801 1813
    {
802
        $where = [];
803
804 1815
        if ($offset > 0) {
805 1809
            $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1);
806
        }
807
808
        if ($limit !== null) {
809 1813
            $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit);
810 2
        }
811 1813
812 1813
        if (empty($where)) {
813
            return $query;
814
        }
815
816
        // Todo OVER() needs ORDER BY data!
817
        return sprintf(
818
            'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
819 1840
            $query,
820
            implode(' AND ', $where)
821 1840
        );
822 1840
    }
823
824
    /**
825 1840
     * {@inheritDoc}
826
     */
827
    public function getLocateExpression($str, $substr, $startPos = false)
828
    {
829
        if ($startPos === false) {
830
            return 'LOCATE(' . $substr . ', ' . $str . ')';
831 1681
        }
832
833 1681
        return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')';
834 1681
    }
835
836
    /**
837 1681
     * {@inheritDoc}
838
     */
839
    public function getSubstringExpression($value, $from, $length = null)
840
    {
841
        if ($length === null) {
842
            return 'SUBSTR(' . $value . ', ' . $from . ')';
843 176
        }
844
845 176
        return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')';
846
    }
847
848
    /**
849
     * {@inheritDoc}
850
     */
851 1652
    public function supportsIdentityColumns()
852
    {
853 1652
        return true;
854
    }
855
856
    /**
857
     * {@inheritDoc}
858
     */
859
    public function prefersIdentityColumns()
860
    {
861 1612
        return true;
862
    }
863 1612
864
    /**
865
     * {@inheritDoc}
866
     *
867
     * DB2 returns all column names in SQL result sets in uppercase.
868
     */
869 1726
    public function getSQLResultCasing($column)
870
    {
871 1726
        return strtoupper($column);
872
    }
873
874
    /**
875
     * {@inheritDoc}
876
     */
877 237
    public function getForUpdateSQL()
878
    {
879 237
        return ' WITH RR USE AND KEEP UPDATE LOCKS';
880
    }
881 237
882
    /**
883
     * {@inheritDoc}
884
     */
885
    public function getDummySelectSQL()
886
    {
887
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
888
889
        return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression);
890
    }
891 241
892
    /**
893 241
     * {@inheritDoc}
894
     *
895
     * DB2 supports savepoints, but they work semantically different than on other vendor platforms.
896
     *
897
     * TODO: We have to investigate how to get DB2 up and running with savepoints.
898
     */
899 2110
    public function supportsSavepoints()
900
    {
901 2110
        return false;
902
    }
903
904 190
    /**
905
     * {@inheritDoc}
906 190
     */
907
    protected function getReservedKeywordsClass()
908
    {
909
        return Keywords\DB2Keywords::class;
910
    }
911
912
    public function getListTableCommentsSQL(string $table) : string
913 190
    {
914
        return sprintf(
915
            <<<'SQL'
916
SELECT REMARKS
917
  FROM SYSIBM.SYSTABLES
918
  WHERE NAME = UPPER( %s )
919
SQL
920
            ,
921
            $this->quoteStringLiteral($table)
922
        );
923
    }
924
}
925