Completed
Pull Request — master (#4001)
by Grégoire
17:58 queued 03:26
created

DB2Platform::getSubstringExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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