Failed Conditions
Push — type-registry ( 0931f1...33c798 )
by Michael
23:21
created

DB2Platform   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 879
Duplicated Lines 0 %

Test Coverage

Coverage 90%

Importance

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