Failed Conditions
Push — develop ( b4c2cf...86369f )
by Sergei
18s queued 13s
created

DB2Platform   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 842
Duplicated Lines 0 %

Test Coverage

Coverage 83.08%

Importance

Changes 0
Metric Value
wmc 114
eloc 225
dl 0
loc 842
ccs 216
cts 260
cp 0.8308
rs 2
c 0
b 0
f 0

57 Methods

Rating   Name   Duplication   Size   Complexity  
A getBlobTypeDeclarationSQL() 0 4 1
A getTimeTypeDeclarationSQL() 0 3 1
A getDateTypeDeclarationSQL() 0 3 1
A getCreateTemporaryTableSnippetSQL() 0 3 1
A gatherAlterColumnSQL() 0 20 4
A getDropViewSQL() 0 3 1
A isCommentedDoctrineType() 0 9 2
A getIndexDeclarationSQL() 0 4 1
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A _getCreateTableSQL() 0 15 3
A getDefaultValueDeclarationSQL() 0 13 5
A initializeDoctrineTypeMappings() 0 18 1
A getIntegerTypeDeclarationSQL() 0 3 1
A getCurrentTimeSQL() 0 3 1
A getClobTypeDeclarationSQL() 0 4 1
A getVarbinaryTypeDeclarationSQLSnippet() 0 3 1
A getDateDiffExpression() 0 3 1
A getName() 0 3 1
A getSmallIntTypeDeclarationSQL() 0 3 1
A getCurrentDateSQL() 0 3 1
A getDropDatabaseSQL() 0 3 1
A getLocateExpression() 0 7 2
A getListTableColumnsSQL() 0 38 1
C getAlterColumnClausesSQL() 0 38 12
A supportsCommentOnStatement() 0 3 1
A getBitAndComparisonExpression() 0 3 1
A getCreateViewSQL() 0 3 1
A getDateArithmeticIntervalExpression() 0 15 3
A getCurrentTimestampSQL() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 7 3
A getBooleanTypeDeclarationSQL() 0 3 1
A getRenameIndexSQL() 0 8 2
A getSubstringExpression() 0 7 2
A getBigIntTypeDeclarationSQL() 0 3 1
A _getCommonIntegerTypeDeclarationSQL() 0 8 2
A getTruncateTableSQL() 0 5 1
A getCreateDatabaseSQL() 0 3 1
A getSQLResultCasing() 0 3 1
A getForUpdateSQL() 0 3 1
A prefersIdentityColumns() 0 3 1
A supportsReleaseSavepoints() 0 3 1
A getReservedKeywordsClass() 0 3 1
F getAlterTableSQL() 0 109 21
A doModifyLimitQuery() 0 21 4
A getBitOrComparisonExpression() 0 3 1
A getListViewsSQL() 0 3 1
A supportsSavepoints() 0 3 1
A getDummySelectSQL() 0 3 1
A getEmptyIdentityInsertSQL() 0 3 1
A getListTableForeignKeysSQL() 0 28 1
A getPreAlterTableIndexForeignKeySQL() 0 28 6
A getListTableIndexesSQL() 0 18 1
A getListTablesSQL() 0 3 1
A supportsIdentityColumns() 0 3 1
A getCurrentDatabaseExpression() 0 3 1
A supportsCreateDropDatabase() 0 3 1
A getTemporaryTableName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like DB2Platform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DB2Platform, and based on these observations, apply Extract Interface, too.

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