Schema.php 25 KB

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