Passed
Pull Request — master (#120)
by Wilmer
29:52 queued 05:37
created

Schema::createColumn()   B

Complexity

Conditions 10
Paths 6

Size

Total Lines 63
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 10.0036

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 34
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 63
ccs 29
cts 30
cp 0.9667
crap 10.0036
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

616
        /** @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...
617
        string|null $scale,
618
        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

618
        /** @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...
619
    ): void {
620 112
        $column->dbType($dbType);
621
622 112
        if (str_contains($dbType, 'FLOAT') || str_contains($dbType, 'DOUBLE')) {
623 21
            $column->type(self::TYPE_DOUBLE);
624 112
        } elseif (str_contains($dbType, 'NUMBER')) {
625 108
            if ($scale === null || $scale > 0) {
626 22
                $column->type(self::TYPE_DECIMAL);
627
            } else {
628 108
                $column->type(self::TYPE_INTEGER);
629
            }
630 86
        } elseif (str_contains($dbType, 'INTEGER')) {
631
            $column->type(self::TYPE_INTEGER);
632 86
        } elseif (str_contains($dbType, 'BLOB')) {
633 23
            $column->type(self::TYPE_BINARY);
634 83
        } elseif (str_contains($dbType, 'CLOB')) {
635 22
            $column->type(self::TYPE_TEXT);
636 82
        } elseif (str_contains($dbType, 'TIMESTAMP')) {
637 20
            $column->type(self::TYPE_TIMESTAMP);
638
        } else {
639 82
            $column->type(self::TYPE_STRING);
640
        }
641
    }
642
643
    /**
644
     * Extracts size, precision and scale information from column's DB type.
645
     *
646
     * @param string $dbType the column's DB type.
647
     * @param string|null $precision total number of digits.
648
     * @param string|null $scale number of digits on the right of the decimal separator.
649
     * @param string $length length for character types.
650
     */
651 112
    protected function extractColumnSize(
652
        ColumnSchema $column,
653
        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

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