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

Schema   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 685
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 92.99%

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 16
dl 0
loc 685
ccs 199
cts 214
cp 0.9299
rs 2.349
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveTableName() 0 14 3
A findSchemaNames() 0 10 1
A findTableNames() 0 14 2
A loadTablePrimaryKey() 0 4 1
A loadTableForeignKeys() 0 4 1
B loadTableIndexes() 0 39 2
A loadTableUniques() 0 4 1
A loadTableChecks() 0 4 1
A loadTableDefaultValues() 0 4 1
A createQueryBuilder() 0 4 1
A resolveTableNames() 0 14 3
A quoteSimpleTableName() 0 4 2
A findViewNames() 0 14 2
B findConstraints() 0 56 6
A getUniqueIndexInformation() 0 23 1
B findUniqueIndexes() 0 20 5
D findColumns() 0 98 16
B loadColumnSchema() 0 24 4
A insert() 0 19 4
A loadTableSchema() 0 11 2
C loadTableConstraints() 0 91 10

How to fix   Complexity   

Complex Class

Complex classes like Schema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Schema, and based on these observations, apply Extract Interface, too.

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\base\NotSupportedException;
11
use yii\db\CheckConstraint;
12
use yii\db\Constraint;
13
use yii\db\ConstraintFinderTrait;
14
use yii\db\Expression;
15
use yii\db\ForeignKeyConstraint;
16
use yii\db\IndexConstraint;
17
use yii\db\TableSchema;
18
use yii\db\ViewFinderTrait;
19
use yii\helpers\ArrayHelper;
20
21
/**
22
 * Schema is the class for retrieving metadata from a PostgreSQL database
23
 * (version 9.x and above).
24
 *
25
 * @author Gevik Babakhani <[email protected]>
26
 * @since 2.0
27
 */
28
class Schema extends \yii\db\Schema
29
{
30
    use ViewFinderTrait;
31
    use ConstraintFinderTrait;
32
33
    /**
34
     * @var string the default schema used for the current session.
35
     */
36
    public $defaultSchema = 'public';
37
    /**
38
     * @var array mapping from physical column types (keys) to abstract
39
     * column types (values)
40
     * @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE
41
     */
42
    public $typeMap = [
43
        'bit' => self::TYPE_INTEGER,
44
        'bit varying' => self::TYPE_INTEGER,
45
        'varbit' => self::TYPE_INTEGER,
46
47
        'bool' => self::TYPE_BOOLEAN,
48
        'boolean' => self::TYPE_BOOLEAN,
49
50
        'box' => self::TYPE_STRING,
51
        'circle' => self::TYPE_STRING,
52
        'point' => self::TYPE_STRING,
53
        'line' => self::TYPE_STRING,
54
        'lseg' => self::TYPE_STRING,
55
        'polygon' => self::TYPE_STRING,
56
        'path' => self::TYPE_STRING,
57
58
        'character' => self::TYPE_CHAR,
59
        'char' => self::TYPE_CHAR,
60
        'bpchar' => self::TYPE_CHAR,
61
        'character varying' => self::TYPE_STRING,
62
        'varchar' => self::TYPE_STRING,
63
        'text' => self::TYPE_TEXT,
64
65
        'bytea' => self::TYPE_BINARY,
66
67
        'cidr' => self::TYPE_STRING,
68
        'inet' => self::TYPE_STRING,
69
        'macaddr' => self::TYPE_STRING,
70
71
        'real' => self::TYPE_FLOAT,
72
        'float4' => self::TYPE_FLOAT,
73
        'double precision' => self::TYPE_DOUBLE,
74
        'float8' => self::TYPE_DOUBLE,
75
        'decimal' => self::TYPE_DECIMAL,
76
        'numeric' => self::TYPE_DECIMAL,
77
78
        'money' => self::TYPE_MONEY,
79
80
        'smallint' => self::TYPE_SMALLINT,
81
        'int2' => self::TYPE_SMALLINT,
82
        'int4' => self::TYPE_INTEGER,
83
        'int' => self::TYPE_INTEGER,
84
        'integer' => self::TYPE_INTEGER,
85
        'bigint' => self::TYPE_BIGINT,
86
        'int8' => self::TYPE_BIGINT,
87
        'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
88
89
        'smallserial' => self::TYPE_SMALLINT,
90
        'serial2' => self::TYPE_SMALLINT,
91
        'serial4' => self::TYPE_INTEGER,
92
        'serial' => self::TYPE_INTEGER,
93
        'bigserial' => self::TYPE_BIGINT,
94
        'serial8' => self::TYPE_BIGINT,
95
        'pg_lsn' => self::TYPE_BIGINT,
96
97
        'date' => self::TYPE_DATE,
98
        'interval' => self::TYPE_STRING,
99
        'time without time zone' => self::TYPE_TIME,
100
        'time' => self::TYPE_TIME,
101
        'time with time zone' => self::TYPE_TIME,
102
        'timetz' => self::TYPE_TIME,
103
        'timestamp without time zone' => self::TYPE_TIMESTAMP,
104
        'timestamp' => self::TYPE_TIMESTAMP,
105
        'timestamp with time zone' => self::TYPE_TIMESTAMP,
106
        'timestamptz' => self::TYPE_TIMESTAMP,
107
        'abstime' => self::TYPE_TIMESTAMP,
108
109
        'tsquery' => self::TYPE_STRING,
110
        'tsvector' => self::TYPE_STRING,
111
        'txid_snapshot' => self::TYPE_STRING,
112
113
        'unknown' => self::TYPE_STRING,
114
115
        'uuid' => self::TYPE_STRING,
116
        'json' => self::TYPE_STRING,
117
        'jsonb' => self::TYPE_STRING,
118
        'xml' => self::TYPE_STRING,
119
    ];
120
121
122
    /**
123
     * @inheritDoc
124
     */
125 53
    protected function resolveTableName($name)
126
    {
127 53
        $resolvedName = new TableSchema();
128 53
        $parts = explode('.', str_replace('"', '', $name));
129 53
        if (isset($parts[1])) {
130
            $resolvedName->schemaName = $parts[0];
131
            $resolvedName->name = $parts[1];
132
        } else {
133 53
            $resolvedName->schemaName = $this->defaultSchema;
134 53
            $resolvedName->name = $name;
135
        }
136 53
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
137 53
        return $resolvedName;
138
    }
139
140
    /**
141
     * @inheritDoc
142
     */
143 1
    protected function findSchemaNames()
144
    {
145
        $sql = <<<SQL
146 1
SELECT ns.nspname AS schema_name
147
FROM pg_namespace ns
148
WHERE ns.nspname != 'information_schema' AND ns.nspname NOT LIKE 'pg_%'
149
ORDER BY ns.nspname
150
SQL;
151 1
        return $this->db->createCommand($sql)->queryColumn();
152
    }
153
154
    /**
155
     * @inheritDoc
156
     */
157 5
    protected function findTableNames($schema = '')
158
    {
159 5
        if ($schema === '') {
160 5
            $schema = $this->defaultSchema;
161
        }
162
        $sql = <<<SQL
163 5
SELECT c.relname AS table_name
164
FROM pg_class c
165
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
166
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f')
167
ORDER BY c.relname
168
SQL;
169 5
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
170
    }
171
172
    /**
173
     * @inheritDoc
174
     */
175 209
    protected function loadTableSchema($name)
176
    {
177 209
        $table = new TableSchema();
178 209
        $this->resolveTableNames($table, $name);
179 209
        if ($this->findColumns($table)) {
180 203
            $this->findConstraints($table);
181 203
            return $table;
182
        }
183
184 16
        return null;
185
    }
186
187
    /**
188
     * @inheritDoc
189
     */
190 13
    protected function loadTablePrimaryKey($tableName)
191
    {
192 13
        return $this->loadTableConstraints($tableName, 'primaryKey');
193
    }
194
195
    /**
196
     * @inheritDoc
197
     */
198 4
    protected function loadTableForeignKeys($tableName)
199
    {
200 4
        return $this->loadTableConstraints($tableName, 'foreignKeys');
201
    }
202
203
    /**
204
     * @inheritDoc
205
     */
206 10
    protected function loadTableIndexes($tableName)
207
    {
208 10
        static $sql = <<<SQL
209
SELECT
210
    "ic"."relname" AS "name",
211
    "ia"."attname" AS "column_name",
212
    "i"."indisunique" AS "index_is_unique",
213
    "i"."indisprimary" AS "index_is_primary"
214
FROM "pg_class" AS "tc"
215
INNER JOIN "pg_namespace" AS "tcns"
216
    ON "tcns"."oid" = "tc"."relnamespace"
217
INNER JOIN "pg_index" AS "i"
218
    ON "i"."indrelid" = "tc"."oid"
219
INNER JOIN "pg_class" AS "ic"
220
    ON "ic"."oid" = "i"."indexrelid"
221
INNER JOIN "pg_attribute" AS "ia"
222
    ON "ia"."attrelid" = "i"."indrelid" AND "ia"."attnum" = ANY ("i"."indkey")
223
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
224
ORDER BY "ia"."attnum" ASC
225
SQL;
226
227 10
        $resolvedName = $this->resolveTableName($tableName);
228 10
        $indexes = $this->db->createCommand($sql, [
229 10
            ':schemaName' => $resolvedName->schemaName,
230 10
            ':tableName' => $resolvedName->name,
231 10
        ])->queryAll();
232 10
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
233 10
        $indexes = ArrayHelper::index($indexes, null, 'name');
234 10
        $result = [];
235 10
        foreach ($indexes as $name => $index) {
236 7
            $result[] = new IndexConstraint([
237 7
                'isPrimary' => (bool) $index[0]['index_is_primary'],
238 7
                'isUnique' => (bool) $index[0]['index_is_unique'],
239 7
                'name' => $name,
240 7
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
241
            ]);
242
        }
243 10
        return $result;
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249 13
    protected function loadTableUniques($tableName)
250
    {
251 13
        return $this->loadTableConstraints($tableName, 'uniques');
252
    }
253
254
    /**
255
     * @inheritDoc
256
     */
257 13
    protected function loadTableChecks($tableName)
258
    {
259 13
        return $this->loadTableConstraints($tableName, 'checks');
260
    }
261
262
    /**
263
     * @inheritDoc
264
     * @throws NotSupportedException if this method is called.
265
     */
266 12
    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...
267
    {
268 12
        throw new NotSupportedException('PostgreSQL does not support default value constraints.');
269
    }
270
271
    /**
272
     * Creates a query builder for the PostgreSQL database.
273
     * @return QueryBuilder query builder instance
274
     */
275 219
    public function createQueryBuilder()
276
    {
277 219
        return new QueryBuilder($this->db);
278
    }
279
280
    /**
281
     * Resolves the table name and schema name (if any).
282
     * @param TableSchema $table the table metadata object
283
     * @param string $name the table name
284
     */
285 209
    protected function resolveTableNames($table, $name)
286
    {
287 209
        $parts = explode('.', str_replace('"', '', $name));
288
289 209
        if (isset($parts[1])) {
290
            $table->schemaName = $parts[0];
291
            $table->name = $parts[1];
292
        } else {
293 209
            $table->schemaName = $this->defaultSchema;
294 209
            $table->name = $name;
295
        }
296
297 209
        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
298 209
    }
299
300
    /**
301
     * Quotes a table name for use in a query.
302
     * A simple table name has no schema prefix.
303
     * @param string $name table name
304
     * @return string the properly quoted table name
305
     */
306 273
    public function quoteSimpleTableName($name)
307
    {
308 273
        return strpos($name, '"') !== false ? $name : '"' . $name . '"';
309
    }
310
311
    /**
312
     * @inheritdoc
313
     */
314
    protected function findViewNames($schema = '')
315
    {
316
        if ($schema === '') {
317
            $schema = $this->defaultSchema;
318
        }
319
        $sql = <<<SQL
320
SELECT c.relname AS table_name
321
FROM pg_class c
322
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
323
WHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm')
324
ORDER BY c.relname
325
SQL;
326
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
327
    }
328
329
    /**
330
     * Collects the foreign key column details for the given table.
331
     * @param TableSchema $table the table metadata
332
     */
333 203
    protected function findConstraints($table)
334
    {
335 203
        $tableName = $this->quoteValue($table->name);
336 203
        $tableSchema = $this->quoteValue($table->schemaName);
337
338
        //We need to extract the constraints de hard way since:
339
        //http://www.postgresql.org/message-id/[email protected]
340
341
        $sql = <<<SQL
342
select
343
    ct.conname as constraint_name,
344
    a.attname as column_name,
345
    fc.relname as foreign_table_name,
346
    fns.nspname as foreign_table_schema,
347
    fa.attname as foreign_column_name
348
from
349
    (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s
350
       FROM pg_constraint ct
351
    ) AS ct
352
    inner join pg_class c on c.oid=ct.conrelid
353
    inner join pg_namespace ns on c.relnamespace=ns.oid
354
    inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s]
355
    left join pg_class fc on fc.oid=ct.confrelid
356
    left join pg_namespace fns on fc.relnamespace=fns.oid
357
    left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s]
358
where
359
    ct.contype='f'
360 203
    and c.relname={$tableName}
361 203
    and ns.nspname={$tableSchema}
362
order by
363
    fns.nspname, fc.relname, a.attnum
364
SQL;
365
366 203
        $constraints = [];
367 203
        foreach ($this->db->createCommand($sql)->queryAll() as $constraint) {
368 105
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
369
                $constraint = array_change_key_case($constraint, CASE_LOWER);
370
            }
371 105
            if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
372
                $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
373
            } else {
374 105
                $foreignTable = $constraint['foreign_table_name'];
375
            }
376 105
            $name = $constraint['constraint_name'];
377 105
            if (!isset($constraints[$name])) {
378 105
                $constraints[$name] = [
379 105
                    'tableName' => $foreignTable,
380
                    'columns' => [],
381
                ];
382
            }
383 105
            $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];
384
        }
385 203
        foreach ($constraints as $name => $constraint) {
386 105
            $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
387
        }
388 203
    }
389
390
    /**
391
     * Gets information about given table unique indexes.
392
     * @param TableSchema $table the table metadata
393
     * @return array with index and column names
394
     */
395 1
    protected function getUniqueIndexInformation($table)
396
    {
397
        $sql = <<<SQL
398 1
SELECT
399
    i.relname as indexname,
400
    pg_get_indexdef(idx.indexrelid, k + 1, TRUE) AS columnname
401
FROM (
402
  SELECT *, generate_subscripts(indkey, 1) AS k
403
  FROM pg_index
404
) idx
405
INNER JOIN pg_class i ON i.oid = idx.indexrelid
406
INNER JOIN pg_class c ON c.oid = idx.indrelid
407
INNER JOIN pg_namespace ns ON c.relnamespace = ns.oid
408
WHERE idx.indisprimary = FALSE AND idx.indisunique = TRUE
409
AND c.relname = :tableName AND ns.nspname = :schemaName
410
ORDER BY i.relname, k
411
SQL;
412
413 1
        return $this->db->createCommand($sql, [
414 1
            ':schemaName' => $table->schemaName,
415 1
            ':tableName' => $table->name,
416 1
        ])->queryAll();
417
    }
418
419
    /**
420
     * Returns all unique indexes for the given table.
421
     * Each array element is of the following structure:
422
     *
423
     * ```php
424
     * [
425
     *     'IndexName1' => ['col1' [, ...]],
426
     *     'IndexName2' => ['col2' [, ...]],
427
     * ]
428
     * ```
429
     *
430
     * @param TableSchema $table the table metadata
431
     * @return array all unique indexes for the given table.
432
     */
433 1
    public function findUniqueIndexes($table)
434
    {
435 1
        $uniqueIndexes = [];
436
437 1
        $rows = $this->getUniqueIndexInformation($table);
438 1
        foreach ($rows as $row) {
439 1
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
440
                $row = array_change_key_case($row, CASE_LOWER);
441
            }
442 1
            $column = $row['columnname'];
443 1
            if (!empty($column) && $column[0] === '"') {
444
                // postgres will quote names that are not lowercase-only
445
                // https://github.com/yiisoft/yii2/issues/10613
446 1
                $column = substr($column, 1, -1);
447
            }
448 1
            $uniqueIndexes[$row['indexname']][] = $column;
449
        }
450
451 1
        return $uniqueIndexes;
452
    }
453
454
    /**
455
     * Collects the metadata of table columns.
456
     * @param TableSchema $table the table metadata
457
     * @return bool whether the table exists in the database
458
     */
459 209
    protected function findColumns($table)
460
    {
461 209
        $tableName = $this->db->quoteValue($table->name);
462 209
        $schemaName = $this->db->quoteValue($table->schemaName);
463
        $sql = <<<SQL
464
SELECT
465
    d.nspname AS table_schema,
466
    c.relname AS table_name,
467
    a.attname AS column_name,
468
    t.typname AS data_type,
469
    a.attlen AS character_maximum_length,
470
    pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
471
    a.atttypmod AS modifier,
472
    a.attnotnull = false AS is_nullable,
473
    CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
474
    coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
475
    array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
476
    CASE atttypid
477
         WHEN 21 /*int2*/ THEN 16
478
         WHEN 23 /*int4*/ THEN 32
479
         WHEN 20 /*int8*/ THEN 64
480
         WHEN 1700 /*numeric*/ THEN
481
              CASE WHEN atttypmod = -1
482
               THEN null
483
               ELSE ((atttypmod - 4) >> 16) & 65535
484
               END
485
         WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
486
         WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
487
         ELSE null
488
      END   AS numeric_precision,
489
      CASE
490
        WHEN atttypid IN (21, 23, 20) THEN 0
491
        WHEN atttypid IN (1700) THEN
492
        CASE
493
            WHEN atttypmod = -1 THEN null
494
            ELSE (atttypmod - 4) & 65535
495
        END
496
           ELSE null
497
      END AS numeric_scale,
498
    CAST(
499
             information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
500
             AS numeric
501
    ) AS size,
502
    a.attnum = any (ct.conkey) as is_pkey
503
FROM
504
    pg_class c
505
    LEFT JOIN pg_attribute a ON a.attrelid = c.oid
506
    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
507
    LEFT JOIN pg_type t ON a.atttypid = t.oid
508
    LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
509
    LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
510
WHERE
511
    a.attnum > 0 and t.typname != ''
512 209
    and c.relname = {$tableName}
513 209
    and d.nspname = {$schemaName}
514
ORDER BY
515
    a.attnum;
516
SQL;
517
518 209
        $columns = $this->db->createCommand($sql)->queryAll();
519 209
        if (empty($columns)) {
520 16
            return false;
521
        }
522 203
        foreach ($columns as $column) {
523 203
            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
524
                $column = array_change_key_case($column, CASE_LOWER);
525
            }
526 203
            $column = $this->loadColumnSchema($column);
527 203
            $table->columns[$column->name] = $column;
528 203
            if ($column->isPrimaryKey) {
529 181
                $table->primaryKey[] = $column->name;
530 181
                if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {
531 126
                    $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...
532
                }
533 181
                $column->defaultValue = null;
534 201
            } elseif ($column->defaultValue) {
535 95
                if ($column->type === 'timestamp' && $column->defaultValue === 'now()') {
536 21
                    $column->defaultValue = new Expression($column->defaultValue);
537 95
                } elseif ($column->type === 'boolean') {
538 89
                    $column->defaultValue = ($column->defaultValue === 'true');
539 28
                } elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) {
540 21
                    $column->defaultValue = bindec(trim($column->defaultValue, 'B\''));
541 28
                } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {
542 21
                    $column->defaultValue = $matches[1];
543 28
                } elseif (preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) {
544 28
                    if ($matches[2] === 'NULL') {
545 7
                        $column->defaultValue = null;
546
                    } else {
547 24
                        $column->defaultValue = $column->phpTypecast($matches[2]);
548
                    }
549
                } else {
550
                    $column->defaultValue = $column->phpTypecast($column->defaultValue);
551
                }
552
            }
553
        }
554
555 203
        return true;
556
    }
557
558
    /**
559
     * Loads the column information into a [[ColumnSchema]] object.
560
     * @param array $info column information
561
     * @return ColumnSchema the column schema object
562
     */
563 203
    protected function loadColumnSchema($info)
564
    {
565 203
        $column = $this->createColumnSchema();
566 203
        $column->allowNull = $info['is_nullable'];
567 203
        $column->autoIncrement = $info['is_autoinc'];
568 203
        $column->comment = $info['column_comment'];
569 203
        $column->dbType = $info['data_type'];
570 203
        $column->defaultValue = $info['column_default'];
571 203
        $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...
572 203
        $column->unsigned = false; // has no meaning in PG
573 203
        $column->isPrimaryKey = $info['is_pkey'];
574 203
        $column->name = $info['column_name'];
575 203
        $column->precision = $info['numeric_precision'];
576 203
        $column->scale = $info['numeric_scale'];
577 203
        $column->size = $info['size'] === null ? null : (int) $info['size'];
578 203
        if (isset($this->typeMap[$column->dbType])) {
579 203
            $column->type = $this->typeMap[$column->dbType];
580
        } else {
581
            $column->type = self::TYPE_STRING;
582
        }
583 203
        $column->phpType = $this->getColumnPhpType($column);
584
585 203
        return $column;
586
    }
587
588
    /**
589
     * @inheritdoc
590
     */
591 30
    public function insert($table, $columns)
592
    {
593 30
        $params = [];
594 30
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
595 30
        $returnColumns = $this->getTableSchema($table)->primaryKey;
596 30
        if (!empty($returnColumns)) {
597 22
            $returning = [];
598 22
            foreach ((array) $returnColumns as $name) {
599 22
                $returning[] = $this->quoteColumnName($name);
600
            }
601 22
            $sql .= ' RETURNING ' . implode(', ', $returning);
602
        }
603
604 30
        $command = $this->db->createCommand($sql, $params);
605 30
        $command->prepare(false);
606 30
        $result = $command->queryOne();
607
608 30
        return !$command->pdoStatement->rowCount() ? false : $result;
609
    }
610
611
    /**
612
     * Loads multiple types of constraints and returns the specified ones.
613
     * @param string $tableName table name.
614
     * @param string $returnType return type:
615
     * - primaryKey
616
     * - foreignKeys
617
     * - uniques
618
     * - checks
619
     * @return mixed constraints.
620
     */
621 43
    private function loadTableConstraints($tableName, $returnType)
622
    {
623 43
        static $sql = <<<SQL
624
SELECT
625
    "c"."conname" AS "name",
626
    "a"."attname" AS "column_name",
627
    "c"."contype" AS "type",
628
    "ftcns"."nspname" AS "foreign_table_schema",
629
    "ftc"."relname" AS "foreign_table_name",
630
    "fa"."attname" AS "foreign_column_name",
631
    "c"."confupdtype" AS "on_update",
632
    "c"."confdeltype" AS "on_delete",
633
    "c"."consrc" AS "check_expr"
634
FROM "pg_class" AS "tc"
635
INNER JOIN "pg_namespace" AS "tcns"
636
    ON "tcns"."oid" = "tc"."relnamespace"
637
INNER JOIN "pg_constraint" AS "c"
638
    ON "c"."conrelid" = "tc"."oid"
639
INNER JOIN "pg_attribute" AS "a"
640
    ON "a"."attrelid" = "c"."conrelid" AND "a"."attnum" = ANY ("c"."conkey")
641
LEFT JOIN "pg_class" AS "ftc"
642
    ON "ftc"."oid" = "c"."confrelid"
643
LEFT JOIN "pg_namespace" AS "ftcns"
644
    ON "ftcns"."oid" = "ftc"."relnamespace"
645
LEFT JOIN "pg_attribute" "fa"
646
    ON "fa"."attrelid" = "c"."confrelid" AND "fa"."attnum" = ANY ("c"."confkey")
647
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
648
ORDER BY "a"."attnum" ASC, "fa"."attnum" ASC
649
SQL;
650 43
        static $actionTypes = [
651
            'a' => 'NO ACTION',
652
            'r' => 'RESTRICT',
653
            'c' => 'CASCADE',
654
            'n' => 'SET NULL',
655
            'd' => 'SET DEFAULT',
656
        ];
657
658 43
        $resolvedName = $this->resolveTableName($tableName);
659 43
        $constraints = $this->db->createCommand($sql, [
660 43
            ':schemaName' => $resolvedName->schemaName,
661 43
            ':tableName' => $resolvedName->name,
662 43
        ])->queryAll();
663 43
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
664 43
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
665
        $result = [
666 43
            'primaryKey' => null,
667
            'foreignKeys' => [],
668
            'uniques' => [],
669
            'checks' => [],
670
        ];
671 43
        foreach ($constraints as $type => $names) {
672 43
            foreach ($names as $name => $constraint) {
673
                switch ($type) {
674 43
                    case 'p':
675 28
                        $result['primaryKey'] = new Constraint([
676 28
                            'name' => $name,
677 28
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
678
                        ]);
679 28
                        break;
680 42
                    case 'f':
681 13
                        $result['foreignKeys'][] = new ForeignKeyConstraint([
682 13
                            'name' => $name,
683 13
                            'columnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'column_name'))),
684 13
                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
685 13
                            'foreignTableName' => $constraint[0]['foreign_table_name'],
686 13
                            'foreignColumnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'foreign_column_name'))),
687 13
                            'onDelete' => isset($actionTypes[$constraint[0]['on_delete']]) ? $actionTypes[$constraint[0]['on_delete']] : null,
688 13
                            'onUpdate' => isset($actionTypes[$constraint[0]['on_update']]) ? $actionTypes[$constraint[0]['on_update']] : null,
689
                        ]);
690 13
                        break;
691 30
                    case 'u':
692 29
                        $result['uniques'][] = new Constraint([
693 29
                            'name' => $name,
694 29
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
695
                        ]);
696 29
                        break;
697 10
                    case 'c':
698 10
                        $result['checks'][] = new CheckConstraint([
699 10
                            'name' => $name,
700 10
                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
701 10
                            'expression' => $constraint[0]['check_expr'],
702
                        ]);
703 43
                        break;
704
                }
705
            }
706
        }
707 43
        foreach ($result as $type => $data) {
708 43
            $this->setTableMetadata($tableName, $type, $data);
709
        }
710 43
        return $result[$returnType];
711
    }
712
}
713