Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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