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 ( ecd2dc...aafa57 )
by Robert
18:49
created

Schema::getViewNames()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
ccs 5
cts 5
cp 1
cc 3
eloc 4
nc 2
nop 2
crap 3
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\pgsql;
9
10
use yii\db\Expression;
11
use yii\db\TableSchema;
12
use yii\db\ColumnSchema;
13
use yii\db\ViewFinderTrait;
14
15
/**
16
 * Schema is the class for retrieving metadata from a PostgreSQL database
17
 * (version 9.x and above).
18
 *
19
 * @property string[] $viewNames All view names in the database. This property is read-only.
20
 *
21
 * @author Gevik Babakhani <[email protected]>
22
 * @since 2.0
23
 */
24
class Schema extends \yii\db\Schema
25
{
26
    use ViewFinderTrait;
27
28
    /**
29
     * @var string the default schema used for the current session.
30
     */
31
    public $defaultSchema = 'public';
32
    /**
33
     * @var array mapping from physical column types (keys) to abstract
34
     * column types (values)
35
     * @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE
36
     */
37
    public $typeMap = [
38
        'bit' => self::TYPE_INTEGER,
39
        'bit varying' => self::TYPE_INTEGER,
40
        'varbit' => self::TYPE_INTEGER,
41
42
        'bool' => self::TYPE_BOOLEAN,
43
        'boolean' => self::TYPE_BOOLEAN,
44
45
        'box' => self::TYPE_STRING,
46
        'circle' => self::TYPE_STRING,
47
        'point' => self::TYPE_STRING,
48
        'line' => self::TYPE_STRING,
49
        'lseg' => self::TYPE_STRING,
50
        'polygon' => self::TYPE_STRING,
51
        'path' => self::TYPE_STRING,
52
53
        'character' => self::TYPE_CHAR,
54
        'char' => self::TYPE_CHAR,
55
        'bpchar' => self::TYPE_CHAR,
56
        'character varying' => self::TYPE_STRING,
57
        'varchar' => self::TYPE_STRING,
58
        'text' => self::TYPE_TEXT,
59
60
        'bytea' => self::TYPE_BINARY,
61
62
        'cidr' => self::TYPE_STRING,
63
        'inet' => self::TYPE_STRING,
64
        'macaddr' => self::TYPE_STRING,
65
66
        'real' => self::TYPE_FLOAT,
67
        'float4' => self::TYPE_FLOAT,
68
        'double precision' => self::TYPE_DOUBLE,
69
        'float8' => self::TYPE_DOUBLE,
70
        'decimal' => self::TYPE_DECIMAL,
71
        'numeric' => self::TYPE_DECIMAL,
72
73
        'money' => self::TYPE_MONEY,
74
75
        'smallint' => self::TYPE_SMALLINT,
76
        'int2' => self::TYPE_SMALLINT,
77
        'int4' => self::TYPE_INTEGER,
78
        'int' => self::TYPE_INTEGER,
79
        'integer' => self::TYPE_INTEGER,
80
        'bigint' => self::TYPE_BIGINT,
81
        'int8' => self::TYPE_BIGINT,
82
        'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
83
84
        'smallserial' => self::TYPE_SMALLINT,
85
        'serial2' => self::TYPE_SMALLINT,
86
        'serial4' => self::TYPE_INTEGER,
87
        'serial' => self::TYPE_INTEGER,
88
        'bigserial' => self::TYPE_BIGINT,
89
        'serial8' => self::TYPE_BIGINT,
90
        'pg_lsn' => self::TYPE_BIGINT,
91
92
        'date' => self::TYPE_DATE,
93
        'interval' => self::TYPE_STRING,
94
        'time without time zone' => self::TYPE_TIME,
95
        'time' => self::TYPE_TIME,
96
        'time with time zone' => self::TYPE_TIME,
97
        'timetz' => self::TYPE_TIME,
98
        'timestamp without time zone' => self::TYPE_TIMESTAMP,
99
        'timestamp' => self::TYPE_TIMESTAMP,
100
        'timestamp with time zone' => self::TYPE_TIMESTAMP,
101
        'timestamptz' => self::TYPE_TIMESTAMP,
102
        'abstime' => self::TYPE_TIMESTAMP,
103
104
        'tsquery' => self::TYPE_STRING,
105
        'tsvector' => self::TYPE_STRING,
106
        'txid_snapshot' => self::TYPE_STRING,
107
108
        'unknown' => self::TYPE_STRING,
109
110
        'uuid' => self::TYPE_STRING,
111
        'json' => self::TYPE_STRING,
112
        'jsonb' => self::TYPE_STRING,
113
        'xml' => self::TYPE_STRING,
114
    ];
115
116
117
    /**
118
     * Creates a query builder for the PostgreSQL database.
119
     * @return QueryBuilder query builder instance
120
     */
121 155
    public function createQueryBuilder()
122
    {
123 155
        return new QueryBuilder($this->db);
124
    }
125
126
    /**
127
     * Resolves the table name and schema name (if any).
128
     * @param TableSchema $table the table metadata object
129
     * @param string $name the table name
130
     */
131 147
    protected function resolveTableNames($table, $name)
132
    {
133 147
        $parts = explode('.', str_replace('"', '', $name));
134
135 147
        if (isset($parts[1])) {
136
            $table->schemaName = $parts[0];
137
            $table->name = $parts[1];
138
        } else {
139 147
            $table->schemaName = $this->defaultSchema;
140 147
            $table->name = $name;
141
        }
142
143 147
        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
144 147
    }
145
146
    /**
147
     * Quotes a table name for use in a query.
148
     * A simple table name has no schema prefix.
149
     * @param string $name table name
150
     * @return string the properly quoted table name
151
     */
152 193
    public function quoteSimpleTableName($name)
153
    {
154 193
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
155
    }
156
157
    /**
158
     * Loads the metadata for the specified table.
159
     * @param string $name table name
160
     * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
161
     */
162 147
    public function loadTableSchema($name)
163
    {
164 147
        $table = new TableSchema();
165 147
        $this->resolveTableNames($table, $name);
166 147
        if ($this->findColumns($table)) {
167 144
            $this->findConstraints($table);
168 144
            return $table;
169
        } else {
170 8
            return null;
171
        }
172
    }
173
174
    /**
175
     * Returns all schema names in the database, including the default one but not system schemas.
176
     * This method should be overridden by child classes in order to support this feature
177
     * because the default implementation simply throws an exception.
178
     * @return array all schema names in the database, except system schemas
179
     * @since 2.0.4
180
     */
181 1
    protected function findSchemaNames()
182
    {
183
        $sql = <<<SQL
184
SELECT ns.nspname AS schema_name
185
FROM pg_namespace ns
186
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%'
187
ORDER BY ns.nspname
188 1
SQL;
189 1
        return $this->db->createCommand($sql)->queryColumn();
190
    }
191
192
    /**
193
     * Returns all table names in the database.
194
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
195
     * @return array all table names in the database. The names have NO schema name prefix.
196
     */
197 7
    protected function findTableNames($schema = '')
198
    {
199 7
        if ($schema === '') {
200 5
            $schema = $this->defaultSchema;
201 5
        }
202
        $sql = <<<SQL
203
SELECT c.relname AS table_name
204
FROM pg_class c
205
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
206
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f')
207
ORDER BY c.relname
208 7
SQL;
209 7
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
210
    }
211
212
    /**
213
     * @inheritdoc
214
     */
215 2
    protected function findViewNames($schema = '')
216
    {
217 2
        if ($schema === '') {
218
            $schema = $this->defaultSchema;
219
        }
220
        $sql = <<<SQL
221
SELECT c.relname AS table_name
222
FROM pg_class c
223
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
224
WHERE ns.nspname = :schemaName AND c.relkind = 'v'
225
ORDER BY c.relname
226 2
SQL;
227 2
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
228
    }
229
230
    /**
231
     * Collects the foreign key column details for the given table.
232
     * @param TableSchema $table the table metadata
233
     */
234 144
    protected function findConstraints($table)
235
    {
236 144
        $tableName = $this->quoteValue($table->name);
237 144
        $tableSchema = $this->quoteValue($table->schemaName);
238
239
        //We need to extract the constraints de hard way since:
240
        //http://www.postgresql.org/message-id/[email protected]
241
242
        $sql = <<<SQL
243
select
244
    ct.conname as constraint_name,
245
    a.attname as column_name,
246
    fc.relname as foreign_table_name,
247
    fns.nspname as foreign_table_schema,
248
    fa.attname as foreign_column_name
249
from
250
    (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s
251
       FROM pg_constraint ct
252
    ) AS ct
253
    inner join pg_class c on c.oid=ct.conrelid
254
    inner join pg_namespace ns on c.relnamespace=ns.oid
255
    inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s]
256
    left join pg_class fc on fc.oid=ct.confrelid
257
    left join pg_namespace fns on fc.relnamespace=fns.oid
258
    left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s]
259
where
260
    ct.contype='f'
261 144
    and c.relname={$tableName}
262 144
    and ns.nspname={$tableSchema}
263
order by
264 144
    fns.nspname, fc.relname, a.attnum
265 144
SQL;
266
267 144
        $constraints = [];
268 144
        foreach ($this->db->createCommand($sql)->queryAll() as $constraint) {
269 56
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
270
                $constraint = array_change_key_case($constraint, CASE_LOWER);
271
            }
272 56
            if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
273
                $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
274
            } else {
275 56
                $foreignTable = $constraint['foreign_table_name'];
276
            }
277 56
            $name = $constraint['constraint_name'];
278 56
            if (!isset($constraints[$name])) {
279 56
                $constraints[$name] = [
280 56
                    'tableName' => $foreignTable,
281 56
                    'columns' => [],
282
                ];
283 56
            }
284 56
            $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];
285 144
        }
286 144
        foreach ($constraints as $name => $constraint) {
287 56
            $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
288 144
        }
289 144
    }
290
291
    /**
292
     * Gets information about given table unique indexes.
293
     * @param TableSchema $table the table metadata
294
     * @return array with index and column names
295
     */
296 1
    protected function getUniqueIndexInformation($table)
297
    {
298
        $sql = <<<SQL
299
SELECT
300
    i.relname as indexname,
301
    pg_get_indexdef(idx.indexrelid, k + 1, TRUE) AS columnname
302
FROM (
303
  SELECT *, generate_subscripts(indkey, 1) AS k
304
  FROM pg_index
305
) idx
306
INNER JOIN pg_class i ON i.oid = idx.indexrelid
307
INNER JOIN pg_class c ON c.oid = idx.indrelid
308
INNER JOIN pg_namespace ns ON c.relnamespace = ns.oid
309
WHERE idx.indisprimary = FALSE AND idx.indisunique = TRUE
310
AND c.relname = :tableName AND ns.nspname = :schemaName
311
ORDER BY i.relname, k
312 1
SQL;
313
314 1
        return $this->db->createCommand($sql, [
315 1
            ':schemaName' => $table->schemaName,
316 1
            ':tableName' => $table->name,
317 1
        ])->queryAll();
318
    }
319
320
    /**
321
     * Returns all unique indexes for the given table.
322
     * Each array element is of the following structure:
323
     *
324
     * ```php
325
     * [
326
     *     'IndexName1' => ['col1' [, ...]],
327
     *     'IndexName2' => ['col2' [, ...]],
328
     * ]
329
     * ```
330
     *
331
     * @param TableSchema $table the table metadata
332
     * @return array all unique indexes for the given table.
333
     */
334 1
    public function findUniqueIndexes($table)
335
    {
336 1
        $uniqueIndexes = [];
337
338 1
        $rows = $this->getUniqueIndexInformation($table);
339 1
        foreach ($rows as $row) {
340 1
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
341
                $row = array_change_key_case($row, CASE_LOWER);
342
            }
343 1
            $column = $row['columnname'];
344 1
            if (!empty($column) && $column[0] === '"') {
345
                // postgres will quote names that are not lowercase-only
346
                // https://github.com/yiisoft/yii2/issues/10613
347 1
                $column = substr($column, 1, -1);
348 1
            }
349 1
            $uniqueIndexes[$row['indexname']][] = $column;
350 1
        }
351
352 1
        return $uniqueIndexes;
353
    }
354
355
    /**
356
     * Collects the metadata of table columns.
357
     * @param TableSchema $table the table metadata
358
     * @return bool whether the table exists in the database
359
     */
360 147
    protected function findColumns($table)
361
    {
362 147
        $tableName = $this->db->quoteValue($table->name);
363 147
        $schemaName = $this->db->quoteValue($table->schemaName);
364
        $sql = <<<SQL
365
SELECT
366
    d.nspname AS table_schema,
367
    c.relname AS table_name,
368
    a.attname AS column_name,
369
    t.typname AS data_type,
370
    a.attlen AS character_maximum_length,
371
    pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
372
    a.atttypmod AS modifier,
373
    a.attnotnull = false AS is_nullable,
374
    CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
375
    coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
376
    array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
377
    CASE atttypid
378
         WHEN 21 /*int2*/ THEN 16
379
         WHEN 23 /*int4*/ THEN 32
380
         WHEN 20 /*int8*/ THEN 64
381
         WHEN 1700 /*numeric*/ THEN
382
              CASE WHEN atttypmod = -1
383
               THEN null
384
               ELSE ((atttypmod - 4) >> 16) & 65535
385
               END
386
         WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
387
         WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
388
         ELSE null
389
      END   AS numeric_precision,
390
      CASE
391
        WHEN atttypid IN (21, 23, 20) THEN 0
392
        WHEN atttypid IN (1700) THEN
393
        CASE
394
            WHEN atttypmod = -1 THEN null
395
            ELSE (atttypmod - 4) & 65535
396
        END
397
           ELSE null
398
      END AS numeric_scale,
399
    CAST(
400
             information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
401
             AS numeric
402
    ) AS size,
403
    a.attnum = any (ct.conkey) as is_pkey
404
FROM
405
    pg_class c
406
    LEFT JOIN pg_attribute a ON a.attrelid = c.oid
407
    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
408
    LEFT JOIN pg_type t ON a.atttypid = t.oid
409
    LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
410
    LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
411
WHERE
412
    a.attnum > 0 and t.typname != ''
413 147
    and c.relname = {$tableName}
414 147
    and d.nspname = {$schemaName}
415
ORDER BY
416 147
    a.attnum;
417 147
SQL;
418
419 147
        $columns = $this->db->createCommand($sql)->queryAll();
420 147
        if (empty($columns)) {
421 8
            return false;
422
        }
423 144
        foreach ($columns as $column) {
424 144
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
425
                $column = array_change_key_case($column, CASE_LOWER);
426
            }
427 144
            $column = $this->loadColumnSchema($column);
428 144
            $table->columns[$column->name] = $column;
429 144
            if ($column->isPrimaryKey) {
430 124
                $table->primaryKey[] = $column->name;
431 124
                if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {
432 115
                    $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace(array('/nex... $column->defaultValue) can also be of type array<integer,string>. However, the property $sequenceName is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
433 115
                }
434 124
                $column->defaultValue = null;
435 144
            } elseif ($column->defaultValue) {
436 91
                if ($column->type === 'timestamp' && $column->defaultValue === 'now()') {
437 21
                    $column->defaultValue = new Expression($column->defaultValue);
438 91
                } elseif ($column->type === 'boolean') {
439 86
                        $column->defaultValue = ($column->defaultValue === 'true');
440 91
                } elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) {
441 21
                    $column->defaultValue = bindec(trim($column->defaultValue, 'B\''));
442 27
                } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {
443 21
                    $column->defaultValue = $matches[1];
444 27
                } elseif (preg_match('/^(?:\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) {
445 5
                    if ($matches[1] === 'NULL') {
446
                        $column->defaultValue = null;
447
                    } else {
448 5
                        $column->defaultValue = $column->phpTypecast($matches[1]);
449
                    }
450 5
                } else {
451 25
                    $column->defaultValue = $column->phpTypecast($column->defaultValue);
452
                }
453 91
            }
454 144
        }
455
456 144
        return true;
457
    }
458
459
    /**
460
     * Loads the column information into a [[ColumnSchema]] object.
461
     * @param array $info column information
462
     * @return ColumnSchema the column schema object
463
     */
464 144
    protected function loadColumnSchema($info)
465
    {
466 144
        $column = $this->createColumnSchema();
467 144
        $column->allowNull = $info['is_nullable'];
468 144
        $column->autoIncrement = $info['is_autoinc'];
469 144
        $column->comment = $info['column_comment'];
470 144
        $column->dbType = $info['data_type'];
471 144
        $column->defaultValue = $info['column_default'];
472 144
        $column->enumValues = ($info['enum_values'] !== null) ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like $info['enum_values'] !==...'enum_values'])) : null can be null. However, the property $enumValues is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
473 144
        $column->unsigned = false; // has no meaning in PG
474 144
        $column->isPrimaryKey = $info['is_pkey'];
475 144
        $column->name = $info['column_name'];
476 144
        $column->precision = $info['numeric_precision'];
477 144
        $column->scale = $info['numeric_scale'];
478 144
        $column->size = $info['size'] === null ? null : (int) $info['size'];
479 144
        if (isset($this->typeMap[$column->dbType])) {
480 144
            $column->type = $this->typeMap[$column->dbType];
481 144
        } else {
482
            $column->type = self::TYPE_STRING;
483
        }
484 144
        $column->phpType = $this->getColumnPhpType($column);
485
486 144
        return $column;
487
    }
488
489
    /**
490
     * @inheritdoc
491
     */
492 27
    public function insert($table, $columns)
493
    {
494 27
        $params = [];
495 27
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
496 27
        $returnColumns = $this->getTableSchema($table)->primaryKey;
497 27
        if (!empty($returnColumns)) {
498 19
            $returning = [];
499 19
            foreach ((array) $returnColumns as $name) {
500 19
                $returning[] = $this->quoteColumnName($name);
501 19
            }
502 19
            $sql .= ' RETURNING ' . implode(', ', $returning);
503 19
        }
504
505 27
        $command = $this->db->createCommand($sql, $params);
506 27
        $command->prepare(false);
507 27
        $result = $command->queryOne();
508
509 27
        return !$command->pdoStatement->rowCount() ? false : $result;
510
    }
511
}
512