Completed
Push — develop ( fa42c1...0ef7d4 )
by Sergei
22:52
created

OraclePlatform::getAlterTableSQL()   F

Complexity

Conditions 22
Paths 7776

Size

Total Lines 121
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 60
CRAP Score 22.118

Importance

Changes 0
Metric Value
eloc 63
dl 0
loc 121
ccs 60
cts 64
cp 0.9375
rs 0
c 0
b 0
f 0
cc 22
nc 7776
nop 1
crap 22.118

How to fix   Long Method    Complexity   

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
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\Schema\ColumnDiff;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Identifier;
9
use Doctrine\DBAL\Schema\Index;
10
use Doctrine\DBAL\Schema\Sequence;
11
use Doctrine\DBAL\Schema\Table;
12
use Doctrine\DBAL\Schema\TableDiff;
13
use Doctrine\DBAL\TransactionIsolationLevel;
14
use Doctrine\DBAL\Types\BinaryType;
15
use InvalidArgumentException;
16
use function array_merge;
17
use function count;
18
use function explode;
19
use function implode;
20
use function preg_match;
21
use function sprintf;
22
use function strlen;
23
use function strpos;
24
use function strtoupper;
25
use function substr;
26
27
/**
28
 * OraclePlatform.
29
 */
30
class OraclePlatform extends AbstractPlatform
31
{
32
    /**
33
     * Assertion for Oracle identifiers.
34
     *
35
     * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
36
     *
37
     * @param string $identifier
38
     *
39
     * @throws DBALException
40
     */
41 322
    public static function assertValidIdentifier($identifier)
42
    {
43 322
        if (! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
44 115
            throw new DBALException('Invalid Oracle identifier');
45
        }
46 207
    }
47
48
    /**
49
     * {@inheritDoc}
50
     */
51
    public function getSubstringExpression($value, $position, $length = null)
52
    {
53
        if ($length !== null) {
54
            return sprintf('SUBSTR(%s, %d, %d)', $value, $position, $length);
55
        }
56
57
        return sprintf('SUBSTR(%s, %d)', $value, $position);
58
    }
59
60
    /**
61
     * {@inheritDoc}
62
     */
63
    public function getNowExpression($type = 'timestamp')
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 1
    public function getLocateExpression($str, $substr, $startPos = false)
78
    {
79 1
        if ($startPos === false) {
80 1
            return 'INSTR(' . $str . ', ' . $substr . ')';
81
        }
82
83 1
        return 'INSTR(' . $str . ', ' . $substr . ', ' . $startPos . ')';
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 1
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
90
    {
91
        switch ($unit) {
92 1
            case DateIntervalUnit::MONTH:
93 1
            case DateIntervalUnit::QUARTER:
94 1
            case DateIntervalUnit::YEAR:
95
                switch ($unit) {
96 1
                    case DateIntervalUnit::QUARTER:
97 1
                        $interval *= 3;
98 1
                        break;
99
100 1
                    case DateIntervalUnit::YEAR:
101 1
                        $interval *= 12;
102 1
                        break;
103
                }
104
105 1
                return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
106
107
            default:
108 1
                $calculationClause = '';
109
110
                switch ($unit) {
111 1
                    case DateIntervalUnit::SECOND:
112 1
                        $calculationClause = '/24/60/60';
113 1
                        break;
114
115 1
                    case DateIntervalUnit::MINUTE:
116 1
                        $calculationClause = '/24/60';
117 1
                        break;
118
119 1
                    case DateIntervalUnit::HOUR:
120 1
                        $calculationClause = '/24';
121 1
                        break;
122
123 1
                    case DateIntervalUnit::WEEK:
124 1
                        $calculationClause = '*7';
125 1
                        break;
126
                }
127
128 1
                return '(' . $date . $operator . $interval . $calculationClause . ')';
129
        }
130
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135 3
    public function getDateDiffExpression($date1, $date2)
136
    {
137 3
        return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2);
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 47
    public function getBitAndComparisonExpression($value1, $value2)
144
    {
145 47
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
146
    }
147
148
    /**
149
     * {@inheritDoc}
150
     */
151 24
    public function getBitOrComparisonExpression($value1, $value2)
152
    {
153 24
        return '(' . $value1 . '-' .
154 24
                $this->getBitAndComparisonExpression($value1, $value2)
155 24
                . '+' . $value2 . ')';
156
    }
157
158
    /**
159
     * {@inheritDoc}
160
     *
161
     * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
162
     * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
163
     * in {@see listSequences()}
164
     */
165 164
    public function getCreateSequenceSQL(Sequence $sequence)
166
    {
167 164
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
168 164
               ' START WITH ' . $sequence->getInitialValue() .
169 164
               ' MINVALUE ' . $sequence->getInitialValue() .
170 164
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
171 164
               $this->getSequenceCacheSQL($sequence);
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177
    public function getAlterSequenceSQL(Sequence $sequence)
178
    {
179
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
180
               ' INCREMENT BY ' . $sequence->getAllocationSize()
181
               . $this->getSequenceCacheSQL($sequence);
182
    }
183
184
    /**
185
     * Cache definition for sequences
186
     *
187
     * @return string
188
     */
189 164
    private function getSequenceCacheSQL(Sequence $sequence)
190
    {
191 164
        if ($sequence->getCache() === 0) {
192 23
            return ' NOCACHE';
193 141
        } elseif ($sequence->getCache() === 1) {
194 23
            return ' NOCACHE';
195 118
        } elseif ($sequence->getCache() > 1) {
196 23
            return ' CACHE ' . $sequence->getCache();
197
        }
198
199 95
        return '';
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 1
    public function getSequenceNextValSQL($sequenceName)
206
    {
207 1
        return 'SELECT ' . $sequenceName . '.nextval FROM DUAL';
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213 23
    public function getSetTransactionIsolationSQL($level)
214
    {
215 23
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
216
    }
217
218
    /**
219
     * {@inheritDoc}
220
     */
221 23
    protected function _getTransactionIsolationLevelSQL($level)
222
    {
223 23
        switch ($level) {
224
            case TransactionIsolationLevel::READ_UNCOMMITTED:
225 23
                return 'READ UNCOMMITTED';
226
            case TransactionIsolationLevel::READ_COMMITTED:
227 23
                return 'READ COMMITTED';
228
            case TransactionIsolationLevel::REPEATABLE_READ:
229
            case TransactionIsolationLevel::SERIALIZABLE:
230 23
                return 'SERIALIZABLE';
231
            default:
232
                return parent::_getTransactionIsolationLevelSQL($level);
233
        }
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239 38
    public function getBooleanTypeDeclarationSQL(array $field)
240
    {
241 38
        return 'NUMBER(1)';
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 361
    public function getIntegerTypeDeclarationSQL(array $field)
248
    {
249 361
        return 'NUMBER(10)';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 15
    public function getBigIntTypeDeclarationSQL(array $field)
256
    {
257 15
        return 'NUMBER(20)';
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 1
    public function getSmallIntTypeDeclarationSQL(array $field)
264
    {
265 1
        return 'NUMBER(5)';
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     */
271 23
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
272
    {
273 23
        return 'TIMESTAMP(0)';
274
    }
275
276
    /**
277
     * {@inheritDoc}
278
     */
279 16
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
280
    {
281 16
        return 'TIMESTAMP(0) WITH TIME ZONE';
282
    }
283
284
    /**
285
     * {@inheritDoc}
286
     */
287 19
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
288
    {
289 19
        return 'DATE';
290
    }
291
292
    /**
293
     * {@inheritDoc}
294
     */
295 18
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
296
    {
297 18
        return 'DATE';
298
    }
299
300
    /**
301
     * {@inheritDoc}
302
     */
303
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
304
    {
305
        return '';
306
    }
307
308
    /**
309
     * {@inheritDoc}
310
     */
311 395
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
312
    {
313 395
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
314 395
                : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320 25
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
321
    {
322 25
        return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')';
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     */
328 46
    public function getBinaryMaxLength()
329
    {
330 46
        return 2000;
331
    }
332
333
    /**
334
     * {@inheritDoc}
335
     */
336 90
    public function getClobTypeDeclarationSQL(array $field)
337
    {
338 90
        return 'CLOB';
339
    }
340
341
    /**
342
     * {@inheritDoc}
343
     */
344 2
    public function getListDatabasesSQL()
345
    {
346 2
        return 'SELECT username FROM all_users';
347
    }
348
349
    /**
350
     * {@inheritDoc}
351
     */
352 28
    public function getListSequencesSQL($database)
353
    {
354 28
        $database = $this->normalizeIdentifier($database);
355 28
        $database = $this->quoteStringLiteral($database->getName());
356
357
        return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' .
358 28
               'WHERE SEQUENCE_OWNER = ' . $database;
359
    }
360
361
    /**
362
     * {@inheritDoc}
363
     */
364 392
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
365
    {
366 392
        $indexes            = $options['indexes'] ?? [];
367 392
        $options['indexes'] = [];
368 392
        $sql                = parent::_getCreateTableSQL($tableName, $columns, $options);
369
370 392
        foreach ($columns as $name => $column) {
371 392
            if (isset($column['sequence'])) {
372
                $sql[] = $this->getCreateSequenceSQL($column['sequence']);
373
            }
374
375 392
            if (! isset($column['autoincrement']) || ! $column['autoincrement'] &&
376 392
               (! isset($column['autoinc']) || ! $column['autoinc'])) {
377 346
                continue;
378
            }
379
380 92
            $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $tableName));
381
        }
382
383 392
        if (isset($indexes) && ! empty($indexes)) {
384 83
            foreach ($indexes as $index) {
385 83
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
386
            }
387
        }
388
389 392
        return $sql;
390
    }
391
392
    /**
393
     * {@inheritDoc}
394
     *
395
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
396
     */
397 50
    public function getListTableIndexesSQL($table, $currentDatabase = null)
398
    {
399 50
        $table = $this->normalizeIdentifier($table);
400 50
        $table = $this->quoteStringLiteral($table->getName());
401
402
        return "SELECT uind_col.index_name AS name,
403
                       (
404
                           SELECT uind.index_type
405
                           FROM   user_indexes uind
406
                           WHERE  uind.index_name = uind_col.index_name
407
                       ) AS type,
408
                       decode(
409
                           (
410
                               SELECT uind.uniqueness
411
                               FROM   user_indexes uind
412
                               WHERE  uind.index_name = uind_col.index_name
413
                           ),
414
                           'NONUNIQUE',
415
                           0,
416
                           'UNIQUE',
417
                           1
418
                       ) AS is_unique,
419
                       uind_col.column_name AS column_name,
420
                       uind_col.column_position AS column_pos,
421
                       (
422
                           SELECT ucon.constraint_type
423
                           FROM   user_constraints ucon
424
                           WHERE  ucon.index_name = uind_col.index_name
425
                       ) AS is_primary
426
             FROM      user_ind_columns uind_col
427 50
             WHERE     uind_col.table_name = " . $table . '
428
             ORDER BY  uind_col.column_position ASC';
429
    }
430
431
    /**
432
     * {@inheritDoc}
433
     */
434 63
    public function getListTablesSQL()
435
    {
436 63
        return 'SELECT * FROM sys.user_tables';
437
    }
438
439
    /**
440
     * {@inheritDoc}
441
     */
442 1
    public function getListViewsSQL($database)
443
    {
444 1
        return 'SELECT view_name, text FROM sys.user_views';
445
    }
446
447
    /**
448
     * {@inheritDoc}
449
     */
450 1
    public function getCreateViewSQL($name, $sql)
451
    {
452 1
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
453
    }
454
455
    /**
456
     * {@inheritDoc}
457
     */
458 1
    public function getDropViewSQL($name)
459
    {
460 1
        return 'DROP VIEW ' . $name;
461
    }
462
463
    /**
464
     * @param string $name
465
     * @param string $table
466
     * @param int    $start
467
     *
468
     * @return string[]
469
     */
470 92
    public function getCreateAutoincrementSql($name, $table, $start = 1)
471
    {
472 92
        $tableIdentifier   = $this->normalizeIdentifier($table);
473 92
        $quotedTableName   = $tableIdentifier->getQuotedName($this);
474 92
        $unquotedTableName = $tableIdentifier->getName();
475
476 92
        $nameIdentifier = $this->normalizeIdentifier($name);
477 92
        $quotedName     = $nameIdentifier->getQuotedName($this);
478 92
        $unquotedName   = $nameIdentifier->getName();
479
480 92
        $sql = [];
481
482 92
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
483
484 92
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
485
486 92
        $sql[] = 'DECLARE
487
  constraints_Count NUMBER;
488
BEGIN
489 92
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
490
  IF constraints_Count = 0 OR constraints_Count = \'\' THEN
491 92
    EXECUTE IMMEDIATE \'' . $this->getCreateConstraintSQL($idx, $quotedTableName) . '\';
492
  END IF;
493
END;';
494
495 92
        $sequenceName = $this->getIdentitySequenceName(
496 92
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
497 92
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
498
        );
499 92
        $sequence     = new Sequence($sequenceName, $start);
500 92
        $sql[]        = $this->getCreateSequenceSQL($sequence);
501
502 92
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
503
   BEFORE INSERT
504 92
   ON ' . $quotedTableName . '
505
   FOR EACH ROW
506
DECLARE
507
   last_Sequence NUMBER;
508
   last_InsertID NUMBER;
509
BEGIN
510 92
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
511 92
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN
512 92
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
513
   ELSE
514
      SELECT NVL(Last_Number, 0) INTO last_Sequence
515
        FROM User_Sequences
516 92
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
517 92
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
518
      WHILE (last_InsertID > last_Sequence) LOOP
519 92
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
520
      END LOOP;
521
   END IF;
522
END;';
523
524 92
        return $sql;
525
    }
526
527
    /**
528
     * Returns the SQL statements to drop the autoincrement for the given table name.
529
     *
530
     * @param string $table The table name to drop the autoincrement for.
531
     *
532
     * @return string[]
533
     */
534 165
    public function getDropAutoincrementSql($table)
535
    {
536 165
        $table                       = $this->normalizeIdentifier($table);
537 165
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
538 165
        $identitySequenceName        = $this->getIdentitySequenceName(
539 165
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
540 165
            ''
541
        );
542
543
        return [
544 165
            'DROP TRIGGER ' . $autoincrementIdentifierName,
545 165
            $this->getDropSequenceSQL($identitySequenceName),
546 165
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
547
        ];
548
    }
549
550
    /**
551
     * Normalizes the given identifier.
552
     *
553
     * Uppercases the given identifier if it is not quoted by intention
554
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
555
     *
556
     * @param string $name The identifier to normalize.
557
     *
558
     * @return Identifier The normalized identifier.
559
     */
560 483
    private function normalizeIdentifier($name)
561
    {
562 483
        $identifier = new Identifier($name);
563
564 483
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
565
    }
566
567
    /**
568
     * Returns the autoincrement primary key identifier name for the given table identifier.
569
     *
570
     * Quotes the autoincrement primary key identifier name
571
     * if the given table name is quoted by intention.
572
     *
573
     * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
574
     *
575
     * @return string
576
     */
577 252
    private function getAutoincrementIdentifierName(Identifier $table)
578
    {
579 252
        $identifierName = $table->getName() . '_AI_PK';
580
581 252
        return $table->isQuoted()
582 47
            ? $this->quoteSingleIdentifier($identifierName)
583 252
            : $identifierName;
584
    }
585
586
    /**
587
     * {@inheritDoc}
588
     */
589 47
    public function getListTableForeignKeysSQL($table)
590
    {
591 47
        $table = $this->normalizeIdentifier($table);
592 47
        $table = $this->quoteStringLiteral($table->getName());
593
594
        return "SELECT alc.constraint_name,
595
          alc.DELETE_RULE,
596
          cols.column_name \"local_column\",
597
          cols.position,
598
          (
599
              SELECT r_cols.table_name
600
              FROM   user_cons_columns r_cols
601
              WHERE  alc.r_constraint_name = r_cols.constraint_name
602
              AND    r_cols.position = cols.position
603
          ) AS \"references_table\",
604
          (
605
              SELECT r_cols.column_name
606
              FROM   user_cons_columns r_cols
607
              WHERE  alc.r_constraint_name = r_cols.constraint_name
608
              AND    r_cols.position = cols.position
609
          ) AS \"foreign_column\"
610
     FROM user_cons_columns cols
611
     JOIN user_constraints alc
612
       ON alc.constraint_name = cols.constraint_name
613
      AND alc.constraint_type = 'R'
614 47
      AND alc.table_name = " . $table . '
615
    ORDER BY cols.constraint_name ASC, cols.position ASC';
616
    }
617
618
    /**
619
     * {@inheritDoc}
620
     */
621 23
    public function getListTableConstraintsSQL($table)
622
    {
623 23
        $table = $this->normalizeIdentifier($table);
624 23
        $table = $this->quoteStringLiteral($table->getName());
625
626 23
        return 'SELECT * FROM user_constraints WHERE table_name = ' . $table;
627
    }
628
629
    /**
630
     * {@inheritDoc}
631
     */
632 149
    public function getListTableColumnsSQL($table, $database = null)
633
    {
634 149
        $table = $this->normalizeIdentifier($table);
635 149
        $table = $this->quoteStringLiteral($table->getName());
636
637 149
        $tabColumnsTableName       = 'user_tab_columns';
638 149
        $colCommentsTableName      = 'user_col_comments';
639 149
        $tabColumnsOwnerCondition  = '';
640 149
        $colCommentsOwnerCondition = '';
641
642 149
        if ($database !== null && $database !== '/') {
643 80
            $database                  = $this->normalizeIdentifier($database);
644 80
            $database                  = $this->quoteStringLiteral($database->getName());
645 80
            $tabColumnsTableName       = 'all_tab_columns';
646 80
            $colCommentsTableName      = 'all_col_comments';
647 80
            $tabColumnsOwnerCondition  = ' AND c.owner = ' . $database;
648 80
            $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER';
649
        }
650
651 149
        return sprintf(
652
            <<<'SQL'
653 149
SELECT   c.*,
654
         (
655
             SELECT d.comments
656
             FROM   %s d
657
             WHERE  d.TABLE_NAME = c.TABLE_NAME%s
658
             AND    d.COLUMN_NAME = c.COLUMN_NAME
659
         ) AS comments
660
FROM     %s c
661
WHERE    c.table_name = %s%s
662
ORDER BY c.column_id
663
SQL
664
            ,
665 149
            $colCommentsTableName,
666 149
            $colCommentsOwnerCondition,
667 149
            $tabColumnsTableName,
668 149
            $table,
669 149
            $tabColumnsOwnerCondition
670
        );
671
    }
672
673
    /**
674
     * {@inheritDoc}
675
     */
676 165
    public function getDropSequenceSQL($sequence)
677
    {
678 165
        if ($sequence instanceof Sequence) {
679
            $sequence = $sequence->getQuotedName($this);
680
        }
681
682 165
        return 'DROP SEQUENCE ' . $sequence;
683
    }
684
685
    /**
686
     * {@inheritDoc}
687
     */
688 47
    public function getDropForeignKeySQL($foreignKey, $table)
689
    {
690 47
        if (! $foreignKey instanceof ForeignKeyConstraint) {
691 23
            $foreignKey = new Identifier($foreignKey);
692
        }
693
694 47
        if (! $table instanceof Table) {
695 47
            $table = new Identifier($table);
696
        }
697
698 47
        $foreignKey = $foreignKey->getQuotedName($this);
699 47
        $table      = $table->getQuotedName($this);
700
701 47
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
702
    }
703
704
    /**
705
     * {@inheritdoc}
706
     */
707 242
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
708
    {
709 242
        $referentialAction = null;
710
711 242
        if ($foreignKey->hasOption('onDelete')) {
712 93
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
713
        }
714
715 242
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
716
    }
717
718
    /**
719
     * {@inheritdoc}
720
     */
721 231
    public function getForeignKeyReferentialActionSQL($action)
722
    {
723 231
        $action = strtoupper($action);
724
725 231
        switch ($action) {
726 231
            case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION.
727 185
            case 'NO ACTION':
728
                // NO ACTION cannot be declared explicitly,
729
                // therefore returning empty string to indicate to OMIT the referential clause.
730 92
                return '';
731
732 139
            case 'CASCADE':
733 69
            case 'SET NULL':
734 116
                return $action;
735
736
            default:
737
                // SET DEFAULT is not supported, throw exception instead.
738 23
                throw new InvalidArgumentException('Invalid foreign key action: ' . $action);
739
        }
740
    }
741
742
    /**
743
     * {@inheritDoc}
744
     */
745 25
    public function getDropDatabaseSQL($database)
746
    {
747 25
        return 'DROP USER ' . $database . ' CASCADE';
748
    }
749
750
    /**
751
     * {@inheritDoc}
752
     */
753 385
    public function getAlterTableSQL(TableDiff $diff)
754
    {
755 385
        $sql         = [];
756 385
        $commentsSQL = [];
757 385
        $columnSql   = [];
758
759 385
        $fields = [];
760
761 385
        foreach ($diff->addedColumns as $column) {
762 93
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
763
                continue;
764
            }
765
766 93
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
767 93
            $comment  = $this->getColumnComment($column);
768
769 93
            if (! $comment) {
770 70
                continue;
771
            }
772
773 23
            $commentsSQL[] = $this->getCommentOnColumnSQL(
774 23
                $diff->getName($this)->getQuotedName($this),
775 23
                $column->getQuotedName($this),
776 23
                $comment
777
            );
778
        }
779
780 385
        if (count($fields)) {
781 93
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
782
        }
783
784 385
        $fields = [];
785 385
        foreach ($diff->changedColumns as $columnDiff) {
786 220
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
787
                continue;
788
            }
789
790
            /** @var ColumnDiff $columnDiff */
791 220
            $column = $columnDiff->column;
792
793
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
794
            // Oracle only supports binary type columns with variable length.
795
            // Avoids unnecessary table alteration statements.
796 220
            if ($column->getType() instanceof BinaryType &&
797 220
                $columnDiff->hasChanged('fixed') &&
798 220
                count($columnDiff->changedProperties) === 1
799
            ) {
800 23
                continue;
801
            }
802
803 197
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
804
805
            /**
806
             * Do not add query part if only comment has changed
807
             */
808 197
            if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
809 141
                $columnInfo = $column->toArray();
810
811 141
                if (! $columnDiff->hasChanged('notnull')) {
812 95
                    unset($columnInfo['notnull']);
813
                }
814
815 141
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
816
            }
817
818 197
            if (! $columnHasChangedComment) {
819 141
                continue;
820
            }
821
822 56
            $commentsSQL[] = $this->getCommentOnColumnSQL(
823 56
                $diff->getName($this)->getQuotedName($this),
824 56
                $column->getQuotedName($this),
825 56
                $this->getColumnComment($column)
826
            );
827
        }
828
829 385
        if (count($fields)) {
830 141
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
831
        }
832
833 385
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
834 93
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
835
                continue;
836
            }
837
838 93
            $oldColumnName = new Identifier($oldColumnName);
839
840 93
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
841 93
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
842
        }
843
844 385
        $fields = [];
845 385
        foreach ($diff->removedColumns as $column) {
846 70
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
847
                continue;
848
            }
849
850 70
            $fields[] = $column->getQuotedName($this);
851
        }
852
853 385
        if (count($fields)) {
854 70
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields) . ')';
855
        }
856
857 385
        $tableSql = [];
858
859 385
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
860 385
            $sql = array_merge($sql, $commentsSQL);
861
862 385
            if ($diff->newName !== false) {
863 47
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
864
            }
865
866 385
            $sql = array_merge(
867 385
                $this->getPreAlterTableIndexForeignKeySQL($diff),
868 385
                $sql,
869 385
                $this->getPostAlterTableIndexForeignKeySQL($diff)
870
            );
871
        }
872
873 385
        return array_merge($sql, $tableSql, $columnSql);
874
    }
875
876
    /**
877
     * {@inheritdoc}
878
     */
879 576
    public function getColumnDeclarationSQL($name, array $field)
880
    {
881 576
        if (isset($field['columnDefinition'])) {
882 23
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
883
        } else {
884 553
            $default = $this->getDefaultValueDeclarationSQL($field);
885
886 553
            $notnull = '';
887
888 553
            if (isset($field['notnull'])) {
889 507
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
890
            }
891
892 553
            $unique = isset($field['unique']) && $field['unique'] ?
893 553
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
894
895 553
            $check = isset($field['check']) && $field['check'] ?
896 553
                ' ' . $field['check'] : '';
897
898 553
            $typeDecl  = $field['type']->getSQLDeclaration($field, $this);
899 553
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
900
        }
901
902 576
        return $name . ' ' . $columnDef;
903
    }
904
905
    /**
906
     * {@inheritdoc}
907
     */
908 117
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
909
    {
910 117
        if (strpos($tableName, '.') !== false) {
911 46
            [$schema]     = explode('.', $tableName);
912 46
            $oldIndexName = $schema . '.' . $oldIndexName;
913
        }
914
915 117
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
916
    }
917
918
    /**
919
     * {@inheritDoc}
920
     */
921
    public function prefersSequences()
922
    {
923
        return true;
924
    }
925
926
    /**
927
     * {@inheritdoc}
928
     */
929 24
    public function usesSequenceEmulatedIdentityColumns()
930
    {
931 24
        return true;
932
    }
933
934
    /**
935
     * {@inheritdoc}
936
     */
937 275
    public function getIdentitySequenceName($tableName, $columnName)
938
    {
939 275
        $table = new Identifier($tableName);
940
941
        // No usage of column name to preserve BC compatibility with <2.5
942 275
        $identitySequenceName = $table->getName() . '_SEQ';
943
944 275
        if ($table->isQuoted()) {
945 70
            $identitySequenceName = '"' . $identitySequenceName . '"';
946
        }
947
948 275
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
949
950 275
        return $identitySequenceIdentifier->getQuotedName($this);
951
    }
952
953
    /**
954
     * {@inheritDoc}
955
     */
956 415
    public function supportsCommentOnStatement()
957
    {
958 415
        return true;
959
    }
960
961
    /**
962
     * {@inheritDoc}
963
     */
964 111
    public function getName()
965
    {
966 111
        return 'oracle';
967
    }
968
969
    /**
970
     * {@inheritDoc}
971
     */
972 169
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
973
    {
974 169
        if ($limit === null && $offset <= 0) {
975 24
            return $query;
976
        }
977
978 145
        if (preg_match('/^\s*SELECT/i', $query)) {
979 145
            if (! preg_match('/\sFROM\s/i', $query)) {
980
                $query .= ' FROM dual';
981
            }
982
983 145
            $columns = ['a.*'];
984
985 145
            if ($offset > 0) {
986 53
                $columns[] = 'ROWNUM AS doctrine_rownum';
987
            }
988
989 145
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
990
991 145
            if ($limit !== null) {
992 122
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
993
            }
994
995 145
            if ($offset > 0) {
996 53
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
997
            }
998
        }
999
1000 145
        return $query;
1001
    }
1002
1003
    /**
1004
     * {@inheritDoc}
1005
     *
1006
     * Oracle returns all column names in SQL result sets in uppercase.
1007
     */
1008
    public function getSQLResultCasing($column)
1009
    {
1010
        return strtoupper($column);
1011
    }
1012
1013
    /**
1014
     * {@inheritDoc}
1015
     */
1016
    public function getCreateTemporaryTableSnippetSQL()
1017
    {
1018
        return 'CREATE GLOBAL TEMPORARY TABLE';
1019
    }
1020
1021
    /**
1022
     * {@inheritDoc}
1023
     */
1024 1
    public function getDateTimeTzFormatString()
1025
    {
1026 1
        return 'Y-m-d H:i:sP';
1027
    }
1028
1029
    /**
1030
     * {@inheritDoc}
1031
     */
1032 1
    public function getDateFormatString()
1033
    {
1034 1
        return 'Y-m-d 00:00:00';
1035
    }
1036
1037
    /**
1038
     * {@inheritDoc}
1039
     */
1040 1
    public function getTimeFormatString()
1041
    {
1042 1
        return '1900-01-01 H:i:s';
1043
    }
1044
1045
    /**
1046
     * {@inheritDoc}
1047
     */
1048
    public function fixSchemaElementName($schemaElementName)
1049
    {
1050
        if (strlen($schemaElementName) > 30) {
1051
            // Trim it
1052
            return substr($schemaElementName, 0, 30);
1053
        }
1054
1055
        return $schemaElementName;
1056
    }
1057
1058
    /**
1059
     * {@inheritDoc}
1060
     */
1061 13
    public function getMaxIdentifierLength()
1062
    {
1063 13
        return 30;
1064
    }
1065
1066
    /**
1067
     * {@inheritDoc}
1068
     */
1069 6
    public function supportsSequences()
1070
    {
1071 6
        return true;
1072
    }
1073
1074
    /**
1075
     * {@inheritDoc}
1076
     */
1077
    public function supportsForeignKeyOnUpdate()
1078
    {
1079
        return false;
1080
    }
1081
1082
    /**
1083
     * {@inheritDoc}
1084
     */
1085 1
    public function supportsReleaseSavepoints()
1086
    {
1087 1
        return false;
1088
    }
1089
1090
    /**
1091
     * {@inheritDoc}
1092
     */
1093 32
    public function getTruncateTableSQL($tableName, $cascade = false)
1094
    {
1095 32
        $tableIdentifier = new Identifier($tableName);
1096
1097 32
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1098
    }
1099
1100
    /**
1101
     * {@inheritDoc}
1102
     */
1103 10
    public function getDummySelectSQL(string $expression = '1') : string
1104
    {
1105 10
        return sprintf('SELECT %s FROM DUAL', $expression);
1106
    }
1107
1108
    /**
1109
     * {@inheritDoc}
1110
     */
1111 116
    protected function initializeDoctrineTypeMappings()
1112
    {
1113 116
        $this->doctrineTypeMapping = [
1114
            'binary_double'  => 'float',
1115
            'binary_float'   => 'float',
1116
            'binary_integer' => 'boolean',
1117
            'blob'           => 'blob',
1118
            'char'           => 'string',
1119
            'clob'           => 'text',
1120
            'date'           => 'date',
1121
            'float'          => 'float',
1122
            'integer'        => 'integer',
1123
            'long'           => 'string',
1124
            'long raw'       => 'blob',
1125
            'nchar'          => 'string',
1126
            'nclob'          => 'text',
1127
            'number'         => 'integer',
1128
            'nvarchar2'      => 'string',
1129
            'pls_integer'    => 'boolean',
1130
            'raw'            => 'binary',
1131
            'rowid'          => 'string',
1132
            'timestamp'      => 'datetime',
1133
            'timestamptz'    => 'datetimetz',
1134
            'urowid'         => 'string',
1135
            'varchar'        => 'string',
1136
            'varchar2'       => 'string',
1137
        ];
1138 116
    }
1139
1140
    /**
1141
     * {@inheritDoc}
1142
     */
1143
    public function releaseSavePoint($savepoint)
1144
    {
1145
        return '';
1146
    }
1147
1148
    /**
1149
     * {@inheritDoc}
1150
     */
1151 1139
    protected function getReservedKeywordsClass()
1152
    {
1153 1139
        return Keywords\OracleKeywords::class;
1154
    }
1155
1156
    /**
1157
     * {@inheritDoc}
1158
     */
1159 5
    public function getBlobTypeDeclarationSQL(array $field)
1160
    {
1161 5
        return 'BLOB';
1162
    }
1163
}
1164