Failed Conditions
Pull Request — master (#2766)
by mon
63:22
created

OraclePlatform::getDateArithmeticIntervalExpression()   D

Complexity

Conditions 10
Paths 14

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 0
cts 25
cp 0
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 28
nc 14
nop 4
crap 110

How to fix   Complexity   

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
 * 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\Types\BinaryType;
30
31
/**
32
 * OraclePlatform.
33
 *
34
 * @since 2.0
35
 * @author Roman Borschel <[email protected]>
36
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
37
 * @author Benjamin Eberlei <[email protected]>
38
 */
39
class OraclePlatform extends AbstractPlatform
40
{
41
    /**
42
     * Assertion for Oracle identifiers.
43
     *
44
     * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
45
     *
46
     * @param string $identifier
47
     *
48
     * @throws DBALException
49
     */
50 14
    public static function assertValidIdentifier($identifier)
51
    {
52 14
        if ( ! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) {
53 5
            throw new DBALException("Invalid Oracle identifier");
54
        }
55 9
    }
56
57
    /**
58
     * {@inheritDoc}
59
     */
60
    public function getSubstringExpression($value, $position, $length = null)
61
    {
62
        if ($length !== null) {
63
            return "SUBSTR($value, $position, $length)";
64
        }
65
66
        return "SUBSTR($value, $position)";
67
    }
68
69
    /**
70
     * {@inheritDoc}
71
     */
72
    public function getNowExpression($type = 'timestamp')
73
    {
74
        switch ($type) {
75
            case 'date':
76
            case 'time':
77
            case 'timestamp':
78
            default:
79
                return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')';
80
        }
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function getLocateExpression($str, $substr, $startPos = false)
87
    {
88
        if ($startPos == false) {
89
            return 'INSTR('.$str.', '.$substr.')';
90
        }
91
92
        return 'INSTR('.$str.', '.$substr.', '.$startPos.')';
93
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98
    public function getGuidExpression()
99
    {
100
        return 'SYS_GUID()';
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
107
    {
108
        switch ($unit) {
109
            case self::DATE_INTERVAL_UNIT_MONTH:
110
            case self::DATE_INTERVAL_UNIT_QUARTER:
111
            case self::DATE_INTERVAL_UNIT_YEAR:
112
                switch ($unit) {
113
                    case self::DATE_INTERVAL_UNIT_QUARTER:
114
                        $interval *= 3;
115
                        break;
116
117
                    case self::DATE_INTERVAL_UNIT_YEAR:
118
                        $interval *= 12;
119
                        break;
120
                }
121
122
                return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
123
124
            default:
125
                $calculationClause = '';
126
127
                switch ($unit) {
128
                    case self::DATE_INTERVAL_UNIT_SECOND:
129
                        $calculationClause = '/24/60/60';
130
                        break;
131
132
                    case self::DATE_INTERVAL_UNIT_MINUTE:
133
                        $calculationClause = '/24/60';
134
                        break;
135
136
                    case self::DATE_INTERVAL_UNIT_HOUR:
137
                        $calculationClause = '/24';
138
                        break;
139
140
                    case self::DATE_INTERVAL_UNIT_WEEK:
141
                        $calculationClause = '*7';
142
                        break;
143
                }
144
145
                return '(' . $date . $operator . $interval . $calculationClause . ')';
146
        }
147
    }
148
149
    /**
150
     * {@inheritDoc}
151
     *
152
     * Note: Since Oracle timestamp differences are calculated down to the microsecond we have to truncate
153
     * them to the difference in days. This is obviously a restriction of the original functionality, but we
154
     * need to make this a portable function.
155
     */
156
    public function getDateDiffExpression($date1, $date2)
157
    {
158
        return "TRUNC(TO_NUMBER(SUBSTR((" . $date1 . "-" . $date2 . "), 1, INSTR(" . $date1 . "-" . $date2 .", ' '))))";
159
    }
160
161
    /**
162
     * {@inheritDoc}
163
     */
164 2
    public function getBitAndComparisonExpression($value1, $value2)
165
    {
166 2
        return 'BITAND('.$value1 . ', ' . $value2 . ')';
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 1
    public function getBitOrComparisonExpression($value1, $value2)
173
    {
174 1
        return '(' . $value1 . '-' .
175 1
                $this->getBitAndComparisonExpression($value1, $value2)
176 1
                . '+' . $value2 . ')';
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     *
182
     * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
183
     * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
184
     * in {@see listSequences()}
185
     */
186 6
    public function getCreateSequenceSQL(Sequence $sequence)
187
    {
188 6
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
189 6
               ' START WITH ' . $sequence->getInitialValue() .
190 6
               ' MINVALUE ' . $sequence->getInitialValue() .
191 6
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
192 6
               $this->getSequenceCacheSQL($sequence);
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198
    public function getAlterSequenceSQL(Sequence $sequence)
199
    {
200
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
201
               ' INCREMENT BY ' . $sequence->getAllocationSize()
202
               . $this->getSequenceCacheSQL($sequence);
203
    }
204
205
    /**
206
     * Cache definition for sequences
207
     *
208
     * @param Sequence $sequence
209
     *
210
     * @return string
211
     */
212 6
    private function getSequenceCacheSQL(Sequence $sequence)
213
    {
214 6
        if ($sequence->getCache() === 0) {
215 1
            return ' NOCACHE';
216 5
        } else if ($sequence->getCache() === 1) {
217 1
            return ' NOCACHE';
218 4
        } else if ($sequence->getCache() > 1) {
219 1
            return ' CACHE ' . $sequence->getCache();
220
        }
221
222 3
        return '';
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228
    public function getSequenceNextValSQL($sequenceName)
229
    {
230
        return 'SELECT ' . $sequenceName . '.nextval FROM DUAL';
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236 1
    public function getSetTransactionIsolationSQL($level)
237
    {
238 1
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 1
    protected function _getTransactionIsolationLevelSQL($level)
245
    {
246
        switch ($level) {
247 1
            case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED:
248 1
                return 'READ UNCOMMITTED';
249 1
            case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED:
250 1
                return 'READ COMMITTED';
251 1
            case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ:
252 1
            case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE:
253 1
                return 'SERIALIZABLE';
254
            default:
255
                return parent::_getTransactionIsolationLevelSQL($level);
256
        }
257
    }
258
259
    /**
260
     * {@inheritDoc}
261
     */
262 1
    public function getBooleanTypeDeclarationSQL(array $field)
263
    {
264 1
        return 'NUMBER(1)';
265
    }
266
267
    /**
268
     * {@inheritDoc}
269
     */
270 10
    public function getIntegerTypeDeclarationSQL(array $field)
271
    {
272 10
        return 'NUMBER(10)';
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     */
278
    public function getBigIntTypeDeclarationSQL(array $field)
279
    {
280
        return 'NUMBER(20)';
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286
    public function getSmallIntTypeDeclarationSQL(array $field)
287
    {
288
        return 'NUMBER(5)';
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
295
    {
296
        return 'TIMESTAMP(0)';
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
303
    {
304
        return 'TIMESTAMP(0) WITH TIME ZONE';
305
    }
306
307
    /**
308
     * {@inheritDoc}
309
     */
310
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
311
    {
312
        return 'DATE';
313
    }
314
315
    /**
316
     * {@inheritDoc}
317
     */
318
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
319
    {
320
        return 'DATE';
321
    }
322
323
    /**
324
     * {@inheritDoc}
325
     */
326
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
327
    {
328
        return '';
329
    }
330
331
    /**
332
     * {@inheritDoc}
333
     */
334 14
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
335
    {
336 14
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
337 14
                : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     */
343 1
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
344
    {
345 1
        return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')';
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 2
    public function getBinaryMaxLength()
352
    {
353 2
        return 2000;
354
    }
355
356
    /**
357
     * {@inheritDoc}
358
     */
359 2
    public function getClobTypeDeclarationSQL(array $field)
360
    {
361 2
        return 'CLOB';
362
    }
363
364
    /**
365
     * Returns the database condition for querying the schema.
366
     *
367
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
368
     *
369
     * @return string
370
     */
371
    private function getDatabaseCondition(?string $database): string
372
    {
373
        if ($database !== null && $database !== '/') {
374
            $databaseIdentifier = $this->normalizeIdentifier($database);
375 1
            return $this->quoteStringLiteral($databaseIdentifier->getName());
376
        }
377 1
        else {
378 1
            return "(SELECT SYS_CONTEXT('userenv', 'current_schema') FROM DUAL)";
379
        }
380
    }
381 1
382
    /**
383
     * {@inheritDoc}
384
     */
385
    public function getListDatabasesSQL()
386
    {
387 11
        return 'SELECT username FROM all_users';
388
    }
389 11
390 11
    /**
391 11
     * {@inheritDoc}
392
     */
393 11
    public function getListSequencesSQL($database)
394 11
    {
395
        $database = $this->normalizeIdentifier($database);
396
        $database = $this->quoteStringLiteral($database->getName());
397
398 11
        return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
399 11
               "WHERE SEQUENCE_OWNER = " . $database;
400 11
    }
401
402
    /**
403
     * {@inheritDoc}
404 11
     */
405 3
    protected function _getCreateTableSQL($table, array $columns, array $options = [])
406 3
    {
407
        $indexes = isset($options['indexes']) ? $options['indexes'] : [];
408
        $options['indexes'] = [];
409
        $sql = parent::_getCreateTableSQL($table, $columns, $options);
410 11
411
        foreach ($columns as $name => $column) {
412
            if (isset($column['sequence'])) {
413
                $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1);
414
            }
415
416
            if (isset($column['autoincrement']) && $column['autoincrement'] ||
417
               (isset($column['autoinc']) && $column['autoinc'])) {
418
                $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table));
419 1
            }
420
        }
421 1
422 1
        if (isset($indexes) && ! empty($indexes)) {
423
            foreach ($indexes as $index) {
424
                $sql[] = $this->getCreateIndexSQL($index, $table);
425
            }
426
        }
427
428
        return $sql;
429
    }
430
431
    /**
432
     * Returns the SQL for a list of indexes in the database.
433
     *
434
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
435
     * @param string $table    The table. If left NULL, the SQL will return all the indexes in the database.
436
     *
437
     * @return string
438
     */
439
    private function getListIndexesSQL(?string $database, ?string $table): string
440
    {
441
        $databaseCondition = $this->getDatabaseCondition($database);
442
        $tableCondition = '';
443
        if ($table !== null) {
444
            $tableIdentifier = $this->normalizeIdentifier($table);
445
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
446
            $tableCondition = " AND ind_col.table_name = $quotedTableIdentifier";
447
        }
448
        return <<<SQL
449 1
          SELECT ind_col.table_name as table_name,
450
                 ind_col.index_name AS name,
451
                 ind.index_type AS type,
452
                 decode(ind.uniqueness, 'NONUNIQUE', 0, 'UNIQUE', 1) AS is_unique,
453
                 ind_col.column_name AS column_name,
454
                 ind_col.column_position AS column_pos,
455
                 con.constraint_type AS is_primary
456
            FROM all_ind_columns ind_col
457
       LEFT JOIN all_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name
458
       LEFT JOIN all_constraints con ON con.owner = ind_col.index_owner AND con.index_name = ind_col.index_name
459
           WHERE ind_col.index_owner = $databaseCondition$tableCondition
460
        ORDER BY ind_col.table_name, ind_col.index_name, ind_col.column_position
461
SQL;
462
    }
463
464
    /**
465
     * {@inheritDoc}
466
     *
467
     * @license New BSD License
468
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
469
     */
470
    public function getListTableIndexesSQL($table, $currentDatabase = null)
471
    {
472
        return $this->getListIndexesSQL($currentDatabase, $table);
473
    }
474
475
    /**
476
     * Returns the SQL for a list of all indexes in the database.
477
     *
478
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
479
     *
480
     * @return string
481
     */
482
    public function getListAllIndexesSQL(?$database = null)
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_VARIABLE on line 482 at column 42
Loading history...
483
    {
484
        return $this->getListIndexesSQL($database, null);
485
    }
486
487
    /**
488
     * {@inheritDoc}
489
     */
490
    public function getListTablesSQL()
491
    {
492 3
        return 'SELECT * FROM sys.user_tables';
493
    }
494 3
495 3
    /**
496 3
     * {@inheritDoc}
497
     */
498 3
    public function getListViewsSQL($database)
499 3
    {
500 3
        return 'SELECT view_name, text FROM sys.user_views';
501
    }
502 3
503
    /**
504 3
     * {@inheritDoc}
505
     */
506 3
    public function getCreateViewSQL($name, $sql)
507
    {
508 3
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
509
    }
510
511 3
    /**
512
     * {@inheritDoc}
513 3
     */
514
    public function getDropViewSQL($name)
515
    {
516
        return 'DROP VIEW '. $name;
517 3
    }
518 3
519 3
    /**
520
     * @param string  $name
521 3
     * @param string  $table
522 3
     * @param integer $start
523
     *
524 3
     * @return array
525
     */
526 3
    public function getCreateAutoincrementSql($name, $table, $start = 1)
527
    {
528
        $tableIdentifier = $this->normalizeIdentifier($table);
529
        $quotedTableName = $tableIdentifier->getQuotedName($this);
530
        $unquotedTableName = $tableIdentifier->getName();
531
532 3
        $nameIdentifier = $this->normalizeIdentifier($name);
533 3
        $quotedName = $nameIdentifier->getQuotedName($this);
534 3
        $unquotedName = $nameIdentifier->getName();
535
536
        $sql = [];
537
538 3
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
539 3
540
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
541 3
542
        $sql[] = 'DECLARE
543
  constraints_Count NUMBER;
544
BEGIN
545
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
546 3
  IF constraints_Count = 0 OR constraints_Count = \'\' THEN
547
    EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\';
548
  END IF;
549
END;';
550
551
        $sequenceName = $this->getIdentitySequenceName(
552
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
553
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
554
        );
555
        $sequence = new Sequence($sequenceName, $start);
556 3
        $sql[] = $this->getCreateSequenceSQL($sequence);
557
558 3
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
559 3
   BEFORE INSERT
560 3
   ON ' . $quotedTableName . '
561 3
   FOR EACH ROW
562 3
DECLARE
563
   last_Sequence NUMBER;
564
   last_InsertID NUMBER;
565
BEGIN
566 3
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
567 3
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN
568 3
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
569
   ELSE
570
      SELECT NVL(Last_Number, 0) INTO last_Sequence
571
        FROM User_Sequences
572
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
573
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
574
      WHILE (last_InsertID > last_Sequence) LOOP
575
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
576
      END LOOP;
577
   END IF;
578
END;';
579
580
        return $sql;
581
    }
582 16
583
    /**
584 16
     * Returns the SQL statements to drop the autoincrement for the given table name.
585
     *
586 16
     * @param string $table The table name to drop the autoincrement for.
587
     *
588
     * @return array
589
     */
590
    public function getDropAutoincrementSql($table)
591
    {
592
        $table = $this->normalizeIdentifier($table);
593
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
594
        $identitySequenceName = $this->getIdentitySequenceName(
595
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
596
            ''
597
        );
598
599 6
        return [
600
            'DROP TRIGGER ' . $autoincrementIdentifierName,
601 6
            $this->getDropSequenceSQL($identitySequenceName),
602
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
603 6
        ];
604 2
    }
605 6
606
    /**
607
     * Normalizes the given identifier.
608
     *
609
     * Uppercases the given identifier if it is not quoted by intention
610
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
611 1
     *
612
     * @param string $name The identifier to normalize.
613 1
     *
614 1
     * @return Identifier The normalized identifier.
615
     */
616
    private function normalizeIdentifier($name)
617
    {
618
        $identifier = new Identifier($name);
619
620
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
621
    }
622
623
    /**
624
     * Returns the autoincrement primary key identifier name for the given table identifier.
625
     *
626
     * Quotes the autoincrement primary key identifier name
627
     * if the given table name is quoted by intention.
628
     *
629
     * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
630
     *
631
     * @return string
632
     */
633
    private function getAutoincrementIdentifierName(Identifier $table)
634
    {
635
        $identifierName = $table->getName() . '_AI_PK';
636 1
637
        return $table->isQuoted()
638
            ? $this->quoteSingleIdentifier($identifierName)
639
            : $identifierName;
640
    }
641
642
    /**
643 1
     * Returns the SQL for a list of foreign keys in the database.
644
     *
645 1
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
646 1
     * @param string $table    The table. If left NULL, the SQL will return all the indexes in the database.
647
     *
648 1
     * @return string
649
     */
650
    private function getListForeignKeysSQL(?string $database, ?string $table): string
651
    {
652
        $databaseCondition = $this->getDatabaseCondition($database);
653
        $tableCondition = '';
654 5
        if ($table !== null) {
655
            $tableIdentifier = $this->normalizeIdentifier($table);
656 5
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
657 5
            $tableCondition = " AND cols.table_name = $quotedTableIdentifier";
658
        }
659 5
        return <<<SQL
660 5
          SELECT cols.table_name,
661 5
                 alc.constraint_name,
662 5
                 alc.DELETE_RULE,
663
                 cols.column_name "local_column",
664 5
                 cols.position,
665 2
                 r_cols.table_name "references_table",
666 2
                 r_cols.column_name "foreign_column"
667 2
            FROM all_cons_columns cols
668 2
       LEFT JOIN all_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name
669 2
       LEFT JOIN all_cons_columns r_cols ON r_cols.owner = alc.r_owner AND r_cols.constraint_name = alc.r_constraint_name AND r_cols.position = cols.position
670 2
           WHERE cols.owner = $databaseCondition AND alc.constraint_type = 'R'$tableCondition
671
        ORDER BY cols.table_name, cols.constraint_name, cols.position
672
SQL;
673
    }
674
675
    /**
676 5
     * {@inheritDoc}
677 5
     */
678
    public function getListTableForeignKeysSQL($table)
679
    {
680 5
        return $this->getListForeignKeysSQL(null, $table);
681 5
    }
682
683
    /**
684
     * Returns the SQL for a list of all foreign keys in the database.
685
     *
686
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
687
     *
688 3
     * @return string
689
     */
690 3
    public function getListAllForeignKeysSQL(?$database = null)
691
    {
692
        return $this->getListForeignKeysSQL($database, null);
693
    }
694 3
695
    /**
696
     * {@inheritDoc}
697
     */
698
    public function getListTableConstraintsSQL($table)
699
    {
700 2
        $table = $this->normalizeIdentifier($table);
701
        $table = $this->quoteStringLiteral($table->getName());
702 2
703 1
        return "SELECT * FROM user_constraints WHERE table_name = " . $table;
704
    }
705
706 2
    /**
707 2
     * Returns the SQL for a list of columns in the database.
708
     *
709
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
710 2
     * @param string $table    The table. If left NULL, the SQL will return all the indexes in the database.
711 2
     *
712
     * @return string
713 2
     */
714
    private function getListColumnsSQL(?string $database, ?string $table): string
715
    {
716
        $databaseCondition = $this->getDatabaseCondition($database);
717
        $tableCondition = '';
718
        if ($table !== null) {
719 10
            $tableIdentifier = $this->normalizeIdentifier($table);
720
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
721 10
            $tableCondition = " AND c.table_name = $quotedTableIdentifier";
722
        }
723 10
        return <<<SQL
724 4
          SELECT c.*,
725
                 d.comments AS comments
726
            FROM all_tab_columns c
727 10
       LEFT JOIN all_col_comments d ON d.OWNER = c.OWNER AND d.TABLE_NAME = c.TABLE_NAME AND d.COLUMN_NAME = c.COLUMN_NAME
728
           WHERE c.owner = $databaseCondition$tableCondition
729
        ORDER BY c.table_name, c.column_id
730
SQL;
731
    }
732
733 10
    /**
734
     * {@inheritDoc}
735 10
     */
736
    public function getListTableColumnsSQL($table, $database = null)
737
    {
738 10
        return $this->getListColumnsSQL($database, $table);
739 8
    }
740
741
    /**
742 4
     * Returns the SQL for a list of all columns in the database.
743
     *
744 6
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
745 3
     *
746 5
     * @return string
747
     */
748
    public function getListAllColumnsSQL(?$database = null)
749
    {
750 1
        return $this->getListColumnsSQL($database, null);
751
    }
752
753
    /**
754
     * {@inheritDoc}
755
     */
756
    public function getDropSequenceSQL($sequence)
757 1
    {
758
        if ($sequence instanceof Sequence) {
759 1
            $sequence = $sequence->getQuotedName($this);
760
        }
761
762
        return 'DROP SEQUENCE ' . $sequence;
763
    }
764
765 16
    /**
766
     * {@inheritDoc}
767 16
     */
768 16
    public function getDropForeignKeySQL($foreignKey, $table)
769 16
    {
770
        if (! $foreignKey instanceof ForeignKeyConstraint) {
771 16
            $foreignKey = new Identifier($foreignKey);
772
        }
773 16
774 4
        if (! $table instanceof Table) {
775
            $table = new Identifier($table);
776
        }
777
778 4
        $foreignKey = $foreignKey->getQuotedName($this);
779 4
        $table = $table->getQuotedName($this);
780 1
781 1
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
782 1
    }
783 4
784
    /**
785
     * {@inheritdoc}
786
     */
787
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
788 16
    {
789 4
        $referentialAction = null;
790
791
        if ($foreignKey->hasOption('onDelete')) {
792 16
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
793 16
        }
794 9
795
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
796
    }
797
798
    /**
799 9
     * {@inheritdoc}
800
     */
801
    public function getForeignKeyReferentialActionSQL($action)
802
    {
803
        $action = strtoupper($action);
804 9
805 9
        switch ($action) {
806 9
            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...
807
            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...
808 1
                // NO ACTION cannot be declared explicitly,
809
                // therefore returning empty string to indicate to OMIT the referential clause.
810
                return '';
811 8
812
            case 'CASCADE':
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...
813
            case 'SET NULL':
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...
814
                return $action;
815
816 8
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

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

Loading history...
817 6
                // SET DEFAULT is not supported, throw exception instead.
818
                throw new \InvalidArgumentException('Invalid foreign key action: ' . $action);
819 6
        }
820 4
    }
821
822
    /**
823 6
     * {@inheritDoc}
824
     */
825
    public function getDropDatabaseSQL($database)
826 8
    {
827 2
        return 'DROP USER ' . $database . ' CASCADE';
828 2
    }
829 2
830 8
    /**
831
     * {@inheritDoc}
832
     */
833
    public function getAlterTableSQL(TableDiff $diff)
834
    {
835 16
        $sql = [];
836 6
        $commentsSQL = [];
837
        $columnSql = [];
838
839 16
        $fields = [];
840 4
841
        foreach ($diff->addedColumns as $column) {
842
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
843
                continue;
844 4
            }
845
846 4
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
847 4
            if ($comment = $this->getColumnComment($column)) {
848
                $commentsSQL[] = $this->getCommentOnColumnSQL(
849
                    $diff->getName($this)->getQuotedName($this),
850 16
                    $column->getQuotedName($this),
851 16
                    $comment
852 3
                );
853
            }
854
        }
855
856 3
        if (count($fields)) {
857
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
858
        }
859 16
860 3
        $fields = [];
861
        foreach ($diff->changedColumns as $columnDiff) {
862
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
863 16
                continue;
864
            }
865 16
866 16
            /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
867
            $column = $columnDiff->column;
868 16
869 2
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
870
            // Oracle only supports binary type columns with variable length.
871
            // Avoids unnecessary table alteration statements.
872 16
            if ($column->getType() instanceof BinaryType &&
873 16
                $columnDiff->hasChanged('fixed') &&
874 16
                count($columnDiff->changedProperties) === 1
875 16
            ) {
876
                continue;
877
            }
878
879 16
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
880
881
            /**
882
             * Do not add query part if only comment has changed
883
             */
884
            if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
885 19
                $columnInfo = $column->toArray();
886
887 19
                if ( ! $columnDiff->hasChanged('notnull')) {
888 1
                    unset($columnInfo['notnull']);
889
                }
890 18
891
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
892 18
            }
893
894 18
            if ($columnHasChangedComment) {
895 16
                $commentsSQL[] = $this->getCommentOnColumnSQL(
896
                    $diff->getName($this)->getQuotedName($this),
897
                    $column->getQuotedName($this),
898 18
                    $this->getColumnComment($column)
899 18
                );
900
            }
901 18
        }
902 18
903
        if (count($fields)) {
904 18
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
905 18
        }
906
907
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
908 19
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
909
                continue;
910
            }
911
912
            $oldColumnName = new Identifier($oldColumnName);
913
914 5
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
915
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this);
916 5
        }
917 2
918 2
        $fields = [];
919
        foreach ($diff->removedColumns as $column) {
920
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
921 5
                continue;
922
            }
923
924
            $fields[] = $column->getQuotedName($this);
925
        }
926
927
        if (count($fields)) {
928
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields).')';
929
        }
930
931
        $tableSql = [];
932
933
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
934
            $sql = array_merge($sql, $commentsSQL);
935 1
936
            if ($diff->newName !== false) {
937 1
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
938
            }
939
940
            $sql = array_merge(
941
                $this->getPreAlterTableIndexForeignKeySQL($diff),
942
                $sql,
943 7
                $this->getPostAlterTableIndexForeignKeySQL($diff)
944
            );
945 7
        }
946
947
        return array_merge($sql, $tableSql, $columnSql);
948 7
    }
949
950 7
    /**
951 3
     * {@inheritdoc}
952
     */
953
    public function getColumnDeclarationSQL($name, array $field)
954 7
    {
955
        if (isset($field['columnDefinition'])) {
956 7
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
957
        } else {
958
            $default = $this->getDefaultValueDeclarationSQL($field);
959
960
            $notnull = '';
961
962 12
            if (isset($field['notnull'])) {
963
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
964 12
            }
965
966
            $unique = (isset($field['unique']) && $field['unique']) ?
967
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
968
969
            $check = (isset($field['check']) && $field['check']) ?
970 2
                ' ' . $field['check'] : '';
971
972 2
            $typeDecl = $field['type']->getSQLDeclaration($field, $this);
973
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
974
        }
975
976
        return $name . ' ' . $columnDef;
977
    }
978 6
979
    /**
980 6
     * {@inheritdoc}
981
     */
982
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
983
    {
984 6
        if (strpos($tableName, '.') !== false) {
985 6
            list($schema) = explode('.', $tableName);
986
            $oldIndexName = $schema . '.' . $oldIndexName;
987
        }
988
989 6
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
990
    }
991 6
992 2
    /**
993
     * {@inheritDoc}
994
     */
995 6
    public function prefersSequences()
996
    {
997 6
        return true;
998 5
    }
999
1000
    /**
1001 6
     * {@inheritdoc}
1002 2
     */
1003
    public function usesSequenceEmulatedIdentityColumns()
1004
    {
1005
        return true;
1006 6
    }
1007
1008
    /**
1009
     * {@inheritdoc}
1010
     */
1011
    public function getIdentitySequenceName($tableName, $columnName)
1012
    {
1013
        $table = new Identifier($tableName);
1014
1015
        // No usage of column name to preserve BC compatibility with <2.5
1016
        $identitySequenceName = $table->getName() . '_SEQ';
1017
1018
        if ($table->isQuoted()) {
1019
            $identitySequenceName = '"' . $identitySequenceName . '"';
1020
        }
1021
1022
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
1023
1024
        return $identitySequenceIdentifier->getQuotedName($this);
1025
    }
1026
1027
    /**
1028
     * {@inheritDoc}
1029
     */
1030
    public function supportsCommentOnStatement()
1031
    {
1032
        return true;
1033
    }
1034
1035
    /**
1036
     * {@inheritDoc}
1037
     */
1038
    public function getName()
1039
    {
1040
        return 'oracle';
1041
    }
1042
1043
    /**
1044
     * {@inheritDoc}
1045
     */
1046
    protected function doModifyLimitQuery($query, $limit, $offset = null)
1047
    {
1048
        if ($limit === null && $offset === null) {
1049
            return $query;
1050
        }
1051
1052
        if (preg_match('/^\s*SELECT/i', $query)) {
1053
            if (!preg_match('/\sFROM\s/i', $query)) {
1054
                $query .= " FROM dual";
1055
            }
1056
1057
            $columns = ['a.*'];
1058
1059
            if ($offset > 0) {
1060
                $columns[] = 'ROWNUM AS doctrine_rownum';
1061
            }
1062
1063
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1064
1065
            if ($limit !== null) {
1066
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1067
            }
1068
1069
            if ($offset > 0) {
1070
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1071
            }
1072
        }
1073
1074
        return $query;
1075
    }
1076
1077
    /**
1078
     * {@inheritDoc}
1079
     *
1080
     * Oracle returns all column names in SQL result sets in uppercase.
1081
     */
1082
    public function getSQLResultCasing($column)
1083
    {
1084
        return strtoupper($column);
1085
    }
1086
1087
    /**
1088
     * {@inheritDoc}
1089
     */
1090
    public function getCreateTemporaryTableSnippetSQL()
1091
    {
1092
        return "CREATE GLOBAL TEMPORARY TABLE";
1093
    }
1094
1095
    /**
1096
     * {@inheritDoc}
1097
     */
1098
    public function getDateTimeTzFormatString()
1099 1
    {
1100
        return 'Y-m-d H:i:sP';
1101 1
    }
1102
1103 1
    /**
1104
     * {@inheritDoc}
1105
     */
1106
    public function getDateFormatString()
1107
    {
1108
        return 'Y-m-d 00:00:00';
1109
    }
1110
1111
    /**
1112
     * {@inheritDoc}
1113
     */
1114
    public function getTimeFormatString()
1115
    {
1116
        return '1900-01-01 H:i:s';
1117 5
    }
1118
1119 5
    /**
1120
     * {@inheritDoc}
1121
     */
1122
    public function fixSchemaElementName($schemaElementName)
1123
    {
1124
        if (strlen($schemaElementName) > 30) {
1125
            // Trim it
1126
            return substr($schemaElementName, 0, 30);
1127
        }
1128
1129
        return $schemaElementName;
1130
    }
1131
1132
    /**
1133
     * {@inheritDoc}
1134
     */
1135
    public function getMaxIdentifierLength()
1136
    {
1137
        return 30;
1138
    }
1139
1140
    /**
1141
     * {@inheritDoc}
1142
     */
1143
    public function supportsSequences()
1144 5
    {
1145
        return true;
1146
    }
1147
1148
    /**
1149
     * {@inheritDoc}
1150
     */
1151
    public function supportsForeignKeyOnUpdate()
1152
    {
1153
        return false;
1154
    }
1155
1156
    /**
1157 49
     * {@inheritDoc}
1158
     */
1159 49
    public function supportsReleaseSavepoints()
1160
    {
1161
        return false;
1162
    }
1163
1164
    /**
1165 1
     * {@inheritDoc}
1166
     */
1167 1
    public function getTruncateTableSQL($tableName, $cascade = false)
1168
    {
1169
        $tableIdentifier = new Identifier($tableName);
1170
1171
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1172
    }
1173 17
1174
    /**
1175 17
     * {@inheritDoc}
1176
     */
1177 17
    public function getDummySelectSQL()
1178
    {
1179
        return 'SELECT 1 FROM DUAL';
1180
    }
1181
1182
    /**
1183
     * {@inheritDoc}
1184
     */
1185
    protected function initializeDoctrineTypeMappings()
1186
    {
1187
        $this->doctrineTypeMapping = [
1188
            'integer'           => 'integer',
1189
            'number'            => 'integer',
1190
            'pls_integer'       => 'boolean',
1191
            'binary_integer'    => 'boolean',
1192
            'varchar'           => 'string',
1193
            'varchar2'          => 'string',
1194
            'nvarchar2'         => 'string',
1195
            'char'              => 'string',
1196
            'nchar'             => 'string',
1197
            'date'              => 'date',
1198
            'timestamp'         => 'datetime',
1199
            'timestamptz'       => 'datetimetz',
1200
            'float'             => 'float',
1201
            'binary_float'      => 'float',
1202
            'binary_double'     => 'float',
1203
            'long'              => 'string',
1204
            'clob'              => 'text',
1205
            'nclob'             => 'text',
1206
            'raw'               => 'binary',
1207
            'long raw'          => 'blob',
1208
            'rowid'             => 'string',
1209
            'urowid'            => 'string',
1210
            'blob'              => 'blob',
1211
        ];
1212
    }
1213
1214
    /**
1215
     * {@inheritDoc}
1216
     */
1217
    public function releaseSavePoint($savepoint)
1218
    {
1219
        return '';
1220
    }
1221
1222
    /**
1223
     * {@inheritDoc}
1224
     */
1225
    protected function getReservedKeywordsClass()
1226
    {
1227
        return Keywords\OracleKeywords::class;
1228
    }
1229
1230
    /**
1231
     * {@inheritDoc}
1232
     */
1233
    public function getBlobTypeDeclarationSQL(array $field)
1234
    {
1235
        return 'BLOB';
1236
    }
1237
1238
    /**
1239
     * {@inheritdoc}
1240
     */
1241
    public function quoteStringLiteral($str)
1242
    {
1243
        $str = str_replace('\\', '\\\\', $str); // Oracle requires backslashes to be escaped aswell.
1244
1245
        return parent::quoteStringLiteral($str);
1246
    }
1247
}
1248