svncommit
2008-09-18 d0b973cf6aed4a7cb705f706624d25b31d19ed52
commit | author | age
95ebbc 1 <?php
T 2 // +----------------------------------------------------------------------+
3 // | PHP versions 4 and 5                                                 |
4 // +----------------------------------------------------------------------+
d1403f 5 // | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
95ebbc 6 // | Stig. S. Bakken, Lukas Smith                                         |
T 7 // | All rights reserved.                                                 |
8 // +----------------------------------------------------------------------+
9 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
10 // | API as well as database abstraction for PHP applications.            |
11 // | This LICENSE is in the BSD license style.                            |
12 // |                                                                      |
13 // | Redistribution and use in source and binary forms, with or without   |
14 // | modification, are permitted provided that the following conditions   |
15 // | are met:                                                             |
16 // |                                                                      |
17 // | Redistributions of source code must retain the above copyright       |
18 // | notice, this list of conditions and the following disclaimer.        |
19 // |                                                                      |
20 // | Redistributions in binary form must reproduce the above copyright    |
21 // | notice, this list of conditions and the following disclaimer in the  |
22 // | documentation and/or other materials provided with the distribution. |
23 // |                                                                      |
24 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
25 // | Lukas Smith nor the names of his contributors may be used to endorse |
26 // | or promote products derived from this software without specific prior|
27 // | written permission.                                                  |
28 // |                                                                      |
29 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
30 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
31 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
32 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
33 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
34 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
35 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
36 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
37 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
38 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
39 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
40 // | POSSIBILITY OF SUCH DAMAGE.                                          |
41 // +----------------------------------------------------------------------+
42 // | Authors: Paul Cooper <pgc@ucecom.com>                                |
43 // |          Lorenzo Alberton <l.alberton@quipo.it>                      |
44 // +----------------------------------------------------------------------+
45 //
d1403f 46 // $Id: pgsql.php,v 1.70 2008/03/13 20:38:09 quipo Exp $
95ebbc 47
T 48 require_once 'MDB2/Driver/Reverse/Common.php';
49
50 /**
51  * MDB2 PostGreSQL driver for the schema reverse engineering module
52  *
53  * @package  MDB2
54  * @category Database
55  * @author   Paul Cooper <pgc@ucecom.com>
56  * @author   Lorenzo Alberton <l.alberton@quipo.it>
57  */
58 class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common
59 {
60     // {{{ getTableFieldDefinition()
61
62     /**
63      * Get the structure of a field into an array
64      *
65      * @param string $table_name name of table that should be used in method
66      * @param string $field_name name of field that should be used in method
67      * @return mixed data array on success, a MDB2 error on failure
68      * @access public
69      */
70     function getTableFieldDefinition($table_name, $field_name)
71     {
72         $db =& $this->getDBInstance();
73         if (PEAR::isError($db)) {
74             return $db;
75         }
76
77         $result = $db->loadModule('Datatype', null, true);
78         if (PEAR::isError($result)) {
79             return $result;
80         }
81
82         list($schema, $table) = $this->splitTableSchema($table_name);
83
84         $query = "SELECT a.attname AS name,
85                          t.typname AS type,
86                          CASE a.attlen
87                            WHEN -1 THEN
88                              CASE t.typname
89                                WHEN 'numeric' THEN (a.atttypmod / 65536)
90                                WHEN 'decimal' THEN (a.atttypmod / 65536)
91                                WHEN 'money'   THEN (a.atttypmod / 65536)
92                                ELSE CASE a.atttypmod
93                                  WHEN -1 THEN NULL
94                                  ELSE a.atttypmod - 4
95                                END
96                              END
97                            ELSE a.attlen
98                          END AS length,
99                          CASE t.typname
100                            WHEN 'numeric' THEN (a.atttypmod % 65536) - 4
101                            WHEN 'decimal' THEN (a.atttypmod % 65536) - 4
102                            WHEN 'money'   THEN (a.atttypmod % 65536) - 4
103                            ELSE 0
104                          END AS scale,
105                          a.attnotnull,
106                          a.atttypmod,
107                          a.atthasdef,
108                          (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128)
109                             FROM pg_attrdef d
110                            WHERE d.adrelid = a.attrelid
111                              AND d.adnum = a.attnum
112                              AND a.atthasdef
113                          ) as default
114                     FROM pg_attribute a,
115                          pg_class c,
116                          pg_type t
117                    WHERE c.relname = ".$db->quote($table, 'text')."
118                      AND a.atttypid = t.oid
119                      AND c.oid = a.attrelid
120                      AND NOT a.attisdropped
121                      AND a.attnum > 0
122                      AND a.attname = ".$db->quote($field_name, 'text')."
123                 ORDER BY a.attnum";
124         $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
125         if (PEAR::isError($column)) {
126             return $column;
127         }
128
129         if (empty($column)) {
130             return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
131                 'it was not specified an existing table column', __FUNCTION__);
132         }
133
134         $column = array_change_key_case($column, CASE_LOWER);
135         $mapped_datatype = $db->datatype->mapNativeDatatype($column);
136         if (PEAR::isError($mapped_datatype)) {
137             return $mapped_datatype;
138         }
139         list($types, $length, $unsigned, $fixed) = $mapped_datatype;
140         $notnull = false;
141         if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') {
142             $notnull = true;
143         }
144         $default = null;
145         if ($column['atthasdef'] === 't'
146             && !preg_match("/nextval\('([^']+)'/", $column['default'])
147         ) {
148             $pattern = '/(\'.*\')::[\w ]+$/i';
149             $default = $column['default'];#substr($column['adsrc'], 1, -1);
150             if (is_null($default) && $notnull) {
151                 $default = '';
152             } elseif (!empty($default) && preg_match($pattern, $default)) {
153                 //remove data type cast
154                 $default = preg_replace ($pattern, '\\1', $default);
155             }
156         }
157         $autoincrement = false;
158         if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) {
159             $autoincrement = true;
160         }
161         $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']);
162         if (!is_null($length)) {
163             $definition[0]['length'] = $length;
164         }
165         if (!is_null($unsigned)) {
166             $definition[0]['unsigned'] = $unsigned;
167         }
168         if (!is_null($fixed)) {
169             $definition[0]['fixed'] = $fixed;
170         }
171         if ($default !== false) {
172             $definition[0]['default'] = $default;
173         }
174         if ($autoincrement !== false) {
175             $definition[0]['autoincrement'] = $autoincrement;
176         }
177         foreach ($types as $key => $type) {
178             $definition[$key] = $definition[0];
179             if ($type == 'clob' || $type == 'blob') {
180                 unset($definition[$key]['default']);
181             }
182             $definition[$key]['type'] = $type;
183             $definition[$key]['mdb2type'] = $type;
184         }
185         return $definition;
186     }
187
188     // }}}
189     // {{{ getTableIndexDefinition()
190
191     /**
192      * Get the structure of an index into an array
193      *
194      * @param string $table_name name of table that should be used in method
195      * @param string $index_name name of index that should be used in method
196      * @return mixed data array on success, a MDB2 error on failure
197      * @access public
198      */
199     function getTableIndexDefinition($table_name, $index_name)
200     {
201         $db =& $this->getDBInstance();
202         if (PEAR::isError($db)) {
203             return $db;
204         }
205         
206         list($schema, $table) = $this->splitTableSchema($table_name);
207
208         $query = 'SELECT relname, indkey FROM pg_index, pg_class';
209         $query.= ' WHERE pg_class.oid = pg_index.indexrelid';
210         $query.= " AND indisunique != 't' AND indisprimary != 't'";
211         $query.= ' AND pg_class.relname = %s';
212         $index_name_mdb2 = $db->getIndexName($index_name);
213         $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
214         if (PEAR::isError($row) || empty($row)) {
215             // fallback to the given $index_name, without transformation
216             $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC);
217         }
218         if (PEAR::isError($row)) {
219             return $row;
220         }
221
222         if (empty($row)) {
223             return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
224                 'it was not specified an existing table index', __FUNCTION__);
225         }
226
227         $row = array_change_key_case($row, CASE_LOWER);
228
229         $db->loadModule('Manager', null, true);
230         $columns = $db->manager->listTableFields($table_name);
231
232         $definition = array();
233
234         $index_column_numbers = explode(' ', $row['indkey']);
235
236         $colpos = 1;
237         foreach ($index_column_numbers as $number) {
238             $definition['fields'][$columns[($number - 1)]] = array(
239                 'position' => $colpos++,
240                 'sorting' => 'ascending',
241             );
242         }
243         return $definition;
244     }
245
246     // }}}
247     // {{{ getTableConstraintDefinition()
248
249     /**
250      * Get the structure of a constraint into an array
251      *
252      * @param string $table_name      name of table that should be used in method
253      * @param string $constraint_name name of constraint that should be used in method
254      * @return mixed data array on success, a MDB2 error on failure
255      * @access public
256      */
257     function getTableConstraintDefinition($table_name, $constraint_name)
258     {
259         $db =& $this->getDBInstance();
260         if (PEAR::isError($db)) {
261             return $db;
262         }
263         
264         list($schema, $table) = $this->splitTableSchema($table_name);
265
266         $query = "SELECT c.oid,
267                          c.conname AS constraint_name,
268                          CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\",
269                          CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\",
270                          CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\",
271                          CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\",
272                          CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable,
273                          CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred,
274                          --array_to_string(c.conkey, ' ') AS constraint_key,
275                          t.relname AS table_name,
276                          t2.relname AS references_table,
277                          CASE confupdtype
278                            WHEN 'a' THEN 'NO ACTION'
279                            WHEN 'r' THEN 'RESTRICT'
280                            WHEN 'c' THEN 'CASCADE'
281                            WHEN 'n' THEN 'SET NULL'
282                            WHEN 'd' THEN 'SET DEFAULT'
283                          END AS onupdate,
284                          CASE confdeltype
285                            WHEN 'a' THEN 'NO ACTION'
286                            WHEN 'r' THEN 'RESTRICT'
287                            WHEN 'c' THEN 'CASCADE'
288                            WHEN 'n' THEN 'SET NULL'
289                            WHEN 'd' THEN 'SET DEFAULT'
290                          END AS ondelete,
291                          CASE confmatchtype
292                            WHEN 'u' THEN 'UNSPECIFIED'
293                            WHEN 'f' THEN 'FULL'
294                            WHEN 'p' THEN 'PARTIAL'
295                          END AS match,
296                          --array_to_string(c.confkey, ' ') AS fk_constraint_key,
297                          consrc
298                     FROM pg_constraint c
299                LEFT JOIN pg_class t  ON c.conrelid  = t.oid
300                LEFT JOIN pg_class t2 ON c.confrelid = t2.oid
301                    WHERE c.conname = %s
302                     AND t.relname = " . $db->quote($table, 'text');
303         $constraint_name_mdb2 = $db->getIndexName($constraint_name);
304         $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
305         if (PEAR::isError($row) || empty($row)) {
306             // fallback to the given $index_name, without transformation
307             $constraint_name_mdb2 = $constraint_name;
308             $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
309         }
310         if (PEAR::isError($row)) {
311             return $row;
312         }
313
314         if (empty($row)) {
315             return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
316                 $constraint_name . ' is not an existing table constraint', __FUNCTION__);
317         }
318
319         $row = array_change_key_case($row, CASE_LOWER);
320
321         $definition = array(
322             'primary' => (boolean)$row['primary'],
323             'unique'  => (boolean)$row['unique'],
324             'foreign' => (boolean)$row['foreign'],
325             'check'   => (boolean)$row['check'],
326             'fields'  => array(),
327             'references' => array(
328                 'table'  => $row['references_table'],
329                 'fields' => array(),
330             ),
331             'deferrable' => (boolean)$row['deferrable'],
332             'initiallydeferred' => (boolean)$row['initiallydeferred'],
333             'onupdate' => $row['onupdate'],
334             'ondelete' => $row['ondelete'],
335             'match'    => $row['match'],
336         );
337
338         $query = 'SELECT a.attname
339                     FROM pg_constraint c
340                LEFT JOIN pg_class t  ON c.conrelid  = t.oid
341                LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey)
342                    WHERE c.conname = %s
343                      AND t.relname = ' . $db->quote($table, 'text');
344         $constraint_name_mdb2 = $db->getIndexName($constraint_name);
345         $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null);
346         if (PEAR::isError($fields)) {
347             return $fields;
348         }
349         $colpos = 1;
350         foreach ($fields as $field) {
351             $definition['fields'][$field] = array(
352                 'position' => $colpos++,
353                 'sorting' => 'ascending',
354             );
355         }
356         
357         if ($definition['foreign']) {
358             $query = 'SELECT a.attname
359                         FROM pg_constraint c
360                    LEFT JOIN pg_class t  ON c.confrelid  = t.oid
d1403f 361                    LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey)
95ebbc 362                        WHERE c.conname = %s
T 363                          AND t.relname = ' . $db->quote($definition['references']['table'], 'text');
364             $constraint_name_mdb2 = $db->getIndexName($constraint_name);
365             $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null);
366             if (PEAR::isError($foreign_fields)) {
367                 return $foreign_fields;
368             }
369             $colpos = 1;
370             foreach ($foreign_fields as $foreign_field) {
371                 $definition['references']['fields'][$foreign_field] = array(
372                     'position' => $colpos++,
373                 );
374             }
375         }
376         
377         if ($definition['check']) {
378             $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')");
379             // ...
380         }
381         return $definition;
382     }
383
384     // }}}
385     // {{{ getTriggerDefinition()
386
387     /**
388      * Get the structure of a trigger into an array
389      *
390      * EXPERIMENTAL
391      *
392      * WARNING: this function is experimental and may change the returned value
393      * at any time until labelled as non-experimental
394      *
395      * @param string $trigger name of trigger that should be used in method
396      * @return mixed data array on success, a MDB2 error on failure
397      * @access public
398      *
399      * @TODO: add support for plsql functions and functions with args
400      */
401     function getTriggerDefinition($trigger)
402     {
403         $db =& $this->getDBInstance();
404         if (PEAR::isError($db)) {
405             return $db;
406         }
407
408         $query = "SELECT trg.tgname AS trigger_name,
409                          tbl.relname AS table_name,
410                          CASE
411                             WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();'
412                             ELSE ''
413                          END AS trigger_body,
414                          CASE trg.tgtype & cast(2 as int2)
415                             WHEN 0 THEN 'AFTER'
416                             ELSE 'BEFORE'
417                          END AS trigger_type,
418                          CASE trg.tgtype & cast(28 as int2)
419                             WHEN 16 THEN 'UPDATE'
420                             WHEN 8 THEN 'DELETE'
421                             WHEN 4 THEN 'INSERT'
422                             WHEN 20 THEN 'INSERT, UPDATE'
423                             WHEN 28 THEN 'INSERT, UPDATE, DELETE'
424                             WHEN 24 THEN 'UPDATE, DELETE'
425                             WHEN 12 THEN 'INSERT, DELETE'
426                          END AS trigger_event,
d1403f 427                          CASE trg.tgenabled
A 428                             WHEN 'O' THEN 't'
429                             ELSE trg.tgenabled
430                          END AS trigger_enabled,
95ebbc 431                          obj_description(trg.oid, 'pg_trigger') AS trigger_comment
T 432                     FROM pg_trigger trg,
433                          pg_class tbl,
434                          pg_proc p
435                    WHERE trg.tgrelid = tbl.oid
436                      AND trg.tgfoid = p.oid
437                      AND trg.tgname = ". $db->quote($trigger, 'text');
438         $types = array(
439             'trigger_name'    => 'text',
440             'table_name'      => 'text',
441             'trigger_body'    => 'text',
442             'trigger_type'    => 'text',
443             'trigger_event'   => 'text',
444             'trigger_comment' => 'text',
445             'trigger_enabled' => 'boolean',
446         );
447         return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
448     }
449     
450     // }}}
451     // {{{ tableInfo()
452
453     /**
454      * Returns information about a table or a result set
455      *
456      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
457      * is a table name.
458      *
459      * @param object|string  $result  MDB2_result object from a query or a
460      *                                 string containing the name of a table.
461      *                                 While this also accepts a query result
462      *                                 resource identifier, this behavior is
463      *                                 deprecated.
464      * @param int            $mode    a valid tableInfo mode
465      *
466      * @return array  an associative array with the information requested.
467      *                 A MDB2_Error object on failure.
468      *
469      * @see MDB2_Driver_Common::tableInfo()
470      */
471     function tableInfo($result, $mode = null)
472     {
473         if (is_string($result)) {
474            return parent::tableInfo($result, $mode);
475         }
476
477         $db =& $this->getDBInstance();
478         if (PEAR::isError($db)) {
479             return $db;
480         }
481
482         $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
483         if (!is_resource($resource)) {
484             return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
485                 'Could not generate result resource', __FUNCTION__);
486         }
487
488         if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
489             if ($db->options['field_case'] == CASE_LOWER) {
490                 $case_func = 'strtolower';
491             } else {
492                 $case_func = 'strtoupper';
493             }
494         } else {
495             $case_func = 'strval';
496         }
497
498         $count = @pg_num_fields($resource);
499         $res   = array();
500
501         if ($mode) {
502             $res['num_fields'] = $count;
503         }
504
505         $db->loadModule('Datatype', null, true);
506         for ($i = 0; $i < $count; $i++) {
507             $res[$i] = array(
508                 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '',
509                 'name'  => $case_func(@pg_field_name($resource, $i)),
510                 'type'  => @pg_field_type($resource, $i),
511                 'length' => @pg_field_size($resource, $i),
512                 'flags' => '',
513             );
514             $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
515             if (PEAR::isError($mdb2type_info)) {
516                return $mdb2type_info;
517             }
518             $res[$i]['mdb2type'] = $mdb2type_info[0][0];
519             if ($mode & MDB2_TABLEINFO_ORDER) {
520                 $res['order'][$res[$i]['name']] = $i;
521             }
522             if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
523                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
524             }
525         }
526
527         return $res;
528     }
529 }
530 ?>