Passed
Push — allow-sf4-console ( abb17e...64a32a )
by Michael
252:50 queued 248:44
created

DB2Platform   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 872
Duplicated Lines 0 %

Test Coverage

Coverage 94.78%

Importance

Changes 0
Metric Value
wmc 123
dl 0
loc 872
ccs 254
cts 268
cp 0.9478
rs 1.728
c 0
b 0
f 0

60 Methods

Rating   Name   Duplication   Size   Complexity  
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 initializeDoctrineTypeMappings() 0 18 1
A getIntegerTypeDeclarationSQL() 0 3 1
A getCurrentTimeSQL() 0 3 1
A getClobTypeDeclarationSQL() 0 4 1
A supportsSavepoints() 0 3 1
A getBinaryMaxLength() 0 3 1
A getForUpdateSQL() 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 getLocateExpression() 0 7 2
A getListTableColumnsSQL() 0 38 1
A supportsReleaseSavepoints() 0 3 1
A getBinaryDefaultLength() 0 3 1
A getReservedKeywordsClass() 0 3 1
A getCreateTemporaryTableSnippetSQL() 0 3 1
C getAlterColumnClausesSQL() 0 38 12
A supportsCommentOnStatement() 0 3 1
A getBitAndComparisonExpression() 0 3 1
F getAlterTableSQL() 0 101 21
A getListTableIndexesSQL() 0 18 1
A getCreateViewSQL() 0 3 1
A gatherAlterColumnSQL() 0 20 4
A getDateArithmeticIntervalExpression() 0 15 3
A getCurrentTimestampSQL() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 7 3
A doModifyLimitQuery() 0 21 4
A getBooleanTypeDeclarationSQL() 0 3 1
A getVarcharTypeDeclarationSQL() 0 8 3
A getRenameIndexSQL() 0 8 2
A getSubstringExpression() 0 7 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 getTruncateTableSQL() 0 5 1
A getCreateDatabaseSQL() 0 3 1
A getListViewsSQL() 0 3 1
A getDummySelectSQL() 0 5 2
A getEmptyIdentityInsertSQL() 0 3 1
B getPreAlterTableIndexForeignKeySQL() 0 28 6
A getListTablesSQL() 0 3 1
A getTemporaryTableName() 0 3 1

How to fix   Complexity   

Complex Class

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

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

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

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