Completed
Push — master ( e0dde8...a7d2aa )
by Carsten
09:47
created

Schema::insert()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 45
rs 4.8196
c 0
b 0
f 0
ccs 0
cts 40
cp 0
cc 10
eloc 31
nc 12
nop 2
crap 110

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

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

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

This check looks from 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.

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

Loading history...
541
    {
542
        $column->dbType = $dbType;
543
544
        if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) {
545
            $column->type = 'double';
546
        } elseif (strpos($dbType, 'NUMBER') !== false) {
547
            if ($scale === null || $scale > 0) {
548
                $column->type = 'decimal';
549
            } else {
550
                $column->type = 'integer';
551
            }
552
        } elseif (strpos($dbType, 'INTEGER') !== false) {
553
            $column->type = 'integer';
554
        } elseif (strpos($dbType, 'BLOB') !== false) {
555
            $column->type = 'binary';
556
        } elseif (strpos($dbType, 'CLOB') !== false) {
557
            $column->type = 'text';
558
        } elseif (strpos($dbType, 'TIMESTAMP') !== false) {
559
            $column->type = 'timestamp';
560
        } else {
561
            $column->type = 'string';
562
        }
563
    }
564
565
    /**
566
     * Extracts size, precision and scale information from column's DB type.
567
     * @param ColumnSchema $column
568
     * @param string $dbType the column's DB type
569
     * @param string $precision total number of digits.
570
     * This parameter is available since version 2.0.4.
571
     * @param string $scale number of digits on the right of the decimal separator.
572
     * This parameter is available since version 2.0.4.
573
     * @param string $length length for character types.
574
     * This parameter is available since version 2.0.4.
575
     */
576
    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.

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

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