Passed
Push — master ( ce698f...fcbf0c )
by Wilmer
10:25 queued 08:39
created

Schema::findColumns()   C

Complexity

Conditions 17
Paths 48

Size

Total Lines 112
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 17.289

Importance

Changes 0
Metric Value
cc 17
eloc 53
c 0
b 0
f 0
nc 48
nop 1
dl 0
loc 112
ccs 36
cts 40
cp 0.9
crap 17.289
rs 5.2166

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
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql\Schema;
6
7
use Yiisoft\Db\Constraint\CheckConstraint;
8
use Yiisoft\Db\Constraint\Constraint;
9
use Yiisoft\Db\Constraint\ConstraintFinderInterface;
10
use Yiisoft\Db\Constraint\ConstraintFinderTrait;
11
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
12
use Yiisoft\Db\Constraint\IndexConstraint;
13
use Yiisoft\Db\Exception\NotSupportedException;
14
use Yiisoft\Db\Expression\Expression;
15
use Yiisoft\Db\Pgsql\Query\QueryBuilder;
16
use Yiisoft\Db\Schema\Schema as AbstractSchema;
17
use Yiisoft\Db\View\ViewFinderTrait;
18
use Yiisoft\Arrays\ArrayHelper;
19
20
/**
21
 * Schema is the class for retrieving metadata from a Postgres SQL database
22
 * (version 9.x and above).
23
 */
24
class Schema extends AbstractSchema implements ConstraintFinderInterface
25
{
26
    use ViewFinderTrait;
27
    use ConstraintFinderTrait;
28
29
    public const TYPE_JSONB = 'jsonb';
30
31
    /**
32
     * @var string the default schema used for the current session.
33
     */
34
    protected ?string $defaultSchema = 'public';
35
    protected string $tableQuoteCharacter = '"';
36
37
    /**
38
     * @var array mapping from physical column types (keys) to abstract column types (values).
39
     *
40
     * {@see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE}
41
     */
42
    private array $typeMap = [
43
        'bit' => self::TYPE_INTEGER,
44
        'bit varying' => self::TYPE_INTEGER,
45
        'varbit' => self::TYPE_INTEGER,
46
        'bool' => self::TYPE_BOOLEAN,
47
        'boolean' => self::TYPE_BOOLEAN,
48
        'box' => self::TYPE_STRING,
49
        'circle' => self::TYPE_STRING,
50
        'point' => self::TYPE_STRING,
51
        'line' => self::TYPE_STRING,
52
        'lseg' => self::TYPE_STRING,
53
        'polygon' => self::TYPE_STRING,
54
        'path' => self::TYPE_STRING,
55
        'character' => self::TYPE_CHAR,
56
        'char' => self::TYPE_CHAR,
57
        'bpchar' => self::TYPE_CHAR,
58
        'character varying' => self::TYPE_STRING,
59
        'varchar' => self::TYPE_STRING,
60
        'text' => self::TYPE_TEXT,
61
        'bytea' => self::TYPE_BINARY,
62
        'cidr' => self::TYPE_STRING,
63
        'inet' => self::TYPE_STRING,
64
        'macaddr' => self::TYPE_STRING,
65
        'real' => self::TYPE_FLOAT,
66
        'float4' => self::TYPE_FLOAT,
67
        'double precision' => self::TYPE_DOUBLE,
68
        'float8' => self::TYPE_DOUBLE,
69
        'decimal' => self::TYPE_DECIMAL,
70
        'numeric' => self::TYPE_DECIMAL,
71
        'money' => self::TYPE_MONEY,
72
        'smallint' => self::TYPE_SMALLINT,
73
        'int2' => self::TYPE_SMALLINT,
74
        'int4' => self::TYPE_INTEGER,
75
        'int' => self::TYPE_INTEGER,
76
        'integer' => self::TYPE_INTEGER,
77
        'bigint' => self::TYPE_BIGINT,
78
        'int8' => self::TYPE_BIGINT,
79
        'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
80
        'smallserial' => self::TYPE_SMALLINT,
81
        'serial2' => self::TYPE_SMALLINT,
82
        'serial4' => self::TYPE_INTEGER,
83
        'serial' => self::TYPE_INTEGER,
84
        'bigserial' => self::TYPE_BIGINT,
85
        'serial8' => self::TYPE_BIGINT,
86
        'pg_lsn' => self::TYPE_BIGINT,
87
        'date' => self::TYPE_DATE,
88
        'interval' => self::TYPE_STRING,
89
        'time without time zone' => self::TYPE_TIME,
90
        'time' => self::TYPE_TIME,
91
        'time with time zone' => self::TYPE_TIME,
92
        'timetz' => self::TYPE_TIME,
93
        'timestamp without time zone' => self::TYPE_TIMESTAMP,
94
        'timestamp' => self::TYPE_TIMESTAMP,
95
        'timestamp with time zone' => self::TYPE_TIMESTAMP,
96
        'timestamptz' => self::TYPE_TIMESTAMP,
97
        'abstime' => self::TYPE_TIMESTAMP,
98
        'tsquery' => self::TYPE_STRING,
99
        'tsvector' => self::TYPE_STRING,
100
        'txid_snapshot' => self::TYPE_STRING,
101
        'unknown' => self::TYPE_STRING,
102
        'uuid' => self::TYPE_STRING,
103
        'json' => self::TYPE_JSON,
104
        'jsonb' => self::TYPE_JSON,
105
        'xml' => self::TYPE_STRING,
106
    ];
107
108 71
    protected function resolveTableName($name)
109
    {
110 71
        $resolvedName = new TableSchema();
111 71
        $parts = \explode('.', \str_replace('"', '', $name));
112
113 71
        if (isset($parts[1])) {
114
            $resolvedName->schemaName($parts[0]);
115
            $resolvedName->name($parts[1]);
116
        } else {
117 71
            $resolvedName->schemaName($this->defaultSchema);
118 71
            $resolvedName->name($name);
119
        }
120 71
        $resolvedName->fullName(($resolvedName->getSchemaName() !== $this->defaultSchema ? $resolvedName->getSchemaName() . '.' : '')
121 71
            . $resolvedName->getName());
122 71
        return $resolvedName;
123
    }
124
125 2
    protected function findSchemaNames()
126
    {
127 2
        static $sql = <<<'SQL'
128
SELECT "ns"."nspname"
129
FROM "pg_namespace" AS "ns"
130
WHERE "ns"."nspname" != 'information_schema' AND "ns"."nspname" NOT LIKE 'pg_%'
131
ORDER BY "ns"."nspname" ASC
132
SQL;
133
134 2
        return $this->getDb()->createCommand($sql)->queryColumn();
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140 5
    protected function findTableNames($schema = '')
141
    {
142 5
        if ($schema === '') {
143 5
            $schema = $this->defaultSchema;
144
        }
145
        $sql = <<<'SQL'
146 5
SELECT c.relname AS table_name
147
FROM pg_class c
148
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
149
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f', 'p')
150
ORDER BY c.relname
151
SQL;
152 5
        return $this->getDb()->createCommand($sql, [':schemaName' => $schema])->queryColumn();
153
    }
154
155 98
    protected function loadTableSchema(string $name): ?TableSchema
156
    {
157 98
        $table = new TableSchema();
158 98
        $this->resolveTableNames($table, $name);
159
160 98
        if ($this->findColumns($table)) {
161 91
            $this->findConstraints($table);
162 91
            return $table;
163
        }
164
165 18
        return null;
166
    }
167
168 31
    protected function loadTablePrimaryKey($tableName)
169
    {
170 31
        return $this->loadTableConstraints($tableName, 'primaryKey');
171
    }
172
173 4
    protected function loadTableForeignKeys($tableName)
174
    {
175 4
        return $this->loadTableConstraints($tableName, 'foreignKeys');
176
    }
177
178 28
    protected function loadTableIndexes($tableName)
179
    {
180 28
        static $sql = <<<'SQL'
181
SELECT
182
    "ic"."relname" AS "name",
183
    "ia"."attname" AS "column_name",
184
    "i"."indisunique" AS "index_is_unique",
185
    "i"."indisprimary" AS "index_is_primary"
186
FROM "pg_class" AS "tc"
187
INNER JOIN "pg_namespace" AS "tcns"
188
    ON "tcns"."oid" = "tc"."relnamespace"
189
INNER JOIN "pg_index" AS "i"
190
    ON "i"."indrelid" = "tc"."oid"
191
INNER JOIN "pg_class" AS "ic"
192
    ON "ic"."oid" = "i"."indexrelid"
193
INNER JOIN "pg_attribute" AS "ia"
194
    ON "ia"."attrelid" = "i"."indrelid" AND "ia"."attnum" = ANY ("i"."indkey")
195
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
196
ORDER BY "ia"."attnum" ASC
197
SQL;
198
199 28
        $resolvedName = $this->resolveTableName($tableName);
200
201 28
        $indexes = $this->getDb()->createCommand($sql, [
202 28
            ':schemaName' => $resolvedName->getSchemaName(),
203 28
            ':tableName' => $resolvedName->getName(),
204 28
        ])->queryAll();
205
206 28
        $indexes = $this->normalizePdoRowKeyCase($indexes, true);
207 28
        $indexes = ArrayHelper::index($indexes, null, 'name');
208 28
        $result = [];
209
210 28
        foreach ($indexes as $name => $index) {
211 25
            $ic = new IndexConstraint();
212
213 25
            $ic->setName($name);
214 25
            $ic->setColumnNames(ArrayHelper::getColumn($index, 'column_name'));
215 25
            $ic->setIsPrimary((bool) $index[0]['index_is_primary']);
216 25
            $ic->setIsUnique((bool) $index[0]['index_is_unique']);
217
218 25
            $result[] = $ic;
219
        }
220
221 28
        return $result;
222
    }
223
224 13
    protected function loadTableUniques($tableName)
225
    {
226 13
        return $this->loadTableConstraints($tableName, 'uniques');
227
    }
228
229 13
    protected function loadTableChecks($tableName)
230
    {
231 13
        return $this->loadTableConstraints($tableName, 'checks');
232
    }
233
234 12
    protected function loadTableDefaultValues($tableName)
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
    protected function loadTableDefaultValues(/** @scrutinizer ignore-unused */ $tableName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236 12
        throw new NotSupportedException('PostgreSQL does not support default value constraints.');
237
    }
238
239
    /**
240
     * Creates a query builder for the PostgreSQL database.
241
     *
242
     * @return QueryBuilder query builder instance
243
     */
244 68
    public function createQueryBuilder()
245
    {
246 68
        return new QueryBuilder($this->getDb());
0 ignored issues
show
Bug introduced by
It seems like $this->getDb() can also be of type null; however, parameter $db of Yiisoft\Db\Pgsql\Query\QueryBuilder::__construct() does only seem to accept Yiisoft\Db\Connection\Connection, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

246
        return new QueryBuilder(/** @scrutinizer ignore-type */ $this->getDb());
Loading history...
247
    }
248
249
    /**
250
     * Resolves the table name and schema name (if any).
251
     * @param TableSchema $table the table metadata object.
252
     *
253
     * @param string $name the table name
254
     */
255 98
    protected function resolveTableNames($table, $name)
256
    {
257 98
        $parts = \explode('.', \str_replace('"', '', $name));
258
259 98
        if (isset($parts[1])) {
260
            $table->schemaName($parts[0]);
261
            $table->name($parts[1]);
262
        } else {
263 98
            $table->schemaName($this->defaultSchema);
264 98
            $table->name($parts[0]);
265
        }
266
267 98
        $table->fullName($table->getSchemaName() !== $this->defaultSchema ? $table->getSchemaName() . '.'
268 98
            . $table->name : $table->getName());
0 ignored issues
show
Bug introduced by
The property name is declared private in Yiisoft\Db\Schema\TableSchema and cannot be accessed from this context.
Loading history...
269 98
    }
270
271
    /**
272
     * {@inheritdoc]
273
     */
274
    protected function findViewNames($schema = '')
275
    {
276
        if ($schema === '') {
277
            $schema = $this->defaultSchema;
278
        }
279
        $sql = <<<'SQL'
280
SELECT c.relname AS table_name
281
FROM pg_class c
282
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
283
WHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm')
284
ORDER BY c.relname
285
SQL;
286
        return $this->getDb()->createCommand($sql, [':schemaName' => $schema])->queryColumn();
287
    }
288
289
    /**
290
     * Collects the foreign key column details for the given table.
291
     *
292
     * @param TableSchema $table the table metadata
293
     */
294 91
    protected function findConstraints($table)
295
    {
296 91
        $tableName = $this->quoteValue($table->getName());
297 91
        $tableSchema = $this->quoteValue($table->getSchemaName());
298
299
        //We need to extract the constraints de hard way since:
300
        //http://www.postgresql.org/message-id/[email protected]
301
302
        $sql = <<<SQL
303 91
select
304
    ct.conname as constraint_name,
305
    a.attname as column_name,
306
    fc.relname as foreign_table_name,
307
    fns.nspname as foreign_table_schema,
308
    fa.attname as foreign_column_name
309
from
310
    (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s
311
       FROM pg_constraint ct
312
    ) AS ct
313
    inner join pg_class c on c.oid=ct.conrelid
314
    inner join pg_namespace ns on c.relnamespace=ns.oid
315
    inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s]
316
    left join pg_class fc on fc.oid=ct.confrelid
317
    left join pg_namespace fns on fc.relnamespace=fns.oid
318
    left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s]
319
where
320
    ct.contype='f'
321 91
    and c.relname={$tableName}
322 91
    and ns.nspname={$tableSchema}
323
order by
324
    fns.nspname, fc.relname, a.attnum
325
SQL;
326
327 91
        $constraints = [];
328
329 91
        foreach ($this->getDb()->createCommand($sql)->queryAll() as $constraint) {
330 9
            if ($this->getDb()->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
331
                $constraint = \array_change_key_case($constraint, CASE_LOWER);
332
            }
333 9
            if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
334
                $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
335
            } else {
336 9
                $foreignTable = $constraint['foreign_table_name'];
337
            }
338 9
            $name = $constraint['constraint_name'];
339 9
            if (!isset($constraints[$name])) {
340 9
                $constraints[$name] = [
341 9
                    'tableName' => $foreignTable,
342
                    'columns' => [],
343
                ];
344
            }
345 9
            $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];
346
        }
347
348 91
        foreach ($constraints as $name => $constraint) {
349 9
            $table->foreignKey($name, \array_merge([$constraint['tableName']], $constraint['columns']));
350
        }
351 91
    }
352
353
    /**
354
     * Gets information about given table unique indexes.
355
     *
356
     * @param TableSchema $table the table metadata
357
     *
358
     * @return array with index and column names
359
     */
360 1
    protected function getUniqueIndexInformation($table)
361
    {
362
        $sql = <<<'SQL'
363 1
SELECT
364
    i.relname as indexname,
365
    pg_get_indexdef(idx.indexrelid, k + 1, TRUE) AS columnname
366
FROM (
367
  SELECT *, generate_subscripts(indkey, 1) AS k
368
  FROM pg_index
369
) idx
370
INNER JOIN pg_class i ON i.oid = idx.indexrelid
371
INNER JOIN pg_class c ON c.oid = idx.indrelid
372
INNER JOIN pg_namespace ns ON c.relnamespace = ns.oid
373
WHERE idx.indisprimary = FALSE AND idx.indisunique = TRUE
374
AND c.relname = :tableName AND ns.nspname = :schemaName
375
ORDER BY i.relname, k
376
SQL;
377
378 1
        return $this->getDb()->createCommand($sql, [
379 1
            ':schemaName' => $table->getSchemaName(),
380 1
            ':tableName' => $table->getName(),
381 1
        ])->queryAll();
382
    }
383
384
    /**
385
     * Returns all unique indexes for the given table.
386
     *
387
     * Each array element is of the following structure:
388
     *
389
     * ```php
390
     * [
391
     *     'IndexName1' => ['col1' [, ...]],
392
     *     'IndexName2' => ['col2' [, ...]],
393
     * ]
394
     * ```
395
     *
396
     * @param TableSchema $table the table metadata
397
     * @return array all unique indexes for the given table.
398
     */
399 1
    public function findUniqueIndexes($table)
400
    {
401 1
        $uniqueIndexes = [];
402
403 1
        foreach ($this->getUniqueIndexInformation($table) as $row) {
404 1
            if ($this->getDb()->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
405
                $row = \array_change_key_case($row, CASE_LOWER);
406
            }
407 1
            $column = $row['columnname'];
408 1
            if (!empty($column) && $column[0] === '"') {
409
                /**
410
                 * postgres will quote names that are not lowercase-only
411
                 * https://github.com/yiisoft/yii2/issues/10613
412
                 */
413 1
                $column = \substr($column, 1, -1);
414
            }
415 1
            $uniqueIndexes[$row['indexname']][] = $column;
416
        }
417
418 1
        return $uniqueIndexes;
419
    }
420
421
    /**
422
     * Collects the metadata of table columns.
423
     *
424
     * @param TableSchema $table the table metadata
425
     *
426
     * @return bool whether the table exists in the database
427
     */
428 98
    protected function findColumns($table): bool
429
    {
430 98
        $tableName = $this->getDb()->quoteValue($table->getName());
431 98
        $schemaName = $this->getDb()->quoteValue($table->getSchemaName());
432
433 98
        $orIdentity = '';
434 98
        if (\version_compare($this->getDb()->getServerVersion(), '12.0', '>=')) {
435
            $orIdentity = 'OR attidentity != \'\'';
436
        }
437
438
        $sql = <<<SQL
439 98
SELECT
440
    d.nspname AS table_schema,
441
    c.relname AS table_name,
442
    a.attname AS column_name,
443
    COALESCE(td.typname, tb.typname, t.typname) AS data_type,
444
    COALESCE(td.typtype, tb.typtype, t.typtype) AS type_type,
445
    a.attlen AS character_maximum_length,
446
    pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
447
    a.atttypmod AS modifier,
448
    a.attnotnull = false AS is_nullable,
449
    CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
450 98
    coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) {$orIdentity} AS is_autoinc,
451
    pg_get_serial_sequence(quote_ident(d.nspname) || '.' || quote_ident(c.relname), a.attname) AS sequence_name,
452
    CASE WHEN COALESCE(td.typtype, tb.typtype, t.typtype) = 'e'::char
453
        THEN array_to_string((SELECT array_agg(enumlabel) FROM pg_enum WHERE enumtypid = COALESCE(td.oid, tb.oid, a.atttypid))::varchar[], ',')
454
        ELSE NULL
455
    END AS enum_values,
456
    CASE atttypid
457
         WHEN 21 /*int2*/ THEN 16
458
         WHEN 23 /*int4*/ THEN 32
459
         WHEN 20 /*int8*/ THEN 64
460
         WHEN 1700 /*numeric*/ THEN
461
              CASE WHEN atttypmod = -1
462
               THEN null
463
               ELSE ((atttypmod - 4) >> 16) & 65535
464
               END
465
         WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
466
         WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
467
         ELSE null
468
      END   AS numeric_precision,
469
      CASE
470
        WHEN atttypid IN (21, 23, 20) THEN 0
471
        WHEN atttypid IN (1700) THEN
472
        CASE
473
            WHEN atttypmod = -1 THEN null
474
            ELSE (atttypmod - 4) & 65535
475
        END
476
           ELSE null
477
      END AS numeric_scale,
478
    CAST(
479
             information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
480
             AS numeric
481
    ) AS size,
482
    a.attnum = any (ct.conkey) as is_pkey,
483
    COALESCE(NULLIF(a.attndims, 0), NULLIF(t.typndims, 0), (t.typcategory='A')::int) AS dimension
484
FROM
485
    pg_class c
486
    LEFT JOIN pg_attribute a ON a.attrelid = c.oid
487
    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
488
    LEFT JOIN pg_type t ON a.atttypid = t.oid
489
    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
490
    LEFT JOIN pg_type td ON t.typndims > 0 AND t.typbasetype > 0 AND tb.typelem = td.oid
491
    LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
492
    LEFT JOIN pg_constraint ct ON ct.conrelid = c.oid AND ct.contype = 'p'
493
WHERE
494
    a.attnum > 0 AND t.typname != '' AND NOT a.attisdropped
495 98
    AND c.relname = {$tableName}
496 98
    AND d.nspname = {$schemaName}
497
ORDER BY
498
    a.attnum;
499
SQL;
500 98
        $columns = $this->getDb()->createCommand($sql)->queryAll();
501 98
        if (empty($columns)) {
502 18
            return false;
503
        }
504 91
        foreach ($columns as $column) {
505 91
            if ($this->getDb()->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
506
                $column = \array_change_key_case($column, CASE_LOWER);
507
            }
508 91
            $column = $this->loadColumnSchema($column);
509 91
            $table->columns($column->getName(), $column);
510 91
            if ($column->getIsPrimaryKey()) {
511 62
                $table->primaryKey($column->getName());
512 62
                if ($table->getSequenceName() === null) {
513 62
                    $table->sequenceName($column->sequenceName);
514
                }
515 62
                $column->defaultValue(null);
516 90
            } elseif ($column->getDefaultValue()) {
517 54
                if ($column->getType() === 'timestamp' && $column->getDefaultValue() === 'now()') {
518 27
                    $column->defaultValue(new Expression($column->getDefaultValue()));
519 54
                } elseif ($column->getType() === 'boolean') {
520 51
                    $column->defaultValue(($column->getDefaultValue() === 'true'));
521 30
                } elseif (\preg_match("/^B'(.*?)'::/", $column->getDefaultValue(), $matches)) {
522 27
                    $column->defaultValue(\bindec($matches[1]));
523 30
                } elseif (\strncasecmp($column->getDbType(), 'bit', 3) === 0 || \strncasecmp($column->getDbType(), 'varbit', 6) === 0) {
524
                    $column->defaultValue(\bindec(\trim($column->getDefaultValue(), 'B\'')));
525 30
                } elseif (\preg_match("/^'(.*?)'::/", $column->getDefaultValue(), $matches)) {
526 28
                    $column->defaultValue($column->phpTypecast($matches[1]));
527 29
                } elseif (\preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $column->getDefaultValue(), $matches)) {
528 29
                    if ($matches[2] === 'NULL') {
529 4
                        $column->defaultValue(null);
530
                    } else {
531 29
                        $column->defaultValue($column->phpTypecast($matches[2]));
532
                    }
533
                } else {
534
                    $column->defaultValue($column->phpTypecast($column->defaultValue));
0 ignored issues
show
Bug introduced by
The property defaultValue is declared private in Yiisoft\Db\Schema\ColumnSchema and cannot be accessed from this context.
Loading history...
535
                }
536
            }
537
        }
538
539 91
        return true;
540
    }
541
542
    /**
543
     * Loads the column information into a {@see ColumnSchema} object.
544
     *
545
     * @param array $info column information
546
     *
547
     * @return ColumnSchema the column schema object
548
     */
549 91
    protected function loadColumnSchema($info): ColumnSchema
550
    {
551
        /** @var ColumnSchema $column */
552 91
        $column = $this->createColumnSchema();
553 91
        $column->allowNull($info['is_nullable']);
554 91
        $column->autoIncrement($info['is_autoinc']);
555 91
        $column->comment($info['column_comment']);
556 91
        $column->dbType($info['data_type']);
557 91
        $column->defaultValue($info['column_default']);
558 91
        $column->enumValues(($info['enum_values'] !== null)
559 91
            ? \explode(',', \str_replace(["''"], ["'"], $info['enum_values'])) : null);
560 91
        $column->unsigned(false); // has no meaning in PG
561 91
        $column->isPrimaryKey((bool) $info['is_pkey']);
562 91
        $column->name($info['column_name']);
563 91
        $column->precision($info['numeric_precision']);
564 91
        $column->scale($info['numeric_scale']);
565 91
        $column->size($info['size'] === null ? null : (int) $info['size']);
566 91
        $column->dimension = (int) $info['dimension'];
567
568
        /**
569
         * pg_get_serial_sequence() doesn't track DEFAULT value change. GENERATED BY IDENTITY columns always have null
570
         * default value
571
         */
572
573 91
        $defaultValue = $column->getDefaultValue();
574 91
        if (isset($defaultValue) && \preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $defaultValue) === 1) {
575 59
            $column->sequenceName = \preg_replace(
576 59
                ['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'],
577 59
                '',
578 59
                $defaultValue
579
            );
580 91
        } elseif (isset($info['sequence_name'])) {
581
            $column->sequenceName = $this->resolveTableName($info['sequence_name'])->getFullName();
582
        }
583
584 91
        if (isset($this->typeMap[$column->getDbType()])) {
585 91
            $column->type($this->typeMap[$column->getDbType()]);
586
        } else {
587
            $column->type(self::TYPE_STRING);
588
        }
589 91
        $column->phpType($this->getColumnPhpType($column));
590
591 91
        return $column;
592
    }
593
594
    /**
595
     * {@inheritdoc}
596
     */
597
    public function insert($table, $columns)
598
    {
599
        $params = [];
600
        $sql = $this->getDb()->getQueryBuilder()->insert($table, $columns, $params);
601
        $returnColumns = $this->getTableSchema($table)->getPrimaryKeys();
0 ignored issues
show
Bug introduced by
The method getPrimaryKeys() does not exist on Yiisoft\Db\Schema\TableSchema. Did you maybe mean getPrimaryKey()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

601
        $returnColumns = $this->getTableSchema($table)->/** @scrutinizer ignore-call */ getPrimaryKeys();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
602
        if (!empty($returnColumns)) {
603
            $returning = [];
604
            foreach ((array) $returnColumns as $name) {
605
                $returning[] = $this->quoteColumnName($name);
606
            }
607
            $sql .= ' RETURNING ' . \implode(', ', $returning);
608
        }
609
610
        $command = $this->getDb()->createCommand($sql, $params);
611
        $command->prepare(false);
612
        $result = $command->queryOne();
613
614
        return !$command->getPdoStatement()->rowCount() ? false : $result;
615
    }
616
617
    /**
618
     * Loads multiple types of constraints and returns the specified ones.
619
     *
620
     * @param string $tableName table name.
621
     * @param string $returnType return type:
622
     * - primaryKey
623
     * - foreignKeys
624
     * - uniques
625
     * - checks
626
     *
627
     * @return mixed constraints.
628
     */
629 61
    private function loadTableConstraints($tableName, $returnType)
630
    {
631 61
        static $sql = <<<'SQL'
632
SELECT
633
    "c"."conname" AS "name",
634
    "a"."attname" AS "column_name",
635
    "c"."contype" AS "type",
636
    "ftcns"."nspname" AS "foreign_table_schema",
637
    "ftc"."relname" AS "foreign_table_name",
638
    "fa"."attname" AS "foreign_column_name",
639
    "c"."confupdtype" AS "on_update",
640
    "c"."confdeltype" AS "on_delete",
641
    pg_get_constraintdef("c"."oid") AS "check_expr"
642
FROM "pg_class" AS "tc"
643
INNER JOIN "pg_namespace" AS "tcns"
644
    ON "tcns"."oid" = "tc"."relnamespace"
645
INNER JOIN "pg_constraint" AS "c"
646
    ON "c"."conrelid" = "tc"."oid"
647
INNER JOIN "pg_attribute" AS "a"
648
    ON "a"."attrelid" = "c"."conrelid" AND "a"."attnum" = ANY ("c"."conkey")
649
LEFT JOIN "pg_class" AS "ftc"
650
    ON "ftc"."oid" = "c"."confrelid"
651
LEFT JOIN "pg_namespace" AS "ftcns"
652
    ON "ftcns"."oid" = "ftc"."relnamespace"
653
LEFT JOIN "pg_attribute" "fa"
654
    ON "fa"."attrelid" = "c"."confrelid" AND "fa"."attnum" = ANY ("c"."confkey")
655
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
656
ORDER BY "a"."attnum" ASC, "fa"."attnum" ASC
657
SQL;
658 61
        static $actionTypes = [
659
            'a' => 'NO ACTION',
660
            'r' => 'RESTRICT',
661
            'c' => 'CASCADE',
662
            'n' => 'SET NULL',
663
            'd' => 'SET DEFAULT',
664
        ];
665
666 61
        $resolvedName = $this->resolveTableName($tableName);
667 61
        $constraints = $this->getDb()->createCommand($sql, [
668 61
            ':schemaName' => $resolvedName->getSchemaName(),
669 61
            ':tableName' => $resolvedName->getName(),
670 61
        ])->queryAll();
671 61
        $constraints = $this->normalizePdoRowKeyCase($constraints, true);
672 61
        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
673
        $result = [
674 61
            'primaryKey' => null,
675
            'foreignKeys' => [],
676
            'uniques' => [],
677
            'checks' => [],
678
        ];
679 61
        foreach ($constraints as $type => $names) {
680 61
            foreach ($names as $name => $constraint) {
681 61
                switch ($type) {
682 61
                    case 'p':
683 46
                        $ct = new Constraint();
684 46
                        $ct->setName($name);
685 46
                        $ct->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
686
687 46
                        $result['primaryKey'] = $ct;
688 46
                        break;
689 59
                    case 'f':
690 13
                        $fk = new ForeignKeyConstraint();
691 13
                        $fk->setName($name);
692 13
                        $fk->setColumnNames(\array_values(
693 13
                            \array_unique(ArrayHelper::getColumn($constraint, 'column_name'))
694
                        ));
695 13
                        $fk->setForeignColumnNames($constraint[0]['foreign_table_schema']);
696 13
                        $fk->setForeignTableName($constraint[0]['foreign_table_name']);
697 13
                        $fk->setForeignColumnNames(\array_values(
698 13
                            \array_unique(ArrayHelper::getColumn($constraint, 'foreign_column_name'))
699
                        ));
700 13
                        $fk->setOnDelete($actionTypes[$constraint[0]['on_delete']] ?? null);
701 13
                        $fk->setOnUpdate($actionTypes[$constraint[0]['on_update']] ?? null);
702
703 13
                        $result['foreignKeys'][] = $fk;
704 13
                        break;
705 47
                    case 'u':
706 46
                        $ct = new Constraint();
707 46
                        $ct->setName($name);
708 46
                        $ct->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
709
710 46
                        $result['uniques'][] = $ct;
711 46
                        break;
712 10
                    case 'c':
713 10
                        $ck = new CheckConstraint();
714 10
                        $ck->setName($name);
715 10
                        $ck->setColumnNames(ArrayHelper::getColumn($constraint, 'column_name'));
716 10
                        $ck->setExpression($constraint[0]['check_expr']);
717
718 10
                        $result['checks'][] = $ck;
719 10
                        break;
720
                }
721
            }
722
        }
723 61
        foreach ($result as $type => $data) {
724 61
            $this->setTableMetadata($tableName, $type, $data);
725
        }
726
727 61
        return $result[$returnType];
728
    }
729
730
    /**
731
     * Creates a column schema for the database.
732
     *
733
     * This method may be overridden by child classes to create a DBMS-specific column schema.
734
     *
735
     * @return ColumnSchema column schema instance.
736
     */
737 91
    protected function createColumnSchema(): ColumnSchema
738
    {
739 91
        return new ColumnSchema();
740
    }
741
}
742