]> git.donarmstrong.com Git - roundcube.git/blob - program/lib/DB/pgsql.php
Imported Upstream version 0.1~beta2.2~dfsg
[roundcube.git] / program / lib / DB / pgsql.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * The PEAR DB driver for PHP's pgsql extension
7  * for interacting with PostgreSQL databases
8  *
9  * PHP versions 4 and 5
10  *
11  * LICENSE: This source file is subject to version 3.0 of the PHP license
12  * that is available through the world-wide-web at the following URI:
13  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
14  * the PHP License and are unable to obtain it through the web, please
15  * send a note to license@php.net so we can mail you a copy immediately.
16  *
17  * @category   Database
18  * @package    DB
19  * @author     Rui Hirokawa <hirokawa@php.net>
20  * @author     Stig Bakken <ssb@php.net>
21  * @author     Daniel Convissor <danielc@php.net>
22  * @copyright  1997-2005 The PHP Group
23  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
24  * @version    CVS: $Id: pgsql.php 12 2005-10-02 11:36:35Z sparc $
25  * @link       http://pear.php.net/package/DB
26  */
27
28 /**
29  * Obtain the DB_common class so it can be extended from
30  */
31 require_once 'DB/common.php';
32
33 /**
34  * The methods PEAR DB uses to interact with PHP's pgsql extension
35  * for interacting with PostgreSQL databases
36  *
37  * These methods overload the ones declared in DB_common.
38  *
39  * @category   Database
40  * @package    DB
41  * @author     Rui Hirokawa <hirokawa@php.net>
42  * @author     Stig Bakken <ssb@php.net>
43  * @author     Daniel Convissor <danielc@php.net>
44  * @copyright  1997-2005 The PHP Group
45  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
46  * @version    Release: @package_version@
47  * @link       http://pear.php.net/package/DB
48  */
49 class DB_pgsql extends DB_common
50 {
51     // {{{ properties
52
53     /**
54      * The DB driver type (mysql, oci8, odbc, etc.)
55      * @var string
56      */
57     var $phptype = 'pgsql';
58
59     /**
60      * The database syntax variant to be used (db2, access, etc.), if any
61      * @var string
62      */
63     var $dbsyntax = 'pgsql';
64
65     /**
66      * The capabilities of this DB implementation
67      *
68      * The 'new_link' element contains the PHP version that first provided
69      * new_link support for this DBMS.  Contains false if it's unsupported.
70      *
71      * Meaning of the 'limit' element:
72      *   + 'emulate' = emulate with fetch row by number
73      *   + 'alter'   = alter the query
74      *   + false     = skip rows
75      *
76      * @var array
77      */
78     var $features = array(
79         'limit'         => 'alter',
80         'new_link'      => '4.3.0',
81         'numrows'       => true,
82         'pconnect'      => true,
83         'prepare'       => false,
84         'ssl'           => true,
85         'transactions'  => true,
86     );
87
88     /**
89      * A mapping of native error codes to DB error codes
90      * @var array
91      */
92     var $errorcode_map = array(
93     );
94
95     /**
96      * The raw database connection created by PHP
97      * @var resource
98      */
99     var $connection;
100
101     /**
102      * The DSN information for connecting to a database
103      * @var array
104      */
105     var $dsn = array();
106
107
108     /**
109      * Should data manipulation queries be committed automatically?
110      * @var bool
111      * @access private
112      */
113     var $autocommit = true;
114
115     /**
116      * The quantity of transactions begun
117      *
118      * {@internal  While this is private, it can't actually be designated
119      * private in PHP 5 because it is directly accessed in the test suite.}}
120      *
121      * @var integer
122      * @access private
123      */
124     var $transaction_opcount = 0;
125
126     /**
127      * The number of rows affected by a data manipulation query
128      * @var integer
129      */
130     var $affected = 0;
131
132     /**
133      * The current row being looked at in fetchInto()
134      * @var array
135      * @access private
136      */
137     var $row = array();
138
139     /**
140      * The number of rows in a given result set
141      * @var array
142      * @access private
143      */
144     var $_num_rows = array();
145
146
147     // }}}
148     // {{{ constructor
149
150     /**
151      * This constructor calls <kbd>$this->DB_common()</kbd>
152      *
153      * @return void
154      */
155     function DB_pgsql()
156     {
157         $this->DB_common();
158     }
159
160     // }}}
161     // {{{ connect()
162
163     /**
164      * Connect to the database server, log in and open the database
165      *
166      * Don't call this method directly.  Use DB::connect() instead.
167      *
168      * PEAR DB's pgsql driver supports the following extra DSN options:
169      *   + connect_timeout  How many seconds to wait for a connection to
170      *                       be established.  Available since PEAR DB 1.7.0.
171      *   + new_link         If set to true, causes subsequent calls to
172      *                       connect() to return a new connection link
173      *                       instead of the existing one.  WARNING: this is
174      *                       not portable to other DBMS's.  Available only
175      *                       if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0.
176      *   + options          Command line options to be sent to the server.
177      *                       Available since PEAR DB 1.6.4.
178      *   + service          Specifies a service name in pg_service.conf that
179      *                       holds additional connection parameters.
180      *                       Available since PEAR DB 1.7.0.
181      *   + sslmode          How should SSL be used when connecting?  Values:
182      *                       disable, allow, prefer or require.
183      *                       Available since PEAR DB 1.7.0.
184      *   + tty              This was used to specify where to send server
185      *                       debug output.  Available since PEAR DB 1.6.4.
186      *
187      * Example of connecting to a new link via a socket:
188      * <code>
189      * require_once 'DB.php';
190      * 
191      * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true';
192      * $options = array(
193      *     'portability' => DB_PORTABILITY_ALL,
194      * );
195      * 
196      * $db =& DB::connect($dsn, $options);
197      * if (PEAR::isError($db)) {
198      *     die($db->getMessage());
199      * }
200      * </code>
201      *
202      * @param array $dsn         the data source name
203      * @param bool  $persistent  should the connection be persistent?
204      *
205      * @return int  DB_OK on success. A DB_Error object on failure.
206      *
207      * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT
208      */
209     function connect($dsn, $persistent = false)
210     {
211         if (!PEAR::loadExtension('pgsql')) {
212             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
213         }
214
215         $this->dsn = $dsn;
216         if ($dsn['dbsyntax']) {
217             $this->dbsyntax = $dsn['dbsyntax'];
218         }
219
220         $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp';
221
222         $params = array('');
223         if ($protocol == 'tcp') {
224             if ($dsn['hostspec']) {
225                 $params[0] .= 'host=' . $dsn['hostspec'];
226             }
227             if ($dsn['port']) {
228                 $params[0] .= ' port=' . $dsn['port'];
229             }
230         } elseif ($protocol == 'unix') {
231             // Allow for pg socket in non-standard locations.
232             if ($dsn['socket']) {
233                 $params[0] .= 'host=' . $dsn['socket'];
234             }
235             if ($dsn['port']) {
236                 $params[0] .= ' port=' . $dsn['port'];
237             }
238         }
239         if ($dsn['database']) {
240             $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\'';
241         }
242         if ($dsn['username']) {
243             $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\'';
244         }
245         if ($dsn['password']) {
246             $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\'';
247         }
248         if (!empty($dsn['options'])) {
249             $params[0] .= ' options=' . $dsn['options'];
250         }
251         if (!empty($dsn['tty'])) {
252             $params[0] .= ' tty=' . $dsn['tty'];
253         }
254         if (!empty($dsn['connect_timeout'])) {
255             $params[0] .= ' connect_timeout=' . $dsn['connect_timeout'];
256         }
257         if (!empty($dsn['sslmode'])) {
258             $params[0] .= ' sslmode=' . $dsn['sslmode'];
259         }
260         if (!empty($dsn['service'])) {
261             $params[0] .= ' service=' . $dsn['service'];
262         }
263
264         if (isset($dsn['new_link'])
265             && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
266         {
267             if (version_compare(phpversion(), '4.3.0', '>=')) {
268                 $params[] = PGSQL_CONNECT_FORCE_NEW;
269             }
270         }
271
272         $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
273
274         $ini = ini_get('track_errors');
275         $php_errormsg = '';
276         if ($ini) {
277             $this->connection = @call_user_func_array($connect_function,
278                                                       $params);
279         } else {
280             ini_set('track_errors', 1);
281             $this->connection = @call_user_func_array($connect_function,
282                                                       $params);
283             ini_set('track_errors', $ini);
284         }
285
286         if (!$this->connection) {
287             return $this->raiseError(DB_ERROR_CONNECT_FAILED,
288                                      null, null, null,
289                                      $php_errormsg);
290         }
291         return DB_OK;
292     }
293
294     // }}}
295     // {{{ disconnect()
296
297     /**
298      * Disconnects from the database server
299      *
300      * @return bool  TRUE on success, FALSE on failure
301      */
302     function disconnect()
303     {
304         $ret = @pg_close($this->connection);
305         $this->connection = null;
306         return $ret;
307     }
308
309     // }}}
310     // {{{ simpleQuery()
311
312     /**
313      * Sends a query to the database server
314      *
315      * @param string  the SQL query string
316      *
317      * @return mixed  + a PHP result resrouce for successful SELECT queries
318      *                + the DB_OK constant for other successful queries
319      *                + a DB_Error object on failure
320      */
321     function simpleQuery($query)
322     {
323         $ismanip = DB::isManip($query);
324         $this->last_query = $query;
325         $query = $this->modifyQuery($query);
326         if (!$this->autocommit && $ismanip) {
327             if ($this->transaction_opcount == 0) {
328                 $result = @pg_exec($this->connection, 'begin;');
329                 if (!$result) {
330                     return $this->pgsqlRaiseError();
331                 }
332             }
333             $this->transaction_opcount++;
334         }
335         $result = @pg_exec($this->connection, $query);
336         if (!$result) {
337             return $this->pgsqlRaiseError();
338         }
339         // Determine which queries that should return data, and which
340         // should return an error code only.
341         if ($ismanip) {
342             $this->affected = @pg_affected_rows($result);
343             return DB_OK;
344         } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) {
345             /* PostgreSQL commands:
346                ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
347                CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
348                GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
349                REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
350                UNLISTEN, UPDATE, VACUUM
351             */
352             $this->row[(int)$result] = 0; // reset the row counter.
353             $numrows = $this->numRows($result);
354             if (is_object($numrows)) {
355                 return $numrows;
356             }
357             $this->_num_rows[(int)$result] = $numrows;
358             $this->affected = 0;
359             return $result;
360         } else {
361             $this->affected = 0;
362             return DB_OK;
363         }
364     }
365
366     // }}}
367     // {{{ nextResult()
368
369     /**
370      * Move the internal pgsql result pointer to the next available result
371      *
372      * @param a valid fbsql result resource
373      *
374      * @access public
375      *
376      * @return true if a result is available otherwise return false
377      */
378     function nextResult($result)
379     {
380         return false;
381     }
382
383     // }}}
384     // {{{ fetchInto()
385
386     /**
387      * Places a row from the result set into the given array
388      *
389      * Formating of the array and the data therein are configurable.
390      * See DB_result::fetchInto() for more information.
391      *
392      * This method is not meant to be called directly.  Use
393      * DB_result::fetchInto() instead.  It can't be declared "protected"
394      * because DB_result is a separate object.
395      *
396      * @param resource $result    the query result resource
397      * @param array    $arr       the referenced array to put the data in
398      * @param int      $fetchmode how the resulting array should be indexed
399      * @param int      $rownum    the row number to fetch (0 = first row)
400      *
401      * @return mixed  DB_OK on success, NULL when the end of a result set is
402      *                 reached or on failure
403      *
404      * @see DB_result::fetchInto()
405      */
406     function fetchInto($result, &$arr, $fetchmode, $rownum = null)
407     {
408         $result_int = (int)$result;
409         $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
410         if ($rownum >= $this->_num_rows[$result_int]) {
411             return null;
412         }
413         if ($fetchmode & DB_FETCHMODE_ASSOC) {
414             $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
415             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
416                 $arr = array_change_key_case($arr, CASE_LOWER);
417             }
418         } else {
419             $arr = @pg_fetch_row($result, $rownum);
420         }
421         if (!$arr) {
422             return null;
423         }
424         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
425             $this->_rtrimArrayValues($arr);
426         }
427         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
428             $this->_convertNullArrayValuesToEmpty($arr);
429         }
430         $this->row[$result_int] = ++$rownum;
431         return DB_OK;
432     }
433
434     // }}}
435     // {{{ freeResult()
436
437     /**
438      * Deletes the result set and frees the memory occupied by the result set
439      *
440      * This method is not meant to be called directly.  Use
441      * DB_result::free() instead.  It can't be declared "protected"
442      * because DB_result is a separate object.
443      *
444      * @param resource $result  PHP's query result resource
445      *
446      * @return bool  TRUE on success, FALSE if $result is invalid
447      *
448      * @see DB_result::free()
449      */
450     function freeResult($result)
451     {
452         if (is_resource($result)) {
453             unset($this->row[(int)$result]);
454             unset($this->_num_rows[(int)$result]);
455             $this->affected = 0;
456             return @pg_freeresult($result);
457         }
458         return false;
459     }
460
461     // }}}
462     // {{{ quote()
463
464     /**
465      * @deprecated  Deprecated in release 1.6.0
466      * @internal
467      */
468     function quote($str)
469     {
470         return $this->quoteSmart($str);
471     }
472
473     // }}}
474     // {{{ quoteSmart()
475
476     /**
477      * Formats input so it can be safely used in a query
478      *
479      * @param mixed $in  the data to be formatted
480      *
481      * @return mixed  the formatted data.  The format depends on the input's
482      *                 PHP type:
483      *                 + null = the string <samp>NULL</samp>
484      *                 + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
485      *                 + integer or double = the unquoted number
486      *                 + other (including strings and numeric strings) =
487      *                   the data escaped according to MySQL's settings
488      *                   then encapsulated between single quotes
489      *
490      * @see DB_common::quoteSmart()
491      * @since Method available since Release 1.6.0
492      */
493     function quoteSmart($in)
494     {
495         if (is_int($in) || is_double($in)) {
496             return $in;
497         } elseif (is_bool($in)) {
498             return $in ? 'TRUE' : 'FALSE';
499         } elseif (is_null($in)) {
500             return 'NULL';
501         } else {
502             return "'" . $this->escapeSimple($in) . "'";
503         }
504     }
505
506     // }}}
507     // {{{ escapeSimple()
508
509     /**
510      * Escapes a string according to the current DBMS's standards
511      *
512      * {@internal PostgreSQL treats a backslash as an escape character,
513      * so they are escaped as well.
514      *
515      * Not using pg_escape_string() yet because it requires PostgreSQL
516      * to be at version 7.2 or greater.}}
517      *
518      * @param string $str  the string to be escaped
519      *
520      * @return string  the escaped string
521      *
522      * @see DB_common::quoteSmart()
523      * @since Method available since Release 1.6.0
524      */
525     function escapeSimple($str)
526     {
527         return str_replace("'", "''", str_replace('\\', '\\\\', $str));
528     }
529
530     // }}}
531     // {{{ numCols()
532
533     /**
534      * Gets the number of columns in a result set
535      *
536      * This method is not meant to be called directly.  Use
537      * DB_result::numCols() instead.  It can't be declared "protected"
538      * because DB_result is a separate object.
539      *
540      * @param resource $result  PHP's query result resource
541      *
542      * @return int  the number of columns.  A DB_Error object on failure.
543      *
544      * @see DB_result::numCols()
545      */
546     function numCols($result)
547     {
548         $cols = @pg_numfields($result);
549         if (!$cols) {
550             return $this->pgsqlRaiseError();
551         }
552         return $cols;
553     }
554
555     // }}}
556     // {{{ numRows()
557
558     /**
559      * Gets the number of rows in a result set
560      *
561      * This method is not meant to be called directly.  Use
562      * DB_result::numRows() instead.  It can't be declared "protected"
563      * because DB_result is a separate object.
564      *
565      * @param resource $result  PHP's query result resource
566      *
567      * @return int  the number of rows.  A DB_Error object on failure.
568      *
569      * @see DB_result::numRows()
570      */
571     function numRows($result)
572     {
573         $rows = @pg_numrows($result);
574         if ($rows === null) {
575             return $this->pgsqlRaiseError();
576         }
577         return $rows;
578     }
579
580     // }}}
581     // {{{ autoCommit()
582
583     /**
584      * Enables or disables automatic commits
585      *
586      * @param bool $onoff  true turns it on, false turns it off
587      *
588      * @return int  DB_OK on success.  A DB_Error object if the driver
589      *               doesn't support auto-committing transactions.
590      */
591     function autoCommit($onoff = false)
592     {
593         // XXX if $this->transaction_opcount > 0, we should probably
594         // issue a warning here.
595         $this->autocommit = $onoff ? true : false;
596         return DB_OK;
597     }
598
599     // }}}
600     // {{{ commit()
601
602     /**
603      * Commits the current transaction
604      *
605      * @return int  DB_OK on success.  A DB_Error object on failure.
606      */
607     function commit()
608     {
609         if ($this->transaction_opcount > 0) {
610             // (disabled) hack to shut up error messages from libpq.a
611             //@fclose(@fopen("php://stderr", "w"));
612             $result = @pg_exec($this->connection, 'end;');
613             $this->transaction_opcount = 0;
614             if (!$result) {
615                 return $this->pgsqlRaiseError();
616             }
617         }
618         return DB_OK;
619     }
620
621     // }}}
622     // {{{ rollback()
623
624     /**
625      * Reverts the current transaction
626      *
627      * @return int  DB_OK on success.  A DB_Error object on failure.
628      */
629     function rollback()
630     {
631         if ($this->transaction_opcount > 0) {
632             $result = @pg_exec($this->connection, 'abort;');
633             $this->transaction_opcount = 0;
634             if (!$result) {
635                 return $this->pgsqlRaiseError();
636             }
637         }
638         return DB_OK;
639     }
640
641     // }}}
642     // {{{ affectedRows()
643
644     /**
645      * Determines the number of rows affected by a data maniuplation query
646      *
647      * 0 is returned for queries that don't manipulate data.
648      *
649      * @return int  the number of rows.  A DB_Error object on failure.
650      */
651     function affectedRows()
652     {
653         return $this->affected;
654     }
655
656     // }}}
657     // {{{ nextId()
658
659     /**
660      * Returns the next free id in a sequence
661      *
662      * @param string  $seq_name  name of the sequence
663      * @param boolean $ondemand  when true, the seqence is automatically
664      *                            created if it does not exist
665      *
666      * @return int  the next id number in the sequence.
667      *               A DB_Error object on failure.
668      *
669      * @see DB_common::nextID(), DB_common::getSequenceName(),
670      *      DB_pgsql::createSequence(), DB_pgsql::dropSequence()
671      */
672     function nextId($seq_name, $ondemand = true)
673     {
674         $seqname = $this->getSequenceName($seq_name);
675         $repeat = false;
676         do {
677             $this->pushErrorHandling(PEAR_ERROR_RETURN);
678             $result =& $this->query("SELECT NEXTVAL('${seqname}')");
679             $this->popErrorHandling();
680             if ($ondemand && DB::isError($result) &&
681                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
682                 $repeat = true;
683                 $this->pushErrorHandling(PEAR_ERROR_RETURN);
684                 $result = $this->createSequence($seq_name);
685                 $this->popErrorHandling();
686                 if (DB::isError($result)) {
687                     return $this->raiseError($result);
688                 }
689             } else {
690                 $repeat = false;
691             }
692         } while ($repeat);
693         if (DB::isError($result)) {
694             return $this->raiseError($result);
695         }
696         $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
697         $result->free();
698         return $arr[0];
699     }
700
701     // }}}
702     // {{{ createSequence()
703
704     /**
705      * Creates a new sequence
706      *
707      * @param string $seq_name  name of the new sequence
708      *
709      * @return int  DB_OK on success.  A DB_Error object on failure.
710      *
711      * @see DB_common::createSequence(), DB_common::getSequenceName(),
712      *      DB_pgsql::nextID(), DB_pgsql::dropSequence()
713      */
714     function createSequence($seq_name)
715     {
716         $seqname = $this->getSequenceName($seq_name);
717         $result = $this->query("CREATE SEQUENCE ${seqname}");
718         return $result;
719     }
720
721     // }}}
722     // {{{ dropSequence()
723
724     /**
725      * Deletes a sequence
726      *
727      * @param string $seq_name  name of the sequence to be deleted
728      *
729      * @return int  DB_OK on success.  A DB_Error object on failure.
730      *
731      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
732      *      DB_pgsql::nextID(), DB_pgsql::createSequence()
733      */
734     function dropSequence($seq_name)
735     {
736         return $this->query('DROP SEQUENCE '
737                             . $this->getSequenceName($seq_name));
738     }
739
740     // }}}
741     // {{{ modifyLimitQuery()
742
743     /**
744      * Adds LIMIT clauses to a query string according to current DBMS standards
745      *
746      * @param string $query   the query to modify
747      * @param int    $from    the row to start to fetching (0 = the first row)
748      * @param int    $count   the numbers of rows to fetch
749      * @param mixed  $params  array, string or numeric data to be used in
750      *                         execution of the statement.  Quantity of items
751      *                         passed must match quantity of placeholders in
752      *                         query:  meaning 1 placeholder for non-array
753      *                         parameters or 1 placeholder per array element.
754      *
755      * @return string  the query string with LIMIT clauses added
756      *
757      * @access protected
758      */
759     function modifyLimitQuery($query, $from, $count, $params = array())
760     {
761         return "$query LIMIT $count OFFSET $from";
762     }
763
764     // }}}
765     // {{{ pgsqlRaiseError()
766
767     /**
768      * Produces a DB_Error object regarding the current problem
769      *
770      * @param int $errno  if the error is being manually raised pass a
771      *                     DB_ERROR* constant here.  If this isn't passed
772      *                     the error information gathered from the DBMS.
773      *
774      * @return object  the DB_Error object
775      *
776      * @see DB_common::raiseError(),
777      *      DB_pgsql::errorNative(), DB_pgsql::errorCode()
778      */
779     function pgsqlRaiseError($errno = null)
780     {
781         $native = $this->errorNative();
782         if ($errno === null) {
783             $errno = $this->errorCode($native);
784         }
785         return $this->raiseError($errno, null, null, null, $native);
786     }
787
788     // }}}
789     // {{{ errorNative()
790
791     /**
792      * Gets the DBMS' native error message produced by the last query
793      *
794      * {@internal Error messages are used instead of error codes 
795      * in order to support older versions of PostgreSQL.}}
796      *
797      * @return string  the DBMS' error message
798      */
799     function errorNative()
800     {
801         return @pg_errormessage($this->connection);
802     }
803
804     // }}}
805     // {{{ errorCode()
806
807     /**
808      * Determines PEAR::DB error code from the database's text error message.
809      *
810      * @param  string  $errormsg  error message returned from the database
811      * @return integer  an error number from a DB error constant
812      */
813     function errorCode($errormsg)
814     {
815         static $error_regexps;
816         if (!isset($error_regexps)) {
817             $error_regexps = array(
818                 '/(relation|sequence|table).*does not exist|class .* not found/i'
819                     => DB_ERROR_NOSUCHTABLE,
820                 '/index .* does not exist/'
821                     => DB_ERROR_NOT_FOUND,
822                 '/column .* does not exist/i'
823                     => DB_ERROR_NOSUCHFIELD,
824                 '/relation .* already exists/i'
825                     => DB_ERROR_ALREADY_EXISTS,
826                 '/(divide|division) by zero$/i'
827                     => DB_ERROR_DIVZERO,
828                 '/pg_atoi: error in .*: can\'t parse /i'
829                     => DB_ERROR_INVALID_NUMBER,
830                 '/invalid input syntax for( type)? (integer|numeric)/i'
831                     => DB_ERROR_INVALID_NUMBER,
832                 '/value .* is out of range for type \w*int/i'
833                     => DB_ERROR_INVALID_NUMBER,
834                 '/integer out of range/i'
835                     => DB_ERROR_INVALID_NUMBER,
836                 '/value too long for type character/i'
837                     => DB_ERROR_INVALID,
838                 '/attribute .* not found|relation .* does not have attribute/i'
839                     => DB_ERROR_NOSUCHFIELD,
840                 '/column .* specified in USING clause does not exist in (left|right) table/i'
841                     => DB_ERROR_NOSUCHFIELD,
842                 '/parser: parse error at or near/i'
843                     => DB_ERROR_SYNTAX,
844                 '/syntax error at/'
845                     => DB_ERROR_SYNTAX,
846                 '/column reference .* is ambiguous/i'
847                     => DB_ERROR_SYNTAX,
848                 '/permission denied/'
849                     => DB_ERROR_ACCESS_VIOLATION,
850                 '/violates not-null constraint/'
851                     => DB_ERROR_CONSTRAINT_NOT_NULL,
852                 '/violates [\w ]+ constraint/'
853                     => DB_ERROR_CONSTRAINT,
854                 '/referential integrity violation/'
855                     => DB_ERROR_CONSTRAINT,
856                 '/more expressions than target columns/i'
857                     => DB_ERROR_VALUE_COUNT_ON_ROW,
858             );
859         }
860         foreach ($error_regexps as $regexp => $code) {
861             if (preg_match($regexp, $errormsg)) {
862                 return $code;
863             }
864         }
865         // Fall back to DB_ERROR if there was no mapping.
866         return DB_ERROR;
867     }
868
869     // }}}
870     // {{{ tableInfo()
871
872     /**
873      * Returns information about a table or a result set
874      *
875      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
876      * is a table name.
877      *
878      * @param object|string  $result  DB_result object from a query or a
879      *                                 string containing the name of a table.
880      *                                 While this also accepts a query result
881      *                                 resource identifier, this behavior is
882      *                                 deprecated.
883      * @param int            $mode    a valid tableInfo mode
884      *
885      * @return array  an associative array with the information requested.
886      *                 A DB_Error object on failure.
887      *
888      * @see DB_common::tableInfo()
889      */
890     function tableInfo($result, $mode = null)
891     {
892         if (is_string($result)) {
893             /*
894              * Probably received a table name.
895              * Create a result resource identifier.
896              */
897             $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
898             $got_string = true;
899         } elseif (isset($result->result)) {
900             /*
901              * Probably received a result object.
902              * Extract the result resource identifier.
903              */
904             $id = $result->result;
905             $got_string = false;
906         } else {
907             /*
908              * Probably received a result resource identifier.
909              * Copy it.
910              * Deprecated.  Here for compatibility only.
911              */
912             $id = $result;
913             $got_string = false;
914         }
915
916         if (!is_resource($id)) {
917             return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
918         }
919
920         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
921             $case_func = 'strtolower';
922         } else {
923             $case_func = 'strval';
924         }
925
926         $count = @pg_numfields($id);
927         $res   = array();
928
929         if ($mode) {
930             $res['num_fields'] = $count;
931         }
932
933         for ($i = 0; $i < $count; $i++) {
934             $res[$i] = array(
935                 'table' => $got_string ? $case_func($result) : '',
936                 'name'  => $case_func(@pg_fieldname($id, $i)),
937                 'type'  => @pg_fieldtype($id, $i),
938                 'len'   => @pg_fieldsize($id, $i),
939                 'flags' => $got_string
940                            ? $this->_pgFieldFlags($id, $i, $result)
941                            : '',
942             );
943             if ($mode & DB_TABLEINFO_ORDER) {
944                 $res['order'][$res[$i]['name']] = $i;
945             }
946             if ($mode & DB_TABLEINFO_ORDERTABLE) {
947                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
948             }
949         }
950
951         // free the result only if we were called on a table
952         if ($got_string) {
953             @pg_freeresult($id);
954         }
955         return $res;
956     }
957
958     // }}}
959     // {{{ _pgFieldFlags()
960
961     /**
962      * Get a column's flags
963      *
964      * Supports "not_null", "default_value", "primary_key", "unique_key"
965      * and "multiple_key".  The default value is passed through
966      * rawurlencode() in case there are spaces in it.
967      *
968      * @param int $resource   the PostgreSQL result identifier
969      * @param int $num_field  the field number
970      *
971      * @return string  the flags
972      *
973      * @access private
974      */
975     function _pgFieldFlags($resource, $num_field, $table_name)
976     {
977         $field_name = @pg_fieldname($resource, $num_field);
978
979         $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
980                                 FROM pg_attribute f, pg_class tab, pg_type typ
981                                 WHERE tab.relname = typ.typname
982                                 AND typ.typrelid = f.attrelid
983                                 AND f.attname = '$field_name'
984                                 AND tab.relname = '$table_name'");
985         if (@pg_numrows($result) > 0) {
986             $row = @pg_fetch_row($result, 0);
987             $flags  = ($row[0] == 't') ? 'not_null ' : '';
988
989             if ($row[1] == 't') {
990                 $result = @pg_exec($this->connection, "SELECT a.adsrc
991                                     FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a
992                                     WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
993                                     AND f.attrelid = a.adrelid AND f.attname = '$field_name'
994                                     AND tab.relname = '$table_name' AND f.attnum = a.adnum");
995                 $row = @pg_fetch_row($result, 0);
996                 $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
997                 $flags .= 'default_' . rawurlencode($num) . ' ';
998             }
999         } else {
1000             $flags = '';
1001         }
1002         $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
1003                                 FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i
1004                                 WHERE tab.relname = typ.typname
1005                                 AND typ.typrelid = f.attrelid
1006                                 AND f.attrelid = i.indrelid
1007                                 AND f.attname = '$field_name'
1008                                 AND tab.relname = '$table_name'");
1009         $count = @pg_numrows($result);
1010
1011         for ($i = 0; $i < $count ; $i++) {
1012             $row = @pg_fetch_row($result, $i);
1013             $keys = explode(' ', $row[2]);
1014
1015             if (in_array($num_field + 1, $keys)) {
1016                 $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
1017                 $flags .= ($row[1] == 't') ? 'primary_key ' : '';
1018                 if (count($keys) > 1)
1019                     $flags .= 'multiple_key ';
1020             }
1021         }
1022
1023         return trim($flags);
1024     }
1025
1026     // }}}
1027     // {{{ getSpecialQuery()
1028
1029     /**
1030      * Obtains the query string needed for listing a given type of objects
1031      *
1032      * @param string $type  the kind of objects you want to retrieve
1033      *
1034      * @return string  the SQL query string or null if the driver doesn't
1035      *                  support the object type requested
1036      *
1037      * @access protected
1038      * @see DB_common::getListOf()
1039      */
1040     function getSpecialQuery($type)
1041     {
1042         switch ($type) {
1043             case 'tables':
1044                 return 'SELECT c.relname AS "Name"'
1045                         . ' FROM pg_class c, pg_user u'
1046                         . ' WHERE c.relowner = u.usesysid'
1047                         . " AND c.relkind = 'r'"
1048                         . ' AND NOT EXISTS'
1049                         . ' (SELECT 1 FROM pg_views'
1050                         . '  WHERE viewname = c.relname)'
1051                         . " AND c.relname !~ '^(pg_|sql_)'"
1052                         . ' UNION'
1053                         . ' SELECT c.relname AS "Name"'
1054                         . ' FROM pg_class c'
1055                         . " WHERE c.relkind = 'r'"
1056                         . ' AND NOT EXISTS'
1057                         . ' (SELECT 1 FROM pg_views'
1058                         . '  WHERE viewname = c.relname)'
1059                         . ' AND NOT EXISTS'
1060                         . ' (SELECT 1 FROM pg_user'
1061                         . '  WHERE usesysid = c.relowner)'
1062                         . " AND c.relname !~ '^pg_'";
1063             case 'schema.tables':
1064                 return "SELECT schemaname || '.' || tablename"
1065                         . ' AS "Name"'
1066                         . ' FROM pg_catalog.pg_tables'
1067                         . ' WHERE schemaname NOT IN'
1068                         . " ('pg_catalog', 'information_schema', 'pg_toast')";
1069             case 'views':
1070                 // Table cols: viewname | viewowner | definition
1071                 return 'SELECT viewname from pg_views WHERE schemaname'
1072                         . " NOT IN ('information_schema', 'pg_catalog')";
1073             case 'users':
1074                 // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd  |valuntil
1075                 return 'SELECT usename FROM pg_user';
1076             case 'databases':
1077                 return 'SELECT datname FROM pg_database';
1078             case 'functions':
1079             case 'procedures':
1080                 return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
1081             default:
1082                 return null;
1083         }
1084     }
1085
1086     // }}}
1087
1088 }
1089
1090 /*
1091  * Local variables:
1092  * tab-width: 4
1093  * c-basic-offset: 4
1094  * End:
1095  */
1096
1097 ?>