Completed
Push — master ( af81c1...f3339a )
by Sergei
21s queued 15s
created

OraclePlatform::normalizeIdentifier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 1
crap 2
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 252
    public static function assertValidIdentifier($identifier)
65
    {
66 252
        if ( ! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
67 90
            throw new DBALException("Invalid Oracle identifier");
68
        }
69 162
    }
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 37
    public function getBitAndComparisonExpression($value1, $value2)
175
    {
176 37
        return 'BITAND('.$value1 . ', ' . $value2 . ')';
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     */
182 19
    public function getBitOrComparisonExpression($value1, $value2)
183
    {
184 19
        return '(' . $value1 . '-' .
185 19
                $this->getBitAndComparisonExpression($value1, $value2)
186 19
                . '+' . $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 133
    public function getCreateSequenceSQL(Sequence $sequence)
197
    {
198 133
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
199 133
               ' START WITH ' . $sequence->getInitialValue() .
200 133
               ' MINVALUE ' . $sequence->getInitialValue() .
201 133
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
202 133
               $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 133
    private function getSequenceCacheSQL(Sequence $sequence)
223
    {
224 133
        if ($sequence->getCache() === 0) {
225 18
            return ' NOCACHE';
226 115
        } else if ($sequence->getCache() === 1) {
227 18
            return ' NOCACHE';
228 97
        } else if ($sequence->getCache() > 1) {
229 18
            return ' CACHE ' . $sequence->getCache();
230
        }
231
232 79
        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 18
    public function getSetTransactionIsolationSQL($level)
247
    {
248 18
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
249
    }
250
251
    /**
252
     * {@inheritDoc}
253
     */
254 18
    protected function _getTransactionIsolationLevelSQL($level)
255
    {
256 1
        switch ($level) {
257 17
            case TransactionIsolationLevel::READ_UNCOMMITTED:
258 18
                return 'READ UNCOMMITTED';
259 17
            case TransactionIsolationLevel::READ_COMMITTED:
260 18
                return 'READ COMMITTED';
261 17
            case TransactionIsolationLevel::REPEATABLE_READ:
262 17
            case TransactionIsolationLevel::SERIALIZABLE:
263 18
                return 'SERIALIZABLE';
264
            default:
265
                return parent::_getTransactionIsolationLevelSQL($level);
266
        }
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 33
    public function getBooleanTypeDeclarationSQL(array $field)
273
    {
274 33
        return 'NUMBER(1)';
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280 309
    public function getIntegerTypeDeclarationSQL(array $field)
281
    {
282 309
        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 20
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
305
    {
306 20
        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 22
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
321
    {
322 22
        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 324
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
345
    {
346 324
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
347 324
                : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     */
353 22
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
354
    {
355 22
        return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')';
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361 40
    public function getBinaryMaxLength()
362
    {
363 40
        return 2000;
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369 80
    public function getClobTypeDeclarationSQL(array $field)
370
    {
371 80
        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 23
    public function getListSequencesSQL($database)
386
    {
387 23
        $database = $this->normalizeIdentifier($database);
388 23
        $database = $this->quoteStringLiteral($database->getName());
389
390
        return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
391 23
               "WHERE SEQUENCE_OWNER = " . $database;
392
    }
393
394
    /**
395
     * {@inheritDoc}
396
     */
397 334
    protected function _getCreateTableSQL($table, array $columns, array $options = [])
398
    {
399 334
        $indexes            = $options['indexes'] ?? [];
400 334
        $options['indexes'] = [];
401 334
        $sql                = parent::_getCreateTableSQL($table, $columns, $options);
402
403 334
        foreach ($columns as $name => $column) {
404 334
            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 334
            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 334
               (isset($column['autoinc']) && $column['autoinc'])) {
410 334
                $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table));
411
            }
412
        }
413
414 334
        if (isset($indexes) && ! empty($indexes)) {
415 68
            foreach ($indexes as $index) {
416 68
                $sql[] = $this->getCreateIndexSQL($index, $table);
417
            }
418
        }
419
420 334
        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 44
    public function getListTableIndexesSQL($table, $currentDatabase = null)
430
    {
431 44
        $table = $this->normalizeIdentifier($table);
432 44
        $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 44
             WHERE     uind_col.table_name = " . $table . "
460
             ORDER BY  uind_col.column_position ASC";
461
    }
462
463
    /**
464
     * {@inheritDoc}
465
     */
466 62
    public function getListTablesSQL()
467
    {
468 62
        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 76
    public function getCreateAutoincrementSql($name, $table, $start = 1)
503
    {
504 76
        $tableIdentifier = $this->normalizeIdentifier($table);
505 76
        $quotedTableName = $tableIdentifier->getQuotedName($this);
506 76
        $unquotedTableName = $tableIdentifier->getName();
507
508 76
        $nameIdentifier = $this->normalizeIdentifier($name);
509 76
        $quotedName = $nameIdentifier->getQuotedName($this);
510 76
        $unquotedName = $nameIdentifier->getName();
511
512 76
        $sql = [];
513
514 76
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
515
516 76
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
517
518 76
        $sql[] = 'DECLARE
519
  constraints_Count NUMBER;
520
BEGIN
521 76
  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 76
    EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\';
524
  END IF;
525
END;';
526
527 76
        $sequenceName = $this->getIdentitySequenceName(
528 76
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
529 76
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
530
        );
531 76
        $sequence = new Sequence($sequenceName, $start);
532 76
        $sql[] = $this->getCreateSequenceSQL($sequence);
533
534 76
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
535
   BEFORE INSERT
536 76
   ON ' . $quotedTableName . '
537
   FOR EACH ROW
538
DECLARE
539
   last_Sequence NUMBER;
540
   last_InsertID NUMBER;
541
BEGIN
542 76
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
543 76
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN
544 76
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
545
   ELSE
546
      SELECT NVL(Last_Number, 0) INTO last_Sequence
547
        FROM User_Sequences
548 76
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
549 76
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
550
      WHILE (last_InsertID > last_Sequence) LOOP
551 76
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
552
      END LOOP;
553
   END IF;
554
END;';
555
556 76
        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 145
    public function getDropAutoincrementSql($table)
567
    {
568 145
        $table = $this->normalizeIdentifier($table);
569 145
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
570 145
        $identitySequenceName = $this->getIdentitySequenceName(
571 145
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
572 145
            ''
573
        );
574
575
        return [
576 145
            'DROP TRIGGER ' . $autoincrementIdentifierName,
577 145
            $this->getDropSequenceSQL($identitySequenceName),
578 145
            $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 397
    private function normalizeIdentifier($name)
593
    {
594 397
        $identifier = new Identifier($name);
595
596 397
        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 217
    private function getAutoincrementIdentifierName(Identifier $table)
610
    {
611 217
        $identifierName = $table->getName() . '_AI_PK';
612
613 217
        return $table->isQuoted()
614 37
            ? $this->quoteSingleIdentifier($identifierName)
615 217
            : $identifierName;
616
    }
617
618
    /**
619
     * {@inheritDoc}
620
     */
621 41
    public function getListTableForeignKeysSQL($table)
622
    {
623 41
        $table = $this->normalizeIdentifier($table);
624 41
        $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 41
      AND alc.table_name = " . $table . "
647
    ORDER BY cols.constraint_name ASC, cols.position ASC";
648
    }
649
650
    /**
651
     * {@inheritDoc}
652
     */
653 18
    public function getListTableConstraintsSQL($table)
654
    {
655 18
        $table = $this->normalizeIdentifier($table);
656 18
        $table = $this->quoteStringLiteral($table->getName());
657
658 18
        return "SELECT * FROM user_constraints WHERE table_name = " . $table;
659
    }
660
661
    /**
662
     * {@inheritDoc}
663
     */
664 123
    public function getListTableColumnsSQL($table, $database = null)
665
    {
666 123
        $table = $this->normalizeIdentifier($table);
667 123
        $table = $this->quoteStringLiteral($table->getName());
668
669 123
        $tabColumnsTableName = "user_tab_columns";
670 123
        $colCommentsTableName = "user_col_comments";
671 123
        $tabColumnsOwnerCondition = '';
672 123
        $colCommentsOwnerCondition = '';
673
674 123
        if (null !== $database && '/' !== $database) {
675 69
            $database = $this->normalizeIdentifier($database);
676 69
            $database = $this->quoteStringLiteral($database->getName());
677 69
            $tabColumnsTableName = "all_tab_columns";
678 69
            $colCommentsTableName = "all_col_comments";
679 69
            $tabColumnsOwnerCondition = "AND c.owner = " . $database;
680 69
            $colCommentsOwnerCondition = "AND d.OWNER = c.OWNER";
681
        }
682
683
        return "SELECT   c.*,
684
                         (
685
                             SELECT d.comments
686 123
                             FROM   $colCommentsTableName d
687 123
                             WHERE  d.TABLE_NAME = c.TABLE_NAME " . $colCommentsOwnerCondition . "
688
                             AND    d.COLUMN_NAME = c.COLUMN_NAME
689
                         ) AS comments
690 123
                FROM     $tabColumnsTableName c
691 123
                WHERE    c.table_name = " . $table . " $tabColumnsOwnerCondition
692
                ORDER BY c.column_id";
693
    }
694
695
    /**
696
     * {@inheritDoc}
697
     */
698 145
    public function getDropSequenceSQL($sequence)
699
    {
700 145
        if ($sequence instanceof Sequence) {
701
            $sequence = $sequence->getQuotedName($this);
702
        }
703
704 145
        return 'DROP SEQUENCE ' . $sequence;
705
    }
706
707
    /**
708
     * {@inheritDoc}
709
     */
710 37
    public function getDropForeignKeySQL($foreignKey, $table)
711
    {
712 37
        if (! $foreignKey instanceof ForeignKeyConstraint) {
713 18
            $foreignKey = new Identifier($foreignKey);
714
        }
715
716 37
        if (! $table instanceof Table) {
717 37
            $table = new Identifier($table);
718
        }
719
720 37
        $foreignKey = $foreignKey->getQuotedName($this);
721 37
        $table = $table->getQuotedName($this);
722
723 37
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
724
    }
725
726
    /**
727
     * {@inheritdoc}
728
     */
729 192
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
730
    {
731 192
        $referentialAction = null;
732
733 192
        if ($foreignKey->hasOption('onDelete')) {
734 73
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
735
        }
736
737 192
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
738
    }
739
740
    /**
741
     * {@inheritdoc}
742
     */
743 181
    public function getForeignKeyReferentialActionSQL($action)
744
    {
745 181
        $action = strtoupper($action);
746
747 11
        switch ($action) {
748 181
            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 145
            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 72
                return '';
753
754 109
            case 'CASCADE':
755 54
            case 'SET NULL':
756 91
                return $action;
757
758
            default:
759
                // SET DEFAULT is not supported, throw exception instead.
760 18
                throw new \InvalidArgumentException('Invalid foreign key action: ' . $action);
761
        }
762
    }
763
764
    /**
765
     * {@inheritDoc}
766
     */
767 20
    public function getDropDatabaseSQL($database)
768
    {
769 20
        return 'DROP USER ' . $database . ' CASCADE';
770
    }
771
772
    /**
773
     * {@inheritDoc}
774
     */
775 305
    public function getAlterTableSQL(TableDiff $diff)
776
    {
777 305
        $sql = [];
778 305
        $commentsSQL = [];
779 305
        $columnSql = [];
780
781 305
        $fields = [];
782
783 305
        foreach ($diff->addedColumns as $column) {
784 73
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
785
                continue;
786
            }
787
788 73
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
789 73
            if ($comment = $this->getColumnComment($column)) {
790 18
                $commentsSQL[] = $this->getCommentOnColumnSQL(
791 18
                    $diff->getName($this)->getQuotedName($this),
792 18
                    $column->getQuotedName($this),
793 73
                    $comment
794
                );
795
            }
796
        }
797
798 305
        if (count($fields)) {
799 73
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
800
        }
801
802 305
        $fields = [];
803 305
        foreach ($diff->changedColumns as $columnDiff) {
804 175
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
805
                continue;
806
            }
807
808
            /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
809 175
            $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 175
            if ($column->getType() instanceof BinaryType &&
815 175
                $columnDiff->hasChanged('fixed') &&
816 175
                count($columnDiff->changedProperties) === 1
817
            ) {
818 18
                continue;
819
            }
820
821 157
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
822
823
            /**
824
             * Do not add query part if only comment has changed
825
             */
826 157
            if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
827 111
                $columnInfo = $column->toArray();
828
829 111
                if ( ! $columnDiff->hasChanged('notnull')) {
830 75
                    unset($columnInfo['notnull']);
831
                }
832
833 111
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
834
            }
835
836 157
            if ($columnHasChangedComment) {
837 46
                $commentsSQL[] = $this->getCommentOnColumnSQL(
838 46
                    $diff->getName($this)->getQuotedName($this),
839 46
                    $column->getQuotedName($this),
840 157
                    $this->getColumnComment($column)
841
                );
842
            }
843
        }
844
845 305
        if (count($fields)) {
846 111
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
847
        }
848
849 305
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
850 73
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
851
                continue;
852
            }
853
854 73
            $oldColumnName = new Identifier($oldColumnName);
855
856 73
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
857 73
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this);
858
        }
859
860 305
        $fields = [];
861 305
        foreach ($diff->removedColumns as $column) {
862 55
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
863
                continue;
864
            }
865
866 55
            $fields[] = $column->getQuotedName($this);
867
        }
868
869 305
        if (count($fields)) {
870 55
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields).')';
871
        }
872
873 305
        $tableSql = [];
874
875 305
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
876 305
            $sql = array_merge($sql, $commentsSQL);
877
878 305
            if ($diff->newName !== false) {
879 37
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
880
            }
881
882 305
            $sql = array_merge(
883 305
                $this->getPreAlterTableIndexForeignKeySQL($diff),
884 305
                $sql,
885 305
                $this->getPostAlterTableIndexForeignKeySQL($diff)
886
            );
887
        }
888
889 305
        return array_merge($sql, $tableSql, $columnSql);
890
    }
891
892
    /**
893
     * {@inheritdoc}
894
     */
895 478
    public function getColumnDeclarationSQL($name, array $field)
896
    {
897 478
        if (isset($field['columnDefinition'])) {
898 18
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
899
        } else {
900 460
            $default = $this->getDefaultValueDeclarationSQL($field);
901
902 460
            $notnull = '';
903
904 460
            if (isset($field['notnull'])) {
905 424
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
906
            }
907
908 460
            $unique = (isset($field['unique']) && $field['unique']) ?
909 460
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
910
911 460
            $check = (isset($field['check']) && $field['check']) ?
912 460
                ' ' . $field['check'] : '';
913
914 460
            $typeDecl = $field['type']->getSQLDeclaration($field, $this);
915 460
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
916
        }
917
918 478
        return $name . ' ' . $columnDef;
919
    }
920
921
    /**
922
     * {@inheritdoc}
923
     */
924 92
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
925
    {
926 92
        if (strpos($tableName, '.') !== false) {
927 36
            list($schema) = explode('.', $tableName);
928 36
            $oldIndexName = $schema . '.' . $oldIndexName;
929
        }
930
931 92
        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 19
    public function usesSequenceEmulatedIdentityColumns()
946
    {
947 19
        return true;
948
    }
949
950
    /**
951
     * {@inheritdoc}
952
     */
953 235
    public function getIdentitySequenceName($tableName, $columnName)
954
    {
955 235
        $table = new Identifier($tableName);
956
957
        // No usage of column name to preserve BC compatibility with <2.5
958 235
        $identitySequenceName = $table->getName() . '_SEQ';
959
960 235
        if ($table->isQuoted()) {
961 55
            $identitySequenceName = '"' . $identitySequenceName . '"';
962
        }
963
964 235
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
965
966 235
        return $identitySequenceIdentifier->getQuotedName($this);
967
    }
968
969
    /**
970
     * {@inheritDoc}
971
     */
972 352
    public function supportsCommentOnStatement()
973
    {
974 352
        return true;
975
    }
976
977
    /**
978
     * {@inheritDoc}
979
     */
980 100
    public function getName()
981
    {
982 100
        return 'oracle';
983
    }
984
985
    /**
986
     * {@inheritDoc}
987
     */
988 116
    protected function doModifyLimitQuery($query, $limit, $offset = null)
989
    {
990 116
        if ($limit === null && $offset === null) {
991
            return $query;
992
        }
993
994 116
        if (preg_match('/^\s*SELECT/i', $query)) {
995 116
            if (!preg_match('/\sFROM\s/i', $query)) {
996
                $query .= " FROM dual";
997
            }
998
999 116
            $columns = ['a.*'];
1000
1001 116
            if ($offset > 0) {
1002 43
                $columns[] = 'ROWNUM AS doctrine_rownum';
1003
            }
1004
1005 116
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1006
1007 116
            if ($limit !== null) {
1008 97
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1009
            }
1010
1011 116
            if ($offset > 0) {
1012 43
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1013
            }
1014
        }
1015
1016 116
        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 30
    public function getTruncateTableSQL($tableName, $cascade = false)
1110
    {
1111 30
        $tableIdentifier = new Identifier($tableName);
1112
1113 30
        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 91
    protected function initializeDoctrineTypeMappings()
1130
    {
1131 91
        $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 91
    }
1157
1158
    /**
1159
     * {@inheritDoc}
1160
     */
1161
    public function releaseSavePoint($savepoint)
1162
    {
1163
        return '';
1164
    }
1165
1166
    /**
1167
     * {@inheritDoc}
1168
     */
1169 894
    protected function getReservedKeywordsClass()
1170
    {
1171 894
        return Keywords\OracleKeywords::class;
1172
    }
1173
1174
    /**
1175
     * {@inheritDoc}
1176
     */
1177 23
    public function getBlobTypeDeclarationSQL(array $field)
1178
    {
1179 23
        return 'BLOB';
1180
    }
1181
1182
    /**
1183
     * {@inheritdoc}
1184
     */
1185 365
    public function quoteStringLiteral($str)
1186
    {
1187 365
        $str = str_replace('\\', '\\\\', $str); // Oracle requires backslashes to be escaped aswell.
1188
1189 365
        return parent::quoteStringLiteral($str);
1190
    }
1191
}
1192