Passed
Push — travis-db2 ( df5cf0...797aa0 )
by Sergei
03:42
created

DB2Platform::getCharMaxLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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