Completed
Pull Request — master (#3133)
by Michael
63:30
created

DB2Platform::getCharMaxLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
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
     * {@inheritdoc}
44
     */
45 36
    public function getCharMaxLength() : int
46
    {
47 36
        return 254;
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 36
    public function getBinaryMaxLength()
54
    {
55 36
        return 32704;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 36
    public function getBinaryDefaultLength()
62
    {
63
        return 1;
64 36
    }
65
66
    /**
67
     * {@inheritDoc}
68
     */
69
    public function getVarcharTypeDeclarationSQL(array $field)
70 90
    {
71
        // for IBM DB2, the CHAR max length is less than VARCHAR default length
72 90
        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
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
83
    {
84
        return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA';
85
    }
86
87
    /**
88
     * {@inheritDoc}
89 90
     */
90
    public function getBlobTypeDeclarationSQL(array $field)
91
    {
92
        // 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...
93
        return 'BLOB(1M)';
94 756
    }
95
96 756
    /**
97
     * {@inheritDoc}
98
     */
99 18
    public function initializeDoctrineTypeMappings()
100
    {
101
        $this->doctrineTypeMapping = [
102 738
            'smallint'      => 'smallint',
103
            'bigint'        => 'bigint',
104
            'integer'       => 'integer',
105
            'time'          => 'time',
106
            'date'          => 'date',
107
            'varchar'       => 'string',
108 270
            'character'     => 'string',
109
            'varbinary'     => 'binary',
110 270
            'binary'        => 'binary',
111 270
            'clob'          => 'text',
112
            'blob'          => 'blob',
113
            'decimal'       => 'decimal',
114
            'double'        => 'float',
115
            'real'          => 'float',
116
            'timestamp'     => 'datetime',
117 18
        ];
118
    }
119 18
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function isCommentedDoctrineType(Type $doctrineType)
124
    {
125 54
        if ($doctrineType->getName() === Type::BOOLEAN) {
126
            // We require a commented boolean type in order to distinguish between boolean and smallint
127
            // as both (have to) map to the same native type.
128 54
            return true;
129
        }
130
131
        return parent::isCommentedDoctrineType($doctrineType);
132
    }
133
134 54
    /**
135
     * {@inheritDoc}
136 54
     */
137
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
138
    {
139
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(254)')
140
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
141
    }
142 36
143
    /**
144 36
     * {@inheritDoc}
145
     */
146
    public function getClobTypeDeclarationSQL(array $field)
147
    {
148
        // 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 216
    }
151
152 216
    /**
153
     * {@inheritDoc}
154
     */
155
    public function getName()
156
    {
157
        return 'db2';
158 18
    }
159
160 18
    /**
161
     * {@inheritDoc}
162
     */
163
    public function getBooleanTypeDeclarationSQL(array $columnDef)
164
    {
165
        return 'SMALLINT';
166 18
    }
167
168 18
    /**
169
     * {@inheritDoc}
170
     */
171
    public function getIntegerTypeDeclarationSQL(array $columnDef)
172
    {
173
        return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
174 216
    }
175
176 216
    /**
177 216
     * {@inheritDoc}
178 36
     */
179
    public function getBigIntTypeDeclarationSQL(array $columnDef)
180
    {
181 216
        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 18
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
196
    {
197 18
        $autoinc = '';
198
        if ( ! empty($columnDef['autoincrement'])) {
199
            $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
200
        }
201
202
        return $autoinc;
203 18
    }
204
205 1
    /**
206 17
     * {@inheritdoc}
207 18
     */
208 18
    public function getBitAndComparisonExpression($value1, $value2)
209 18
    {
210
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
211 17
    }
212 18
213 18
    /**
214 18
     * {@inheritdoc}
215
     */
216
    public function getBitOrComparisonExpression($value1, $value2)
217 18
    {
218
        return 'BITOR(' . $value1 . ', ' . $value2 . ')';
219
    }
220
221
    /**
222
     * {@inheritdoc}
223 18
     */
224
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
225 18
    {
226
        switch ($unit) {
227
            case DateIntervalUnit::WEEK:
228
                $interval *= 7;
229
                $unit      = DateIntervalUnit::DAY;
230
                break;
231 18
232
            case DateIntervalUnit::QUARTER:
233 18
                $interval *= 3;
234 18
                $unit      = DateIntervalUnit::MONTH;
235
                break;
236
        }
237 18
238
        return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243 18
     */
244
    public function getDateDiffExpression($date1, $date2)
245 18
    {
246
        return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')';
247
    }
248
249
    /**
250
     * {@inheritDoc}
251 18
     */
252
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
253 18
    {
254
        if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
255
            return "TIMESTAMP(0) WITH DEFAULT";
256
        }
257
258
        return 'TIMESTAMP(0)';
259 36
    }
260
261 36
    /**
262
     * {@inheritDoc}
263 36
     */
264
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
265
    {
266
        return 'DATE';
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
273
    {
274
        return 'TIME';
275
    }
276 18
277
    /**
278 18
     * {@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
                 c.tabname,
312
                 c.colname,
313 18
                 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
        ORDER BY subq.colno
342
        ";
343 18
    }
344
345 18
    /**
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
        return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS";
359
    }
360 18
361
    /**
362
     * {@inheritDoc}
363
     */
364
    public function getListTableIndexesSQL($table, $currentDatabase = null)
365
    {
366
        $table = $this->quoteStringLiteral($table);
367 18
368
        return "SELECT   idx.INDNAME AS key_name,
369 18
                         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
        return "SELECT   fkcol.COLNAME AS local_column,
393
                         fk.REFTABNAME AS foreign_table,
394 18
                         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 18
                             WHEN fk.DELETERULE = 'C' THEN 'CASCADE'
402
                             WHEN fk.DELETERULE = 'N' THEN 'SET NULL'
403 18
                             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 18
                AND      fk.TABSCHEMA = fkcol.TABSCHEMA
410
                AND      fk.TABNAME = fkcol.TABNAME
411 18
                JOIN     SYSCAT.KEYCOLUSE AS pkcol
412
                ON       fk.REFKEYNAME = pkcol.CONSTNAME
413
                AND      fk.REFTABSCHEMA = pkcol.TABSCHEMA
414
                AND      fk.REFTABNAME = pkcol.TABNAME
415
                WHERE    fk.TABNAME = UPPER(" . $table . ")
416
                ORDER BY fkcol.COLSEQ ASC";
417 18
    }
418
419 18
    /**
420
     * {@inheritDoc}
421
     */
422
    public function getCreateViewSQL($name, $sql)
423
    {
424
        return "CREATE VIEW ".$name." AS ".$sql;
425 18
    }
426
427 18
    /**
428
     * {@inheritDoc}
429
     */
430
    public function getDropViewSQL($name)
431
    {
432
        return "DROP VIEW ".$name;
433 18
    }
434
435 18
    /**
436
     * {@inheritDoc}
437
     */
438
    public function getCreateDatabaseSQL($database)
439
    {
440
        return "CREATE DATABASE ".$database;
441 18
    }
442
443 18
    /**
444
     * {@inheritDoc}
445
     */
446
    public function getDropDatabaseSQL($database)
447
    {
448
        return "DROP DATABASE " . $database;
449 234
    }
450
451 234
    /**
452
     * {@inheritDoc}
453
     */
454
    public function supportsCreateDropDatabase()
455
    {
456
        return false;
457 36
    }
458
459 36
    /**
460
     * {@inheritDoc}
461
     */
462
    public function supportsReleaseSavepoints()
463
    {
464
        return false;
465 18
    }
466
467 18
    /**
468
     * {@inheritdoc}
469
     */
470
    public function supportsCommentOnStatement()
471
    {
472
        return true;
473 36
    }
474
475 36
    /**
476
     * {@inheritDoc}
477
     */
478
    public function getCurrentDateSQL()
479
    {
480
        return 'CURRENT DATE';
481 18
    }
482
483
    /**
484 18
     * {@inheritDoc}
485
     */
486
    public function getCurrentTimeSQL()
487
    {
488
        return 'CURRENT TIME';
489
    }
490 216
491
    /**
492 216
     * {@inheritDoc}
493 216
     */
494 216
    public function getCurrentTimestampSQL()
495
    {
496 216
        return "CURRENT TIMESTAMP";
497
    }
498 216
499
    /**
500 216
     * {@inheritDoc}
501 72
     */
502
    public function getIndexDeclarationSQL($name, Index $index)
503 216
    {
504
        // Index declaration in statements like CREATE TABLE is not supported.
505
        throw DBALException::notSupported(__METHOD__);
506
    }
507
508
    /**
509 432
     * {@inheritDoc}
510
     */
511 432
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
512 432
    {
513 432
        $indexes = [];
514
        if (isset($options['indexes'])) {
515 432
            $indexes = $options['indexes'];
516 432
        }
517 72
        $options['indexes'] = [];
518
519
        $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
520
521 72
        foreach ($indexes as $definition) {
522 72
            $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
523
        }
524
        return $sqls;
525 72
    }
526 72
527 72
    /**
528
     * {@inheritDoc}
529 54
     */
530
    public function getAlterTableSQL(TableDiff $diff)
531
    {
532 72
        $sql = [];
533
        $columnSql = [];
534 72
        $commentsSQL = [];
535
536 72
        $queryParts = [];
537 18
        foreach ($diff->addedColumns as $column) {
538 18
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
539 18
                continue;
540 72
            }
541
542
            $columnDef = $column->toArray();
543
            $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
544
545 432
            // Adding non-nullable columns to a table requires a default value to be specified.
546 54
            if ( ! empty($columnDef['notnull']) &&
547
                ! isset($columnDef['default']) &&
548
                empty($columnDef['autoincrement'])
549
            ) {
550 54
                $queryPart .= ' WITH DEFAULT';
551
            }
552
553 432
            $queryParts[] = $queryPart;
554 306
555
            $comment = $this->getColumnComment($column);
556
557
            if (null !== $comment && '' !== $comment) {
558 306
                $commentsSQL[] = $this->getCommentOnColumnSQL(
559 18
                    $diff->getName($this)->getQuotedName($this),
560 18
                    $column->getQuotedName($this),
561 18
                    $comment
562 18
                );
563
            }
564
        }
565 18
566 18
        foreach ($diff->removedColumns as $column) {
567
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
568
                continue;
569
            }
570 288
571
            $queryParts[] =  'DROP COLUMN ' . $column->getQuotedName($this);
572
        }
573 432
574 72
        foreach ($diff->changedColumns as $columnDiff) {
575
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
576
                continue;
577
            }
578 72
579
            if ($columnDiff->hasChanged('comment')) {
580 72
                $commentsSQL[] = $this->getCommentOnColumnSQL(
581 72
                    $diff->getName($this)->getQuotedName($this),
582
                    $columnDiff->column->getQuotedName($this),
583
                    $this->getColumnComment($columnDiff->column)
584 432
                );
585
586 432
                if (count($columnDiff->changedProperties) === 1) {
587 432
                    continue;
588 324
                }
589
            }
590
591
            $this->gatherAlterColumnSQL($diff->fromTable, $columnDiff, $sql, $queryParts);
592 432
        }
593 306
594
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
595
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
596 432
                continue;
597
            }
598 432
599 36
            $oldColumnName = new Identifier($oldColumnName);
600
601
            $queryParts[] =  'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .
602 432
                ' TO ' . $column->getQuotedName($this);
603 432
        }
604 432
605 432
        $tableSql = [];
606
607
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
608
            if (count($queryParts) > 0) {
609 432
                $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
619
            if ($diff->newName !== false) {
620 288
                $sql[] =  'RENAME TABLE ' . $diff->getName($this)->getQuotedName($this) . ' TO ' . $diff->getNewName()->getQuotedName($this);
621
            }
622 288
623
            $sql = array_merge(
624 288
                $this->getPreAlterTableIndexForeignKeySQL($diff),
625 36
                $sql,
626
                $this->getPostAlterTableIndexForeignKeySQL($diff)
627
            );
628
        }
629 252
630 234
        return array_merge($sql, $tableSql, $columnSql);
631
    }
632 234
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 18
     * @param array      $sql        The sequence of table alteration statements to fill.
639 18
     * @param array      $queryParts The sequence of column alteration clauses to fill.
640
     */
641 18
    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
649
        // If we have a single column alteration, we can append the clause to the main query.
650 288
        if (count($alterColumnClauses) === 1) {
651
            $queryParts[] = current($alterColumnClauses);
652 288
653
            return;
654 288
        }
655
656 288
        // We have multiple alterations for the same column,
657 18
        // so we need to trigger a complete ALTER TABLE statement
658
        // for each ALTER COLUMN clause.
659
        foreach ($alterColumnClauses as $alterColumnClause) {
660 270
            $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause;
661
        }
662 270
    }
663 216
664 198
    /**
665 180
     * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff.
666 270
     *
667
     * @param ColumnDiff $columnDiff The column diff to evaluate.
668 144
     *
669
     * @return array
670
     */
671 270
    private function getAlterColumnClausesSQL(ColumnDiff $columnDiff)
672 72
    {
673
        $column = $columnDiff->column->toArray();
674
675 270
        $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this);
676 72
677 54
        if ($column['columnDefinition']) {
678
            return [$alterClause . ' ' . $column['columnDefinition']];
679 54
        }
680 54
681
        $clauses = [];
682
683 18
        if ($columnDiff->hasChanged('type') ||
684
            $columnDiff->hasChanged('length') ||
685
            $columnDiff->hasChanged('precision') ||
686
            $columnDiff->hasChanged('scale') ||
687 270
            $columnDiff->hasChanged('fixed')
688
        ) {
689
            $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this);
690
        }
691
692
        if ($columnDiff->hasChanged('notnull')) {
693 432
            $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL';
694
        }
695 432
696 432
        if ($columnDiff->hasChanged('default')) {
697
            if (isset($column['default'])) {
698 432
                $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
        $sql = [];
717
        $table = $diff->getName($this)->getQuotedName($this);
718 432
719
        foreach ($diff->removedIndexes as $remKey => $remIndex) {
720 432
            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 90
                    } else {
727
                        $sql[] = $this->getDropIndexSQL($remIndex, $table);
728 90
                    }
729 36
730 36
                    $sql[] = $this->getCreateIndexSQL($addIndex, $table);
731
732
                    unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]);
733 90
734
                    break;
735
                }
736
            }
737
        }
738
739 396
        $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff));
740
741 396
        return $sql;
742 36
    }
743
744
    /**
745 378
     * {@inheritdoc}
746
     */
747
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
748
    {
749
        if (strpos($tableName, '.') !== false) {
750
            list($schema) = explode('.', $tableName);
751 378
            $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
            return '';
764
        }
765 18
766
        if (isset($field['version']) && $field['version']) {
767 18
            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
    {
780
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
781 18
    }
782
783 18
    /**
784
     * {@inheritDoc}
785 18
     */
786 18
    public function getCreateTemporaryTableSnippetSQL()
787
    {
788
        return "DECLARE GLOBAL TEMPORARY TABLE";
789 18
    }
790 18
791
    /**
792
     * {@inheritDoc}
793 18
     */
794 18
    public function getTemporaryTableName($tableName)
795
    {
796
        return "SESSION." . $tableName;
797
    }
798 18
799 18
    /**
800 18
     * {@inheritDoc}
801 18
     */
802
    protected function doModifyLimitQuery($query, $limit, $offset = null)
803
    {
804
        $where = [];
805
806
        if ($offset > 0) {
807
            $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1);
808 18
        }
809
810 18
        if ($limit !== null) {
811 18
            $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit);
812
        }
813
814 18
        if (empty($where)) {
815
            return $query;
816
        }
817
818
        // Todo OVER() needs ORDER BY data!
819
        return sprintf(
820 18
            'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s',
821
            $query,
822 18
            implode(' AND ', $where)
823 18
        );
824
    }
825
826 18
    /**
827
     * {@inheritDoc}
828
     */
829
    public function getLocateExpression($str, $substr, $startPos = false)
830
    {
831
        if ($startPos == false) {
832 18
            return 'LOCATE(' . $substr . ', ' . $str . ')';
833
        }
834 18
835
        return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')';
836
    }
837
838
    /**
839
     * {@inheritDoc}
840 18
     */
841
    public function getSubstringExpression($value, $from, $length = null)
842 18
    {
843
        if ($length === null) {
844
            return 'SUBSTR(' . $value . ', ' . $from . ')';
845
        }
846
847
        return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')';
848
    }
849
850 18
    /**
851
     * {@inheritDoc}
852 18
     */
853
    public function supportsIdentityColumns()
854
    {
855
        return true;
856
    }
857
858 18
    /**
859
     * {@inheritDoc}
860 18
     */
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
    /**
877
     * {@inheritDoc}
878
     */
879
    public function getForUpdateSQL()
880 18
    {
881
        return ' WITH RR USE AND KEEP UPDATE LOCKS';
882 18
    }
883
884
    /**
885
     * {@inheritDoc}
886
     */
887
    public function getDummySelectSQL()
888 936
    {
889
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
890 936
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