Passed
Pull Request — master (#20362)
by Wilmer
05:37
created

Schema::resolveTableName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 13
ccs 0
cts 10
cp 0
crap 12
rs 9.9332
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\oci;
9
10
use Yii;
11
use yii\base\InvalidCallException;
12
use yii\base\NotSupportedException;
13
use yii\db\CheckConstraint;
14
use yii\db\ColumnSchema;
15
use yii\db\Connection;
16
use yii\db\Constraint;
17
use yii\db\ConstraintFinderInterface;
18
use yii\db\ConstraintFinderTrait;
19
use yii\db\Expression;
20
use yii\db\ForeignKeyConstraint;
21
use yii\db\IndexConstraint;
22
use yii\db\TableSchema;
23
use yii\helpers\ArrayHelper;
24
25
/**
26
 * Schema is the class for retrieving metadata from an Oracle database.
27
 *
28
 * @property-read string $lastInsertID The row ID of the last row inserted, or the last value retrieved from
29
 * the sequence object.
30
 *
31
 * @author Qiang Xue <[email protected]>
32
 * @since 2.0
33
 */
34
class Schema extends \yii\db\Schema implements ConstraintFinderInterface
35
{
36
    use ConstraintFinderTrait;
37
38
    /**
39
     * @var array map of DB errors and corresponding exceptions
40
     * If left part is found in DB error message exception class from the right part is used.
41
     */
42
    public $exceptionMap = [
43
        'ORA-00001: unique constraint' => 'yii\db\IntegrityException',
44
    ];
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    protected $tableQuoteCharacter = '"';
50
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function init()
56
    {
57
        parent::init();
58
        if ($this->defaultSchema === null) {
59
            $username = $this->db->username;
60
            if (empty($username)) {
61
                $username = isset($this->db->masters[0]['username']) ? $this->db->masters[0]['username'] : '';
62
            }
63
            $this->defaultSchema = strtoupper($username);
64
        }
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    protected function resolveTableName($name)
71
    {
72
        $resolvedName = new TableSchema();
73
        $parts = explode('.', str_replace('"', '', $name));
74
        if (isset($parts[1])) {
75
            $resolvedName->schemaName = $parts[0];
76
            $resolvedName->name = $parts[1];
77
        } else {
78
            $resolvedName->schemaName = $this->defaultSchema;
79
            $resolvedName->name = $name;
80
        }
81
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
82
        return $resolvedName;
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     * @see https://docs.oracle.com/cd/B28359_01/server.111/b28337/tdpsg_user_accounts.htm
88
     */
89
    protected function findSchemaNames()
90
    {
91
        static $sql = <<<'SQL'
92
SELECT "u"."USERNAME"
93
FROM "DBA_USERS" "u"
94
WHERE "u"."DEFAULT_TABLESPACE" NOT IN ('SYSTEM', 'SYSAUX')
95
ORDER BY "u"."USERNAME" ASC
96
SQL;
97
98
        return $this->db->createCommand($sql)->queryColumn();
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    protected function findTableNames($schema = '')
105
    {
106
        if ($schema === '') {
107
            $sql = <<<'SQL'
108
SELECT
109
    TABLE_NAME
110
FROM USER_TABLES
111
UNION ALL
112
SELECT
113
    VIEW_NAME AS TABLE_NAME
114
FROM USER_VIEWS
115
UNION ALL
116
SELECT
117
    MVIEW_NAME AS TABLE_NAME
118
FROM USER_MVIEWS
119
ORDER BY TABLE_NAME
120
SQL;
121
            $command = $this->db->createCommand($sql);
122
        } else {
123
            $sql = <<<'SQL'
124
SELECT
125
    OBJECT_NAME AS TABLE_NAME
126
FROM ALL_OBJECTS
127
WHERE
128
    OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
129
    AND OWNER = :schema
130
ORDER BY OBJECT_NAME
131
SQL;
132
            $command = $this->db->createCommand($sql, [':schema' => $schema]);
133
        }
134
135
        $rows = $command->queryAll();
136
        $names = [];
137
        foreach ($rows as $row) {
138
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
0 ignored issues
show
Bug introduced by
The method getAttribute() does not exist on null. ( Ignorable by Annotation )

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

138
            if ($this->db->slavePdo->/** @scrutinizer ignore-call */ getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
139
                $row = array_change_key_case($row, CASE_UPPER);
140
            }
141
            $names[] = $row['TABLE_NAME'];
142
        }
143
144
        return $names;
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    protected function loadTableSchema($name)
151
    {
152
        $table = new TableSchema();
153
        $this->resolveTableNames($table, $name);
154
        if ($this->findColumns($table)) {
155
            $this->findConstraints($table);
156
            return $table;
157
        }
158
159
        return null;
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    protected function loadTablePrimaryKey($tableName)
166
    {
167
        return $this->loadTableConstraints($tableName, 'primaryKey');
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    protected function loadTableForeignKeys($tableName)
174
    {
175
        return $this->loadTableConstraints($tableName, 'foreignKeys');
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    protected function loadTableIndexes($tableName)
182
    {
183
        static $sql = <<<'SQL'
184
SELECT
185
    /*+ PUSH_PRED("ui") PUSH_PRED("uicol") PUSH_PRED("uc") */
186
    "ui"."INDEX_NAME" AS "name",
187
    "uicol"."COLUMN_NAME" AS "column_name",
188
    CASE "ui"."UNIQUENESS" WHEN 'UNIQUE' THEN 1 ELSE 0 END AS "index_is_unique",
189
    CASE WHEN "uc"."CONSTRAINT_NAME" IS NOT NULL THEN 1 ELSE 0 END AS "index_is_primary"
190
FROM "SYS"."USER_INDEXES" "ui"
191
LEFT JOIN "SYS"."USER_IND_COLUMNS" "uicol"
192
    ON "uicol"."INDEX_NAME" = "ui"."INDEX_NAME"
193
LEFT JOIN "SYS"."USER_CONSTRAINTS" "uc"
194
    ON "uc"."OWNER" = "ui"."TABLE_OWNER" AND "uc"."CONSTRAINT_NAME" = "ui"."INDEX_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P'
195
WHERE "ui"."TABLE_OWNER" = :schemaName AND "ui"."TABLE_NAME" = :tableName
196
ORDER BY "uicol"."COLUMN_POSITION" ASC
197
SQL;
198
199
        $resolvedName = $this->resolveTableName($tableName);
200
        $indexes = $this->db->createCommand($sql, [
201
            ':schemaName' => $resolvedName->schemaName,
202
            ':tableName' => $resolvedName->name,
203
        ])->queryAll();
204
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
205
        $indexes = ArrayHelper::index($indexes, null, 'name');
206
        $result = [];
207
        foreach ($indexes as $name => $index) {
208
            $result[] = new IndexConstraint([
209
                'isPrimary' => (bool) $index[0]['index_is_primary'],
210
                'isUnique' => (bool) $index[0]['index_is_unique'],
211
                'name' => $name,
212
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
213
            ]);
214
        }
215
216
        return $result;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    protected function loadTableUniques($tableName)
223
    {
224
        return $this->loadTableConstraints($tableName, 'uniques');
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    protected function loadTableChecks($tableName)
231
    {
232
        return $this->loadTableConstraints($tableName, 'checks');
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     * @throws NotSupportedException if this method is called.
238
     */
239
    protected function loadTableDefaultValues($tableName)
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

239
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ $tableName)

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...
240
    {
241
        throw new NotSupportedException('Oracle does not support default value constraints.');
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247
    public function releaseSavepoint($name)
248
    {
249
        // does nothing as Oracle does not support this
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function quoteSimpleTableName($name)
256
    {
257
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function createQueryBuilder()
264
    {
265
        return Yii::createObject(QueryBuilder::class, [$this->db]);
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function createColumnSchemaBuilder($type, $length = null)
272
    {
273
        return Yii::createObject(ColumnSchemaBuilder::class, [$type, $length]);
274
    }
275
276
    /**
277
     * Resolves the table name and schema name (if any).
278
     *
279
     * @param TableSchema $table the table metadata object
280
     * @param string $name the table name
281
     */
282
    protected function resolveTableNames($table, $name)
283
    {
284
        $parts = explode('.', str_replace('"', '', $name));
285
        if (isset($parts[1])) {
286
            $table->schemaName = $parts[0];
287
            $table->name = $parts[1];
288
        } else {
289
            $table->schemaName = $this->defaultSchema;
290
            $table->name = $name;
291
        }
292
293
        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
294
    }
295
296
    /**
297
     * Collects the table column metadata.
298
     * @param TableSchema $table the table schema
299
     * @return bool whether the table exists
300
     */
301
    protected function findColumns($table)
302
    {
303
        $sql = <<<'SQL'
304
SELECT
305
    A.COLUMN_NAME,
306
    A.DATA_TYPE,
307
    A.DATA_PRECISION,
308
    A.DATA_SCALE,
309
    (
310
      CASE A.CHAR_USED WHEN 'C' THEN A.CHAR_LENGTH
311
        ELSE A.DATA_LENGTH
312
      END
313
    ) AS DATA_LENGTH,
314
    A.NULLABLE,
315
    A.DATA_DEFAULT,
316
    COM.COMMENTS AS COLUMN_COMMENT
317
FROM ALL_TAB_COLUMNS A
318
    INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME)
319
    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)
320
WHERE
321
    A.OWNER = :schemaName
322
    AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
323
    AND B.OBJECT_NAME = :tableName
324
ORDER BY A.COLUMN_ID
325
SQL;
326
327
        try {
328
            $columns = $this->db->createCommand($sql, [
329
                ':tableName' => $table->name,
330
                ':schemaName' => $table->schemaName,
331
            ])->queryAll();
332
        } catch (\Exception $e) {
333
            return false;
334
        }
335
336
        if (empty($columns)) {
337
            return false;
338
        }
339
340
        foreach ($columns as $column) {
341
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
342
                $column = array_change_key_case($column, CASE_UPPER);
343
            }
344
            $c = $this->createColumn($column);
345
            $table->columns[$c->name] = $c;
346
        }
347
348
        return true;
349
    }
350
351
    /**
352
     * Sequence name of table.
353
     *
354
     * @param string $tableName
355
     * @internal param \yii\db\TableSchema $table->name the table schema
356
     * @return string|null whether the sequence exists
357
     */
358
    protected function getTableSequenceName($tableName)
359
    {
360
        $sequenceNameSql = <<<'SQL'
361
SELECT
362
    UD.REFERENCED_NAME AS SEQUENCE_NAME
363
FROM USER_DEPENDENCIES UD
364
    JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME)
365
WHERE
366
    UT.TABLE_NAME = :tableName
367
    AND UD.TYPE = 'TRIGGER'
368
    AND UD.REFERENCED_TYPE = 'SEQUENCE'
369
SQL;
370
        $sequenceName = $this->db->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar();
371
        return $sequenceName === false ? null : $sequenceName;
372
    }
373
374
    /**
375
     * @Overrides method in class 'Schema'
376
     * @see https://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this
377
     *
378
     * Returns the ID of the last inserted row or sequence value.
379
     * @param string $sequenceName name of the sequence object (required by some DBMS)
380
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
381
     * @throws InvalidCallException if the DB connection is not active
382
     */
383
    public function getLastInsertID($sequenceName = '')
384
    {
385
        if ($this->db->isActive) {
386
            // get the last insert id from the master connection
387
            $sequenceName = $this->quoteSimpleTableName($sequenceName);
388
            return $this->db->useMaster(function (Connection $db) use ($sequenceName) {
389
                return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar();
390
            });
391
        } else {
392
            throw new InvalidCallException('DB Connection is not active.');
393
        }
394
    }
395
396
    /**
397
     * Creates ColumnSchema instance.
398
     *
399
     * @param array $column
400
     * @return ColumnSchema
401
     */
402
    protected function createColumn($column)
403
    {
404
        $c = $this->createColumnSchema();
405
        $c->name = $column['COLUMN_NAME'];
406
        $c->allowNull = $column['NULLABLE'] === 'Y';
407
        $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT'];
408
        $c->isPrimaryKey = false;
409
        $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']);
410
        $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']);
411
412
        $c->phpType = $this->getColumnPhpType($c);
413
414
        if (!$c->isPrimaryKey) {
415
            if (stripos((string) $column['DATA_DEFAULT'], 'timestamp') !== false) {
416
                $c->defaultValue = null;
417
            } else {
418
                $defaultValue = (string) $column['DATA_DEFAULT'];
419
                if ($c->type === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') {
420
                    $c->defaultValue = new Expression('CURRENT_TIMESTAMP');
421
                } else {
422
                    if ($defaultValue !== null) {
0 ignored issues
show
introduced by
The condition $defaultValue !== null is always true.
Loading history...
423
                        if (
424
                            strlen($defaultValue) > 2
425
                            && strncmp($defaultValue, "'", 1) === 0
426
                            && substr($defaultValue, -1) === "'"
427
                        ) {
428
                            $defaultValue = substr($defaultValue, 1, -1);
429
                        } else {
430
                            $defaultValue = trim($defaultValue);
431
                        }
432
                    }
433
                    $c->defaultValue = $c->phpTypecast($defaultValue);
434
                }
435
            }
436
        }
437
438
        return $c;
439
    }
440
441
    /**
442
     * Finds constraints and fills them into TableSchema object passed.
443
     * @param TableSchema $table
444
     */
445
    protected function findConstraints($table)
446
    {
447
        $sql = <<<'SQL'
448
SELECT
449
    /*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */
450
    D.CONSTRAINT_NAME,
451
    D.CONSTRAINT_TYPE,
452
    C.COLUMN_NAME,
453
    C.POSITION,
454
    D.R_CONSTRAINT_NAME,
455
    E.TABLE_NAME AS TABLE_REF,
456
    F.COLUMN_NAME AS COLUMN_REF,
457
    C.TABLE_NAME
458
FROM ALL_CONS_COLUMNS C
459
    INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
460
    LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
461
    LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
462
WHERE
463
    C.OWNER = :schemaName
464
    AND C.TABLE_NAME = :tableName
465
ORDER BY D.CONSTRAINT_NAME, C.POSITION
466
SQL;
467
        $command = $this->db->createCommand($sql, [
468
            ':tableName' => $table->name,
469
            ':schemaName' => $table->schemaName,
470
        ]);
471
        $constraints = [];
472
        foreach ($command->queryAll() as $row) {
473
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
474
                $row = array_change_key_case($row, CASE_UPPER);
475
            }
476
477
            if ($row['CONSTRAINT_TYPE'] === 'P') {
478
                $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true;
479
                $table->primaryKey[] = $row['COLUMN_NAME'];
480
                if (empty($table->sequenceName)) {
481
                    $table->sequenceName = $this->getTableSequenceName($table->name);
482
                }
483
            }
484
485
            if ($row['CONSTRAINT_TYPE'] !== 'R') {
486
                // this condition is not checked in SQL WHERE because of an Oracle Bug:
487
                // see https://github.com/yiisoft/yii2/pull/8844
488
                continue;
489
            }
490
491
            $name = $row['CONSTRAINT_NAME'];
492
            if (!isset($constraints[$name])) {
493
                $constraints[$name] = [
494
                    'tableName' => $row['TABLE_REF'],
495
                    'columns' => [],
496
                ];
497
            }
498
            $constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF'];
499
        }
500
501
        foreach ($constraints as $constraint) {
502
            $name = current(array_keys($constraint));
503
504
            $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
505
        }
506
    }
507
508
    /**
509
     * Returns all unique indexes for the given table.
510
     * Each array element is of the following structure:.
511
     *
512
     * ```php
513
     * [
514
     *     'IndexName1' => ['col1' [, ...]],
515
     *     'IndexName2' => ['col2' [, ...]],
516
     * ]
517
     * ```
518
     *
519
     * @param TableSchema $table the table metadata
520
     * @return array all unique indexes for the given table.
521
     * @since 2.0.4
522
     */
523
    public function findUniqueIndexes($table)
524
    {
525
        $query = <<<'SQL'
526
SELECT
527
    DIC.INDEX_NAME,
528
    DIC.COLUMN_NAME
529
FROM ALL_INDEXES DI
530
    INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME
531
WHERE
532
    DI.UNIQUENESS = 'UNIQUE'
533
    AND DIC.TABLE_OWNER = :schemaName
534
    AND DIC.TABLE_NAME = :tableName
535
ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION
536
SQL;
537
        $result = [];
538
        $command = $this->db->createCommand($query, [
539
            ':tableName' => $table->name,
540
            ':schemaName' => $table->schemaName,
541
        ]);
542
        foreach ($command->queryAll() as $row) {
543
            $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME'];
544
        }
545
546
        return $result;
547
    }
548
549
    /**
550
     * Extracts the data types for the given column.
551
     * @param ColumnSchema $column
552
     * @param string $dbType DB type
553
     * @param string $precision total number of digits.
554
     * This parameter is available since version 2.0.4.
555
     * @param string $scale number of digits on the right of the decimal separator.
556
     * This parameter is available since version 2.0.4.
557
     * @param string $length length for character types.
558
     * This parameter is available since version 2.0.4.
559
     */
560
    protected function extractColumnType($column, $dbType, $precision, $scale, $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

560
    protected function extractColumnType($column, $dbType, $precision, $scale, /** @scrutinizer ignore-unused */ $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...
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

560
    protected function extractColumnType($column, $dbType, /** @scrutinizer ignore-unused */ $precision, $scale, $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...
561
    {
562
        $column->dbType = $dbType;
563
564
        if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) {
565
            $column->type = 'double';
566
        } elseif (strpos($dbType, 'NUMBER') !== false) {
567
            if ($scale === null || $scale > 0) {
568
                $column->type = 'decimal';
569
            } else {
570
                $column->type = 'integer';
571
            }
572
        } elseif (strpos($dbType, 'INTEGER') !== false) {
573
            $column->type = 'integer';
574
        } elseif (strpos($dbType, 'BLOB') !== false) {
575
            $column->type = 'binary';
576
        } elseif (strpos($dbType, 'CLOB') !== false) {
577
            $column->type = 'text';
578
        } elseif (strpos($dbType, 'TIMESTAMP') !== false) {
579
            $column->type = 'timestamp';
580
        } else {
581
            $column->type = 'string';
582
        }
583
    }
584
585
    /**
586
     * Extracts size, precision and scale information from column's DB type.
587
     * @param ColumnSchema $column
588
     * @param string $dbType the column's DB type
589
     * @param string $precision total number of digits.
590
     * This parameter is available since version 2.0.4.
591
     * @param string $scale number of digits on the right of the decimal separator.
592
     * This parameter is available since version 2.0.4.
593
     * @param string $length length for character types.
594
     * This parameter is available since version 2.0.4.
595
     */
596
    protected function extractColumnSize($column, $dbType, $precision, $scale, $length)
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

596
    protected function extractColumnSize($column, /** @scrutinizer ignore-unused */ $dbType, $precision, $scale, $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...
597
    {
598
        $column->size = trim((string) $length) === '' ? null : (int) $length;
599
        $column->precision = trim((string) $precision) === '' ? null : (int) $precision;
600
        $column->scale = trim((string) $scale) === '' ? null : (int) $scale;
601
    }
602
603
    /**
604
     * {@inheritdoc}
605
     */
606
    public function insert($table, $columns)
607
    {
608
        $params = [];
609
        $returnParams = [];
610
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
611
        $tableSchema = $this->getTableSchema($table);
612
        $returnColumns = $tableSchema->primaryKey;
613
        if (!empty($returnColumns)) {
614
            $columnSchemas = $tableSchema->columns;
615
            $returning = [];
616
            foreach ((array) $returnColumns as $name) {
617
                $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams));
618
                $returnParams[$phName] = [
619
                    'column' => $name,
620
                    'value' => '',
621
                ];
622
                if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->phpType !== 'integer') {
623
                    $returnParams[$phName]['dataType'] = \PDO::PARAM_STR;
624
                } else {
625
                    $returnParams[$phName]['dataType'] = \PDO::PARAM_INT;
626
                }
627
                $returnParams[$phName]['size'] = isset($columnSchemas[$name]->size) ? $columnSchemas[$name]->size : -1;
628
                $returning[] = $this->quoteColumnName($name);
629
            }
630
            $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams));
631
        }
632
633
        $command = $this->db->createCommand($sql, $params);
634
        $command->prepare(false);
635
636
        foreach ($returnParams as $name => &$value) {
637
            $command->pdoStatement->bindParam($name, $value['value'], $value['dataType'], $value['size']);
638
        }
639
640
        if (!$command->execute()) {
641
            return false;
642
        }
643
644
        $result = [];
645
        foreach ($returnParams as $value) {
646
            $result[$value['column']] = $value['value'];
647
        }
648
649
        return $result;
650
    }
651
652
    /**
653
     * Loads multiple types of constraints and returns the specified ones.
654
     * @param string $tableName table name.
655
     * @param string $returnType return type:
656
     * - primaryKey
657
     * - foreignKeys
658
     * - uniques
659
     * - checks
660
     * @return mixed constraints.
661
     */
662
    private function loadTableConstraints($tableName, $returnType)
663
    {
664
        static $sql = <<<'SQL'
665
SELECT
666
    /*+ PUSH_PRED("uc") PUSH_PRED("uccol") PUSH_PRED("fuc") */
667
    "uc"."CONSTRAINT_NAME" AS "name",
668
    "uccol"."COLUMN_NAME" AS "column_name",
669
    "uc"."CONSTRAINT_TYPE" AS "type",
670
    "fuc"."OWNER" AS "foreign_table_schema",
671
    "fuc"."TABLE_NAME" AS "foreign_table_name",
672
    "fuccol"."COLUMN_NAME" AS "foreign_column_name",
673
    "uc"."DELETE_RULE" AS "on_delete",
674
    "uc"."SEARCH_CONDITION" AS "check_expr"
675
FROM "USER_CONSTRAINTS" "uc"
676
INNER JOIN "USER_CONS_COLUMNS" "uccol"
677
    ON "uccol"."OWNER" = "uc"."OWNER" AND "uccol"."CONSTRAINT_NAME" = "uc"."CONSTRAINT_NAME"
678
LEFT JOIN "USER_CONSTRAINTS" "fuc"
679
    ON "fuc"."OWNER" = "uc"."R_OWNER" AND "fuc"."CONSTRAINT_NAME" = "uc"."R_CONSTRAINT_NAME"
680
LEFT JOIN "USER_CONS_COLUMNS" "fuccol"
681
    ON "fuccol"."OWNER" = "fuc"."OWNER" AND "fuccol"."CONSTRAINT_NAME" = "fuc"."CONSTRAINT_NAME" AND "fuccol"."POSITION" = "uccol"."POSITION"
682
WHERE "uc"."OWNER" = :schemaName AND "uc"."TABLE_NAME" = :tableName
683
ORDER BY "uccol"."POSITION" ASC
684
SQL;
685
686
        $resolvedName = $this->resolveTableName($tableName);
687
        $constraints = $this->db->createCommand($sql, [
688
            ':schemaName' => $resolvedName->schemaName,
689
            ':tableName' => $resolvedName->name,
690
        ])->queryAll();
691
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
692
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
693
        $result = [
694
            'primaryKey' => null,
695
            'foreignKeys' => [],
696
            'uniques' => [],
697
            'checks' => [],
698
        ];
699
        foreach ($constraints as $type => $names) {
700
            foreach ($names as $name => $constraint) {
701
                switch ($type) {
702
                    case 'P':
703
                        $result['primaryKey'] = new Constraint([
704
                            'name' => $name,
705
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
706
                        ]);
707
                        break;
708
                    case 'R':
709
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
710
                            'name' => $name,
711
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
712
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
713
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
714
                            'foreignColumnNames' => ArrayHelper::getColumn($constraint, 'foreign_column_name'),
715
                            'onDelete' => $constraint[0]['on_delete'],
716
                            'onUpdate' => null,
717
                        ]);
718
                        break;
719
                    case 'U':
720
                        $result['uniques'][] = new Constraint([
721
                            'name' => $name,
722
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
723
                        ]);
724
                        break;
725
                    case 'C':
726
                        $result['checks'][] = new CheckConstraint([
727
                            'name' => $name,
728
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
729
                            'expression' => $constraint[0]['check_expr'],
730
                        ]);
731
                        break;
732
                }
733
            }
734
        }
735
        foreach ($result as $type => $data) {
736
            $this->setTableMetadata($tableName, $type, $data);
737
        }
738
739
        return $result[$returnType];
740
    }
741
}
742