Failed Conditions
Pull Request — 2.11.x (#3850)
by Benjamin
09:19
created

DB2Platform   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 891
Duplicated Lines 0 %

Test Coverage

Coverage 88.77%

Importance

Changes 0
Metric Value
wmc 124
eloc 237
dl 0
loc 891
ccs 245
cts 276
cp 0.8877
rs 2
c 0
b 0
f 0

61 Methods

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

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
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 1941
    public function getCharMaxLength() : int
29
    {
30 1941
        return 254;
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 1737
    public function getBinaryMaxLength()
37
    {
38 1737
        return 32704;
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 1733
    public function getBinaryDefaultLength()
45
    {
46 1733
        return 1;
47
    }
48
49
    /**
50
     * {@inheritDoc}
51
     */
52 2060
    public function getVarcharTypeDeclarationSQL(array $field)
53
    {
54
        // for IBM DB2, the CHAR max length is less than VARCHAR default length
55 2060
        if (! isset($field['length']) && ! empty($field['fixed'])) {
56 1929
            $field['length'] = $this->getCharMaxLength();
57
        }
58
59 2060
        return parent::getVarcharTypeDeclarationSQL($field);
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65 1933
    public function getBlobTypeDeclarationSQL(array $field)
66
    {
67
        // todo blob(n) with $field['length'];
68 1933
        return 'BLOB(1M)';
69
    }
70
71
    /**
72
     * {@inheritDoc}
73
     */
74 1920
    public function initializeDoctrineTypeMappings()
75
    {
76 1920
        $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 1920
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 2168
    public function isCommentedDoctrineType(Type $doctrineType)
99
    {
100 2168
        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 1154
            return true;
104
        }
105
106 2164
        return parent::isCommentedDoctrineType($doctrineType);
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112 2064
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
113
    {
114 2064
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(254)')
115 2064
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121 1729
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
122
    {
123 1729
        return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA';
124
    }
125
126
    /**
127
     * {@inheritDoc}
128
     */
129 1937
    public function getClobTypeDeclarationSQL(array $field)
130
    {
131
        // todo clob(n) with $field['length'];
132 1937
        return 'CLOB(1M)';
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     */
138 2037
    public function getName()
139
    {
140 2037
        return 'db2';
141
    }
142
143
    /**
144
     * {@inheritDoc}
145
     */
146 1933
    public function getBooleanTypeDeclarationSQL(array $columnDef)
147
    {
148 1933
        return 'SMALLINT';
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154 2048
    public function getIntegerTypeDeclarationSQL(array $columnDef)
155
    {
156 2048
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
157
    }
158
159
    /**
160
     * {@inheritDoc}
161
     */
162 1929
    public function getBigIntTypeDeclarationSQL(array $columnDef)
163
    {
164 1929
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 1929
    public function getSmallIntTypeDeclarationSQL(array $columnDef)
171
    {
172 1929
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
173
    }
174
175
    /**
176
     * {@inheritDoc}
177
     */
178 2048
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
179
    {
180 2048
        $autoinc = '';
181 2048
        if (! empty($columnDef['autoincrement'])) {
182 1933
            $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
183
        }
184
185 2048
        return $autoinc;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 929
    public function getBitAndComparisonExpression($value1, $value2)
192
    {
193 929
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 4
    public function getBitOrComparisonExpression($value1, $value2)
200
    {
201 4
        return 'BITOR(' . $value1 . ', ' . $value2 . ')';
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 1829
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
208
    {
209
        switch ($unit) {
210 1829
            case DateIntervalUnit::WEEK:
211 1829
                $interval *= 7;
212 1829
                $unit      = DateIntervalUnit::DAY;
213 1829
                break;
214
215 1829
            case DateIntervalUnit::QUARTER:
216 1829
                $interval *= 3;
217 1829
                $unit      = DateIntervalUnit::MONTH;
218 1829
                break;
219
        }
220
221 1829
        return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227 1829
    public function getDateDiffExpression($date1, $date2)
228
    {
229 1829
        return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     */
235 1929
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
236
    {
237 1929
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] === true) {
238 1929
            return 'TIMESTAMP(0) WITH DEFAULT';
239
        }
240
241 1929
        return 'TIMESTAMP(0)';
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 1929
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
248
    {
249 1929
        return 'DATE';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 1929
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
256
    {
257 1929
        return 'TIME';
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 1883
    public function getTruncateTableSQL($tableName, $cascade = false)
264
    {
265 1883
        $tableIdentifier = new Identifier($tableName);
266
267 1883
        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 1379
    public function getListTableColumnsSQL($table, $database = null)
279
    {
280 1379
        $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 1379
               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
    public function getListTablesSQL()
330
    {
331
        return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337
    public function getListViewsSQL($database)
338
    {
339
        return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS';
340
    }
341
342
    /**
343
     * {@inheritDoc}
344
     */
345 4
    public function getListTableIndexesSQL($table, $currentDatabase = null)
346
    {
347 4
        $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 4
                WHERE    idx.TABNAME = UPPER(" . $table . ')
363
                ORDER BY idxcol.COLSEQ ASC';
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369 1354
    public function getListTableForeignKeysSQL($table)
370
    {
371 1354
        $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 1354
                WHERE    fk.TABNAME = UPPER(" . $table . ')
397
                ORDER BY fkcol.COLSEQ ASC';
398
    }
399
400
    /**
401
     * {@inheritDoc}
402
     */
403 1879
    public function getCreateViewSQL($name, $sql)
404
    {
405 1879
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
406
    }
407
408
    /**
409
     * {@inheritDoc}
410
     */
411 1879
    public function getDropViewSQL($name)
412
    {
413 1879
        return 'DROP VIEW ' . $name;
414
    }
415
416
    /**
417
     * {@inheritDoc}
418
     */
419 1879
    public function getCreateDatabaseSQL($database)
420
    {
421 1879
        return 'CREATE DATABASE ' . $database;
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427 1879
    public function getDropDatabaseSQL($database)
428
    {
429 1879
        return 'DROP DATABASE ' . $database;
430
    }
431
432
    /**
433
     * {@inheritDoc}
434
     */
435 4
    public function supportsCreateDropDatabase()
436
    {
437 4
        return false;
438
    }
439
440
    /**
441
     * {@inheritDoc}
442
     */
443 4
    public function supportsReleaseSavepoints()
444
    {
445 4
        return false;
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451 2052
    public function supportsCommentOnStatement()
452
    {
453 2052
        return true;
454
    }
455
456
    /**
457
     * {@inheritDoc}
458
     */
459 1833
    public function getCurrentDateSQL()
460
    {
461 1833
        return 'CURRENT DATE';
462
    }
463
464
    /**
465
     * {@inheritDoc}
466
     */
467 1829
    public function getCurrentTimeSQL()
468
    {
469 1829
        return 'CURRENT TIME';
470
    }
471
472
    /**
473
     * {@inheritDoc}
474
     */
475 1833
    public function getCurrentTimestampSQL()
476
    {
477 1833
        return 'CURRENT TIMESTAMP';
478
    }
479
480
    /**
481
     * {@inheritDoc}
482
     */
483 479
    public function getIndexDeclarationSQL($name, Index $index)
484
    {
485
        // Index declaration in statements like CREATE TABLE is not supported.
486 479
        throw DBALException::notSupported(__METHOD__);
487
    }
488
489
    /**
490
     * {@inheritDoc}
491
     */
492 2048
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
493
    {
494 2048
        $indexes = [];
495 2048
        if (isset($options['indexes'])) {
496 2048
            $indexes = $options['indexes'];
497
        }
498 2048
        $options['indexes'] = [];
499
500 2048
        $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
501
502 2048
        foreach ($indexes as $definition) {
503 2016
            $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
504
        }
505
506 2048
        return $sqls;
507
    }
508
509
    /**
510
     * {@inheritDoc}
511
     */
512 1746
    public function getAlterTableSQL(TableDiff $diff)
513
    {
514 1746
        $sql         = [];
515 1746
        $columnSql   = [];
516 1746
        $commentsSQL = [];
517
518 1746
        $queryParts = [];
519 1746
        foreach ($diff->addedColumns as $column) {
520 916
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
521
                continue;
522
            }
523
524 916
            $columnDef = $column->toArray();
525 916
            $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 916
            if (! empty($columnDef['notnull']) &&
529 916
                ! isset($columnDef['default']) &&
530 916
                empty($columnDef['autoincrement'])
531
            ) {
532 862
                $queryPart .= ' WITH DEFAULT';
533
            }
534
535 916
            $queryParts[] = $queryPart;
536
537 916
            $comment = $this->getColumnComment($column);
538
539 916
            if ($comment === null || $comment === '') {
540 912
                continue;
541
            }
542
543 804
            $commentsSQL[] = $this->getCommentOnColumnSQL(
544 804
                $diff->getName($this)->getQuotedName($this),
545 804
                $column->getQuotedName($this),
546
                $comment
547
            );
548
        }
549
550 1746
        foreach ($diff->removedColumns as $column) {
551 912
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
552
                continue;
553
            }
554
555 912
            $queryParts[] =  'DROP COLUMN ' . $column->getQuotedName($this);
556
        }
557
558 1746
        foreach ($diff->changedColumns as $columnDiff) {
559 1718
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
560
                continue;
561
            }
562
563 1718
            if ($columnDiff->hasChanged('comment')) {
564 804
                $commentsSQL[] = $this->getCommentOnColumnSQL(
565 804
                    $diff->getName($this)->getQuotedName($this),
566 804
                    $columnDiff->column->getQuotedName($this),
567 804
                    $this->getColumnComment($columnDiff->column)
568
                );
569
570 804
                if (count($columnDiff->changedProperties) === 1) {
571 804
                    continue;
572
                }
573
            }
574
575 1714
            $this->gatherAlterColumnSQL($diff->getName($this), $columnDiff, $sql, $queryParts);
576
        }
577
578 1746
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
579 866
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
580
                continue;
581
            }
582
583 866
            $oldColumnName = new Identifier($oldColumnName);
584
585 866
            $queryParts[] =  'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
586 866
                ' TO ' . $column->getQuotedName($this);
587
        }
588
589 1746
        $tableSql = [];
590
591 1746
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
592 1746
            if (count($queryParts) > 0) {
593 1722
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(' ', $queryParts);
594
            }
595
596
            // Some table alteration operations require a table reorganization.
597 1746
            if (! empty($diff->removedColumns) || ! empty($diff->changedColumns)) {
598 1718
                $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')";
599
            }
600
601 1746
            $sql = array_merge($sql, $commentsSQL);
602
603 1746
            $newName = $diff->getNewName();
604
605 1746
            if ($newName !== false) {
606 908
                $sql[] = sprintf(
607 8
                    'RENAME TABLE %s TO %s',
608 908
                    $diff->getName($this)->getQuotedName($this),
609 908
                    $newName->getQuotedName($this)
610
                );
611
            }
612
613 1746
            $sql = array_merge(
614 1746
                $this->getPreAlterTableIndexForeignKeySQL($diff),
615 1746
                $sql,
616 1746
                $this->getPostAlterTableIndexForeignKeySQL($diff)
617
            );
618
        }
619
620 1746
        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 1714
    private function gatherAlterColumnSQL(Identifier $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts)
632
    {
633 1714
        $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff);
634
635 1714
        if (empty($alterColumnClauses)) {
636 1433
            return;
637
        }
638
639
        // If we have a single column alteration, we can append the clause to the main query.
640 1706
        if (count($alterColumnClauses) === 1) {
641 1702
            $queryParts[] = current($alterColumnClauses);
642
643 1702
            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 904
        foreach ($alterColumnClauses as $alterColumnClause) {
650 904
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
651
        }
652 904
    }
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 1714
    private function getAlterColumnClausesSQL(ColumnDiff $columnDiff)
662
    {
663 1714
        $column = $columnDiff->column->toArray();
664
665 1714
        $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this);
666
667 1714
        if ($column['columnDefinition']) {
668 1654
            return [$alterClause . ' ' . $column['columnDefinition']];
669
        }
670
671 1685
        $clauses = [];
672
673 1685
        if ($columnDiff->hasChanged('type') ||
674 1673
            $columnDiff->hasChanged('length') ||
675 1669
            $columnDiff->hasChanged('precision') ||
676 1665
            $columnDiff->hasChanged('scale') ||
677 1685
            $columnDiff->hasChanged('fixed')
678
        ) {
679 1657
            $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
680
        }
681
682 1685
        if ($columnDiff->hasChanged('notnull')) {
683 1516
            $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
684
        }
685
686 1685
        if ($columnDiff->hasChanged('default')) {
687 1466
            if (isset($column['default'])) {
688 1462
                $defaultClause = $this->getDefaultValueDeclarationSQL($column);
689
690 1462
                if ($defaultClause) {
691 1462
                    $clauses[] = $alterClause . ' SET' . $defaultClause;
692
                }
693
            } else {
694 1404
                $clauses[] = $alterClause . ' DROP DEFAULT';
695
            }
696
        }
697
698 1685
        return $clauses;
699
    }
700
701
    /**
702
     * {@inheritDoc}
703
     */
704 1746
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
705
    {
706 1746
        $sql   = [];
707 1746
        $table = $diff->getName($this)->getQuotedName($this);
708
709 1746
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
710
            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 1746
        $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
730
731 1746
        return $sql;
732
    }
733
734
    /**
735
     * {@inheritdoc}
736
     */
737 370
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
738
    {
739 370
        if (strpos($tableName, '.') !== false) {
740 283
            [$schema]     = explode('.', $tableName);
741 283
            $oldIndexName = $schema . '.' . $oldIndexName;
742
        }
743
744 370
        return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
745
    }
746
747
    /**
748
     * {@inheritDoc}
749
     */
750 2088
    public function getDefaultValueDeclarationSQL($field)
751
    {
752 2088
        if (! empty($field['autoincrement'])) {
753 1433
            return '';
754
        }
755
756 2084
        if (isset($field['version']) && $field['version']) {
757
            if ((string) $field['type'] !== 'DateTime') {
758
                $field['default'] = '1';
759
            }
760
        }
761
762 2084
        return parent::getDefaultValueDeclarationSQL($field);
763
    }
764
765
    /**
766
     * {@inheritDoc}
767
     */
768
    public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
769
    {
770
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
771
    }
772
773
    /**
774
     * {@inheritDoc}
775
     */
776 1879
    public function getCreateTemporaryTableSnippetSQL()
777
    {
778 1879
        return 'DECLARE GLOBAL TEMPORARY TABLE';
779
    }
780
781
    /**
782
     * {@inheritDoc}
783
     */
784
    public function getTemporaryTableName($tableName)
785
    {
786
        return 'SESSION.' . $tableName;
787
    }
788
789
    /**
790
     * {@inheritDoc}
791
     */
792 1808
    protected function doModifyLimitQuery($query, $limit, $offset = null)
793
    {
794 1808
        $where = [];
795
796 1808
        if ($offset > 0) {
797 1804
            $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1);
798
        }
799
800 1808
        if ($limit !== null) {
801 1804
            $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit);
802
        }
803
804 1808
        if (empty($where)) {
805 1808
            return $query;
806
        }
807
808
        // Todo OVER() needs ORDER BY data!
809 1804
        return sprintf(
810 4
            'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
811 1804
            $query,
812 1804
            implode(' AND ', $where)
813
        );
814
    }
815
816
    /**
817
     * {@inheritDoc}
818
     */
819 1829
    public function getLocateExpression($str, $substr, $startPos = false)
820
    {
821 1829
        if ($startPos === false) {
822 1829
            return 'LOCATE(' . $substr . ', ' . $str . ')';
823
        }
824
825 1829
        return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')';
826
    }
827
828
    /**
829
     * {@inheritDoc}
830
     */
831 1829
    public function getSubstringExpression($value, $from, $length = null)
832
    {
833 1829
        if ($length === null) {
834 1829
            return 'SUBSTR(' . $value . ', ' . $from . ')';
835
        }
836
837 1829
        return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')';
838
    }
839
840
    /**
841
     * {@inheritDoc}
842
     */
843 4
    public function supportsIdentityColumns()
844
    {
845 4
        return true;
846
    }
847
848
    /**
849
     * {@inheritDoc}
850
     */
851 1779
    public function prefersIdentityColumns()
852
    {
853 1779
        return true;
854
    }
855
856
    /**
857
     * {@inheritDoc}
858
     *
859
     * DB2 returns all column names in SQL result sets in uppercase.
860
     */
861 1754
    public function getSQLResultCasing($column)
862
    {
863 1754
        return strtoupper($column);
864
    }
865
866
    /**
867
     * {@inheritDoc}
868
     */
869 1829
    public function getForUpdateSQL()
870
    {
871 1829
        return ' WITH RR USE AND KEEP UPDATE LOCKS';
872
    }
873
874
    /**
875
     * {@inheritDoc}
876
     */
877
    public function getDummySelectSQL()
878
    {
879
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
880
881
        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 4
    public function supportsSavepoints()
892
    {
893 4
        return false;
894
    }
895
896
    /**
897
     * {@inheritDoc}
898
     */
899 2208
    protected function getReservedKeywordsClass()
900
    {
901 2208
        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