Completed
Push — master ( 208059...77ecc6 )
by Marco
34:46 queued 14s
created

DB2Platform   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 878
Duplicated Lines 0 %

Test Coverage

Coverage 94.51%

Importance

Changes 0
Metric Value
wmc 123
eloc 232
dl 0
loc 878
ccs 258
cts 273
cp 0.9451
rs 2
c 0
b 0
f 0

60 Methods

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