source: branches/dev/data/module/MDB2.php @ 15918

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