Completed
Push — develop ( dcb0ff...425513 )
by Sergei
23s queued 13s
created

OraclePlatform::getCurrentDatabaseExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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