Failed Conditions
Pull Request — master (#2766)
by mon
09:56
created

OraclePlatform::getDatabaseCondition()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 1
crap 3
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 7
    private function getDatabaseCondition(?string $database): string
372
    {
373 7
        if ($database !== null && $database !== '/') {
374 2
            $databaseIdentifier = $this->normalizeIdentifier($database);
375 2
            return $this->quoteStringLiteral($databaseIdentifier->getName());
376
        }
377
        else {
378 5
            return "(SELECT SYS_CONTEXT('userenv', 'current_schema') FROM DUAL)";
379
        }
380
    }
381
382
    /**
383
     * {@inheritDoc}
384
     */
385
    public function getListDatabasesSQL()
386
    {
387
        return 'SELECT username FROM all_users';
388
    }
389
390
    /**
391
     * {@inheritDoc}
392
     */
393 1
    public function getListSequencesSQL($database)
394
    {
395 1
        $database = $this->normalizeIdentifier($database);
396 1
        $database = $this->quoteStringLiteral($database->getName());
397
398
        return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
399 1
               "WHERE SEQUENCE_OWNER = " . $database;
400
    }
401
402
    /**
403
     * {@inheritDoc}
404
     */
405 11
    protected function _getCreateTableSQL($table, array $columns, array $options = [])
406
    {
407 11
        $indexes = isset($options['indexes']) ? $options['indexes'] : [];
408 11
        $options['indexes'] = [];
409 11
        $sql = parent::_getCreateTableSQL($table, $columns, $options);
410
411 11
        foreach ($columns as $name => $column) {
412 11
            if (isset($column['sequence'])) {
413
                $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

413
                /** @scrutinizer ignore-call */ 
414
                $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...
414
            }
415
416 11
            if (isset($column['autoincrement']) && $column['autoincrement'] ||
417 11
               (isset($column['autoinc']) && $column['autoinc'])) {
418 11
                $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table));
419
            }
420
        }
421
422 11
        if (isset($indexes) && ! empty($indexes)) {
423 3
            foreach ($indexes as $index) {
424 3
                $sql[] = $this->getCreateIndexSQL($index, $table);
425
            }
426
        }
427
428 11
        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 1
    private function getListIndexesSQL(?string $database, ?string $table): string
440
    {
441 1
        $databaseCondition = $this->getDatabaseCondition($database);
442 1
        $tableCondition = '';
443 1
        if ($table !== null) {
444 1
            $tableIdentifier = $this->normalizeIdentifier($table);
445 1
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
446 1
            $tableCondition = " AND ind_col.table_name = $quotedTableIdentifier";
447
        }
448
        return
449
<<<SQL
450
          SELECT ind_col.table_name as table_name,
451
                 ind_col.index_name AS name,
452
                 ind.index_type AS type,
453
                 decode(ind.uniqueness, 'NONUNIQUE', 0, 'UNIQUE', 1) AS is_unique,
454
                 ind_col.column_name AS column_name,
455
                 ind_col.column_position AS column_pos,
456
                 con.constraint_type AS is_primary
457
            FROM all_ind_columns ind_col
458
       LEFT JOIN all_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name
459
       LEFT JOIN all_constraints con ON con.owner = ind_col.index_owner AND con.index_name = ind_col.index_name
460 1
           WHERE ind_col.index_owner = $databaseCondition$tableCondition
461
        ORDER BY ind_col.table_name, ind_col.index_name, ind_col.column_position
462
SQL;
463
    }
464
465
    /**
466
     * {@inheritDoc}
467
     *
468
     * @license New BSD License
469
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
470
     */
471 1
    public function getListTableIndexesSQL($table, $currentDatabase = null)
472
    {
473 1
        return $this->getListIndexesSQL($currentDatabase, $table);
474
    }
475
476
    /**
477
     * Returns the SQL for a list of all indexes in the database.
478
     *
479
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
480
     *
481
     * @return string
482
     */
483
    public function getListAllIndexesSQL(?string $database = null): string
484
    {
485
        return $this->getListIndexesSQL($database, null);
486
    }
487
488
    /**
489
     * {@inheritDoc}
490
     */
491
    public function getListTablesSQL()
492
    {
493
        return 'SELECT * FROM sys.user_tables';
494
    }
495
496
    /**
497
     * {@inheritDoc}
498
     */
499
    public function getListViewsSQL($database)
500
    {
501
        return 'SELECT view_name, text FROM sys.user_views';
502
    }
503
504
    /**
505
     * {@inheritDoc}
506
     */
507
    public function getCreateViewSQL($name, $sql)
508
    {
509
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
510
    }
511
512
    /**
513
     * {@inheritDoc}
514
     */
515
    public function getDropViewSQL($name)
516
    {
517
        return 'DROP VIEW '. $name;
518
    }
519
520
    /**
521
     * @param string  $name
522
     * @param string  $table
523
     * @param integer $start
524
     *
525
     * @return array
526
     */
527 3
    public function getCreateAutoincrementSql($name, $table, $start = 1)
528
    {
529 3
        $tableIdentifier = $this->normalizeIdentifier($table);
530 3
        $quotedTableName = $tableIdentifier->getQuotedName($this);
531 3
        $unquotedTableName = $tableIdentifier->getName();
532
533 3
        $nameIdentifier = $this->normalizeIdentifier($name);
534 3
        $quotedName = $nameIdentifier->getQuotedName($this);
535 3
        $unquotedName = $nameIdentifier->getName();
536
537 3
        $sql = [];
538
539 3
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
540
541 3
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
542
543 3
        $sql[] = 'DECLARE
544
  constraints_Count NUMBER;
545
BEGIN
546 3
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
547
  IF constraints_Count = 0 OR constraints_Count = \'\' THEN
548 3
    EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\';
549
  END IF;
550
END;';
551
552 3
        $sequenceName = $this->getIdentitySequenceName(
553 3
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
554 3
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName
555
        );
556 3
        $sequence = new Sequence($sequenceName, $start);
557 3
        $sql[] = $this->getCreateSequenceSQL($sequence);
558
559 3
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
560
   BEFORE INSERT
561 3
   ON ' . $quotedTableName . '
562
   FOR EACH ROW
563
DECLARE
564
   last_Sequence NUMBER;
565
   last_InsertID NUMBER;
566
BEGIN
567 3
   SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
568 3
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN
569 3
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
570
   ELSE
571
      SELECT NVL(Last_Number, 0) INTO last_Sequence
572
        FROM User_Sequences
573 3
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
574 3
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
575
      WHILE (last_InsertID > last_Sequence) LOOP
576 3
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
577
      END LOOP;
578
   END IF;
579
END;';
580
581 3
        return $sql;
582
    }
583
584
    /**
585
     * Returns the SQL statements to drop the autoincrement for the given table name.
586
     *
587
     * @param string $table The table name to drop the autoincrement for.
588
     *
589
     * @return array
590
     */
591 3
    public function getDropAutoincrementSql($table)
592
    {
593 3
        $table = $this->normalizeIdentifier($table);
594 3
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
595 3
        $identitySequenceName = $this->getIdentitySequenceName(
596 3
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
597 3
            ''
598
        );
599
600
        return [
601 3
            'DROP TRIGGER ' . $autoincrementIdentifierName,
602 3
            $this->getDropSequenceSQL($identitySequenceName),
603 3
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
604
        ];
605
    }
606
607
    /**
608
     * Normalizes the given identifier.
609
     *
610
     * Uppercases the given identifier if it is not quoted by intention
611
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
612
     *
613
     * @param string $name The identifier to normalize.
614
     *
615
     * @return Identifier The normalized identifier.
616
     */
617 16
    private function normalizeIdentifier($name)
618
    {
619 16
        $identifier = new Identifier($name);
620
621 16
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
622
    }
623
624
    /**
625
     * Returns the autoincrement primary key identifier name for the given table identifier.
626
     *
627
     * Quotes the autoincrement primary key identifier name
628
     * if the given table name is quoted by intention.
629
     *
630
     * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
631
     *
632
     * @return string
633
     */
634 6
    private function getAutoincrementIdentifierName(Identifier $table)
635
    {
636 6
        $identifierName = $table->getName() . '_AI_PK';
637
638 6
        return $table->isQuoted()
639 2
            ? $this->quoteSingleIdentifier($identifierName)
640 6
            : $identifierName;
641
    }
642
643
    /**
644
     * Returns the SQL for a list of foreign keys in the database.
645
     *
646
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
647
     * @param string $table    The table. If left NULL, the SQL will return all the indexes in the database.
648
     *
649
     * @return string
650
     */
651 1
    private function getListForeignKeysSQL(?string $database, ?string $table): string
652
    {
653 1
        $databaseCondition = $this->getDatabaseCondition($database);
654 1
        $tableCondition = '';
655 1
        if ($table !== null) {
656 1
            $tableIdentifier = $this->normalizeIdentifier($table);
657 1
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
658 1
            $tableCondition = " AND cols.table_name = $quotedTableIdentifier";
659
        }
660
        return
661
<<<SQL
662
          SELECT cols.table_name,
663
                 alc.constraint_name,
664
                 alc.DELETE_RULE,
665
                 cols.column_name "local_column",
666
                 cols.position,
667
                 r_cols.table_name "references_table",
668
                 r_cols.column_name "foreign_column"
669
            FROM all_cons_columns cols
670
       LEFT JOIN all_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name
671
       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
672 1
           WHERE cols.owner = $databaseCondition AND alc.constraint_type = 'R'$tableCondition
673
        ORDER BY cols.table_name, cols.constraint_name, cols.position
674
SQL;
675
    }
676
677
    /**
678
     * {@inheritDoc}
679
     */
680 1
    public function getListTableForeignKeysSQL($table)
681
    {
682 1
        return $this->getListForeignKeysSQL(null, $table);
683
    }
684
685
    /**
686
     * Returns the SQL for a list of all foreign keys in the database.
687
     *
688
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
689
     *
690
     * @return string
691
     */
692
    public function getListAllForeignKeysSQL(?string $database = null): string
693
    {
694
        return $this->getListForeignKeysSQL($database, null);
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 1
    public function getListTableConstraintsSQL($table)
701
    {
702 1
        $table = $this->normalizeIdentifier($table);
703 1
        $table = $this->quoteStringLiteral($table->getName());
704
705 1
        return "SELECT * FROM user_constraints WHERE table_name = " . $table;
706
    }
707
708
    /**
709
     * Returns the SQL for a list of columns in the database.
710
     *
711
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
712
     * @param string $table    The table. If left NULL, the SQL will return all the indexes in the database.
713
     *
714
     * @return string
715
     */
716 5
    private function getListColumnsSQL(?string $database, ?string $table): string
717
    {
718 5
        $databaseCondition = $this->getDatabaseCondition($database);
719 5
        $tableCondition = '';
720 5
        if ($table !== null) {
721 5
            $tableIdentifier = $this->normalizeIdentifier($table);
722 5
            $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName());
723 5
            $tableCondition = " AND c.table_name = $quotedTableIdentifier";
724
        }
725
        return
726
<<<SQL
727
          SELECT c.*,
728
                 d.comments AS comments
729
            FROM all_tab_columns c
730
       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
731 5
           WHERE c.owner = $databaseCondition$tableCondition
732
        ORDER BY c.table_name, c.column_id
733
SQL;
734
    }
735
736
    /**
737
     * {@inheritDoc}
738
     */
739 5
    public function getListTableColumnsSQL($table, $database = null)
740
    {
741 5
        return $this->getListColumnsSQL($database, $table);
742
    }
743
744
    /**
745
     * Returns the SQL for a list of all columns in the database.
746
     *
747
     * @param string $database The database schema. If NULL or '/', the currently active schema will be queried.
748
     *
749
     * @return string
750
     */
751
    public function getListAllColumnsSQL(?string $database = null): string
752
    {
753
        return $this->getListColumnsSQL($database, null);
754
    }
755
756
    /**
757
     * {@inheritDoc}
758
     */
759 3
    public function getDropSequenceSQL($sequence)
760
    {
761 3
        if ($sequence instanceof Sequence) {
762
            $sequence = $sequence->getQuotedName($this);
763
        }
764
765 3
        return 'DROP SEQUENCE ' . $sequence;
766
    }
767
768
    /**
769
     * {@inheritDoc}
770
     */
771 2
    public function getDropForeignKeySQL($foreignKey, $table)
772
    {
773 2
        if (! $foreignKey instanceof ForeignKeyConstraint) {
774 1
            $foreignKey = new Identifier($foreignKey);
775
        }
776
777 2
        if (! $table instanceof Table) {
778 2
            $table = new Identifier($table);
779
        }
780
781 2
        $foreignKey = $foreignKey->getQuotedName($this);
782 2
        $table = $table->getQuotedName($this);
783
784 2
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
785
    }
786
787
    /**
788
     * {@inheritdoc}
789
     */
790 10
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
791
    {
792 10
        $referentialAction = null;
793
794 10
        if ($foreignKey->hasOption('onDelete')) {
795 4
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
796
        }
797
798 10
        return $referentialAction ? ' ON DELETE ' . $referentialAction : '';
799
    }
800
801
    /**
802
     * {@inheritdoc}
803
     */
804 10
    public function getForeignKeyReferentialActionSQL($action)
805
    {
806 10
        $action = strtoupper($action);
807
808
        switch ($action) {
809 10
            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...
810 8
            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...
811
                // NO ACTION cannot be declared explicitly,
812
                // therefore returning empty string to indicate to OMIT the referential clause.
813 4
                return '';
814
815 6
            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...
816 3
            case 'SET NULL':
817 5
                return $action;
818
819
            default:
820
                // SET DEFAULT is not supported, throw exception instead.
821 1
                throw new \InvalidArgumentException('Invalid foreign key action: ' . $action);
822
        }
823
    }
824
825
    /**
826
     * {@inheritDoc}
827
     */
828 1
    public function getDropDatabaseSQL($database)
829
    {
830 1
        return 'DROP USER ' . $database . ' CASCADE';
831
    }
832
833
    /**
834
     * {@inheritDoc}
835
     */
836 16
    public function getAlterTableSQL(TableDiff $diff)
837
    {
838 16
        $sql = [];
839 16
        $commentsSQL = [];
840 16
        $columnSql = [];
841
842 16
        $fields = [];
843
844 16
        foreach ($diff->addedColumns as $column) {
845 4
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
846
                continue;
847
            }
848
849 4
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
850 4
            if ($comment = $this->getColumnComment($column)) {
851 1
                $commentsSQL[] = $this->getCommentOnColumnSQL(
852 1
                    $diff->getName($this)->getQuotedName($this),
853 1
                    $column->getQuotedName($this),
854 4
                    $comment
855
                );
856
            }
857
        }
858
859 16
        if (count($fields)) {
860 4
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')';
861
        }
862
863 16
        $fields = [];
864 16
        foreach ($diff->changedColumns as $columnDiff) {
865 9
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
866
                continue;
867
            }
868
869
            /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
870 9
            $column = $columnDiff->column;
871
872
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
873
            // Oracle only supports binary type columns with variable length.
874
            // Avoids unnecessary table alteration statements.
875 9
            if ($column->getType() instanceof BinaryType &&
876 9
                $columnDiff->hasChanged('fixed') &&
877 9
                count($columnDiff->changedProperties) === 1
878
            ) {
879 1
                continue;
880
            }
881
882 8
            $columnHasChangedComment = $columnDiff->hasChanged('comment');
883
884
            /**
885
             * Do not add query part if only comment has changed
886
             */
887 8
            if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
888 6
                $columnInfo = $column->toArray();
889
890 6
                if ( ! $columnDiff->hasChanged('notnull')) {
891 4
                    unset($columnInfo['notnull']);
892
                }
893
894 6
                $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo);
895
            }
896
897 8
            if ($columnHasChangedComment) {
898 2
                $commentsSQL[] = $this->getCommentOnColumnSQL(
899 2
                    $diff->getName($this)->getQuotedName($this),
900 2
                    $column->getQuotedName($this),
901 8
                    $this->getColumnComment($column)
902
                );
903
            }
904
        }
905
906 16
        if (count($fields)) {
907 6
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')';
908
        }
909
910 16
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
911 4
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
912
                continue;
913
            }
914
915 4
            $oldColumnName = new Identifier($oldColumnName);
916
917 4
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
918 4
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this);
919
        }
920
921 16
        $fields = [];
922 16
        foreach ($diff->removedColumns as $column) {
923 3
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
924
                continue;
925
            }
926
927 3
            $fields[] = $column->getQuotedName($this);
928
        }
929
930 16
        if (count($fields)) {
931 3
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields).')';
932
        }
933
934 16
        $tableSql = [];
935
936 16
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
937 16
            $sql = array_merge($sql, $commentsSQL);
938
939 16
            if ($diff->newName !== false) {
940 2
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
941
            }
942
943 16
            $sql = array_merge(
944 16
                $this->getPreAlterTableIndexForeignKeySQL($diff),
945 16
                $sql,
946 16
                $this->getPostAlterTableIndexForeignKeySQL($diff)
947
            );
948
        }
949
950 16
        return array_merge($sql, $tableSql, $columnSql);
951
    }
952
953
    /**
954
     * {@inheritdoc}
955
     */
956 19
    public function getColumnDeclarationSQL($name, array $field)
957
    {
958 19
        if (isset($field['columnDefinition'])) {
959 1
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
960
        } else {
961 18
            $default = $this->getDefaultValueDeclarationSQL($field);
962
963 18
            $notnull = '';
964
965 18
            if (isset($field['notnull'])) {
966 16
                $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL';
967
            }
968
969 18
            $unique = (isset($field['unique']) && $field['unique']) ?
970 18
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
971
972 18
            $check = (isset($field['check']) && $field['check']) ?
973 18
                ' ' . $field['check'] : '';
974
975 18
            $typeDecl = $field['type']->getSQLDeclaration($field, $this);
976 18
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
977
        }
978
979 19
        return $name . ' ' . $columnDef;
980
    }
981
982
    /**
983
     * {@inheritdoc}
984
     */
985 5
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
986
    {
987 5
        if (strpos($tableName, '.') !== false) {
988 2
            list($schema) = explode('.', $tableName);
989 2
            $oldIndexName = $schema . '.' . $oldIndexName;
990
        }
991
992 5
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
993
    }
994
995
    /**
996
     * {@inheritDoc}
997
     */
998
    public function prefersSequences()
999
    {
1000
        return true;
1001
    }
1002
1003
    /**
1004
     * {@inheritdoc}
1005
     */
1006 1
    public function usesSequenceEmulatedIdentityColumns()
1007
    {
1008 1
        return true;
1009
    }
1010
1011
    /**
1012
     * {@inheritdoc}
1013
     */
1014 7
    public function getIdentitySequenceName($tableName, $columnName)
1015
    {
1016 7
        $table = new Identifier($tableName);
1017
1018
        // No usage of column name to preserve BC compatibility with <2.5
1019 7
        $identitySequenceName = $table->getName() . '_SEQ';
1020
1021 7
        if ($table->isQuoted()) {
1022 3
            $identitySequenceName = '"' . $identitySequenceName . '"';
1023
        }
1024
1025 7
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
1026
1027 7
        return $identitySequenceIdentifier->getQuotedName($this);
1028
    }
1029
1030
    /**
1031
     * {@inheritDoc}
1032
     */
1033 12
    public function supportsCommentOnStatement()
1034
    {
1035 12
        return true;
1036
    }
1037
1038
    /**
1039
     * {@inheritDoc}
1040
     */
1041 2
    public function getName()
1042
    {
1043 2
        return 'oracle';
1044
    }
1045
1046
    /**
1047
     * {@inheritDoc}
1048
     */
1049 6
    protected function doModifyLimitQuery($query, $limit, $offset = null)
1050
    {
1051 6
        if ($limit === null && $offset === null) {
1052
            return $query;
1053
        }
1054
1055 6
        if (preg_match('/^\s*SELECT/i', $query)) {
1056 6
            if (!preg_match('/\sFROM\s/i', $query)) {
1057
                $query .= " FROM dual";
1058
            }
1059
1060 6
            $columns = ['a.*'];
1061
1062 6
            if ($offset > 0) {
1063 2
                $columns[] = 'ROWNUM AS doctrine_rownum';
1064
            }
1065
1066 6
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1067
1068 6
            if ($limit !== null) {
1069 5
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1070
            }
1071
1072 6
            if ($offset > 0) {
1073 2
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1074
            }
1075
        }
1076
1077 6
        return $query;
1078
    }
1079
1080
    /**
1081
     * {@inheritDoc}
1082
     *
1083
     * Oracle returns all column names in SQL result sets in uppercase.
1084
     */
1085
    public function getSQLResultCasing($column)
1086
    {
1087
        return strtoupper($column);
1088
    }
1089
1090
    /**
1091
     * {@inheritDoc}
1092
     */
1093
    public function getCreateTemporaryTableSnippetSQL()
1094
    {
1095
        return "CREATE GLOBAL TEMPORARY TABLE";
1096
    }
1097
1098
    /**
1099
     * {@inheritDoc}
1100
     */
1101
    public function getDateTimeTzFormatString()
1102
    {
1103
        return 'Y-m-d H:i:sP';
1104
    }
1105
1106
    /**
1107
     * {@inheritDoc}
1108
     */
1109
    public function getDateFormatString()
1110
    {
1111
        return 'Y-m-d 00:00:00';
1112
    }
1113
1114
    /**
1115
     * {@inheritDoc}
1116
     */
1117
    public function getTimeFormatString()
1118
    {
1119
        return '1900-01-01 H:i:s';
1120
    }
1121
1122
    /**
1123
     * {@inheritDoc}
1124
     */
1125
    public function fixSchemaElementName($schemaElementName)
1126
    {
1127
        if (strlen($schemaElementName) > 30) {
1128
            // Trim it
1129
            return substr($schemaElementName, 0, 30);
1130
        }
1131
1132
        return $schemaElementName;
1133
    }
1134
1135
    /**
1136
     * {@inheritDoc}
1137
     */
1138
    public function getMaxIdentifierLength()
1139
    {
1140
        return 30;
1141
    }
1142
1143
    /**
1144
     * {@inheritDoc}
1145
     */
1146
    public function supportsSequences()
1147
    {
1148
        return true;
1149
    }
1150
1151
    /**
1152
     * {@inheritDoc}
1153
     */
1154
    public function supportsForeignKeyOnUpdate()
1155
    {
1156
        return false;
1157
    }
1158
1159
    /**
1160
     * {@inheritDoc}
1161
     */
1162
    public function supportsReleaseSavepoints()
1163
    {
1164
        return false;
1165
    }
1166
1167
    /**
1168
     * {@inheritDoc}
1169
     */
1170 1
    public function getTruncateTableSQL($tableName, $cascade = false)
1171
    {
1172 1
        $tableIdentifier = new Identifier($tableName);
1173
1174 1
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1175
    }
1176
1177
    /**
1178
     * {@inheritDoc}
1179
     */
1180
    public function getDummySelectSQL()
1181
    {
1182
        return 'SELECT 1 FROM DUAL';
1183
    }
1184
1185
    /**
1186
     * {@inheritDoc}
1187
     */
1188 5
    protected function initializeDoctrineTypeMappings()
1189
    {
1190 5
        $this->doctrineTypeMapping = [
1191
            'integer'           => 'integer',
1192
            'number'            => 'integer',
1193
            'pls_integer'       => 'boolean',
1194
            'binary_integer'    => 'boolean',
1195
            'varchar'           => 'string',
1196
            'varchar2'          => 'string',
1197
            'nvarchar2'         => 'string',
1198
            'char'              => 'string',
1199
            'nchar'             => 'string',
1200
            'date'              => 'date',
1201
            'timestamp'         => 'datetime',
1202
            'timestamptz'       => 'datetimetz',
1203
            'float'             => 'float',
1204
            'binary_float'      => 'float',
1205
            'binary_double'     => 'float',
1206
            'long'              => 'string',
1207
            'clob'              => 'text',
1208
            'nclob'             => 'text',
1209
            'raw'               => 'binary',
1210
            'long raw'          => 'blob',
1211
            'rowid'             => 'string',
1212
            'urowid'            => 'string',
1213
            'blob'              => 'blob',
1214
        ];
1215 5
    }
1216
1217
    /**
1218
     * {@inheritDoc}
1219
     */
1220
    public function releaseSavePoint($savepoint)
1221
    {
1222
        return '';
1223
    }
1224
1225
    /**
1226
     * {@inheritDoc}
1227
     */
1228 49
    protected function getReservedKeywordsClass()
1229
    {
1230 49
        return Keywords\OracleKeywords::class;
1231
    }
1232
1233
    /**
1234
     * {@inheritDoc}
1235
     */
1236 1
    public function getBlobTypeDeclarationSQL(array $field)
1237
    {
1238 1
        return 'BLOB';
1239
    }
1240
1241
    /**
1242
     * {@inheritdoc}
1243
     */
1244 17
    public function quoteStringLiteral($str)
1245
    {
1246 17
        $str = str_replace('\\', '\\\\', $str); // Oracle requires backslashes to be escaped aswell.
1247
1248 17
        return parent::quoteStringLiteral($str);
1249
    }
1250
}
1251