Completed
Push — master ( 7a38c7...a3c5ae )
by Dmitry
11:27
created

Schema::findViewNames()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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