Failed Conditions
Pull Request — master (#3339)
by Sergei
40:43
created

OraclePlatform::getIdentitySequenceName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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