Passed
Pull Request — master (#15)
by Wilmer
01:37
created

Schema::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Oracle;
6
7
use PDO;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Db\Connection\Connection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yiisoft\Db\Oracle\Connection. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Yiisoft\Db\Constraint\CheckConstraint;
11
use Yiisoft\Db\Constraint\Constraint;
12
use Yiisoft\Db\Constraint\ConstraintFinderInterface;
13
use Yiisoft\Db\Constraint\ConstraintFinderTrait;
14
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
15
use Yiisoft\Db\Constraint\IndexConstraint;
16
use Yiisoft\Db\Exception\Exception;
17
use Yiisoft\Db\Exception\InvalidCallException;
18
use Yiisoft\Db\Exception\IntegrityException;
19
use Yiisoft\Db\Exception\NotSupportedException;
20
use Yiisoft\Db\Expression\Expression;
21
use Yiisoft\Db\Schema\ColumnSchema;
22
use Yiisoft\Db\Schema\Schema as AbstractSchema;
23
24
/**
25
 * Schema is the class for retrieving metadata from an Oracle database.
26
 *
27
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
28
 * sequence object. This property is read-only.
29
 */
30
final class Schema extends AbstractSchema implements ConstraintFinderInterface
31
{
32
    use ConstraintFinderTrait;
33
34
    /**
35
     * @var array map of DB errors and corresponding exceptions.
36
     *
37
     * If left part is found in DB error message exception class from the right part is used.
38
     */
39
    protected array $exceptionMap = [
40
        'ORA-00001: unique constraint' => IntegrityException::class,
41
    ];
42
43
    protected $tableQuoteCharacter = '"';
44
45
    public function __construct(Connection $db)
46
    {
47
        $this->defaultSchema = strtoupper($db->getUsername());
48
49
        parent::__construct($db);
50
    }
51
52
    protected function resolveTableName(string $name): TableSchema
53
    {
54
        $resolvedName = new TableSchema();
55
56
        $parts = explode('.', str_replace('"', '', $name));
57
58
        if (isset($parts[1])) {
59
            $resolvedName->schemaName($parts[0]);
60
            $resolvedName->name($parts[1]);
61
        } else {
62
            $resolvedName->schemaName($this->defaultSchema);
63
            $resolvedName->name($name);
64
        }
65
66
        $fullName = ($resolvedName->getSchemaName() !== $this->defaultSchema
67
            ? $resolvedName->getSchemaName() . '.' : '') . $resolvedName->getName();
68
69
        $resolvedName->fullName($fullName);
70
71
        return $resolvedName;
72
    }
73
74
    /**
75
     * @see https://docs.oracle.com/cd/B28359_01/server.111/b28337/tdpsg_user_accounts.htm
76
     */
77
    protected function findSchemaNames(): array
78
    {
79
        static $sql = <<<'SQL'
80
SELECT "u"."USERNAME"
81
FROM "DBA_USERS" "u"
82
WHERE "u"."DEFAULT_TABLESPACE" NOT IN ('SYSTEM', 'SYSAUX')
83
ORDER BY "u"."USERNAME" ASC
84
SQL;
85
86
        return $this->getDb()->createCommand($sql)->queryColumn();
87
    }
88
89
    protected function findTableNames(string $schema = ''): array
90
    {
91
        if ($schema === '') {
92
            $sql = <<<'SQL'
93
SELECT
94
    TABLE_NAME
95
FROM USER_TABLES
96
UNION ALL
97
SELECT
98
    VIEW_NAME AS TABLE_NAME
99
FROM USER_VIEWS
100
UNION ALL
101
SELECT
102
    MVIEW_NAME AS TABLE_NAME
103
FROM USER_MVIEWS
104
ORDER BY TABLE_NAME
105
SQL;
106
107
            $command = $this->getDb()->createCommand($sql);
108
        } else {
109
            $sql = <<<'SQL'
110
SELECT
111
    OBJECT_NAME AS TABLE_NAME
112
FROM ALL_OBJECTS
113
WHERE
114
    OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
115
    AND OWNER = :schema
116
ORDER BY OBJECT_NAME
117
SQL;
118
            $command = $this->getDb()->createCommand($sql, [':schema' => $schema]);
119
        }
120
121
        $rows = $command->queryAll();
122
        $names = [];
123
124
        foreach ($rows as $row) {
125
            if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) {
126
                $row = array_change_key_case($row, CASE_UPPER);
127
            }
128
            $names[] = $row['TABLE_NAME'];
129
        }
130
131
        return $names;
132
    }
133
134
    protected function loadTableSchema(string $name): ?TableSchema
135
    {
136
        $table = new TableSchema();
137
138
        $this->resolveTableNames($table, $name);
139
140
        if ($this->findColumns($table)) {
141
            $this->findConstraints($table);
142
            return $table;
143
        }
144
145
        return null;
146
    }
147
148
    protected function loadTablePrimaryKey(string $tableName): ?Constraint
149
    {
150
        return $this->loadTableConstraints($tableName, 'primaryKey');
151
    }
152
153
    protected function loadTableForeignKeys(string $tableName): array
154
    {
155
        return $this->loadTableConstraints($tableName, 'foreignKeys');
156
    }
157
158
    protected function loadTableIndexes(string $tableName): array
159
    {
160
        static $sql = <<<'SQL'
161
SELECT
162
    /*+ PUSH_PRED("ui") PUSH_PRED("uicol") PUSH_PRED("uc") */
163
    "ui"."INDEX_NAME" AS "name",
164
    "uicol"."COLUMN_NAME" AS "column_name",
165
    CASE "ui"."UNIQUENESS" WHEN 'UNIQUE' THEN 1 ELSE 0 END AS "index_is_unique",
166
    CASE WHEN "uc"."CONSTRAINT_NAME" IS NOT NULL THEN 1 ELSE 0 END AS "index_is_primary"
167
FROM "SYS"."USER_INDEXES" "ui"
168
LEFT JOIN "SYS"."USER_IND_COLUMNS" "uicol"
169
    ON "uicol"."INDEX_NAME" = "ui"."INDEX_NAME"
170
LEFT JOIN "SYS"."USER_CONSTRAINTS" "uc"
171
    ON "uc"."OWNER" = "ui"."TABLE_OWNER" AND "uc"."CONSTRAINT_NAME" = "ui"."INDEX_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P'
172
WHERE "ui"."TABLE_OWNER" = :schemaName AND "ui"."TABLE_NAME" = :tableName
173
ORDER BY "uicol"."COLUMN_POSITION" ASC
174
SQL;
175
176
        $resolvedName = $this->resolveTableName($tableName);
177
178
        $indexes = $this->getDb()->createCommand($sql, [
179
            ':schemaName' => $resolvedName->getSchemaName(),
180
            ':tableName' => $resolvedName->getName(),
181
        ])->queryAll();
182
183
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
184
185
        $indexes = ArrayHelper::index($indexes, null, 'name');
186
187
        $result = [];
188
        foreach ($indexes as $name => $index) {
189
            $columnNames = ArrayHelper::getColumn($index, 'column_name');
190
191
            if ($columnNames[0] === null) {
192
                $columnNames[0] = '';
193
            }
194
195
            $result[] = (new IndexConstraint())
196
                ->primary((bool) $index[0]['index_is_primary'])
197
                ->unique((bool) $index[0]['index_is_unique'])
198
                ->name($name)
199
                ->columnNames($columnNames);
200
        }
201
202
        return $result;
203
    }
204
205
    protected function loadTableUniques(string $tableName): array
206
    {
207
        return $this->loadTableConstraints($tableName, 'uniques');
208
    }
209
210
    protected function loadTableChecks(string $tableName): array
211
    {
212
        return $this->loadTableConstraints($tableName, 'checks');
213
    }
214
215
    /**
216
     * @throws NotSupportedException if this method is called.
217
     */
218
    protected function loadTableDefaultValues(string $tableName): array
0 ignored issues
show
Unused Code introduced by
The parameter $tableName 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

218
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ string $tableName): array

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...
219
    {
220
        throw new NotSupportedException('Oracle does not support default value constraints.');
221
    }
222
223
    public function releaseSavepoint(string $name): void
224
    {
225
        /* does nothing as Oracle does not support this */
226
    }
227
228
    public function quoteSimpleTableName(string $name): string
229
    {
230
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
231
    }
232
233
    public function createQueryBuilder(): QueryBuilder
234
    {
235
        return new QueryBuilder($this->getDb());
236
    }
237
238
    /**
239
     * Create a column schema builder instance giving the type and value precision.
240
     *
241
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
242
     *
243
     * @param string $type type of the column. See {@see ColumnSchemaBuilder::$type}.
244
     * @param array|int|string $length length or precision of the column {@see ColumnSchemaBuilder::$length}.
245
     *
246
     * @return ColumnSchemaBuilder column schema builder instance
247
     */
248
    public function createColumnSchemaBuilder(string $type, $length = null): ColumnSchemaBuilder
249
    {
250
        return new ColumnSchemaBuilder($type, $length, $this->getDb());
251
    }
252
253
    /**
254
     * Resolves the table name and schema name (if any).
255
     *
256
     * @param TableSchema $table the table metadata object
257
     * @param string $name the table name
258
     */
259
    protected function resolveTableNames(TableSchema $table, string $name): void
260
    {
261
        $parts = explode('.', str_replace('"', '', $name));
262
263
        if (isset($parts[1])) {
264
            $table->schemaName($parts[0]);
265
            $table->name($parts[1]);
266
        } else {
267
            $table->schemaName($this->defaultSchema);
268
            $table->name($name);
269
        }
270
271
        $table->fullName($table->getSchemaName() !== $this->defaultSchema
272
            ? $table->getSchemaName() . '.' . $table->getName() : $table->getName());
273
    }
274
275
    /**
276
     * Collects the table column metadata.
277
     *
278
     * @param TableSchema $table the table schema.
279
     *
280
     * @return bool whether the table exists.
281
     */
282
    protected function findColumns(TableSchema $table): bool
283
    {
284
        $sql = <<<'SQL'
285
SELECT
286
    A.COLUMN_NAME,
287
    A.DATA_TYPE,
288
    A.DATA_PRECISION,
289
    A.DATA_SCALE,
290
    (
291
      CASE A.CHAR_USED WHEN 'C' THEN A.CHAR_LENGTH
292
        ELSE A.DATA_LENGTH
293
      END
294
    ) AS DATA_LENGTH,
295
    A.NULLABLE,
296
    A.DATA_DEFAULT,
297
    COM.COMMENTS AS COLUMN_COMMENT
298
FROM ALL_TAB_COLUMNS A
299
    INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME)
300
    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)
301
WHERE
302
    A.OWNER = :schemaName
303
    AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
304
    AND B.OBJECT_NAME = :tableName
305
ORDER BY A.COLUMN_ID
306
SQL;
307
308
        try {
309
            $columns = $this->getDb()->createCommand($sql, [
310
                ':tableName' => $table->getName(),
311
                ':schemaName' => $table->getSchemaName(),
312
            ])->queryAll();
313
        } catch (Exception $e) {
314
            return false;
315
        }
316
317
        if (empty($columns)) {
318
            return false;
319
        }
320
321
        foreach ($columns as $column) {
322
            if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) {
323
                $column = array_change_key_case($column, CASE_UPPER);
324
            }
325
326
            $c = $this->createColumn($column);
327
328
            $table->columns($c->getName(), $c);
329
        }
330
331
        return true;
332
    }
333
334
    /**
335
     * Sequence name of table.
336
     *
337
     * @param string $tableName
338
     *
339
     * @internal TableSchema `$table->getName()` the table schema
340
     *
341
     * @return string|null whether the sequence exists
342
     */
343
    protected function getTableSequenceName(string $tableName): ?string
344
    {
345
        $sequenceNameSql = <<<SQL
346
SELECT
347
    UD.REFERENCED_NAME AS SEQUENCE_NAME
348
FROM USER_DEPENDENCIES UD
349
    JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME)
350
WHERE
351
    UT.TABLE_NAME = :tableName
352
    AND UD.TYPE = 'TRIGGER'
353
    AND UD.REFERENCED_TYPE = 'SEQUENCE'
354
SQL;
355
        $sequenceName = $this->getDb()->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar();
356
357
        return $sequenceName === false ? null : $sequenceName;
358
    }
359
360
    /**
361
     * @Overrides method in class 'Schema'
362
     *
363
     * {@see https://secure.php.net/manual/en/function.PDO-lastInsertId.php} -> Oracle does not support this.
364
     *
365
     * Returns the ID of the last inserted row or sequence value.
366
     *
367
     * @param string $sequenceName name of the sequence object (required by some DBMS)
368
     *
369
     * @throws InvalidCallException if the DB connection is not active.
370
     *
371
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object.
372
     */
373
    public function getLastInsertID(string $sequenceName = ''): string
374
    {
375
        if ($this->getDb()->isActive()) {
376
            /* get the last insert id from the master connection */
377
            $sequenceName = $this->quoteSimpleTableName($sequenceName);
378
379
            return $this->getDb()->useMaster(function (Connection $db) use ($sequenceName) {
380
                return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar();
381
            });
382
        } else {
383
            throw new InvalidCallException('DB Connection is not active.');
384
        }
385
    }
386
387
    /**
388
     * Creates ColumnSchema instance.
389
     *
390
     * @param array $column
391
     *
392
     * @return ColumnSchema
393
     */
394
    protected function createColumn($column)
395
    {
396
        $c = $this->createColumnSchema();
397
398
        $c->name($column['COLUMN_NAME']);
399
        $c->allowNull($column['NULLABLE'] === 'Y');
400
        $c->comment($column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']);
401
        $c->primaryKey(false);
402
403
        $this->extractColumnType(
404
            $c,
405
            $column['DATA_TYPE'],
406
            $column['DATA_PRECISION'],
407
            $column['DATA_SCALE'],
408
            $column['DATA_LENGTH']
409
        );
410
411
        $this->extractColumnSize(
412
            $c,
413
            $column['DATA_TYPE'],
414
            $column['DATA_PRECISION'],
415
            $column['DATA_SCALE'],
416
            $column['DATA_LENGTH']
417
        );
418
419
        $c->phpType($this->getColumnPhpType($c));
420
421
        if (!$c->isPrimaryKey()) {
422
            if ($column['DATA_DEFAULT'] !== null && stripos($column['DATA_DEFAULT'], 'timestamp') !== false) {
423
                $c->defaultValue(null);
424
            } else {
425
                $defaultValue = $column['DATA_DEFAULT'];
426
427
                if ($c->getType() === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') {
428
                    $c->defaultValue(new Expression('CURRENT_TIMESTAMP'));
429
                } else {
430
                    if ($defaultValue !== null) {
431
                        if (($len = strlen($defaultValue)) > 2 && $defaultValue[0] === "'"
432
                            && $defaultValue[$len - 1] === "'"
433
                        ) {
434
                            $defaultValue = substr($column['DATA_DEFAULT'], 1, -1);
435
                        } else {
436
                            $defaultValue = trim($defaultValue);
437
                        }
438
                    }
439
                    $c->defaultValue($c->phpTypecast($defaultValue));
440
                }
441
            }
442
        }
443
444
        return $c;
445
    }
446
447
    /**
448
     * Finds constraints and fills them into TableSchema object passed.
449
     *
450
     * @param TableSchema $table
451
     */
452
    protected function findConstraints(TableSchema $table): void
453
    {
454
        $sql = <<<'SQL'
455
SELECT
456
    /*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */
457
    D.CONSTRAINT_NAME,
458
    D.CONSTRAINT_TYPE,
459
    C.COLUMN_NAME,
460
    C.POSITION,
461
    D.R_CONSTRAINT_NAME,
462
    E.TABLE_NAME AS TABLE_REF,
463
    F.COLUMN_NAME AS COLUMN_REF,
464
    C.TABLE_NAME
465
FROM ALL_CONS_COLUMNS C
466
    INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
467
    LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
468
    LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
469
WHERE
470
    C.OWNER = :schemaName
471
    AND C.TABLE_NAME = :tableName
472
ORDER BY D.CONSTRAINT_NAME, C.POSITION
473
SQL;
474
475
        $command = $this->getDb()->createCommand($sql, [
476
            ':tableName' => $table->getName(),
477
            ':schemaName' => $table->getSchemaName(),
478
        ]);
479
480
        $constraints = [];
481
482
        foreach ($command->queryAll() as $row) {
483
            if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) {
484
                $row = array_change_key_case($row, CASE_UPPER);
485
            }
486
487
            if ($row['CONSTRAINT_TYPE'] === 'P') {
488
                $table->getColumns()[$row['COLUMN_NAME']]->primaryKey(true);
489
                $table->primaryKey($row['COLUMN_NAME']);
490
491
                if (empty($table->getSequenceName())) {
492
                    $table->sequenceName($this->getTableSequenceName($table->getName()));
0 ignored issues
show
Bug introduced by
It seems like $table->getName() can also be of type null; however, parameter $tableName of Yiisoft\Db\Oracle\Schema::getTableSequenceName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

492
                    $table->sequenceName($this->getTableSequenceName(/** @scrutinizer ignore-type */ $table->getName()));
Loading history...
493
                }
494
            }
495
496
            if ($row['CONSTRAINT_TYPE'] !== 'R') {
497
                /**
498
                 * This condition is not checked in SQL WHERE because of an Oracle Bug:
499
                 *
500
                 * {@see https://github.com/yiisoft/yii2/pull/8844}
501
                 */
502
                continue;
503
            }
504
505
            $name = $row['CONSTRAINT_NAME'];
506
507
            if (!isset($constraints[$name])) {
508
                $constraints[$name] = [
509
                    'tableName' => $row['TABLE_REF'],
510
                    'columns' => [],
511
                ];
512
            }
513
514
            $constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF'];
515
        }
516
517
        foreach ($constraints as $constraint) {
518
            $name = current(array_keys($constraint));
519
520
            $table->foreignKey($name, array_merge([$constraint['tableName']], $constraint['columns']));
521
        }
522
    }
523
524
    /**
525
     * Returns all unique indexes for the given table.
526
     *
527
     * Each array element is of the following structure:.
528
     *
529
     * ```php
530
     * [
531
     *     'IndexName1' => ['col1' [, ...]],
532
     *     'IndexName2' => ['col2' [, ...]],
533
     * ]
534
     * ```
535
     *
536
     * @param TableSchema $table the table metadata.
537
     *
538
     * @return array all unique indexes for the given table.
539
     */
540
    public function findUniqueIndexes(TableSchema $table): array
541
    {
542
        $query = <<<'SQL'
543
SELECT
544
    DIC.INDEX_NAME,
545
    DIC.COLUMN_NAME
546
FROM ALL_INDEXES DI
547
    INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME
548
WHERE
549
    DI.UNIQUENESS = 'UNIQUE'
550
    AND DIC.TABLE_OWNER = :schemaName
551
    AND DIC.TABLE_NAME = :tableName
552
ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION
553
SQL;
554
        $result = [];
555
556
        $command = $this->getDb()->createCommand($query, [
557
            ':tableName' => $table->getName(),
558
            ':schemaName' => $table->getschemaName(),
559
        ]);
560
561
        foreach ($command->queryAll() as $row) {
562
            $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME'];
563
        }
564
565
        return $result;
566
    }
567
568
    /**
569
     * Extracts the data types for the given column.
570
     *
571
     * @param ColumnSchema $column
572
     * @param string $dbType DB type.
573
     * @param string $precision total number of digits.
574
     * @param string $scale number of digits on the right of the decimal separator.
575
     * @param string $length length for character types.
576
     */
577
    protected function extractColumnType($column, $dbType, $precision, $scale, $length): void
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

577
    protected function extractColumnType($column, $dbType, /** @scrutinizer ignore-unused */ $precision, $scale, $length): void

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...
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

577
    protected function extractColumnType($column, $dbType, $precision, $scale, /** @scrutinizer ignore-unused */ $length): void

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...
578
    {
579
        $column->dbType($dbType);
580
581
        if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) {
582
            $column->type('double');
583
        } elseif (strpos($dbType, 'NUMBER') !== false) {
584
            if ($scale === null || $scale > 0) {
585
                $column->type('decimal');
586
            } else {
587
                $column->type('integer');
588
            }
589
        } elseif (strpos($dbType, 'INTEGER') !== false) {
590
            $column->type('integer');
591
        } elseif (strpos($dbType, 'BLOB') !== false) {
592
            $column->type('binary');
593
        } elseif (strpos($dbType, 'CLOB') !== false) {
594
            $column->type('text');
595
        } elseif (strpos($dbType, 'TIMESTAMP') !== false) {
596
            $column->type('timestamp');
597
        } else {
598
            $column->type('string');
599
        }
600
    }
601
602
    /**
603
     * Extracts size, precision and scale information from column's DB type.
604
     *
605
     * @param ColumnSchema $column
606
     * @param string $dbType the column's DB type.
607
     * @param string $precision total number of digits.
608
     * @param string $scale number of digits on the right of the decimal separator.
609
     * @param string $length length for character types.
610
     */
611
    protected function extractColumnSize($column, $dbType, $precision, $scale, $length): void
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

611
    protected function extractColumnSize($column, /** @scrutinizer ignore-unused */ $dbType, $precision, $scale, $length): void

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...
612
    {
613
        $column->size(trim($length) === '' ? null : (int) $length);
614
        $column->precision(trim((string) $precision) === '' ? null : (int) $precision);
615
        $column->scale($scale === '' || $scale === null ? null : (int) $scale);
616
    }
617
618
    public function insert($table, $columns)
619
    {
620
        $params = [];
621
        $returnParams = [];
622
        $sql = $this->getDb()->getQueryBuilder()->insert($table, $columns, $params);
623
        $tableSchema = $this->getTableSchema($table);
624
        $returnColumns = $tableSchema->getPrimaryKey();
625
626
        if (!empty($returnColumns)) {
627
            $columnSchemas = $tableSchema->getColumns();
628
629
            $returning = [];
630
            foreach ((array) $returnColumns as $name) {
631
                $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams));
632
633
                $returnParams[$phName] = [
634
                    'column' => $name,
635
                    'value' => '',
636
                ];
637
638
                if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->getPhpType() !== 'integer') {
639
                    $returnParams[$phName]['dataType'] = PDO::PARAM_STR;
640
                } else {
641
                    $returnParams[$phName]['dataType'] = PDO::PARAM_INT;
642
                }
643
644
                $returnParams[$phName]['size'] = $columnSchemas[$name]->getSize() !== null
645
                    ? $columnSchemas[$name]->getSize() : -1;
646
647
                $returning[] = $this->quoteColumnName($name);
648
            }
649
650
            $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams));
651
        }
652
653
        $command = $this->getDb()->createCommand($sql, $params);
654
655
        $command->prepare(false);
656
657
        foreach ($returnParams as $name => &$value) {
658
            $command->getPdoStatement()->bindParam($name, $value['value'], $value['dataType'], $value['size']);
659
        }
660
661
        if (!$command->execute()) {
662
            return false;
663
        }
664
665
        $result = [];
666
        foreach ($returnParams as $value) {
667
            $result[$value['column']] = $value['value'];
668
        }
669
670
        return $result;
671
    }
672
673
    /**
674
     * Loads multiple types of constraints and returns the specified ones.
675
     *
676
     * @param string $tableName table name.
677
     * @param string $returnType return type:
678
     * - primaryKey
679
     * - foreignKeys
680
     * - uniques
681
     * - checks
682
     *
683
     * @return mixed constraints.
684
     */
685
    private function loadTableConstraints(string $tableName, string $returnType)
686
    {
687
        $sql = <<<'SQL'
688
SELECT
689
    /*+ PUSH_PRED("uc") PUSH_PRED("uccol") PUSH_PRED("fuc") */
690
    "uc"."CONSTRAINT_NAME" AS "name",
691
    "uccol"."COLUMN_NAME" AS "column_name",
692
    "uc"."CONSTRAINT_TYPE" AS "type",
693
    "fuc"."OWNER" AS "foreign_table_schema",
694
    "fuc"."TABLE_NAME" AS "foreign_table_name",
695
    "fuccol"."COLUMN_NAME" AS "foreign_column_name",
696
    "uc"."DELETE_RULE" AS "on_delete",
697
    "uc"."SEARCH_CONDITION" AS "check_expr"
698
FROM "USER_CONSTRAINTS" "uc"
699
INNER JOIN "USER_CONS_COLUMNS" "uccol"
700
    ON "uccol"."OWNER" = "uc"."OWNER" AND "uccol"."CONSTRAINT_NAME" = "uc"."CONSTRAINT_NAME"
701
LEFT JOIN "USER_CONSTRAINTS" "fuc"
702
    ON "fuc"."OWNER" = "uc"."R_OWNER" AND "fuc"."CONSTRAINT_NAME" = "uc"."R_CONSTRAINT_NAME"
703
LEFT JOIN "USER_CONS_COLUMNS" "fuccol"
704
    ON "fuccol"."OWNER" = "fuc"."OWNER" AND "fuccol"."CONSTRAINT_NAME" = "fuc"."CONSTRAINT_NAME" AND "fuccol"."POSITION" = "uccol"."POSITION"
705
WHERE "uc"."OWNER" = :schemaName AND "uc"."TABLE_NAME" = :tableName
706
ORDER BY "uccol"."POSITION" ASC
707
SQL;
708
709
        $resolvedName = $this->resolveTableName($tableName);
710
711
        $constraints = $this->getDb()->createCommand($sql, [
712
            ':schemaName' => $resolvedName->getSchemaName(),
713
            ':tableName' => $resolvedName->getName(),
714
        ])->queryAll();
715
716
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
717
718
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
719
720
        $result = [
721
            'primaryKey' => null,
722
            'foreignKeys' => [],
723
            'uniques' => [],
724
            'checks' => [],
725
        ];
726
727
        foreach ($constraints as $type => $names) {
728
            foreach ($names as $name => $constraint) {
729
                switch ($type) {
730
                    case 'P':
731
                        $result['primaryKey'] = (new Constraint())
732
                            ->name($name)
733
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'));
734
                        break;
735
                    case 'R':
736
                        $result['foreignKeys'][] = (new ForeignKeyConstraint())
737
                            ->name($name)
738
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'))
739
                            ->foreignSchemaName($constraint[0]['foreign_table_schema'])
740
                            ->foreignTableName($constraint[0]['foreign_table_name'])
741
                            ->foreignColumnNames(ArrayHelper::getColumn($constraint, 'foreign_column_name'))
742
                            ->onDelete($constraint[0]['on_delete'])
743
                            ->onUpdate(null);
744
                        break;
745
                    case 'U':
746
                        $result['uniques'][] = (new Constraint())
747
                            ->name($name)
748
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'));
749
                        break;
750
                    case 'C':
751
                        $result['checks'][] = (new CheckConstraint())
752
                            ->name($name)
753
                            ->columnNames(ArrayHelper::getColumn($constraint, 'column_name'))
754
                            ->expression($constraint[0]['check_expr']);
755
                        break;
756
                }
757
            }
758
        }
759
760
        foreach ($result as $type => $data) {
761
            $this->setTableMetadata($tableName, $type, $data);
762
        }
763
764
        return $result[$returnType];
765
    }
766
767
    /**
768
     * Creates a column schema for the database.
769
     *
770
     * This method may be overridden by child classes to create a DBMS-specific column schema.
771
     *
772
     * @return ColumnSchema column schema instance.
773
     */
774
    protected function createColumnSchema(): ColumnSchema
775
    {
776
        return new ColumnSchema();
777
    }
778
}
779