| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 | <?php/** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */namespace yii\db\pgsql;use yii\base\NotSupportedException;use yii\db\CheckConstraint;use yii\db\Constraint;use yii\db\ConstraintFinderInterface;use yii\db\ConstraintFinderTrait;use yii\db\Expression;use yii\db\ForeignKeyConstraint;use yii\db\IndexConstraint;use yii\db\TableSchema;use yii\db\ViewFinderTrait;use yii\helpers\ArrayHelper;/** * Schema is the class for retrieving metadata from a PostgreSQL database * (version 9.x and above). * * @author Gevik Babakhani <gevikb@gmail.com> * @since 2.0 */class Schema extends \yii\db\Schema implements ConstraintFinderInterface{    use ViewFinderTrait;    use ConstraintFinderTrait;    const TYPE_JSONB = 'jsonb';    /**     * @var string the default schema used for the current session.     */    public $defaultSchema = 'public';    /**     * {@inheritdoc}     */    public $columnSchemaClass = 'yii\db\pgsql\ColumnSchema';    /**     * @var array mapping from physical column types (keys) to abstract     * column types (values)     * @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE     */    public $typeMap = [        'bit' => self::TYPE_INTEGER,        'bit varying' => self::TYPE_INTEGER,        'varbit' => self::TYPE_INTEGER,        'bool' => self::TYPE_BOOLEAN,        'boolean' => self::TYPE_BOOLEAN,        'box' => self::TYPE_STRING,        'circle' => self::TYPE_STRING,        'point' => self::TYPE_STRING,        'line' => self::TYPE_STRING,        'lseg' => self::TYPE_STRING,        'polygon' => self::TYPE_STRING,        'path' => self::TYPE_STRING,        'character' => self::TYPE_CHAR,        'char' => self::TYPE_CHAR,        'bpchar' => self::TYPE_CHAR,        'character varying' => self::TYPE_STRING,        'varchar' => self::TYPE_STRING,        'text' => self::TYPE_TEXT,        'bytea' => self::TYPE_BINARY,        'cidr' => self::TYPE_STRING,        'inet' => self::TYPE_STRING,        'macaddr' => self::TYPE_STRING,        'real' => self::TYPE_FLOAT,        'float4' => self::TYPE_FLOAT,        'double precision' => self::TYPE_DOUBLE,        'float8' => self::TYPE_DOUBLE,        'decimal' => self::TYPE_DECIMAL,        'numeric' => self::TYPE_DECIMAL,        'money' => self::TYPE_MONEY,        'smallint' => self::TYPE_SMALLINT,        'int2' => self::TYPE_SMALLINT,        'int4' => self::TYPE_INTEGER,        'int' => self::TYPE_INTEGER,        'integer' => self::TYPE_INTEGER,        'bigint' => self::TYPE_BIGINT,        'int8' => self::TYPE_BIGINT,        'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!        'smallserial' => self::TYPE_SMALLINT,        'serial2' => self::TYPE_SMALLINT,        'serial4' => self::TYPE_INTEGER,        'serial' => self::TYPE_INTEGER,        'bigserial' => self::TYPE_BIGINT,        'serial8' => self::TYPE_BIGINT,        'pg_lsn' => self::TYPE_BIGINT,        'date' => self::TYPE_DATE,        'interval' => self::TYPE_STRING,        'time without time zone' => self::TYPE_TIME,        'time' => self::TYPE_TIME,        'time with time zone' => self::TYPE_TIME,        'timetz' => self::TYPE_TIME,        'timestamp without time zone' => self::TYPE_TIMESTAMP,        'timestamp' => self::TYPE_TIMESTAMP,        'timestamp with time zone' => self::TYPE_TIMESTAMP,        'timestamptz' => self::TYPE_TIMESTAMP,        'abstime' => self::TYPE_TIMESTAMP,        'tsquery' => self::TYPE_STRING,        'tsvector' => self::TYPE_STRING,        'txid_snapshot' => self::TYPE_STRING,        'unknown' => self::TYPE_STRING,        'uuid' => self::TYPE_STRING,        'json' => self::TYPE_JSON,        'jsonb' => self::TYPE_JSON,        'xml' => self::TYPE_STRING,    ];    /**     * {@inheritdoc}     */    protected $tableQuoteCharacter = '"';    /**     * {@inheritdoc}     */    protected function resolveTableName($name)    {        $resolvedName = new TableSchema();        $parts = explode('.', str_replace('"', '', $name));        if (isset($parts[1])) {            $resolvedName->schemaName = $parts[0];            $resolvedName->name = $parts[1];        } else {            $resolvedName->schemaName = $this->defaultSchema;            $resolvedName->name = $name;        }        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;        return $resolvedName;    }    /**     * {@inheritdoc}     */    protected function findSchemaNames()    {        static $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" ASCSQL;        return $this->db->createCommand($sql)->queryColumn();    }    /**     * {@inheritdoc}     */    protected function findTableNames($schema = '')    {        if ($schema === '') {            $schema = $this->defaultSchema;        }        $sql = <<<'SQL'SELECT c.relname AS table_nameFROM pg_class cINNER JOIN pg_namespace ns ON ns.oid = c.relnamespaceWHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f', 'p')ORDER BY c.relnameSQL;        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();    }    /**     * {@inheritdoc}     */    protected function loadTableSchema($name)    {        $table = new TableSchema();        $this->resolveTableNames($table, $name);        if ($this->findColumns($table)) {            $this->findConstraints($table);            return $table;        }        return null;    }    /**     * {@inheritdoc}     */    protected function loadTablePrimaryKey($tableName)    {        return $this->loadTableConstraints($tableName, 'primaryKey');    }    /**     * {@inheritdoc}     */    protected function loadTableForeignKeys($tableName)    {        return $this->loadTableConstraints($tableName, 'foreignKeys');    }    /**     * {@inheritdoc}     */    protected function loadTableIndexes($tableName)    {        static $sql = <<<'SQL'SELECT    "ic"."relname" AS "name",    "ia"."attname" AS "column_name",    "i"."indisunique" AS "index_is_unique",    "i"."indisprimary" AS "index_is_primary"FROM "pg_class" AS "tc"INNER JOIN "pg_namespace" AS "tcns"    ON "tcns"."oid" = "tc"."relnamespace"INNER JOIN "pg_index" AS "i"    ON "i"."indrelid" = "tc"."oid"INNER JOIN "pg_class" AS "ic"    ON "ic"."oid" = "i"."indexrelid"INNER JOIN "pg_attribute" AS "ia"    ON "ia"."attrelid" = "i"."indrelid" AND "ia"."attnum" = ANY ("i"."indkey")WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableNameORDER BY "ia"."attnum" ASCSQL;        $resolvedName = $this->resolveTableName($tableName);        $indexes = $this->db->createCommand($sql, [            ':schemaName' => $resolvedName->schemaName,            ':tableName' => $resolvedName->name,        ])->queryAll();        $indexes = $this->normalizePdoRowKeyCase($indexes, true);        $indexes = ArrayHelper::index($indexes, null, 'name');        $result = [];        foreach ($indexes as $name => $index) {            $result[] = new IndexConstraint([                'isPrimary' => (bool) $index[0]['index_is_primary'],                'isUnique' => (bool) $index[0]['index_is_unique'],                'name' => $name,                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),            ]);        }        return $result;    }    /**     * {@inheritdoc}     */    protected function loadTableUniques($tableName)    {        return $this->loadTableConstraints($tableName, 'uniques');    }    /**     * {@inheritdoc}     */    protected function loadTableChecks($tableName)    {        return $this->loadTableConstraints($tableName, 'checks');    }    /**     * {@inheritdoc}     * @throws NotSupportedException if this method is called.     */    protected function loadTableDefaultValues($tableName)    {        throw new NotSupportedException('PostgreSQL does not support default value constraints.');    }    /**     * Creates a query builder for the PostgreSQL database.     * @return QueryBuilder query builder instance     */    public function createQueryBuilder()    {        return new QueryBuilder($this->db);    }    /**     * Resolves the table name and schema name (if any).     * @param TableSchema $table the table metadata object     * @param string $name the table name     */    protected function resolveTableNames($table, $name)    {        $parts = explode('.', str_replace('"', '', $name));        if (isset($parts[1])) {            $table->schemaName = $parts[0];            $table->name = $parts[1];        } else {            $table->schemaName = $this->defaultSchema;            $table->name = $parts[0];        }        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;    }    /**     * {@inheritdoc]     */    protected function findViewNames($schema = '')    {        if ($schema === '') {            $schema = $this->defaultSchema;        }        $sql = <<<'SQL'SELECT c.relname AS table_nameFROM pg_class cINNER JOIN pg_namespace ns ON ns.oid = c.relnamespaceWHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm')ORDER BY c.relnameSQL;        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();    }    /**     * Collects the foreign key column details for the given table.     * @param TableSchema $table the table metadata     */    protected function findConstraints($table)    {        $tableName = $this->quoteValue($table->name);        $tableSchema = $this->quoteValue($table->schemaName);        //We need to extract the constraints de hard way since:        //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us        $sql = <<<SQLselect    ct.conname as constraint_name,    a.attname as column_name,    fc.relname as foreign_table_name,    fns.nspname as foreign_table_schema,    fa.attname as foreign_column_namefrom    (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s       FROM pg_constraint ct    ) AS ct    inner join pg_class c on c.oid=ct.conrelid    inner join pg_namespace ns on c.relnamespace=ns.oid    inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s]    left join pg_class fc on fc.oid=ct.confrelid    left join pg_namespace fns on fc.relnamespace=fns.oid    left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s]where    ct.contype='f'    and c.relname={$tableName}    and ns.nspname={$tableSchema}order by    fns.nspname, fc.relname, a.attnumSQL;        $constraints = [];        foreach ($this->db->createCommand($sql)->queryAll() as $constraint) {            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {                $constraint = array_change_key_case($constraint, CASE_LOWER);            }            if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {                $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];            } else {                $foreignTable = $constraint['foreign_table_name'];            }            $name = $constraint['constraint_name'];            if (!isset($constraints[$name])) {                $constraints[$name] = [                    'tableName' => $foreignTable,                    'columns' => [],                ];            }            $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];        }        foreach ($constraints as $name => $constraint) {            $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);        }    }    /**     * Gets information about given table unique indexes.     * @param TableSchema $table the table metadata     * @return array with index and column names     */    protected function getUniqueIndexInformation($table)    {        $sql = <<<'SQL'SELECT    i.relname as indexname,    pg_get_indexdef(idx.indexrelid, k + 1, TRUE) AS columnnameFROM (  SELECT *, generate_subscripts(indkey, 1) AS k  FROM pg_index) idxINNER JOIN pg_class i ON i.oid = idx.indexrelidINNER JOIN pg_class c ON c.oid = idx.indrelidINNER JOIN pg_namespace ns ON c.relnamespace = ns.oidWHERE idx.indisprimary = FALSE AND idx.indisunique = TRUEAND c.relname = :tableName AND ns.nspname = :schemaNameORDER BY i.relname, kSQL;        return $this->db->createCommand($sql, [            ':schemaName' => $table->schemaName,            ':tableName' => $table->name,        ])->queryAll();    }    /**     * Returns all unique indexes for the given table.     *     * Each array element is of the following structure:     *     * ```php     * [     *     'IndexName1' => ['col1' [, ...]],     *     'IndexName2' => ['col2' [, ...]],     * ]     * ```     *     * @param TableSchema $table the table metadata     * @return array all unique indexes for the given table.     */    public function findUniqueIndexes($table)    {        $uniqueIndexes = [];        foreach ($this->getUniqueIndexInformation($table) as $row) {            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {                $row = array_change_key_case($row, CASE_LOWER);            }            $column = $row['columnname'];            if (!empty($column) && $column[0] === '"') {                // postgres will quote names that are not lowercase-only                // https://github.com/yiisoft/yii2/issues/10613                $column = substr($column, 1, -1);            }            $uniqueIndexes[$row['indexname']][] = $column;        }        return $uniqueIndexes;    }    /**     * Collects the metadata of table columns.     * @param TableSchema $table the table metadata     * @return bool whether the table exists in the database     */    protected function findColumns($table)    {        $tableName = $this->db->quoteValue($table->name);        $schemaName = $this->db->quoteValue($table->schemaName);        $orIdentity = '';        if (version_compare($this->db->serverVersion, '12.0', '>=')) {            $orIdentity = 'OR attidentity != \'\'';        }        $sql = <<<SQLSELECT    d.nspname AS table_schema,    c.relname AS table_name,    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,    a.attlen AS character_maximum_length,    pg_catalog.col_description(c.oid, a.attnum) AS column_comment,    a.atttypmod AS modifier,    a.attnotnull = false AS is_nullable,    CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) 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 enum_values,    CASE atttypid         WHEN 21 /*int2*/ THEN 16         WHEN 23 /*int4*/ THEN 32         WHEN 20 /*int8*/ THEN 64         WHEN 1700 /*numeric*/ THEN              CASE WHEN atttypmod = -1               THEN null               ELSE ((atttypmod - 4) >> 16) & 65535               END         WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/         WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/         ELSE null      END   AS numeric_precision,      CASE        WHEN atttypid IN (21, 23, 20) THEN 0        WHEN atttypid IN (1700) THEN        CASE            WHEN atttypmod = -1 THEN null            ELSE (atttypmod - 4) & 65535        END           ELSE null      END AS numeric_scale,    CAST(             information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))             AS numeric    ) AS size,    a.attnum = any (ct.conkey) as is_pkey,    COALESCE(NULLIF(a.attndims, 0), NULLIF(t.typndims, 0), (t.typcategory='A')::int) AS dimensionFROM    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_constraint ct ON ct.conrelid = c.oid AND ct.contype = 'p'WHERE    a.attnum > 0 AND t.typname != '' AND NOT a.attisdropped    AND c.relname = {$tableName}    AND d.nspname = {$schemaName}ORDER BY    a.attnum;SQL;        $columns = $this->db->createCommand($sql)->queryAll();        if (empty($columns)) {            return false;        }        foreach ($columns as $column) {            if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {                $column = array_change_key_case($column, CASE_LOWER);            }            $column = $this->loadColumnSchema($column);            $table->columns[$column->name] = $column;            if ($column->isPrimaryKey) {                $table->primaryKey[] = $column->name;                if ($table->sequenceName === null) {                    $table->sequenceName = $column->sequenceName;                }                $column->defaultValue = null;            } elseif ($column->defaultValue) {                if ($column->type === 'timestamp' && $column->defaultValue === 'now()') {                    $column->defaultValue = new Expression($column->defaultValue);                } elseif ($column->type === 'boolean') {                    $column->defaultValue = ($column->defaultValue === 'true');                } elseif (strncasecmp($column->dbType, 'bit', 3) === 0 || strncasecmp($column->dbType, 'varbit', 6) === 0) {                    $column->defaultValue = bindec(trim($column->defaultValue, 'B\''));                } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {                    $column->defaultValue = $column->phpTypecast($matches[1]);                } elseif (preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) {                    if ($matches[2] === 'NULL') {                        $column->defaultValue = null;                    } else {                        $column->defaultValue = $column->phpTypecast($matches[2]);                    }                } else {                    $column->defaultValue = $column->phpTypecast($column->defaultValue);                }            }        }        return true;    }    /**     * Loads the column information into a [[ColumnSchema]] object.     * @param array $info column information     * @return ColumnSchema the column schema object     */    protected function loadColumnSchema($info)    {        /** @var ColumnSchema $column */        $column = $this->createColumnSchema();        $column->allowNull = $info['is_nullable'];        $column->autoIncrement = $info['is_autoinc'];        $column->comment = $info['column_comment'];        $column->dbType = $info['data_type'];        $column->defaultValue = $info['column_default'];        $column->enumValues = ($info['enum_values'] !== null) ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null;        $column->unsigned = false; // has no meaning in PG        $column->isPrimaryKey = $info['is_pkey'];        $column->name = $info['column_name'];        $column->precision = $info['numeric_precision'];        $column->scale = $info['numeric_scale'];        $column->size = $info['size'] === null ? null : (int) $info['size'];        $column->dimension = (int)$info['dimension'];        // pg_get_serial_sequence() doesn't track DEFAULT value change. GENERATED BY IDENTITY columns always have null default value        if (isset($column->defaultValue) && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {            $column->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue);        } elseif (isset($info['sequence_name'])) {            $column->sequenceName = $this->resolveTableName($info['sequence_name'])->fullName;        }        if (isset($this->typeMap[$column->dbType])) {            $column->type = $this->typeMap[$column->dbType];        } else {            $column->type = self::TYPE_STRING;        }        $column->phpType = $this->getColumnPhpType($column);        return $column;    }    /**     * {@inheritdoc}     */    public function insert($table, $columns)    {        $params = [];        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);        $returnColumns = $this->getTableSchema($table)->primaryKey;        if (!empty($returnColumns)) {            $returning = [];            foreach ((array) $returnColumns as $name) {                $returning[] = $this->quoteColumnName($name);            }            $sql .= ' RETURNING ' . implode(', ', $returning);        }        $command = $this->db->createCommand($sql, $params);        $command->prepare(false);        $result = $command->queryOne();        return !$command->pdoStatement->rowCount() ? false : $result;    }    /**     * Loads multiple types of constraints and returns the specified ones.     * @param string $tableName table name.     * @param string $returnType return type:     * - primaryKey     * - foreignKeys     * - uniques     * - checks     * @return mixed constraints.     */    private function loadTableConstraints($tableName, $returnType)    {        static $sql = <<<'SQL'SELECT    "c"."conname" AS "name",    "a"."attname" AS "column_name",    "c"."contype" AS "type",    "ftcns"."nspname" AS "foreign_table_schema",    "ftc"."relname" AS "foreign_table_name",    "fa"."attname" AS "foreign_column_name",    "c"."confupdtype" AS "on_update",    "c"."confdeltype" AS "on_delete",    pg_get_constraintdef("c"."oid") AS "check_expr"FROM "pg_class" AS "tc"INNER JOIN "pg_namespace" AS "tcns"    ON "tcns"."oid" = "tc"."relnamespace"INNER JOIN "pg_constraint" AS "c"    ON "c"."conrelid" = "tc"."oid"INNER JOIN "pg_attribute" AS "a"    ON "a"."attrelid" = "c"."conrelid" AND "a"."attnum" = ANY ("c"."conkey")LEFT JOIN "pg_class" AS "ftc"    ON "ftc"."oid" = "c"."confrelid"LEFT JOIN "pg_namespace" AS "ftcns"    ON "ftcns"."oid" = "ftc"."relnamespace"LEFT JOIN "pg_attribute" "fa"    ON "fa"."attrelid" = "c"."confrelid" AND "fa"."attnum" = ANY ("c"."confkey")WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableNameORDER BY "a"."attnum" ASC, "fa"."attnum" ASCSQL;        static $actionTypes = [            'a' => 'NO ACTION',            'r' => 'RESTRICT',            'c' => 'CASCADE',            'n' => 'SET NULL',            'd' => 'SET DEFAULT',        ];        $resolvedName = $this->resolveTableName($tableName);        $constraints = $this->db->createCommand($sql, [            ':schemaName' => $resolvedName->schemaName,            ':tableName' => $resolvedName->name,        ])->queryAll();        $constraints = $this->normalizePdoRowKeyCase($constraints, true);        $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);        $result = [            'primaryKey' => null,            'foreignKeys' => [],            'uniques' => [],            'checks' => [],        ];        foreach ($constraints as $type => $names) {            foreach ($names as $name => $constraint) {                switch ($type) {                    case 'p':                        $result['primaryKey'] = new Constraint([                            'name' => $name,                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),                        ]);                        break;                    case 'f':                        $result['foreignKeys'][] = new ForeignKeyConstraint([                            'name' => $name,                            'columnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'column_name'))),                            'foreignSchemaName' => $constraint[0]['foreign_table_schema'],                            'foreignTableName' => $constraint[0]['foreign_table_name'],                            'foreignColumnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'foreign_column_name'))),                            'onDelete' => isset($actionTypes[$constraint[0]['on_delete']]) ? $actionTypes[$constraint[0]['on_delete']] : null,                            'onUpdate' => isset($actionTypes[$constraint[0]['on_update']]) ? $actionTypes[$constraint[0]['on_update']] : null,                        ]);                        break;                    case 'u':                        $result['uniques'][] = new Constraint([                            'name' => $name,                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),                        ]);                        break;                    case 'c':                        $result['checks'][] = new CheckConstraint([                            'name' => $name,                            'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),                            'expression' => $constraint[0]['check_expr'],                        ]);                        break;                }            }        }        foreach ($result as $type => $data) {            $this->setTableMetadata($tableName, $type, $data);        }        return $result[$returnType];    }}
 |