Completed
Push — ip-validator-message ( 93ed68...63eb47 )
by Dmitry
09:50
created

Schema::findColumns()   D

Complexity

Conditions 15
Paths 12

Size

Total Lines 95
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 15

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 95
ccs 38
cts 38
cp 1
rs 4.9121
cc 15
eloc 36
nc 12
nop 1
crap 15

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