alecpl
2008-04-29 6ee5ed253b92593ac5784300ac425f34f7fd1728
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 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
7 // | Stig. S. Bakken, Lukas Smith                                         |
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: Lukas Smith <smith@pooteeweet.org>                           |
44 // +----------------------------------------------------------------------+
45 //
46 // $Id: MDB2.php,v 1.307 2007/11/10 13:29:05 quipo Exp $
47 //
48
49 /**
50  * @package     MDB2
51  * @category    Database
52  * @author      Lukas Smith <smith@pooteeweet.org>
53  */
54
55 require_once 'PEAR.php';
56
57 // {{{ Error constants
58
59 /**
60  * The method mapErrorCode in each MDB2_dbtype implementation maps
61  * native error codes to one of these.
62  *
63  * If you add an error code here, make sure you also add a textual
64  * version of it in MDB2::errorMessage().
65  */
66
67 define('MDB2_OK',                      true);
68 define('MDB2_ERROR',                     -1);
69 define('MDB2_ERROR_SYNTAX',              -2);
70 define('MDB2_ERROR_CONSTRAINT',          -3);
71 define('MDB2_ERROR_NOT_FOUND',           -4);
72 define('MDB2_ERROR_ALREADY_EXISTS',      -5);
73 define('MDB2_ERROR_UNSUPPORTED',         -6);
74 define('MDB2_ERROR_MISMATCH',            -7);
75 define('MDB2_ERROR_INVALID',             -8);
76 define('MDB2_ERROR_NOT_CAPABLE',         -9);
77 define('MDB2_ERROR_TRUNCATED',          -10);
78 define('MDB2_ERROR_INVALID_NUMBER',     -11);
79 define('MDB2_ERROR_INVALID_DATE',       -12);
80 define('MDB2_ERROR_DIVZERO',            -13);
81 define('MDB2_ERROR_NODBSELECTED',       -14);
82 define('MDB2_ERROR_CANNOT_CREATE',      -15);
83 define('MDB2_ERROR_CANNOT_DELETE',      -16);
84 define('MDB2_ERROR_CANNOT_DROP',        -17);
85 define('MDB2_ERROR_NOSUCHTABLE',        -18);
86 define('MDB2_ERROR_NOSUCHFIELD',        -19);
87 define('MDB2_ERROR_NEED_MORE_DATA',     -20);
88 define('MDB2_ERROR_NOT_LOCKED',         -21);
89 define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22);
90 define('MDB2_ERROR_INVALID_DSN',        -23);
91 define('MDB2_ERROR_CONNECT_FAILED',     -24);
92 define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25);
93 define('MDB2_ERROR_NOSUCHDB',           -26);
94 define('MDB2_ERROR_ACCESS_VIOLATION',   -27);
95 define('MDB2_ERROR_CANNOT_REPLACE',     -28);
96 define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29);
97 define('MDB2_ERROR_DEADLOCK',           -30);
98 define('MDB2_ERROR_CANNOT_ALTER',       -31);
99 define('MDB2_ERROR_MANAGER',            -32);
100 define('MDB2_ERROR_MANAGER_PARSE',      -33);
101 define('MDB2_ERROR_LOADMODULE',         -34);
102 define('MDB2_ERROR_INSUFFICIENT_DATA',  -35);
103 // }}}
104 // {{{ Verbose constants
105 /**
106  * These are just helper constants to more verbosely express parameters to prepare()
107  */
108
109 define('MDB2_PREPARE_MANIP', false);
110 define('MDB2_PREPARE_RESULT', null);
111
112 // }}}
113 // {{{ Fetchmode constants
114
115 /**
116  * This is a special constant that tells MDB2 the user hasn't specified
117  * any particular get mode, so the default should be used.
118  */
119 define('MDB2_FETCHMODE_DEFAULT', 0);
120
121 /**
122  * Column data indexed by numbers, ordered from 0 and up
123  */
124 define('MDB2_FETCHMODE_ORDERED', 1);
125
126 /**
127  * Column data indexed by column names
128  */
129 define('MDB2_FETCHMODE_ASSOC', 2);
130
131 /**
132  * Column data as object properties
133  */
134 define('MDB2_FETCHMODE_OBJECT', 3);
135
136 /**
137  * For multi-dimensional results: normally the first level of arrays
138  * is the row number, and the second level indexed by column number or name.
139  * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays
140  * is the column name, and the second level the row number.
141  */
142 define('MDB2_FETCHMODE_FLIPPED', 4);
143
144 // }}}
145 // {{{ Portability mode constants
146
147 /**
148  * Portability: turn off all portability features.
149  * @see MDB2_Driver_Common::setOption()
150  */
151 define('MDB2_PORTABILITY_NONE', 0);
152
153 /**
154  * Portability: convert names of tables and fields to case defined in the
155  * "field_case" option when using the query*(), fetch*() and tableInfo() methods.
156  * @see MDB2_Driver_Common::setOption()
157  */
158 define('MDB2_PORTABILITY_FIX_CASE', 1);
159
160 /**
161  * Portability: right trim the data output by query*() and fetch*().
162  * @see MDB2_Driver_Common::setOption()
163  */
164 define('MDB2_PORTABILITY_RTRIM', 2);
165
166 /**
167  * Portability: force reporting the number of rows deleted.
168  * @see MDB2_Driver_Common::setOption()
169  */
170 define('MDB2_PORTABILITY_DELETE_COUNT', 4);
171
172 /**
173  * Portability: not needed in MDB2 (just left here for compatibility to DB)
174  * @see MDB2_Driver_Common::setOption()
175  */
176 define('MDB2_PORTABILITY_NUMROWS', 8);
177
178 /**
179  * Portability: makes certain error messages in certain drivers compatible
180  * with those from other DBMS's.
181  *
182  * + mysql, mysqli:  change unique/primary key constraints
183  *   MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT
184  *
185  * + odbc(access):  MS's ODBC driver reports 'no such field' as code
186  *   07001, which means 'too few parameters.'  When this option is on
187  *   that code gets mapped to MDB2_ERROR_NOSUCHFIELD.
188  *
189  * @see MDB2_Driver_Common::setOption()
190  */
191 define('MDB2_PORTABILITY_ERRORS', 16);
192
193 /**
194  * Portability: convert empty values to null strings in data output by
195  * query*() and fetch*().
196  * @see MDB2_Driver_Common::setOption()
197  */
198 define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32);
199
200 /**
201  * Portability: removes database/table qualifiers from associative indexes
202  * @see MDB2_Driver_Common::setOption()
203  */
204 define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64);
205
206 /**
207  * Portability: turn on all portability features.
208  * @see MDB2_Driver_Common::setOption()
209  */
210 define('MDB2_PORTABILITY_ALL', 127);
211
212 // }}}
213 // {{{ Globals for class instance tracking
214
215 /**
216  * These are global variables that are used to track the various class instances
217  */
218
219 $GLOBALS['_MDB2_databases'] = array();
220 $GLOBALS['_MDB2_dsninfo_default'] = array(
221     'phptype'  => false,
222     'dbsyntax' => false,
223     'username' => false,
224     'password' => false,
225     'protocol' => false,
226     'hostspec' => false,
227     'port'     => false,
228     'socket'   => false,
229     'database' => false,
230     'mode'     => false,
231 );
232
233 // }}}
234 // {{{ class MDB2
235
236 /**
237  * The main 'MDB2' class is simply a container class with some static
238  * methods for creating DB objects as well as some utility functions
239  * common to all parts of DB.
240  *
241  * The object model of MDB2 is as follows (indentation means inheritance):
242  *
243  * MDB2          The main MDB2 class.  This is simply a utility class
244  *              with some 'static' methods for creating MDB2 objects as
245  *              well as common utility functions for other MDB2 classes.
246  *
247  * MDB2_Driver_Common   The base for each MDB2 implementation.  Provides default
248  * |            implementations (in OO lingo virtual methods) for
249  * |            the actual DB implementations as well as a bunch of
250  * |            query utility functions.
251  * |
252  * +-MDB2_Driver_mysql  The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common.
253  *              When calling MDB2::factory or MDB2::connect for MySQL
254  *              connections, the object returned is an instance of this
255  *              class.
256  * +-MDB2_Driver_pgsql  The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common.
257  *              When calling MDB2::factory or MDB2::connect for PostGreSQL
258  *              connections, the object returned is an instance of this
259  *              class.
260  *
261  * @package     MDB2
262  * @category    Database
263  * @author      Lukas Smith <smith@pooteeweet.org>
264  */
265 class MDB2
266 {
267     // {{{ function setOptions(&$db, $options)
268
269     /**
270      * set option array   in an exiting database object
271      *
272      * @param   MDB2_Driver_Common  MDB2 object
273      * @param   array   An associative array of option names and their values.
274      *
275      * @return mixed   MDB2_OK or a PEAR Error object
276      *
277      * @access  public
278      */
279     function setOptions(&$db, $options)
280     {
281         if (is_array($options)) {
282             foreach ($options as $option => $value) {
283                 $test = $db->setOption($option, $value);
284                 if (PEAR::isError($test)) {
285                     return $test;
286                 }
287             }
288         }
289         return MDB2_OK;
290     }
291
292     // }}}
293     // {{{ function classExists($classname)
294
295     /**
296      * Checks if a class exists without triggering __autoload
297      *
298      * @param   string  classname
299      *
300      * @return  bool    true success and false on error
301      * @static
302      * @access  public
303      */
304     function classExists($classname)
305     {
306         if (version_compare(phpversion(), "5.0", ">=")) {
307             return class_exists($classname, false);
308         }
309         return class_exists($classname);
310     }
311
312     // }}}
313     // {{{ function loadClass($class_name, $debug)
314
315     /**
316      * Loads a PEAR class.
317      *
318      * @param   string  classname to load
319      * @param   bool    if errors should be suppressed
320      *
321      * @return  mixed   true success or PEAR_Error on failure
322      *
323      * @access  public
324      */
325     function loadClass($class_name, $debug)
326     {
327         if (!MDB2::classExists($class_name)) {
328             $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
329             if ($debug) {
330                 $include = include_once($file_name);
331             } else {
332                 $include = @include_once($file_name);
333             }
334             if (!$include) {
335                 if (!MDB2::fileExists($file_name)) {
336                     $msg = "unable to find package '$class_name' file '$file_name'";
337                 } else {
338                     $msg = "unable to load class '$class_name' from file '$file_name'";
339                 }
340                 $err =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg);
341                 return $err;
342             }
343         }
344         return MDB2_OK;
345     }
346
347     // }}}
348     // {{{ function &factory($dsn, $options = false)
349
350     /**
351      * Create a new MDB2 object for the specified database type
352      *
353      * IMPORTANT: In order for MDB2 to work properly it is necessary that
354      * you make sure that you work with a reference of the original
355      * object instead of a copy (this is a PHP4 quirk).
356      *
357      * For example:
358      *     $db =& MDB2::factory($dsn);
359      *          ^^
360      * And not:
361      *     $db = MDB2::factory($dsn);
362      *
363      * @param   mixed   'data source name', see the MDB2::parseDSN
364      *                      method for a description of the dsn format.
365      *                      Can also be specified as an array of the
366      *                      format returned by MDB2::parseDSN.
367      * @param   array   An associative array of option names and
368      *                            their values.
369      *
370      * @return  mixed   a newly created MDB2 object, or false on error
371      *
372      * @access  public
373      */
374     function &factory($dsn, $options = false)
375     {
376         $dsninfo = MDB2::parseDSN($dsn);
377         if (empty($dsninfo['phptype'])) {
378             $err =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND,
379                 null, null, 'no RDBMS driver specified');
380             return $err;
381         }
382         $class_name = 'MDB2_Driver_'.$dsninfo['phptype'];
383
384         $debug = (!empty($options['debug']));
385         $err = MDB2::loadClass($class_name, $debug);
386         if (PEAR::isError($err)) {
387             return $err;
388         }
389
390         $db =& new $class_name();
391         $db->setDSN($dsninfo);
392         $err = MDB2::setOptions($db, $options);
393         if (PEAR::isError($err)) {
394             return $err;
395         }
396
397         return $db;
398     }
399
400     // }}}
401     // {{{ function &connect($dsn, $options = false)
402
403     /**
404      * Create a new MDB2 connection object and connect to the specified
405      * database
406      *
407      * IMPORTANT: In order for MDB2 to work properly it is necessary that
408      * you make sure that you work with a reference of the original
409      * object instead of a copy (this is a PHP4 quirk).
410      *
411      * For example:
412      *     $db =& MDB2::connect($dsn);
413      *          ^^
414      * And not:
415      *     $db = MDB2::connect($dsn);
416      *          ^^
417      *
418      * @param   mixed   'data source name', see the MDB2::parseDSN
419      *                            method for a description of the dsn format.
420      *                            Can also be specified as an array of the
421      *                            format returned by MDB2::parseDSN.
422      * @param   array   An associative array of option names and
423      *                            their values.
424      *
425      * @return  mixed   a newly created MDB2 connection object, or a MDB2
426      *                  error object on error
427      *
428      * @access  public
429      * @see     MDB2::parseDSN
430      */
431     function &connect($dsn, $options = false)
432     {
433         $db =& MDB2::factory($dsn, $options);
434         if (PEAR::isError($db)) {
435             return $db;
436         }
437
438         $err = $db->connect();
439         if (PEAR::isError($err)) {
440             $dsn = $db->getDSN('string', 'xxx');
441             $db->disconnect();
442             $err->addUserInfo($dsn);
443             return $err;
444         }
445
446         return $db;
447     }
448
449     // }}}
450     // {{{ function &singleton($dsn = null, $options = false)
451
452     /**
453      * Returns a MDB2 connection with the requested DSN.
454      * A new MDB2 connection object is only created if no object with the
455      * requested DSN exists yet.
456      *
457      * IMPORTANT: In order for MDB2 to work properly it is necessary that
458      * you make sure that you work with a reference of the original
459      * object instead of a copy (this is a PHP4 quirk).
460      *
461      * For example:
462      *     $db =& MDB2::singleton($dsn);
463      *          ^^
464      * And not:
465      *     $db = MDB2::singleton($dsn);
466      *          ^^
467      *
468      * @param   mixed   'data source name', see the MDB2::parseDSN
469      *                            method for a description of the dsn format.
470      *                            Can also be specified as an array of the
471      *                            format returned by MDB2::parseDSN.
472      * @param   array   An associative array of option names and
473      *                            their values.
474      *
475      * @return  mixed   a newly created MDB2 connection object, or a MDB2
476      *                  error object on error
477      *
478      * @access  public
479      * @see     MDB2::parseDSN
480      */
481     function &singleton($dsn = null, $options = false)
482     {
483         if ($dsn) {
484             $dsninfo = MDB2::parseDSN($dsn);
485             $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo);
486             $keys = array_keys($GLOBALS['_MDB2_databases']);
487             for ($i=0, $j=count($keys); $i<$j; ++$i) {
488                 if (isset($GLOBALS['_MDB2_databases'][$keys[$i]])) {
489                     $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array');
490                     if (count(array_diff_assoc($tmp_dsn, $dsninfo)) == 0) {
491                         MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options);
492                         return $GLOBALS['_MDB2_databases'][$keys[$i]];
493                     }
494                 }
495             }
496         } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) {
497             $db =& $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])];
498             return $db;
499         }
500         $db =& MDB2::factory($dsn, $options);
501         return $db;
502     }
503
504     // }}}
505     // {{{ function areEquals()
506
507     /**
508      * It looks like there's a memory leak in array_diff() in PHP 5.1.x,
509      * so use this method instead.
510      * @see http://pear.php.net/bugs/bug.php?id=11790
511      *
512      * @param array $arr1
513      * @param array $arr2
514      * @return boolean
515      */
516     function areEquals($arr1, $arr2)
517     {
518         if (count($arr1) != count($arr2)) {
519             return false;
520         }
521         foreach (array_keys($arr1) as $k) {
522             if (!array_key_exists($k, $arr2) || $arr1[$k] != $arr2[$k]) {
523                 return false;
524             }
525         }
526         return true;
527     }
528
529     // }}}
530     // {{{ function loadFile($file)
531
532     /**
533      * load a file (like 'Date')
534      *
535      * @param   string  name of the file in the MDB2 directory (without '.php')
536      *
537      * @return  string  name of the file that was included
538      *
539      * @access  public
540      */
541     function loadFile($file)
542     {
543         $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php';
544         if (!MDB2::fileExists($file_name)) {
545             return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
546                 'unable to find: '.$file_name);
547         }
548         if (!include_once($file_name)) {
549             return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
550                 'unable to load driver class: '.$file_name);
551         }
552         return $file_name;
553     }
554
555     // }}}
556     // {{{ function apiVersion()
557
558     /**
559      * Return the MDB2 API version
560      *
561      * @return  string  the MDB2 API version number
562      *
563      * @access  public
564      */
565     function apiVersion()
566     {
567         return '2.5.0a2';
568     }
569
570     // }}}
571     // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
572
573     /**
574      * This method is used to communicate an error and invoke error
575      * callbacks etc.  Basically a wrapper for PEAR::raiseError
576      * without the message string.
577      *
578      * @param   mixed  int error code
579      *
580      * @param   int    error mode, see PEAR_Error docs
581      *
582      * @param   mixed  If error mode is PEAR_ERROR_TRIGGER, this is the
583      *                 error level (E_USER_NOTICE etc).  If error mode is
584      *                 PEAR_ERROR_CALLBACK, this is the callback function,
585      *                 either as a function name, or as an array of an
586      *                 object and method name.  For other error modes this
587      *                 parameter is ignored.
588      *
589      * @param   string Extra debug information.  Defaults to the last
590      *                 query and native error code.
591      *
592      * @return PEAR_Error instance of a PEAR Error object
593      *
594      * @access  private
595      * @see     PEAR_Error
596      */
597     function &raiseError($code = null,
598                          $mode = null,
599                          $options = null,
600                          $userinfo = null,
601                          $dummy1 = null,
602                          $dummy2 = null,
603                          $dummy3 = false)
604     {
605         $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true);
606         return $err;
607     }
608
609     // }}}
610     // {{{ function isError($data, $code = null)
611
612     /**
613      * Tell whether a value is a MDB2 error.
614      *
615      * @param   mixed   the value to test
616      * @param   int     if is an error object, return true
617      *                        only if $code is a string and
618      *                        $db->getMessage() == $code or
619      *                        $code is an integer and $db->getCode() == $code
620      *
621      * @return  bool    true if parameter is an error
622      *
623      * @access  public
624      */
625     function isError($data, $code = null)
626     {
627         if (is_a($data, 'MDB2_Error')) {
628             if (is_null($code)) {
629                 return true;
630             } elseif (is_string($code)) {
631                 return $data->getMessage() === $code;
632             } else {
633                 $code = (array)$code;
634                 return in_array($data->getCode(), $code);
635             }
636         }
637         return false;
638     }
639
640     // }}}
641     // {{{ function isConnection($value)
642
643     /**
644      * Tell whether a value is a MDB2 connection
645      *
646      * @param   mixed   value to test
647      *
648      * @return  bool    whether $value is a MDB2 connection
649      *
650      * @access  public
651      */
652     function isConnection($value)
653     {
654         return is_a($value, 'MDB2_Driver_Common');
655     }
656
657     // }}}
658     // {{{ function isResult($value)
659
660     /**
661      * Tell whether a value is a MDB2 result
662      *
663      * @param   mixed   value to test
664      *
665      * @return  bool    whether $value is a MDB2 result
666      *
667      * @access  public
668      */
669     function isResult($value)
670     {
671         return is_a($value, 'MDB2_Result');
672     }
673
674     // }}}
675     // {{{ function isResultCommon($value)
676
677     /**
678      * Tell whether a value is a MDB2 result implementing the common interface
679      *
680      * @param   mixed   value to test
681      *
682      * @return  bool    whether $value is a MDB2 result implementing the common interface
683      *
684      * @access  public
685      */
686     function isResultCommon($value)
687     {
688         return is_a($value, 'MDB2_Result_Common');
689     }
690
691     // }}}
692     // {{{ function isStatement($value)
693
694     /**
695      * Tell whether a value is a MDB2 statement interface
696      *
697      * @param   mixed   value to test
698      *
699      * @return  bool    whether $value is a MDB2 statement interface
700      *
701      * @access  public
702      */
703     function isStatement($value)
704     {
705         return is_a($value, 'MDB2_Statement_Common');
706     }
707
708     // }}}
709     // {{{ function errorMessage($value = null)
710
711     /**
712      * Return a textual error message for a MDB2 error code
713      *
714      * @param   int|array   integer error code,
715                                 null to get the current error code-message map,
716                                 or an array with a new error code-message map
717      *
718      * @return  string  error message, or false if the error code was
719      *                  not recognized
720      *
721      * @access  public
722      */
723     function errorMessage($value = null)
724     {
725         static $errorMessages;
726
727         if (is_array($value)) {
728             $errorMessages = $value;
729             return MDB2_OK;
730         }
731
732         if (!isset($errorMessages)) {
733             $errorMessages = array(
734                 MDB2_OK                       => 'no error',
735                 MDB2_ERROR                    => 'unknown error',
736                 MDB2_ERROR_ALREADY_EXISTS     => 'already exists',
737                 MDB2_ERROR_CANNOT_CREATE      => 'can not create',
738                 MDB2_ERROR_CANNOT_ALTER       => 'can not alter',
739                 MDB2_ERROR_CANNOT_REPLACE     => 'can not replace',
740                 MDB2_ERROR_CANNOT_DELETE      => 'can not delete',
741                 MDB2_ERROR_CANNOT_DROP        => 'can not drop',
742                 MDB2_ERROR_CONSTRAINT         => 'constraint violation',
743                 MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
744                 MDB2_ERROR_DIVZERO            => 'division by zero',
745                 MDB2_ERROR_INVALID            => 'invalid',
746                 MDB2_ERROR_INVALID_DATE       => 'invalid date or time',
747                 MDB2_ERROR_INVALID_NUMBER     => 'invalid number',
748                 MDB2_ERROR_MISMATCH           => 'mismatch',
749                 MDB2_ERROR_NODBSELECTED       => 'no database selected',
750                 MDB2_ERROR_NOSUCHFIELD        => 'no such field',
751                 MDB2_ERROR_NOSUCHTABLE        => 'no such table',
752                 MDB2_ERROR_NOT_CAPABLE        => 'MDB2 backend not capable',
753                 MDB2_ERROR_NOT_FOUND          => 'not found',
754                 MDB2_ERROR_NOT_LOCKED         => 'not locked',
755                 MDB2_ERROR_SYNTAX             => 'syntax error',
756                 MDB2_ERROR_UNSUPPORTED        => 'not supported',
757                 MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
758                 MDB2_ERROR_INVALID_DSN        => 'invalid DSN',
759                 MDB2_ERROR_CONNECT_FAILED     => 'connect failed',
760                 MDB2_ERROR_NEED_MORE_DATA     => 'insufficient data supplied',
761                 MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
762                 MDB2_ERROR_NOSUCHDB           => 'no such database',
763                 MDB2_ERROR_ACCESS_VIOLATION   => 'insufficient permissions',
764                 MDB2_ERROR_LOADMODULE         => 'error while including on demand module',
765                 MDB2_ERROR_TRUNCATED          => 'truncated',
766                 MDB2_ERROR_DEADLOCK           => 'deadlock detected',
767             );
768         }
769
770         if (is_null($value)) {
771             return $errorMessages;
772         }
773
774         if (PEAR::isError($value)) {
775             $value = $value->getCode();
776         }
777
778         return isset($errorMessages[$value]) ?
779            $errorMessages[$value] : $errorMessages[MDB2_ERROR];
780     }
781
782     // }}}
783     // {{{ function parseDSN($dsn)
784
785     /**
786      * Parse a data source name.
787      *
788      * Additional keys can be added by appending a URI query string to the
789      * end of the DSN.
790      *
791      * The format of the supplied DSN is in its fullest form:
792      * <code>
793      *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
794      * </code>
795      *
796      * Most variations are allowed:
797      * <code>
798      *  phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
799      *  phptype://username:password@hostspec/database_name
800      *  phptype://username:password@hostspec
801      *  phptype://username@hostspec
802      *  phptype://hostspec/database
803      *  phptype://hostspec
804      *  phptype(dbsyntax)
805      *  phptype
806      * </code>
807      *
808      * @param   string  Data Source Name to be parsed
809      *
810      * @return  array   an associative array with the following keys:
811      *  + phptype:  Database backend used in PHP (mysql, odbc etc.)
812      *  + dbsyntax: Database used with regards to SQL syntax etc.
813      *  + protocol: Communication protocol to use (tcp, unix etc.)
814      *  + hostspec: Host specification (hostname[:port])
815      *  + database: Database to use on the DBMS server
816      *  + username: User name for login
817      *  + password: Password for login
818      *
819      * @access  public
820      * @author  Tomas V.V.Cox <cox@idecnet.com>
821      */
822     function parseDSN($dsn)
823     {
824         $parsed = $GLOBALS['_MDB2_dsninfo_default'];
825
826         if (is_array($dsn)) {
827             $dsn = array_merge($parsed, $dsn);
828             if (!$dsn['dbsyntax']) {
829                 $dsn['dbsyntax'] = $dsn['phptype'];
830             }
831             return $dsn;
832         }
833
834         // Find phptype and dbsyntax
835         if (($pos = strpos($dsn, '://')) !== false) {
836             $str = substr($dsn, 0, $pos);
837             $dsn = substr($dsn, $pos + 3);
838         } else {
839             $str = $dsn;
840             $dsn = null;
841         }
842
843         // Get phptype and dbsyntax
844         // $str => phptype(dbsyntax)
845         if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
846             $parsed['phptype']  = $arr[1];
847             $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
848         } else {
849             $parsed['phptype']  = $str;
850             $parsed['dbsyntax'] = $str;
851         }
852
853         if (!count($dsn)) {
854             return $parsed;
855         }
856
857         // Get (if found): username and password
858         // $dsn => username:password@protocol+hostspec/database
859         if (($at = strrpos($dsn,'@')) !== false) {
860             $str = substr($dsn, 0, $at);
861             $dsn = substr($dsn, $at + 1);
862             if (($pos = strpos($str, ':')) !== false) {
863                 $parsed['username'] = rawurldecode(substr($str, 0, $pos));
864                 $parsed['password'] = rawurldecode(substr($str, $pos + 1));
865             } else {
866                 $parsed['username'] = rawurldecode($str);
867             }
868         }
869
870         // Find protocol and hostspec
871
872         // $dsn => proto(proto_opts)/database
873         if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
874             $proto       = $match[1];
875             $proto_opts  = $match[2] ? $match[2] : false;
876             $dsn         = $match[3];
877
878         // $dsn => protocol+hostspec/database (old format)
879         } else {
880             if (strpos($dsn, '+') !== false) {
881                 list($proto, $dsn) = explode('+', $dsn, 2);
882             }
883             if (   strpos($dsn, '//') === 0
884                 && strpos($dsn, '/', 2) !== false
885                 && $parsed['phptype'] == 'oci8'
886             ) {
887                 //oracle's "Easy Connect" syntax:
888                 //"username/password@[//]host[:port][/service_name]"
889                 //e.g. "scott/tiger@//mymachine:1521/oracle"
890                 $proto_opts = $dsn;
891                 $dsn = null;
892             } elseif (strpos($dsn, '/') !== false) {
893                 list($proto_opts, $dsn) = explode('/', $dsn, 2);
894             } else {
895                 $proto_opts = $dsn;
896                 $dsn = null;
897             }
898         }
899
900         // process the different protocol options
901         $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
902         $proto_opts = rawurldecode($proto_opts);
903         if (strpos($proto_opts, ':') !== false) {
904             list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
905         }
906         if ($parsed['protocol'] == 'tcp') {
907             $parsed['hostspec'] = $proto_opts;
908         } elseif ($parsed['protocol'] == 'unix') {
909             $parsed['socket'] = $proto_opts;
910         }
911
912         // Get dabase if any
913         // $dsn => database
914         if ($dsn) {
915             // /database
916             if (($pos = strpos($dsn, '?')) === false) {
917                 $parsed['database'] = $dsn;
918             // /database?param1=value1&param2=value2
919             } else {
920                 $parsed['database'] = substr($dsn, 0, $pos);
921                 $dsn = substr($dsn, $pos + 1);
922                 if (strpos($dsn, '&') !== false) {
923                     $opts = explode('&', $dsn);
924                 } else { // database?param1=value1
925                     $opts = array($dsn);
926                 }
927                 foreach ($opts as $opt) {
928                     list($key, $value) = explode('=', $opt);
929                     if (!isset($parsed[$key])) {
930                         // don't allow params overwrite
931                         $parsed[$key] = rawurldecode($value);
932                     }
933                 }
934             }
935         }
936
937         return $parsed;
938     }
939
940     // }}}
941     // {{{ function fileExists($file)
942
943     /**
944      * Checks if a file exists in the include path
945      *
946      * @param   string  filename
947      *
948      * @return  bool    true success and false on error
949      *
950      * @access  public
951      */
952     function fileExists($file)
953     {
954         // safe_mode does notwork with is_readable()
955         if (!@ini_get('safe_mode')) {
956              $dirs = explode(PATH_SEPARATOR, ini_get('include_path'));
957              foreach ($dirs as $dir) {
958                  if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
959                      return true;
960                  }
961             }
962         } else {
963             $fp = @fopen($file, 'r', true);
964             if (is_resource($fp)) {
965                 @fclose($fp);
966                 return true;
967             }
968         }
969         return false;
970     }
971     // }}}
972 }
973
974 // }}}
975 // {{{ class MDB2_Error extends PEAR_Error
976
977 /**
978  * MDB2_Error implements a class for reporting portable database error
979  * messages.
980  *
981  * @package     MDB2
982  * @category    Database
983  * @author Stig Bakken <ssb@fast.no>
984  */
985 class MDB2_Error extends PEAR_Error
986 {
987     // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null)
988
989     /**
990      * MDB2_Error constructor.
991      *
992      * @param   mixed   MDB2 error code, or string with error message.
993      * @param   int     what 'error mode' to operate in
994      * @param   int     what error level to use for $mode & PEAR_ERROR_TRIGGER
995      * @param   mixed   additional debug info, such as the last query
996      */
997     function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN,
998               $level = E_USER_NOTICE, $debuginfo = null, $dummy = null)
999     {
1000         if (is_null($code)) {
1001             $code = MDB2_ERROR;
1002         }
1003         $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code,
1004             $mode, $level, $debuginfo);
1005     }
1006
1007     // }}}
1008 }
1009
1010 // }}}
1011 // {{{ class MDB2_Driver_Common extends PEAR
1012
1013 /**
1014  * MDB2_Driver_Common: Base class that is extended by each MDB2 driver
1015  *
1016  * @package     MDB2
1017  * @category    Database
1018  * @author      Lukas Smith <smith@pooteeweet.org>
1019  */
1020 class MDB2_Driver_Common extends PEAR
1021 {
1022     // {{{ Variables (Properties)
1023
1024     /**
1025      * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array
1026      * @var     int
1027      * @access  public
1028      */
1029     var $db_index = 0;
1030
1031     /**
1032      * DSN used for the next query
1033      * @var     array
1034      * @access  protected
1035      */
1036     var $dsn = array();
1037
1038     /**
1039      * DSN that was used to create the current connection
1040      * @var     array
1041      * @access  protected
1042      */
1043     var $connected_dsn = array();
1044
1045     /**
1046      * connection resource
1047      * @var     mixed
1048      * @access  protected
1049      */
1050     var $connection = 0;
1051
1052     /**
1053      * if the current opened connection is a persistent connection
1054      * @var     bool
1055      * @access  protected
1056      */
1057     var $opened_persistent;
1058
1059     /**
1060      * the name of the database for the next query
1061      * @var     string
1062      * @access  protected
1063      */
1064     var $database_name = '';
1065
1066     /**
1067      * the name of the database currently selected
1068      * @var     string
1069      * @access  protected
1070      */
1071     var $connected_database_name = '';
1072
1073     /**
1074      * server version information
1075      * @var     string
1076      * @access  protected
1077      */
1078     var $connected_server_info = '';
1079
1080     /**
1081      * list of all supported features of the given driver
1082      * @var     array
1083      * @access  public
1084      */
1085     var $supported = array(
1086         'sequences' => false,
1087         'indexes' => false,
1088         'affected_rows' => false,
1089         'summary_functions' => false,
1090         'order_by_text' => false,
1091         'transactions' => false,
1092         'savepoints' => false,
1093         'current_id' => false,
1094         'limit_queries' => false,
1095         'LOBs' => false,
1096         'replace' => false,
1097         'sub_selects' => false,
1098         'auto_increment' => false,
1099         'primary_key' => false,
1100         'result_introspection' => false,
1101         'prepared_statements' => false,
1102         'identifier_quoting' => false,
1103         'pattern_escaping' => false,
1104         'new_link' => false,
1105     );
1106
1107     /**
1108      * Array of supported options that can be passed to the MDB2 instance.
1109      * 
1110      * The options can be set during object creation, using
1111      * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can 
1112      * also be set after the object is created, using MDB2::setOptions() or 
1113      * MDB2_Driver_Common::setOption().
1114      * The list of available option includes:
1115      * <ul>
1116      *  <li>$options['ssl'] -> boolean: determines if ssl should be used for connections</li>
1117      *  <li>$options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names</li>
1118      *  <li>$options['disable_query'] -> boolean: determines if queries should be executed</li>
1119      *  <li>$options['result_class'] -> string: class used for result sets</li>
1120      *  <li>$options['buffered_result_class'] -> string: class used for buffered result sets</li>
1121      *  <li>$options['result_wrap_class'] -> string: class used to wrap result sets into</li>
1122      *  <li>$options['result_buffering'] -> boolean should results be buffered or not?</li>
1123      *  <li>$options['fetch_class'] -> string: class to use when fetch mode object is used</li>
1124      *  <li>$options['persistent'] -> boolean: persistent connection?</li>
1125      *  <li>$options['debug'] -> integer: numeric debug level</li>
1126      *  <li>$options['debug_handler'] -> string: function/method that captures debug messages</li>
1127      *  <li>$options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler</li>
1128      *  <li>$options['default_text_field_length'] -> integer: default text field length to use</li>
1129      *  <li>$options['lob_buffer_length'] -> integer: LOB buffer length</li>
1130      *  <li>$options['log_line_break'] -> string: line-break format</li>
1131      *  <li>$options['idxname_format'] -> string: pattern for index name</li>
1132      *  <li>$options['seqname_format'] -> string: pattern for sequence name</li>
1133      *  <li>$options['savepoint_format'] -> string: pattern for auto generated savepoint names</li>
1134      *  <li>$options['statement_format'] -> string: pattern for prepared statement names</li>
1135      *  <li>$options['seqcol_name'] -> string: sequence column name</li>
1136      *  <li>$options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used</li>
1137      *  <li>$options['use_transactions'] -> boolean: if transaction use should be enabled</li>
1138      *  <li>$options['decimal_places'] -> integer: number of decimal places to handle</li>
1139      *  <li>$options['portability'] -> integer: portability constant</li>
1140      *  <li>$options['modules'] -> array: short to long module name mapping for __call()</li>
1141      *  <li>$options['emulate_prepared'] -> boolean: force prepared statements to be emulated</li>
1142      *  <li>$options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes</li>
1143      *  <li>$options['datatype_map_callback'] -> array: callback function/method that should be called</li>
1144      *  <li>$options['bindname_format'] -> string: regular expression pattern for named parameters
1145      * </ul>
1146      *
1147      * @var     array
1148      * @access  public
1149      * @see     MDB2::connect()
1150      * @see     MDB2::factory()
1151      * @see     MDB2::singleton()
1152      * @see     MDB2_Driver_Common::setOption()
1153      */
1154     var $options = array(
1155         'ssl' => false,
1156         'field_case' => CASE_LOWER,
1157         'disable_query' => false,
1158         'result_class' => 'MDB2_Result_%s',
1159         'buffered_result_class' => 'MDB2_BufferedResult_%s',
1160         'result_wrap_class' => false,
1161         'result_buffering' => true,
1162         'fetch_class' => 'stdClass',
1163         'persistent' => false,
1164         'debug' => 0,
1165         'debug_handler' => 'MDB2_defaultDebugOutput',
1166         'debug_expanded_output' => false,
1167         'default_text_field_length' => 4096,
1168         'lob_buffer_length' => 8192,
1169         'log_line_break' => "\n",
1170         'idxname_format' => '%s_idx',
1171         'seqname_format' => '%s_seq',
1172         'savepoint_format' => 'MDB2_SAVEPOINT_%s',
1173         'statement_format' => 'MDB2_STATEMENT_%1$s_%2$s',
1174         'seqcol_name' => 'sequence',
1175         'quote_identifier' => false,
1176         'use_transactions' => true,
1177         'decimal_places' => 2,
1178         'portability' => MDB2_PORTABILITY_ALL,
1179         'modules' => array(
1180             'ex' => 'Extended',
1181             'dt' => 'Datatype',
1182             'mg' => 'Manager',
1183             'rv' => 'Reverse',
1184             'na' => 'Native',
1185             'fc' => 'Function',
1186         ),
1187         'emulate_prepared' => false,
1188         'datatype_map' => array(),
1189         'datatype_map_callback' => array(),
1190         'nativetype_map_callback' => array(),
1191         'lob_allow_url_include' => false,
1192         'bindname_format' => '(?:\d+)|(?:[a-zA-Z][a-zA-Z0-9_]*)',
1193     );
1194
1195     /**
1196      * string array
1197      * @var     string
1198      * @access  protected
1199      */
1200     var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => false, 'escape_pattern' => false);
1201
1202     /**
1203      * identifier quoting
1204      * @var     array
1205      * @access  protected
1206      */
1207     var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"');
1208
1209     /**
1210      * sql comments
1211      * @var     array
1212      * @access  protected
1213      */
1214     var $sql_comments = array(
1215         array('start' => '--', 'end' => "\n", 'escape' => false),
1216         array('start' => '/*', 'end' => '*/', 'escape' => false),
1217     );
1218
1219     /**
1220      * comparision wildcards
1221      * @var     array
1222      * @access  protected
1223      */
1224     var $wildcards = array('%', '_');
1225
1226     /**
1227      * column alias keyword
1228      * @var     string
1229      * @access  protected
1230      */
1231     var $as_keyword = ' AS ';
1232
1233     /**
1234      * warnings
1235      * @var     array
1236      * @access  protected
1237      */
1238     var $warnings = array();
1239
1240     /**
1241      * string with the debugging information
1242      * @var     string
1243      * @access  public
1244      */
1245     var $debug_output = '';
1246
1247     /**
1248      * determine if there is an open transaction
1249      * @var     bool
1250      * @access  protected
1251      */
1252     var $in_transaction = false;
1253
1254     /**
1255      * the smart transaction nesting depth
1256      * @var     int
1257      * @access  protected
1258      */
1259     var $nested_transaction_counter = null;
1260
1261     /**
1262      * the first error that occured inside a nested transaction
1263      * @var     MDB2_Error|bool
1264      * @access  protected
1265      */
1266     var $has_transaction_error = false;
1267
1268     /**
1269      * result offset used in the next query
1270      * @var     int
1271      * @access  protected
1272      */
1273     var $offset = 0;
1274
1275     /**
1276      * result limit used in the next query
1277      * @var     int
1278      * @access  protected
1279      */
1280     var $limit = 0;
1281
1282     /**
1283      * Database backend used in PHP (mysql, odbc etc.)
1284      * @var     string
1285      * @access  public
1286      */
1287     var $phptype;
1288
1289     /**
1290      * Database used with regards to SQL syntax etc.
1291      * @var     string
1292      * @access  public
1293      */
1294     var $dbsyntax;
1295
1296     /**
1297      * the last query sent to the driver
1298      * @var     string
1299      * @access  public
1300      */
1301     var $last_query;
1302
1303     /**
1304      * the default fetchmode used
1305      * @var     int
1306      * @access  protected
1307      */
1308     var $fetchmode = MDB2_FETCHMODE_ORDERED;
1309
1310     /**
1311      * array of module instances
1312      * @var     array
1313      * @access  protected
1314      */
1315     var $modules = array();
1316
1317     /**
1318      * determines of the PHP4 destructor emulation has been enabled yet
1319      * @var     array
1320      * @access  protected
1321      */
1322     var $destructor_registered = true;
1323
1324     // }}}
1325     // {{{ constructor: function __construct()
1326
1327     /**
1328      * Constructor
1329      */
1330     function __construct()
1331     {
1332         end($GLOBALS['_MDB2_databases']);
1333         $db_index = key($GLOBALS['_MDB2_databases']) + 1;
1334         $GLOBALS['_MDB2_databases'][$db_index] = &$this;
1335         $this->db_index = $db_index;
1336     }
1337
1338     // }}}
1339     // {{{ function MDB2_Driver_Common()
1340
1341     /**
1342      * PHP 4 Constructor
1343      */
1344     function MDB2_Driver_Common()
1345     {
1346         $this->destructor_registered = false;
1347         $this->__construct();
1348     }
1349
1350     // }}}
1351     // {{{ destructor: function __destruct()
1352
1353     /**
1354      *  Destructor
1355      */
1356     function __destruct()
1357     {
1358         $this->disconnect(false);
1359     }
1360
1361     // }}}
1362     // {{{ function free()
1363
1364     /**
1365      * Free the internal references so that the instance can be destroyed
1366      *
1367      * @return  bool    true on success, false if result is invalid
1368      *
1369      * @access  public
1370      */
1371     function free()
1372     {
1373         unset($GLOBALS['_MDB2_databases'][$this->db_index]);
1374         unset($this->db_index);
1375         return MDB2_OK;
1376     }
1377
1378     // }}}
1379     // {{{ function __toString()
1380
1381     /**
1382      * String conversation
1383      *
1384      * @return  string representation of the object
1385      *
1386      * @access  public
1387      */
1388     function __toString()
1389     {
1390         $info = get_class($this);
1391         $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')';
1392         if ($this->connection) {
1393             $info.= ' [connected]';
1394         }
1395         return $info;
1396     }
1397
1398     // }}}
1399     // {{{ function errorInfo($error = null)
1400
1401     /**
1402      * This method is used to collect information about an error
1403      *
1404      * @param   mixed   error code or resource
1405      *
1406      * @return  array   with MDB2 errorcode, native error code, native message
1407      *
1408      * @access  public
1409      */
1410     function errorInfo($error = null)
1411     {
1412         return array($error, null, null);
1413     }
1414
1415     // }}}
1416     // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
1417
1418     /**
1419      * This method is used to communicate an error and invoke error
1420      * callbacks etc.  Basically a wrapper for PEAR::raiseError
1421      * without the message string.
1422      *
1423      * @param   mixed   integer error code, or a PEAR error object (all other
1424      *                  parameters are ignored if this parameter is an object
1425      * @param   int     error mode, see PEAR_Error docs
1426      * @param   mixed   If error mode is PEAR_ERROR_TRIGGER, this is the
1427          *              error level (E_USER_NOTICE etc).  If error mode is
1428      *                  PEAR_ERROR_CALLBACK, this is the callback function,
1429      *                  either as a function name, or as an array of an
1430      *                  object and method name.  For other error modes this
1431      *                  parameter is ignored.
1432      * @param   string  Extra debug information.  Defaults to the last
1433      *                  query and native error code.
1434      * @param   string  name of the method that triggered the error
1435      *
1436      * @return PEAR_Error   instance of a PEAR Error object
1437      *
1438      * @access  public
1439      * @see     PEAR_Error
1440      */
1441     function &raiseError($code = null, $mode = null, $options = null, $userinfo = null, $method = null)
1442     {
1443         $userinfo = "[Error message: $userinfo]\n";
1444         // The error is yet a MDB2 error object
1445         if (PEAR::isError($code)) {
1446             // because we use the static PEAR::raiseError, our global
1447             // handler should be used if it is set
1448             if (is_null($mode) && !empty($this->_default_error_mode)) {
1449                 $mode    = $this->_default_error_mode;
1450                 $options = $this->_default_error_options;
1451             }
1452             if (is_null($userinfo)) {
1453                 $userinfo = $code->getUserinfo();
1454             }
1455             $code = $code->getCode();
1456         } elseif ($code == MDB2_ERROR_NOT_FOUND) {
1457             // extension not loaded: don't call $this->errorInfo() or the script
1458             // will die
1459         } elseif (isset($this->connection)) {
1460             if (!empty($this->last_query)) {
1461                 $userinfo.= "[Last executed query: {$this->last_query}]\n";
1462             }
1463             $native_errno = $native_msg = null;
1464             list($code, $native_errno, $native_msg) = $this->errorInfo($code);
1465             if (!is_null($native_errno) && $native_errno !== '') {
1466                 $userinfo.= "[Native code: $native_errno]\n";
1467             }
1468             if (!is_null($native_msg) && $native_msg !== '') {
1469                 $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n";
1470             }
1471             if (!is_null($method)) {
1472                 $userinfo = $method.': '.$userinfo;
1473             }
1474         }
1475
1476         $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true);
1477         if ($err->getMode() !== PEAR_ERROR_RETURN
1478             && isset($this->nested_transaction_counter) && !$this->has_transaction_error) {
1479             $this->has_transaction_error =& $err;
1480         }
1481         return $err;
1482     }
1483
1484     // }}}
1485     // {{{ function resetWarnings()
1486
1487     /**
1488      * reset the warning array
1489      *
1490      * @return void
1491      *
1492      * @access  public
1493      */
1494     function resetWarnings()
1495     {
1496         $this->warnings = array();
1497     }
1498
1499     // }}}
1500     // {{{ function getWarnings()
1501
1502     /**
1503      * Get all warnings in reverse order.
1504      * This means that the last warning is the first element in the array
1505      *
1506      * @return  array   with warnings
1507      *
1508      * @access  public
1509      * @see     resetWarnings()
1510      */
1511     function getWarnings()
1512     {
1513         return array_reverse($this->warnings);
1514     }
1515
1516     // }}}
1517     // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass')
1518
1519     /**
1520      * Sets which fetch mode should be used by default on queries
1521      * on this connection
1522      *
1523      * @param   int     MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC
1524      *                               or MDB2_FETCHMODE_OBJECT
1525      * @param   string  the class name of the object to be returned
1526      *                               by the fetch methods when the
1527      *                               MDB2_FETCHMODE_OBJECT mode is selected.
1528      *                               If no class is specified by default a cast
1529      *                               to object from the assoc array row will be
1530      *                               done.  There is also the possibility to use
1531      *                               and extend the 'MDB2_row' class.
1532      *
1533      * @return  mixed   MDB2_OK or MDB2 Error Object
1534      *
1535      * @access  public
1536      * @see     MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT
1537      */
1538     function setFetchMode($fetchmode, $object_class = 'stdClass')
1539     {
1540         switch ($fetchmode) {
1541         case MDB2_FETCHMODE_OBJECT:
1542             $this->options['fetch_class'] = $object_class;
1543         case MDB2_FETCHMODE_ORDERED:
1544         case MDB2_FETCHMODE_ASSOC:
1545             $this->fetchmode = $fetchmode;
1546             break;
1547         default:
1548             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
1549                 'invalid fetchmode mode', __FUNCTION__);
1550         }
1551
1552         return MDB2_OK;
1553     }
1554
1555     // }}}
1556     // {{{ function setOption($option, $value)
1557
1558     /**
1559      * set the option for the db class
1560      *
1561      * @param   string  option name
1562      * @param   mixed   value for the option
1563      *
1564      * @return  mixed   MDB2_OK or MDB2 Error Object
1565      *
1566      * @access  public
1567      */
1568     function setOption($option, $value)
1569     {
1570         if (array_key_exists($option, $this->options)) {
1571             $this->options[$option] = $value;
1572             return MDB2_OK;
1573         }
1574         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
1575             "unknown option $option", __FUNCTION__);
1576     }
1577
1578     // }}}
1579     // {{{ function getOption($option)
1580
1581     /**
1582      * Returns the value of an option
1583      *
1584      * @param   string  option name
1585      *
1586      * @return  mixed   the option value or error object
1587      *
1588      * @access  public
1589      */
1590     function getOption($option)
1591     {
1592         if (array_key_exists($option, $this->options)) {
1593             return $this->options[$option];
1594         }
1595         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
1596             "unknown option $option", __FUNCTION__);
1597     }
1598
1599     // }}}
1600     // {{{ function debug($message, $scope = '', $is_manip = null)
1601
1602     /**
1603      * set a debug message
1604      *
1605      * @param   string  message that should be appended to the debug variable
1606      * @param   string  usually the method name that triggered the debug call:
1607      *                  for example 'query', 'prepare', 'execute', 'parameters',
1608      *                  'beginTransaction', 'commit', 'rollback'
1609      * @param   array   contains context information about the debug() call
1610      *                  common keys are: is_manip, time, result etc.
1611      *
1612      * @return void
1613      *
1614      * @access  public
1615      */
1616     function debug($message, $scope = '', $context = array())
1617     {
1618         if ($this->options['debug'] && $this->options['debug_handler']) {
1619             if (!$this->options['debug_expanded_output']) {
1620                 if (!empty($context['when']) && $context['when'] !== 'pre') {
1621                     return null;
1622                 }
1623                 $context = empty($context['is_manip']) ? false : $context['is_manip'];
1624             }
1625             return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context));
1626         }
1627         return null;
1628     }
1629
1630     // }}}
1631     // {{{ function getDebugOutput()
1632
1633     /**
1634      * output debug info
1635      *
1636      * @return  string  content of the debug_output class variable
1637      *
1638      * @access  public
1639      */
1640     function getDebugOutput()
1641     {
1642         return $this->debug_output;
1643     }
1644
1645     // }}}
1646     // {{{ function escape($text)
1647
1648     /**
1649      * Quotes a string so it can be safely used in a query. It will quote
1650      * the text so it can safely be used within a query.
1651      *
1652      * @param   string  the input string to quote
1653      * @param   bool    escape wildcards
1654      *
1655      * @return  string  quoted string
1656      *
1657      * @access  public
1658      */
1659     function escape($text, $escape_wildcards = false)
1660     {
1661         if ($escape_wildcards) {
1662             $text = $this->escapePattern($text);
1663         }
1664
1665         $text = str_replace($this->string_quoting['end'], $this->string_quoting['escape'] . $this->string_quoting['end'], $text);
1666         return $text;
1667     }
1668
1669     // }}}
1670     // {{{ function escapePattern($text)
1671
1672     /**
1673      * Quotes pattern (% and _) characters in a string)
1674      *
1675      * @param   string  the input string to quote
1676      *
1677      * @return  string  quoted string
1678      *
1679      * @access  public
1680      */
1681     function escapePattern($text)
1682     {
1683         if ($this->string_quoting['escape_pattern']) {
1684             $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text);
1685             foreach ($this->wildcards as $wildcard) {
1686                 $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text);
1687             }
1688         }
1689         return $text;
1690     }
1691
1692     // }}}
1693     // {{{ function quoteIdentifier($str, $check_option = false)
1694
1695     /**
1696      * Quote a string so it can be safely used as a table or column name
1697      *
1698      * Delimiting style depends on which database driver is being used.
1699      *
1700      * NOTE: just because you CAN use delimited identifiers doesn't mean
1701      * you SHOULD use them.  In general, they end up causing way more
1702      * problems than they solve.
1703      *
1704      * NOTE: if you have table names containing periods, don't use this method
1705      * (@see bug #11906)
1706      *
1707      * Portability is broken by using the following characters inside
1708      * delimited identifiers:
1709      *   + backtick (<kbd>`</kbd>) -- due to MySQL
1710      *   + double quote (<kbd>"</kbd>) -- due to Oracle
1711      *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
1712      *
1713      * Delimited identifiers are known to generally work correctly under
1714      * the following drivers:
1715      *   + mssql
1716      *   + mysql
1717      *   + mysqli
1718      *   + oci8
1719      *   + pgsql
1720      *   + sqlite
1721      *
1722      * InterBase doesn't seem to be able to use delimited identifiers
1723      * via PHP 4.  They work fine under PHP 5.
1724      *
1725      * @param   string  identifier name to be quoted
1726      * @param   bool    check the 'quote_identifier' option
1727      *
1728      * @return  string  quoted identifier string
1729      *
1730      * @access  public
1731      */
1732     function quoteIdentifier($str, $check_option = false)
1733     {
1734         if ($check_option && !$this->options['quote_identifier']) {
1735             return $str;
1736         }
1737         $str = str_replace($this->identifier_quoting['end'], $this->identifier_quoting['escape'] . $this->identifier_quoting['end'], $str);
1738         $parts = explode('.', $str);
1739         foreach (array_keys($parts) as $k) {
1740             $parts[$k] = $this->identifier_quoting['start'] . $parts[$k] . $this->identifier_quoting['end'];
1741         }
1742         return implode('.', $parts);
1743     }
1744
1745     // }}}
1746     // {{{ function getAsKeyword()
1747
1748     /**
1749      * Gets the string to alias column
1750      *
1751      * @return string to use when aliasing a column
1752      */
1753     function getAsKeyword()
1754     {
1755         return $this->as_keyword;
1756     }
1757
1758     // }}}
1759     // {{{ function getConnection()
1760
1761     /**
1762      * Returns a native connection
1763      *
1764      * @return  mixed   a valid MDB2 connection object,
1765      *                  or a MDB2 error object on error
1766      *
1767      * @access  public
1768      */
1769     function getConnection()
1770     {
1771         $result = $this->connect();
1772         if (PEAR::isError($result)) {
1773             return $result;
1774         }
1775         return $this->connection;
1776     }
1777
1778      // }}}
1779     // {{{ function _fixResultArrayValues(&$row, $mode)
1780
1781     /**
1782      * Do all necessary conversions on result arrays to fix DBMS quirks
1783      *
1784      * @param   array   the array to be fixed (passed by reference)
1785      * @param   array   bit-wise addition of the required portability modes
1786      *
1787      * @return  void
1788      *
1789      * @access  protected
1790      */
1791     function _fixResultArrayValues(&$row, $mode)
1792     {
1793         switch ($mode) {
1794         case MDB2_PORTABILITY_EMPTY_TO_NULL:
1795             foreach ($row as $key => $value) {
1796                 if ($value === '') {
1797                     $row[$key] = null;
1798                 }
1799             }
1800             break;
1801         case MDB2_PORTABILITY_RTRIM:
1802             foreach ($row as $key => $value) {
1803                 if (is_string($value)) {
1804                     $row[$key] = rtrim($value);
1805                 }
1806             }
1807             break;
1808         case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES:
1809             $tmp_row = array();
1810             foreach ($row as $key => $value) {
1811                 $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
1812             }
1813             $row = $tmp_row;
1814             break;
1815         case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL):
1816             foreach ($row as $key => $value) {
1817                 if ($value === '') {
1818                     $row[$key] = null;
1819                 } elseif (is_string($value)) {
1820                     $row[$key] = rtrim($value);
1821                 }
1822             }
1823             break;
1824         case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
1825             $tmp_row = array();
1826             foreach ($row as $key => $value) {
1827                 if (is_string($value)) {
1828                     $value = rtrim($value);
1829                 }
1830                 $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
1831             }
1832             $row = $tmp_row;
1833             break;
1834         case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
1835             $tmp_row = array();
1836             foreach ($row as $key => $value) {
1837                 if ($value === '') {
1838                     $value = null;
1839                 }
1840                 $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
1841             }
1842             $row = $tmp_row;
1843             break;
1844         case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES):
1845             $tmp_row = array();
1846             foreach ($row as $key => $value) {
1847                 if ($value === '') {
1848                     $value = null;
1849                 } elseif (is_string($value)) {
1850                     $value = rtrim($value);
1851                 }
1852                 $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value;
1853             }
1854             $row = $tmp_row;
1855             break;
1856         }
1857     }
1858
1859     // }}}
1860     // {{{ function &loadModule($module, $property = null, $phptype_specific = null)
1861
1862     /**
1863      * loads a module
1864      *
1865      * @param   string  name of the module that should be loaded
1866      *                  (only used for error messages)
1867      * @param   string  name of the property into which the class will be loaded
1868      * @param   bool    if the class to load for the module is specific to the
1869      *                  phptype
1870      *
1871      * @return  object  on success a reference to the given module is returned
1872      *                  and on failure a PEAR error
1873      *
1874      * @access  public
1875      */
1876     function &loadModule($module, $property = null, $phptype_specific = null)
1877     {
1878         if (!$property) {
1879             $property = strtolower($module);
1880         }
1881
1882         if (!isset($this->{$property})) {
1883             $version = $phptype_specific;
1884             if ($phptype_specific !== false) {
1885                 $version = true;
1886                 $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype;
1887                 $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
1888             }
1889             if ($phptype_specific === false
1890                 || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name))
1891             ) {
1892                 $version = false;
1893                 $class_name = 'MDB2_'.$module;
1894                 $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
1895             }
1896
1897             $err = MDB2::loadClass($class_name, $this->getOption('debug'));
1898             if (PEAR::isError($err)) {
1899                 return $err;
1900             }
1901
1902             // load module in a specific version
1903             if ($version) {
1904                 if (method_exists($class_name, 'getClassName')) {
1905                     $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index);
1906                     if ($class_name != $class_name_new) {
1907                         $class_name = $class_name_new;
1908                         $err = MDB2::loadClass($class_name, $this->getOption('debug'));
1909                         if (PEAR::isError($err)) {
1910                             return $err;
1911                         }
1912                     }
1913                 }
1914             }
1915
1916             if (!MDB2::classExists($class_name)) {
1917                 $err =& $this->raiseError(MDB2_ERROR_LOADMODULE, null, null,
1918                     "unable to load module '$module' into property '$property'", __FUNCTION__);
1919                 return $err;
1920             }
1921             $this->{$property} = new $class_name($this->db_index);
1922             $this->modules[$module] =& $this->{$property};
1923             if ($version) {
1924                 // this will be used in the connect method to determine if the module
1925                 // needs to be loaded with a different version if the server
1926                 // version changed in between connects
1927                 $this->loaded_version_modules[] = $property;
1928             }
1929         }
1930
1931         return $this->{$property};
1932     }
1933
1934     // }}}
1935     // {{{ function __call($method, $params)
1936
1937     /**
1938      * Calls a module method using the __call magic method
1939      *
1940      * @param   string  Method name.
1941      * @param   array   Arguments.
1942      *
1943      * @return  mixed   Returned value.
1944      */
1945     function __call($method, $params)
1946     {
1947         $module = null;
1948         if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match)
1949             && isset($this->options['modules'][$match[1]])
1950         ) {
1951             $module = $this->options['modules'][$match[1]];
1952             $method = strtolower($match[2]).$match[3];
1953             if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) {
1954                 $result =& $this->loadModule($module);
1955                 if (PEAR::isError($result)) {
1956                     return $result;
1957                 }
1958             }
1959         } else {
1960             foreach ($this->modules as $key => $foo) {
1961                 if (is_object($this->modules[$key])
1962                     && method_exists($this->modules[$key], $method)
1963                 ) {
1964                     $module = $key;
1965                     break;
1966                 }
1967             }
1968         }
1969         if (!is_null($module)) {
1970             return call_user_func_array(array(&$this->modules[$module], $method), $params);
1971         }
1972         trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR);
1973     }
1974
1975     // }}}
1976     // {{{ function beginTransaction($savepoint = null)
1977
1978     /**
1979      * Start a transaction or set a savepoint.
1980      *
1981      * @param   string  name of a savepoint to set
1982      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
1983      *
1984      * @access  public
1985      */
1986     function beginTransaction($savepoint = null)
1987     {
1988         $this->debug('Starting transaction', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
1989         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
1990             'transactions are not supported', __FUNCTION__);
1991     }
1992
1993     // }}}
1994     // {{{ function commit($savepoint = null)
1995
1996     /**
1997      * Commit the database changes done during a transaction that is in
1998      * progress or release a savepoint. This function may only be called when
1999      * auto-committing is disabled, otherwise it will fail. Therefore, a new
2000      * transaction is implicitly started after committing the pending changes.
2001      *
2002      * @param   string  name of a savepoint to release
2003      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2004      *
2005      * @access  public
2006      */
2007     function commit($savepoint = null)
2008     {
2009         $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
2010         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2011             'commiting transactions is not supported', __FUNCTION__);
2012     }
2013
2014     // }}}
2015     // {{{ function rollback($savepoint = null)
2016
2017     /**
2018      * Cancel any database changes done during a transaction or since a specific
2019      * savepoint that is in progress. This function may only be called when
2020      * auto-committing is disabled, otherwise it will fail. Therefore, a new
2021      * transaction is implicitly started after canceling the pending changes.
2022      *
2023      * @param   string  name of a savepoint to rollback to
2024      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2025      *
2026      * @access  public
2027      */
2028     function rollback($savepoint = null)
2029     {
2030         $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
2031         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2032             'rolling back transactions is not supported', __FUNCTION__);
2033     }
2034
2035     // }}}
2036     // {{{ function inTransaction($ignore_nested = false)
2037
2038     /**
2039      * If a transaction is currently open.
2040      *
2041      * @param   bool    if the nested transaction count should be ignored
2042      * @return  int|bool    - an integer with the nesting depth is returned if a
2043      *                      nested transaction is open
2044      *                      - true is returned for a normal open transaction
2045      *                      - false is returned if no transaction is open
2046      *
2047      * @access  public
2048      */
2049     function inTransaction($ignore_nested = false)
2050     {
2051         if (!$ignore_nested && isset($this->nested_transaction_counter)) {
2052             return $this->nested_transaction_counter;
2053         }
2054         return $this->in_transaction;
2055     }
2056
2057     // }}}
2058     // {{{ function setTransactionIsolation($isolation)
2059
2060     /**
2061      * Set the transacton isolation level.
2062      *
2063      * @param   string  standard isolation level
2064      *                  READ UNCOMMITTED (allows dirty reads)
2065      *                  READ COMMITTED (prevents dirty reads)
2066      *                  REPEATABLE READ (prevents nonrepeatable reads)
2067      *                  SERIALIZABLE (prevents phantom reads)
2068      * @param   array some transaction options:
2069      *                  'wait' => 'WAIT' | 'NO WAIT'
2070      *                  'rw'   => 'READ WRITE' | 'READ ONLY'
2071      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2072      *
2073      * @access  public
2074      * @since   2.1.1
2075      */
2076     function setTransactionIsolation($isolation, $options = array())
2077     {
2078         $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
2079         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2080             'isolation level setting is not supported', __FUNCTION__);
2081     }
2082
2083     // }}}
2084     // {{{ function beginNestedTransaction($savepoint = false)
2085
2086     /**
2087      * Start a nested transaction.
2088      *
2089      * @return  mixed   MDB2_OK on success/savepoint name, a MDB2 error on failure
2090      *
2091      * @access  public
2092      * @since   2.1.1
2093      */
2094     function beginNestedTransaction()
2095     {
2096         if ($this->in_transaction) {
2097             ++$this->nested_transaction_counter;
2098             $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter);
2099             if ($this->supports('savepoints') && $savepoint) {
2100                 return $this->beginTransaction($savepoint);
2101             }
2102             return MDB2_OK;
2103         }
2104         $this->has_transaction_error = false;
2105         $result = $this->beginTransaction();
2106         $this->nested_transaction_counter = 1;
2107         return $result;
2108     }
2109
2110     // }}}
2111     // {{{ function completeNestedTransaction($force_rollback = false, $release = false)
2112
2113     /**
2114      * Finish a nested transaction by rolling back if an error occured or
2115      * committing otherwise.
2116      *
2117      * @param   bool    if the transaction should be rolled back regardless
2118      *                  even if no error was set within the nested transaction
2119      * @return  mixed   MDB_OK on commit/counter decrementing, false on rollback
2120      *                  and a MDB2 error on failure
2121      *
2122      * @access  public
2123      * @since   2.1.1
2124      */
2125     function completeNestedTransaction($force_rollback = false)
2126     {
2127         if ($this->nested_transaction_counter > 1) {
2128             $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter);
2129             if ($this->supports('savepoints') && $savepoint) {
2130                 if ($force_rollback || $this->has_transaction_error) {
2131                     $result = $this->rollback($savepoint);
2132                     if (!PEAR::isError($result)) {
2133                         $result = false;
2134                         $this->has_transaction_error = false;
2135                     }
2136                 } else {
2137                     $result = $this->commit($savepoint);
2138                 }
2139             } else {
2140                 $result = MDB2_OK;
2141             }
2142             --$this->nested_transaction_counter;
2143             return $result;
2144         }
2145
2146         $this->nested_transaction_counter = null;
2147         $result = MDB2_OK;
2148
2149         // transaction has not yet been rolled back
2150         if ($this->in_transaction) {
2151             if ($force_rollback || $this->has_transaction_error) {
2152                 $result = $this->rollback();
2153                 if (!PEAR::isError($result)) {
2154                     $result = false;
2155                 }
2156             } else {
2157                 $result = $this->commit();
2158             }
2159         }
2160         $this->has_transaction_error = false;
2161         return $result;
2162     }
2163
2164     // }}}
2165     // {{{ function failNestedTransaction($error = null, $immediately = false)
2166
2167     /**
2168      * Force setting nested transaction to failed.
2169      *
2170      * @param   mixed   value to return in getNestededTransactionError()
2171      * @param   bool    if the transaction should be rolled back immediately
2172      * @return  bool    MDB2_OK
2173      *
2174      * @access  public
2175      * @since   2.1.1
2176      */
2177     function failNestedTransaction($error = null, $immediately = false)
2178     {
2179         if (is_null($error)) {
2180             $error = $this->has_transaction_error ? $this->has_transaction_error : true;
2181         } elseif (!$error) {
2182             $error = true;
2183         }
2184         $this->has_transaction_error = $error;
2185         if (!$immediately) {
2186             return MDB2_OK;
2187         }
2188         return $this->rollback();
2189     }
2190
2191     // }}}
2192     // {{{ function getNestedTransactionError()
2193
2194     /**
2195      * The first error that occured since the transaction start.
2196      *
2197      * @return  MDB2_Error|bool     MDB2 error object if an error occured or false.
2198      *
2199      * @access  public
2200      * @since   2.1.1
2201      */
2202     function getNestedTransactionError()
2203     {
2204         return $this->has_transaction_error;
2205     }
2206
2207     // }}}
2208     // {{{ connect()
2209
2210     /**
2211      * Connect to the database
2212      *
2213      * @return true on success, MDB2 Error Object on failure
2214      */
2215     function connect()
2216     {
2217         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2218             'method not implemented', __FUNCTION__);
2219     }
2220
2221     // }}}
2222     // {{{ setCharset($charset, $connection = null)
2223
2224     /**
2225      * Set the charset on the current connection
2226      *
2227      * @param string    charset
2228      * @param resource  connection handle
2229      *
2230      * @return true on success, MDB2 Error Object on failure
2231      */
2232     function setCharset($charset, $connection = null)
2233     {
2234         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2235             'method not implemented', __FUNCTION__);
2236     }
2237
2238     // }}}
2239     // {{{ function disconnect($force = true)
2240
2241     /**
2242      * Log out and disconnect from the database.
2243      *
2244      * @param   bool    if the disconnect should be forced even if the
2245      *                  connection is opened persistently
2246      *
2247      * @return  mixed   true on success, false if not connected and error
2248      *                  object on error
2249      *
2250      * @access  public
2251      */
2252     function disconnect($force = true)
2253     {
2254         $this->connection = 0;
2255         $this->connected_dsn = array();
2256         $this->connected_database_name = '';
2257         $this->opened_persistent = null;
2258         $this->connected_server_info = '';
2259         $this->in_transaction = null;
2260         $this->nested_transaction_counter = null;
2261         return MDB2_OK;
2262     }
2263
2264     // }}}
2265     // {{{ function setDatabase($name)
2266
2267     /**
2268      * Select a different database
2269      *
2270      * @param   string  name of the database that should be selected
2271      *
2272      * @return  string  name of the database previously connected to
2273      *
2274      * @access  public
2275      */
2276     function setDatabase($name)
2277     {
2278         $previous_database_name = (isset($this->database_name)) ? $this->database_name : '';
2279         $this->database_name = $name;
2280         $this->disconnect(false);
2281         return $previous_database_name;
2282     }
2283
2284     // }}}
2285     // {{{ function getDatabase()
2286
2287     /**
2288      * Get the current database
2289      *
2290      * @return  string  name of the database
2291      *
2292      * @access  public
2293      */
2294     function getDatabase()
2295     {
2296         return $this->database_name;
2297     }
2298
2299     // }}}
2300     // {{{ function setDSN($dsn)
2301
2302     /**
2303      * set the DSN
2304      *
2305      * @param   mixed   DSN string or array
2306      *
2307      * @return  MDB2_OK
2308      *
2309      * @access  public
2310      */
2311     function setDSN($dsn)
2312     {
2313         $dsn_default = $GLOBALS['_MDB2_dsninfo_default'];
2314         $dsn = MDB2::parseDSN($dsn);
2315         if (array_key_exists('database', $dsn)) {
2316             $this->database_name = $dsn['database'];
2317             unset($dsn['database']);
2318         }
2319         $this->dsn = array_merge($dsn_default, $dsn);
2320         return $this->disconnect(false);
2321     }
2322
2323     // }}}
2324     // {{{ function getDSN($type = 'string', $hidepw = false)
2325
2326     /**
2327      * return the DSN as a string
2328      *
2329      * @param   string  format to return ("array", "string")
2330      * @param   string  string to hide the password with
2331      *
2332      * @return  mixed   DSN in the chosen type
2333      *
2334      * @access  public
2335      */
2336     function getDSN($type = 'string', $hidepw = false)
2337     {
2338         $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn);
2339         $dsn['phptype'] = $this->phptype;
2340         $dsn['database'] = $this->database_name;
2341         if ($hidepw) {
2342             $dsn['password'] = $hidepw;
2343         }
2344         switch ($type) {
2345         // expand to include all possible options
2346         case 'string':
2347            $dsn = $dsn['phptype'].
2348                ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : '').
2349                '://'.$dsn['username'].':'.
2350                 $dsn['password'].'@'.$dsn['hostspec'].
2351                 ($dsn['port'] ? (':'.$dsn['port']) : '').
2352                 '/'.$dsn['database'];
2353             break;
2354         case 'array':
2355         default:
2356             break;
2357         }
2358         return $dsn;
2359     }
2360
2361     // }}}
2362     // {{{ function &standaloneQuery($query, $types = null, $is_manip = false)
2363
2364    /**
2365      * execute a query as database administrator
2366      *
2367      * @param   string  the SQL query
2368      * @param   mixed   array that contains the types of the columns in
2369      *                        the result set
2370      * @param   bool    if the query is a manipulation query
2371      *
2372      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2373      *
2374      * @access  public
2375      */
2376     function &standaloneQuery($query, $types = null, $is_manip = false)
2377     {
2378         $offset = $this->offset;
2379         $limit = $this->limit;
2380         $this->offset = $this->limit = 0;
2381         $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
2382
2383         $connection = $this->getConnection();
2384         if (PEAR::isError($connection)) {
2385             return $connection;
2386         }
2387
2388         $result =& $this->_doQuery($query, $is_manip, $connection, false);
2389         if (PEAR::isError($result)) {
2390             return $result;
2391         }
2392
2393         if ($is_manip) {
2394             $affected_rows =  $this->_affectedRows($connection, $result);
2395             return $affected_rows;
2396         }
2397         $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset);
2398         return $result;
2399     }
2400
2401     // }}}
2402     // {{{ function _modifyQuery($query, $is_manip, $limit, $offset)
2403
2404     /**
2405      * Changes a query string for various DBMS specific reasons
2406      *
2407      * @param   string  query to modify
2408      * @param   bool    if it is a DML query
2409      * @param   int  limit the number of rows
2410      * @param   int  start reading from given offset
2411      *
2412      * @return  string  modified query
2413      *
2414      * @access  protected
2415      */
2416     function _modifyQuery($query, $is_manip, $limit, $offset)
2417     {
2418         return $query;
2419     }
2420
2421     // }}}
2422     // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
2423
2424     /**
2425      * Execute a query
2426      * @param   string  query
2427      * @param   bool    if the query is a manipulation query
2428      * @param   resource connection handle
2429      * @param   string  database name
2430      *
2431      * @return  result or error object
2432      *
2433      * @access  protected
2434      */
2435     function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
2436     {
2437         $this->last_query = $query;
2438         $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
2439         if ($result) {
2440             if (PEAR::isError($result)) {
2441                 return $result;
2442             }
2443             $query = $result;
2444         }
2445         $err =& $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2446             'method not implemented', __FUNCTION__);
2447         return $err;
2448     }
2449
2450     // }}}
2451     // {{{ function _affectedRows($connection, $result = null)
2452
2453     /**
2454      * Returns the number of rows affected
2455      *
2456      * @param   resource result handle
2457      * @param   resource connection handle
2458      *
2459      * @return  mixed   MDB2 Error Object or the number of rows affected
2460      *
2461      * @access  private
2462      */
2463     function _affectedRows($connection, $result = null)
2464     {
2465         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2466             'method not implemented', __FUNCTION__);
2467     }
2468
2469     // }}}
2470     // {{{ function &exec($query)
2471
2472     /**
2473      * Execute a manipulation query to the database and return the number of affected rows
2474      *
2475      * @param   string  the SQL query
2476      *
2477      * @return  mixed   number of affected rows on success, a MDB2 error on failure
2478      *
2479      * @access  public
2480      */
2481     function &exec($query)
2482     {
2483         $offset = $this->offset;
2484         $limit = $this->limit;
2485         $this->offset = $this->limit = 0;
2486         $query = $this->_modifyQuery($query, true, $limit, $offset);
2487
2488         $connection = $this->getConnection();
2489         if (PEAR::isError($connection)) {
2490             return $connection;
2491         }
2492
2493         $result =& $this->_doQuery($query, true, $connection, $this->database_name);
2494         if (PEAR::isError($result)) {
2495             return $result;
2496         }
2497
2498         $affectedRows = $this->_affectedRows($connection, $result);
2499         return $affectedRows;
2500     }
2501
2502     // }}}
2503     // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false)
2504
2505     /**
2506      * Send a query to the database and return any results
2507      *
2508      * @param   string  the SQL query
2509      * @param   mixed   array that contains the types of the columns in
2510      *                        the result set
2511      * @param   mixed   string which specifies which result class to use
2512      * @param   mixed   string which specifies which class to wrap results in
2513      *
2514      * @return mixed   an MDB2_Result handle on success, a MDB2 error on failure
2515      *
2516      * @access  public
2517      */
2518     function &query($query, $types = null, $result_class = true, $result_wrap_class = false)
2519     {
2520         $offset = $this->offset;
2521         $limit = $this->limit;
2522         $this->offset = $this->limit = 0;
2523         $query = $this->_modifyQuery($query, false, $limit, $offset);
2524
2525         $connection = $this->getConnection();
2526         if (PEAR::isError($connection)) {
2527             return $connection;
2528         }
2529
2530         $result =& $this->_doQuery($query, false, $connection, $this->database_name);
2531         if (PEAR::isError($result)) {
2532             return $result;
2533         }
2534
2535         $result =& $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset);
2536         return $result;
2537     }
2538
2539     // }}}
2540     // {{{ function &_wrapResult($result, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null)
2541
2542     /**
2543      * wrap a result set into the correct class
2544      *
2545      * @param   resource result handle
2546      * @param   mixed   array that contains the types of the columns in
2547      *                        the result set
2548      * @param   mixed   string which specifies which result class to use
2549      * @param   mixed   string which specifies which class to wrap results in
2550      * @param   string  number of rows to select
2551      * @param   string  first row to select
2552      *
2553      * @return mixed   an MDB2_Result, a MDB2 error on failure
2554      *
2555      * @access  protected
2556      */
2557     function &_wrapResult($result, $types = array(), $result_class = true,
2558         $result_wrap_class = false, $limit = null, $offset = null)
2559     {
2560         if ($types === true) {
2561             if ($this->supports('result_introspection')) {
2562                 $this->loadModule('Reverse', null, true);
2563                 $tableInfo = $this->reverse->tableInfo($result);
2564                 if (PEAR::isError($tableInfo)) {
2565                     return $tableInfo;
2566                 }
2567                 $types = array();
2568                 foreach ($tableInfo as $field) {
2569                     $types[] = $field['mdb2type'];
2570                 }
2571             } else {
2572                 $types = null;
2573             }
2574         }
2575
2576         if ($result_class === true) {
2577             $result_class = $this->options['result_buffering']
2578                 ? $this->options['buffered_result_class'] : $this->options['result_class'];
2579         }
2580
2581         if ($result_class) {
2582             $class_name = sprintf($result_class, $this->phptype);
2583             if (!MDB2::classExists($class_name)) {
2584                 $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
2585                     'result class does not exist '.$class_name, __FUNCTION__);
2586                 return $err;
2587             }
2588             $result =& new $class_name($this, $result, $limit, $offset);
2589             if (!MDB2::isResultCommon($result)) {
2590                 $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
2591                     'result class is not extended from MDB2_Result_Common', __FUNCTION__);
2592                 return $err;
2593             }
2594             if (!empty($types)) {
2595                 $err = $result->setResultTypes($types);
2596                 if (PEAR::isError($err)) {
2597                     $result->free();
2598                     return $err;
2599                 }
2600             }
2601         }
2602         if ($result_wrap_class === true) {
2603             $result_wrap_class = $this->options['result_wrap_class'];
2604         }
2605         if ($result_wrap_class) {
2606             if (!MDB2::classExists($result_wrap_class)) {
2607                 $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
2608                     'result wrap class does not exist '.$result_wrap_class, __FUNCTION__);
2609                 return $err;
2610             }
2611             $result = new $result_wrap_class($result, $this->fetchmode);
2612         }
2613         return $result;
2614     }
2615
2616     // }}}
2617     // {{{ function getServerVersion($native = false)
2618
2619     /**
2620      * return version information about the server
2621      *
2622      * @param   bool    determines if the raw version string should be returned
2623      *
2624      * @return  mixed   array with version information or row string
2625      *
2626      * @access  public
2627      */
2628     function getServerVersion($native = false)
2629     {
2630         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2631             'method not implemented', __FUNCTION__);
2632     }
2633
2634     // }}}
2635     // {{{ function setLimit($limit, $offset = null)
2636
2637     /**
2638      * set the range of the next query
2639      *
2640      * @param   string  number of rows to select
2641      * @param   string  first row to select
2642      *
2643      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2644      *
2645      * @access  public
2646      */
2647     function setLimit($limit, $offset = null)
2648     {
2649         if (!$this->supports('limit_queries')) {
2650             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2651                 'limit is not supported by this driver', __FUNCTION__);
2652         }
2653         $limit = (int)$limit;
2654         if ($limit < 0) {
2655             return $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
2656                 'it was not specified a valid selected range row limit', __FUNCTION__);
2657         }
2658         $this->limit = $limit;
2659         if (!is_null($offset)) {
2660             $offset = (int)$offset;
2661             if ($offset < 0) {
2662                 return $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
2663                     'it was not specified a valid first selected range row', __FUNCTION__);
2664             }
2665             $this->offset = $offset;
2666         }
2667         return MDB2_OK;
2668     }
2669
2670     // }}}
2671     // {{{ function subSelect($query, $type = false)
2672
2673     /**
2674      * simple subselect emulation: leaves the query untouched for all RDBMS
2675      * that support subselects
2676      *
2677      * @param   string  the SQL query for the subselect that may only
2678      *                      return a column
2679      * @param   string  determines type of the field
2680      *
2681      * @return  string  the query
2682      *
2683      * @access  public
2684      */
2685     function subSelect($query, $type = false)
2686     {
2687         if ($this->supports('sub_selects') === true) {
2688             return $query;
2689         }
2690
2691         if (!$this->supports('sub_selects')) {
2692             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2693                 'method not implemented', __FUNCTION__);
2694         }
2695
2696         $col = $this->queryCol($query, $type);
2697         if (PEAR::isError($col)) {
2698             return $col;
2699         }
2700         if (!is_array($col) || count($col) == 0) {
2701             return 'NULL';
2702         }
2703         if ($type) {
2704             $this->loadModule('Datatype', null, true);
2705             return $this->datatype->implodeArray($col, $type);
2706         }
2707         return implode(', ', $col);
2708     }
2709
2710     // }}}
2711     // {{{ function replace($table, $fields)
2712
2713     /**
2714      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
2715      * query, except that if there is already a row in the table with the same
2716      * key field values, the REPLACE query just updates its values instead of
2717      * inserting a new row.
2718      *
2719      * The REPLACE type of query does not make part of the SQL standards. Since
2720      * practically only MySQL and SQLite implement it natively, this type of
2721      * query isemulated through this method for other DBMS using standard types
2722      * of queries inside a transaction to assure the atomicity of the operation.
2723      *
2724      * @param   string  name of the table on which the REPLACE query will
2725      *       be executed.
2726      * @param   array   associative array   that describes the fields and the
2727      *       values that will be inserted or updated in the specified table. The
2728      *       indexes of the array are the names of all the fields of the table.
2729      *       The values of the array are also associative arrays that describe
2730      *       the values and other properties of the table fields.
2731      *
2732      *       Here follows a list of field properties that need to be specified:
2733      *
2734      *       value
2735      *           Value to be assigned to the specified field. This value may be
2736      *           of specified in database independent type format as this
2737      *           function can perform the necessary datatype conversions.
2738      *
2739      *           Default: this property is required unless the Null property is
2740      *           set to 1.
2741      *
2742      *       type
2743      *           Name of the type of the field. Currently, all types MDB2
2744      *           are supported except for clob and blob.
2745      *
2746      *           Default: no type conversion
2747      *
2748      *       null
2749      *           bool    property that indicates that the value for this field
2750      *           should be set to null.
2751      *
2752      *           The default value for fields missing in INSERT queries may be
2753      *           specified the definition of a table. Often, the default value
2754      *           is already null, but since the REPLACE may be emulated using
2755      *           an UPDATE query, make sure that all fields of the table are
2756      *           listed in this function argument array.
2757      *
2758      *           Default: 0
2759      *
2760      *       key
2761      *           bool    property that indicates that this field should be
2762      *           handled as a primary key or at least as part of the compound
2763      *           unique index of the table that will determine the row that will
2764      *           updated if it exists or inserted a new row otherwise.
2765      *
2766      *           This function will fail if no key field is specified or if the
2767      *           value of a key field is set to null because fields that are
2768      *           part of unique index they may not be null.
2769      *
2770      *           Default: 0
2771      *
2772      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
2773      *
2774      * @access  public
2775      */
2776     function replace($table, $fields)
2777     {
2778         if (!$this->supports('replace')) {
2779             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
2780                 'replace query is not supported', __FUNCTION__);
2781         }
2782         $count = count($fields);
2783         $condition = $values = array();
2784         for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) {
2785             $name = key($fields);
2786             if (isset($fields[$name]['null']) && $fields[$name]['null']) {
2787                 $value = 'NULL';
2788             } else {
2789                 $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
2790                 $value = $this->quote($fields[$name]['value'], $type);
2791             }
2792             $values[$name] = $value;
2793             if (isset($fields[$name]['key']) && $fields[$name]['key']) {
2794                 if ($value === 'NULL') {
2795                     return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
2796                         'key value '.$name.' may not be NULL', __FUNCTION__);
2797                 }
2798                 $condition[] = $name . '=' . $value;
2799             }
2800         }
2801         if (empty($condition)) {
2802             return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
2803                 'not specified which fields are keys', __FUNCTION__);
2804         }
2805
2806         $result = null;
2807         $in_transaction = $this->in_transaction;
2808         if (!$in_transaction && PEAR::isError($result = $this->beginTransaction())) {
2809             return $result;
2810         }
2811
2812         $connection = $this->getConnection();
2813         if (PEAR::isError($connection)) {
2814             return $connection;
2815         }
2816
2817         $condition = ' WHERE '.implode(' AND ', $condition);
2818         $query = "DELETE FROM $table$condition";
2819         $result =& $this->_doQuery($query, true, $connection);
2820         if (!PEAR::isError($result)) {
2821             $affected_rows = $this->_affectedRows($connection, $result);
2822             $insert = implode(', ', array_keys($values));
2823             $values = implode(', ', $values);
2824             $query = "INSERT INTO $table ($insert) VALUES ($values)";
2825             $result =& $this->_doQuery($query, true, $connection);
2826             if (!PEAR::isError($result)) {
2827                 $affected_rows += $this->_affectedRows($connection, $result);;
2828             }
2829         }
2830
2831         if (!$in_transaction) {
2832             if (PEAR::isError($result)) {
2833                 $this->rollback();
2834             } else {
2835                 $result = $this->commit();
2836             }
2837         }
2838
2839         if (PEAR::isError($result)) {
2840             return $result;
2841         }
2842
2843         return $affected_rows;
2844     }
2845
2846     // }}}
2847     // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array())
2848
2849     /**
2850      * Prepares a query for multiple execution with execute().
2851      * With some database backends, this is emulated.
2852      * prepare() requires a generic query as string like
2853      * 'INSERT INTO numbers VALUES(?,?)' or
2854      * 'INSERT INTO numbers VALUES(:foo,:bar)'.
2855      * The ? and :name and are placeholders which can be set using
2856      * bindParam() and the query can be sent off using the execute() method.
2857      * The allowed format for :name can be set with the 'bindname_format' option.
2858      *
2859      * @param   string  the query to prepare
2860      * @param   mixed   array that contains the types of the placeholders
2861      * @param   mixed   array that contains the types of the columns in
2862      *                        the result set or MDB2_PREPARE_RESULT, if set to
2863      *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
2864      * @param   mixed   key (field) value (parameter) pair for all lob placeholders
2865      *
2866      * @return  mixed   resource handle for the prepared query on success, 
2867      *                  a MDB2 error on failure
2868      *
2869      * @access  public
2870      * @see     bindParam, execute
2871      */
2872     function &prepare($query, $types = null, $result_types = null, $lobs = array())
2873     {
2874         $is_manip = ($result_types === MDB2_PREPARE_MANIP);
2875         $offset = $this->offset;
2876         $limit = $this->limit;
2877         $this->offset = $this->limit = 0;
2878         $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
2879         if ($result) {
2880             if (PEAR::isError($result)) {
2881                 return $result;
2882             }
2883             $query = $result;
2884         }
2885         $placeholder_type_guess = $placeholder_type = null;
2886         $question = '?';
2887         $colon = ':';
2888         $positions = array();
2889         $position = 0;
2890         $ignores = $this->sql_comments;
2891         $ignores[] = $this->string_quoting;
2892         $ignores[] = $this->identifier_quoting;
2893         while ($position < strlen($query)) {
2894             $q_position = strpos($query, $question, $position);
2895             $c_position = strpos($query, $colon, $position);
2896             if ($q_position && $c_position) {
2897                 $p_position = min($q_position, $c_position);
2898             } elseif ($q_position) {
2899                 $p_position = $q_position;
2900             } elseif ($c_position) {
2901                 $p_position = $c_position;
2902             } else {
2903                 break;
2904             }
2905             if (is_null($placeholder_type)) {
2906                 $placeholder_type_guess = $query[$p_position];
2907             }
2908
2909             $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
2910             if (PEAR::isError($new_pos)) {
2911                 return $new_pos;
2912             }
2913             if ($new_pos != $position) {
2914                 $position = $new_pos;
2915                 continue; //evaluate again starting from the new position
2916             }
2917
2918             if ($query[$position] == $placeholder_type_guess) {
2919                 if (is_null($placeholder_type)) {
2920                     $placeholder_type = $query[$p_position];
2921                     $question = $colon = $placeholder_type;
2922                     if (!empty($types) && is_array($types)) {
2923                         if ($placeholder_type == ':') {
2924                             if (is_int(key($types))) {
2925                                 $types_tmp = $types;
2926                                 $types = array();
2927                                 $count = -1;
2928                             }
2929                         } else {
2930                             $types = array_values($types);
2931                         }
2932                     }
2933                 }
2934                 if ($placeholder_type == ':') {
2935                     $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
2936                     $parameter = preg_replace($regexp, '\\1', $query);
2937                     if ($parameter === '') {
2938                         $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
2939                             'named parameter name must match "bindname_format" option', __FUNCTION__);
2940                         return $err;
2941                     }
2942                     $positions[$p_position] = $parameter;
2943                     $query = substr_replace($query, '?', $position, strlen($parameter)+1);
2944                     // use parameter name in type array
2945                     if (isset($count) && isset($types_tmp[++$count])) {
2946                         $types[$parameter] = $types_tmp[$count];
2947                     }
2948                 } else {
2949                     $positions[$p_position] = count($positions);
2950                 }
2951                 $position = $p_position + 1;
2952             } else {
2953                 $position = $p_position;
2954             }
2955         }
2956         $class_name = 'MDB2_Statement_'.$this->phptype;
2957         $statement = null;
2958         $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
2959         $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
2960         return $obj;
2961     }
2962
2963     // }}}
2964     // {{{ function _skipDelimitedStrings($query, $position, $p_position)
2965     
2966     /**
2967      * Utility method, used by prepare() to avoid replacing placeholders within delimited strings.
2968      * Check if the placeholder is contained within a delimited string.
2969      * If so, skip it and advance the position, otherwise return the current position,
2970      * which is valid
2971      *
2972      * @param string $query
2973      * @param integer $position current string cursor position
2974      * @param integer $p_position placeholder position
2975      *
2976      * @return mixed integer $new_position on success
2977      *               MDB2_Error on failure
2978      *
2979      * @access  protected
2980      */
2981     function _skipDelimitedStrings($query, $position, $p_position)
2982     {
2983         $ignores = $this->sql_comments;
2984         $ignores[] = $this->string_quoting;
2985         $ignores[] = $this->identifier_quoting;
2986         
2987         foreach ($ignores as $ignore) {
2988             if (!empty($ignore['start'])) {
2989                 if (is_int($start_quote = strpos($query, $ignore['start'], $position)) && $start_quote < $p_position) {
2990                     $end_quote = $start_quote;
2991                     do {
2992                         if (!is_int($end_quote = strpos($query, $ignore['end'], $end_quote + 1))) {
2993                             if ($ignore['end'] === "\n") {
2994                                 $end_quote = strlen($query) - 1;
2995                             } else {
2996                                 $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
2997                                     'query with an unterminated text string specified', __FUNCTION__);
2998                                 return $err;
2999                             }
3000                         }
3001                     } while ($ignore['escape'] && $query[($end_quote - 1)] == $ignore['escape']);
3002                     $position = $end_quote + 1;
3003                     return $position;
3004                 }
3005             }
3006         }
3007         return $position;
3008     }
3009     
3010     // }}}
3011     // {{{ function quote($value, $type = null, $quote = true)
3012
3013     /**
3014      * Convert a text value into a DBMS specific format that is suitable to
3015      * compose query statements.
3016      *
3017      * @param   string  text string value that is intended to be converted.
3018      * @param   string  type to which the value should be converted to
3019      * @param   bool    quote
3020      * @param   bool    escape wildcards
3021      *
3022      * @return  string  text string that represents the given argument value in
3023      *       a DBMS specific format.
3024      *
3025      * @access  public
3026      */
3027     function quote($value, $type = null, $quote = true, $escape_wildcards = false)
3028     {
3029         $result = $this->loadModule('Datatype', null, true);
3030         if (PEAR::isError($result)) {
3031             return $result;
3032         }
3033
3034         return $this->datatype->quote($value, $type, $quote, $escape_wildcards);
3035     }
3036
3037     // }}}
3038     // {{{ function getDeclaration($type, $name, $field)
3039
3040     /**
3041      * Obtain DBMS specific SQL code portion needed to declare
3042      * of the given type
3043      *
3044      * @param   string  type to which the value should be converted to
3045      * @param   string  name the field to be declared.
3046      * @param   string  definition of the field
3047      *
3048      * @return  string  DBMS specific SQL code portion that should be used to
3049      *                 declare the specified field.
3050      *
3051      * @access  public
3052      */
3053     function getDeclaration($type, $name, $field)
3054     {
3055         $result = $this->loadModule('Datatype', null, true);
3056         if (PEAR::isError($result)) {
3057             return $result;
3058         }
3059         return $this->datatype->getDeclaration($type, $name, $field);
3060     }
3061
3062     // }}}
3063     // {{{ function compareDefinition($current, $previous)
3064
3065     /**
3066      * Obtain an array of changes that may need to applied
3067      *
3068      * @param   array   new definition
3069      * @param   array   old definition
3070      *
3071      * @return  array   containing all changes that will need to be applied
3072      *
3073      * @access  public
3074      */
3075     function compareDefinition($current, $previous)
3076     {
3077         $result = $this->loadModule('Datatype', null, true);
3078         if (PEAR::isError($result)) {
3079             return $result;
3080         }
3081         return $this->datatype->compareDefinition($current, $previous);
3082     }
3083
3084     // }}}
3085     // {{{ function supports($feature)
3086
3087     /**
3088      * Tell whether a DB implementation or its backend extension
3089      * supports a given feature.
3090      *
3091      * @param   string  name of the feature (see the MDB2 class doc)
3092      *
3093      * @return  bool|string if this DB implementation supports a given feature
3094      *                      false means no, true means native,
3095      *                      'emulated' means emulated
3096      *
3097      * @access  public
3098      */
3099     function supports($feature)
3100     {
3101         if (array_key_exists($feature, $this->supported)) {
3102             return $this->supported[$feature];
3103         }
3104         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3105             "unknown support feature $feature", __FUNCTION__);
3106     }
3107
3108     // }}}
3109     // {{{ function getSequenceName($sqn)
3110
3111     /**
3112      * adds sequence name formatting to a sequence name
3113      *
3114      * @param   string  name of the sequence
3115      *
3116      * @return  string  formatted sequence name
3117      *
3118      * @access  public
3119      */
3120     function getSequenceName($sqn)
3121     {
3122         return sprintf($this->options['seqname_format'],
3123             preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn));
3124     }
3125
3126     // }}}
3127     // {{{ function getIndexName($idx)
3128
3129     /**
3130      * adds index name formatting to a index name
3131      *
3132      * @param   string  name of the index
3133      *
3134      * @return  string  formatted index name
3135      *
3136      * @access  public
3137      */
3138     function getIndexName($idx)
3139     {
3140         return sprintf($this->options['idxname_format'],
3141             preg_replace('/[^a-z0-9_\$]/i', '_', $idx));
3142     }
3143
3144     // }}}
3145     // {{{ function nextID($seq_name, $ondemand = true)
3146
3147     /**
3148      * Returns the next free id of a sequence
3149      *
3150      * @param   string  name of the sequence
3151      * @param   bool    when true missing sequences are automatic created
3152      *
3153      * @return  mixed   MDB2 Error Object or id
3154      *
3155      * @access  public
3156      */
3157     function nextID($seq_name, $ondemand = true)
3158     {
3159         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3160             'method not implemented', __FUNCTION__);
3161     }
3162
3163     // }}}
3164     // {{{ function lastInsertID($table = null, $field = null)
3165
3166     /**
3167      * Returns the autoincrement ID if supported or $id or fetches the current
3168      * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
3169      *
3170      * @param   string  name of the table into which a new row was inserted
3171      * @param   string  name of the field into which a new row was inserted
3172      *
3173      * @return  mixed   MDB2 Error Object or id
3174      *
3175      * @access  public
3176      */
3177     function lastInsertID($table = null, $field = null)
3178     {
3179         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3180             'method not implemented', __FUNCTION__);
3181     }
3182
3183     // }}}
3184     // {{{ function currID($seq_name)
3185
3186     /**
3187      * Returns the current id of a sequence
3188      *
3189      * @param   string  name of the sequence
3190      *
3191      * @return  mixed   MDB2 Error Object or id
3192      *
3193      * @access  public
3194      */
3195     function currID($seq_name)
3196     {
3197         $this->warnings[] = 'database does not support getting current
3198             sequence value, the sequence value was incremented';
3199         return $this->nextID($seq_name);
3200     }
3201
3202     // }}}
3203     // {{{ function queryOne($query, $type = null, $colnum = 0)
3204
3205     /**
3206      * Execute the specified query, fetch the value from the first column of
3207      * the first row of the result set and then frees
3208      * the result set.
3209      *
3210      * @param   string  the SELECT query statement to be executed.
3211      * @param   string  optional argument that specifies the expected
3212      *       datatype of the result set field, so that an eventual conversion
3213      *       may be performed. The default datatype is text, meaning that no
3214      *       conversion is performed
3215      * @param   int     the column number to fetch
3216      *
3217      * @return  mixed   MDB2_OK or field value on success, a MDB2 error on failure
3218      *
3219      * @access  public
3220      */
3221     function queryOne($query, $type = null, $colnum = 0)
3222     {
3223         $result = $this->query($query, $type);
3224         if (!MDB2::isResultCommon($result)) {
3225             return $result;
3226         }
3227
3228         $one = $result->fetchOne($colnum);
3229         $result->free();
3230         return $one;
3231     }
3232
3233     // }}}
3234     // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT)
3235
3236     /**
3237      * Execute the specified query, fetch the values from the first
3238      * row of the result set into an array and then frees
3239      * the result set.
3240      *
3241      * @param   string  the SELECT query statement to be executed.
3242      * @param   array   optional array argument that specifies a list of
3243      *       expected datatypes of the result set columns, so that the eventual
3244      *       conversions may be performed. The default list of datatypes is
3245      *       empty, meaning that no conversion is performed.
3246      * @param   int     how the array data should be indexed
3247      *
3248      * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
3249      *
3250      * @access  public
3251      */
3252     function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT)
3253     {
3254         $result = $this->query($query, $types);
3255         if (!MDB2::isResultCommon($result)) {
3256             return $result;
3257         }
3258
3259         $row = $result->fetchRow($fetchmode);
3260         $result->free();
3261         return $row;
3262     }
3263
3264     // }}}
3265     // {{{ function queryCol($query, $type = null, $colnum = 0)
3266
3267     /**
3268      * Execute the specified query, fetch the value from the first column of
3269      * each row of the result set into an array and then frees the result set.
3270      *
3271      * @param   string  the SELECT query statement to be executed.
3272      * @param   string  optional argument that specifies the expected
3273      *       datatype of the result set field, so that an eventual conversion
3274      *       may be performed. The default datatype is text, meaning that no
3275      *       conversion is performed
3276      * @param   int     the row number to fetch
3277      *
3278      * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
3279      *
3280      * @access  public
3281      */
3282     function queryCol($query, $type = null, $colnum = 0)
3283     {
3284         $result = $this->query($query, $type);
3285         if (!MDB2::isResultCommon($result)) {
3286             return $result;
3287         }
3288
3289         $col = $result->fetchCol($colnum);
3290         $result->free();
3291         return $col;
3292     }
3293
3294     // }}}
3295     // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false)
3296
3297     /**
3298      * Execute the specified query, fetch all the rows of the result set into
3299      * a two dimensional array and then frees the result set.
3300      *
3301      * @param   string  the SELECT query statement to be executed.
3302      * @param   array   optional array argument that specifies a list of
3303      *       expected datatypes of the result set columns, so that the eventual
3304      *       conversions may be performed. The default list of datatypes is
3305      *       empty, meaning that no conversion is performed.
3306      * @param   int     how the array data should be indexed
3307      * @param   bool    if set to true, the $all will have the first
3308      *       column as its first dimension
3309      * @param   bool    used only when the query returns exactly
3310      *       two columns. If true, the values of the returned array will be
3311      *       one-element arrays instead of scalars.
3312      * @param   bool    if true, the values of the returned array is
3313      *       wrapped in another array.  If the same key value (in the first
3314      *       column) repeats itself, the values will be appended to this array
3315      *       instead of overwriting the existing values.
3316      *
3317      * @return  mixed   MDB2_OK or data array on success, a MDB2 error on failure
3318      *
3319      * @access  public
3320      */
3321     function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT,
3322         $rekey = false, $force_array = false, $group = false)
3323     {
3324         $result = $this->query($query, $types);
3325         if (!MDB2::isResultCommon($result)) {
3326             return $result;
3327         }
3328
3329         $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group);
3330         $result->free();
3331         return $all;
3332     }
3333
3334     // }}}
3335 }
3336
3337 // }}}
3338 // {{{ class MDB2_Result
3339
3340 /**
3341  * The dummy class that all user space result classes should extend from
3342  *
3343  * @package     MDB2
3344  * @category    Database
3345  * @author      Lukas Smith <smith@pooteeweet.org>
3346  */
3347 class MDB2_Result
3348 {
3349 }
3350
3351 // }}}
3352 // {{{ class MDB2_Result_Common extends MDB2_Result
3353
3354 /**
3355  * The common result class for MDB2 result objects
3356  *
3357  * @package     MDB2
3358  * @category    Database
3359  * @author      Lukas Smith <smith@pooteeweet.org>
3360  */
3361 class MDB2_Result_Common extends MDB2_Result
3362 {
3363     // {{{ Variables (Properties)
3364
3365     var $db;
3366     var $result;
3367     var $rownum = -1;
3368     var $types = array();
3369     var $values = array();
3370     var $offset;
3371     var $offset_count = 0;
3372     var $limit;
3373     var $column_names;
3374
3375     // }}}
3376     // {{{ constructor: function __construct(&$db, &$result, $limit = 0, $offset = 0)
3377
3378     /**
3379      * Constructor
3380      */
3381     function __construct(&$db, &$result, $limit = 0, $offset = 0)
3382     {
3383         $this->db =& $db;
3384         $this->result =& $result;
3385         $this->offset = $offset;
3386         $this->limit = max(0, $limit - 1);
3387     }
3388
3389     // }}}
3390     // {{{ function MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0)
3391
3392     /**
3393      * PHP 4 Constructor
3394      */
3395     function MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0)
3396     {
3397         $this->__construct($db, $result, $limit, $offset);
3398     }
3399
3400     // }}}
3401     // {{{ function setResultTypes($types)
3402
3403     /**
3404      * Define the list of types to be associated with the columns of a given
3405      * result set.
3406      *
3407      * This function may be called before invoking fetchRow(), fetchOne(),
3408      * fetchCol() and fetchAll() so that the necessary data type
3409      * conversions are performed on the data to be retrieved by them. If this
3410      * function is not called, the type of all result set columns is assumed
3411      * to be text, thus leading to not perform any conversions.
3412      *
3413      * @param   array   variable that lists the
3414      *       data types to be expected in the result set columns. If this array
3415      *       contains less types than the number of columns that are returned
3416      *       in the result set, the remaining columns are assumed to be of the
3417      *       type text. Currently, the types clob and blob are not fully
3418      *       supported.
3419      *
3420      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3421      *
3422      * @access  public
3423      */
3424     function setResultTypes($types)
3425     {
3426         $load = $this->db->loadModule('Datatype', null, true);
3427         if (PEAR::isError($load)) {
3428             return $load;
3429         }
3430         $types = $this->db->datatype->checkResultTypes($types);
3431         if (PEAR::isError($types)) {
3432             return $types;
3433         }
3434         $this->types = $types;
3435         return MDB2_OK;
3436     }
3437
3438     // }}}
3439     // {{{ function seek($rownum = 0)
3440
3441     /**
3442      * Seek to a specific row in a result set
3443      *
3444      * @param   int     number of the row where the data can be found
3445      *
3446      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3447      *
3448      * @access  public
3449      */
3450     function seek($rownum = 0)
3451     {
3452         $target_rownum = $rownum - 1;
3453         if ($this->rownum > $target_rownum) {
3454             return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3455                 'seeking to previous rows not implemented', __FUNCTION__);
3456         }
3457         while ($this->rownum < $target_rownum) {
3458             $this->fetchRow();
3459         }
3460         return MDB2_OK;
3461     }
3462
3463     // }}}
3464     // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
3465
3466     /**
3467      * Fetch and return a row of data
3468      *
3469      * @param   int     how the array data should be indexed
3470      * @param   int     number of the row where the data can be found
3471      *
3472      * @return  int     data array on success, a MDB2 error on failure
3473      *
3474      * @access  public
3475      */
3476     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
3477     {
3478         $err =& $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3479             'method not implemented', __FUNCTION__);
3480         return $err;
3481     }
3482
3483     // }}}
3484     // {{{ function fetchOne($colnum = 0)
3485
3486     /**
3487      * fetch single column from the next row from a result set
3488      *
3489      * @param   int     the column number to fetch
3490      * @param   int     number of the row where the data can be found
3491      *
3492      * @return  string  data on success, a MDB2 error on failure
3493      *
3494      * @access  public
3495      */
3496     function fetchOne($colnum = 0, $rownum = null)
3497     {
3498         $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC;
3499         $row = $this->fetchRow($fetchmode, $rownum);
3500         if (!is_array($row) || PEAR::isError($row)) {
3501             return $row;
3502         }
3503         if (!array_key_exists($colnum, $row)) {
3504             return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null,
3505                 'column is not defined in the result set: '.$colnum, __FUNCTION__);
3506         }
3507         return $row[$colnum];
3508     }
3509
3510     // }}}
3511     // {{{ function fetchCol($colnum = 0)
3512
3513     /**
3514      * Fetch and return a column from the current row pointer position
3515      *
3516      * @param   int     the column number to fetch
3517      *
3518      * @return  mixed   data array on success, a MDB2 error on failure
3519      *
3520      * @access  public
3521      */
3522     function fetchCol($colnum = 0)
3523     {
3524         $column = array();
3525         $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC;
3526         $row = $this->fetchRow($fetchmode);
3527         if (is_array($row)) {
3528             if (!array_key_exists($colnum, $row)) {
3529                 return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null,
3530                     'column is not defined in the result set: '.$colnum, __FUNCTION__);
3531             }
3532             do {
3533                 $column[] = $row[$colnum];
3534             } while (is_array($row = $this->fetchRow($fetchmode)));
3535         }
3536         if (PEAR::isError($row)) {
3537             return $row;
3538         }
3539         return $column;
3540     }
3541
3542     // }}}
3543     // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false)
3544
3545     /**
3546      * Fetch and return all rows from the current row pointer position
3547      *
3548      * @param   int     $fetchmode  the fetch mode to use:
3549      *                            + MDB2_FETCHMODE_ORDERED
3550      *                            + MDB2_FETCHMODE_ASSOC
3551      *                            + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED
3552      *                            + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED
3553      * @param   bool    if set to true, the $all will have the first
3554      *       column as its first dimension
3555      * @param   bool    used only when the query returns exactly
3556      *       two columns. If true, the values of the returned array will be
3557      *       one-element arrays instead of scalars.
3558      * @param   bool    if true, the values of the returned array is
3559      *       wrapped in another array.  If the same key value (in the first
3560      *       column) repeats itself, the values will be appended to this array
3561      *       instead of overwriting the existing values.
3562      *
3563      * @return  mixed   data array on success, a MDB2 error on failure
3564      *
3565      * @access  public
3566      * @see     getAssoc()
3567      */
3568     function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false,
3569         $force_array = false, $group = false)
3570     {
3571         $all = array();
3572         $row = $this->fetchRow($fetchmode);
3573         if (PEAR::isError($row)) {
3574             return $row;
3575         } elseif (!$row) {
3576             return $all;
3577         }
3578
3579         $shift_array = $rekey ? false : null;
3580         if (!is_null($shift_array)) {
3581             if (is_object($row)) {
3582                 $colnum = count(get_object_vars($row));
3583             } else {
3584                 $colnum = count($row);
3585             }
3586             if ($colnum < 2) {
3587                 return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null,
3588                     'rekey feature requires atleast 2 column', __FUNCTION__);
3589             }
3590             $shift_array = (!$force_array && $colnum == 2);
3591         }
3592
3593         if ($rekey) {
3594             do {
3595                 if (is_object($row)) {
3596                     $arr = get_object_vars($row);
3597                     $key = reset($arr);
3598                     unset($row->{$key});
3599                 } else {
3600                     if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
3601                         $key = reset($row);
3602                         unset($row[key($row)]);
3603                     } else {
3604                         $key = array_shift($row);
3605                     }
3606                     if ($shift_array) {
3607                         $row = array_shift($row);
3608                     }
3609                 }
3610                 if ($group) {
3611                     $all[$key][] = $row;
3612                 } else {
3613                     $all[$key] = $row;
3614                 }
3615             } while (($row = $this->fetchRow($fetchmode)));
3616         } elseif ($fetchmode & MDB2_FETCHMODE_FLIPPED) {
3617             do {
3618                 foreach ($row as $key => $val) {
3619                     $all[$key][] = $val;
3620                 }
3621             } while (($row = $this->fetchRow($fetchmode)));
3622         } else {
3623             do {
3624                 $all[] = $row;
3625             } while (($row = $this->fetchRow($fetchmode)));
3626         }
3627
3628         return $all;
3629     }
3630
3631     // }}}
3632     // {{{ function rowCount()
3633     /**
3634      * Returns the actual row number that was last fetched (count from 0)
3635      * @return  int
3636      *
3637      * @access  public
3638      */
3639     function rowCount()
3640     {
3641         return $this->rownum + 1;
3642     }
3643
3644     // }}}
3645     // {{{ function numRows()
3646
3647     /**
3648      * Returns the number of rows in a result object
3649      *
3650      * @return  mixed   MDB2 Error Object or the number of rows
3651      *
3652      * @access  public
3653      */
3654     function numRows()
3655     {
3656         return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3657             'method not implemented', __FUNCTION__);
3658     }
3659
3660     // }}}
3661     // {{{ function nextResult()
3662
3663     /**
3664      * Move the internal result pointer to the next available result
3665      *
3666      * @return  true on success, false if there is no more result set or an error object on failure
3667      *
3668      * @access  public
3669      */
3670     function nextResult()
3671     {
3672         return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3673             'method not implemented', __FUNCTION__);
3674     }
3675
3676     // }}}
3677     // {{{ function getColumnNames()
3678
3679     /**
3680      * Retrieve the names of columns returned by the DBMS in a query result or
3681      * from the cache.
3682      *
3683      * @param   bool    If set to true the values are the column names,
3684      *                  otherwise the names of the columns are the keys.
3685      * @return  mixed   Array variable that holds the names of columns or an
3686      *                  MDB2 error on failure.
3687      *                  Some DBMS may not return any columns when the result set
3688      *                  does not contain any rows.
3689      *
3690      * @access  public
3691      */
3692     function getColumnNames($flip = false)
3693     {
3694         if (!isset($this->column_names)) {
3695             $result = $this->_getColumnNames();
3696             if (PEAR::isError($result)) {
3697                 return $result;
3698             }
3699             $this->column_names = $result;
3700         }
3701         if ($flip) {
3702             return array_flip($this->column_names);
3703         }
3704         return $this->column_names;
3705     }
3706
3707     // }}}
3708     // {{{ function _getColumnNames()
3709
3710     /**
3711      * Retrieve the names of columns returned by the DBMS in a query result.
3712      *
3713      * @return  mixed   Array variable that holds the names of columns as keys
3714      *                  or an MDB2 error on failure.
3715      *                  Some DBMS may not return any columns when the result set
3716      *                  does not contain any rows.
3717      *
3718      * @access  private
3719      */
3720     function _getColumnNames()
3721     {
3722         return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3723             'method not implemented', __FUNCTION__);
3724     }
3725
3726     // }}}
3727     // {{{ function numCols()
3728
3729     /**
3730      * Count the number of columns returned by the DBMS in a query result.
3731      *
3732      * @return  mixed   integer value with the number of columns, a MDB2 error
3733      *       on failure
3734      *
3735      * @access  public
3736      */
3737     function numCols()
3738     {
3739         return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
3740             'method not implemented', __FUNCTION__);
3741     }
3742
3743     // }}}
3744     // {{{ function getResource()
3745
3746     /**
3747      * return the resource associated with the result object
3748      *
3749      * @return  resource
3750      *
3751      * @access  public
3752      */
3753     function getResource()
3754     {
3755         return $this->result;
3756     }
3757
3758     // }}}
3759     // {{{ function bindColumn($column, &$value, $type = null)
3760
3761     /**
3762      * Set bind variable to a column.
3763      *
3764      * @param   int     column number or name
3765      * @param   mixed   variable reference
3766      * @param   string  specifies the type of the field
3767      *
3768      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3769      *
3770      * @access  public
3771      */
3772     function bindColumn($column, &$value, $type = null)
3773     {
3774         if (!is_numeric($column)) {
3775             $column_names = $this->getColumnNames();
3776             if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
3777                 if ($this->db->options['field_case'] == CASE_LOWER) {
3778                     $column = strtolower($column);
3779                 } else {
3780                     $column = strtoupper($column);
3781                 }
3782             }
3783             $column = $column_names[$column];
3784         }
3785         $this->values[$column] =& $value;
3786         if (!is_null($type)) {
3787             $this->types[$column] = $type;
3788         }
3789         return MDB2_OK;
3790     }
3791
3792     // }}}
3793     // {{{ function _assignBindColumns($row)
3794
3795     /**
3796      * Bind a variable to a value in the result row.
3797      *
3798      * @param   array   row data
3799      *
3800      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3801      *
3802      * @access  private
3803      */
3804     function _assignBindColumns($row)
3805     {
3806         $row = array_values($row);
3807         foreach ($row as $column => $value) {
3808             if (array_key_exists($column, $this->values)) {
3809                 $this->values[$column] = $value;
3810             }
3811         }
3812         return MDB2_OK;
3813     }
3814
3815     // }}}
3816     // {{{ function free()
3817
3818     /**
3819      * Free the internal resources associated with result.
3820      *
3821      * @return  bool    true on success, false if result is invalid
3822      *
3823      * @access  public
3824      */
3825     function free()
3826     {
3827         $this->result = false;
3828         return MDB2_OK;
3829     }
3830
3831     // }}}
3832 }
3833
3834 // }}}
3835 // {{{ class MDB2_Row
3836
3837 /**
3838  * The simple class that accepts row data as an array
3839  *
3840  * @package     MDB2
3841  * @category    Database
3842  * @author      Lukas Smith <smith@pooteeweet.org>
3843  */
3844 class MDB2_Row
3845 {
3846     // {{{ constructor: function __construct(&$row)
3847
3848     /**
3849      * constructor
3850      *
3851      * @param   resource    row data as array
3852      */
3853     function __construct(&$row)
3854     {
3855         foreach ($row as $key => $value) {
3856             $this->$key = &$row[$key];
3857         }
3858     }
3859
3860     // }}}
3861     // {{{ function MDB2_Row(&$row)
3862
3863     /**
3864      * PHP 4 Constructor
3865      *
3866      * @param   resource    row data as array
3867      */
3868     function MDB2_Row(&$row)
3869     {
3870         $this->__construct($row);
3871     }
3872
3873     // }}}
3874 }
3875
3876 // }}}
3877 // {{{ class MDB2_Statement_Common
3878
3879 /**
3880  * The common statement class for MDB2 statement objects
3881  *
3882  * @package     MDB2
3883  * @category    Database
3884  * @author      Lukas Smith <smith@pooteeweet.org>
3885  */
3886 class MDB2_Statement_Common
3887 {
3888     // {{{ Variables (Properties)
3889
3890     var $db;
3891     var $statement;
3892     var $query;
3893     var $result_types;
3894     var $types;
3895     var $values = array();
3896     var $limit;
3897     var $offset;
3898     var $is_manip;
3899
3900     // }}}
3901     // {{{ constructor: function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
3902
3903     /**
3904      * Constructor
3905      */
3906     function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
3907     {
3908         $this->db =& $db;
3909         $this->statement =& $statement;
3910         $this->positions = $positions;
3911         $this->query = $query;
3912         $this->types = (array)$types;
3913         $this->result_types = (array)$result_types;
3914         $this->limit = $limit;
3915         $this->is_manip = $is_manip;
3916         $this->offset = $offset;
3917     }
3918
3919     // }}}
3920     // {{{ function MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
3921
3922     /**
3923      * PHP 4 Constructor
3924      */
3925     function MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null)
3926     {
3927         $this->__construct($db, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
3928     }
3929
3930     // }}}
3931     // {{{ function bindValue($parameter, &$value, $type = null)
3932
3933     /**
3934      * Set the value of a parameter of a prepared query.
3935      *
3936      * @param   int     the order number of the parameter in the query
3937      *       statement. The order number of the first parameter is 1.
3938      * @param   mixed   value that is meant to be assigned to specified
3939      *       parameter. The type of the value depends on the $type argument.
3940      * @param   string  specifies the type of the field
3941      *
3942      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3943      *
3944      * @access  public
3945      */
3946     function bindValue($parameter, $value, $type = null)
3947     {
3948         if (!is_numeric($parameter)) {
3949             $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter);
3950         }
3951         if (!in_array($parameter, $this->positions)) {
3952             return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
3953                 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
3954         }
3955         $this->values[$parameter] = $value;
3956         if (!is_null($type)) {
3957             $this->types[$parameter] = $type;
3958         }
3959         return MDB2_OK;
3960     }
3961
3962     // }}}
3963     // {{{ function bindValueArray($values, $types = null)
3964
3965     /**
3966      * Set the values of multiple a parameter of a prepared query in bulk.
3967      *
3968      * @param   array   specifies all necessary information
3969      *       for bindValue() the array elements must use keys corresponding to
3970      *       the number of the position of the parameter.
3971      * @param   array   specifies the types of the fields
3972      *
3973      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
3974      *
3975      * @access  public
3976      * @see     bindParam()
3977      */
3978     function bindValueArray($values, $types = null)
3979     {
3980         $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
3981         $parameters = array_keys($values);
3982         foreach ($parameters as $key => $parameter) {
3983             $this->db->expectError(MDB2_ERROR_NOT_FOUND);
3984             $err = $this->bindValue($parameter, $values[$parameter], $types[$key]);
3985             $this->db->popExpect();
3986             if (PEAR::isError($err)) {
3987                 if ($err->getCode() == MDB2_ERROR_NOT_FOUND) {
3988                     //ignore (extra value for missing placeholder)
3989                     continue;
3990                 }
3991                 return $err;
3992             }
3993         }
3994         return MDB2_OK;
3995     }
3996
3997     // }}}
3998     // {{{ function bindParam($parameter, &$value, $type = null)
3999
4000     /**
4001      * Bind a variable to a parameter of a prepared query.
4002      *
4003      * @param   int     the order number of the parameter in the query
4004      *       statement. The order number of the first parameter is 1.
4005      * @param   mixed   variable that is meant to be bound to specified
4006      *       parameter. The type of the value depends on the $type argument.
4007      * @param   string  specifies the type of the field
4008      *
4009      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
4010      *
4011      * @access  public
4012      */
4013     function bindParam($parameter, &$value, $type = null)
4014     {
4015         if (!is_numeric($parameter)) {
4016             $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter);
4017         }
4018         if (!in_array($parameter, $this->positions)) {
4019             return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
4020                 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
4021         }
4022         $this->values[$parameter] =& $value;
4023         if (!is_null($type)) {
4024             $this->types[$parameter] = $type;
4025         }
4026         return MDB2_OK;
4027     }
4028
4029     // }}}
4030     // {{{ function bindParamArray(&$values, $types = null)
4031
4032     /**
4033      * Bind the variables of multiple a parameter of a prepared query in bulk.
4034      *
4035      * @param   array   specifies all necessary information
4036      *       for bindParam() the array elements must use keys corresponding to
4037      *       the number of the position of the parameter.
4038      * @param   array   specifies the types of the fields
4039      *
4040      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
4041      *
4042      * @access  public
4043      * @see     bindParam()
4044      */
4045     function bindParamArray(&$values, $types = null)
4046     {
4047         $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
4048         $parameters = array_keys($values);
4049         foreach ($parameters as $key => $parameter) {
4050             $err = $this->bindParam($parameter, $values[$parameter], $types[$key]);
4051             if (PEAR::isError($err)) {
4052                 return $err;
4053             }
4054         }
4055         return MDB2_OK;
4056     }
4057
4058     // }}}
4059     // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false)
4060
4061     /**
4062      * Execute a prepared query statement.
4063      *
4064      * @param   array   specifies all necessary information
4065      *       for bindParam() the array elements must use keys corresponding to
4066      *       the number of the position of the parameter.
4067      * @param   mixed   specifies which result class to use
4068      * @param   mixed   specifies which class to wrap results in
4069      *
4070      * @return  mixed   a result handle or MDB2_OK on success, a MDB2 error on failure
4071      *
4072      * @access  public
4073      */
4074     function &execute($values = null, $result_class = true, $result_wrap_class = false)
4075     {
4076         if (is_null($this->positions)) {
4077             return $this->db->raiseError(MDB2_ERROR, null, null,
4078                 'Prepared statement has already been freed', __FUNCTION__);
4079         }
4080
4081         $values = (array)$values;
4082         if (!empty($values)) {
4083             $err = $this->bindValueArray($values);
4084             if (PEAR::isError($err)) {
4085                 return $this->db->raiseError(MDB2_ERROR, null, null,
4086                                             'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__);
4087             }
4088         }
4089         $result =& $this->_execute($result_class, $result_wrap_class);
4090         return $result;
4091     }
4092
4093     // }}}
4094     // {{{ function &_execute($result_class = true, $result_wrap_class = false)
4095
4096     /**
4097      * Execute a prepared query statement helper method.
4098      *
4099      * @param   mixed   specifies which result class to use
4100      * @param   mixed   specifies which class to wrap results in
4101      *
4102      * @return  mixed   MDB2_Result or integer on success, a MDB2 error on failure
4103      *
4104      * @access  private
4105      */
4106     function &_execute($result_class = true, $result_wrap_class = false)
4107     {
4108         $this->last_query = $this->query;
4109         $query = '';
4110         $last_position = 0;
4111         foreach ($this->positions as $current_position => $parameter) {
4112             if (!array_key_exists($parameter, $this->values)) {
4113                 return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
4114                     'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
4115             }
4116             $value = $this->values[$parameter];
4117             $query.= substr($this->query, $last_position, $current_position - $last_position);
4118             if (!isset($value)) {
4119                 $value_quoted = 'NULL';
4120             } else {
4121                 $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null;
4122                 $value_quoted = $this->db->quote($value, $type);
4123                 if (PEAR::isError($value_quoted)) {
4124                     return $value_quoted;
4125                 }
4126             }
4127             $query.= $value_quoted;
4128             $last_position = $current_position + 1;
4129         }
4130         $query.= substr($this->query, $last_position);
4131
4132         $this->db->offset = $this->offset;
4133         $this->db->limit = $this->limit;
4134         if ($this->is_manip) {
4135             $result = $this->db->exec($query);
4136         } else {
4137             $result =& $this->db->query($query, $this->result_types, $result_class, $result_wrap_class);
4138         }
4139         return $result;
4140     }
4141
4142     // }}}
4143     // {{{ function free()
4144
4145     /**
4146      * Release resources allocated for the specified prepared query.
4147      *
4148      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
4149      *
4150      * @access  public
4151      */
4152     function free()
4153     {
4154         if (is_null($this->positions)) {
4155             return $this->db->raiseError(MDB2_ERROR, null, null,
4156                 'Prepared statement has already been freed', __FUNCTION__);
4157         }
4158
4159         $this->statement = null;
4160         $this->positions = null;
4161         $this->query = null;
4162         $this->types = null;
4163         $this->result_types = null;
4164         $this->limit = null;
4165         $this->is_manip = null;
4166         $this->offset = null;
4167         $this->values = null;
4168
4169         return MDB2_OK;
4170     }
4171
4172     // }}}
4173 }
4174
4175 // }}}
4176 // {{{ class MDB2_Module_Common
4177
4178 /**
4179  * The common modules class for MDB2 module objects
4180  *
4181  * @package     MDB2
4182  * @category    Database
4183  * @author      Lukas Smith <smith@pooteeweet.org>
4184  */
4185 class MDB2_Module_Common
4186 {
4187     // {{{ Variables (Properties)
4188
4189     /**
4190      * contains the key to the global MDB2 instance array of the associated
4191      * MDB2 instance
4192      *
4193      * @var     int
4194      * @access  protected
4195      */
4196     var $db_index;
4197
4198     // }}}
4199     // {{{ constructor: function __construct($db_index)
4200
4201     /**
4202      * Constructor
4203      */
4204     function __construct($db_index)
4205     {
4206         $this->db_index = $db_index;
4207     }
4208
4209     // }}}
4210     // {{{ function MDB2_Module_Common($db_index)
4211
4212     /**
4213      * PHP 4 Constructor
4214      */
4215     function MDB2_Module_Common($db_index)
4216     {
4217         $this->__construct($db_index);
4218     }
4219
4220     // }}}
4221     // {{{ function &getDBInstance()
4222
4223     /**
4224      * Get the instance of MDB2 associated with the module instance
4225      *
4226      * @return  object  MDB2 instance or a MDB2 error on failure
4227      *
4228      * @access  public
4229      */
4230     function &getDBInstance()
4231     {
4232         if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) {
4233             $result =& $GLOBALS['_MDB2_databases'][$this->db_index];
4234         } else {
4235             $result =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null,
4236                 'could not find MDB2 instance');
4237         }
4238         return $result;
4239     }
4240
4241     // }}}
4242 }
4243
4244 // }}}
4245 // {{{ function MDB2_closeOpenTransactions()
4246
4247 /**
4248  * Close any open transactions form persistent connections
4249  *
4250  * @return  void
4251  *
4252  * @access  public
4253  */
4254
4255 function MDB2_closeOpenTransactions()
4256 {
4257     reset($GLOBALS['_MDB2_databases']);
4258     while (next($GLOBALS['_MDB2_databases'])) {
4259         $key = key($GLOBALS['_MDB2_databases']);
4260         if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent
4261             && $GLOBALS['_MDB2_databases'][$key]->in_transaction
4262         ) {
4263             $GLOBALS['_MDB2_databases'][$key]->rollback();
4264         }
4265     }
4266 }
4267
4268 // }}}
4269 // {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null)
4270
4271 /**
4272  * default debug output handler
4273  *
4274  * @param   object  reference to an MDB2 database object
4275  * @param   string  usually the method name that triggered the debug call:
4276  *                  for example 'query', 'prepare', 'execute', 'parameters',
4277  *                  'beginTransaction', 'commit', 'rollback'
4278  * @param   string  message that should be appended to the debug variable
4279  * @param   array   contains context information about the debug() call
4280  *                  common keys are: is_manip, time, result etc.
4281  *
4282  * @return  void|string optionally return a modified message, this allows
4283  *                      rewriting a query before being issued or prepared
4284  *
4285  * @access  public
4286  */
4287 function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array())
4288 {
4289     $db->debug_output.= $scope.'('.$db->db_index.'): ';
4290     $db->debug_output.= $message.$db->getOption('log_line_break');
4291     return $message;
4292 }
4293
4294 // }}}
4295 ?>