svncommit
2008-09-18 d0b973cf6aed4a7cb705f706624d25b31d19ed52
commit | author | age
95ebbc 1 <?php
T 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
5 // +----------------------------------------------------------------------+
d1403f 6 // | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
95ebbc 7 // | Stig. S. Bakken, Lukas Smith                                         |
T 8 // | All rights reserved.                                                 |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
11 // | API as well as database abstraction for PHP applications.            |
12 // | This LICENSE is in the BSD license style.                            |
13 // |                                                                      |
14 // | Redistribution and use in source and binary forms, with or without   |
15 // | modification, are permitted provided that the following conditions   |
16 // | are met:                                                             |
17 // |                                                                      |
18 // | Redistributions of source code must retain the above copyright       |
19 // | notice, this list of conditions and the following disclaimer.        |
20 // |                                                                      |
21 // | Redistributions in binary form must reproduce the above copyright    |
22 // | notice, this list of conditions and the following disclaimer in the  |
23 // | documentation and/or other materials provided with the distribution. |
24 // |                                                                      |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission.                                                  |
29 // |                                                                      |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41 // | POSSIBILITY OF SUCH DAMAGE.                                          |
42 // +----------------------------------------------------------------------+
43 // | Author: Paul Cooper <pgc@ucecom.com>                                 |
44 // +----------------------------------------------------------------------+
45 //
d1403f 46 // $Id: pgsql.php,v 1.197 2008/03/08 14:18:39 quipo Exp $
95ebbc 47
T 48 /**
49  * MDB2 PostGreSQL driver
50  *
51  * @package MDB2
52  * @category Database
53  * @author  Paul Cooper <pgc@ucecom.com>
54  */
55 class MDB2_Driver_pgsql extends MDB2_Driver_Common
56 {
57     // {{{ properties
58     var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '\\');
59
60     var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"');
61     // }}}
62     // {{{ constructor
63
64     /**
65      * Constructor
66      */
67     function __construct()
68     {
69         parent::__construct();
70
71         $this->phptype = 'pgsql';
72         $this->dbsyntax = 'pgsql';
73
74         $this->supported['sequences'] = true;
75         $this->supported['indexes'] = true;
76         $this->supported['affected_rows'] = true;
77         $this->supported['summary_functions'] = true;
78         $this->supported['order_by_text'] = true;
79         $this->supported['transactions'] = true;
80         $this->supported['savepoints'] = true;
81         $this->supported['current_id'] = true;
82         $this->supported['limit_queries'] = true;
83         $this->supported['LOBs'] = true;
84         $this->supported['replace'] = 'emulated';
85         $this->supported['sub_selects'] = true;
d1403f 86         $this->supported['triggers'] = true;
95ebbc 87         $this->supported['auto_increment'] = 'emulated';
T 88         $this->supported['primary_key'] = true;
89         $this->supported['result_introspection'] = true;
90         $this->supported['prepared_statements'] = true;
91         $this->supported['identifier_quoting'] = true;
92         $this->supported['pattern_escaping'] = true;
93         $this->supported['new_link'] = true;
94
d1403f 95         $this->options['DBA_username'] = false;
A 96         $this->options['DBA_password'] = false;
95ebbc 97         $this->options['multi_query'] = false;
T 98         $this->options['disable_smart_seqname'] = false;
d1403f 99         $this->options['max_identifiers_length'] = 63;
95ebbc 100     }
T 101
102     // }}}
103     // {{{ errorInfo()
104
105     /**
106      * This method is used to collect information about an error
107      *
108      * @param integer $error
109      * @return array
110      * @access public
111      */
112     function errorInfo($error = null)
113     {
114         // Fall back to MDB2_ERROR if there was no mapping.
115         $error_code = MDB2_ERROR;
116
117         $native_msg = '';
118         if (is_resource($error)) {
119             $native_msg = @pg_result_error($error);
120         } elseif ($this->connection) {
121             $native_msg = @pg_last_error($this->connection);
122             if (!$native_msg && @pg_connection_status($this->connection) === PGSQL_CONNECTION_BAD) {
123                 $native_msg = 'Database connection has been lost.';
124                 $error_code = MDB2_ERROR_CONNECT_FAILED;
125             }
d1403f 126         } else {
A 127             $native_msg = @pg_last_error();
95ebbc 128         }
T 129
130         static $error_regexps;
131         if (empty($error_regexps)) {
132             $error_regexps = array(
133                 '/column .* (of relation .*)?does not exist/i'
134                     => MDB2_ERROR_NOSUCHFIELD,
135                 '/(relation|sequence|table).*does not exist|class .* not found/i'
136                     => MDB2_ERROR_NOSUCHTABLE,
d1403f 137                 '/database .* does not exist/'
A 138                     => MDB2_ERROR_NOT_FOUND,
95ebbc 139                 '/index .* does not exist/'
T 140                     => MDB2_ERROR_NOT_FOUND,
d1403f 141                 '/database .* already exists/i'
A 142                     => MDB2_ERROR_ALREADY_EXISTS,
95ebbc 143                 '/relation .* already exists/i'
T 144                     => MDB2_ERROR_ALREADY_EXISTS,
145                 '/(divide|division) by zero$/i'
146                     => MDB2_ERROR_DIVZERO,
147                 '/pg_atoi: error in .*: can\'t parse /i'
148                     => MDB2_ERROR_INVALID_NUMBER,
149                 '/invalid input syntax for( type)? (integer|numeric)/i'
150                     => MDB2_ERROR_INVALID_NUMBER,
151                 '/value .* is out of range for type \w*int/i'
152                     => MDB2_ERROR_INVALID_NUMBER,
153                 '/integer out of range/i'
154                     => MDB2_ERROR_INVALID_NUMBER,
155                 '/value too long for type character/i'
156                     => MDB2_ERROR_INVALID,
157                 '/attribute .* not found|relation .* does not have attribute/i'
158                     => MDB2_ERROR_NOSUCHFIELD,
159                 '/column .* specified in USING clause does not exist in (left|right) table/i'
160                     => MDB2_ERROR_NOSUCHFIELD,
161                 '/parser: parse error at or near/i'
162                     => MDB2_ERROR_SYNTAX,
163                 '/syntax error at/'
164                     => MDB2_ERROR_SYNTAX,
165                 '/column reference .* is ambiguous/i'
166                     => MDB2_ERROR_SYNTAX,
167                 '/permission denied/'
168                     => MDB2_ERROR_ACCESS_VIOLATION,
169                 '/violates not-null constraint/'
170                     => MDB2_ERROR_CONSTRAINT_NOT_NULL,
171                 '/violates [\w ]+ constraint/'
172                     => MDB2_ERROR_CONSTRAINT,
173                 '/referential integrity violation/'
174                     => MDB2_ERROR_CONSTRAINT,
175                 '/more expressions than target columns/i'
176                     => MDB2_ERROR_VALUE_COUNT_ON_ROW,
177             );
178         }
179         if (is_numeric($error) && $error < 0) {
180             $error_code = $error;
181         } else {
182             foreach ($error_regexps as $regexp => $code) {
183                 if (preg_match($regexp, $native_msg)) {
184                     $error_code = $code;
185                     break;
186                 }
187             }
188         }
189         return array($error_code, null, $native_msg);
190     }
191
192     // }}}
193     // {{{ escape()
194
195     /**
196      * Quotes a string so it can be safely used in a query. It will quote
197      * the text so it can safely be used within a query.
198      *
199      * @param   string  the input string to quote
200      * @param   bool    escape wildcards
201      *
202      * @return  string  quoted string
203      *
204      * @access  public
205      */
206     function escape($text, $escape_wildcards = false)
207     {
208         if ($escape_wildcards) {
209             $text = $this->escapePattern($text);
210         }
211         $connection = $this->getConnection();
212         if (PEAR::isError($connection)) {
213             return $connection;
214         }
d1403f 215         if (is_resource($connection) && version_compare(PHP_VERSION, '5.2.0RC5', '>=')) {
95ebbc 216             $text = @pg_escape_string($connection, $text);
T 217         } else {
218             $text = @pg_escape_string($text);
219         }
220         return $text;
221     }
222
223     // }}}
224     // {{{ beginTransaction()
225
226     /**
227      * Start a transaction or set a savepoint.
228      *
229      * @param   string  name of a savepoint to set
230      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
231      *
232      * @access  public
233      */
234     function beginTransaction($savepoint = null)
235     {
236         $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
237         if (!is_null($savepoint)) {
238             if (!$this->in_transaction) {
239                 return $this->raiseError(MDB2_ERROR_INVALID, null, null,
240                     'savepoint cannot be released when changes are auto committed', __FUNCTION__);
241             }
242             $query = 'SAVEPOINT '.$savepoint;
243             return $this->_doQuery($query, true);
244         } elseif ($this->in_transaction) {
245             return MDB2_OK;  //nothing to do
246         }
247         if (!$this->destructor_registered && $this->opened_persistent) {
248             $this->destructor_registered = true;
249             register_shutdown_function('MDB2_closeOpenTransactions');
250         }
251         $result =& $this->_doQuery('BEGIN', true);
252         if (PEAR::isError($result)) {
253             return $result;
254         }
255         $this->in_transaction = true;
256         return MDB2_OK;
257     }
258
259     // }}}
260     // {{{ commit()
261
262     /**
263      * Commit the database changes done during a transaction that is in
264      * progress or release a savepoint. This function may only be called when
265      * auto-committing is disabled, otherwise it will fail. Therefore, a new
266      * transaction is implicitly started after committing the pending changes.
267      *
268      * @param   string  name of a savepoint to release
269      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
270      *
271      * @access  public
272      */
273     function commit($savepoint = null)
274     {
275         $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
276         if (!$this->in_transaction) {
277             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
278                 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
279         }
280         if (!is_null($savepoint)) {
281             $query = 'RELEASE SAVEPOINT '.$savepoint;
282             return $this->_doQuery($query, true);
283         }
284
285         $result =& $this->_doQuery('COMMIT', true);
286         if (PEAR::isError($result)) {
287             return $result;
288         }
289         $this->in_transaction = false;
290         return MDB2_OK;
291     }
292
293     // }}}
294     // {{{ rollback()
295
296     /**
297      * Cancel any database changes done during a transaction or since a specific
298      * savepoint that is in progress. This function may only be called when
299      * auto-committing is disabled, otherwise it will fail. Therefore, a new
300      * transaction is implicitly started after canceling the pending changes.
301      *
302      * @param   string  name of a savepoint to rollback to
303      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
304      *
305      * @access  public
306      */
307     function rollback($savepoint = null)
308     {
309         $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
310         if (!$this->in_transaction) {
311             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
312                 'rollback cannot be done changes are auto committed', __FUNCTION__);
313         }
314         if (!is_null($savepoint)) {
315             $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
316             return $this->_doQuery($query, true);
317         }
318
319         $query = 'ROLLBACK';
320         $result =& $this->_doQuery($query, true);
321         if (PEAR::isError($result)) {
322             return $result;
323         }
324         $this->in_transaction = false;
325         return MDB2_OK;
326     }
327
328     // }}}
329     // {{{ function setTransactionIsolation()
330
331     /**
332      * Set the transacton isolation level.
333      *
334      * @param   string  standard isolation level
335      *                  READ UNCOMMITTED (allows dirty reads)
336      *                  READ COMMITTED (prevents dirty reads)
337      *                  REPEATABLE READ (prevents nonrepeatable reads)
338      *                  SERIALIZABLE (prevents phantom reads)
339      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
340      *
341      * @access  public
342      * @since   2.1.1
343      */
344     function setTransactionIsolation($isolation)
345     {
346         $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
347         switch ($isolation) {
348         case 'READ UNCOMMITTED':
349         case 'READ COMMITTED':
350         case 'REPEATABLE READ':
351         case 'SERIALIZABLE':
352             break;
353         default:
354             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
355                 'isolation level is not supported: '.$isolation, __FUNCTION__);
356         }
357
358         $query = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL $isolation";
359         return $this->_doQuery($query, true);
360     }
361
362     // }}}
363     // {{{ _doConnect()
364
365     /**
d1403f 366      * Do the grunt work of connecting to the database
95ebbc 367      *
T 368      * @return mixed connection resource on success, MDB2 Error Object on failure
369      * @access protected
d1403f 370      */
A 371     function _doConnect($username, $password, $database_name, $persistent = false)
95ebbc 372     {
d1403f 373         if (!PEAR::loadExtension($this->phptype)) {
A 374             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
375                 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
376         }
377         
95ebbc 378         if ($database_name == '') {
T 379             $database_name = 'template1';
380         }
381
382         $protocol = $this->dsn['protocol'] ? $this->dsn['protocol'] : 'tcp';
383
384         $params = array('');
385         if ($protocol == 'tcp') {
386             if ($this->dsn['hostspec']) {
387                 $params[0].= 'host=' . $this->dsn['hostspec'];
388             }
389             if ($this->dsn['port']) {
390                 $params[0].= ' port=' . $this->dsn['port'];
391             }
392         } elseif ($protocol == 'unix') {
393             // Allow for pg socket in non-standard locations.
394             if ($this->dsn['socket']) {
395                 $params[0].= 'host=' . $this->dsn['socket'];
396             }
397             if ($this->dsn['port']) {
398                 $params[0].= ' port=' . $this->dsn['port'];
399             }
400         }
401         if ($database_name) {
402             $params[0].= ' dbname=\'' . addslashes($database_name) . '\'';
403         }
d1403f 404         if ($username) {
A 405             $params[0].= ' user=\'' . addslashes($username) . '\'';
95ebbc 406         }
d1403f 407         if ($password) {
A 408             $params[0].= ' password=\'' . addslashes($password) . '\'';
95ebbc 409         }
T 410         if (!empty($this->dsn['options'])) {
411             $params[0].= ' options=' . $this->dsn['options'];
412         }
413         if (!empty($this->dsn['tty'])) {
414             $params[0].= ' tty=' . $this->dsn['tty'];
415         }
416         if (!empty($this->dsn['connect_timeout'])) {
417             $params[0].= ' connect_timeout=' . $this->dsn['connect_timeout'];
418         }
419         if (!empty($this->dsn['sslmode'])) {
420             $params[0].= ' sslmode=' . $this->dsn['sslmode'];
421         }
422         if (!empty($this->dsn['service'])) {
423             $params[0].= ' service=' . $this->dsn['service'];
424         }
425
426         if (!empty($this->dsn['new_link'])
427             && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true))
428         {
429             if (version_compare(phpversion(), '4.3.0', '>=')) {
430                 $params[] = PGSQL_CONNECT_FORCE_NEW;
431             }
432         }
433
434         $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
435         $connection = @call_user_func_array($connect_function, $params);
436         if (!$connection) {
437             return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
438                 'unable to establish a connection', __FUNCTION__);
439         }
440
441        if (empty($this->dsn['disable_iso_date'])) {
442             if (!@pg_query($connection, "SET SESSION DATESTYLE = 'ISO'")) {
443                 return $this->raiseError(null, null, null,
444                     'Unable to set date style to iso', __FUNCTION__);
445             }
446        }
447
448         if (!empty($this->dsn['charset'])) {
449             $result = $this->setCharset($this->dsn['charset'], $connection);
450             if (PEAR::isError($result)) {
451                 return $result;
452             }
453         }
454
455         return $connection;
456     }
457
458     // }}}
459     // {{{ connect()
460
461     /**
462      * Connect to the database
463      *
464      * @return true on success, MDB2 Error Object on failure
465      * @access public
d1403f 466      */
95ebbc 467     function connect()
T 468     {
469         if (is_resource($this->connection)) {
470             //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
471             if (MDB2::areEquals($this->connected_dsn, $this->dsn)
472                 && $this->connected_database_name == $this->database_name
473                 && ($this->opened_persistent == $this->options['persistent'])
474             ) {
475                 return MDB2_OK;
476             }
477             $this->disconnect(false);
478         }
479
480         if ($this->database_name) {
d1403f 481             $connection = $this->_doConnect($this->dsn['username'],
A 482                                             $this->dsn['password'],
483                                             $this->database_name,
484                                             $this->options['persistent']);
95ebbc 485             if (PEAR::isError($connection)) {
T 486                 return $connection;
487             }
d1403f 488
95ebbc 489             $this->connection = $connection;
T 490             $this->connected_dsn = $this->dsn;
491             $this->connected_database_name = $this->database_name;
492             $this->opened_persistent = $this->options['persistent'];
493             $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
494         }
d1403f 495
95ebbc 496         return MDB2_OK;
T 497     }
498
499     // }}}
500     // {{{ setCharset()
501
502     /**
503      * Set the charset on the current connection
504      *
505      * @param string    charset
506      * @param resource  connection handle
507      *
508      * @return true on success, MDB2 Error Object on failure
509      */
510     function setCharset($charset, $connection = null)
511     {
512         if (is_null($connection)) {
513             $connection = $this->getConnection();
514             if (PEAR::isError($connection)) {
515                 return $connection;
516             }
517         }
d1403f 518         if (is_array($charset)) {
A 519             $charset   = array_shift($charset);
520             $this->warnings[] = 'postgresql does not support setting client collation';
521         }
95ebbc 522         $result = @pg_set_client_encoding($connection, $charset);
T 523         if ($result == -1) {
524             return $this->raiseError(null, null, null,
525                 'Unable to set client charset: '.$charset, __FUNCTION__);
526         }
527         return MDB2_OK;
d1403f 528     }
A 529
530     // }}}
531     // {{{ databaseExists()
532
533     /**
534      * check if given database name is exists?
535      *
536      * @param string $name    name of the database that should be checked
537      *
538      * @return mixed true/false on success, a MDB2 error on failure
539      * @access public
540      */
541     function databaseExists($name)
542     {
543         $res = $this->_doConnect($this->dsn['username'],
544                                  $this->dsn['password'],
545                                  $this->escape($name),
546                                  $this->options['persistent']);
547         if (!PEAR::isError($res)) {
548             return true;
549         }
550
551         return false;
95ebbc 552     }
T 553
554     // }}}
555     // {{{ disconnect()
556
557     /**
558      * Log out and disconnect from the database.
559      *
560      * @param  boolean $force if the disconnect should be forced even if the
561      *                        connection is opened persistently
562      * @return mixed true on success, false if not connected and error
563      *                object on error
564      * @access public
565      */
566     function disconnect($force = true)
567     {
568         if (is_resource($this->connection)) {
569             if ($this->in_transaction) {
570                 $dsn = $this->dsn;
571                 $database_name = $this->database_name;
572                 $persistent = $this->options['persistent'];
573                 $this->dsn = $this->connected_dsn;
574                 $this->database_name = $this->connected_database_name;
575                 $this->options['persistent'] = $this->opened_persistent;
576                 $this->rollback();
577                 $this->dsn = $dsn;
578                 $this->database_name = $database_name;
579                 $this->options['persistent'] = $persistent;
580             }
581
582             if (!$this->opened_persistent || $force) {
583                 @pg_close($this->connection);
584             }
585         }
586         return parent::disconnect($force);
587     }
588
589     // }}}
590     // {{{ standaloneQuery()
591
592    /**
593      * execute a query as DBA
594      *
595      * @param string $query the SQL query
596      * @param mixed   $types  array that contains the types of the columns in
597      *                        the result set
598      * @param boolean $is_manip  if the query is a manipulation query
599      * @return mixed MDB2_OK on success, a MDB2 error on failure
600      * @access public
601      */
602     function &standaloneQuery($query, $types = null, $is_manip = false)
603     {
d1403f 604         $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
A 605         $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
606         $connection = $this->_doConnect($user, $pass, $this->database_name, $this->options['persistent']);
95ebbc 607         if (PEAR::isError($connection)) {
d1403f 608             return $connection;
95ebbc 609         }
T 610
611         $offset = $this->offset;
612         $limit = $this->limit;
613         $this->offset = $this->limit = 0;
614         $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
615
d1403f 616         $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name);
A 617         if (!PEAR::isError($result)) {
618             if ($is_manip) {
619                 $result =  $this->_affectedRows($connection, $result);
620             } else {
621                 $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset);
622             }
95ebbc 623         }
T 624
d1403f 625         @pg_close($connection);
95ebbc 626         return $result;
T 627     }
628
629     // }}}
630     // {{{ _doQuery()
631
632     /**
633      * Execute a query
634      * @param string $query  query
635      * @param boolean $is_manip  if the query is a manipulation query
636      * @param resource $connection
637      * @param string $database_name
638      * @return result or error object
639      * @access protected
640      */
641     function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
642     {
643         $this->last_query = $query;
644         $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
645         if ($result) {
646             if (PEAR::isError($result)) {
647                 return $result;
648             }
649             $query = $result;
650         }
651         if ($this->options['disable_query']) {
652             $result = $is_manip ? 0 : null;
653             return $result;
654         }
655
656         if (is_null($connection)) {
657             $connection = $this->getConnection();
658             if (PEAR::isError($connection)) {
659                 return $connection;
660             }
661         }
662
663         $function = $this->options['multi_query'] ? 'pg_send_query' : 'pg_query';
664         $result = @$function($connection, $query);
665         if (!$result) {
666             $err =& $this->raiseError(null, null, null,
667                 'Could not execute statement', __FUNCTION__);
668             return $err;
669         } elseif ($this->options['multi_query']) {
670             if (!($result = @pg_get_result($connection))) {
671                 $err =& $this->raiseError(null, null, null,
672                         'Could not get the first result from a multi query', __FUNCTION__);
673                 return $err;
674             }
675         }
676
677         $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
678         return $result;
679     }
680
681     // }}}
682     // {{{ _affectedRows()
683
684     /**
685      * Returns the number of rows affected
686      *
687      * @param resource $result
688      * @param resource $connection
689      * @return mixed MDB2 Error Object or the number of rows affected
690      * @access private
691      */
692     function _affectedRows($connection, $result = null)
693     {
694         if (is_null($connection)) {
695             $connection = $this->getConnection();
696             if (PEAR::isError($connection)) {
697                 return $connection;
698             }
699         }
700         return @pg_affected_rows($result);
701     }
702
703     // }}}
704     // {{{ _modifyQuery()
705
706     /**
707      * Changes a query string for various DBMS specific reasons
708      *
709      * @param string $query  query to modify
710      * @param boolean $is_manip  if it is a DML query
711      * @param integer $limit  limit the number of rows
712      * @param integer $offset  start reading from given offset
713      * @return string modified query
714      * @access protected
715      */
716     function _modifyQuery($query, $is_manip, $limit, $offset)
717     {
718         if ($limit > 0
719             && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
720         ) {
721             $query = rtrim($query);
722             if (substr($query, -1) == ';') {
723                 $query = substr($query, 0, -1);
724             }
725             if ($is_manip) {
726                 $query = $this->_modifyManipQuery($query, $limit);
727             } else {
728                 $query.= " LIMIT $limit OFFSET $offset";
729             }
730         }
731         return $query;
732     }
733     
734     // }}}
735     // {{{ _modifyManipQuery()
736     
737     /**
738      * Changes a manip query string for various DBMS specific reasons
739      *
740      * @param string $query  query to modify
741      * @param integer $limit  limit the number of rows
742      * @return string modified query
743      * @access protected
744      */
745     function _modifyManipQuery($query, $limit)
746     {
747         $pos = strpos(strtolower($query), 'where');
748         $where = $pos ? substr($query, $pos) : '';
749
750         $manip_clause = '(\bDELETE\b\s+(?:\*\s+)?\bFROM\b|\bUPDATE\b)';
751         $from_clause  = '([\w\.]+)';
752         $where_clause = '(?:(.*)\bWHERE\b\s+(.*))|(.*)';
753         $pattern = '/^'. $manip_clause . '\s+' . $from_clause .'(?:\s)*(?:'. $where_clause .')?$/i';
754         $matches = preg_match($pattern, $query, $match);
755         if ($matches) {
756             $manip = $match[1];
757             $from  = $match[2];
758             $what  = (count($matches) == 6) ? $match[5] : $match[3];
759             return $manip.' '.$from.' '.$what.' WHERE ctid=(SELECT ctid FROM '.$from.' '.$where.' LIMIT '.$limit.')';
760         }
761         //return error?
762         return $query;
763     }
764
765     // }}}
766     // {{{ getServerVersion()
767
768     /**
769      * return version information about the server
770      *
771      * @param bool   $native  determines if the raw version string should be returned
772      * @return mixed array/string with version information or MDB2 error object
773      * @access public
774      */
775     function getServerVersion($native = false)
776     {
777         $query = 'SHOW SERVER_VERSION';
778         if ($this->connected_server_info) {
779             $server_info = $this->connected_server_info;
780         } else {
781             $server_info = $this->queryOne($query, 'text');
782             if (PEAR::isError($server_info)) {
783                 return $server_info;
784             }
785         }
786         // cache server_info
787         $this->connected_server_info = $server_info;
788         if (!$native && !PEAR::isError($server_info)) {
789             $tmp = explode('.', $server_info, 3);
790             if (empty($tmp[2])
791                 && isset($tmp[1])
792                 && preg_match('/(\d+)(.*)/', $tmp[1], $tmp2)
793             ) {
794                 $server_info = array(
795                     'major' => $tmp[0],
796                     'minor' => $tmp2[1],
797                     'patch' => null,
798                     'extra' => $tmp2[2],
799                     'native' => $server_info,
800                 );
801             } else {
802                 $server_info = array(
803                     'major' => isset($tmp[0]) ? $tmp[0] : null,
804                     'minor' => isset($tmp[1]) ? $tmp[1] : null,
805                     'patch' => isset($tmp[2]) ? $tmp[2] : null,
806                     'extra' => null,
807                     'native' => $server_info,
808                 );
809             }
810         }
811         return $server_info;
812     }
813
814     // }}}
815     // {{{ prepare()
816
817     /**
818      * Prepares a query for multiple execution with execute().
819      * With some database backends, this is emulated.
820      * prepare() requires a generic query as string like
821      * 'INSERT INTO numbers VALUES(?,?)' or
822      * 'INSERT INTO numbers VALUES(:foo,:bar)'.
823      * The ? and :name and are placeholders which can be set using
824      * bindParam() and the query can be sent off using the execute() method.
825      * The allowed format for :name can be set with the 'bindname_format' option.
826      *
827      * @param string $query the query to prepare
828      * @param mixed   $types  array that contains the types of the placeholders
829      * @param mixed   $result_types  array that contains the types of the columns in
830      *                        the result set or MDB2_PREPARE_RESULT, if set to
831      *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
832      * @param mixed   $lobs   key (field) value (parameter) pair for all lob placeholders
833      * @return mixed resource handle for the prepared query on success, a MDB2
834      *        error on failure
835      * @access public
836      * @see bindParam, execute
837      */
838     function &prepare($query, $types = null, $result_types = null, $lobs = array())
839     {
840         if ($this->options['emulate_prepared']) {
841             $obj =& parent::prepare($query, $types, $result_types, $lobs);
842             return $obj;
843         }
844         $is_manip = ($result_types === MDB2_PREPARE_MANIP);
845         $offset = $this->offset;
846         $limit = $this->limit;
847         $this->offset = $this->limit = 0;
848         $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
849         if ($result) {
850             if (PEAR::isError($result)) {
851                 return $result;
852             }
853             $query = $result;
854         }
855         $pgtypes = function_exists('pg_prepare') ? false : array();
856         if ($pgtypes !== false && !empty($types)) {
857             $this->loadModule('Datatype', null, true);
858         }
859         $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
860         $placeholder_type_guess = $placeholder_type = null;
861         $question = '?';
862         $colon = ':';
863         $positions = array();
864         $position = $parameter = 0;
865         while ($position < strlen($query)) {
866             $q_position = strpos($query, $question, $position);
867             $c_position = strpos($query, $colon, $position);
868             //skip "::type" cast ("select id::varchar(20) from sometable where name=?")
869             $doublecolon_position = strpos($query, '::', $position);
870             if ($doublecolon_position !== false && $doublecolon_position == $c_position) {
871                 $c_position = strpos($query, $colon, $position+2);
872             }
873             if ($q_position && $c_position) {
874                 $p_position = min($q_position, $c_position);
875             } elseif ($q_position) {
876                 $p_position = $q_position;
877             } elseif ($c_position) {
878                 $p_position = $c_position;
879             } else {
880                 break;
881             }
882             if (is_null($placeholder_type)) {
883                 $placeholder_type_guess = $query[$p_position];
884             }
885             
886             $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
887             if (PEAR::isError($new_pos)) {
888                 return $new_pos;
889             }
890             if ($new_pos != $position) {
891                 $position = $new_pos;
892                 continue; //evaluate again starting from the new position
893             }
894
895             if ($query[$position] == $placeholder_type_guess) {
896                 if (is_null($placeholder_type)) {
897                     $placeholder_type = $query[$p_position];
898                     $question = $colon = $placeholder_type;
899                     if (!empty($types) && is_array($types)) {
900                         if ($placeholder_type == ':') {
901                         } else {
902                             $types = array_values($types);
903                         }
904                     }
905                 }
906                 if ($placeholder_type_guess == '?') {
907                     $length = 1;
908                     $name = $parameter;
909                 } else {
910                     $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
911                     $param = preg_replace($regexp, '\\1', $query);
912                     if ($param === '') {
913                         $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
914                             'named parameter name must match "bindname_format" option', __FUNCTION__);
915                         return $err;
916                     }
917                     $length = strlen($param) + 1;
918                     $name = $param;
919                 }
920                 if ($pgtypes !== false) {
921                     if (is_array($types) && array_key_exists($name, $types)) {
922                         $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$name]);
923                     } elseif (is_array($types) && array_key_exists($parameter, $types)) {
924                         $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$parameter]);
925                     } else {
926                         $pgtypes[] = 'text';
927                     }
928                 }
929                 if (($key_parameter = array_search($name, $positions))) {
930                     $next_parameter = 1;
931                     foreach ($positions as $key => $value) {
932                         if ($key_parameter == $key) {
933                             break;
934                         }
935                         ++$next_parameter;
936                     }
937                 } else {
938                     ++$parameter;
939                     $next_parameter = $parameter;
940                     $positions[] = $name;
941                 }
942                 $query = substr_replace($query, '$'.$parameter, $position, $length);
943                 $position = $p_position + strlen($parameter);
944             } else {
945                 $position = $p_position;
946             }
947         }
948         $connection = $this->getConnection();
949         if (PEAR::isError($connection)) {
950             return $connection;
951         }
952         static $prep_statement_counter = 1;
d1403f 953         $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand()));
A 954         $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']);
95ebbc 955         if ($pgtypes === false) {
T 956             $result = @pg_prepare($connection, $statement_name, $query);
957             if (!$result) {
958                 $err =& $this->raiseError(null, null, null,
959                     'Unable to create prepared statement handle', __FUNCTION__);
960                 return $err;
961             }
962         } else {
963             $types_string = '';
964             if ($pgtypes) {
965                 $types_string = ' ('.implode(', ', $pgtypes).') ';
966             }
967             $query = 'PREPARE '.$statement_name.$types_string.' AS '.$query;
968             $statement =& $this->_doQuery($query, true, $connection);
969             if (PEAR::isError($statement)) {
970                 return $statement;
971             }
972         }
973
974         $class_name = 'MDB2_Statement_'.$this->phptype;
975         $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
976         $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
977         return $obj;
978     }
979
980     // }}}
981     // {{{ function getSequenceName($sqn)
982
983     /**
984      * adds sequence name formatting to a sequence name
985      *
986      * @param   string  name of the sequence
987      *
988      * @return  string  formatted sequence name
989      *
990      * @access  public
991      */
992     function getSequenceName($sqn)
993     {
994         if (false === $this->options['disable_smart_seqname']) {
995             if (strpos($sqn, '_') !== false) {
996                 list($table, $field) = explode('_', $sqn, 2);
997             }
998             $schema_list = $this->queryOne("SELECT array_to_string(current_schemas(false), ',')");
999             if (PEAR::isError($schema_list) || empty($schema_list) || count($schema_list) < 2) {
1000                 $order_by = ' a.attnum';
1001                 $schema_clause = ' AND n.nspname=current_schema()';
1002             } else {
1003                 $schemas = explode(',', $schema_list);
1004                 $schema_clause = ' AND n.nspname IN ('.$schema_list.')';
1005                 $counter = 1;
1006                 $order_by = ' CASE ';
1007                 foreach ($schemas as $schema) {
1008                     $order_by .= ' WHEN n.nspname='.$schema.' THEN '.$counter++;
1009                 }
1010                 $order_by .= ' ELSE '.$counter.' END, a.attnum';
1011             }
1012
1013             $query = "SELECT substring((SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128)
1014                             FROM pg_attrdef d
1015                            WHERE d.adrelid = a.attrelid
1016                              AND d.adnum = a.attnum
1017                              AND a.atthasdef
d1403f 1018                          ) FROM 'nextval[^'']*''([^'']*)')
95ebbc 1019                         FROM pg_attribute a
T 1020                     LEFT JOIN pg_class c ON c.oid = a.attrelid
1021                     LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef
1022                     LEFT JOIN pg_namespace n ON c.relnamespace = n.oid
1023                        WHERE (c.relname = ".$this->quote($sqn, 'text');
1024             if (!empty($field)) {
1025                 $query .= " OR (c.relname = ".$this->quote($table, 'text')." AND a.attname = ".$this->quote($field, 'text').")";
1026             }
1027             $query .= "      )"
1028                          .$schema_clause."
1029                          AND NOT a.attisdropped
1030                          AND a.attnum > 0
1031                          AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval%'
1032                     ORDER BY ".$order_by;
1033             $seqname = $this->queryOne($query);
1034             if (!PEAR::isError($seqname) && !empty($seqname) && is_string($seqname)) {
1035                 return $seqname;
1036             }
1037         }
1038
1039         return sprintf($this->options['seqname_format'],
1040             preg_replace('/[^\w\$.]/i', '_', $sqn));
1041     }
1042
1043     // }}}
1044     // {{{ nextID()
1045
1046     /**
1047      * Returns the next free id of a sequence
1048      *
1049      * @param string $seq_name name of the sequence
1050      * @param boolean $ondemand when true the sequence is
1051      *                          automatic created, if it
1052      *                          not exists
1053      * @return mixed MDB2 Error Object or id
1054      * @access public
1055      */
1056     function nextID($seq_name, $ondemand = true)
1057     {
1058         $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1059         $query = "SELECT NEXTVAL('$sequence_name')";
d1403f 1060         $this->pushErrorHandling(PEAR_ERROR_RETURN);
95ebbc 1061         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
T 1062         $result = $this->queryOne($query, 'integer');
1063         $this->popExpect();
d1403f 1064         $this->popErrorHandling();
95ebbc 1065         if (PEAR::isError($result)) {
T 1066             if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
1067                 $this->loadModule('Manager', null, true);
1068                 $result = $this->manager->createSequence($seq_name);
1069                 if (PEAR::isError($result)) {
1070                     return $this->raiseError($result, null, null,
1071                         'on demand sequence could not be created', __FUNCTION__);
1072                 }
1073                 return $this->nextId($seq_name, false);
1074             }
1075         }
1076         return $result;
1077     }
1078
1079     // }}}
1080     // {{{ lastInsertID()
1081
1082     /**
1083      * Returns the autoincrement ID if supported or $id or fetches the current
1084      * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
1085      *
1086      * @param string $table name of the table into which a new row was inserted
1087      * @param string $field name of the field into which a new row was inserted
1088      * @return mixed MDB2 Error Object or id
1089      * @access public
1090      */
1091     function lastInsertID($table = null, $field = null)
1092     {
1093         if (empty($table) && empty($field)) {
1094             return $this->queryOne('SELECT lastval()', 'integer');
1095         }
1096         $seq = $table.(empty($field) ? '' : '_'.$field);
d1403f 1097         $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq), true);
95ebbc 1098         return $this->queryOne("SELECT currval('$sequence_name')", 'integer');
T 1099     }
1100
1101     // }}}
1102     // {{{ currID()
1103
1104     /**
1105      * Returns the current id of a sequence
1106      *
1107      * @param string $seq_name name of the sequence
1108      * @return mixed MDB2 Error Object or id
1109      * @access public
1110      */
1111     function currID($seq_name)
1112     {
1113         $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1114         return $this->queryOne("SELECT last_value FROM $sequence_name", 'integer');
1115     }
1116 }
1117
1118 /**
1119  * MDB2 PostGreSQL result driver
1120  *
1121  * @package MDB2
1122  * @category Database
1123  * @author  Paul Cooper <pgc@ucecom.com>
1124  */
1125 class MDB2_Result_pgsql extends MDB2_Result_Common
1126 {
1127     // }}}
1128     // {{{ fetchRow()
1129
1130     /**
1131      * Fetch a row and insert the data into an existing array.
1132      *
1133      * @param int       $fetchmode  how the array data should be indexed
1134      * @param int    $rownum    number of the row where the data can be found
1135      * @return int data array on success, a MDB2 error on failure
1136      * @access public
1137      */
1138     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
1139     {
1140         if (!is_null($rownum)) {
1141             $seek = $this->seek($rownum);
1142             if (PEAR::isError($seek)) {
1143                 return $seek;
1144             }
1145         }
1146         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
1147             $fetchmode = $this->db->fetchmode;
1148         }
1149         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
1150             $row = @pg_fetch_array($this->result, null, PGSQL_ASSOC);
1151             if (is_array($row)
1152                 && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
1153             ) {
1154                 $row = array_change_key_case($row, $this->db->options['field_case']);
1155             }
1156         } else {
1157             $row = @pg_fetch_row($this->result);
1158         }
1159         if (!$row) {
1160             if ($this->result === false) {
1161                 $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1162                     'resultset has already been freed', __FUNCTION__);
1163                 return $err;
1164             }
1165             $null = null;
1166             return $null;
1167         }
1168         $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
1169         $rtrim = false;
1170         if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
1171             if (empty($this->types)) {
1172                 $mode += MDB2_PORTABILITY_RTRIM;
1173             } else {
1174                 $rtrim = true;
1175             }
1176         }
1177         if ($mode) {
1178             $this->db->_fixResultArrayValues($row, $mode);
1179         }
1180         if (!empty($this->types)) {
1181             $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
1182         }
1183         if (!empty($this->values)) {
1184             $this->_assignBindColumns($row);
1185         }
1186         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
1187             $object_class = $this->db->options['fetch_class'];
1188             if ($object_class == 'stdClass') {
1189                 $row = (object) $row;
1190             } else {
1191                 $row = &new $object_class($row);
1192             }
1193         }
1194         ++$this->rownum;
1195         return $row;
1196     }
1197
1198     // }}}
1199     // {{{ _getColumnNames()
1200
1201     /**
1202      * Retrieve the names of columns returned by the DBMS in a query result.
1203      *
1204      * @return  mixed   Array variable that holds the names of columns as keys
1205      *                  or an MDB2 error on failure.
1206      *                  Some DBMS may not return any columns when the result set
1207      *                  does not contain any rows.
1208      * @access private
1209      */
1210     function _getColumnNames()
1211     {
1212         $columns = array();
1213         $numcols = $this->numCols();
1214         if (PEAR::isError($numcols)) {
1215             return $numcols;
1216         }
1217         for ($column = 0; $column < $numcols; $column++) {
1218             $column_name = @pg_field_name($this->result, $column);
1219             $columns[$column_name] = $column;
1220         }
1221         if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1222             $columns = array_change_key_case($columns, $this->db->options['field_case']);
1223         }
1224         return $columns;
1225     }
1226
1227     // }}}
1228     // {{{ numCols()
1229
1230     /**
1231      * Count the number of columns returned by the DBMS in a query result.
1232      *
1233      * @access public
1234      * @return mixed integer value with the number of columns, a MDB2 error
1235      *                       on failure
1236      */
1237     function numCols()
1238     {
1239         $cols = @pg_num_fields($this->result);
1240         if (is_null($cols)) {
1241             if ($this->result === false) {
1242                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1243                     'resultset has already been freed', __FUNCTION__);
1244             } elseif (is_null($this->result)) {
1245                 return count($this->types);
1246             }
1247             return $this->db->raiseError(null, null, null,
1248                 'Could not get column count', __FUNCTION__);
1249         }
1250         return $cols;
1251     }
1252
1253     // }}}
1254     // {{{ nextResult()
1255
1256     /**
1257      * Move the internal result pointer to the next available result
1258      *
1259      * @return true on success, false if there is no more result set or an error object on failure
1260      * @access public
1261      */
1262     function nextResult()
1263     {
1264         $connection = $this->db->getConnection();
1265         if (PEAR::isError($connection)) {
1266             return $connection;
1267         }
1268
1269         if (!($this->result = @pg_get_result($connection))) {
1270             return false;
1271         }
1272         return MDB2_OK;
1273     }
1274
1275     // }}}
1276     // {{{ free()
1277
1278     /**
1279      * Free the internal resources associated with result.
1280      *
1281      * @return boolean true on success, false if result is invalid
1282      * @access public
1283      */
1284     function free()
1285     {
1286         if (is_resource($this->result) && $this->db->connection) {
1287             $free = @pg_free_result($this->result);
1288             if ($free === false) {
1289                 return $this->db->raiseError(null, null, null,
1290                     'Could not free result', __FUNCTION__);
1291             }
1292         }
1293         $this->result = false;
1294         return MDB2_OK;
1295     }
1296 }
1297
1298 /**
1299  * MDB2 PostGreSQL buffered result driver
1300  *
1301  * @package MDB2
1302  * @category Database
1303  * @author  Paul Cooper <pgc@ucecom.com>
1304  */
1305 class MDB2_BufferedResult_pgsql extends MDB2_Result_pgsql
1306 {
1307     // {{{ seek()
1308
1309     /**
1310      * Seek to a specific row in a result set
1311      *
1312      * @param int    $rownum    number of the row where the data can be found
1313      * @return mixed MDB2_OK on success, a MDB2 error on failure
1314      * @access public
1315      */
1316     function seek($rownum = 0)
1317     {
1318         if ($this->rownum != ($rownum - 1) && !@pg_result_seek($this->result, $rownum)) {
1319             if ($this->result === false) {
1320                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1321                     'resultset has already been freed', __FUNCTION__);
1322             } elseif (is_null($this->result)) {
1323                 return MDB2_OK;
1324             }
1325             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1326                 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1327         }
1328         $this->rownum = $rownum - 1;
1329         return MDB2_OK;
1330     }
1331
1332     // }}}
1333     // {{{ valid()
1334
1335     /**
1336      * Check if the end of the result set has been reached
1337      *
1338      * @return mixed true or false on sucess, a MDB2 error on failure
1339      * @access public
1340      */
1341     function valid()
1342     {
1343         $numrows = $this->numRows();
1344         if (PEAR::isError($numrows)) {
1345             return $numrows;
1346         }
1347         return $this->rownum < ($numrows - 1);
1348     }
1349
1350     // }}}
1351     // {{{ numRows()
1352
1353     /**
1354      * Returns the number of rows in a result object
1355      *
1356      * @return mixed MDB2 Error Object or the number of rows
1357      * @access public
1358      */
1359     function numRows()
1360     {
1361         $rows = @pg_num_rows($this->result);
1362         if (is_null($rows)) {
1363             if ($this->result === false) {
1364                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1365                     'resultset has already been freed', __FUNCTION__);
1366             } elseif (is_null($this->result)) {
1367                 return 0;
1368             }
1369             return $this->db->raiseError(null, null, null,
1370                 'Could not get row count', __FUNCTION__);
1371         }
1372         return $rows;
1373     }
1374 }
1375
1376 /**
1377  * MDB2 PostGreSQL statement driver
1378  *
1379  * @package MDB2
1380  * @category Database
1381  * @author  Paul Cooper <pgc@ucecom.com>
1382  */
1383 class MDB2_Statement_pgsql extends MDB2_Statement_Common
1384 {
1385     // {{{ _execute()
1386
1387     /**
1388      * Execute a prepared query statement helper method.
1389      *
1390      * @param mixed $result_class string which specifies which result class to use
1391      * @param mixed $result_wrap_class string which specifies which class to wrap results in
1392      * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
1393      * @access private
1394      */
1395     function &_execute($result_class = true, $result_wrap_class = false)
1396     {
1397         if (is_null($this->statement)) {
1398             $result =& parent::_execute($result_class, $result_wrap_class);
1399             return $result;
1400         }
1401         $this->db->last_query = $this->query;
1402         $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
1403         if ($this->db->getOption('disable_query')) {
1404             $result = $this->is_manip ? 0 : null;
1405             return $result;
1406         }
1407
1408         $connection = $this->db->getConnection();
1409         if (PEAR::isError($connection)) {
1410             return $connection;
1411         }
1412
1413         $query = false;
1414         $parameters = array();
1415         // todo: disabled until pg_execute() bytea issues are cleared up
1416         if (true || !function_exists('pg_execute')) {
1417             $query = 'EXECUTE '.$this->statement;
1418         }
1419         if (!empty($this->positions)) {
1420             foreach ($this->positions as $parameter) {
1421                 if (!array_key_exists($parameter, $this->values)) {
1422                     return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
1423                         'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
1424                 }
1425                 $value = $this->values[$parameter];
1426                 $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1427                 if (is_resource($value) || $type == 'clob' || $type == 'blob' || $this->db->options['lob_allow_url_include']) {
1428                     if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1429                         if ($match[1] == 'file://') {
1430                             $value = $match[2];
1431                         }
1432                         $value = @fopen($value, 'r');
1433                         $close = true;
1434                     }
1435                     if (is_resource($value)) {
1436                         $data = '';
1437                         while (!@feof($value)) {
1438                             $data.= @fread($value, $this->db->options['lob_buffer_length']);
1439                         }
1440                         if ($close) {
1441                             @fclose($value);
1442                         }
1443                         $value = $data;
1444                     }
1445                 }
1446                 $quoted = $this->db->quote($value, $type, $query);
1447                 if (PEAR::isError($quoted)) {
1448                     return $quoted;
1449                 }
1450                 $parameters[] = $quoted;
1451             }
1452             if ($query) {
1453                 $query.= ' ('.implode(', ', $parameters).')';
1454             }
1455         }
1456
1457         if (!$query) {
1458             $result = @pg_execute($connection, $this->statement, $parameters);
1459             if (!$result) {
1460                 $err =& $this->db->raiseError(null, null, null,
1461                     'Unable to execute statement', __FUNCTION__);
1462                 return $err;
1463             }
1464         } else {
1465             $result = $this->db->_doQuery($query, $this->is_manip, $connection);
1466             if (PEAR::isError($result)) {
1467                 return $result;
1468             }
1469         }
1470
1471         if ($this->is_manip) {
1472             $affected_rows = $this->db->_affectedRows($connection, $result);
1473             return $affected_rows;
1474         }
1475
1476         $result =& $this->db->_wrapResult($result, $this->result_types,
1477             $result_class, $result_wrap_class, $this->limit, $this->offset);
1478         $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
1479         return $result;
1480     }
1481
1482     // }}}
1483     // {{{ free()
1484
1485     /**
1486      * Release resources allocated for the specified prepared query.
1487      *
1488      * @return mixed MDB2_OK on success, a MDB2 error on failure
1489      * @access public
1490      */
1491     function free()
1492     {
1493         if (is_null($this->positions)) {
1494             return $this->db->raiseError(MDB2_ERROR, null, null,
1495                 'Prepared statement has already been freed', __FUNCTION__);
1496         }
1497         $result = MDB2_OK;
1498
1499         if (!is_null($this->statement)) {
1500             $connection = $this->db->getConnection();
1501             if (PEAR::isError($connection)) {
1502                 return $connection;
1503             }
1504             $query = 'DEALLOCATE PREPARE '.$this->statement;
1505             $result = $this->db->_doQuery($query, true, $connection);
1506         }
1507
1508         parent::free();
1509         return $result;
1510     }
1511 }
1512 ?>