PostgresSchema.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <?php
  2. /**
  3. * PHP Version 5.4
  4. *
  5. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @since CakePHP(tm) v 3.0.0
  15. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  16. */
  17. namespace Cake\Database\Schema;
  18. use Cake\Database\Exception;
  19. use Cake\Database\Schema\Table;
  20. /**
  21. * Schema management/reflection features for Postgres.
  22. */
  23. class PostgresSchema extends BaseSchema {
  24. /**
  25. * {@inheritdoc}
  26. *
  27. */
  28. public function listTablesSql($config) {
  29. $sql = 'SELECT table_name as name FROM information_schema.tables WHERE table_schema = ? ORDER BY name';
  30. $schema = empty($config['schema']) ? 'public' : $config['schema'];
  31. return [$sql, [$schema]];
  32. }
  33. /**
  34. * {@inheritdoc}
  35. *
  36. */
  37. public function describeTableSql($name, $config) {
  38. $sql =
  39. 'SELECT DISTINCT table_schema AS schema, column_name AS name, data_type AS type,
  40. is_nullable AS null, column_default AS default,
  41. character_maximum_length AS char_length,
  42. d.description as comment,
  43. ordinal_position
  44. FROM information_schema.columns c
  45. INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema)
  46. INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name)
  47. LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position)
  48. LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position)
  49. WHERE table_name = ? AND table_schema = ? AND table_catalog = ?
  50. ORDER BY ordinal_position';
  51. $schema = empty($config['schema']) ? 'public' : $config['schema'];
  52. return [$sql, [$name, $schema, $config['database']]];
  53. }
  54. /**
  55. * Convert a column definition to the abstract types.
  56. *
  57. * The returned type will be a type that
  58. * Cake\Database\Type can handle.
  59. *
  60. * @param string $column The column type + length
  61. * @throws Cake\Database\Exception when column cannot be parsed.
  62. * @return array Array of column information.
  63. */
  64. protected function _convertColumn($column) {
  65. preg_match('/([a-z\s]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
  66. if (empty($matches)) {
  67. throw new Exception(__d('cake_dev', 'Unable to parse column type from "%s"', $column));
  68. }
  69. $col = strtolower($matches[1]);
  70. $length = null;
  71. if (isset($matches[2])) {
  72. $length = (int)$matches[2];
  73. }
  74. if (in_array($col, array('date', 'time', 'boolean'))) {
  75. return ['type' => $col, 'length' => null];
  76. }
  77. if (strpos($col, 'timestamp') !== false) {
  78. return ['type' => 'datetime', 'length' => null];
  79. }
  80. if ($col === 'serial' || $col === 'integer') {
  81. return ['type' => 'integer', 'length' => 10];
  82. }
  83. if ($col === 'bigserial' || $col === 'bigint') {
  84. return ['type' => 'biginteger', 'length' => 20];
  85. }
  86. if ($col === 'smallint') {
  87. return ['type' => 'integer', 'length' => 5];
  88. }
  89. if ($col === 'inet') {
  90. return ['type' => 'string', 'length' => 39];
  91. }
  92. if ($col === 'uuid') {
  93. return ['type' => 'string', 'fixed' => true, 'length' => 36];
  94. }
  95. if ($col === 'char' || $col === 'character') {
  96. return ['type' => 'string', 'fixed' => true, 'length' => $length];
  97. }
  98. if (strpos($col, 'char') !== false) {
  99. return ['type' => 'string', 'length' => $length];
  100. }
  101. if (strpos($col, 'text') !== false) {
  102. return ['type' => 'text', 'length' => null];
  103. }
  104. if ($col === 'bytea') {
  105. return ['type' => 'binary', 'length' => null];
  106. }
  107. if ($col === 'real' || strpos($col, 'double') !== false) {
  108. return ['type' => 'float', 'length' => null];
  109. }
  110. if (
  111. strpos($col, 'numeric') !== false ||
  112. strpos($col, 'money') !== false ||
  113. strpos($col, 'decimal') !== false
  114. ) {
  115. return ['type' => 'decimal', 'length' => null];
  116. }
  117. return ['type' => 'text', 'length' => null];
  118. }
  119. /**
  120. * {@inheritdoc}
  121. *
  122. */
  123. public function convertFieldDescription(Table $table, $row) {
  124. $field = $this->_convertColumn($row['type']);
  125. if ($field['type'] === 'boolean') {
  126. if ($row['default'] === 'true') {
  127. $row['default'] = 1;
  128. }
  129. if ($row['default'] === 'false') {
  130. $row['default'] = 0;
  131. }
  132. }
  133. $field += [
  134. 'null' => $row['null'] === 'YES' ? true : false,
  135. 'default' => $row['default'],
  136. 'comment' => $row['comment']
  137. ];
  138. $field['length'] = $row['char_length'] ?: $field['length'];
  139. $table->addColumn($row['name'], $field);
  140. }
  141. /**
  142. * {@inheritdoc}
  143. *
  144. */
  145. public function describeIndexSql($table, $config) {
  146. $sql = 'SELECT
  147. c2.relname,
  148. i.indisprimary,
  149. i.indisunique,
  150. i.indisvalid,
  151. pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS statement
  152. FROM pg_catalog.pg_class AS c,
  153. pg_catalog.pg_class AS c2,
  154. pg_catalog.pg_index AS i
  155. WHERE c.oid = (
  156. SELECT c.oid
  157. FROM pg_catalog.pg_class c
  158. LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
  159. WHERE c.relname = ?
  160. AND pg_catalog.pg_table_is_visible(c.oid)
  161. AND n.nspname = ?
  162. )
  163. AND c.oid = i.indrelid
  164. AND i.indexrelid = c2.oid
  165. ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname';
  166. $schema = 'public';
  167. if (!empty($config['schema'])) {
  168. $schema = $config['schema'];
  169. }
  170. return [$sql, [$table, $schema]];
  171. }
  172. /**
  173. * {@inheritdoc}
  174. *
  175. */
  176. public function convertIndexDescription(Table $table, $row) {
  177. $type = Table::INDEX_INDEX;
  178. $name = $row['relname'];
  179. if ($row['indisprimary']) {
  180. $name = $type = Table::CONSTRAINT_PRIMARY;
  181. }
  182. if ($row['indisunique'] && $type === Table::INDEX_INDEX) {
  183. $type = Table::CONSTRAINT_UNIQUE;
  184. }
  185. preg_match('/\(([^\)]+)\)/', $row['statement'], $matches);
  186. $columns = explode(', ', $matches[1]);
  187. if ($type === Table::CONSTRAINT_PRIMARY || $type === Table::CONSTRAINT_UNIQUE) {
  188. $table->addConstraint($name, [
  189. 'type' => $type,
  190. 'columns' => $columns
  191. ]);
  192. return;
  193. }
  194. $table->addIndex($name, [
  195. 'type' => $type,
  196. 'columns' => $columns
  197. ]);
  198. }
  199. /**
  200. * {@inheritdoc}
  201. *
  202. */
  203. public function describeForeignKeySql($table, $config) {
  204. $sql = "SELECT
  205. r.conname AS name,
  206. r.confupdtype AS update_type,
  207. r.confdeltype AS delete_type,
  208. pg_catalog.pg_get_constraintdef(r.oid, true) AS definition
  209. FROM pg_catalog.pg_constraint AS r
  210. WHERE r.conrelid = (
  211. SELECT c.oid
  212. FROM pg_catalog.pg_class AS c,
  213. pg_catalog.pg_namespace AS n
  214. WHERE c.relname = ?
  215. AND n.nspname = ?
  216. AND n.oid = c.relnamespace
  217. )
  218. AND r.contype = 'f'";
  219. $schema = empty($config['schema']) ? 'public' : $config['schema'];
  220. return [$sql, [$table, $schema]];
  221. }
  222. /**
  223. * {@inheritdoc}
  224. *
  225. */
  226. public function convertForeignKeyDescription(Table $table, $row) {
  227. preg_match('/REFERENCES ([^\)]+)\(([^\)]+)\)/', $row['definition'], $matches);
  228. $tableName = $matches[1];
  229. $column = $matches[2];
  230. preg_match('/FOREIGN KEY \(([^\)]+)\) REFERENCES/', $row['definition'], $matches);
  231. $columns = explode(',', $matches[1]);
  232. $data = [
  233. 'type' => Table::CONSTRAINT_FOREIGN,
  234. 'columns' => $columns,
  235. 'references' => [$tableName, $column],
  236. 'update' => $this->_convertOnClause($row['update_type']),
  237. 'delete' => $this->_convertOnClause($row['delete_type']),
  238. ];
  239. $name = $row['name'];
  240. $table->addConstraint($name, $data);
  241. }
  242. /**
  243. * {@inheritdoc}
  244. *
  245. */
  246. protected function _convertOnClause($clause) {
  247. if ($clause === 'r') {
  248. return Table::ACTION_RESTRICT;
  249. }
  250. if ($clause === 'a') {
  251. return Table::ACTION_NO_ACTION;
  252. }
  253. if ($clause === 'c') {
  254. return Table::ACTION_CASCADE;
  255. }
  256. return Table::ACTION_SET_NULL;
  257. }
  258. /**
  259. * {@inheritdoc}
  260. *
  261. */
  262. public function columnSql(Table $table, $name) {
  263. $data = $table->column($name);
  264. $out = $this->_driver->quoteIdentifier($name);
  265. $typeMap = [
  266. 'biginteger' => ' BIGINT',
  267. 'boolean' => ' BOOLEAN',
  268. 'binary' => ' BYTEA',
  269. 'float' => ' FLOAT',
  270. 'decimal' => ' DECIMAL',
  271. 'text' => ' TEXT',
  272. 'date' => ' DATE',
  273. 'time' => ' TIME',
  274. 'datetime' => ' TIMESTAMP',
  275. 'timestamp' => ' TIMESTAMP',
  276. ];
  277. if (isset($typeMap[$data['type']])) {
  278. $out .= $typeMap[$data['type']];
  279. }
  280. if ($data['type'] === 'integer') {
  281. $type = ' INTEGER';
  282. if (in_array($name, (array)$table->primaryKey())) {
  283. $type = ' SERIAL';
  284. unset($data['null'], $data['default']);
  285. }
  286. $out .= $type;
  287. }
  288. if ($data['type'] === 'string') {
  289. $isFixed = !empty($data['fixed']);
  290. $type = ' VARCHAR';
  291. if ($isFixed) {
  292. $type = ' CHAR';
  293. }
  294. if ($isFixed && isset($data['length']) && $data['length'] == 36) {
  295. $type = ' UUID';
  296. }
  297. $out .= $type;
  298. if (isset($data['length']) && $data['length'] != 36) {
  299. $out .= '(' . (int)$data['length'] . ')';
  300. }
  301. }
  302. if ($data['type'] === 'float' && isset($data['precision'])) {
  303. $out .= '(' . (int)$data['precision'] . ')';
  304. }
  305. if ($data['type'] === 'decimal' &&
  306. (isset($data['length']) || isset($data['precision']))
  307. ) {
  308. $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
  309. }
  310. if (isset($data['null']) && $data['null'] === false) {
  311. $out .= ' NOT NULL';
  312. }
  313. if (isset($data['null']) && $data['null'] === true) {
  314. $out .= ' DEFAULT NULL';
  315. unset($data['default']);
  316. }
  317. if (isset($data['default']) && $data['type'] !== 'timestamp') {
  318. $out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
  319. }
  320. return $out;
  321. }
  322. /**
  323. * {@inheritdoc}
  324. *
  325. */
  326. public function indexSql(Table $table, $name) {
  327. $data = $table->index($name);
  328. $columns = array_map(
  329. [$this->_driver, 'quoteIdentifier'],
  330. $data['columns']
  331. );
  332. return sprintf('CREATE INDEX %s ON %s (%s)',
  333. $this->_driver->quoteIdentifier($name),
  334. $this->_driver->quoteIdentifier($table->name()),
  335. implode(', ', $columns)
  336. );
  337. }
  338. /**
  339. * {@inheritdoc}
  340. *
  341. */
  342. public function constraintSql(Table $table, $name) {
  343. $data = $table->constraint($name);
  344. $out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
  345. if ($data['type'] === Table::CONSTRAINT_PRIMARY) {
  346. $out = 'PRIMARY KEY';
  347. }
  348. if ($data['type'] === Table::CONSTRAINT_UNIQUE) {
  349. $out .= ' UNIQUE';
  350. }
  351. return $this->_keySql($out, $data);
  352. }
  353. /**
  354. * Helper method for generating key SQL snippets.
  355. *
  356. * @param string $prefix The key prefix
  357. * @param array $data Key data.
  358. * @return string
  359. */
  360. protected function _keySql($prefix, $data) {
  361. $columns = array_map(
  362. [$this->_driver, 'quoteIdentifier'],
  363. $data['columns']
  364. );
  365. if ($data['type'] === Table::CONSTRAINT_FOREIGN) {
  366. return $prefix . sprintf(
  367. ' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
  368. implode(', ', $columns),
  369. $this->_driver->quoteIdentifier($data['references'][0]),
  370. $this->_driver->quoteIdentifier($data['references'][1]),
  371. $this->_foreignOnClause($data['update']),
  372. $this->_foreignOnClause($data['delete'])
  373. );
  374. }
  375. return $prefix . ' (' . implode(', ', $columns) . ')';
  376. }
  377. /**
  378. * {@inheritdoc}
  379. *
  380. */
  381. public function createTableSql(Table $table, $columns, $constraints, $indexes) {
  382. $content = array_merge($columns, $constraints);
  383. $content = implode(",\n", array_filter($content));
  384. $tableName = $this->_driver->quoteIdentifier($table->name());
  385. $out = [];
  386. $out[] = sprintf("CREATE TABLE %s (\n%s\n)", $tableName, $content);
  387. foreach ($indexes as $index) {
  388. $out[] = $index;
  389. }
  390. foreach ($table->columns() as $column) {
  391. $columnData = $table->column($column);
  392. if (isset($columnData['comment'])) {
  393. $out[] = sprintf('COMMENT ON COLUMN %s.%s IS %s',
  394. $tableName,
  395. $this->_driver->quoteIdentifier($column),
  396. $this->_driver->schemaValue($columnData['comment'])
  397. );
  398. }
  399. }
  400. return $out;
  401. }
  402. /**
  403. * {@inheritdoc}
  404. *
  405. */
  406. public function truncateTableSql(Table $table) {
  407. $name = $this->_driver->quoteIdentifier($table->name());
  408. return [
  409. sprintf('TRUNCATE %s RESTART IDENTITY', $name)
  410. ];
  411. }
  412. }