]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/pgsql.php
DB updated to 1.8.2
[quix0rs-gnu-social.git] / extlib / 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 version 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-2007 The PHP Group
23  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
24  * @version    CVS: $Id$
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-2007 The PHP Group
45  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
46  * @version    Release: 1.8.2
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 = $this->_checkManip($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
340         /*
341          * Determine whether queries produce affected rows, result or nothing.
342          *
343          * This logic was introduced in version 1.1 of the file by ssb,
344          * though the regex has been modified slightly since then.
345          *
346          * PostgreSQL commands:
347          * ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
348          * CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
349          * GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
350          * REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
351          * UNLISTEN, UPDATE, VACUUM, WITH
352          */
353         if ($ismanip) {
354             $this->affected = @pg_affected_rows($result);
355             return DB_OK;
356         } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|FETCH|SHOW|WITH)\s/si',
357                              $query))
358         {
359             $this->row[(int)$result] = 0; // reset the row counter.
360             $numrows = $this->numRows($result);
361             if (is_object($numrows)) {
362                 return $numrows;
363             }
364             $this->_num_rows[(int)$result] = $numrows;
365             $this->affected = 0;
366             return $result;
367         } else {
368             $this->affected = 0;
369             return DB_OK;
370         }
371     }
372
373     // }}}
374     // {{{ nextResult()
375
376     /**
377      * Move the internal pgsql result pointer to the next available result
378      *
379      * @param a valid fbsql result resource
380      *
381      * @access public
382      *
383      * @return true if a result is available otherwise return false
384      */
385     function nextResult($result)
386     {
387         return false;
388     }
389
390     // }}}
391     // {{{ fetchInto()
392
393     /**
394      * Places a row from the result set into the given array
395      *
396      * Formating of the array and the data therein are configurable.
397      * See DB_result::fetchInto() for more information.
398      *
399      * This method is not meant to be called directly.  Use
400      * DB_result::fetchInto() instead.  It can't be declared "protected"
401      * because DB_result is a separate object.
402      *
403      * @param resource $result    the query result resource
404      * @param array    $arr       the referenced array to put the data in
405      * @param int      $fetchmode how the resulting array should be indexed
406      * @param int      $rownum    the row number to fetch (0 = first row)
407      *
408      * @return mixed  DB_OK on success, NULL when the end of a result set is
409      *                 reached or on failure
410      *
411      * @see DB_result::fetchInto()
412      */
413     function fetchInto($result, &$arr, $fetchmode, $rownum = null)
414     {
415         $result_int = (int)$result;
416         $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
417         if ($rownum >= $this->_num_rows[$result_int]) {
418             return null;
419         }
420         if ($fetchmode & DB_FETCHMODE_ASSOC) {
421             $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
422             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
423                 $arr = array_change_key_case($arr, CASE_LOWER);
424             }
425         } else {
426             $arr = @pg_fetch_row($result, $rownum);
427         }
428         if (!$arr) {
429             return null;
430         }
431         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
432             $this->_rtrimArrayValues($arr);
433         }
434         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
435             $this->_convertNullArrayValuesToEmpty($arr);
436         }
437         $this->row[$result_int] = ++$rownum;
438         return DB_OK;
439     }
440
441     // }}}
442     // {{{ freeResult()
443
444     /**
445      * Deletes the result set and frees the memory occupied by the result set
446      *
447      * This method is not meant to be called directly.  Use
448      * DB_result::free() instead.  It can't be declared "protected"
449      * because DB_result is a separate object.
450      *
451      * @param resource $result  PHP's query result resource
452      *
453      * @return bool  TRUE on success, FALSE if $result is invalid
454      *
455      * @see DB_result::free()
456      */
457     function freeResult($result)
458     {
459         if (is_resource($result)) {
460             unset($this->row[(int)$result]);
461             unset($this->_num_rows[(int)$result]);
462             $this->affected = 0;
463             return @pg_freeresult($result);
464         }
465         return false;
466     }
467
468     // }}}
469     // {{{ quoteBoolean()
470
471     /**
472      * Formats a boolean value for use within a query in a locale-independent
473      * manner.
474      *
475      * @param boolean the boolean value to be quoted.
476      * @return string the quoted string.
477      * @see DB_common::quoteSmart()
478      * @since Method available since release 1.7.8.
479      */
480     function quoteBoolean($boolean) {
481         return $boolean ? 'TRUE' : 'FALSE';
482     }
483      
484     // }}}
485     // {{{ escapeSimple()
486
487     /**
488      * Escapes a string according to the current DBMS's standards
489      *
490      * {@internal PostgreSQL treats a backslash as an escape character,
491      * so they are escaped as well.
492      *
493      * @param string $str  the string to be escaped
494      *
495      * @return string  the escaped string
496      *
497      * @see DB_common::quoteSmart()
498      * @since Method available since Release 1.6.0
499      */
500     function escapeSimple($str)
501     {
502         if (function_exists('pg_escape_string')) {
503             /* This fixes an undocumented BC break in PHP 5.2.0 which changed
504              * the prototype of pg_escape_string. I'm not thrilled about having
505              * to sniff the PHP version, quite frankly, but it's the only way
506              * to deal with the problem. Revision 1.331.2.13.2.10 on
507              * php-src/ext/pgsql/pgsql.c (PHP_5_2 branch) is to blame, for the
508              * record. */
509             if (version_compare(PHP_VERSION, '5.2.0', '>=')) {
510                 return pg_escape_string($this->connection, $str);
511             } else {
512                 return pg_escape_string($str);
513             }
514         } else {
515             return str_replace("'", "''", str_replace('\\', '\\\\', $str));
516         }
517     }
518
519     // }}}
520     // {{{ numCols()
521
522     /**
523      * Gets the number of columns in a result set
524      *
525      * This method is not meant to be called directly.  Use
526      * DB_result::numCols() instead.  It can't be declared "protected"
527      * because DB_result is a separate object.
528      *
529      * @param resource $result  PHP's query result resource
530      *
531      * @return int  the number of columns.  A DB_Error object on failure.
532      *
533      * @see DB_result::numCols()
534      */
535     function numCols($result)
536     {
537         $cols = @pg_numfields($result);
538         if (!$cols) {
539             return $this->pgsqlRaiseError();
540         }
541         return $cols;
542     }
543
544     // }}}
545     // {{{ numRows()
546
547     /**
548      * Gets the number of rows in a result set
549      *
550      * This method is not meant to be called directly.  Use
551      * DB_result::numRows() instead.  It can't be declared "protected"
552      * because DB_result is a separate object.
553      *
554      * @param resource $result  PHP's query result resource
555      *
556      * @return int  the number of rows.  A DB_Error object on failure.
557      *
558      * @see DB_result::numRows()
559      */
560     function numRows($result)
561     {
562         $rows = @pg_numrows($result);
563         if ($rows === null) {
564             return $this->pgsqlRaiseError();
565         }
566         return $rows;
567     }
568
569     // }}}
570     // {{{ autoCommit()
571
572     /**
573      * Enables or disables automatic commits
574      *
575      * @param bool $onoff  true turns it on, false turns it off
576      *
577      * @return int  DB_OK on success.  A DB_Error object if the driver
578      *               doesn't support auto-committing transactions.
579      */
580     function autoCommit($onoff = false)
581     {
582         // XXX if $this->transaction_opcount > 0, we should probably
583         // issue a warning here.
584         $this->autocommit = $onoff ? true : false;
585         return DB_OK;
586     }
587
588     // }}}
589     // {{{ commit()
590
591     /**
592      * Commits the current transaction
593      *
594      * @return int  DB_OK on success.  A DB_Error object on failure.
595      */
596     function commit()
597     {
598         if ($this->transaction_opcount > 0) {
599             // (disabled) hack to shut up error messages from libpq.a
600             //@fclose(@fopen("php://stderr", "w"));
601             $result = @pg_exec($this->connection, 'end;');
602             $this->transaction_opcount = 0;
603             if (!$result) {
604                 return $this->pgsqlRaiseError();
605             }
606         }
607         return DB_OK;
608     }
609
610     // }}}
611     // {{{ rollback()
612
613     /**
614      * Reverts the current transaction
615      *
616      * @return int  DB_OK on success.  A DB_Error object on failure.
617      */
618     function rollback()
619     {
620         if ($this->transaction_opcount > 0) {
621             $result = @pg_exec($this->connection, 'abort;');
622             $this->transaction_opcount = 0;
623             if (!$result) {
624                 return $this->pgsqlRaiseError();
625             }
626         }
627         return DB_OK;
628     }
629
630     // }}}
631     // {{{ affectedRows()
632
633     /**
634      * Determines the number of rows affected by a data maniuplation query
635      *
636      * 0 is returned for queries that don't manipulate data.
637      *
638      * @return int  the number of rows.  A DB_Error object on failure.
639      */
640     function affectedRows()
641     {
642         return $this->affected;
643     }
644
645     // }}}
646     // {{{ nextId()
647
648     /**
649      * Returns the next free id in a sequence
650      *
651      * @param string  $seq_name  name of the sequence
652      * @param boolean $ondemand  when true, the seqence is automatically
653      *                            created if it does not exist
654      *
655      * @return int  the next id number in the sequence.
656      *               A DB_Error object on failure.
657      *
658      * @see DB_common::nextID(), DB_common::getSequenceName(),
659      *      DB_pgsql::createSequence(), DB_pgsql::dropSequence()
660      */
661     function nextId($seq_name, $ondemand = true)
662     {
663         $seqname = $this->getSequenceName($seq_name);
664         $repeat = false;
665         do {
666             $this->pushErrorHandling(PEAR_ERROR_RETURN);
667             $result = $this->query("SELECT NEXTVAL('${seqname}')");
668             $this->popErrorHandling();
669             if ($ondemand && DB::isError($result) &&
670                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
671                 $repeat = true;
672                 $this->pushErrorHandling(PEAR_ERROR_RETURN);
673                 $result = $this->createSequence($seq_name);
674                 $this->popErrorHandling();
675                 if (DB::isError($result)) {
676                     return $this->raiseError($result);
677                 }
678             } else {
679                 $repeat = false;
680             }
681         } while ($repeat);
682         if (DB::isError($result)) {
683             return $this->raiseError($result);
684         }
685         $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
686         $result->free();
687         return $arr[0];
688     }
689
690     // }}}
691     // {{{ createSequence()
692
693     /**
694      * Creates a new sequence
695      *
696      * @param string $seq_name  name of the new sequence
697      *
698      * @return int  DB_OK on success.  A DB_Error object on failure.
699      *
700      * @see DB_common::createSequence(), DB_common::getSequenceName(),
701      *      DB_pgsql::nextID(), DB_pgsql::dropSequence()
702      */
703     function createSequence($seq_name)
704     {
705         $seqname = $this->getSequenceName($seq_name);
706         $result = $this->query("CREATE SEQUENCE ${seqname}");
707         return $result;
708     }
709
710     // }}}
711     // {{{ dropSequence()
712
713     /**
714      * Deletes a sequence
715      *
716      * @param string $seq_name  name of the sequence to be deleted
717      *
718      * @return int  DB_OK on success.  A DB_Error object on failure.
719      *
720      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
721      *      DB_pgsql::nextID(), DB_pgsql::createSequence()
722      */
723     function dropSequence($seq_name)
724     {
725         return $this->query('DROP SEQUENCE '
726                             . $this->getSequenceName($seq_name));
727     }
728
729     // }}}
730     // {{{ modifyLimitQuery()
731
732     /**
733      * Adds LIMIT clauses to a query string according to current DBMS standards
734      *
735      * @param string $query   the query to modify
736      * @param int    $from    the row to start to fetching (0 = the first row)
737      * @param int    $count   the numbers of rows to fetch
738      * @param mixed  $params  array, string or numeric data to be used in
739      *                         execution of the statement.  Quantity of items
740      *                         passed must match quantity of placeholders in
741      *                         query:  meaning 1 placeholder for non-array
742      *                         parameters or 1 placeholder per array element.
743      *
744      * @return string  the query string with LIMIT clauses added
745      *
746      * @access protected
747      */
748     function modifyLimitQuery($query, $from, $count, $params = array())
749     {
750         return "$query LIMIT $count OFFSET $from";
751     }
752
753     // }}}
754     // {{{ pgsqlRaiseError()
755
756     /**
757      * Produces a DB_Error object regarding the current problem
758      *
759      * @param int $errno  if the error is being manually raised pass a
760      *                     DB_ERROR* constant here.  If this isn't passed
761      *                     the error information gathered from the DBMS.
762      *
763      * @return object  the DB_Error object
764      *
765      * @see DB_common::raiseError(),
766      *      DB_pgsql::errorNative(), DB_pgsql::errorCode()
767      */
768     function pgsqlRaiseError($errno = null)
769     {
770         $native = $this->errorNative();
771         if (!$native) {
772             $native = 'Database connection has been lost.';
773             $errno = DB_ERROR_CONNECT_FAILED;
774         }
775         if ($errno === null) {
776             $errno = $this->errorCode($native);
777         }
778         return $this->raiseError($errno, null, null, null, $native);
779     }
780
781     // }}}
782     // {{{ errorNative()
783
784     /**
785      * Gets the DBMS' native error message produced by the last query
786      *
787      * {@internal Error messages are used instead of error codes 
788      * in order to support older versions of PostgreSQL.}}
789      *
790      * @return string  the DBMS' error message
791      */
792     function errorNative()
793     {
794         return @pg_errormessage($this->connection);
795     }
796
797     // }}}
798     // {{{ errorCode()
799
800     /**
801      * Determines PEAR::DB error code from the database's text error message.
802      *
803      * @param  string  $errormsg  error message returned from the database
804      * @return integer  an error number from a DB error constant
805      */
806     function errorCode($errormsg)
807     {
808         static $error_regexps;
809         if (!isset($error_regexps)) {
810             $error_regexps = array(
811                 '/column .* (of relation .*)?does not exist/i'
812                     => DB_ERROR_NOSUCHFIELD,
813                 '/(relation|sequence|table).*does not exist|class .* not found/i'
814                     => DB_ERROR_NOSUCHTABLE,
815                 '/index .* does not exist/'
816                     => DB_ERROR_NOT_FOUND,
817                 '/relation .* already exists/i'
818                     => DB_ERROR_ALREADY_EXISTS,
819                 '/(divide|division) by zero$/i'
820                     => DB_ERROR_DIVZERO,
821                 '/pg_atoi: error in .*: can\'t parse /i'
822                     => DB_ERROR_INVALID_NUMBER,
823                 '/invalid input syntax for( type)? (integer|numeric)/i'
824                     => DB_ERROR_INVALID_NUMBER,
825                 '/value .* is out of range for type \w*int/i'
826                     => DB_ERROR_INVALID_NUMBER,
827                 '/integer out of range/i'
828                     => DB_ERROR_INVALID_NUMBER,
829                 '/value too long for type character/i'
830                     => DB_ERROR_INVALID,
831                 '/attribute .* not found|relation .* does not have attribute/i'
832                     => DB_ERROR_NOSUCHFIELD,
833                 '/column .* specified in USING clause does not exist in (left|right) table/i'
834                     => DB_ERROR_NOSUCHFIELD,
835                 '/parser: parse error at or near/i'
836                     => DB_ERROR_SYNTAX,
837                 '/syntax error at/'
838                     => DB_ERROR_SYNTAX,
839                 '/column reference .* is ambiguous/i'
840                     => DB_ERROR_SYNTAX,
841                 '/permission denied/'
842                     => DB_ERROR_ACCESS_VIOLATION,
843                 '/violates not-null constraint/'
844                     => DB_ERROR_CONSTRAINT_NOT_NULL,
845                 '/violates [\w ]+ constraint/'
846                     => DB_ERROR_CONSTRAINT,
847                 '/referential integrity violation/'
848                     => DB_ERROR_CONSTRAINT,
849                 '/more expressions than target columns/i'
850                     => DB_ERROR_VALUE_COUNT_ON_ROW,
851             );
852         }
853         foreach ($error_regexps as $regexp => $code) {
854             if (preg_match($regexp, $errormsg)) {
855                 return $code;
856             }
857         }
858         // Fall back to DB_ERROR if there was no mapping.
859         return DB_ERROR;
860     }
861
862     // }}}
863     // {{{ tableInfo()
864
865     /**
866      * Returns information about a table or a result set
867      *
868      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
869      * is a table name.
870      *
871      * @param object|string  $result  DB_result object from a query or a
872      *                                 string containing the name of a table.
873      *                                 While this also accepts a query result
874      *                                 resource identifier, this behavior is
875      *                                 deprecated.
876      * @param int            $mode    a valid tableInfo mode
877      *
878      * @return array  an associative array with the information requested.
879      *                 A DB_Error object on failure.
880      *
881      * @see DB_common::tableInfo()
882      */
883     function tableInfo($result, $mode = null)
884     {
885         if (is_string($result)) {
886             /*
887              * Probably received a table name.
888              * Create a result resource identifier.
889              */
890             $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
891             $got_string = true;
892         } elseif (isset($result->result)) {
893             /*
894              * Probably received a result object.
895              * Extract the result resource identifier.
896              */
897             $id = $result->result;
898             $got_string = false;
899         } else {
900             /*
901              * Probably received a result resource identifier.
902              * Copy it.
903              * Deprecated.  Here for compatibility only.
904              */
905             $id = $result;
906             $got_string = false;
907         }
908
909         if (!is_resource($id)) {
910             return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
911         }
912
913         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
914             $case_func = 'strtolower';
915         } else {
916             $case_func = 'strval';
917         }
918
919         $count = @pg_numfields($id);
920         $res   = array();
921
922         if ($mode) {
923             $res['num_fields'] = $count;
924         }
925
926         for ($i = 0; $i < $count; $i++) {
927             $res[$i] = array(
928                 'table' => $got_string ? $case_func($result) : '',
929                 'name'  => $case_func(@pg_fieldname($id, $i)),
930                 'type'  => @pg_fieldtype($id, $i),
931                 'len'   => @pg_fieldsize($id, $i),
932                 'flags' => $got_string
933                            ? $this->_pgFieldFlags($id, $i, $result)
934                            : '',
935             );
936             if ($mode & DB_TABLEINFO_ORDER) {
937                 $res['order'][$res[$i]['name']] = $i;
938             }
939             if ($mode & DB_TABLEINFO_ORDERTABLE) {
940                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
941             }
942         }
943
944         // free the result only if we were called on a table
945         if ($got_string) {
946             @pg_freeresult($id);
947         }
948         return $res;
949     }
950
951     // }}}
952     // {{{ _pgFieldFlags()
953
954     /**
955      * Get a column's flags
956      *
957      * Supports "not_null", "default_value", "primary_key", "unique_key"
958      * and "multiple_key".  The default value is passed through
959      * rawurlencode() in case there are spaces in it.
960      *
961      * @param int $resource   the PostgreSQL result identifier
962      * @param int $num_field  the field number
963      *
964      * @return string  the flags
965      *
966      * @access private
967      */
968     function _pgFieldFlags($resource, $num_field, $table_name)
969     {
970         $field_name = @pg_fieldname($resource, $num_field);
971
972         // Check if there's a schema in $table_name and update things
973         // accordingly.
974         $from = 'pg_attribute f, pg_class tab, pg_type typ';
975         if (strpos($table_name, '.') !== false) {
976             $from .= ', pg_namespace nsp';
977             list($schema, $table) = explode('.', $table_name);
978             $tableWhere = "tab.relname = '$table' AND tab.relnamespace = nsp.oid AND nsp.nspname = '$schema'";
979         } else {
980             $tableWhere = "tab.relname = '$table_name'";
981         }
982
983         $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
984                                 FROM $from
985                                 WHERE tab.relname = typ.typname
986                                 AND typ.typrelid = f.attrelid
987                                 AND f.attname = '$field_name'
988                                 AND $tableWhere");
989         if (@pg_numrows($result) > 0) {
990             $row = @pg_fetch_row($result, 0);
991             $flags  = ($row[0] == 't') ? 'not_null ' : '';
992
993             if ($row[1] == 't') {
994                 $result = @pg_exec($this->connection, "SELECT a.adsrc
995                                     FROM $from, pg_attrdef a
996                                     WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
997                                     AND f.attrelid = a.adrelid AND f.attname = '$field_name'
998                                     AND $tableWhere AND f.attnum = a.adnum");
999                 $row = @pg_fetch_row($result, 0);
1000                 $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
1001                 $flags .= 'default_' . rawurlencode($num) . ' ';
1002             }
1003         } else {
1004             $flags = '';
1005         }
1006         $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
1007                                 FROM $from, pg_index i
1008                                 WHERE tab.relname = typ.typname
1009                                 AND typ.typrelid = f.attrelid
1010                                 AND f.attrelid = i.indrelid
1011                                 AND f.attname = '$field_name'
1012                                 AND $tableWhere");
1013         $count = @pg_numrows($result);
1014
1015         for ($i = 0; $i < $count ; $i++) {
1016             $row = @pg_fetch_row($result, $i);
1017             $keys = explode(' ', $row[2]);
1018
1019             if (in_array($num_field + 1, $keys)) {
1020                 $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
1021                 $flags .= ($row[1] == 't') ? 'primary_key ' : '';
1022                 if (count($keys) > 1)
1023                     $flags .= 'multiple_key ';
1024             }
1025         }
1026
1027         return trim($flags);
1028     }
1029
1030     // }}}
1031     // {{{ getSpecialQuery()
1032
1033     /**
1034      * Obtains the query string needed for listing a given type of objects
1035      *
1036      * @param string $type  the kind of objects you want to retrieve
1037      *
1038      * @return string  the SQL query string or null if the driver doesn't
1039      *                  support the object type requested
1040      *
1041      * @access protected
1042      * @see DB_common::getListOf()
1043      */
1044     function getSpecialQuery($type)
1045     {
1046         switch ($type) {
1047             case 'tables':
1048                 return 'SELECT c.relname AS "Name"'
1049                         . ' FROM pg_class c, pg_user u'
1050                         . ' WHERE c.relowner = u.usesysid'
1051                         . " AND c.relkind = 'r'"
1052                         . ' AND NOT EXISTS'
1053                         . ' (SELECT 1 FROM pg_views'
1054                         . '  WHERE viewname = c.relname)'
1055                         . " AND c.relname !~ '^(pg_|sql_)'"
1056                         . ' UNION'
1057                         . ' SELECT c.relname AS "Name"'
1058                         . ' FROM pg_class c'
1059                         . " WHERE c.relkind = 'r'"
1060                         . ' AND NOT EXISTS'
1061                         . ' (SELECT 1 FROM pg_views'
1062                         . '  WHERE viewname = c.relname)'
1063                         . ' AND NOT EXISTS'
1064                         . ' (SELECT 1 FROM pg_user'
1065                         . '  WHERE usesysid = c.relowner)'
1066                         . " AND c.relname !~ '^pg_'";
1067             case 'schema.tables':
1068                 return "SELECT schemaname || '.' || tablename"
1069                         . ' AS "Name"'
1070                         . ' FROM pg_catalog.pg_tables'
1071                         . ' WHERE schemaname NOT IN'
1072                         . " ('pg_catalog', 'information_schema', 'pg_toast')";
1073             case 'schema.views':
1074                 return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname"
1075                         . " NOT IN ('information_schema', 'pg_catalog')";
1076             case 'views':
1077                 // Table cols: viewname | viewowner | definition
1078                 return 'SELECT viewname from pg_views WHERE schemaname'
1079                         . " NOT IN ('information_schema', 'pg_catalog')";
1080             case 'users':
1081                 // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd  |valuntil
1082                 return 'SELECT usename FROM pg_user';
1083             case 'databases':
1084                 return 'SELECT datname FROM pg_database';
1085             case 'functions':
1086             case 'procedures':
1087                 return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
1088             default:
1089                 return null;
1090         }
1091     }
1092
1093     // }}}
1094     // {{{ _checkManip()
1095
1096     /**
1097      * Checks if the given query is a manipulation query. This also takes into
1098      * account the _next_query_manip flag and sets the _last_query_manip flag
1099      * (and resets _next_query_manip) according to the result.
1100      *
1101      * @param string The query to check.
1102      *
1103      * @return boolean true if the query is a manipulation query, false
1104      * otherwise
1105      *
1106      * @access protected
1107      */
1108     function _checkManip($query)
1109     {
1110         return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query)
1111                 || parent::_checkManip($query));
1112     }
1113
1114 }
1115
1116 /*
1117  * Local variables:
1118  * tab-width: 4
1119  * c-basic-offset: 4
1120  * End:
1121  */
1122
1123 ?>