1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: 12: 13: 14: 15: 16: 17:
18: class CPgsqlSchema extends CDbSchema
19: {
20: const DEFAULT_SCHEMA='public';
21:
22: 23: 24: 25:
26: public $columnTypes=array(
27: 'pk' => 'serial NOT NULL PRIMARY KEY',
28: 'bigpk' => 'bigserial NOT NULL PRIMARY KEY',
29: 'string' => 'character varying (255)',
30: 'text' => 'text',
31: 'integer' => 'integer',
32: 'bigint' => 'bigint',
33: 'float' => 'double precision',
34: 'decimal' => 'numeric',
35: 'datetime' => 'timestamp',
36: 'timestamp' => 'timestamp',
37: 'time' => 'time',
38: 'date' => 'date',
39: 'binary' => 'bytea',
40: 'boolean' => 'boolean',
41: 'money' => 'decimal(19,4)',
42: );
43:
44: private $_sequences=array();
45:
46: 47: 48: 49: 50: 51: 52:
53: public function quoteSimpleTableName($name)
54: {
55: return '"'.$name.'"';
56: }
57:
58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
68: public function resetSequence($table,$value=null)
69: {
70: if($table->sequenceName===null)
71: return;
72: $sequence='"'.$table->sequenceName.'"';
73: if(strpos($sequence,'.')!==false)
74: $sequence=str_replace('.','"."',$sequence);
75: if($value!==null)
76: $value=(int)$value;
77: else
78: $value="(SELECT COALESCE(MAX(\"{$table->primaryKey}\"),0) FROM {$table->rawName})+1";
79: $this->getDbConnection()
80: ->createCommand("SELECT SETVAL('$sequence',$value,false)")
81: ->execute();
82: }
83:
84: 85: 86: 87: 88: 89:
90: public function checkIntegrity($check=true,$schema='')
91: {
92: $enable=$check ? 'ENABLE' : 'DISABLE';
93: $tableNames=$this->getTableNames($schema);
94: $db=$this->getDbConnection();
95: foreach($tableNames as $tableName)
96: {
97: $tableName='"'.$tableName.'"';
98: if(strpos($tableName,'.')!==false)
99: $tableName=str_replace('.','"."',$tableName);
100: $db->createCommand("ALTER TABLE $tableName $enable TRIGGER ALL")->execute();
101: }
102: }
103:
104: 105: 106: 107: 108:
109: protected function loadTable($name)
110: {
111: $table=new CPgsqlTableSchema;
112: $this->resolveTableNames($table,$name);
113: if(!$this->findColumns($table))
114: return null;
115: $this->findConstraints($table);
116:
117: if(is_string($table->primaryKey) && isset($this->_sequences[$table->rawName.'.'.$table->primaryKey]))
118: $table->sequenceName=$this->_sequences[$table->rawName.'.'.$table->primaryKey];
119: elseif(is_array($table->primaryKey))
120: {
121: foreach($table->primaryKey as $pk)
122: {
123: if(isset($this->_sequences[$table->rawName.'.'.$pk]))
124: {
125: $table->sequenceName=$this->_sequences[$table->rawName.'.'.$pk];
126: break;
127: }
128: }
129: }
130:
131: return $table;
132: }
133:
134: 135: 136: 137: 138:
139: protected function resolveTableNames($table,$name)
140: {
141: $parts=explode('.',str_replace('"','',$name));
142: if(isset($parts[1]))
143: {
144: $schemaName=$parts[0];
145: $tableName=$parts[1];
146: }
147: else
148: {
149: $schemaName=self::DEFAULT_SCHEMA;
150: $tableName=$parts[0];
151: }
152:
153: $table->name=$tableName;
154: $table->schemaName=$schemaName;
155: if($schemaName===self::DEFAULT_SCHEMA)
156: $table->rawName=$this->quoteTableName($tableName);
157: else
158: $table->rawName=$this->quoteTableName($schemaName).'.'.$this->quoteTableName($tableName);
159: }
160:
161: 162: 163: 164: 165:
166: protected function findColumns($table)
167: {
168: $sql=<<<EOD
169: SELECT a.attname, LOWER(format_type(a.atttypid, a.atttypmod)) AS type, d.adsrc, a.attnotnull, a.atthasdef,
170: pg_catalog.col_description(a.attrelid, a.attnum) AS comment
171: FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
172: WHERE a.attnum > 0 AND NOT a.attisdropped
173: AND a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=:table
174: AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :schema))
175: ORDER BY a.attnum
176: EOD;
177: $command=$this->getDbConnection()->createCommand($sql);
178: $command->bindValue(':table',$table->name);
179: $command->bindValue(':schema',$table->schemaName);
180:
181: if(($columns=$command->queryAll())===array())
182: return false;
183:
184: foreach($columns as $column)
185: {
186: $c=$this->createColumn($column);
187: $table->columns[$c->name]=$c;
188:
189: if(stripos($column['adsrc'],'nextval')===0 && preg_match('/nextval\([^\']*\'([^\']+)\'[^\)]*\)/i',$column['adsrc'],$matches))
190: {
191: if(strpos($matches[1],'.')!==false || $table->schemaName===self::DEFAULT_SCHEMA)
192: $this->_sequences[$table->rawName.'.'.$c->name]=$matches[1];
193: else
194: $this->_sequences[$table->rawName.'.'.$c->name]=$table->schemaName.'.'.$matches[1];
195: $c->autoIncrement=true;
196: }
197: }
198: return true;
199: }
200:
201: 202: 203: 204: 205:
206: protected function createColumn($column)
207: {
208: $c=new CPgsqlColumnSchema;
209: $c->name=$column['attname'];
210: $c->rawName=$this->quoteColumnName($c->name);
211: $c->allowNull=!$column['attnotnull'];
212: $c->isPrimaryKey=false;
213: $c->isForeignKey=false;
214: $c->comment=$column['comment']===null ? '' : $column['comment'];
215:
216: $c->init($column['type'],$column['atthasdef'] ? $column['adsrc'] : null);
217:
218: return $c;
219: }
220:
221: 222: 223: 224:
225: protected function findConstraints($table)
226: {
227: $sql=<<<EOD
228: SELECT conname, consrc, contype, indkey FROM (
229: SELECT
230: conname,
231: CASE WHEN contype='f' THEN
232: pg_catalog.pg_get_constraintdef(oid)
233: ELSE
234: 'CHECK (' || consrc || ')'
235: END AS consrc,
236: contype,
237: conrelid AS relid,
238: NULL AS indkey
239: FROM
240: pg_catalog.pg_constraint
241: WHERE
242: contype IN ('f', 'c')
243: UNION ALL
244: SELECT
245: pc.relname,
246: NULL,
247: CASE WHEN indisprimary THEN
248: 'p'
249: ELSE
250: 'u'
251: END,
252: pi.indrelid,
253: indkey
254: FROM
255: pg_catalog.pg_class pc,
256: pg_catalog.pg_index pi
257: WHERE
258: pc.oid=pi.indexrelid
259: AND EXISTS (
260: SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
261: ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
262: WHERE d.classid = pc.tableoid AND d.objid = pc.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
263: )
264: ) AS sub
265: WHERE relid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=:table
266: AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
267: WHERE nspname=:schema))
268: EOD;
269: $command=$this->getDbConnection()->createCommand($sql);
270: $command->bindValue(':table',$table->name);
271: $command->bindValue(':schema',$table->schemaName);
272: foreach($command->queryAll() as $row)
273: {
274: if($row['contype']==='p')
275: $this->findPrimaryKey($table,$row['indkey']);
276: elseif($row['contype']==='f')
277: $this->findForeignKey($table,$row['consrc']);
278: }
279: }
280:
281: 282: 283: 284: 285:
286: protected function findPrimaryKey($table,$indices)
287: {
288: $indices=implode(', ',preg_split('/\s+/',$indices));
289: $sql=<<<EOD
290: SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
291: attrelid=(
292: SELECT oid FROM pg_catalog.pg_class WHERE relname=:table AND relnamespace=(
293: SELECT oid FROM pg_catalog.pg_namespace WHERE nspname=:schema
294: )
295: )
296: AND attnum IN ({$indices})
297: EOD;
298: $command=$this->getDbConnection()->createCommand($sql);
299: $command->bindValue(':table',$table->name);
300: $command->bindValue(':schema',$table->schemaName);
301: foreach($command->queryAll() as $row)
302: {
303: $name=$row['attname'];
304: if(isset($table->columns[$name]))
305: {
306: $table->columns[$name]->isPrimaryKey=true;
307: if($table->primaryKey===null)
308: $table->primaryKey=$name;
309: elseif(is_string($table->primaryKey))
310: $table->primaryKey=array($table->primaryKey,$name);
311: else
312: $table->primaryKey[]=$name;
313: }
314: }
315: }
316:
317: 318: 319: 320: 321:
322: protected function findForeignKey($table,$src)
323: {
324: $matches=array();
325: $brackets='\(([^\)]+)\)';
326: $pattern="/FOREIGN\s+KEY\s+{$brackets}\s+REFERENCES\s+([^\(]+){$brackets}/i";
327: if(preg_match($pattern,str_replace('"','',$src),$matches))
328: {
329: $keys=preg_split('/,\s+/', $matches[1]);
330: $tableName=$matches[2];
331: $fkeys=preg_split('/,\s+/', $matches[3]);
332: foreach($keys as $i=>$key)
333: {
334: $table->foreignKeys[$key]=array($tableName,$fkeys[$i]);
335: if(isset($table->columns[$key]))
336: $table->columns[$key]->isForeignKey=true;
337: }
338: }
339: }
340:
341: 342: 343: 344: 345: 346:
347: protected function findTableNames($schema='')
348: {
349: if($schema==='')
350: $schema=self::DEFAULT_SCHEMA;
351: $sql=<<<EOD
352: SELECT table_name, table_schema FROM information_schema.tables
353: WHERE table_schema=:schema AND table_type='BASE TABLE'
354: EOD;
355: $command=$this->getDbConnection()->createCommand($sql);
356: $command->bindParam(':schema',$schema);
357: $rows=$command->queryAll();
358: $names=array();
359: foreach($rows as $row)
360: {
361: if($schema===self::DEFAULT_SCHEMA)
362: $names[]=$row['table_name'];
363: else
364: $names[]=$row['table_schema'].'.'.$row['table_name'];
365: }
366: return $names;
367: }
368:
369: 370: 371: 372: 373: 374: 375:
376: public function renameTable($table, $newName)
377: {
378: return 'ALTER TABLE ' . $this->quoteTableName($table) . ' RENAME TO ' . $this->quoteTableName($newName);
379: }
380:
381: 382: 383: 384: 385: 386: 387: 388: 389: 390:
391: public function addColumn($table, $column, $type)
392: {
393: $type=$this->getColumnType($type);
394: $sql='ALTER TABLE ' . $this->quoteTableName($table)
395: . ' ADD COLUMN ' . $this->quoteColumnName($column) . ' '
396: . $type;
397: return $sql;
398: }
399:
400: 401: 402: 403: 404: 405: 406: 407: 408: 409:
410: public function alterColumn($table, $column, $type)
411: {
412: $type=$this->getColumnType($type);
413: $sql='ALTER TABLE ' . $this->quoteTableName($table) . ' ALTER COLUMN '
414: . $this->quoteColumnName($column) . ' TYPE ' . $this->getColumnType($type);
415: return $sql;
416: }
417:
418: 419: 420: 421: 422: 423: 424: 425: 426: 427:
428: public function createIndex($name, $table, $columns, $unique=false)
429: {
430: $cols=array();
431: if (is_string($columns))
432: $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
433: foreach($columns as $col)
434: {
435: if(strpos($col,'(')!==false)
436: $cols[]=$col;
437: else
438: $cols[]=$this->quoteColumnName($col);
439: }
440: if ($unique)
441: {
442: return 'ALTER TABLE ONLY '
443: . $this->quoteTableName($table).' ADD CONSTRAINT '
444: . $this->quoteTableName($name).' UNIQUE ('.implode(', ',$cols).')';
445: }
446: else
447: {
448: return 'CREATE INDEX '
449: . $this->quoteTableName($name).' ON '
450: . $this->quoteTableName($table).' ('.implode(', ',$cols).')';
451: }
452: }
453:
454: 455: 456: 457: 458: 459: 460:
461: public function dropIndex($name, $table)
462: {
463: return 'DROP INDEX '.$this->quoteTableName($name);
464: }
465:
466: 467: 468: 469: 470:
471: protected function createCommandBuilder()
472: {
473: return new CPgsqlCommandBuilder($this);
474: }
475: }
476: