Passed
Push — fix-php-docs ( 5267e5...e79c6c )
by Wilmer
12:35 queued 06:39
created

Schema::findConstraints()   B

Complexity

Conditions 7
Paths 20

Size

Total Lines 80
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 42
nc 20
nop 1
dl 0
loc 80
ccs 26
cts 26
cp 1
crap 7
rs 8.3146
c 0
b 0
f 0

How to fix   Long Method   

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
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Oracle;
6
7
use Throwable;
8
use Yiisoft\Db\Cache\SchemaCache;
9
use Yiisoft\Db\Connection\ConnectionInterface;
10
use Yiisoft\Db\Constraint\CheckConstraint;
11
use Yiisoft\Db\Constraint\Constraint;
12
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
13
use Yiisoft\Db\Constraint\IndexConstraint;
14
use Yiisoft\Db\Exception\Exception;
15
use Yiisoft\Db\Exception\InvalidConfigException;
16
use Yiisoft\Db\Exception\NotSupportedException;
17
use Yiisoft\Db\Expression\Expression;
18
use Yiisoft\Db\Helper\ArrayHelper;
19
use Yiisoft\Db\Schema\AbstractSchema;
20
use Yiisoft\Db\Schema\Builder\ColumnInterface;
21
use Yiisoft\Db\Schema\ColumnSchemaInterface;
22
use Yiisoft\Db\Schema\TableSchemaInterface;
23
24
use function array_merge;
25
use function array_reverse;
26
use function implode;
27
use function is_array;
28
use function md5;
29
use function serialize;
30
use function str_contains;
31
use function strlen;
32
use function substr;
33
use function trim;
34
35
/**
36
 * Implements the Oracle Server specific schema, supporting Oracle Server 11C and above.
37
 *
38
 * @psalm-type ConstraintArray = array<
39
 *   array-key,
40
 *   array {
41
 *     name: string,
42
 *     column_name: string,
43
 *     type: string,
44
 *     foreign_table_schema: string|null,
45
 *     foreign_table_name: string|null,
46
 *     foreign_column_name: string|null,
47
 *     on_update: string,
48
 *     on_delete: string,
49
 *     check_expr: string
50
 *   }
51
 * >
52
 */
53
final class Schema extends AbstractSchema
54
{
55 533
    public function __construct(protected ConnectionInterface $db, SchemaCache $schemaCache, string $defaultSchema)
56
    {
57 533
        $this->defaultSchema = $defaultSchema;
58 533
        parent::__construct($db, $schemaCache);
59
    }
60
61 7
    public function createColumn(string $type, array|int|string $length = null): ColumnInterface
62
    {
63 7
        return new Column($type, $length);
64
    }
65
66 197
    protected function resolveTableName(string $name): TableSchemaInterface
67
    {
68 197
        $resolvedName = new TableSchema();
69
70 197
        $parts = array_reverse(
71 197
            $this->db->getQuoter()->getTableNameParts($name)
72 197
        );
73
74 197
        $resolvedName->name($parts[0] ?? '');
75 197
        $resolvedName->schemaName($parts[1] ?? $this->defaultSchema);
76
77 197
        $resolvedName->fullName(
78 197
            $resolvedName->getSchemaName() !== $this->defaultSchema ?
79 197
            implode('.', array_reverse($parts)) : $resolvedName->getName()
80 197
        );
81
82 197
        return $resolvedName;
83
    }
84
85
    /**
86
     * @link https://docs.oracle.com/cd/B28359_01/server.111/b28337/tdpsg_user_accounts.htm
87
     *
88
     * @throws Exception
89
     * @throws InvalidConfigException
90
     * @throws NotSupportedException
91
     * @throws Throwable
92
     */
93 1
    protected function findSchemaNames(): array
94
    {
95 1
        $sql = <<<SQL
96
        SELECT "u"."USERNAME"
97
        FROM "DBA_USERS" "u"
98
        WHERE "u"."DEFAULT_TABLESPACE" NOT IN ('SYSTEM', 'SYSAUX')
99
        ORDER BY "u"."USERNAME" ASC
100 1
        SQL;
101
102 1
        return $this->db->createCommand($sql)->queryColumn();
103
    }
104
105
    /**
106
     * @throws Exception
107
     * @throws InvalidConfigException
108
     * @throws Throwable
109
     */
110 138
    protected function findTableComment(TableSchemaInterface $tableSchema): void
111
    {
112 138
        $sql = <<<SQL
113
        SELECT "COMMENTS"
114
        FROM ALL_TAB_COMMENTS
115
        WHERE
116
              "OWNER" = :schemaName AND
117
              "TABLE_NAME" = :tableName
118 138
        SQL;
119
120 138
        $comment = $this->db->createCommand($sql, [
121 138
            ':schemaName' => $tableSchema->getSchemaName(),
122 138
            ':tableName' => $tableSchema->getName(),
123 138
        ])->queryScalar();
124
125 138
        $tableSchema->comment(is_string($comment) ? $comment : null);
126
    }
127
128
    /**
129
     * @throws Exception
130
     * @throws InvalidConfigException
131
     * @throws Throwable
132
     */
133 12
    protected function findTableNames(string $schema = ''): array
134
    {
135 12
        if ($schema === '') {
136 11
            $sql = <<<SQL
137
            SELECT TABLE_NAME
138
            FROM USER_TABLES
139
            UNION ALL
140
            SELECT VIEW_NAME AS TABLE_NAME
141
            FROM USER_VIEWS
142
            UNION ALL
143
            SELECT MVIEW_NAME AS TABLE_NAME
144
            FROM USER_MVIEWS
145
            ORDER BY TABLE_NAME
146 11
            SQL;
147
148 11
            $command = $this->db->createCommand($sql);
149
        } else {
150 1
            $sql = <<<SQL
151
            SELECT OBJECT_NAME AS TABLE_NAME
152
            FROM ALL_OBJECTS
153
            WHERE OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') AND OWNER = :schema
154
            ORDER BY OBJECT_NAME
155 1
            SQL;
156 1
            $command = $this->db->createCommand($sql, [':schema' => $schema]);
157
        }
158
159 12
        $rows = $command->queryAll();
160 12
        $names = [];
161
162
        /** @psalm-var string[][] $rows */
163 12
        foreach ($rows as $row) {
164
            /** @psalm-var string[] $row */
165 12
            $row = $this->normalizeRowKeyCase($row, false);
166 12
            $names[] = $row['table_name'];
167
        }
168
169 12
        return $names;
170
    }
171
172
    /**
173
     * @throws Exception
174
     * @throws InvalidConfigException
175
     * @throws Throwable
176
     */
177 138
    protected function loadTableSchema(string $name): TableSchemaInterface|null
178
    {
179 138
        $table = $this->resolveTableName($name);
180 138
        $this->findTableComment($table);
181
182 138
        if ($this->findColumns($table)) {
183 121
            $this->findConstraints($table);
184 121
            return $table;
185
        }
186
187 32
        return null;
188
    }
189
190
    /**
191
     * @throws Exception
192
     * @throws InvalidConfigException
193
     * @throws NotSupportedException
194
     * @throws Throwable
195
     */
196 47
    protected function loadTablePrimaryKey(string $tableName): Constraint|null
197
    {
198
        /** @psalm-var mixed $tablePrimaryKey */
199 47
        $tablePrimaryKey = $this->loadTableConstraints($tableName, self::PRIMARY_KEY);
200 47
        return $tablePrimaryKey instanceof Constraint ? $tablePrimaryKey : null;
201
    }
202
203
    /**
204
     * @throws Exception
205
     * @throws InvalidConfigException
206
     * @throws NotSupportedException
207
     * @throws Throwable
208
     */
209 8
    protected function loadTableForeignKeys(string $tableName): array
210
    {
211
        /** @psalm-var mixed $tableForeignKeys */
212 8
        $tableForeignKeys = $this->loadTableConstraints($tableName, self::FOREIGN_KEYS);
213 8
        return is_array($tableForeignKeys) ? $tableForeignKeys : [];
214
    }
215
216
    /**
217
     * @throws Exception
218
     * @throws InvalidConfigException
219
     * @throws NotSupportedException
220
     * @throws Throwable
221
     */
222 38
    protected function loadTableIndexes(string $tableName): array
223
    {
224 38
        $sql = <<<SQL
225
        SELECT "ui"."INDEX_NAME" AS "name", "uicol"."COLUMN_NAME" AS "column_name",
226
        CASE "ui"."UNIQUENESS" WHEN 'UNIQUE' THEN 1 ELSE 0 END AS "index_is_unique",
227
        CASE WHEN "uc"."CONSTRAINT_NAME" IS NOT NULL THEN 1 ELSE 0 END AS "index_is_primary"
228
        FROM "SYS"."USER_INDEXES" "ui"
229
        LEFT JOIN "SYS"."USER_IND_COLUMNS" "uicol"
230
        ON "uicol"."INDEX_NAME" = "ui"."INDEX_NAME"
231
        LEFT JOIN "SYS"."USER_CONSTRAINTS" "uc"
232
        ON "uc"."OWNER" = "ui"."TABLE_OWNER" AND "uc"."CONSTRAINT_NAME" = "ui"."INDEX_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P'
233
        WHERE "ui"."TABLE_OWNER" = :schemaName AND "ui"."TABLE_NAME" = :tableName
234
        ORDER BY "uicol"."COLUMN_POSITION" ASC
235 38
        SQL;
236
237 38
        $resolvedName = $this->resolveTableName($tableName);
238 38
        $indexes = $this->db->createCommand($sql, [
239 38
            ':schemaName' => $resolvedName->getSchemaName(),
240 38
            ':tableName' => $resolvedName->getName(),
241 38
        ])->queryAll();
242
243
        /** @psalm-var array[] $indexes */
244 38
        $indexes = $this->normalizeRowKeyCase($indexes, true);
245 38
        $indexes = ArrayHelper::index($indexes, null, ['name']);
246
247 38
        $result = [];
248
249
        /**
250
         * @psalm-var object|string|null $name
251
         * @psalm-var array[] $index
252
         */
253 38
        foreach ($indexes as $name => $index) {
254 35
            $columnNames = ArrayHelper::getColumn($index, 'column_name');
255
256 35
            if ($columnNames[0] === null) {
257 20
                $columnNames[0] = '';
258
            }
259
260 35
            $result[] = (new IndexConstraint())
261 35
                ->primary((bool) $index[0]['index_is_primary'])
262 35
                ->unique((bool) $index[0]['index_is_unique'])
263 35
                ->name($name)
264 35
                ->columnNames($columnNames);
265
        }
266
267 38
        return $result;
268
    }
269
270
    /**
271
     * @throws Exception
272
     * @throws InvalidConfigException
273
     * @throws NotSupportedException
274
     * @throws Throwable
275
     */
276 17
    protected function loadTableUniques(string $tableName): array
277
    {
278
        /** @psalm-var mixed $tableUniques */
279 17
        $tableUniques = $this->loadTableConstraints($tableName, self::UNIQUES);
280 17
        return is_array($tableUniques) ? $tableUniques : [];
281
    }
282
283
    /**
284
     * @throws Exception
285
     * @throws InvalidConfigException
286
     * @throws NotSupportedException
287
     * @throws Throwable
288
     */
289 17
    protected function loadTableChecks(string $tableName): array
290
    {
291
        /** @psalm-var mixed $tableCheck */
292 17
        $tableCheck = $this->loadTableConstraints($tableName, self::CHECKS);
293 17
        return is_array($tableCheck) ? $tableCheck : [];
294
    }
295
296
    /**
297
     * @throws NotSupportedException If this method is called.
298
     */
299 13
    protected function loadTableDefaultValues(string $tableName): array
300
    {
301 13
        throw new NotSupportedException(__METHOD__ . ' is not supported by Oracle.');
302
    }
303
304
    /**
305
     * Collects the table column metadata.
306
     *
307
     * @param TableSchemaInterface $table The table schema.
308
     *
309
     * @throws Exception
310
     * @throws Throwable
311
     *
312
     * @return bool Whether the table exists.
313
     */
314 138
    protected function findColumns(TableSchemaInterface $table): bool
315
    {
316 138
        $sql = <<<SQL
317
        SELECT
318
            A.COLUMN_NAME,
319
            A.DATA_TYPE,
320
            A.DATA_PRECISION,
321
            A.DATA_SCALE,
322
            (
323
            CASE A.CHAR_USED WHEN 'C' THEN A.CHAR_LENGTH
324
                ELSE A.DATA_LENGTH
325
            END
326
            ) AS DATA_LENGTH,
327
            A.NULLABLE,
328
            A.DATA_DEFAULT,
329
            (
330
                SELECT COUNT(*)
331
                FROM ALL_CONSTRAINTS AC
332
                INNER JOIN ALL_CONS_COLUMNS ACC ON ACC.CONSTRAINT_NAME=AC.CONSTRAINT_NAME
333
                WHERE
334
                     AC.OWNER = A.OWNER
335
                   AND AC.TABLE_NAME = B.OBJECT_NAME
336
                   AND ACC.COLUMN_NAME = A.COLUMN_NAME
337
                   AND AC.CONSTRAINT_TYPE = 'P'
338
            ) AS IS_PK,
339
            COM.COMMENTS AS COLUMN_COMMENT
340
        FROM ALL_TAB_COLUMNS A
341
            INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME)
342
            LEFT JOIN ALL_COL_COMMENTS COM ON (A.OWNER = COM.OWNER AND A.TABLE_NAME = COM.TABLE_NAME AND A.COLUMN_NAME = COM.COLUMN_NAME)
343
        WHERE
344
            A.OWNER = :schemaName
345
            AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
346
            AND B.OBJECT_NAME = :tableName
347
        ORDER BY A.COLUMN_ID
348 138
        SQL;
349
350 138
        $columns = $this->db->createCommand($sql, [
351 138
            ':tableName' => $table->getName(),
352 138
            ':schemaName' => $table->getSchemaName(),
353 138
        ])->queryAll();
354
355 138
        if ($columns === []) {
356 32
            return false;
357
        }
358
359
        /** @psalm-var string[][] $columns */
360 121
        foreach ($columns as $column) {
361 121
            $column = $this->normalizeRowKeyCase($column, false);
362
363 121
            $c = $this->createColumnSchema($column);
364
365 121
            $table->columns($c->getName(), $c);
366
        }
367
368 121
        return true;
369
    }
370
371
    /**
372
     * Sequence name of table.
373
     *
374
     * @throws Exception
375
     * @throws InvalidConfigException
376
     * @throws Throwable
377
     *
378
     * @return bool|float|int|string|null Whether the sequence exists.
379
     *
380
     * @internal TableSchemaInterface `$table->getName()` The table schema.
381
     */
382 75
    protected function getTableSequenceName(string $tableName): bool|float|int|string|null
383
    {
384 75
        $sequenceNameSql = <<<SQL
385
        SELECT
386
            UD.REFERENCED_NAME AS SEQUENCE_NAME
387
        FROM USER_DEPENDENCIES UD
388
            JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME)
389
        WHERE
390
            UT.TABLE_NAME = :tableName
391
            AND UD.TYPE = 'TRIGGER'
392
            AND UD.REFERENCED_TYPE = 'SEQUENCE'
393 75
        SQL;
394 75
        $sequenceName = $this->db->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar();
395
396 75
        return $sequenceName === false ? null : $sequenceName;
397
    }
398
399
    /**
400
     * Creates ColumnSchema instance.
401
     */
402 121
    protected function createColumnSchema(array|string $column): ColumnSchemaInterface
403
    {
404 121
        $c = new ColumnSchema();
405
406
        /**
407
         * @psalm-var array{
408
         *   column_name: string,
409
         *   data_type: string,
410
         *   data_precision: string,
411
         *   data_scale: string,
412
         *   data_length: string,
413
         *   nullable: string,
414
         *   data_default: string|null,
415
         *   is_pk: string|null,
416
         *   column_comment: string|null
417
         * } $column
418
         */
419 121
        $c->name($column['column_name']);
420 121
        $c->allowNull($column['nullable'] === 'Y');
421 121
        $c->comment($column['column_comment'] ?? '');
422 121
        $c->primaryKey((int) ($column['is_pk'] ?? 0) > 0);
423
424 121
        $this->extractColumnType(
425 121
            $c,
426 121
            $column['data_type'],
427 121
            $column['data_precision'],
428 121
            $column['data_scale'],
429 121
            $column['data_length']
430 121
        );
431
432 121
        $this->extractColumnSize(
433 121
            $c,
434 121
            $column['data_type'],
435 121
            $column['data_precision'],
436 121
            $column['data_scale'],
437 121
            $column['data_length']
438 121
        );
439
440 121
        $c->phpType($this->getColumnPhpType($c));
441
442 121
        if (!$c->isPrimaryKey()) {
443 119
            if ($column['data_default'] === null) {
444 116
                $c->defaultValue(null);
445
            } else {
446 69
                $defaultValue = $column['data_default'];
447
448 69
                if ($c->getType() === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') {
449
                    $c->defaultValue(new Expression('CURRENT_TIMESTAMP'));
450
                } else {
451
                    if (
452 69
                            strlen($defaultValue) > 2
453 69
                            && str_starts_with($defaultValue, "'")
454 69
                            && str_ends_with($defaultValue, "'")
455
                        ) {
456 25
                        $defaultValue = substr($defaultValue, 1, -1);
457
                    } else {
458 69
                        $defaultValue = trim($defaultValue);
459
                    }
460
461 69
                    $c->defaultValue($c->phpTypecast($defaultValue));
462
                }
463
            }
464
        }
465
466 121
        return $c;
467
    }
468
469
    /**
470
     * Finds constraints and fills them into TableSchemaInterface object passed.
471
     *
472
     * @throws Exception
473
     * @throws InvalidConfigException
474
     * @throws Throwable
475
     *
476
     * @psalm-suppress PossiblyNullArrayOffset
477
     */
478 121
    protected function findConstraints(TableSchemaInterface $table): void
479
    {
480 121
        $sql = <<<SQL
481
        SELECT
482
            /*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */
483
            D.CONSTRAINT_NAME,
484
            D.CONSTRAINT_TYPE,
485
            C.COLUMN_NAME,
486
            C.POSITION,
487
            D.R_CONSTRAINT_NAME,
488
            E.TABLE_NAME AS TABLE_REF,
489
            F.COLUMN_NAME AS COLUMN_REF,
490
            C.TABLE_NAME
491
        FROM ALL_CONS_COLUMNS C
492
            INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
493
            LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
494
            LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
495
        WHERE
496
            C.OWNER = :schemaName
497
            AND C.TABLE_NAME = :tableName
498
            ORDER BY D.CONSTRAINT_NAME, C.POSITION
499 121
        SQL;
500
501
        /**
502
         * @psalm-var array{
503
         *   array{
504
         *     constraint_name: string,
505
         *     constraint_type: string,
506
         *     column_name: string,
507
         *     position: string|null,
508
         *     r_constraint_name: string|null,
509
         *     table_ref: string|null,
510
         *     column_ref: string|null,
511
         *     table_name: string
512
         *   }
513
         * } $rows
514
         */
515 121
        $rows = $this->db->createCommand(
516 121
            $sql,
517 121
            [':tableName' => $table->getName(), ':schemaName' => $table->getSchemaName()]
518 121
        )->queryAll();
519
520 121
        $constraints = [];
521
522 121
        foreach ($rows as $row) {
523
            /** @psalm-var string[] $row */
524 112
            $row = $this->normalizeRowKeyCase($row, false);
525
526 112
            if ($row['constraint_type'] === 'P') {
527 75
                $table->getColumns()[$row['column_name']]->primaryKey(true);
528 75
                $table->primaryKey($row['column_name']);
529
530 75
                if (empty($table->getSequenceName())) {
531 75
                    $table->sequenceName((string) $this->getTableSequenceName($table->getName()));
532
                }
533
            }
534
535 112
            if ($row['constraint_type'] !== 'R') {
536
                /**
537
                 * This condition isn't checked in SQL WHERE because of an Oracle Bug:
538
                 *
539
                 * @link https://github.com/yiisoft/yii2/pull/8844
540
                 */
541 112
                continue;
542
            }
543
544 14
            $name = $row['constraint_name'];
545
546 14
            if (!isset($constraints[$name])) {
547 14
                $constraints[$name] = [
548 14
                    'tableName' => $row['table_ref'],
549 14
                    'columns' => [],
550 14
                ];
551
            }
552
553 14
            $constraints[$name]['columns'][$row['column_name']] = $row['column_ref'];
554
        }
555
556 121
        foreach ($constraints as $index => $constraint) {
557 14
            $table->foreignKey($index, array_merge([$constraint['tableName']], $constraint['columns']));
558
        }
559
    }
560
561
    /**
562
     * Returns all unique indexes for the given table.
563
     *
564
     * Each array element is of the following structure:.
565
     *
566
     * ```php
567
     * [
568
     *     'IndexName1' => ['col1' [, ...]],
569
     *     'IndexName2' => ['col2' [, ...]],
570
     * ]
571
     * ```
572
     *
573
     * @param TableSchemaInterface $table The table metadata.
574
     *
575
     * @throws Exception
576
     * @throws InvalidConfigException
577
     * @throws Throwable
578
     *
579
     * @return array All unique indexes for the given table.
580
     */
581 1
    public function findUniqueIndexes(TableSchemaInterface $table): array
582
    {
583 1
        $query = <<<SQL
584
        SELECT
585
            DIC.INDEX_NAME,
586
            DIC.COLUMN_NAME
587
        FROM ALL_INDEXES DI
588
            INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME
589
        WHERE
590
            DI.UNIQUENESS = 'UNIQUE'
591
            AND DIC.TABLE_OWNER = :schemaName
592
            AND DIC.TABLE_NAME = :tableName
593
        ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION
594 1
        SQL;
595 1
        $result = [];
596
597 1
        $rows = $this->db->createCommand(
598 1
            $query,
599 1
            [':tableName' => $table->getName(), ':schemaName' => $table->getschemaName()]
600 1
        )->queryAll();
601
602
        /** @psalm-var array<array{INDEX_NAME: string, COLUMN_NAME: string}> $rows */
603 1
        foreach ($rows as $row) {
604 1
            $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME'];
605
        }
606
607 1
        return $result;
608
    }
609
610
    /**
611
     * Extracts the data types for the given column.
612
     *
613
     * @param string $dbType DB type.
614
     * @param string|null $precision The total number of digits.
615
     * @param string|null $scale The number of digits on the right of the decimal separator.
616
     * @param string $length The length for character types.
617
     */
618 121
    protected function extractColumnType(
619
        ColumnSchema $column,
620
        string $dbType,
621
        string|null $precision,
0 ignored issues
show
Unused Code introduced by
The parameter $precision is not used and could be removed. ( Ignorable by Annotation )

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

621
        /** @scrutinizer ignore-unused */ string|null $precision,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
622
        string|null $scale,
623
        string $length
0 ignored issues
show
Unused Code introduced by
The parameter $length is not used and could be removed. ( Ignorable by Annotation )

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

623
        /** @scrutinizer ignore-unused */ string $length

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
624
    ): void {
625 121
        $column->dbType($dbType);
626
627 121
        if (str_contains($dbType, 'FLOAT') || str_contains($dbType, 'DOUBLE')) {
628 28
            $column->type(self::TYPE_DOUBLE);
629 121
        } elseif (str_contains($dbType, 'NUMBER')) {
630 118
            if ($scale === null || $scale > 0) {
631 31
                $column->type(self::TYPE_DECIMAL);
632
            } else {
633 118
                $column->type(self::TYPE_INTEGER);
634
            }
635 90
        } elseif (str_contains($dbType, 'BLOB')) {
636 30
            $column->type(self::TYPE_BINARY);
637 86
        } elseif (str_contains($dbType, 'CLOB')) {
638 24
            $column->type(self::TYPE_TEXT);
639 85
        } elseif (str_contains($dbType, 'TIMESTAMP')) {
640 26
            $column->type(self::TYPE_TIMESTAMP);
641
        } else {
642 85
            $column->type(self::TYPE_STRING);
643
        }
644
    }
645
646
    /**
647
     * Extracts size, precision and scale information from column's DB type.
648
     *
649
     * @param string $dbType The column's DB type.
650
     * @param string|null $precision Total number of digits.
651
     * @param string|null $scale Number of digits on the right of the decimal separator.
652
     * @param string $length The length for character types.
653
     */
654 121
    protected function extractColumnSize(
655
        ColumnSchema $column,
656
        string $dbType,
0 ignored issues
show
Unused Code introduced by
The parameter $dbType is not used and could be removed. ( Ignorable by Annotation )

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

656
        /** @scrutinizer ignore-unused */ string $dbType,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
657
        string|null $precision,
658
        string|null $scale,
659
        string $length
660
    ): void {
661 121
        $column->size(trim($length) === '' ? null : (int) $length);
662 121
        $column->precision(trim((string) $precision) === '' ? null : (int) $precision);
663 121
        $column->scale($scale === '' || $scale === null ? null : (int) $scale);
664
    }
665
666
    /**
667
     * Loads multiple types of constraints and returns the specified ones.
668
     *
669
     * @param string $tableName The table name.
670
     * @param string $returnType The return type:
671
     * - primaryKey
672
     * - foreignKeys
673
     * - uniques
674
     * - checks
675
     *
676
     * @throws Exception
677
     * @throws InvalidConfigException
678
     * @throws NotSupportedException
679
     * @throws Throwable
680
     *
681
     * @return mixed Constraints.
682
     */
683 89
    private function loadTableConstraints(string $tableName, string $returnType): mixed
684
    {
685 89
        $sql = <<<SQL
686
        SELECT
687
            "uc"."CONSTRAINT_NAME" AS "name",
688
            "uccol"."COLUMN_NAME" AS "column_name",
689
            "uc"."CONSTRAINT_TYPE" AS "type",
690
            "fuc"."OWNER" AS "foreign_table_schema",
691
            "fuc"."TABLE_NAME" AS "foreign_table_name",
692
            "fuccol"."COLUMN_NAME" AS "foreign_column_name",
693
            "uc"."DELETE_RULE" AS "on_delete",
694
            "uc"."SEARCH_CONDITION" AS "check_expr"
695
        FROM "USER_CONSTRAINTS" "uc"
696
        INNER JOIN "USER_CONS_COLUMNS" "uccol"
697
        ON "uccol"."OWNER" = "uc"."OWNER" AND "uccol"."CONSTRAINT_NAME" = "uc"."CONSTRAINT_NAME"
698
        LEFT JOIN "USER_CONSTRAINTS" "fuc"
699
        ON "fuc"."OWNER" = "uc"."R_OWNER" AND "fuc"."CONSTRAINT_NAME" = "uc"."R_CONSTRAINT_NAME"
700
        LEFT JOIN "USER_CONS_COLUMNS" "fuccol"
701
        ON "fuccol"."OWNER" = "fuc"."OWNER" AND "fuccol"."CONSTRAINT_NAME" = "fuc"."CONSTRAINT_NAME" AND "fuccol"."POSITION" = "uccol"."POSITION"
702
        WHERE "uc"."OWNER" = :schemaName AND "uc"."TABLE_NAME" = :tableName
703
        ORDER BY "uccol"."POSITION" ASC
704 89
        SQL;
705
706 89
        $resolvedName = $this->resolveTableName($tableName);
707 89
        $constraints = $this->db->createCommand($sql, [
708 89
            ':schemaName' => $resolvedName->getSchemaName(),
709 89
            ':tableName' => $resolvedName->getName(),
710 89
        ])->queryAll();
711
712
        /** @psalm-var array[] $constraints */
713 89
        $constraints = $this->normalizeRowKeyCase($constraints, true);
714 89
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
715
716 89
        $result = [
717 89
            self::PRIMARY_KEY => null,
718 89
            self::FOREIGN_KEYS => [],
719 89
            self::UNIQUES => [],
720 89
            self::CHECKS => [],
721 89
        ];
722
723
        /**
724
         * @psalm-var string $type
725
         * @psalm-var array $names
726
         */
727 89
        foreach ($constraints as $type => $names) {
728
            /**
729
             * @psalm-var object|string|null $name
730
             * @psalm-var ConstraintArray $constraint
731
             */
732 82
            foreach ($names as $name => $constraint) {
733
                switch ($type) {
734 82
                    case 'P':
735 57
                        $result[self::PRIMARY_KEY] = (new Constraint())
736 57
                            ->name($name)
737 57
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'));
738 57
                        break;
739 82
                    case 'R':
740 19
                        $result[self::FOREIGN_KEYS][] = (new ForeignKeyConstraint())
741 19
                            ->name($name)
742 19
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'))
743 19
                            ->foreignSchemaName($constraint[0]['foreign_table_schema'])
744 19
                            ->foreignTableName($constraint[0]['foreign_table_name'])
745 19
                            ->foreignColumnNames(ArrayHelper::getColumn($constraint, 'foreign_column_name'))
746 19
                            ->onDelete($constraint[0]['on_delete'])
747 19
                            ->onUpdate(null);
748 19
                        break;
749 82
                    case 'U':
750 58
                        $result[self::UNIQUES][] = (new Constraint())
751 58
                            ->name($name)
752 58
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'));
753 58
                        break;
754 82
                    case 'C':
755 82
                        $result[self::CHECKS][] = (new CheckConstraint())
756 82
                            ->name($name)
757 82
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'))
758 82
                            ->expression($constraint[0]['check_expr']);
759 82
                        break;
760
                }
761
            }
762
        }
763
764 89
        foreach ($result as $type => $data) {
765 89
            $this->setTableMetadata($tableName, $type, $data);
766
        }
767
768 89
        return $result[$returnType];
769
    }
770
771
    /**
772
     * @throws Exception
773
     * @throws InvalidConfigException
774
     * @throws Throwable
775
     */
776 2
    protected function findViewNames(string $schema = ''): array
777
    {
778 2
        $sql = match ($schema) {
779 2
            '' => <<<SQL
780
            SELECT VIEW_NAME FROM USER_VIEWS
781 2
            SQL,
782 2
            default => <<<SQL
783 2
            SELECT VIEW_NAME FROM ALL_VIEWS WHERE OWNER = '$schema'
784 2
            SQL,
785 2
        };
786
787
        /** @psalm-var string[][] $views */
788 2
        $views = $this->db->createCommand($sql)->queryAll();
789
790 2
        foreach ($views as $key => $view) {
791 2
            $views[$key] = $view['VIEW_NAME'];
792
        }
793
794 2
        return $views;
795
    }
796
797
    /**
798
     * Returns the cache key for the specified table name.
799
     *
800
     * @param string $name The table name.
801
     *
802
     * @return array The cache key.
803
     */
804 235
    protected function getCacheKey(string $name): array
805
    {
806 235
        return array_merge([self::class], $this->db->getCacheKey(), [$this->getRawTableName($name)]);
807
    }
808
809
    /**
810
     * Returns the cache tag name.
811
     *
812
     * This allows {@see refresh()} to invalidate all cached table schemas.
813
     *
814
     * @return string The cache tag name.
815
     */
816 236
    protected function getCacheTag(): string
817
    {
818 236
        return md5(serialize(array_merge([self::class], $this->db->getCacheKey())));
819
    }
820
}
821