PageRenderTime 26ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

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