Completed
Pull Request — master (#3610)
by Sergei
03:03
created

OraclePlatform::getCreateAutoincrementSql()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 3

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

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