Passed
Pull Request — master (#3133)
by Sergei
04:02
created

DB2Platform::getAlterColumnClausesSQL()   C

Complexity

Conditions 12
Paths 25

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 13.152

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 5.1612
c 0
b 0
f 0
ccs 16
cts 20
cp 0.8
cc 12
eloc 21
nc 25
nop 1
crap 13.152

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 34
     * {@inheritdoc}
44
     */
45 34
    public function getCharMaxLength() : int
46
    {
47
        return 254;
48
    }
49
50
    /**
51 34
     * {@inheritdoc}
52
     */
53 34
    public function getBinaryMaxLength()
54
    {
55
        return 32704;
56
    }
57
58
    /**
59 34
     * {@inheritdoc}
60
     */
61
    public function getBinaryDefaultLength()
62 34
    {
63
        return 1;
64
    }
65
66
    /**
67
     * {@inheritDoc}
68 85
     */
69
    public function getVarcharTypeDeclarationSQL(array $field)
70 85
    {
71
        // for IBM DB2, the CHAR max length is less than VARCHAR default length
72
        if (! isset($field['length']) && ! empty($field['fixed'])) {
73
            $field['length'] = $this->getCharMaxLength();
74
        }
75
76
        return parent::getVarcharTypeDeclarationSQL($field);
77
    }
78
79
    /**
80
     * {@inheritDoc}
81
     */
82
    public function getBlobTypeDeclarationSQL(array $field)
83
    {
84
        // todo blob(n) with $field['length'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
85
        return 'BLOB(1M)';
86
    }
87 85
88
    /**
89
     * {@inheritDoc}
90
     */
91
    public function initializeDoctrineTypeMappings()
92 714
    {
93
        $this->doctrineTypeMapping = [
94 714
            'smallint'      => 'smallint',
95
            'bigint'        => 'bigint',
96
            'integer'       => 'integer',
97 17
            'time'          => 'time',
98
            'date'          => 'date',
99
            'varchar'       => 'string',
100 697
            'character'     => 'string',
101
            'varbinary'     => 'binary',
102
            'binary'        => 'binary',
103
            'clob'          => 'text',
104
            'blob'          => 'blob',
105
            'decimal'       => 'decimal',
106 255
            'double'        => 'float',
107
            'real'          => 'float',
108 255
            'timestamp'     => 'datetime',
109 255
        ];
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 17
    public function isCommentedDoctrineType(Type $doctrineType)
116
    {
117 17
        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
            return true;
121
        }
122
123 51
        return parent::isCommentedDoctrineType($doctrineType);
124
    }
125
126 51
    /**
127
     * {@inheritDoc}
128
     */
129
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
130
    {
131
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(254)')
132 51
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
133
    }
134 51
135
    /**
136
     * {@inheritDoc}
137
     */
138
    public function getClobTypeDeclarationSQL(array $field)
139
    {
140 34
        // todo clob(n) with $field['length'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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