GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 6d277d...c73de3 )
by Robert
11:50
created

Schema::findUniqueIndexes()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 24
cp 0
rs 8.9713
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 1
crap 6
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\db\ColumnSchema;
12
use yii\db\Connection;
13
use yii\db\Expression;
14
use yii\db\TableSchema;
15
16
/**
17
 * Schema is the class for retrieving metadata from an Oracle database
18
 *
19
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
20
 * sequence object. This property is read-only.
21
 *
22
 * @author Qiang Xue <[email protected]>
23
 * @since 2.0
24
 */
25
class Schema extends \yii\db\Schema
26
{
27
    /**
28
     * @var array map of DB errors and corresponding exceptions
29
     * If left part is found in DB error message exception class from the right part is used.
30
     */
31
    public $exceptionMap = [
32
        'ORA-00001: unique constraint' => 'yii\db\IntegrityException',
33
    ];
34
35
36
    /**
37
     * @inheritdoc
38
     */
39
    public function init()
40
    {
41
        parent::init();
42
        if ($this->defaultSchema === null) {
43
            $this->defaultSchema = strtoupper($this->db->username);
44
        }
45
    }
46
47
    /**
48
     * @inheritdoc
49
     */
50
    public function releaseSavepoint($name)
51
    {
52
        // does nothing as Oracle does not support this
53
    }
54
55
    /**
56
     * @inheritdoc
57
     */
58
    public function quoteSimpleTableName($name)
59
    {
60
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
61
    }
62
63
    /**
64
     * @inheritdoc
65
     */
66
    public function createQueryBuilder()
67
    {
68
        return new QueryBuilder($this->db);
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74
    public function createColumnSchemaBuilder($type, $length = null)
75
    {
76
        return new ColumnSchemaBuilder($type, $length, $this->db);
77
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82
    public function loadTableSchema($name)
83
    {
84
        $table = new TableSchema();
85
        $this->resolveTableNames($table, $name);
86
87
        if ($this->findColumns($table)) {
88
            $this->findConstraints($table);
89
90
            return $table;
91
        } else {
92
            return null;
93
        }
94
    }
95
96
    /**
97
     * Resolves the table name and schema name (if any).
98
     *
99
     * @param TableSchema $table the table metadata object
100
     * @param string $name the table name
101
     */
102
    protected function resolveTableNames($table, $name)
103
    {
104
        $parts = explode('.', str_replace('"', '', $name));
105
        if (isset($parts[1])) {
106
            $table->schemaName = $parts[0];
107
            $table->name = $parts[1];
108
        } else {
109
            $table->schemaName = $this->defaultSchema;
110
            $table->name = $name;
111
        }
112
113
        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
114
    }
115
116
    /**
117
     * Collects the table column metadata.
118
     * @param TableSchema $table the table schema
119
     * @return bool whether the table exists
120
     */
121
    protected function findColumns($table)
122
    {
123
        $sql = <<<SQL
124
SELECT 
125
    A.COLUMN_NAME, 
126
    A.DATA_TYPE, 
127
    A.DATA_PRECISION, 
128
    A.DATA_SCALE, 
129
    A.DATA_LENGTH,
130
    A.NULLABLE, 
131
    A.DATA_DEFAULT,
132
    COM.COMMENTS AS COLUMN_COMMENT
133
FROM ALL_TAB_COLUMNS A
134
    INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME)
135
    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)
136
WHERE
137
    A.OWNER = :schemaName
138
    AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
139
    AND B.OBJECT_NAME = :tableName
140
ORDER BY A.COLUMN_ID
141
SQL;
142
143
        try {
144
            $columns = $this->db->createCommand($sql, [
145
                ':tableName' => $table->name,
146
                ':schemaName' => $table->schemaName,
147
            ])->queryAll();
148
        } catch (\Exception $e) {
149
            return false;
150
        }
151
152
        if (empty($columns)) {
153
            return false;
154
        }
155
156
        foreach ($columns as $column) {
157
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
158
                $column = array_change_key_case($column, CASE_UPPER);
159
            }
160
            $c = $this->createColumn($column);
161
            $table->columns[$c->name] = $c;
162
        }
163
        return true;
164
    }
165
166
    /**
167
     * Sequence name of table
168
     *
169
     * @param string $tableName
170
     * @internal param \yii\db\TableSchema $table->name the table schema
171
     * @return string|null whether the sequence exists
172
     */
173
    protected function getTableSequenceName($tableName)
174
    {
175
176
        $sequenceNameSql = <<<SQL
177
SELECT 
178
    UD.REFERENCED_NAME AS SEQUENCE_NAME
179
FROM USER_DEPENDENCIES UD
180
    JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME)
181
WHERE 
182
    UT.TABLE_NAME = :tableName
183
    AND UD.TYPE = 'TRIGGER'
184
    AND UD.REFERENCED_TYPE = 'SEQUENCE'
185
SQL;
186
        $sequenceName = $this->db->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar();
187
        return $sequenceName === false ? null : $sequenceName;
188
    }
189
190
    /**
191
     * @Overrides method in class 'Schema'
192
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this
193
     *
194
     * Returns the ID of the last inserted row or sequence value.
195
     * @param string $sequenceName name of the sequence object (required by some DBMS)
196
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
197
     * @throws InvalidCallException if the DB connection is not active
198
     */
199
    public function getLastInsertID($sequenceName = '')
200
    {
201
        if ($this->db->isActive) {
202
            // get the last insert id from the master connection
203
            $sequenceName = $this->quoteSimpleTableName($sequenceName);
204
            return $this->db->useMaster(function (Connection $db) use ($sequenceName) {
205
                return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar();
206
            });
207
        } else {
208
            throw new InvalidCallException('DB Connection is not active.');
209
        }
210
    }
211
212
    /**
213
     * Creates ColumnSchema instance
214
     *
215
     * @param array $column
216
     * @return ColumnSchema
217
     */
218
    protected function createColumn($column)
219
    {
220
        $c = $this->createColumnSchema();
221
        $c->name = $column['COLUMN_NAME'];
222
        $c->allowNull = $column['NULLABLE'] === 'Y';
223
        $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT'];
224
        $c->isPrimaryKey = false;
225
        $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']);
226
        $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']);
227
228
        $c->phpType = $this->getColumnPhpType($c);
229
230
        if (!$c->isPrimaryKey) {
231
            if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) {
232
                $c->defaultValue = null;
233
            } else {
234
                $defaultValue = $column['DATA_DEFAULT'];
235
                if ($c->type === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') {
236
                    $c->defaultValue = new Expression('CURRENT_TIMESTAMP');
237
                } else {
238
                    if ($defaultValue !== null) {
239
                        if (($len = strlen($defaultValue)) > 2 && $defaultValue[0] === "'"
240
                            && $defaultValue[$len - 1] === "'"
241
                        ) {
242
                            $defaultValue = substr($column['DATA_DEFAULT'], 1, -1);
243
                        } else {
244
                            $defaultValue = trim($defaultValue);
245
                        }
246
                    }
247
                    $c->defaultValue = $c->phpTypecast($defaultValue);
248
                }
249
            }
250
        }
251
252
        return $c;
253
    }
254
255
    /**
256
     * Finds constraints and fills them into TableSchema object passed
257
     * @param TableSchema $table
258
     */
259
    protected function findConstraints($table)
260
    {
261
        $sql = <<<SQL
262
SELECT
263
    /*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */
264
    D.CONSTRAINT_NAME,
265
    D.CONSTRAINT_TYPE,
266
    C.COLUMN_NAME,
267
    C.POSITION,
268
    D.R_CONSTRAINT_NAME,
269
    E.TABLE_NAME AS TABLE_REF,
270
    F.COLUMN_NAME AS COLUMN_REF,
271
    C.TABLE_NAME
272
FROM ALL_CONS_COLUMNS C
273
    INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
274
    LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
275
    LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
276
WHERE
277
    C.OWNER = :schemaName
278
    AND C.TABLE_NAME = :tableName
279
ORDER BY D.CONSTRAINT_NAME, C.POSITION
280
SQL;
281
        $command = $this->db->createCommand($sql, [
282
            ':tableName' => $table->name,
283
            ':schemaName' => $table->schemaName,
284
        ]);
285
        $constraints = [];
286
        foreach ($command->queryAll() as $row) {
287
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
288
                $row = array_change_key_case($row, CASE_UPPER);
289
            }
290
291
            if ($row['CONSTRAINT_TYPE'] === 'P') {
292
                $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true;
293
                $table->primaryKey[] = $row['COLUMN_NAME'];
294
                if (empty($table->sequenceName)) {
295
                    $table->sequenceName = $this->getTableSequenceName($table->name);
296
                }
297
            }
298
299
            if ($row['CONSTRAINT_TYPE'] !== 'R') {
300
                // this condition is not checked in SQL WHERE because of an Oracle Bug:
301
                // see https://github.com/yiisoft/yii2/pull/8844
302
                continue;
303
            }
304
305
            $name = $row['CONSTRAINT_NAME'];
306
            if (!isset($constraints[$name])) {
307
                $constraints[$name] = [
308
                    'tableName' => $row['TABLE_REF'],
309
                    'columns' => [],
310
                ];
311
            }
312
            $constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF'];
313
        }
314
315
        foreach ($constraints as $constraint) {
316
            $name = array_keys($constraint);
317
            $name = current($name);
318
319
            $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
320
        }
321
    }
322
323
    /**
324
     * @inheritdoc
325
     */
326
    protected function findSchemaNames()
327
    {
328
        $sql = <<<SQL
329
SELECT
330
    USERNAME
331
FROM DBA_USERS U
332
WHERE 
333
    EXISTS (SELECT 1 FROM DBA_OBJECTS O WHERE O.OWNER = U.USERNAME)
334
    AND DEFAULT_TABLESPACE NOT IN ('SYSTEM','SYSAUX')
335
SQL;
336
        return $this->db->createCommand($sql)->queryColumn();
337
    }
338
339
    /**
340
     * @inheritdoc
341
     */
342
    protected function findTableNames($schema = '')
343
    {
344
        if ($schema === '') {
345
            $sql = <<<SQL
346
SELECT 
347
    TABLE_NAME 
348
FROM USER_TABLES
349
UNION ALL
350
SELECT 
351
    VIEW_NAME AS TABLE_NAME 
352
FROM USER_VIEWS
353
UNION ALL
354
SELECT 
355
    MVIEW_NAME AS TABLE_NAME 
356
FROM USER_MVIEWS
357
ORDER BY TABLE_NAME
358
SQL;
359
            $command = $this->db->createCommand($sql);
360
        } else {
361
            $sql = <<<SQL
362
SELECT 
363
    OBJECT_NAME AS TABLE_NAME
364
FROM ALL_OBJECTS
365
WHERE 
366
    OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') 
367
    AND OWNER = :schema
368
ORDER BY OBJECT_NAME
369
SQL;
370
            $command = $this->db->createCommand($sql, [':schema' => $schema]);
371
        }
372
373
        $rows = $command->queryAll();
374
        $names = [];
375
        foreach ($rows as $row) {
376
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
377
                $row = array_change_key_case($row, CASE_UPPER);
378
            }
379
            $names[] = $row['TABLE_NAME'];
380
        }
381
        return $names;
382
    }
383
384
    /**
385
     * Returns all unique indexes for the given table.
386
     * Each array element is of the following structure:
387
     *
388
     * ```php
389
     * [
390
     *     'IndexName1' => ['col1' [, ...]],
391
     *     'IndexName2' => ['col2' [, ...]],
392
     * ]
393
     * ```
394
     *
395
     * @param TableSchema $table the table metadata
396
     * @return array all unique indexes for the given table.
397
     * @since 2.0.4
398
     */
399
    public function findUniqueIndexes($table)
400
    {
401
        $query = <<<SQL
402
SELECT 
403
    DIC.INDEX_NAME,
404
    DIC.COLUMN_NAME
405
FROM ALL_INDEXES DI
406
    INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME
407
WHERE 
408
    DI.UNIQUENESS = 'UNIQUE'
409
    AND DIC.TABLE_OWNER = :schemaName
410
    AND DIC.TABLE_NAME = :tableName
411
ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION
412
SQL;
413
        $result = [];
414
        $command = $this->db->createCommand($query, [
415
            ':tableName' => $table->name,
416
            ':schemaName' => $table->schemaName,
417
        ]);
418
        foreach ($command->queryAll() as $row) {
419
            $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME'];
420
        }
421
        return $result;
422
    }
423
424
    /**
425
     * Extracts the data types for the given column
426
     * @param ColumnSchema $column
427
     * @param string $dbType DB type
428
     * @param string $precision total number of digits.
429
     * This parameter is available since version 2.0.4.
430
     * @param string $scale number of digits on the right of the decimal separator.
431
     * This parameter is available since version 2.0.4.
432
     * @param string $length length for character types.
433
     * This parameter is available since version 2.0.4.
434
     */
435
    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...
436
    {
437
        $column->dbType = $dbType;
438
439
        if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) {
440
            $column->type = 'double';
441
        } elseif (strpos($dbType, 'NUMBER') !== false) {
442
            if ($scale === null || $scale > 0) {
443
                $column->type = 'decimal';
444
            } else {
445
                $column->type = 'integer';
446
            }
447
        } elseif (strpos($dbType, 'INTEGER') !== false) {
448
            $column->type = 'integer';
449
        } elseif (strpos($dbType, 'BLOB') !== false) {
450
            $column->type = 'binary';
451
        } elseif (strpos($dbType, 'CLOB') !== false) {
452
            $column->type = 'text';
453
        } elseif (strpos($dbType, 'TIMESTAMP') !== false) {
454
            $column->type = 'timestamp';
455
        } else {
456
            $column->type = 'string';
457
        }
458
    }
459
460
    /**
461
     * Extracts size, precision and scale information from column's DB type.
462
     * @param ColumnSchema $column
463
     * @param string $dbType the column's DB type
464
     * @param string $precision total number of digits.
465
     * This parameter is available since version 2.0.4.
466
     * @param string $scale number of digits on the right of the decimal separator.
467
     * This parameter is available since version 2.0.4.
468
     * @param string $length length for character types.
469
     * This parameter is available since version 2.0.4.
470
     */
471
    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...
472
    {
473
        $column->size = trim($length) === '' ? null : (int)$length;
474
        $column->precision = trim($precision) === '' ? null : (int)$precision;
475
        $column->scale = trim($scale) === '' ? null : (int)$scale;
476
    }
477
478
    /**
479
     * @inheritdoc
480
     */
481
    public function insert($table, $columns)
482
    {
483
        $params = [];
484
        $returnParams = [];
485
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
486
        $tableSchema = $this->getTableSchema($table);
487
        $returnColumns = $tableSchema->primaryKey;
488
        if (!empty($returnColumns)) {
489
            $columnSchemas = $tableSchema->columns;
490
            $returning = [];
491
            foreach ((array)$returnColumns as $name) {
492
                $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams));
493
                $returnParams[$phName] = [
494
                    'column' => $name,
495
                    'value' => null,
496
                ];
497
                if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->phpType !== 'integer') {
498
                    $returnParams[$phName]['dataType'] = \PDO::PARAM_STR;
499
                } else {
500
                    $returnParams[$phName]['dataType'] = \PDO::PARAM_INT;
501
                }
502
                $returnParams[$phName]['size'] = isset($columnSchemas[$name]) && isset($columnSchemas[$name]->size) ? $columnSchemas[$name]->size : -1;
503
                $returning[] = $this->quoteColumnName($name);
504
            }
505
            $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams));
506
        }
507
508
        $command = $this->db->createCommand($sql, $params);
509
        $command->prepare(false);
510
511
        foreach ($returnParams as $name => &$value) {
512
            $command->pdoStatement->bindParam($name, $value['value'], $value['dataType'], $value['size']);
513
        }
514
515
        if (!$command->execute()) {
516
            return false;
517
        }
518
519
        $result = [];
520
        foreach ($returnParams as $value) {
521
            $result[$value['column']] = $value['value'];
522
        }
523
524
        return $result;
525
    }
526
}
527