Passed
Pull Request — 3.0.x (#4000)
by Grégoire
08:27
created

DB2Platform   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 891
Duplicated Lines 0 %

Test Coverage

Coverage 88.45%

Importance

Changes 0
Metric Value
eloc 238
dl 0
loc 891
rs 2
c 0
b 0
f 0
wmc 123
ccs 245
cts 277
cp 0.8845

61 Methods

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