Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\Platforms;
4
 
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Exception;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Identifier;
9
use Doctrine\DBAL\Schema\Index;
10
use Doctrine\DBAL\Schema\OracleSchemaManager;
11
use Doctrine\DBAL\Schema\Sequence;
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Schema\TableDiff;
14
use Doctrine\DBAL\TransactionIsolationLevel;
15
use Doctrine\DBAL\Types\BinaryType;
16
use Doctrine\Deprecations\Deprecation;
17
use InvalidArgumentException;
18
 
19
use function array_merge;
20
use function count;
21
use function explode;
22
use function func_get_arg;
23
use function func_num_args;
24
use function implode;
25
use function preg_match;
26
use function sprintf;
27
use function strlen;
28
use function strpos;
29
use function strtoupper;
30
use function substr;
31
 
32
/**
33
 * OraclePlatform.
34
 */
35
class OraclePlatform extends AbstractPlatform
36
{
37
    /**
38
     * Assertion for Oracle identifiers.
39
     *
40
     * @deprecated
41
     *
42
     * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm
43
     *
44
     * @param string $identifier
45
     *
46
     * @return void
47
     *
48
     * @throws Exception
49
     */
50
    public static function assertValidIdentifier($identifier)
51
    {
52
        if (preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier) === 0) {
53
            throw new Exception('Invalid Oracle identifier');
54
        }
55
    }
56
 
57
    /**
58
     * {@inheritDoc}
59
     */
60
    public function getSubstringExpression($string, $start, $length = null)
61
    {
62
        if ($length !== null) {
63
            return sprintf('SUBSTR(%s, %d, %d)', $string, $start, $length);
64
        }
65
 
66
        return sprintf('SUBSTR(%s, %d)', $string, $start);
67
    }
68
 
69
    /**
70
     * @deprecated Generate dates within the application.
71
     *
72
     * @param string $type
73
     *
74
     * @return string
75
     */
76
    public function getNowExpression($type = 'timestamp')
77
    {
78
        Deprecation::trigger(
79
            'doctrine/dbal',
80
            'https://github.com/doctrine/dbal/pull/4753',
81
            'OraclePlatform::getNowExpression() is deprecated. Generate dates within the application.',
82
        );
83
 
84
        switch ($type) {
85
            case 'date':
86
            case 'time':
87
            case 'timestamp':
88
            default:
89
                return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')';
90
        }
91
    }
92
 
93
    /**
94
     * {@inheritDoc}
95
     */
96
    public function getLocateExpression($str, $substr, $startPos = false)
97
    {
98
        if ($startPos === false) {
99
            return 'INSTR(' . $str . ', ' . $substr . ')';
100
        }
101
 
102
        return 'INSTR(' . $str . ', ' . $substr . ', ' . $startPos . ')';
103
    }
104
 
105
    /**
106
     * {@inheritdoc}
107
     */
108
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
109
    {
110
        switch ($unit) {
111
            case DateIntervalUnit::MONTH:
112
            case DateIntervalUnit::QUARTER:
113
            case DateIntervalUnit::YEAR:
114
                switch ($unit) {
115
                    case DateIntervalUnit::QUARTER:
116
                        $interval *= 3;
117
                        break;
118
 
119
                    case DateIntervalUnit::YEAR:
120
                        $interval *= 12;
121
                        break;
122
                }
123
 
124
                return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')';
125
 
126
            default:
127
                $calculationClause = '';
128
 
129
                switch ($unit) {
130
                    case DateIntervalUnit::SECOND:
131
                        $calculationClause = '/24/60/60';
132
                        break;
133
 
134
                    case DateIntervalUnit::MINUTE:
135
                        $calculationClause = '/24/60';
136
                        break;
137
 
138
                    case DateIntervalUnit::HOUR:
139
                        $calculationClause = '/24';
140
                        break;
141
 
142
                    case DateIntervalUnit::WEEK:
143
                        $calculationClause = '*7';
144
                        break;
145
                }
146
 
147
                return '(' . $date . $operator . $interval . $calculationClause . ')';
148
        }
149
    }
150
 
151
    /**
152
     * {@inheritDoc}
153
     */
154
    public function getDateDiffExpression($date1, $date2)
155
    {
156
        return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2);
157
    }
158
 
159
    /**
160
     * {@inheritDoc}
161
     */
162
    public function getBitAndComparisonExpression($value1, $value2)
163
    {
164
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
165
    }
166
 
167
    public function getCurrentDatabaseExpression(): string
168
    {
169
        return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')";
170
    }
171
 
172
    /**
173
     * {@inheritDoc}
174
     */
175
    public function getBitOrComparisonExpression($value1, $value2)
176
    {
177
        return '(' . $value1 . '-' .
178
                $this->getBitAndComparisonExpression($value1, $value2)
179
                . '+' . $value2 . ')';
180
    }
181
 
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function getCreatePrimaryKeySQL(Index $index, $table): string
186
    {
187
        if ($table instanceof Table) {
188
            Deprecation::trigger(
189
                'doctrine/dbal',
190
                'https://github.com/doctrine/dbal/issues/4798',
191
                'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.',
192
                __METHOD__,
193
            );
194
 
195
            $table = $table->getQuotedName($this);
196
        }
197
 
198
        return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this)
199
            . ' PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')';
200
    }
201
 
202
    /**
203
     * {@inheritDoc}
204
     *
205
     * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
206
     * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
207
     * in {@see listSequences()}
208
     */
209
    public function getCreateSequenceSQL(Sequence $sequence)
210
    {
211
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
212
               ' START WITH ' . $sequence->getInitialValue() .
213
               ' MINVALUE ' . $sequence->getInitialValue() .
214
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
215
               $this->getSequenceCacheSQL($sequence);
216
    }
217
 
218
    /**
219
     * {@inheritDoc}
220
     */
221
    public function getAlterSequenceSQL(Sequence $sequence)
222
    {
223
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
224
               ' INCREMENT BY ' . $sequence->getAllocationSize()
225
               . $this->getSequenceCacheSQL($sequence);
226
    }
227
 
228
    /**
229
     * Cache definition for sequences
230
     */
231
    private function getSequenceCacheSQL(Sequence $sequence): string
232
    {
233
        if ($sequence->getCache() === 0) {
234
            return ' NOCACHE';
235
        }
236
 
237
        if ($sequence->getCache() === 1) {
238
            return ' NOCACHE';
239
        }
240
 
241
        if ($sequence->getCache() > 1) {
242
            return ' CACHE ' . $sequence->getCache();
243
        }
244
 
245
        return '';
246
    }
247
 
248
    /**
249
     * {@inheritDoc}
250
     */
251
    public function getSequenceNextValSQL($sequence)
252
    {
253
        return 'SELECT ' . $sequence . '.nextval FROM DUAL';
254
    }
255
 
256
    /**
257
     * {@inheritDoc}
258
     */
259
    public function getSetTransactionIsolationSQL($level)
260
    {
261
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
262
    }
263
 
264
    /**
265
     * {@inheritDoc}
266
     */
267
    protected function _getTransactionIsolationLevelSQL($level)
268
    {
269
        switch ($level) {
270
            case TransactionIsolationLevel::READ_UNCOMMITTED:
271
                return 'READ UNCOMMITTED';
272
 
273
            case TransactionIsolationLevel::READ_COMMITTED:
274
                return 'READ COMMITTED';
275
 
276
            case TransactionIsolationLevel::REPEATABLE_READ:
277
            case TransactionIsolationLevel::SERIALIZABLE:
278
                return 'SERIALIZABLE';
279
 
280
            default:
281
                return parent::_getTransactionIsolationLevelSQL($level);
282
        }
283
    }
284
 
285
    /**
286
     * {@inheritDoc}
287
     */
288
    public function getBooleanTypeDeclarationSQL(array $column)
289
    {
290
        return 'NUMBER(1)';
291
    }
292
 
293
    /**
294
     * {@inheritDoc}
295
     */
296
    public function getIntegerTypeDeclarationSQL(array $column)
297
    {
298
        return 'NUMBER(10)';
299
    }
300
 
301
    /**
302
     * {@inheritDoc}
303
     */
304
    public function getBigIntTypeDeclarationSQL(array $column)
305
    {
306
        return 'NUMBER(20)';
307
    }
308
 
309
    /**
310
     * {@inheritDoc}
311
     */
312
    public function getSmallIntTypeDeclarationSQL(array $column)
313
    {
314
        return 'NUMBER(5)';
315
    }
316
 
317
    /**
318
     * {@inheritDoc}
319
     */
320
    public function getDateTimeTypeDeclarationSQL(array $column)
321
    {
322
        return 'TIMESTAMP(0)';
323
    }
324
 
325
    /**
326
     * {@inheritDoc}
327
     */
328
    public function getDateTimeTzTypeDeclarationSQL(array $column)
329
    {
330
        return 'TIMESTAMP(0) WITH TIME ZONE';
331
    }
332
 
333
    /**
334
     * {@inheritDoc}
335
     */
336
    public function getDateTypeDeclarationSQL(array $column)
337
    {
338
        return 'DATE';
339
    }
340
 
341
    /**
342
     * {@inheritDoc}
343
     */
344
    public function getTimeTypeDeclarationSQL(array $column)
345
    {
346
        return 'DATE';
347
    }
348
 
349
    /**
350
     * {@inheritDoc}
351
     */
352
    protected function _getCommonIntegerTypeDeclarationSQL(array $column)
353
    {
354
        return '';
355
    }
356
 
357
    /**
358
     * {@inheritDoc}
359
     */
360
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/)
361
    {
362
        if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) {
363
            Deprecation::trigger(
364
                'doctrine/dbal',
365
                'https://github.com/doctrine/dbal/issues/3263',
366
                'Relying on the default string column length on Oracle is deprecated'
367
                    . ', specify the length explicitly.',
368
            );
369
        }
370
 
371
        return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
372
                : ($length > 0 ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
373
    }
374
 
375
    /**
376
     * {@inheritdoc}
377
     */
378
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/)
379
    {
380
        if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) {
381
            Deprecation::trigger(
382
                'doctrine/dbal',
383
                'https://github.com/doctrine/dbal/issues/3263',
384
                'Relying on the default binary column length on Oracle is deprecated'
385
                . ', specify the length explicitly.',
386
            );
387
        }
388
 
389
        return 'RAW(' . ($length > 0 ? $length : $this->getBinaryMaxLength()) . ')';
390
    }
391
 
392
    /**
393
     * {@inheritdoc}
394
     *
395
     * @deprecated
396
     */
397
    public function getBinaryMaxLength()
398
    {
399
        Deprecation::triggerIfCalledFromOutside(
400
            'doctrine/dbal',
401
            'https://github.com/doctrine/dbal/issues/3263',
402
            'OraclePlatform::getBinaryMaxLength() is deprecated.',
403
        );
404
 
405
        return 2000;
406
    }
407
 
408
    /**
409
     * {@inheritDoc}
410
     */
411
    public function getClobTypeDeclarationSQL(array $column)
412
    {
413
        return 'CLOB';
414
    }
415
 
416
    /**
417
     * {@inheritDoc}
418
     *
419
     * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
420
     */
421
    public function getListDatabasesSQL()
422
    {
423
        return 'SELECT username FROM all_users';
424
    }
425
 
426
    /**
427
     * {@inheritDoc}
428
     *
429
     * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
430
     */
431
    public function getListSequencesSQL($database)
432
    {
433
        $database = $this->normalizeIdentifier($database);
434
        $database = $this->quoteStringLiteral($database->getName());
435
 
436
        return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' .
437
               'WHERE SEQUENCE_OWNER = ' . $database;
438
    }
439
 
440
    /**
441
     * {@inheritDoc}
442
     */
443
    protected function _getCreateTableSQL($name, array $columns, array $options = [])
444
    {
445
        $indexes            = $options['indexes'] ?? [];
446
        $options['indexes'] = [];
447
        $sql                = parent::_getCreateTableSQL($name, $columns, $options);
448
 
449
        foreach ($columns as $columnName => $column) {
450
            if (isset($column['sequence'])) {
451
                $sql[] = $this->getCreateSequenceSQL($column['sequence']);
452
            }
453
 
454
            if (
455
                ! isset($column['autoincrement']) || ! $column['autoincrement'] &&
456
                (! isset($column['autoinc']) || ! $column['autoinc'])
457
            ) {
458
                continue;
459
            }
460
 
461
            $sql = array_merge($sql, $this->getCreateAutoincrementSql($columnName, $name));
462
        }
463
 
464
        foreach ($indexes as $index) {
465
            $sql[] = $this->getCreateIndexSQL($index, $name);
466
        }
467
 
468
        return $sql;
469
    }
470
 
471
    /**
472
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
473
     *
474
     * {@inheritDoc}
475
     *
476
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
477
     */
478
    public function getListTableIndexesSQL($table, $database = null)
479
    {
480
        $table = $this->normalizeIdentifier($table);
481
        $table = $this->quoteStringLiteral($table->getName());
482
 
483
        return "SELECT uind_col.index_name AS name,
484
                       (
485
                           SELECT uind.index_type
486
                           FROM   user_indexes uind
487
                           WHERE  uind.index_name = uind_col.index_name
488
                       ) AS type,
489
                       decode(
490
                           (
491
                               SELECT uind.uniqueness
492
                               FROM   user_indexes uind
493
                               WHERE  uind.index_name = uind_col.index_name
494
                           ),
495
                           'NONUNIQUE',
496
                           0,
497
                           'UNIQUE',
498
                           1
499
                       ) AS is_unique,
500
                       uind_col.column_name AS column_name,
501
                       uind_col.column_position AS column_pos,
502
                       (
503
                           SELECT ucon.constraint_type
504
                           FROM   user_constraints ucon
505
                           WHERE  ucon.index_name = uind_col.index_name
506
                             AND  ucon.table_name = uind_col.table_name
507
                       ) AS is_primary
508
             FROM      user_ind_columns uind_col
509
             WHERE     uind_col.table_name = " . $table . '
510
             ORDER BY  uind_col.column_position ASC';
511
    }
512
 
513
    /**
514
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
515
     *
516
     * {@inheritDoc}
517
     */
518
    public function getListTablesSQL()
519
    {
520
        return 'SELECT * FROM sys.user_tables';
521
    }
522
 
523
    /**
524
     * {@inheritDoc}
525
     *
526
     * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy.
527
     */
528
    public function getListViewsSQL($database)
529
    {
530
        return 'SELECT view_name, text FROM sys.user_views';
531
    }
532
 
533
    /**
534
     * @internal The method should be only used from within the OraclePlatform class hierarchy.
535
     *
536
     * @param string $name
537
     * @param string $table
538
     * @param int    $start
539
     *
540
     * @return string[]
541
     */
542
    public function getCreateAutoincrementSql($name, $table, $start = 1)
543
    {
544
        $tableIdentifier   = $this->normalizeIdentifier($table);
545
        $quotedTableName   = $tableIdentifier->getQuotedName($this);
546
        $unquotedTableName = $tableIdentifier->getName();
547
 
548
        $nameIdentifier = $this->normalizeIdentifier($name);
549
        $quotedName     = $nameIdentifier->getQuotedName($this);
550
        $unquotedName   = $nameIdentifier->getName();
551
 
552
        $sql = [];
553
 
554
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
555
 
556
        $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true);
557
 
558
        $sql[] = "DECLARE
559
  constraints_Count NUMBER;
560
BEGIN
561
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count
562
    FROM USER_CONSTRAINTS
563
   WHERE TABLE_NAME = '" . $unquotedTableName . "'
564
     AND CONSTRAINT_TYPE = 'P';
565
  IF constraints_Count = 0 OR constraints_Count = '' THEN
566
    EXECUTE IMMEDIATE '" . $this->getCreateConstraintSQL($idx, $quotedTableName) . "';
567
  END IF;
568
END;";
569
 
570
        $sequenceName = $this->getIdentitySequenceName(
571
            $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName,
572
            $nameIdentifier->isQuoted() ? $quotedName : $unquotedName,
573
        );
574
        $sequence     = new Sequence($sequenceName, $start);
575
        $sql[]        = $this->getCreateSequenceSQL($sequence);
576
 
577
        $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
578
   BEFORE INSERT
579
   ON ' . $quotedTableName . '
580
   FOR EACH ROW
581
DECLARE
582
   last_Sequence NUMBER;
583
   last_InsertID NUMBER;
584
BEGIN
585
   IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN
586
      SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
587
   ELSE
588
      SELECT NVL(Last_Number, 0) INTO last_Sequence
589
        FROM User_Sequences
590
       WHERE Sequence_Name = \'' . $sequence->getName() . '\';
591
      SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
592
      WHILE (last_InsertID > last_Sequence) LOOP
593
         SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
594
      END LOOP;
595
      SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
596
   END IF;
597
END;';
598
 
599
        return $sql;
600
    }
601
 
602
    /**
603
     * @internal The method should be only used from within the OracleSchemaManager class hierarchy.
604
     *
605
     * Returns the SQL statements to drop the autoincrement for the given table name.
606
     *
607
     * @param string $table The table name to drop the autoincrement for.
608
     *
609
     * @return string[]
610
     */
611
    public function getDropAutoincrementSql($table)
612
    {
613
        $table                       = $this->normalizeIdentifier($table);
614
        $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
615
        $identitySequenceName        = $this->getIdentitySequenceName(
616
            $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(),
617
            '',
618
        );
619
 
620
        return [
621
            'DROP TRIGGER ' . $autoincrementIdentifierName,
622
            $this->getDropSequenceSQL($identitySequenceName),
623
            $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)),
624
        ];
625
    }
626
 
627
    /**
628
     * Normalizes the given identifier.
629
     *
630
     * Uppercases the given identifier if it is not quoted by intention
631
     * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
632
     *
633
     * @param string $name The identifier to normalize.
634
     */
635
    private function normalizeIdentifier($name): Identifier
636
    {
637
        $identifier = new Identifier($name);
638
 
639
        return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
640
    }
641
 
642
    /**
643
     * Adds suffix to identifier,
644
     *
645
     * if the new string exceeds max identifier length,
646
     * keeps $suffix, cuts from $identifier as much as the part exceeding.
647
     */
648
    private function addSuffix(string $identifier, string $suffix): string
649
    {
650
        $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix);
651
        if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) {
652
            $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix);
653
        }
654
 
655
        return $identifier . $suffix;
656
    }
657
 
658
    /**
659
     * Returns the autoincrement primary key identifier name for the given table identifier.
660
     *
661
     * Quotes the autoincrement primary key identifier name
662
     * if the given table name is quoted by intention.
663
     */
664
    private function getAutoincrementIdentifierName(Identifier $table): string
665
    {
666
        $identifierName = $this->addSuffix($table->getName(), '_AI_PK');
667
 
668
        return $table->isQuoted()
669
            ? $this->quoteSingleIdentifier($identifierName)
670
            : $identifierName;
671
    }
672
 
673
    /**
674
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
675
     *
676
     * {@inheritDoc}
677
     */
678
    public function getListTableForeignKeysSQL($table)
679
    {
680
        $table = $this->normalizeIdentifier($table);
681
        $table = $this->quoteStringLiteral($table->getName());
682
 
683
        return "SELECT alc.constraint_name,
684
          alc.DELETE_RULE,
685
          cols.column_name \"local_column\",
686
          cols.position,
687
          (
688
              SELECT r_cols.table_name
689
              FROM   user_cons_columns r_cols
690
              WHERE  alc.r_constraint_name = r_cols.constraint_name
691
              AND    r_cols.position = cols.position
692
          ) AS \"references_table\",
693
          (
694
              SELECT r_cols.column_name
695
              FROM   user_cons_columns r_cols
696
              WHERE  alc.r_constraint_name = r_cols.constraint_name
697
              AND    r_cols.position = cols.position
698
          ) AS \"foreign_column\"
699
     FROM user_cons_columns cols
700
     JOIN user_constraints alc
701
       ON alc.constraint_name = cols.constraint_name
702
      AND alc.constraint_type = 'R'
703
      AND alc.table_name = " . $table . '
704
    ORDER BY cols.constraint_name ASC, cols.position ASC';
705
    }
706
 
707
    /**
708
     * @deprecated
709
     *
710
     * {@inheritDoc}
711
     */
712
    public function getListTableConstraintsSQL($table)
713
    {
714
        $table = $this->normalizeIdentifier($table);
715
        $table = $this->quoteStringLiteral($table->getName());
716
 
717
        return 'SELECT * FROM user_constraints WHERE table_name = ' . $table;
718
    }
719
 
720
    /**
721
     * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
722
     *
723
     * {@inheritDoc}
724
     */
725
    public function getListTableColumnsSQL($table, $database = null)
726
    {
727
        $table = $this->normalizeIdentifier($table);
728
        $table = $this->quoteStringLiteral($table->getName());
729
 
730
        $tabColumnsTableName       = 'user_tab_columns';
731
        $colCommentsTableName      = 'user_col_comments';
732
        $tabColumnsOwnerCondition  = '';
733
        $colCommentsOwnerCondition = '';
734
 
735
        if ($database !== null && $database !== '/') {
736
            $database                  = $this->normalizeIdentifier($database);
737
            $database                  = $this->quoteStringLiteral($database->getName());
738
            $tabColumnsTableName       = 'all_tab_columns';
739
            $colCommentsTableName      = 'all_col_comments';
740
            $tabColumnsOwnerCondition  = ' AND c.owner = ' . $database;
741
            $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER';
742
        }
743
 
744
        return sprintf(
745
            <<<'SQL'
746
SELECT   c.*,
747
         (
748
             SELECT d.comments
749
             FROM   %s d
750
             WHERE  d.TABLE_NAME = c.TABLE_NAME%s
751
             AND    d.COLUMN_NAME = c.COLUMN_NAME
752
         ) AS comments
753
FROM     %s c
754
WHERE    c.table_name = %s%s
755
ORDER BY c.column_id
756
SQL
757
            ,
758
            $colCommentsTableName,
759
            $colCommentsOwnerCondition,
760
            $tabColumnsTableName,
761
            $table,
762
            $tabColumnsOwnerCondition,
763
        );
764
    }
765
 
766
    /**
767
     * {@inheritDoc}
768
     */
769
    public function getDropForeignKeySQL($foreignKey, $table)
770
    {
771
        if ($foreignKey instanceof ForeignKeyConstraint) {
772
            Deprecation::trigger(
773
                'doctrine/dbal',
774
                'https://github.com/doctrine/dbal/issues/4798',
775
                'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.'
776
                . ' Pass it as a quoted name instead.',
777
                __METHOD__,
778
            );
779
        } else {
780
            $foreignKey = new Identifier($foreignKey);
781
        }
782
 
783
        if ($table instanceof Table) {
784
            Deprecation::trigger(
785
                'doctrine/dbal',
786
                'https://github.com/doctrine/dbal/issues/4798',
787
                'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.',
788
                __METHOD__,
789
            );
790
        } else {
791
            $table = new Identifier($table);
792
        }
793
 
794
        $foreignKey = $foreignKey->getQuotedName($this);
795
        $table      = $table->getQuotedName($this);
796
 
797
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
798
    }
799
 
800
    /**
801
     * {@inheritdoc}
802
     *
803
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
804
     */
805
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
806
    {
807
        $referentialAction = '';
808
 
809
        if ($foreignKey->hasOption('onDelete')) {
810
            $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
811
        }
812
 
813
        if ($referentialAction !== '') {
814
            return ' ON DELETE ' . $referentialAction;
815
        }
816
 
817
        return '';
818
    }
819
 
820
    /**
821
     * {@inheritdoc}
822
     *
823
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
824
     */
825
    public function getForeignKeyReferentialActionSQL($action)
826
    {
827
        $action = strtoupper($action);
828
 
829
        switch ($action) {
830
            case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION.
831
            case 'NO ACTION':
832
                // NO ACTION cannot be declared explicitly,
833
                // therefore returning empty string to indicate to OMIT the referential clause.
834
                return '';
835
 
836
            case 'CASCADE':
837
            case 'SET NULL':
838
                return $action;
839
 
840
            default:
841
                // SET DEFAULT is not supported, throw exception instead.
842
                throw new InvalidArgumentException('Invalid foreign key action: ' . $action);
843
        }
844
    }
845
 
846
    /**
847
     * {@inheritDoc}
848
     */
849
    public function getCreateDatabaseSQL($name)
850
    {
851
        return 'CREATE USER ' . $name;
852
    }
853
 
854
    /**
855
     * {@inheritDoc}
856
     */
857
    public function getDropDatabaseSQL($name)
858
    {
859
        return 'DROP USER ' . $name . ' CASCADE';
860
    }
861
 
862
    /**
863
     * {@inheritDoc}
864
     */
865
    public function getAlterTableSQL(TableDiff $diff)
866
    {
867
        $sql         = [];
868
        $commentsSQL = [];
869
        $columnSql   = [];
870
 
871
        $fields = [];
872
 
873
        $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this);
874
 
875
        foreach ($diff->getAddedColumns() as $column) {
876
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
877
                continue;
878
            }
879
 
880
            $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
881
            $comment  = $this->getColumnComment($column);
882
 
883
            if ($comment === null || $comment === '') {
884
                continue;
885
            }
886
 
887
            $commentsSQL[] = $this->getCommentOnColumnSQL(
888
                $tableNameSQL,
889
                $column->getQuotedName($this),
890
                $comment,
891
            );
892
        }
893
 
894
        if (count($fields) > 0) {
895
            $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $fields) . ')';
896
        }
897
 
898
        $fields = [];
899
        foreach ($diff->getModifiedColumns() as $columnDiff) {
900
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
901
                continue;
902
            }
903
 
904
            $newColumn = $columnDiff->getNewColumn();
905
 
906
            // Do not generate column alteration clause if type is binary and only fixed property has changed.
907
            // Oracle only supports binary type columns with variable length.
908
            // Avoids unnecessary table alteration statements.
909
            if (
910
                $newColumn->getType() instanceof BinaryType &&
911
                $columnDiff->hasFixedChanged() &&
912
                count($columnDiff->changedProperties) === 1
913
            ) {
914
                continue;
915
            }
916
 
917
            $columnHasChangedComment = $columnDiff->hasCommentChanged();
918
 
919
            /**
920
             * Do not add query part if only comment has changed
921
             */
922
            if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) {
923
                $newColumnProperties = $newColumn->toArray();
924
 
925
                if (! $columnDiff->hasNotNullChanged()) {
926
                    unset($newColumnProperties['notnull']);
927
                }
928
 
929
                $fields[] = $newColumn->getQuotedName($this) . $this->getColumnDeclarationSQL('', $newColumnProperties);
930
            }
931
 
932
            if (! $columnHasChangedComment) {
933
                continue;
934
            }
935
 
936
            $commentsSQL[] = $this->getCommentOnColumnSQL(
937
                $tableNameSQL,
938
                $newColumn->getQuotedName($this),
939
                $this->getColumnComment($newColumn),
940
            );
941
        }
942
 
943
        if (count($fields) > 0) {
944
            $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $fields) . ')';
945
        }
946
 
947
        foreach ($diff->getRenamedColumns() as $oldColumnName => $column) {
948
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
949
                continue;
950
            }
951
 
952
            $oldColumnName = new Identifier($oldColumnName);
953
 
954
            $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this)
955
                . ' TO ' . $column->getQuotedName($this);
956
        }
957
 
958
        $fields = [];
959
        foreach ($diff->getDroppedColumns() as $column) {
960
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
961
                continue;
962
            }
963
 
964
            $fields[] = $column->getQuotedName($this);
965
        }
966
 
967
        if (count($fields) > 0) {
968
            $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $fields) . ')';
969
        }
970
 
971
        $tableSql = [];
972
 
973
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
974
            $sql = array_merge($sql, $commentsSQL);
975
 
976
            $newName = $diff->getNewName();
977
 
978
            if ($newName !== false) {
979
                Deprecation::trigger(
980
                    'doctrine/dbal',
981
                    'https://github.com/doctrine/dbal/pull/5663',
982
                    'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.',
983
                    __METHOD__,
984
                );
985
 
986
                $sql[] = sprintf(
987
                    'ALTER TABLE %s RENAME TO %s',
988
                    $tableNameSQL,
989
                    $newName->getQuotedName($this),
990
                );
991
            }
992
 
993
            $sql = array_merge(
994
                $this->getPreAlterTableIndexForeignKeySQL($diff),
995
                $sql,
996
                $this->getPostAlterTableIndexForeignKeySQL($diff),
997
            );
998
        }
999
 
1000
        return array_merge($sql, $tableSql, $columnSql);
1001
    }
1002
 
1003
    /**
1004
     * {@inheritdoc}
1005
     *
1006
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1007
     */
1008
    public function getColumnDeclarationSQL($name, array $column)
1009
    {
1010
        if (isset($column['columnDefinition'])) {
1011
            $columnDef = $this->getCustomTypeDeclarationSQL($column);
1012
        } else {
1013
            $default = $this->getDefaultValueDeclarationSQL($column);
1014
 
1015
            $notnull = '';
1016
 
1017
            if (isset($column['notnull'])) {
1018
                $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL';
1019
            }
1020
 
1021
            if (! empty($column['unique'])) {
1022
                Deprecation::trigger(
1023
                    'doctrine/dbal',
1024
                    'https://github.com/doctrine/dbal/pull/5656',
1025
                    'The usage of the "unique" column property is deprecated. Use unique constraints instead.',
1026
                );
1027
 
1028
                $unique = ' ' . $this->getUniqueFieldDeclarationSQL();
1029
            } else {
1030
                $unique = '';
1031
            }
1032
 
1033
            if (! empty($column['check'])) {
1034
                Deprecation::trigger(
1035
                    'doctrine/dbal',
1036
                    'https://github.com/doctrine/dbal/pull/5656',
1037
                    'The usage of the "check" column property is deprecated.',
1038
                );
1039
 
1040
                $check = ' ' . $column['check'];
1041
            } else {
1042
                $check = '';
1043
            }
1044
 
1045
            $typeDecl  = $column['type']->getSQLDeclaration($column, $this);
1046
            $columnDef = $typeDecl . $default . $notnull . $unique . $check;
1047
        }
1048
 
1049
        return $name . ' ' . $columnDef;
1050
    }
1051
 
1052
    /**
1053
     * {@inheritdoc}
1054
     */
1055
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
1056
    {
1057
        if (strpos($tableName, '.') !== false) {
1058
            [$schema]     = explode('.', $tableName);
1059
            $oldIndexName = $schema . '.' . $oldIndexName;
1060
        }
1061
 
1062
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
1063
    }
1064
 
1065
    /**
1066
     * {@inheritdoc}
1067
     *
1068
     * @deprecated
1069
     */
1070
    public function usesSequenceEmulatedIdentityColumns()
1071
    {
1072
        Deprecation::trigger(
1073
            'doctrine/dbal',
1074
            'https://github.com/doctrine/dbal/pull/5513',
1075
            '%s is deprecated.',
1076
            __METHOD__,
1077
        );
1078
 
1079
        return true;
1080
    }
1081
 
1082
    /**
1083
     * {@inheritdoc}
1084
     *
1085
     * @internal The method should be only used from within the OraclePlatform class hierarchy.
1086
     */
1087
    public function getIdentitySequenceName($tableName, $columnName)
1088
    {
1089
        $table = new Identifier($tableName);
1090
 
1091
        // No usage of column name to preserve BC compatibility with <2.5
1092
        $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ');
1093
 
1094
        if ($table->isQuoted()) {
1095
            $identitySequenceName = '"' . $identitySequenceName . '"';
1096
        }
1097
 
1098
        $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
1099
 
1100
        return $identitySequenceIdentifier->getQuotedName($this);
1101
    }
1102
 
1103
    /**
1104
     * {@inheritDoc}
1105
     *
1106
     * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
1107
     */
1108
    public function supportsCommentOnStatement()
1109
    {
1110
        return true;
1111
    }
1112
 
1113
    /**
1114
     * {@inheritDoc}
1115
     */
1116
    public function getName()
1117
    {
1118
        Deprecation::triggerIfCalledFromOutside(
1119
            'doctrine/dbal',
1120
            'https://github.com/doctrine/dbal/issues/4749',
1121
            'OraclePlatform::getName() is deprecated. Identify platforms by their class.',
1122
        );
1123
 
1124
        return 'oracle';
1125
    }
1126
 
1127
    /**
1128
     * {@inheritDoc}
1129
     */
1130
    protected function doModifyLimitQuery($query, $limit, $offset)
1131
    {
1132
        if ($limit === null && $offset <= 0) {
1133
            return $query;
1134
        }
1135
 
1136
        if (preg_match('/^\s*SELECT/i', $query) === 1) {
1137
            if (preg_match('/\sFROM\s/i', $query) === 0) {
1138
                $query .= ' FROM dual';
1139
            }
1140
 
1141
            $columns = ['a.*'];
1142
 
1143
            if ($offset > 0) {
1144
                $columns[] = 'ROWNUM AS doctrine_rownum';
1145
            }
1146
 
1147
            $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query);
1148
 
1149
            if ($limit !== null) {
1150
                $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit);
1151
            }
1152
 
1153
            if ($offset > 0) {
1154
                $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1);
1155
            }
1156
        }
1157
 
1158
        return $query;
1159
    }
1160
 
1161
    /**
1162
     * {@inheritDoc}
1163
     */
1164
    public function getCreateTemporaryTableSnippetSQL()
1165
    {
1166
        return 'CREATE GLOBAL TEMPORARY TABLE';
1167
    }
1168
 
1169
    /**
1170
     * {@inheritDoc}
1171
     */
1172
    public function getDateTimeTzFormatString()
1173
    {
1174
        return 'Y-m-d H:i:sP';
1175
    }
1176
 
1177
    /**
1178
     * {@inheritDoc}
1179
     */
1180
    public function getDateFormatString()
1181
    {
1182
        return 'Y-m-d 00:00:00';
1183
    }
1184
 
1185
    /**
1186
     * {@inheritDoc}
1187
     */
1188
    public function getTimeFormatString()
1189
    {
1190
        return '1900-01-01 H:i:s';
1191
    }
1192
 
1193
    /**
1194
     * {@inheritDoc}
1195
     */
1196
    public function getMaxIdentifierLength()
1197
    {
1198
        return 30;
1199
    }
1200
 
1201
    /**
1202
     * {@inheritDoc}
1203
     */
1204
    public function supportsSequences()
1205
    {
1206
        return true;
1207
    }
1208
 
1209
    /**
1210
     * {@inheritDoc}
1211
     */
1212
    public function supportsReleaseSavepoints()
1213
    {
1214
        return false;
1215
    }
1216
 
1217
    /**
1218
     * {@inheritDoc}
1219
     */
1220
    public function getTruncateTableSQL($tableName, $cascade = false)
1221
    {
1222
        $tableIdentifier = new Identifier($tableName);
1223
 
1224
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1225
    }
1226
 
1227
    /**
1228
     * {@inheritDoc}
1229
     */
1230
    public function getDummySelectSQL()
1231
    {
1232
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
1233
 
1234
        return sprintf('SELECT %s FROM DUAL', $expression);
1235
    }
1236
 
1237
    /**
1238
     * {@inheritDoc}
1239
     */
1240
    protected function initializeDoctrineTypeMappings()
1241
    {
1242
        $this->doctrineTypeMapping = [
1243
            'binary_double'  => 'float',
1244
            'binary_float'   => 'float',
1245
            'binary_integer' => 'boolean',
1246
            'blob'           => 'blob',
1247
            'char'           => 'string',
1248
            'clob'           => 'text',
1249
            'date'           => 'date',
1250
            'float'          => 'float',
1251
            'integer'        => 'integer',
1252
            'long'           => 'string',
1253
            'long raw'       => 'blob',
1254
            'nchar'          => 'string',
1255
            'nclob'          => 'text',
1256
            'number'         => 'integer',
1257
            'nvarchar2'      => 'string',
1258
            'pls_integer'    => 'boolean',
1259
            'raw'            => 'binary',
1260
            'rowid'          => 'string',
1261
            'timestamp'      => 'datetime',
1262
            'timestamptz'    => 'datetimetz',
1263
            'urowid'         => 'string',
1264
            'varchar'        => 'string',
1265
            'varchar2'       => 'string',
1266
        ];
1267
    }
1268
 
1269
    /**
1270
     * {@inheritDoc}
1271
     */
1272
    public function releaseSavePoint($savepoint)
1273
    {
1274
        return '';
1275
    }
1276
 
1277
    /**
1278
     * {@inheritDoc}
1279
     *
1280
     * @deprecated Implement {@see createReservedKeywordsList()} instead.
1281
     */
1282
    protected function getReservedKeywordsClass()
1283
    {
1284
        Deprecation::triggerIfCalledFromOutside(
1285
            'doctrine/dbal',
1286
            'https://github.com/doctrine/dbal/issues/4510',
1287
            'OraclePlatform::getReservedKeywordsClass() is deprecated,'
1288
            . ' use OraclePlatform::createReservedKeywordsList() instead.',
1289
        );
1290
 
1291
        return Keywords\OracleKeywords::class;
1292
    }
1293
 
1294
    /**
1295
     * {@inheritDoc}
1296
     */
1297
    public function getBlobTypeDeclarationSQL(array $column)
1298
    {
1299
        return 'BLOB';
1300
    }
1301
 
1302
    /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */
1303
    public function getListTableCommentsSQL(string $table, ?string $database = null): string
1304
    {
1305
        $tableCommentsName = 'user_tab_comments';
1306
        $ownerCondition    = '';
1307
 
1308
        if ($database !== null && $database !== '/') {
1309
            $tableCommentsName = 'all_tab_comments';
1310
            $ownerCondition    = ' AND owner = ' . $this->quoteStringLiteral(
1311
                $this->normalizeIdentifier($database)->getName(),
1312
            );
1313
        }
1314
 
1315
        return sprintf(
1316
            <<<'SQL'
1317
SELECT comments FROM %s WHERE table_name = %s%s
1318
SQL
1319
            ,
1320
            $tableCommentsName,
1321
            $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()),
1322
            $ownerCondition,
1323
        );
1324
    }
1325
 
1326
    public function createSchemaManager(Connection $connection): OracleSchemaManager
1327
    {
1328
        return new OracleSchemaManager($connection, $this);
1329
    }
1330
}