Passed
Pull Request — master (#3133)
by Sergei
03:46
created

DB2Platform::getCharMaxLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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