0 follower

Final Class Yiisoft\Db\Pgsql\Schema

InheritanceYiisoft\Db\Pgsql\Schema » Yiisoft\Db\Driver\Pdo\AbstractPdoSchema

Implements the PostgreSQL Server specific schema, supporting PostgreSQL Server version 9.6 and above.

Psalm Types

Name Value
ColumnArray array{check: string|null, column_name: string, data_type: string, type_type: string|null, type_scheme: string, character_maximum_length: integer|string, column_comment: string|null, is_nullable: boolean|string, column_default: string|null, is_autoinc: boolean|string, sequence_name: string|null, values: string|null, size: integer|string|null, scale: integer|string|null, contype: string|null, dimension: integer|string, collation: string|null, collation_schema: string, schema: string, table: string}
FindConstraintArray array{constraint_name: string, column_name: string, foreign_table_name: string, foreign_table_schema: string, foreign_column_name: string}
CreateInfo array{dimension?: integer|string, columns?: array<string, \Yiisoft\Db\Schema\Column\ColumnInterface>}

Protected Properties

Hide inherited properties

Property Type Description Defined By
$defaultSchema string The default schema used for the current session. Yiisoft\Db\Pgsql\Schema

Property Details

Hide inherited properties

$defaultSchema protected property

The default schema used for the current session.

protected string $defaultSchema 'public'

Method Details

Hide inherited methods

findColumns() protected method

Collects the metadata of table columns.

protected boolean findColumns ( \Yiisoft\Db\Schema\TableSchemaInterface $table )
$table \Yiisoft\Db\Schema\TableSchemaInterface

The table metadata.

return boolean

Whether the table exists in the database.

                protected function findColumns(TableSchemaInterface $table): bool
{
    $orIdentity = '';
    if (version_compare($this->db->getServerInfo()->getVersion(), '12.0', '>=')) {
        $orIdentity = 'OR a.attidentity != \'\'';
    }
    $sql = <<<SQL
    SELECT
        a.attname AS column_name,
        COALESCE(td.typname, tb.typname, t.typname) AS data_type,
        COALESCE(td.typtype, tb.typtype, t.typtype) AS type_type,
        (SELECT nspname FROM pg_namespace WHERE oid = COALESCE(td.typnamespace, tb.typnamespace, t.typnamespace)) AS type_scheme,
        a.attlen AS character_maximum_length,
        pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
        NOT (a.attnotnull OR t.typnotnull) AS is_nullable,
        COALESCE(t.typdefault, pg_get_expr(ad.adbin, ad.adrelid)) AS column_default,
        COALESCE(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval', false) $orIdentity AS is_autoinc,
        pg_get_serial_sequence(quote_ident(d.nspname) || '.' || quote_ident(c.relname), a.attname)
        AS sequence_name,
        CASE WHEN COALESCE(td.typtype, tb.typtype, t.typtype) = 'e'::char
            THEN array_to_string(
                (
                    SELECT array_agg(enumlabel)
                    FROM pg_enum
                    WHERE enumtypid = COALESCE(td.oid, tb.oid, a.atttypid)
                )::varchar[],
            ',')
            ELSE NULL
        END AS values,
        COALESCE(
            information_schema._pg_char_max_length(
                COALESCE(td.oid, tb.oid, a.atttypid),
                a.atttypmod
            ),
            information_schema._pg_datetime_precision(
                COALESCE(td.oid, tb.oid, a.atttypid),
                a.atttypmod
            ),
            CASE a.atttypmod
                WHEN -1 THEN null
                ELSE ((a.atttypmod - 4) >> 16) & 65535
            END
        ) AS size,
        information_schema._pg_numeric_scale(
            COALESCE(td.oid, tb.oid, a.atttypid),
            a.atttypmod
        ) AS scale,
        ct.contype,
        pg_get_constraintdef(chk.oid) AS "check",
        COALESCE(NULLIF(a.attndims, 0), NULLIF(t.typndims, 0), (t.typcategory='A')::int) AS dimension,
        co.collname AS collation,
        nco.nspname AS collation_schema
    FROM
        pg_class c
        LEFT JOIN pg_attribute a ON a.attrelid = c.oid
        LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
        LEFT JOIN pg_type t ON a.atttypid = t.oid
        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
        LEFT JOIN pg_type td ON t.typndims > 0 AND t.typbasetype > 0 AND tb.typelem = td.oid
        LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
        LEFT JOIN pg_rewrite rw ON c.relkind = 'v' AND rw.ev_class = c.oid AND rw.rulename = '_RETURN'
        LEFT JOIN pg_constraint ct ON (ct.contype = 'p' OR ct.contype = 'u' AND cardinality(ct.conkey) = 1)
            AND (
                ct.conrelid = c.oid AND a.attnum = ANY (ct.conkey)
                OR rw.ev_action IS NOT NULL AND ct.conrelid != 0
                AND strpos(rw.ev_action, ':resorigtbl ' || ct.conrelid || ' ') > 0
                AND rw.ev_action ~ ('.* :resno ' || a.attnum || ' :resname \S+ :ressortgroupref \d+ :resorigtbl '
                    || ct.conrelid || ' :resorigcol (?:'
                    || replace(substr(ct.conkey::text, 2, length(ct.conkey::text) - 2), ',', '|') || ') .*')
            )
        LEFT JOIN pg_constraint chk ON (chk.contype = 'c' AND cardinality(chk.conkey) = 1)
            AND (
                chk.conrelid = c.oid AND a.attnum = ANY (chk.conkey)
                OR rw.ev_action IS NOT NULL AND chk.conrelid != 0
                AND strpos(rw.ev_action, ':resorigtbl ' || chk.conrelid || ' ') > 0
                AND rw.ev_action ~ ('.* :resno ' || a.attnum || ' :resname \S+ :ressortgroupref \d+ :resorigtbl '
                    || chk.conrelid || ' :resorigcol (?:'
                    || replace(substr(chk.conkey::text, 2, length(chk.conkey::text) - 2), ',', '|') || ') .*')
            )
        LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
            ON a.attcollation = co.oid AND (nco.nspname != 'pg_catalog' OR co.collname != 'default')
    WHERE
        a.attnum > 0 AND t.typname != '' AND NOT a.attisdropped
        AND c.relname = :tableName
        AND d.nspname = :schemaName
    ORDER BY
        a.attnum;
    SQL;
    $schemaName = $table->getSchemaName() ?: $this->defaultSchema;
    $tableName = $table->getName();
    $columns = $this->db->createCommand($sql, [
        ':schemaName' => $schemaName,
        ':tableName' => $tableName,
    ])->queryAll();
    if (empty($columns)) {
        return false;
    }
    /** @psalm-var ColumnArray $info */
    foreach ($columns as $info) {
        $info = array_change_key_case($info);
        $info['schema'] = $schemaName;
        $info['table'] = $tableName;
        /** @psalm-var ColumnArray $info */
        $column = $this->loadColumn($info);
        $table->column($info['column_name'], $column);
        if ($column instanceof SequenceColumnInterface
            && $column->isPrimaryKey()
            && $table->getSequenceName() === null
        ) {
            $table->sequenceName($column->getSequenceName());
        }
    }
    return true;
}

            
findConstraints() protected method

protected void findConstraints ( \Yiisoft\Db\Schema\TableSchemaInterface $table )
$table \Yiisoft\Db\Schema\TableSchemaInterface

                protected function findConstraints(TableSchemaInterface $table): void
{
    $tableName = $this->resolveFullName($table->getName(), $table->getSchemaName());
    $table->checks(...$this->getTableMetadata($tableName, SchemaInterface::CHECKS));
    $table->foreignKeys(...$this->getTableMetadata($tableName, SchemaInterface::FOREIGN_KEYS));
    $table->indexes(...$this->getTableMetadata($tableName, SchemaInterface::INDEXES));
}

            
findSchemaNames() protected method

protected array findSchemaNames ( )

                protected function findSchemaNames(): array
{
    $sql = <<<SQL
    SELECT "ns"."nspname"
    FROM "pg_namespace" AS "ns"
    WHERE "ns"."nspname" != 'information_schema' AND "ns"."nspname" NOT LIKE 'pg_%'
    ORDER BY "ns"."nspname" ASC
    SQL;
    /** @var string[] */
    return $this->db->createCommand($sql)->queryColumn();
}

            
findTableComment() protected method

protected void findTableComment ( \Yiisoft\Db\Schema\TableSchemaInterface $tableSchema )
$tableSchema \Yiisoft\Db\Schema\TableSchemaInterface

                protected function findTableComment(TableSchemaInterface $tableSchema): void
{
    $sql = <<<SQL
    SELECT obj_description(pc.oid, 'pg_class')
    FROM pg_catalog.pg_class pc
    INNER JOIN pg_namespace pn ON pc.relnamespace = pn.oid
    WHERE
    pc.relname=:tableName AND
    pn.nspname=:schemaName
    SQL;
    $comment = $this->db->createCommand($sql, [
        ':schemaName' => $tableSchema->getSchemaName() ?: $this->defaultSchema,
        ':tableName' => $tableSchema->getName(),
    ])->queryScalar();
    $tableSchema->comment(is_string($comment) ? $comment : null);
}

            
findTableNames() protected method

protected array findTableNames ( string $schema '' )
$schema string

                protected function findTableNames(string $schema = ''): array
{
    if ($schema === '') {
        $schema = $this->defaultSchema;
    }
    $sql = <<<SQL
    SELECT c.relname AS table_name
    FROM pg_class c
    INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
    WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f', 'p')
    ORDER BY c.relname
    SQL;
    /** @var string[] */
    return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
}

            
findViewNames() protected method

protected array findViewNames ( string $schema '' )
$schema string

                protected function findViewNames(string $schema = ''): array
{
    if ($schema === '') {
        $schema = $this->defaultSchema;
    }
    $sql = <<<SQL
    SELECT c.relname AS table_name
    FROM pg_class c
    INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
    WHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm')
    ORDER BY c.relname
    SQL;
    /** @var string[] */
    return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
}

            
loadResultColumn() protected method

protected \Yiisoft\Db\Schema\Column\ColumnInterface|null loadResultColumn ( array $metadata )
$metadata array

                protected function loadResultColumn(array $metadata): ?ColumnInterface
{
    if (empty($metadata['native_type'])) {
        return null;
    }
    $dbType = $metadata['native_type'];
    $columnInfo = ['source' => ColumnInfoSource::QUERY_RESULT];
    if (!empty($metadata['table'])) {
        $columnInfo['table'] = $metadata['table'];
        $columnInfo['name'] = $metadata['name'];
    } elseif (!empty($metadata['name'])) {
        $columnInfo['name'] = $metadata['name'];
    }
    if ($metadata['precision'] !== -1) {
        $columnInfo['size'] = match ($dbType) {
            'varchar', 'bpchar' => $metadata['precision'] - 4,
            'numeric' => (($metadata['precision'] - 4) >> 16) & 0xFFFF,
            'interval' => ($metadata['precision'] & 0xFFFF) === 0xFFFF ? 6 : $metadata['precision'] & 0xFFFF,
            default => $metadata['precision'],
        };
        if ($dbType === 'numeric') {
            $columnInfo['scale'] = ($metadata['precision'] - 4) & 0xFFFF;
        }
    }
    $isArray = $dbType[0] === '_';
    if ($isArray) {
        $dbType = substr($dbType, 1);
    }
    if ($metadata['pgsql:oid'] > 16000) {
        /** @var string[] $typeInfo */
        $typeInfo = $this->db->createCommand(
            <<<SQL
            SELECT
                ns.nspname AS schema,
                COALESCE(t2.typname, t.typname) AS typname,
                COALESCE(t2.typtype, t.typtype) AS typtype,
                CASE WHEN COALESCE(t2.typtype, t.typtype) = 'e'::char
                    THEN array_to_string(
                        (
                            SELECT array_agg(enumlabel)
                            FROM pg_enum
                            WHERE enumtypid = COALESCE(t2.oid, t.oid)
                        )::varchar[],
                    ',')
                    ELSE NULL
                END AS values
            FROM pg_type AS t
            LEFT JOIN pg_type AS t2 ON t.typcategory='A' AND t2.oid = t.typelem OR t.typbasetype > 0 AND t2.oid = t.typbasetype
            LEFT JOIN pg_namespace AS ns ON ns.oid = COALESCE(t2.typnamespace, t.typnamespace)
            WHERE t.oid = :oid
            SQL,
            [':oid' => $metadata['pgsql:oid']],
        )->queryOne();
        $dbType = $this->resolveFullName($typeInfo['typname'], $typeInfo['schema']);
        if ($typeInfo['typtype'] === 'c') {
            $structured = new TableSchema($typeInfo['typname'], $typeInfo['schema']);
            if ($this->findColumns($structured)) {
                $columnInfo['columns'] = $structured->getColumns();
            }
            $columnInfo['type'] = ColumnType::STRUCTURED;
        } elseif (!empty($typeInfo['values'])) {
            $columnInfo['values'] = explode(',', str_replace(["''"], ["'"], $typeInfo['values']));
        }
    }
    $columnFactory = $this->db->getColumnFactory();
    $column = $columnFactory->fromDbType($dbType, $columnInfo);
    if ($isArray) {
        $columnInfo['dbType'] = $dbType;
        $columnInfo['column'] = $column;
        return $columnFactory->fromType(ColumnType::ARRAY, $columnInfo);
    }
    return $column;
}

            
loadTableChecks() protected method

protected array loadTableChecks ( string $tableName )
$tableName string

                protected function loadTableChecks(string $tableName): array
{
    /** @var Check[] */
    return $this->loadTableConstraints($tableName, self::CHECKS);
}

            
loadTableDefaultValues() protected method

protected array loadTableDefaultValues ( string $tableName )
$tableName string

                protected function loadTableDefaultValues(string $tableName): array
{
    throw new NotSupportedException(__METHOD__ . ' is not supported by PostgreSQL.');
}

            
loadTableForeignKeys() protected method

protected array loadTableForeignKeys ( string $tableName )
$tableName string

                protected function loadTableForeignKeys(string $tableName): array
{
    /** @var ForeignKey[] */
    return $this->loadTableConstraints($tableName, self::FOREIGN_KEYS);
}

            
loadTableIndexes() protected method

protected array loadTableIndexes ( string $tableName )
$tableName string

                protected function loadTableIndexes(string $tableName): array
{
    $sql = <<<SQL
    SELECT
        "ic"."relname" AS "name",
        "ia"."attname" AS "column_name",
        "i"."indisunique" AS "is_unique",
        "i"."indisprimary" AS "is_primary_key"
    FROM "pg_class" AS "tc"
    INNER JOIN "pg_namespace" AS "tcns"
        ON "tcns"."oid" = "tc"."relnamespace"
    LEFT JOIN "pg_rewrite" AS "rw"
        ON "tc"."relkind" = 'v' AND "rw"."ev_class" = "tc"."oid" AND "rw"."rulename" = '_RETURN'
    INNER JOIN "pg_index" AS "i"
        ON "i"."indrelid" = "tc"."oid"
            OR "rw"."ev_action" IS NOT NULL
            AND strpos("rw"."ev_action", ':resorigtbl ' || "i"."indrelid" || ' :resorigcol ' || "i"."indkey"[0] || ' ') > 0
    INNER JOIN "pg_class" AS "ic"
        ON "ic"."oid" = "i"."indexrelid"
    INNER JOIN "pg_attribute" AS "ia"
        ON "ia"."attrelid" = "i"."indexrelid" AND "ia"."attnum" <= cardinality("i"."indoption")
    WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
    ORDER BY "i"."indkey", "ia"."attnum" ASC
    SQL;
    $nameParts = $this->db->getQuoter()->getTableNameParts($tableName);
    $indexes = $this->db->createCommand($sql, [
        ':schemaName' => $nameParts['schemaName'] ?? $this->defaultSchema,
        ':tableName' => $nameParts['name'],
    ])->queryAll();
    /** @psalm-var list<array<string,mixed>> $indexes */
    $indexes = array_map(array_change_key_case(...), $indexes);
    $indexes = DbArrayHelper::arrange($indexes, ['name']);
    $result = [];
    /**
     * @var string $name
     * @psalm-var list<
     *   array{
     *     name: string,
     *     column_name: string,
     *     is_unique: bool,
     *     is_primary_key: bool
     *   }
     * > $index
     */
    foreach ($indexes as $name => $index) {
        $result[$name] = new Index(
            $name,
            array_column($index, 'column_name'),
            $index[0]['is_unique'],
            $index[0]['is_primary_key'],
        );
    }
    return $result;
}

            
loadTableSchema() protected method

protected \Yiisoft\Db\Schema\TableSchemaInterface|null loadTableSchema ( string $name )
$name string

                protected function loadTableSchema(string $name): ?TableSchemaInterface
{
    $table = new TableSchema(...$this->db->getQuoter()->getTableNameParts($name));
    if ($this->findColumns($table)) {
        $this->findTableComment($table);
        $this->findConstraints($table);
        return $table;
    }
    return null;
}

            
resolveFullName() protected method

protected string resolveFullName ( string $name, string $schemaName '' )
$name string
$schemaName string

                protected function resolveFullName(string $name, string $schemaName = ''): string
{
    $quoter = $this->db->getQuoter();
    $rawName = $quoter->getRawTableName($name);
    return match ($schemaName) {
        '', 'pg_catalog', $this->defaultSchema => $rawName,
        default => $quoter->getRawTableName($schemaName) . ".$rawName",
    };
}