Completed
Pull Request — master (#3610)
by Sergei
31:59 queued 28:53
created

OraclePlatform::getCreateAutoincrementSql()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 56
ccs 27
cts 27
cp 1
rs 8.9599
c 0
b 0
f 0
cc 3
nc 1
nop 3
crap 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Exception\ColumnLengthRequired;
9
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10
use Doctrine\DBAL\Schema\Identifier;
11
use Doctrine\DBAL\Schema\Index;
12
use Doctrine\DBAL\Schema\Sequence;
13
use Doctrine\DBAL\Schema\Table;
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\TransactionIsolationLevel;
16
use Doctrine\DBAL\Types\BinaryType;
17
use InvalidArgumentException;
18
use function array_merge;
19
use function count;
20
use function explode;
21
use function implode;
22
use function preg_match;
23
use function sprintf;
24
use function strlen;
25
use function strpos;
26
use function strtoupper;
27
use function substr;
28
29
/**
30
 * OraclePlatform.
31
 */
32
class OraclePlatform extends AbstractPlatform
33
{
34
    /**
35
     * Assertion for Oracle identifiers.
36
     *
37
     * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
38
     *
39
     * @throws DBALException
40
     */
41 378
    public static function assertValidIdentifier(string $identifier) : void
42
    {
43 378
        if (! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
44 135
            throw new DBALException('Invalid Oracle identifier.');
45
        }
46 243
    }
47
48
    /**
49
     * {@inheritDoc}
50
     */
51 6
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
52
    {
53 6
        if ($length === null) {
54 2
            return sprintf('SUBSTR(%s, %s)', $string, $start);
55
        }
56
57 4
        return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length);
58
    }
59
60
    /**
61
     * {@inheritDoc}
62
     */
63
    public function getNowExpression(string $type = 'timestamp') : string
64
    {
65
        switch ($type) {
66
            case 'date':
67
            case 'time':
68
            case 'timestamp':
69
            default:
70
                return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')';
71
        }
72
    }
73
74
    /**
75
     * {@inheritDoc}
76
     */
77 2
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
78
    {
79 2
        if ($start === null) {
80 2
            return sprintf('INSTR(%s, %s)', $string, $substring);
81
        }
82
83 2
        return sprintf('INSTR(%s, %s, %s)', $string, $substring, $start);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 96
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
90
    {
91
        switch ($unit) {
92 96
            case DateIntervalUnit::MONTH:
93 84
            case DateIntervalUnit::QUARTER:
94 72
            case DateIntervalUnit::YEAR:
95
                switch ($unit) {
96 36
                    case DateIntervalUnit::QUARTER:
97 12
                        $interval = $this->multiplyInterval($interval, 3);
98 12
                        break;
99
100 24
                    case DateIntervalUnit::YEAR:
101 12
                        $interval = $this->multiplyInterval($interval, 12);
102 12
                        break;
103
                }
104
105 36
                return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
106
107
            default:
108 60
                $calculationClause = '';
109
110
                switch ($unit) {
111 60
                    case DateIntervalUnit::SECOND:
112 12
                        $calculationClause = '/24/60/60';
113 12
                        break;
114
115 48
                    case DateIntervalUnit::MINUTE:
116 12
                        $calculationClause = '/24/60';
117 12
                        break;
118
119 36
                    case DateIntervalUnit::HOUR:
120 12
                        $calculationClause = '/24';
121 12
                        break;
122
123 24
                    case DateIntervalUnit::WEEK:
124 12
                        $calculationClause = '*7';
125 12
                        break;
126
                }
127
128 60
                return '(' . $date . $operator . $interval . $calculationClause . ')';
129
        }
130
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135 6
    public function getDateDiffExpression(string $date1, string $date2) : string
136
    {
137 6
        return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2);
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 56
    public function getBitAndComparisonExpression(string $value1, string $value2) : string
144
    {
145 56
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
146
    }
147
148
    /**
149
     * {@inheritDoc}
150
     */
151 126
    public function getCurrentDatabaseExpression() : string
152
    {
153 126
        return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')";
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 29
    public function getBitOrComparisonExpression(string $value1, string $value2) : string
160
    {
161 29
        return '(' . $value1 . '-' .
162 29
                $this->getBitAndComparisonExpression($value1, $value2)
163 29
                . '+' . $value2 . ')';
164
    }
165
166
    /**
167
     * {@inheritDoc}
168
     *
169
     * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
170
     * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
171
     * in {@see listSequences()}
172
     */
173 208
    public function getCreateSequenceSQL(Sequence $sequence) : string
174
    {
175 208
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
176 208
               ' START WITH ' . $sequence->getInitialValue() .
177 208
               ' MINVALUE ' . $sequence->getInitialValue() .
178 208
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
179 208
               $this->getSequenceCacheSQL($sequence);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function getAlterSequenceSQL(Sequence $sequence) : string
186
    {
187
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
188
               ' INCREMENT BY ' . $sequence->getAllocationSize()
189
               . $this->getSequenceCacheSQL($sequence);
190
    }
191
192
    /**
193
     * Cache definition for sequences
194
     */
195 208
    private function getSequenceCacheSQL(Sequence $sequence) : string
196
    {
197 208
        if ($sequence->getCache() === 0) {
198 27
            return ' NOCACHE';
199
        }
200
201 181
        if ($sequence->getCache() === 1) {
202 27
            return ' NOCACHE';
203
        }
204
205 154
        if ($sequence->getCache() > 1) {
206 27
            return ' CACHE ' . $sequence->getCache();
207
        }
208
209 127
        return '';
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     */
215 1
    public function getSequenceNextValSQL(string $sequenceName) : string
216
    {
217 1
        return 'SELECT ' . $sequenceName . '.nextval FROM DUAL';
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223 27
    public function getSetTransactionIsolationSQL(int $level) : string
224
    {
225 27
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 27
    protected function _getTransactionIsolationLevelSQL(int $level) : string
232
    {
233
        switch ($level) {
234 27
            case TransactionIsolationLevel::READ_UNCOMMITTED:
235 27
                return 'READ UNCOMMITTED';
236 27
            case TransactionIsolationLevel::READ_COMMITTED:
237 27
                return 'READ COMMITTED';
238 27
            case TransactionIsolationLevel::REPEATABLE_READ:
239 27
            case TransactionIsolationLevel::SERIALIZABLE:
240 27
                return 'SERIALIZABLE';
241
            default:
242
                return parent::_getTransactionIsolationLevelSQL($level);
243
        }
244
    }
245
246
    /**
247
     * {@inheritDoc}
248
     */
249 58
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
250
    {
251 58
        return 'NUMBER(1)';
252
    }
253
254
    /**
255
     * {@inheritDoc}
256
     */
257 526
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
258
    {
259 526
        return 'NUMBER(10)';
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     */
265 29
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
266
    {
267 29
        return 'NUMBER(20)';
268
    }
269
270
    /**
271
     * {@inheritDoc}
272
     */
273 2
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
274
    {
275 2
        return 'NUMBER(5)';
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281 47
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
282
    {
283 47
        return 'TIMESTAMP(0)';
284
    }
285
286
    /**
287
     * {@inheritDoc}
288
     */
289 31
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) : string
290
    {
291 31
        return 'TIMESTAMP(0) WITH TIME ZONE';
292
    }
293
294
    /**
295
     * {@inheritDoc}
296
     */
297 39
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
298
    {
299 39
        return 'DATE';
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305 35
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
306
    {
307 35
        return 'DATE';
308
    }
309
310
    /**
311
     * {@inheritDoc}
312
     */
313
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
314
    {
315
        return '';
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321 495
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
322
    {
323 495
        if ($length === null) {
324 27
            throw ColumnLengthRequired::new($this, 'VARCHAR2');
325
        }
326
327 468
        return sprintf('VARCHAR2(%d)', $length);
328
    }
329
330
    /**
331
     * {@inheritDoc}
332
     */
333 111
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
334
    {
335 111
        if ($length === null) {
336 54
            throw ColumnLengthRequired::new($this, 'RAW');
337
        }
338
339 57
        return sprintf('RAW(%d)', $length);
340
    }
341
342
    /**
343
     * {@inheritDoc}
344
     */
345 57
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
346
    {
347 57
        return $this->getBinaryTypeDeclarationSQLSnippet($length);
348
    }
349
350
    /**
351
     * {@inheritDoc}
352
     */
353 129
    public function getClobTypeDeclarationSQL(array $field) : string
354
    {
355 129
        return 'CLOB';
356
    }
357
358
    /**
359
     * {@inheritDoc}
360
     */
361 4
    public function getListDatabasesSQL() : string
362
    {
363 4
        return 'SELECT username FROM all_users';
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369 36
    public function getListSequencesSQL(string $database) : string
370
    {
371
        return 'SELECT SEQUENCE_NAME, MIN_VALUE, INCREMENT_BY FROM SYS.ALL_SEQUENCES WHERE SEQUENCE_OWNER = '
372 36
            . $this->quoteStringLiteral(
373 36
                $this->normalizeIdentifier($database)->getName()
374
            );
375
    }
376
377
    /**
378
     * {@inheritDoc}
379
     */
380 570
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
381
    {
382 570
        $indexes            = $options['indexes'] ?? [];
383 570
        $options['indexes'] = [];
384 570
        $sql                = parent::_getCreateTableSQL($tableName, $columns, $options);
385
386 570
        foreach ($columns as $column) {
387 570
            if (isset($column['sequence'])) {
388
                $sql[] = $this->getCreateSequenceSQL($column['sequence']);
389
            }
390
391 570
            if (empty($column['autoincrement'])) {
392 516
                continue;
393
            }
394
395 121
            $sql = array_merge($sql, $this->getCreateAutoincrementSql($column['name'], $tableName));
396
        }
397
398 570
        if (isset($indexes) && ! empty($indexes)) {
399 109
            foreach ($indexes as $index) {
400 109
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
401
            }
402
        }
403
404 570
        return $sql;
405
    }
406
407
    /**
408
     * {@inheritDoc}
409
     *
410
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
411
     */
412 115
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
413
    {
414 115
        $table = $this->normalizeIdentifier($table);
415 115
        $table = $this->quoteStringLiteral($table->getName());
416
417
        return "SELECT uind_col.index_name AS name,
418
                       (
419
                           SELECT uind.index_type
420
                           FROM   user_indexes uind
421
                           WHERE  uind.index_name = uind_col.index_name
422
                       ) AS type,
423
                       decode(
424
                           (
425
                               SELECT uind.uniqueness
426
                               FROM   user_indexes uind
427
                               WHERE  uind.index_name = uind_col.index_name
428
                           ),
429
                           'NONUNIQUE',
430
                           0,
431
                           'UNIQUE',
432
                           1
433
                       ) AS is_unique,
434
                       uind_col.column_name AS column_name,
435
                       uind_col.column_position AS column_pos,
436
                       (
437
                           SELECT ucon.constraint_type
438
                           FROM   user_constraints ucon
439
                           WHERE  ucon.index_name = uind_col.index_name
440
                       ) AS is_primary
441
             FROM      user_ind_columns uind_col
442 115
             WHERE     uind_col.table_name = " . $table . '
443
             ORDER BY  uind_col.column_position ASC';
444
    }
445
446
    /**
447
     * {@inheritDoc}
448
     */
449 26
    public function getListTablesSQL() : string
450
    {
451 26
        return 'SELECT * FROM sys.user_tables';
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457 2
    public function getListViewsSQL(string $database) : string
458
    {
459 2
        return 'SELECT view_name, text FROM sys.user_views';
460
    }
461
462
    /**
463
     * {@inheritDoc}
464
     */
465 2
    public function getCreateViewSQL(string $name, string $sql) : string
466
    {
467 2
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
468
    }
469
470
    /**
471
     * {@inheritDoc}
472
     */
473 2
    public function getDropViewSQL(string $name) : string
474
    {
475 2
        return 'DROP VIEW ' . $name;
476
    }
477
478
    /**
479
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
480
     */
481 121
    public function getCreateAutoincrementSql(string $name, string $table, int $start = 1) : array
482
    {
483 121
        $tableIdentifier   = $this->normalizeIdentifier($table);
484 121
        $quotedTableName   = $tableIdentifier->getQuotedName($this);
485 121
        $unquotedTableName = $tableIdentifier->getName();
486
487 121
        $nameIdentifier = $this->normalizeIdentifier($name);
488 121
        $quotedName     = $nameIdentifier->getQuotedName($this);
489 121
        $unquotedName   = $nameIdentifier->getName();
490
491 121
        $sql = [];
492
493 121
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
494
495 121
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
496
497 121
        $sql[] = 'DECLARE
498
  constraints_Count NUMBER;
499
BEGIN
500 121
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
501
  IF constraints_Count = 0 OR constraints_Count = \'\' THEN
502 121
    EXECUTE IMMEDIATE \'' . $this->getCreateConstraintSQL($idx, $quotedTableName) . '\';
503
  END IF;
504
END;';
505
506 121
        $sequenceName = $this->getIdentitySequenceName(
507 121
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
508 121
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
509
        );
510 121
        $sequence     = new Sequence($sequenceName, $start);
511 121
        $sql[]        = $this->getCreateSequenceSQL($sequence);
512
513 121
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
514
   BEFORE INSERT
515 121
   ON ' . $quotedTableName . '
516
   FOR EACH ROW
517
DECLARE
518
   last_Sequence NUMBER;
519
   last_InsertID NUMBER;
520
BEGIN
521 121
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
522 121
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN
523 121
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
524
   ELSE
525
      SELECT NVL(Last_Number, 0) INTO last_Sequence
526
        FROM User_Sequences
527 121
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
528 121
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
529
      WHILE (last_InsertID > last_Sequence) LOOP
530 121
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
531
      END LOOP;
532
   END IF;
533
END;';
534
535 121
        return $sql;
536
    }
537
538
    /**
539
     * Returns the SQL statements to drop the autoincrement for the given table name.
540
     *
541
     * @param string $table The table name to drop the autoincrement for.
542
     *
543
     * @return string[]
544
     */
545 344
    public function getDropAutoincrementSql(string $table) : array
546
    {
547 344
        $table                       = $this->normalizeIdentifier($table);
548 344
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
549 344
        $identitySequenceName        = $this->getIdentitySequenceName(
550 344
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
551 344
            ''
552
        );
553
554
        return [
555 344
            'DROP TRIGGER ' . $autoincrementIdentifierName,
556 344
            $this->getDropSequenceSQL($identitySequenceName),
557 344
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
558
        ];
559
    }
560
561
    /**
562
     * Normalizes the given identifier.
563
     *
564
     * Uppercases the given identifier if it is not quoted by intention
565
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
566
     *
567
     * @param string $name The identifier to normalize.
568
     *
569
     * @return Identifier The normalized identifier.
570
     */
571 729
    private function normalizeIdentifier(string $name) : Identifier
572
    {
573 729
        $identifier = new Identifier($name);
574
575 729
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
576
    }
577
578
    /**
579
     * Returns the autoincrement primary key identifier name for the given table identifier.
580
     *
581
     * Quotes the autoincrement primary key identifier name
582
     * if the given table name is quoted by intention.
583
     *
584
     * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
585
     */
586 427
    private function getAutoincrementIdentifierName(Identifier $table) : string
587
    {
588 427
        $identifierName = $table->getName() . '_AI_PK';
589
590 427
        return $table->isQuoted()
591 56
            ? $this->quoteSingleIdentifier($identifierName)
592 427
            : $identifierName;
593
    }
594
595
    /**
596
     * {@inheritDoc}
597
     */
598 109
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
599
    {
600 109
        $table = $this->normalizeIdentifier($table);
601 109
        $table = $this->quoteStringLiteral($table->getName());
602
603
        return "SELECT alc.constraint_name,
604
          alc.DELETE_RULE,
605
          cols.column_name \"local_column\",
606
          cols.position,
607
          (
608
              SELECT r_cols.table_name
609
              FROM   user_cons_columns r_cols
610
              WHERE  alc.r_constraint_name = r_cols.constraint_name
611
              AND    r_cols.position = cols.position
612
          ) AS \"references_table\",
613
          (
614
              SELECT r_cols.column_name
615
              FROM   user_cons_columns r_cols
616
              WHERE  alc.r_constraint_name = r_cols.constraint_name
617
              AND    r_cols.position = cols.position
618
          ) AS \"foreign_column\"
619
     FROM user_cons_columns cols
620
     JOIN user_constraints alc
621
       ON alc.constraint_name = cols.constraint_name
622
      AND alc.constraint_type = 'R'
623 109
      AND alc.table_name = " . $table . '
624
    ORDER BY cols.constraint_name ASC, cols.position ASC';
625
    }
626
627
    /**
628
     * {@inheritDoc}
629
     */
630 27
    public function getListTableConstraintsSQL(string $table) : string
631
    {
632 27
        $table = $this->normalizeIdentifier($table);
633 27
        $table = $this->quoteStringLiteral($table->getName());
634
635 27
        return 'SELECT * FROM user_constraints WHERE table_name = ' . $table;
636
    }
637
638
    /**
639
     * {@inheritDoc}
640
     */
641 237
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
642
    {
643 237
        $table = $this->normalizeIdentifier($table);
644 237
        $table = $this->quoteStringLiteral($table->getName());
645
646 237
        $tabColumnsTableName       = 'user_tab_columns';
647 237
        $colCommentsTableName      = 'user_col_comments';
648 237
        $tabColumnsOwnerCondition  = '';
649 237
        $colCommentsOwnerCondition = '';
650
651 237
        if ($database !== null && $database !== '/') {
652 156
            $database                  = $this->normalizeIdentifier($database);
653 156
            $database                  = $this->quoteStringLiteral($database->getName());
654 156
            $tabColumnsTableName       = 'all_tab_columns';
655 156
            $colCommentsTableName      = 'all_col_comments';
656 156
            $tabColumnsOwnerCondition  = ' AND c.owner = ' . $database;
657 156
            $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER';
658
        }
659
660 237
        return sprintf(
661
            <<<'SQL'
662 112
SELECT   c.*,
663
         (
664
             SELECT d.comments
665
             FROM   %s d
666
             WHERE  d.TABLE_NAME = c.TABLE_NAME%s
667
             AND    d.COLUMN_NAME = c.COLUMN_NAME
668
         ) AS comments
669
FROM     %s c
670
WHERE    c.table_name = %s%s
671
ORDER BY c.column_id
672
SQL
673
            ,
674 237
            $colCommentsTableName,
675 237
            $colCommentsOwnerCondition,
676 237
            $tabColumnsTableName,
677 237
            $table,
678 237
            $tabColumnsOwnerCondition
679
        );
680
    }
681
682
    /**
683
     * {@inheritDoc}
684
     */
685 344
    public function getDropSequenceSQL($sequence) : string
686
    {
687 344
        if ($sequence instanceof Sequence) {
688
            $sequence = $sequence->getQuotedName($this);
689
        }
690
691 344
        return 'DROP SEQUENCE ' . $sequence;
692
    }
693
694
    /**
695
     * {@inheritDoc}
696
     */
697 56
    public function getDropForeignKeySQL($foreignKey, $table) : string
698
    {
699 56
        if (! $foreignKey instanceof ForeignKeyConstraint) {
700 27
            $foreignKey = new Identifier($foreignKey);
701
        }
702
703 56
        if (! $table instanceof Table) {
704 56
            $table = new Identifier($table);
705
        }
706
707 56
        $foreignKey = $foreignKey->getQuotedName($this);
708 56
        $table      = $table->getQuotedName($this);
709
710 56
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
711
    }
712
713
    /**
714
     * {@inheritdoc}
715
     */
716 294
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
717
    {
718 294
        $referentialAction = null;
719
720 294
        if ($foreignKey->hasOption('onDelete')) {
721 110
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
722
        }
723
724 294
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
725
    }
726
727
    /**
728
     * {@inheritdoc}
729
     */
730 272
    public function getForeignKeyReferentialActionSQL(string $action) : string
731
    {
732 272
        $action = strtoupper($action);
733
734 22
        switch ($action) {
735 272
            case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION.
736 218
            case 'NO ACTION':
737
                // NO ACTION cannot be declared explicitly,
738
                // therefore returning empty string to indicate to OMIT the referential clause.
739 108
                return '';
740
741 164
            case 'CASCADE':
742 81
            case 'SET NULL':
743 137
                return $action;
744
745
            default:
746
                // SET DEFAULT is not supported, throw exception instead.
747 27
                throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $action));
748
        }
749
    }
750
751
    /**
752
     * {@inheritDoc}
753
     */
754 31
    public function getDropDatabaseSQL(string $database) : string
755
    {
756 31
        return 'DROP USER ' . $database . ' CASCADE';
757
    }
758
759
    /**
760
     * {@inheritDoc}
761
     */
762 466
    public function getAlterTableSQL(TableDiff $diff) : array
763
    {
764 466
        $sql         = [];
765 466
        $commentsSQL = [];
766 466
        $columnSql   = [];
767
768 466
        $fields = [];
769
770 466
        foreach ($diff->addedColumns as $column) {
771 110
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
772
                continue;
773
            }
774
775 110
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
776 110
            $comment  = $this->getColumnComment($column);
777
778 110
            if (! $comment) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $comment of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
779 83
                continue;
780
            }
781
782 27
            $commentsSQL[] = $this->getCommentOnColumnSQL(
783 27
                $diff->getName($this)->getQuotedName($this),
784 27
                $column->getQuotedName($this),
785
                $comment
786
            );
787
        }
788
789 466
        if (count($fields)) {
790 110
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
791
        }
792
793 466
        $fields = [];
794 466
        foreach ($diff->changedColumns as $columnDiff) {
795 269
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
796
                continue;
797
            }
798
799 269
            $column = $columnDiff->column;
800
801
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
802
            // Oracle only supports binary type columns with variable length.
803
            // Avoids unnecessary table alteration statements.
804 269
            if ($column->getType() instanceof BinaryType &&
805 269
                $columnDiff->hasChanged('fixed') &&
806 269
                count($columnDiff->changedProperties) === 1
807
            ) {
808 27
                continue;
809
            }
810
811 242
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
812
813
            /**
814
             * Do not add query part if only comment has changed
815
             */
816 242
            if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
817 168
                $columnInfo = $column->toArray();
818
819 168
                if (! $columnDiff->hasChanged('notnull')) {
820 114
                    unset($columnInfo['notnull']);
821
                }
822
823 168
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
824
            }
825
826 242
            if (! $columnHasChangedComment) {
827 168
                continue;
828
            }
829
830 74
            $commentsSQL[] = $this->getCommentOnColumnSQL(
831 74
                $diff->getName($this)->getQuotedName($this),
832 74
                $column->getQuotedName($this),
833 74
                $this->getColumnComment($column)
834
            );
835
        }
836
837 466
        if (count($fields)) {
838 168
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
839
        }
840
841 466
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
842 110
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
843
                continue;
844
            }
845
846 110
            $oldColumnName = new Identifier($oldColumnName);
847
848 110
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
849 110
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
850
        }
851
852 466
        $fields = [];
853 466
        foreach ($diff->removedColumns as $column) {
854 83
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
855
                continue;
856
            }
857
858 83
            $fields[] = $column->getQuotedName($this);
859
        }
860
861 466
        if (count($fields)) {
862 83
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields) . ')';
863
        }
864
865 466
        $tableSql = [];
866
867 466
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
868 466
            $sql = array_merge($sql, $commentsSQL);
869
870 466
            $newName = $diff->getNewName();
871
872 466
            if ($newName !== null) {
873 56
                $sql[] = sprintf(
874 6
                    'ALTER TABLE %s RENAME TO %s',
875 56
                    $diff->getName($this)->getQuotedName($this),
876 56
                    $newName->getQuotedName($this)
877
                );
878
            }
879
880 466
            $sql = array_merge(
881 466
                $this->getPreAlterTableIndexForeignKeySQL($diff),
882 466
                $sql,
883 466
                $this->getPostAlterTableIndexForeignKeySQL($diff)
884
            );
885
        }
886
887 466
        return array_merge($sql, $tableSql, $columnSql);
888
    }
889
890
    /**
891
     * {@inheritdoc}
892
     */
893 786
    public function getColumnDeclarationSQL(string $name, array $field) : string
894
    {
895 786
        if (isset($field['columnDefinition'])) {
896 27
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
897
        } else {
898 759
            $default = $this->getDefaultValueDeclarationSQL($field);
899
900 759
            $notnull = '';
901
902 759
            if (isset($field['notnull'])) {
903 705
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
904
            }
905
906 759
            $unique = isset($field['unique']) && $field['unique'] ?
907 759
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
908
909 759
            $check = isset($field['check']) && $field['check'] ?
910 759
                ' ' . $field['check'] : '';
911
912 759
            $typeDecl  = $field['type']->getSQLDeclaration($field, $this);
913 759
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
914
        }
915
916 786
        return $name . ' ' . $columnDef;
917
    }
918
919
    /**
920
     * {@inheritdoc}
921
     */
922 139
    protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName) : array
923
    {
924 139
        if (strpos($tableName, '.') !== false) {
925 54
            [$schema]     = explode('.', $tableName);
0 ignored issues
show
Bug introduced by
The variable $schema does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
926 54
            $oldIndexName = $schema . '.' . $oldIndexName;
927
        }
928
929 139
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
930
    }
931
932
    /**
933
     * {@inheritDoc}
934
     */
935
    public function prefersSequences() : bool
936
    {
937
        return true;
938
    }
939
940
    /**
941
     * {@inheritdoc}
942
     */
943 28
    public function usesSequenceEmulatedIdentityColumns() : bool
944
    {
945 28
        return true;
946
    }
947
948
    /**
949
     * {@inheritdoc}
950
     */
951 454
    public function getIdentitySequenceName(string $tableName, string $columnName) : string
952
    {
953 454
        $table = new Identifier($tableName);
954
955
        // No usage of column name to preserve BC compatibility with <2.5
956 454
        $identitySequenceName = $table->getName() . '_SEQ';
957
958 454
        if ($table->isQuoted()) {
959 83
            $identitySequenceName = '"' . $identitySequenceName . '"';
960
        }
961
962 454
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
963
964 454
        return $identitySequenceIdentifier->getQuotedName($this);
965
    }
966
967
    /**
968
     * {@inheritDoc}
969
     */
970 597
    public function supportsCommentOnStatement() : bool
971
    {
972 597
        return true;
973
    }
974
975
    /**
976
     * {@inheritDoc}
977
     */
978 259
    public function getName() : string
979
    {
980 259
        return 'oracle';
981
    }
982
983
    /**
984
     * {@inheritDoc}
985
     */
986 205
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
987
    {
988 205
        if ($limit === null && $offset <= 0) {
989 29
            return $query;
990
        }
991
992 176
        if (preg_match('/^\s*SELECT/i', $query)) {
993 176
            if (! preg_match('/\sFROM\s/i', $query)) {
994
                $query .= ' FROM dual';
995
            }
996
997 176
            $columns = ['a.*'];
998
999 176
            if ($offset > 0) {
1000 68
                $columns[] = 'ROWNUM AS doctrine_rownum';
1001
            }
1002
1003 176
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1004
1005 176
            if ($limit !== null) {
1006 149
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1007
            }
1008
1009 176
            if ($offset > 0) {
1010 68
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1011
            }
1012
        }
1013
1014 176
        return $query;
1015
    }
1016
1017
    /**
1018
     * {@inheritDoc}
1019
     *
1020
     * Oracle returns all column names in SQL result sets in uppercase.
1021
     */
1022
    public function getSQLResultCasing(string $column) : string
1023
    {
1024
        return strtoupper($column);
1025
    }
1026
1027
    /**
1028
     * {@inheritDoc}
1029
     */
1030
    public function getCreateTemporaryTableSnippetSQL() : string
1031
    {
1032
        return 'CREATE GLOBAL TEMPORARY TABLE';
1033
    }
1034
1035
    /**
1036
     * {@inheritDoc}
1037
     */
1038 2
    public function getDateTimeTzFormatString() : string
1039
    {
1040 2
        return 'Y-m-d H:i:sP';
1041
    }
1042
1043
    /**
1044
     * {@inheritDoc}
1045
     */
1046 2
    public function getDateFormatString() : string
1047
    {
1048 2
        return 'Y-m-d 00:00:00';
1049
    }
1050
1051
    /**
1052
     * {@inheritDoc}
1053
     */
1054 2
    public function getTimeFormatString() : string
1055
    {
1056 2
        return '1900-01-01 H:i:s';
1057
    }
1058
1059
    /**
1060
     * {@inheritDoc}
1061
     */
1062
    public function fixSchemaElementName(string $schemaElementName) : string
1063
    {
1064
        if (strlen($schemaElementName) > 30) {
1065
            // Trim it
1066
            return substr($schemaElementName, 0, 30);
1067
        }
1068
1069
        return $schemaElementName;
1070
    }
1071
1072
    /**
1073
     * {@inheritDoc}
1074
     */
1075 26
    public function getMaxIdentifierLength() : int
1076
    {
1077 26
        return 30;
1078
    }
1079
1080
    /**
1081
     * {@inheritDoc}
1082
     */
1083 10
    public function supportsSequences() : bool
1084
    {
1085 10
        return true;
1086
    }
1087
1088
    /**
1089
     * {@inheritDoc}
1090
     */
1091
    public function supportsForeignKeyOnUpdate() : bool
1092
    {
1093
        return false;
1094
    }
1095
1096
    /**
1097
     * {@inheritDoc}
1098
     */
1099 2
    public function supportsReleaseSavepoints() : bool
1100
    {
1101 2
        return false;
1102
    }
1103
1104
    /**
1105
     * {@inheritDoc}
1106
     */
1107 45
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
1108
    {
1109 45
        $tableIdentifier = new Identifier($tableName);
1110
1111 45
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1112
    }
1113
1114
    /**
1115
     * {@inheritDoc}
1116
     */
1117 157
    public function getDummySelectSQL(string $expression = '1') : string
1118
    {
1119 157
        return sprintf('SELECT %s FROM DUAL', $expression);
1120
    }
1121
1122
    /**
1123
     * {@inheritDoc}
1124
     */
1125 137
    protected function initializeDoctrineTypeMappings() : void
1126
    {
1127 137
        $this->doctrineTypeMapping = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('binary_double' =>...'varchar2' => 'string') of type array<string,string,{"bi...","varchar2":"string"}> is incompatible with the declared type array<integer,string>|null of property $doctrineTypeMapping.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1128
            'binary_double'  => 'float',
1129
            'binary_float'   => 'float',
1130
            'binary_integer' => 'boolean',
1131
            'blob'           => 'blob',
1132
            'char'           => 'string',
1133
            'clob'           => 'text',
1134
            'date'           => 'date',
1135
            'float'          => 'float',
1136
            'integer'        => 'integer',
1137
            'long'           => 'string',
1138
            'long raw'       => 'blob',
1139
            'nchar'          => 'string',
1140
            'nclob'          => 'text',
1141
            'number'         => 'integer',
1142
            'nvarchar2'      => 'string',
1143
            'pls_integer'    => 'boolean',
1144
            'raw'            => 'binary',
1145
            'rowid'          => 'string',
1146
            'timestamp'      => 'datetime',
1147
            'timestamptz'    => 'datetimetz',
1148
            'urowid'         => 'string',
1149
            'varchar'        => 'string',
1150
            'varchar2'       => 'string',
1151
        ];
1152 137
    }
1153
1154
    /**
1155
     * {@inheritDoc}
1156
     */
1157
    public function releaseSavePoint(string $savepoint) : string
1158
    {
1159
        return '';
1160
    }
1161
1162
    /**
1163
     * {@inheritDoc}
1164
     */
1165 1346
    protected function getReservedKeywordsClass() : string
1166
    {
1167 1346
        return Keywords\OracleKeywords::class;
1168
    }
1169
1170
    /**
1171
     * {@inheritDoc}
1172
     */
1173 6
    public function getBlobTypeDeclarationSQL(array $field) : string
1174
    {
1175 6
        return 'BLOB';
1176
    }
1177
1178 78
    public function getListTableCommentsSQL(string $table, ?string $database = null) : string
1179
    {
1180 78
        $tableCommentsName = 'user_tab_comments';
1181 78
        $ownerCondition    = '';
1182
1183 78
        if ($database !== null && $database !== '/') {
1184
            $tableCommentsName = 'all_tab_comments';
1185
            $ownerCondition    = ' AND owner = ' . $this->quoteStringLiteral($this->normalizeIdentifier($database)->getName());
1186
        }
1187
1188 78
        return sprintf(
1189
            <<<'SQL'
1190 78
SELECT comments FROM %s WHERE table_name = %s%s
1191
SQL
1192
            ,
1193 78
            $tableCommentsName,
1194 78
            $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()),
1195 78
            $ownerCondition
1196
        );
1197
    }
1198
}
1199