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

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