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

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

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

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