Passed
Pull Request — master (#3157)
by Sergei
15:02
created

OraclePlatform::getListDatabasesSQL()   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
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Platforms;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
24
use Doctrine\DBAL\Schema\Identifier;
25
use Doctrine\DBAL\Schema\Index;
26
use Doctrine\DBAL\Schema\Sequence;
27
use Doctrine\DBAL\Schema\Table;
28
use Doctrine\DBAL\Schema\TableDiff;
29
use Doctrine\DBAL\TransactionIsolationLevel;
30
use Doctrine\DBAL\Types\BinaryType;
31
use function array_merge;
32
use function count;
33
use function explode;
34
use function func_get_arg;
35
use function func_num_args;
36
use function implode;
37
use function preg_match;
38
use function sprintf;
39
use function str_replace;
40
use function strlen;
41
use function strpos;
42
use function strtoupper;
43
use function substr;
44
45
/**
46
 * OraclePlatform.
47
 *
48
 * @since 2.0
49
 * @author Roman Borschel <[email protected]>
50
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
51
 * @author Benjamin Eberlei <[email protected]>
52
 */
53
class OraclePlatform extends AbstractPlatform
54
{
55
    /**
56
     * Assertion for Oracle identifiers.
57
     *
58
     * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
59
     *
60
     * @param string $identifier
61
     *
62
     * @throws DBALException
63
     */
64 266
    public static function assertValidIdentifier($identifier)
65
    {
66 266
        if ( ! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
67 95
            throw new DBALException("Invalid Oracle identifier");
68
        }
69 171
    }
70
71
    /**
72
     * {@inheritDoc}
73
     */
74
    public function getSubstringExpression($value, $position, $length = null)
75
    {
76
        if ($length !== null) {
77
            return "SUBSTR($value, $position, $length)";
78
        }
79
80
        return "SUBSTR($value, $position)";
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function getNowExpression($type = 'timestamp')
87
    {
88
        switch ($type) {
89
            case 'date':
90
            case 'time':
91
            case 'timestamp':
92
            default:
93
                return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')';
94
        }
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 1
    public function getLocateExpression($str, $substr, $startPos = false)
101
    {
102 1
        if ($startPos == false) {
103 1
            return 'INSTR('.$str.', '.$substr.')';
104
        }
105
106 1
        return 'INSTR('.$str.', '.$substr.', '.$startPos.')';
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112
    public function getGuidExpression()
113
    {
114
        return 'SYS_GUID()';
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 1
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
121
    {
122
        switch ($unit) {
123 1
            case DateIntervalUnit::MONTH:
124 1
            case DateIntervalUnit::QUARTER:
125 1
            case DateIntervalUnit::YEAR:
126
                switch ($unit) {
127 1
                    case DateIntervalUnit::QUARTER:
128 1
                        $interval *= 3;
129 1
                        break;
130
131 1
                    case DateIntervalUnit::YEAR:
132 1
                        $interval *= 12;
133 1
                        break;
134
                }
135
136 1
                return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
137
138
            default:
139 1
                $calculationClause = '';
140
141
                switch ($unit) {
142 1
                    case DateIntervalUnit::SECOND:
143 1
                        $calculationClause = '/24/60/60';
144 1
                        break;
145
146 1
                    case DateIntervalUnit::MINUTE:
147 1
                        $calculationClause = '/24/60';
148 1
                        break;
149
150 1
                    case DateIntervalUnit::HOUR:
151 1
                        $calculationClause = '/24';
152 1
                        break;
153
154 1
                    case DateIntervalUnit::WEEK:
155 1
                        $calculationClause = '*7';
156 1
                        break;
157
                }
158
159 1
                return '(' . $date . $operator . $interval . $calculationClause . ')';
160
        }
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     */
166 3
    public function getDateDiffExpression($date1, $date2)
167
    {
168 3
        return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2);
169
    }
170
171
    /**
172
     * {@inheritDoc}
173
     */
174 39
    public function getBitAndComparisonExpression($value1, $value2)
175
    {
176 39
        return 'BITAND('.$value1 . ', ' . $value2 . ')';
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     */
182 20
    public function getBitOrComparisonExpression($value1, $value2)
183
    {
184 20
        return '(' . $value1 . '-' .
185 20
                $this->getBitAndComparisonExpression($value1, $value2)
186 20
                . '+' . $value2 . ')';
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     *
192
     * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
193
     * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
194
     * in {@see listSequences()}
195
     */
196 140
    public function getCreateSequenceSQL(Sequence $sequence)
197
    {
198 140
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
199 140
               ' START WITH ' . $sequence->getInitialValue() .
200 140
               ' MINVALUE ' . $sequence->getInitialValue() .
201 140
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
202 140
               $this->getSequenceCacheSQL($sequence);
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208
    public function getAlterSequenceSQL(Sequence $sequence)
209
    {
210
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
211
               ' INCREMENT BY ' . $sequence->getAllocationSize()
212
               . $this->getSequenceCacheSQL($sequence);
213
    }
214
215
    /**
216
     * Cache definition for sequences
217
     *
218
     * @param Sequence $sequence
219
     *
220
     * @return string
221
     */
222 140
    private function getSequenceCacheSQL(Sequence $sequence)
223
    {
224 140
        if ($sequence->getCache() === 0) {
225 19
            return ' NOCACHE';
226 121
        } else if ($sequence->getCache() === 1) {
227 19
            return ' NOCACHE';
228 102
        } else if ($sequence->getCache() > 1) {
229 19
            return ' CACHE ' . $sequence->getCache();
230
        }
231
232 83
        return '';
233
    }
234
235
    /**
236
     * {@inheritDoc}
237
     */
238 1
    public function getSequenceNextValSQL($sequenceName)
239
    {
240 1
        return 'SELECT ' . $sequenceName . '.nextval FROM DUAL';
241
    }
242
243
    /**
244
     * {@inheritDoc}
245
     */
246 19
    public function getSetTransactionIsolationSQL($level)
247
    {
248 19
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
249
    }
250
251
    /**
252
     * {@inheritDoc}
253
     */
254 19
    protected function _getTransactionIsolationLevelSQL($level)
255
    {
256 1
        switch ($level) {
257 18
            case TransactionIsolationLevel::READ_UNCOMMITTED:
258 19
                return 'READ UNCOMMITTED';
259 18
            case TransactionIsolationLevel::READ_COMMITTED:
260 19
                return 'READ COMMITTED';
261 18
            case TransactionIsolationLevel::REPEATABLE_READ:
262 18
            case TransactionIsolationLevel::SERIALIZABLE:
263 19
                return 'SERIALIZABLE';
264
            default:
265
                return parent::_getTransactionIsolationLevelSQL($level);
266
        }
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 34
    public function getBooleanTypeDeclarationSQL(array $field)
273
    {
274 34
        return 'NUMBER(1)';
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280 320
    public function getIntegerTypeDeclarationSQL(array $field)
281
    {
282 320
        return 'NUMBER(10)';
283
    }
284
285
    /**
286
     * {@inheritDoc}
287
     */
288 15
    public function getBigIntTypeDeclarationSQL(array $field)
289
    {
290 15
        return 'NUMBER(20)';
291
    }
292
293
    /**
294
     * {@inheritDoc}
295
     */
296 1
    public function getSmallIntTypeDeclarationSQL(array $field)
297
    {
298 1
        return 'NUMBER(5)';
299
    }
300
301
    /**
302
     * {@inheritDoc}
303
     */
304 23
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
305
    {
306 23
        return 'TIMESTAMP(0)';
307
    }
308
309
    /**
310
     * {@inheritDoc}
311
     */
312 16
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
313
    {
314 16
        return 'TIMESTAMP(0) WITH TIME ZONE';
315
    }
316
317
    /**
318
     * {@inheritDoc}
319
     */
320 19
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
321
    {
322 19
        return 'DATE';
323
    }
324
325
    /**
326
     * {@inheritDoc}
327
     */
328 18
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
329
    {
330 18
        return 'DATE';
331
    }
332
333
    /**
334
     * {@inheritDoc}
335
     */
336
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
337
    {
338
        return '';
339
    }
340
341
    /**
342
     * {@inheritDoc}
343
     */
344 339
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
345
    {
346 339
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
347 339
                : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     */
353 20
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
354
    {
355 20
        return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')';
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361 39
    public function getBinaryMaxLength()
362
    {
363 39
        return 2000;
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369 82
    public function getClobTypeDeclarationSQL(array $field)
370
    {
371 82
        return 'CLOB';
372
    }
373
374
    /**
375
     * {@inheritDoc}
376
     */
377 2
    public function getListDatabasesSQL()
378
    {
379 2
        return 'SELECT username FROM all_users';
380
    }
381
382
    /**
383
     * {@inheritDoc}
384
     */
385 24
    public function getListSequencesSQL($database)
386
    {
387 24
        $database = $this->normalizeIdentifier($database);
388 24
        $database = $this->quoteStringLiteral($database->getName());
389
390
        return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
391 24
               "WHERE SEQUENCE_OWNER = " . $database;
392
    }
393
394
    /**
395
     * {@inheritDoc}
396
     */
397 346
    protected function _getCreateTableSQL($table, array $columns, array $options = [])
398
    {
399 346
        $indexes            = $options['indexes'] ?? [];
400 346
        $options['indexes'] = [];
401 346
        $sql                = parent::_getCreateTableSQL($table, $columns, $options);
402
403 346
        foreach ($columns as $name => $column) {
404 346
            if (isset($column['sequence'])) {
405
                $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Platforms\...:getCreateSequenceSQL() has too many arguments starting with 1. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

405
                /** @scrutinizer ignore-call */ 
406
                $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
406
            }
407
408 346
            if (isset($column['autoincrement']) && $column['autoincrement'] ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: {currentAssign}, Probably Intended Meaning: {alternativeAssign}
Loading history...
409 346
               (isset($column['autoinc']) && $column['autoinc'])) {
410 346
                $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table));
411
            }
412
        }
413
414 346
        if (isset($indexes) && ! empty($indexes)) {
415 71
            foreach ($indexes as $index) {
416 71
                $sql[] = $this->getCreateIndexSQL($index, $table);
417
            }
418
        }
419
420 346
        return $sql;
421
    }
422
423
    /**
424
     * {@inheritDoc}
425
     *
426
     * @license New BSD License
427
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
428
     */
429 45
    public function getListTableIndexesSQL($table, $currentDatabase = null)
430
    {
431 45
        $table = $this->normalizeIdentifier($table);
432 45
        $table = $this->quoteStringLiteral($table->getName());
433
434
        return "SELECT uind_col.index_name AS name,
435
                       (
436
                           SELECT uind.index_type
437
                           FROM   user_indexes uind
438
                           WHERE  uind.index_name = uind_col.index_name
439
                       ) AS type,
440
                       decode(
441
                           (
442
                               SELECT uind.uniqueness
443
                               FROM   user_indexes uind
444
                               WHERE  uind.index_name = uind_col.index_name
445
                           ),
446
                           'NONUNIQUE',
447
                           0,
448
                           'UNIQUE',
449
                           1
450
                       ) AS is_unique,
451
                       uind_col.column_name AS column_name,
452
                       uind_col.column_position AS column_pos,
453
                       (
454
                           SELECT ucon.constraint_type
455
                           FROM   user_constraints ucon
456
                           WHERE  ucon.index_name = uind_col.index_name
457
                       ) AS is_primary
458
             FROM      user_ind_columns uind_col
459 45
             WHERE     uind_col.table_name = " . $table . "
460
             ORDER BY  uind_col.column_position ASC";
461
    }
462
463
    /**
464
     * {@inheritDoc}
465
     */
466 63
    public function getListTablesSQL()
467
    {
468 63
        return 'SELECT * FROM sys.user_tables';
469
    }
470
471
    /**
472
     * {@inheritDoc}
473
     */
474 1
    public function getListViewsSQL($database)
475
    {
476 1
        return 'SELECT view_name, text FROM sys.user_views';
477
    }
478
479
    /**
480
     * {@inheritDoc}
481
     */
482 1
    public function getCreateViewSQL($name, $sql)
483
    {
484 1
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
485
    }
486
487
    /**
488
     * {@inheritDoc}
489
     */
490 1
    public function getDropViewSQL($name)
491
    {
492 1
        return 'DROP VIEW '. $name;
493
    }
494
495
    /**
496
     * @param string $name
497
     * @param string $table
498
     * @param int    $start
499
     *
500
     * @return array
501
     */
502 80
    public function getCreateAutoincrementSql($name, $table, $start = 1)
503
    {
504 80
        $tableIdentifier = $this->normalizeIdentifier($table);
505 80
        $quotedTableName = $tableIdentifier->getQuotedName($this);
506 80
        $unquotedTableName = $tableIdentifier->getName();
507
508 80
        $nameIdentifier = $this->normalizeIdentifier($name);
509 80
        $quotedName = $nameIdentifier->getQuotedName($this);
510 80
        $unquotedName = $nameIdentifier->getName();
511
512 80
        $sql = [];
513
514 80
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
515
516 80
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
517
518 80
        $sql[] = 'DECLARE
519
  constraints_Count NUMBER;
520
BEGIN
521 80
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
522
  IF constraints_Count = 0 OR constraints_Count = \'\' THEN
523 80
    EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\';
524
  END IF;
525
END;';
526
527 80
        $sequenceName = $this->getIdentitySequenceName(
528 80
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
529 80
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
530
        );
531 80
        $sequence = new Sequence($sequenceName, $start);
532 80
        $sql[] = $this->getCreateSequenceSQL($sequence);
533
534 80
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
535
   BEFORE INSERT
536 80
   ON ' . $quotedTableName . '
537
   FOR EACH ROW
538
DECLARE
539
   last_Sequence NUMBER;
540
   last_InsertID NUMBER;
541
BEGIN
542 80
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
543 80
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN
544 80
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
545
   ELSE
546
      SELECT NVL(Last_Number, 0) INTO last_Sequence
547
        FROM User_Sequences
548 80
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
549 80
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
550
      WHILE (last_InsertID > last_Sequence) LOOP
551 80
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
552
      END LOOP;
553
   END IF;
554
END;';
555
556 80
        return $sql;
557
    }
558
559
    /**
560
     * Returns the SQL statements to drop the autoincrement for the given table name.
561
     *
562
     * @param string $table The table name to drop the autoincrement for.
563
     *
564
     * @return array
565
     */
566 152
    public function getDropAutoincrementSql($table)
567
    {
568 152
        $table = $this->normalizeIdentifier($table);
569 152
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
570 152
        $identitySequenceName = $this->getIdentitySequenceName(
571 152
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
572 152
            ''
573
        );
574
575
        return [
576 152
            'DROP TRIGGER ' . $autoincrementIdentifierName,
577 152
            $this->getDropSequenceSQL($identitySequenceName),
578 152
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
579
        ];
580
    }
581
582
    /**
583
     * Normalizes the given identifier.
584
     *
585
     * Uppercases the given identifier if it is not quoted by intention
586
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
587
     *
588
     * @param string $name The identifier to normalize.
589
     *
590
     * @return Identifier The normalized identifier.
591
     */
592 417
    private function normalizeIdentifier($name)
593
    {
594 417
        $identifier = new Identifier($name);
595
596 417
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
597
    }
598
599
    /**
600
     * Returns the autoincrement primary key identifier name for the given table identifier.
601
     *
602
     * Quotes the autoincrement primary key identifier name
603
     * if the given table name is quoted by intention.
604
     *
605
     * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
606
     *
607
     * @return string
608
     */
609 227
    private function getAutoincrementIdentifierName(Identifier $table)
610
    {
611 227
        $identifierName = $table->getName() . '_AI_PK';
612
613 227
        return $table->isQuoted()
614 39
            ? $this->quoteSingleIdentifier($identifierName)
615 227
            : $identifierName;
616
    }
617
618
    /**
619
     * {@inheritDoc}
620
     */
621 42
    public function getListTableForeignKeysSQL($table)
622
    {
623 42
        $table = $this->normalizeIdentifier($table);
624 42
        $table = $this->quoteStringLiteral($table->getName());
625
626
        return "SELECT alc.constraint_name,
627
          alc.DELETE_RULE,
628
          cols.column_name \"local_column\",
629
          cols.position,
630
          (
631
              SELECT r_cols.table_name
632
              FROM   user_cons_columns r_cols
633
              WHERE  alc.r_constraint_name = r_cols.constraint_name
634
              AND    r_cols.position = cols.position
635
          ) AS \"references_table\",
636
          (
637
              SELECT r_cols.column_name
638
              FROM   user_cons_columns r_cols
639
              WHERE  alc.r_constraint_name = r_cols.constraint_name
640
              AND    r_cols.position = cols.position
641
          ) AS \"foreign_column\"
642
     FROM user_cons_columns cols
643
     JOIN user_constraints alc
644
       ON alc.constraint_name = cols.constraint_name
645
      AND alc.constraint_type = 'R'
646 42
      AND alc.table_name = " . $table . "
647
    ORDER BY cols.constraint_name ASC, cols.position ASC";
648
    }
649
650
    /**
651
     * {@inheritDoc}
652
     */
653 19
    public function getListTableConstraintsSQL($table)
654
    {
655 19
        $table = $this->normalizeIdentifier($table);
656 19
        $table = $this->quoteStringLiteral($table->getName());
657
658 19
        return "SELECT * FROM user_constraints WHERE table_name = " . $table;
659
    }
660
661
    /**
662
     * {@inheritDoc}
663
     */
664 128
    public function getListTableColumnsSQL($table, $database = null)
665
    {
666 128
        $table = $this->normalizeIdentifier($table);
667 128
        $table = $this->quoteStringLiteral($table->getName());
668
669 128
        $tabColumnsTableName = "user_tab_columns";
670 128
        $colCommentsTableName = "user_col_comments";
671 128
        $tabColumnsOwnerCondition = '';
672 128
        $colCommentsOwnerCondition = '';
673
674 128
        if (null !== $database && '/' !== $database) {
675 71
            $database = $this->normalizeIdentifier($database);
676 71
            $database = $this->quoteStringLiteral($database->getName());
677 71
            $tabColumnsTableName = "all_tab_columns";
678 71
            $colCommentsTableName = "all_col_comments";
679 71
            $tabColumnsOwnerCondition = "AND c.owner = " . $database;
680 71
            $colCommentsOwnerCondition = "AND d.OWNER = c.OWNER";
681
        }
682
683
        return "SELECT   c.*,
684
                         (
685
                             SELECT d.comments
686 128
                             FROM   $colCommentsTableName d
687 128
                             WHERE  d.TABLE_NAME = c.TABLE_NAME " . $colCommentsOwnerCondition . "
688
                             AND    d.COLUMN_NAME = c.COLUMN_NAME
689
                         ) AS comments
690 128
                FROM     $tabColumnsTableName c
691 128
                WHERE    c.table_name = " . $table . " $tabColumnsOwnerCondition
692
                ORDER BY c.column_id";
693
    }
694
695
    /**
696
     * {@inheritDoc}
697
     */
698 152
    public function getDropSequenceSQL($sequence)
699
    {
700 152
        if ($sequence instanceof Sequence) {
701
            $sequence = $sequence->getQuotedName($this);
702
        }
703
704 152
        return 'DROP SEQUENCE ' . $sequence;
705
    }
706
707
    /**
708
     * {@inheritDoc}
709
     */
710 39
    public function getDropForeignKeySQL($foreignKey, $table)
711
    {
712 39
        if (! $foreignKey instanceof ForeignKeyConstraint) {
713 19
            $foreignKey = new Identifier($foreignKey);
714
        }
715
716 39
        if (! $table instanceof Table) {
717 39
            $table = new Identifier($table);
718
        }
719
720 39
        $foreignKey = $foreignKey->getQuotedName($this);
721 39
        $table = $table->getQuotedName($this);
722
723 39
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
724
    }
725
726
    /**
727
     * {@inheritdoc}
728
     */
729 202
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
730
    {
731 202
        $referentialAction = null;
732
733 202
        if ($foreignKey->hasOption('onDelete')) {
734 77
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
735
        }
736
737 202
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
738
    }
739
740
    /**
741
     * {@inheritdoc}
742
     */
743 191
    public function getForeignKeyReferentialActionSQL($action)
744
    {
745 191
        $action = strtoupper($action);
746
747 11
        switch ($action) {
748 191
            case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION.
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
749 153
            case 'NO ACTION':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
750
                // NO ACTION cannot be declared explicitly,
751
                // therefore returning empty string to indicate to OMIT the referential clause.
752 76
                return '';
753
754 115
            case 'CASCADE':
755 57
            case 'SET NULL':
756 96
                return $action;
757
758
            default:
759
                // SET DEFAULT is not supported, throw exception instead.
760 19
                throw new \InvalidArgumentException('Invalid foreign key action: ' . $action);
761
        }
762
    }
763
764
    /**
765
     * {@inheritDoc}
766
     */
767 21
    public function getDropDatabaseSQL($database)
768
    {
769 21
        return 'DROP USER ' . $database . ' CASCADE';
770
    }
771
772
    /**
773
     * {@inheritDoc}
774
     */
775 321
    public function getAlterTableSQL(TableDiff $diff)
776
    {
777 321
        $sql = [];
778 321
        $commentsSQL = [];
779 321
        $columnSql = [];
780
781 321
        $fields = [];
782
783 321
        foreach ($diff->addedColumns as $column) {
784 77
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
785
                continue;
786
            }
787
788 77
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
789 77
            if ($comment = $this->getColumnComment($column)) {
790 19
                $commentsSQL[] = $this->getCommentOnColumnSQL(
791 19
                    $diff->getName($this)->getQuotedName($this),
792 19
                    $column->getQuotedName($this),
793 77
                    $comment
794
                );
795
            }
796
        }
797
798 321
        if (count($fields)) {
799 77
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
800
        }
801
802 321
        $fields = [];
803 321
        foreach ($diff->changedColumns as $columnDiff) {
804 184
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
805
                continue;
806
            }
807
808
            /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
809 184
            $column = $columnDiff->column;
810
811
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
812
            // Oracle only supports binary type columns with variable length.
813
            // Avoids unnecessary table alteration statements.
814 184
            if ($column->getType() instanceof BinaryType &&
815 184
                $columnDiff->hasChanged('fixed') &&
816 184
                count($columnDiff->changedProperties) === 1
817
            ) {
818 19
                continue;
819
            }
820
821 165
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
822
823
            /**
824
             * Do not add query part if only comment has changed
825
             */
826 165
            if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
827 117
                $columnInfo = $column->toArray();
828
829 117
                if ( ! $columnDiff->hasChanged('notnull')) {
830 79
                    unset($columnInfo['notnull']);
831
                }
832
833 117
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
834
            }
835
836 165
            if ($columnHasChangedComment) {
837 48
                $commentsSQL[] = $this->getCommentOnColumnSQL(
838 48
                    $diff->getName($this)->getQuotedName($this),
839 48
                    $column->getQuotedName($this),
840 165
                    $this->getColumnComment($column)
841
                );
842
            }
843
        }
844
845 321
        if (count($fields)) {
846 117
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
847
        }
848
849 321
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
850 77
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
851
                continue;
852
            }
853
854 77
            $oldColumnName = new Identifier($oldColumnName);
855
856 77
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
857 77
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this);
858
        }
859
860 321
        $fields = [];
861 321
        foreach ($diff->removedColumns as $column) {
862 58
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
863
                continue;
864
            }
865
866 58
            $fields[] = $column->getQuotedName($this);
867
        }
868
869 321
        if (count($fields)) {
870 58
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields).')';
871
        }
872
873 321
        $tableSql = [];
874
875 321
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
876 321
            $sql = array_merge($sql, $commentsSQL);
877
878 321
            if ($diff->newName !== false) {
879 39
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
880
            }
881
882 321
            $sql = array_merge(
883 321
                $this->getPreAlterTableIndexForeignKeySQL($diff),
884 321
                $sql,
885 321
                $this->getPostAlterTableIndexForeignKeySQL($diff)
886
            );
887
        }
888
889 321
        return array_merge($sql, $tableSql, $columnSql);
890
    }
891
892
    /**
893
     * {@inheritdoc}
894
     */
895 498
    public function getColumnDeclarationSQL($name, array $field)
896
    {
897 498
        if (isset($field['columnDefinition'])) {
898 19
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
899
        } else {
900 479
            $default = $this->getDefaultValueDeclarationSQL($field);
901
902 479
            $notnull = '';
903
904 479
            if (isset($field['notnull'])) {
905 441
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
906
            }
907
908 479
            $unique = (isset($field['unique']) && $field['unique']) ?
909 479
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
910
911 479
            $check = (isset($field['check']) && $field['check']) ?
912 479
                ' ' . $field['check'] : '';
913
914 479
            $typeDecl = $field['type']->getSQLDeclaration($field, $this);
915 479
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
916
        }
917
918 498
        return $name . ' ' . $columnDef;
919
    }
920
921
    /**
922
     * {@inheritdoc}
923
     */
924 97
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
925
    {
926 97
        if (strpos($tableName, '.') !== false) {
927 38
            list($schema) = explode('.', $tableName);
928 38
            $oldIndexName = $schema . '.' . $oldIndexName;
929
        }
930
931 97
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
932
    }
933
934
    /**
935
     * {@inheritDoc}
936
     */
937
    public function prefersSequences()
938
    {
939
        return true;
940
    }
941
942
    /**
943
     * {@inheritdoc}
944
     */
945 20
    public function usesSequenceEmulatedIdentityColumns()
946
    {
947 20
        return true;
948
    }
949
950
    /**
951
     * {@inheritdoc}
952
     */
953 246
    public function getIdentitySequenceName($tableName, $columnName)
954
    {
955 246
        $table = new Identifier($tableName);
956
957
        // No usage of column name to preserve BC compatibility with <2.5
958 246
        $identitySequenceName = $table->getName() . '_SEQ';
959
960 246
        if ($table->isQuoted()) {
961 58
            $identitySequenceName = '"' . $identitySequenceName . '"';
962
        }
963
964 246
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
965
966 246
        return $identitySequenceIdentifier->getQuotedName($this);
967
    }
968
969
    /**
970
     * {@inheritDoc}
971
     */
972 365
    public function supportsCommentOnStatement()
973
    {
974 365
        return true;
975
    }
976
977
    /**
978
     * {@inheritDoc}
979
     */
980 103
    public function getName()
981
    {
982 103
        return 'oracle';
983
    }
984
985
    /**
986
     * {@inheritDoc}
987
     */
988 141
    protected function doModifyLimitQuery($query, $limit, $offset = null)
989
    {
990 141
        if ($limit === null && $offset <= 0) {
991 20
            return $query;
992
        }
993
994 121
        if (preg_match('/^\s*SELECT/i', $query)) {
995 121
            if (!preg_match('/\sFROM\s/i', $query)) {
996
                $query .= " FROM dual";
997
            }
998
999 121
            $columns = ['a.*'];
1000
1001 121
            if ($offset > 0) {
1002 45
                $columns[] = 'ROWNUM AS doctrine_rownum';
1003
            }
1004
1005 121
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1006
1007 121
            if ($limit !== null) {
1008 102
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1009
            }
1010
1011 121
            if ($offset > 0) {
1012 45
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1013
            }
1014
        }
1015
1016 121
        return $query;
1017
    }
1018
1019
    /**
1020
     * {@inheritDoc}
1021
     *
1022
     * Oracle returns all column names in SQL result sets in uppercase.
1023
     */
1024
    public function getSQLResultCasing($column)
1025
    {
1026
        return strtoupper($column);
1027
    }
1028
1029
    /**
1030
     * {@inheritDoc}
1031
     */
1032
    public function getCreateTemporaryTableSnippetSQL()
1033
    {
1034
        return "CREATE GLOBAL TEMPORARY TABLE";
1035
    }
1036
1037
    /**
1038
     * {@inheritDoc}
1039
     */
1040 1
    public function getDateTimeTzFormatString()
1041
    {
1042 1
        return 'Y-m-d H:i:sP';
1043
    }
1044
1045
    /**
1046
     * {@inheritDoc}
1047
     */
1048 1
    public function getDateFormatString()
1049
    {
1050 1
        return 'Y-m-d 00:00:00';
1051
    }
1052
1053
    /**
1054
     * {@inheritDoc}
1055
     */
1056 1
    public function getTimeFormatString()
1057
    {
1058 1
        return '1900-01-01 H:i:s';
1059
    }
1060
1061
    /**
1062
     * {@inheritDoc}
1063
     */
1064
    public function fixSchemaElementName($schemaElementName)
1065
    {
1066
        if (strlen($schemaElementName) > 30) {
1067
            // Trim it
1068
            return substr($schemaElementName, 0, 30);
1069
        }
1070
1071
        return $schemaElementName;
1072
    }
1073
1074
    /**
1075
     * {@inheritDoc}
1076
     */
1077 13
    public function getMaxIdentifierLength()
1078
    {
1079 13
        return 30;
1080
    }
1081
1082
    /**
1083
     * {@inheritDoc}
1084
     */
1085 6
    public function supportsSequences()
1086
    {
1087 6
        return true;
1088
    }
1089
1090
    /**
1091
     * {@inheritDoc}
1092
     */
1093
    public function supportsForeignKeyOnUpdate()
1094
    {
1095
        return false;
1096
    }
1097
1098
    /**
1099
     * {@inheritDoc}
1100
     */
1101 1
    public function supportsReleaseSavepoints()
1102
    {
1103 1
        return false;
1104
    }
1105
1106
    /**
1107
     * {@inheritDoc}
1108
     */
1109 28
    public function getTruncateTableSQL($tableName, $cascade = false)
1110
    {
1111 28
        $tableIdentifier = new Identifier($tableName);
1112
1113 28
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1114
    }
1115
1116
    /**
1117
     * {@inheritDoc}
1118
     */
1119 8
    public function getDummySelectSQL()
1120
    {
1121 8
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
1122
1123 8
        return sprintf('SELECT %s FROM DUAL', $expression);
1124
    }
1125
1126
    /**
1127
     * {@inheritDoc}
1128
     */
1129 96
    protected function initializeDoctrineTypeMappings()
1130
    {
1131 96
        $this->doctrineTypeMapping = [
1132
            'integer'           => 'integer',
1133
            'number'            => 'integer',
1134
            'pls_integer'       => 'boolean',
1135
            'binary_integer'    => 'boolean',
1136
            'varchar'           => 'string',
1137
            'varchar2'          => 'string',
1138
            'nvarchar2'         => 'string',
1139
            'char'              => 'string',
1140
            'nchar'             => 'string',
1141
            'date'              => 'date',
1142
            'timestamp'         => 'datetime',
1143
            'timestamptz'       => 'datetimetz',
1144
            'float'             => 'float',
1145
            'binary_float'      => 'float',
1146
            'binary_double'     => 'float',
1147
            'long'              => 'string',
1148
            'clob'              => 'text',
1149
            'nclob'             => 'text',
1150
            'raw'               => 'binary',
1151
            'long raw'          => 'blob',
1152
            'rowid'             => 'string',
1153
            'urowid'            => 'string',
1154
            'blob'              => 'blob',
1155
        ];
1156 96
    }
1157
1158
    /**
1159
     * {@inheritDoc}
1160
     */
1161
    public function releaseSavePoint($savepoint)
1162
    {
1163
        return '';
1164
    }
1165
1166
    /**
1167
     * {@inheritDoc}
1168
     */
1169 943
    protected function getReservedKeywordsClass()
1170
    {
1171 943
        return Keywords\OracleKeywords::class;
1172
    }
1173
1174
    /**
1175
     * {@inheritDoc}
1176
     */
1177 24
    public function getBlobTypeDeclarationSQL(array $field)
1178
    {
1179 24
        return 'BLOB';
1180
    }
1181
1182
    /**
1183
     * {@inheritdoc}
1184
     */
1185 382
    public function quoteStringLiteral($str)
1186
    {
1187 382
        $str = str_replace('\\', '\\\\', $str); // Oracle requires backslashes to be escaped aswell.
1188
1189 382
        return parent::quoteStringLiteral($str);
1190
    }
1191
}
1192