PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/db/schema/pgsql/CPgsqlSchema.php

http://yii-doctrine2.googlecode.com/
PHP | 392 lines | 249 code | 26 blank | 117 comment | 31 complexity | f8963c2de186f8cf069a5247ff35488a MD5 | raw file
Possible License(s): BSD-2-Clause, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * CPgsqlSchema class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CPgsqlSchema is the class for retrieving metadata information from a PostgreSQL database.
  12. *
  13. * @author Qiang Xue <qiang.xue@gmail.com>
  14. * @version $Id: CPgsqlSchema.php 3099 2011-03-19 01:26:47Z qiang.xue $
  15. * @package system.db.schema.pgsql
  16. * @since 1.0
  17. */
  18. class CPgsqlSchema extends CDbSchema
  19. {
  20. const DEFAULT_SCHEMA = 'public';
  21. /**
  22. * @var array the abstract column types mapped to physical column types.
  23. * @since 1.1.6
  24. */
  25. public $columnTypes = array ('pk' => 'serial NOT NULL PRIMARY KEY', 'string' => 'character varying (255)', 'text' => 'text', 'integer' => 'integer', 'float' => 'double precision', 'decimal' => 'numeric', 'datetime' => 'time', 'timestamp' => 'timestamp', 'time' => 'time', 'date' => 'date', 'binary' => 'bytea', 'boolean' => 'boolean' );
  26. private $_sequences = array ();
  27. /**
  28. * Quotes a table name for use in a query.
  29. * A simple table name does not schema prefix.
  30. * @param string $name table name
  31. * @return string the properly quoted table name
  32. * @since 1.1.6
  33. */
  34. public function quoteSimpleTableName($name)
  35. {
  36. return '"' . $name . '"';
  37. }
  38. /**
  39. * Resets the sequence value of a table's primary key.
  40. * The sequence will be reset such that the primary key of the next new row inserted
  41. * will have the specified value or 1.
  42. * @param CDbTableSchema $table the table schema whose primary key sequence will be reset
  43. * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
  44. * the next new row's primary key will have a value 1.
  45. * @since 1.1
  46. */
  47. public function resetSequence($table, $value = null)
  48. {
  49. if ($table->sequenceName !== null) {
  50. $seq = '"' . $table->sequenceName . '"';
  51. if (strpos ( $seq, '.' ) !== false)
  52. $seq = str_replace ( '.', '"."', $seq );
  53. if ($value === null)
  54. $value = "(SELECT COALESCE(MAX(\"{$table->primaryKey}\"),0) FROM {$table->rawName}) + 1";
  55. else
  56. $value = ( int ) $value;
  57. $this->getDbConnection ()->createCommand ( "SELECT SETVAL('$seq', $value, false)" )->execute ();
  58. }
  59. }
  60. /**
  61. * Enables or disables integrity check.
  62. * @param boolean $check whether to turn on or off the integrity check.
  63. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
  64. * @since 1.1
  65. */
  66. public function checkIntegrity($check = true, $schema = '')
  67. {
  68. $enable = $check ? 'ENABLE' : 'DISABLE';
  69. $tableNames = $this->getTableNames ( $schema );
  70. $db = $this->getDbConnection ();
  71. foreach ( $tableNames as $tableName ) {
  72. $tableName = '"' . $tableName . '"';
  73. if (strpos ( $tableName, '.' ) !== false)
  74. $tableName = str_replace ( '.', '"."', $tableName );
  75. $db->createCommand ( "ALTER TABLE $tableName $enable TRIGGER ALL" )->execute ();
  76. }
  77. }
  78. /**
  79. * Loads the metadata for the specified table.
  80. * @param string $name table name
  81. * @return CDbTableSchema driver dependent table metadata.
  82. */
  83. protected function loadTable($name)
  84. {
  85. $table = new CPgsqlTableSchema ();
  86. $this->resolveTableNames ( $table, $name );
  87. if (! $this->findColumns ( $table ))
  88. return null;
  89. $this->findConstraints ( $table );
  90. if (is_string ( $table->primaryKey ) && isset ( $this->_sequences [$table->rawName . '.' . $table->primaryKey] ))
  91. $table->sequenceName = $this->_sequences [$table->rawName . '.' . $table->primaryKey];
  92. else if (is_array ( $table->primaryKey )) {
  93. foreach ( $table->primaryKey as $pk ) {
  94. if (isset ( $this->_sequences [$table->rawName . '.' . $pk] )) {
  95. $table->sequenceName = $this->_sequences [$table->rawName . '.' . $pk];
  96. break;
  97. }
  98. }
  99. }
  100. return $table;
  101. }
  102. /**
  103. * Generates various kinds of table names.
  104. * @param CPgsqlTableSchema $table the table instance
  105. * @param string $name the unquoted table name
  106. */
  107. protected function resolveTableNames($table, $name)
  108. {
  109. $parts = explode ( '.', str_replace ( '"', '', $name ) );
  110. if (isset ( $parts [1] )) {
  111. $schemaName = $parts [0];
  112. $tableName = $parts [1];
  113. } else {
  114. $schemaName = self::DEFAULT_SCHEMA;
  115. $tableName = $parts [0];
  116. }
  117. $table->name = $tableName;
  118. $table->schemaName = $schemaName;
  119. if ($schemaName === self::DEFAULT_SCHEMA)
  120. $table->rawName = $this->quoteTableName ( $tableName );
  121. else
  122. $table->rawName = $this->quoteTableName ( $schemaName ) . '.' . $this->quoteTableName ( $tableName );
  123. }
  124. /**
  125. * Collects the table column metadata.
  126. * @param CPgsqlTableSchema $table the table metadata
  127. * @return boolean whether the table exists in the database
  128. */
  129. protected function findColumns($table)
  130. {
  131. $sql = <<<EOD
  132. SELECT a.attname, LOWER(format_type(a.atttypid, a.atttypmod)) AS type, d.adsrc, a.attnotnull, a.atthasdef
  133. FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
  134. WHERE a.attnum > 0 AND NOT a.attisdropped
  135. AND a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=:table
  136. AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :schema))
  137. ORDER BY a.attnum
  138. EOD;
  139. $command = $this->getDbConnection ()->createCommand ( $sql );
  140. $command->bindValue ( ':table', $table->name );
  141. $command->bindValue ( ':schema', $table->schemaName );
  142. if (($columns = $command->queryAll ()) === array ())
  143. return false;
  144. foreach ( $columns as $column ) {
  145. $c = $this->createColumn ( $column );
  146. $table->columns [$c->name] = $c;
  147. if (stripos ( $column ['adsrc'], 'nextval' ) === 0 && preg_match ( '/nextval\([^\']*\'([^\']+)\'[^\)]*\)/i', $column ['adsrc'], $matches )) {
  148. if (strpos ( $matches [1], '.' ) !== false || $table->schemaName === self::DEFAULT_SCHEMA)
  149. $this->_sequences [$table->rawName . '.' . $c->name] = $matches [1];
  150. else
  151. $this->_sequences [$table->rawName . '.' . $c->name] = $table->schemaName . '.' . $matches [1];
  152. $c->autoIncrement = true;
  153. }
  154. }
  155. return true;
  156. }
  157. /**
  158. * Creates a table column.
  159. * @param array $column column metadata
  160. * @return CDbColumnSchema normalized column metadata
  161. */
  162. protected function createColumn($column)
  163. {
  164. $c = new CPgsqlColumnSchema ();
  165. $c->name = $column ['attname'];
  166. $c->rawName = $this->quoteColumnName ( $c->name );
  167. $c->allowNull = ! $column ['attnotnull'];
  168. $c->isPrimaryKey = false;
  169. $c->isForeignKey = false;
  170. $c->init ( $column ['type'], $column ['atthasdef'] ? $column ['adsrc'] : null );
  171. return $c;
  172. }
  173. /**
  174. * Collects the primary and foreign key column details for the given table.
  175. * @param CPgsqlTableSchema $table the table metadata
  176. */
  177. protected function findConstraints($table)
  178. {
  179. $sql = <<<EOD
  180. SELECT conname, consrc, contype, indkey FROM (
  181. SELECT
  182. conname,
  183. CASE WHEN contype='f' THEN
  184. pg_catalog.pg_get_constraintdef(oid)
  185. ELSE
  186. 'CHECK (' || consrc || ')'
  187. END AS consrc,
  188. contype,
  189. conrelid AS relid,
  190. NULL AS indkey
  191. FROM
  192. pg_catalog.pg_constraint
  193. WHERE
  194. contype IN ('f', 'c')
  195. UNION ALL
  196. SELECT
  197. pc.relname,
  198. NULL,
  199. CASE WHEN indisprimary THEN
  200. 'p'
  201. ELSE
  202. 'u'
  203. END,
  204. pi.indrelid,
  205. indkey
  206. FROM
  207. pg_catalog.pg_class pc,
  208. pg_catalog.pg_index pi
  209. WHERE
  210. pc.oid=pi.indexrelid
  211. AND EXISTS (
  212. SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
  213. ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
  214. WHERE d.classid = pc.tableoid AND d.objid = pc.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
  215. )
  216. ) AS sub
  217. WHERE relid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=:table
  218. AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
  219. WHERE nspname=:schema))
  220. EOD;
  221. $command = $this->getDbConnection ()->createCommand ( $sql );
  222. $command->bindValue ( ':table', $table->name );
  223. $command->bindValue ( ':schema', $table->schemaName );
  224. foreach ( $command->queryAll () as $row ) {
  225. if ($row ['contype'] === 'p') // primary key
  226. $this->findPrimaryKey ( $table, $row ['indkey'] );
  227. else if ($row ['contype'] === 'f') // foreign key
  228. $this->findForeignKey ( $table, $row ['consrc'] );
  229. }
  230. }
  231. /**
  232. * Collects primary key information.
  233. * @param CPgsqlTableSchema $table the table metadata
  234. * @param string $indices pgsql primary key index list
  235. */
  236. protected function findPrimaryKey($table, $indices)
  237. {
  238. $indices = implode ( ', ', preg_split ( '/\s+/', $indices ) );
  239. $sql = <<<EOD
  240. SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
  241. attrelid=(
  242. SELECT oid FROM pg_catalog.pg_class WHERE relname=:table AND relnamespace=(
  243. SELECT oid FROM pg_catalog.pg_namespace WHERE nspname=:schema
  244. )
  245. )
  246. AND attnum IN ({$indices})
  247. EOD;
  248. $command = $this->getDbConnection ()->createCommand ( $sql );
  249. $command->bindValue ( ':table', $table->name );
  250. $command->bindValue ( ':schema', $table->schemaName );
  251. foreach ( $command->queryAll () as $row ) {
  252. $name = $row ['attname'];
  253. if (isset ( $table->columns [$name] )) {
  254. $table->columns [$name]->isPrimaryKey = true;
  255. if ($table->primaryKey === null)
  256. $table->primaryKey = $name;
  257. else if (is_string ( $table->primaryKey ))
  258. $table->primaryKey = array ($table->primaryKey, $name );
  259. else
  260. $table->primaryKey [] = $name;
  261. }
  262. }
  263. }
  264. /**
  265. * Collects foreign key information.
  266. * @param CPgsqlTableSchema $table the table metadata
  267. * @param string $src pgsql foreign key definition
  268. */
  269. protected function findForeignKey($table, $src)
  270. {
  271. $matches = array ();
  272. $brackets = '\(([^\)]+)\)';
  273. $pattern = "/FOREIGN\s+KEY\s+{$brackets}\s+REFERENCES\s+([^\(]+){$brackets}/i";
  274. if (preg_match ( $pattern, str_replace ( '"', '', $src ), $matches )) {
  275. $keys = preg_split ( '/,\s+/', $matches [1] );
  276. $tableName = $matches [2];
  277. $fkeys = preg_split ( '/,\s+/', $matches [3] );
  278. foreach ( $keys as $i => $key ) {
  279. $table->foreignKeys [$key] = array ($tableName, $fkeys [$i] );
  280. if (isset ( $table->columns [$key] ))
  281. $table->columns [$key]->isForeignKey = true;
  282. }
  283. }
  284. }
  285. /**
  286. * Returns all table names in the database.
  287. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
  288. * If not empty, the returned table names will be prefixed with the schema name.
  289. * @return array all table names in the database.
  290. * @since 1.0.2
  291. */
  292. protected function findTableNames($schema = '')
  293. {
  294. if ($schema === '')
  295. $schema = self::DEFAULT_SCHEMA;
  296. $sql = <<<EOD
  297. SELECT table_name, table_schema FROM information_schema.tables
  298. WHERE table_schema=:schema AND table_type='BASE TABLE'
  299. EOD;
  300. $command = $this->getDbConnection ()->createCommand ( $sql );
  301. $command->bindParam ( ':schema', $schema );
  302. $rows = $command->queryAll ();
  303. $names = array ();
  304. foreach ( $rows as $row ) {
  305. if ($schema === self::DEFAULT_SCHEMA)
  306. $names [] = $row ['table_name'];
  307. else
  308. $names [] = $row ['table_schema'] . '.' . $row ['table_name'];
  309. }
  310. return $names;
  311. }
  312. /**
  313. * Builds a SQL statement for renaming a DB table.
  314. * @param string $table the table to be renamed. The name will be properly quoted by the method.
  315. * @param string $newName the new table name. The name will be properly quoted by the method.
  316. * @return string the SQL statement for renaming a DB table.
  317. * @since 1.1.6
  318. */
  319. public function renameTable($table, $newName)
  320. {
  321. return 'ALTER TABLE ' . $this->quoteTableName ( $table ) . ' RENAME TO ' . $this->quoteTableName ( $newName );
  322. }
  323. /**
  324. * Builds a SQL statement for adding a new DB column.
  325. * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
  326. * @param string $column the name of the new column. The name will be properly quoted by the method.
  327. * @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
  328. * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
  329. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
  330. * @return string the SQL statement for adding a new column.
  331. * @since 1.1.6
  332. */
  333. public function addColumn($table, $column, $type)
  334. {
  335. $type = $this->getColumnType ( $type );
  336. $sql = 'ALTER TABLE ' . $this->quoteTableName ( $table ) . ' ADD COLUMN ' . $this->quoteColumnName ( $column ) . ' ' . $this->getColumnType ( $type );
  337. return $sql;
  338. }
  339. /**
  340. * Builds a SQL statement for changing the definition of a column.
  341. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
  342. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
  343. * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
  344. * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
  345. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
  346. * @return string the SQL statement for changing the definition of a column.
  347. * @since 1.1.6
  348. */
  349. public function alterColumn($table, $column, $type)
  350. {
  351. $type = $this->getColumnType ( $type );
  352. $sql = 'ALTER TABLE ' . $this->quoteTableName ( $table ) . ' ALTER COLUMN ' . $this->quoteColumnName ( $column ) . ' TYPE ' . $this->getColumnType ( $type );
  353. return $sql;
  354. }
  355. /**
  356. * Builds a SQL statement for dropping an index.
  357. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
  358. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
  359. * @return string the SQL statement for dropping an index.
  360. * @since 1.1.6
  361. */
  362. public function dropIndex($name, $table)
  363. {
  364. return 'DROP INDEX ' . $this->quoteTableName ( $name );
  365. }
  366. }