]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB.php
get_class case-sensitivity issues
[SourceForge/phpwiki.git] / lib / pear / DB.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Stig Bakken <ssb@php.net>                                   |
17 // |          Tomas V.V.Cox <cox@idecnet.com>                             |
18 // +----------------------------------------------------------------------+
19 //
20 //
21 // Based on DB 1.3 from the pear.php.net repository. 
22 // The only modifications made have been modification of the include paths,
23 // plus the inclusion depricated DB_Warning class.
24 //
25 rcs_id('$Id: DB.php,v 1.5 2004-06-20 15:30:06 rurban Exp $');
26 rcs_id('From Pear CVS: Id: DB.php,v 1.20 2003/05/07 16:54:45 mj Exp');
27 //
28 // Database independent query interface.
29 //
30
31 require_once "PEAR.php";
32
33 // {{{ constants
34 // {{{ error codes
35 /*
36  * The method mapErrorCode in each DB_dbtype implementation maps
37  * native error codes to one of these.
38  *
39  * If you add an error code here, make sure you also add a textual
40  * version of it in DB::errorMessage().
41  */
42
43 define("DB_OK",                         1);
44 define("DB_ERROR",                     -1);
45 define("DB_ERROR_SYNTAX",              -2);
46 define("DB_ERROR_CONSTRAINT",          -3);
47 define("DB_ERROR_NOT_FOUND",           -4);
48 define("DB_ERROR_ALREADY_EXISTS",      -5);
49 define("DB_ERROR_UNSUPPORTED",         -6);
50 define("DB_ERROR_MISMATCH",            -7);
51 define("DB_ERROR_INVALID",             -8);
52 define("DB_ERROR_NOT_CAPABLE",         -9);
53 define("DB_ERROR_TRUNCATED",          -10);
54 define("DB_ERROR_INVALID_NUMBER",     -11);
55 define("DB_ERROR_INVALID_DATE",       -12);
56 define("DB_ERROR_DIVZERO",            -13);
57 define("DB_ERROR_NODBSELECTED",       -14);
58 define("DB_ERROR_CANNOT_CREATE",      -15);
59 define("DB_ERROR_CANNOT_DELETE",      -16);
60 define("DB_ERROR_CANNOT_DROP",        -17);
61 define("DB_ERROR_NOSUCHTABLE",        -18);
62 define("DB_ERROR_NOSUCHFIELD",        -19);
63 define("DB_ERROR_NEED_MORE_DATA",     -20);
64 define("DB_ERROR_NOT_LOCKED",         -21);
65 define("DB_ERROR_VALUE_COUNT_ON_ROW", -22);
66 define("DB_ERROR_INVALID_DSN",        -23);
67 define("DB_ERROR_CONNECT_FAILED",     -24);
68 define("DB_ERROR_EXTENSION_NOT_FOUND",-25);
69 define("DB_ERROR_ACCESS_VIOLATION",   -26);
70 define("DB_ERROR_NOSUCHDB",           -27);
71
72 // }}}
73 // {{{ warning codes
74 /*
75  * Warnings are not detected as errors by DB::isError(), and are not
76  * fatal.  You can detect whether an error is in fact a warning with
77  * DB::isWarning().
78  *
79  * @deprecated
80  */
81
82 define('DB_WARNING',           -1000);
83 define('DB_WARNING_READ_ONLY', -1001);
84
85 // }}}
86 // {{{ prepared statement-related
87 /*
88  * These constants are used when storing information about prepared
89  * statements (using the "prepare" method in DB_dbtype).
90  *
91  * The prepare/execute model in DB is mostly borrowed from the ODBC
92  * extension, in a query the "?" character means a scalar parameter.
93  * There are two extensions though, a "&" character means an opaque
94  * parameter.  An opaque parameter is simply a file name, the real
95  * data are in that file (useful for putting uploaded files into your
96  * database and such). The "!" char means a parameter that must be
97  * left as it is.
98  * They modify the quote behavoir:
99  * DB_PARAM_SCALAR (?) => 'original string quoted'
100  * DB_PARAM_OPAQUE (&) => 'string from file quoted'
101  * DB_PARAM_MISC   (!) => original string
102  */
103
104 define('DB_PARAM_SCALAR', 1);
105 define('DB_PARAM_OPAQUE', 2);
106 define('DB_PARAM_MISC',   3);
107
108 // }}}
109 // {{{ binary data-related
110 /*
111  * These constants define different ways of returning binary data
112  * from queries.  Again, this model has been borrowed from the ODBC
113  * extension.
114  *
115  * DB_BINMODE_PASSTHRU sends the data directly through to the browser
116  * when data is fetched from the database.
117  * DB_BINMODE_RETURN lets you return data as usual.
118  * DB_BINMODE_CONVERT returns data as well, only it is converted to
119  * hex format, for example the string "123" would become "313233".
120  */
121
122 define('DB_BINMODE_PASSTHRU', 1);
123 define('DB_BINMODE_RETURN',   2);
124 define('DB_BINMODE_CONVERT',  3);
125
126 // }}}
127 // {{{ fetch modes
128 /**
129  * This is a special constant that tells DB the user hasn't specified
130  * any particular get mode, so the default should be used.
131  */
132
133 define('DB_FETCHMODE_DEFAULT', 0);
134
135 /**
136  * Column data indexed by numbers, ordered from 0 and up
137  */
138
139 define('DB_FETCHMODE_ORDERED', 1);
140
141 /**
142  * Column data indexed by column names
143  */
144
145 define('DB_FETCHMODE_ASSOC', 2);
146
147 /**
148  * Column data as object properties
149  */
150
151 define('DB_FETCHMODE_OBJECT', 3);
152
153 /**
154  * For multi-dimensional results: normally the first level of arrays
155  * is the row number, and the second level indexed by column number or name.
156  * DB_FETCHMODE_FLIPPED switches this order, so the first level of arrays
157  * is the column name, and the second level the row number.
158  */
159
160 define('DB_FETCHMODE_FLIPPED', 4);
161
162 /* for compatibility */
163
164 define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
165 define('DB_GETMODE_ASSOC',   DB_FETCHMODE_ASSOC);
166 define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
167
168 // }}}
169 // {{{ tableInfo() && autoPrepare()-related
170 /**
171  * these are constants for the tableInfo-function
172  * they are bitwised or'ed. so if there are more constants to be defined
173  * in the future, adjust DB_TABLEINFO_FULL accordingly
174  */
175
176 define('DB_TABLEINFO_ORDER', 1);
177 define('DB_TABLEINFO_ORDERTABLE', 2);
178 define('DB_TABLEINFO_FULL', 3);
179
180 /*
181  * Used by autoPrepare()
182  */
183 define('DB_AUTOQUERY_INSERT', 1);
184 define('DB_AUTOQUERY_UPDATE', 2);
185
186 // }}}
187 // }}}
188
189 // {{{ class DB
190 /**
191  * The main "DB" class is simply a container class with some static
192  * methods for creating DB objects as well as some utility functions
193  * common to all parts of DB.
194  *
195  * The object model of DB is as follows (indentation means inheritance):
196  *
197  * DB           The main DB class.  This is simply a utility class
198  *              with some "static" methods for creating DB objects as
199  *              well as common utility functions for other DB classes.
200  *
201  * DB_common    The base for each DB implementation.  Provides default
202  * |            implementations (in OO lingo virtual methods) for
203  * |            the actual DB implementations as well as a bunch of
204  * |            query utility functions.
205  * |
206  * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
207  *              When calling DB::factory or DB::connect for MySQL
208  *              connections, the object returned is an instance of this
209  *              class.
210  *
211  * @package  DB
212  * @author   Stig Bakken <ssb@php.net>
213  * @since    PHP 4.0
214  */
215
216 class DB
217 {
218     // {{{ &factory()
219     /**
220      * Create a new DB connection object for the specified database
221      * type
222      *
223      * @param string $type database type, for example "mysql"
224      *
225      * @return mixed a newly created DB object, or a DB error code on
226      * error
227      *
228      * access public
229      */
230
231     function &factory($type)
232     {
233         @include_once("DB/${type}.php");
234
235         $classname = "DB_${type}";
236
237         if (!class_exists($classname)) {
238             return PEAR::raiseError(null, DB_ERROR_NOT_FOUND,
239                                     null, null, null, 'DB_Error', true);
240         }
241
242         @$obj =& new $classname;
243
244         return $obj;
245     }
246
247     // }}}
248     // {{{ &connect()
249     /**
250      * Create a new DB connection object and connect to the specified
251      * database
252      *
253      * @param mixed $dsn "data source name", see the DB::parseDSN
254      * method for a description of the dsn format.  Can also be
255      * specified as an array of the format returned by DB::parseDSN.
256      *
257      * @param mixed $options An associative array of option names and
258      * their values.  For backwards compatibility, this parameter may
259      * also be a boolean that tells whether the connection should be
260      * persistent.  See DB_common::setOption for more information on
261      * connection options.
262      *
263      * @return mixed a newly created DB connection object, or a DB
264      * error object on error
265      *
266      * @see DB::parseDSN
267      * @see DB::isError
268      * @see DB_common::setOption
269      */
270     function &connect($dsn, $options = false)
271     {
272         if (is_array($dsn)) {
273             $dsninfo = $dsn;
274         } else {
275             $dsninfo = DB::parseDSN($dsn);
276         }
277         $type = $dsninfo["phptype"];
278
279         if (is_array($options) && isset($options["debug"]) &&
280             $options["debug"] >= 2) {
281             // expose php errors with sufficient debug level
282             include_once "DB/${type}.php";
283         } else {
284             @include_once "DB/${type}.php";
285         }
286
287         $classname = "DB_${type}";
288         if (!class_exists($classname)) {
289             return PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
290                                     "Unable to include the DB/{$type}.php file for `$dsn'",
291                                     'DB_Error', true);
292         }
293
294         @$obj =& new $classname;
295
296         if (is_array($options)) {
297             foreach ($options as $option => $value) {
298                 $test = $obj->setOption($option, $value);
299                 if (DB::isError($test)) {
300                     return $test;
301                 }
302             }
303         } else {
304             $obj->setOption('persistent', $options);
305         }
306         $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
307         if (DB::isError($err)) {
308             $err->addUserInfo($dsn);
309             return $err;
310         }
311
312         return $obj;
313     }
314
315     // }}}
316     // {{{ apiVersion()
317     /**
318      * Return the DB API version
319      *
320      * @return int the DB API version number
321      *
322      * @access public
323      */
324     function apiVersion()
325     {
326         return 2;
327     }
328
329     // }}}
330     // {{{ isError()
331     /**
332      * Tell whether a result code from a DB method is an error
333      *
334      * @param int $value result code
335      *
336      * @return bool whether $value is an error
337      *
338      * @access public
339      */
340     function isError($value)
341     {
342         return (is_object($value) &&
343                 (strtolower(get_class($value)) == 'db_error' ||
344                  is_subclass_of($value, 'db_error')));
345     }
346
347     // }}}
348     // {{{ isConnection()
349     /**
350      * Tell whether a value is a DB connection
351      *
352      * @param mixed $value value to test
353      *
354      * @return bool whether $value is a DB connection
355      *
356      * @access public
357      */
358     function isConnection($value)
359     {
360         return (is_object($value) &&
361                 is_subclass_of($value, 'db_common') &&
362                 method_exists($value, 'simpleQuery'));
363     }
364
365     // }}}
366     // {{{ isManip()
367     /**
368      * Tell whether a query is a data manipulation query (insert,
369      * update or delete) or a data definition query (create, drop,
370      * alter, grant, revoke).
371      *
372      * @access public
373      *
374      * @param string $query the query
375      *
376      * @return boolean whether $query is a data manipulation query
377      */
378     function isManip($query)
379     {
380         $manips = 'INSERT|UPDATE|DELETE|'.'REPLACE|CREATE|DROP|'.
381                   'ALTER|GRANT|REVOKE|'.'LOCK|UNLOCK';
382         if (preg_match('/^\s*"?('.$manips.')\s+/i', $query)) {
383             return true;
384         }
385         return false;
386     }
387
388     // }}}
389     // {{{ errorMessage()
390     /**
391      * Return a textual error message for a DB error code
392      *
393      * @param integer $value error code
394      *
395      * @return string error message, or false if the error code was
396      * not recognized
397      */
398     function errorMessage($value)
399     {
400         static $errorMessages;
401         if (!isset($errorMessages)) {
402             $errorMessages = array(
403                 DB_ERROR                    => 'unknown error',
404                 DB_ERROR_ALREADY_EXISTS     => 'already exists',
405                 DB_ERROR_CANNOT_CREATE      => 'can not create',
406                 DB_ERROR_CANNOT_DELETE      => 'can not delete',
407                 DB_ERROR_CANNOT_DROP        => 'can not drop',
408                 DB_ERROR_CONSTRAINT         => 'constraint violation',
409                 DB_ERROR_DIVZERO            => 'division by zero',
410                 DB_ERROR_INVALID            => 'invalid',
411                 DB_ERROR_INVALID_DATE       => 'invalid date or time',
412                 DB_ERROR_INVALID_NUMBER     => 'invalid number',
413                 DB_ERROR_MISMATCH           => 'mismatch',
414                 DB_ERROR_NODBSELECTED       => 'no database selected',
415                 DB_ERROR_NOSUCHFIELD        => 'no such field',
416                 DB_ERROR_NOSUCHTABLE        => 'no such table',
417                 DB_ERROR_NOT_CAPABLE        => 'DB backend not capable',
418                 DB_ERROR_NOT_FOUND          => 'not found',
419                 DB_ERROR_NOT_LOCKED         => 'not locked',
420                 DB_ERROR_SYNTAX             => 'syntax error',
421                 DB_ERROR_UNSUPPORTED        => 'not supported',
422                 DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
423                 DB_ERROR_INVALID_DSN        => 'invalid DSN',
424                 DB_ERROR_CONNECT_FAILED     => 'connect failed',
425                 DB_OK                       => 'no error',
426                 DB_WARNING                  => 'unknown warning',
427                 DB_WARNING_READ_ONLY        => 'read only',
428                 DB_ERROR_NEED_MORE_DATA     => 'insufficient data supplied',
429                 DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
430                 DB_ERROR_NOSUCHDB           => 'no such database',
431                 DB_ERROR_ACCESS_VIOLATION   => 'insufficient permissions',
432                 DB_ERROR_TRUNCATED          => 'truncated'
433             );
434         }
435
436         if (DB::isError($value)) {
437             $value = $value->getCode();
438         }
439
440         return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[DB_ERROR];
441     }
442
443     // }}}
444     // {{{ parseDSN()
445     /**
446      * Parse a data source name
447      *
448      * A array with the following keys will be returned:
449      *  phptype: Database backend used in PHP (mysql, odbc etc.)
450      *  dbsyntax: Database used with regards to SQL syntax etc.
451      *  protocol: Communication protocol to use (tcp, unix etc.)
452      *  hostspec: Host specification (hostname[:port])
453      *  database: Database to use on the DBMS server
454      *  username: User name for login
455      *  password: Password for login
456      *
457      * The format of the supplied DSN is in its fullest form:
458      *
459      *  phptype(dbsyntax)://username:password@protocol+hostspec/database
460      *
461      * Most variations are allowed:
462      *
463      *  phptype://username:password@protocol+hostspec:110//usr/db_file.db
464      *  phptype://username:password@hostspec/database_name
465      *  phptype://username:password@hostspec
466      *  phptype://username@hostspec
467      *  phptype://hostspec/database
468      *  phptype://hostspec
469      *  phptype(dbsyntax)
470      *  phptype
471      *
472      * @param string $dsn Data Source Name to be parsed
473      *
474      * @return array an associative array
475      *
476      * @author Tomas V.V.Cox <cox@idecnet.com>
477      */
478     function parseDSN($dsn)
479     {
480         if (is_array($dsn)) {
481             return $dsn;
482         }
483
484         $parsed = array(
485             'phptype'  => false,
486             'dbsyntax' => false,
487             'username' => false,
488             'password' => false,
489             'protocol' => false,
490             'hostspec' => false,
491             'port'     => false,
492             'socket'   => false,
493             'database' => false
494         );
495
496         // Find phptype and dbsyntax
497         if (($pos = strpos($dsn, '://')) !== false) {
498             $str = substr($dsn, 0, $pos);
499             $dsn = substr($dsn, $pos + 3);
500         } else {
501             $str = $dsn;
502             $dsn = NULL;
503         }
504
505         // Get phptype and dbsyntax
506         // $str => phptype(dbsyntax)
507         if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
508             $parsed['phptype']  = $arr[1];
509             $parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2];
510         } else {
511             $parsed['phptype']  = $str;
512             $parsed['dbsyntax'] = $str;
513         }
514
515         if (empty($dsn)) {
516             return $parsed;
517         }
518
519         // Get (if found): username and password
520         // $dsn => username:password@protocol+hostspec/database
521         if (($at = strrpos($dsn,'@')) !== false) {
522             $str = substr($dsn, 0, $at);
523             $dsn = substr($dsn, $at + 1);
524             if (($pos = strpos($str, ':')) !== false) {
525                 $parsed['username'] = rawurldecode(substr($str, 0, $pos));
526                 $parsed['password'] = rawurldecode(substr($str, $pos + 1));
527             } else {
528                 $parsed['username'] = rawurldecode($str);
529             }
530         }
531
532         // Find protocol and hostspec
533
534         // $dsn => proto(proto_opts)/database
535         if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
536             $proto       = $match[1];
537             $proto_opts  = (!empty($match[2])) ? $match[2] : false;
538             $dsn         = $match[3];
539
540         // $dsn => protocol+hostspec/database (old format)
541         } else {
542             if (strpos($dsn, '+') !== false) {
543                 list($proto, $dsn) = explode('+', $dsn, 2);
544             }
545             if (strpos($dsn, '/') !== false) {
546                 list($proto_opts, $dsn) = explode('/', $dsn, 2);
547             } else {
548                 $proto_opts = $dsn;
549                 $dsn = null;
550             }
551         }
552
553         // process the different protocol options
554         $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
555         $proto_opts = rawurldecode($proto_opts);
556         if ($parsed['protocol'] == 'tcp') {
557             if (strpos($proto_opts, ':') !== false) {
558                 list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts);
559             } else {
560                 $parsed['hostspec'] = $proto_opts;
561             }
562         } elseif ($parsed['protocol'] == 'unix') {
563             $parsed['socket'] = $proto_opts;
564         }
565
566         // Get dabase if any
567         // $dsn => database
568         if (!empty($dsn)) {
569             // /database
570             if (($pos = strpos($dsn, '?')) === false) {
571                 $parsed['database'] = $dsn;
572             // /database?param1=value1&param2=value2
573             } else {
574                 $parsed['database'] = substr($dsn, 0, $pos);
575                 $dsn = substr($dsn, $pos + 1);
576                 if (strpos($dsn, '&') !== false) {
577                     $opts = explode('&', $dsn);
578                 } else { // database?param1=value1
579                     $opts = array($dsn);
580                 }
581                 foreach ($opts as $opt) {
582                     list($key, $value) = explode('=', $opt);
583                     if (!isset($parsed[$key])) { // don't allow params overwrite
584                         $parsed[$key] = rawurldecode($value);
585                     }
586                 }
587             }
588         }
589
590         return $parsed;
591     }
592
593     // }}}
594     // {{{ assertExtension()
595     /**
596      * Load a PHP database extension if it is not loaded already.
597      *
598      * @access public
599      *
600      * @param string $name the base name of the extension (without the .so or
601      *                     .dll suffix)
602      *
603      * @return boolean true if the extension was already or successfully
604      *                 loaded, false if it could not be loaded
605      */
606     function assertExtension($name)
607     {
608         if (!extension_loaded($name)) {
609             $dlext = OS_WINDOWS ? '.dll' : '.so';
610             $dlprefix = OS_WINDOWS ? 'php_' : '';
611             @dl($dlprefix . $name . $dlext);
612             return extension_loaded($name);
613         }
614         return true;
615     }
616     // }}}
617 }
618 // }}}
619
620 // {{{ class DB_Error
621 /**
622  * DB_Error implements a class for reporting portable database error
623  * messages.
624  *
625  * @package  DB
626  * @author Stig Bakken <ssb@php.net>
627  */
628 class DB_Error extends PEAR_Error
629 {
630     // {{{ constructor
631     /**
632      * DB_Error constructor.
633      *
634      * @param mixed   $code   DB error code, or string with error message.
635      * @param integer $mode   what "error mode" to operate in
636      * @param integer $level  what error level to use for $mode & PEAR_ERROR_TRIGGER
637      * @param mixed   $debuginfo  additional debug info, such as the last query
638      *
639      * @access public
640      *
641      * @see PEAR_Error
642      */
643
644     function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
645               $level = E_USER_NOTICE, $debuginfo = null)
646     {
647         if (is_int($code)) {
648             $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, $mode, $level, $debuginfo);
649         } else {
650             $this->PEAR_Error("DB Error: $code", DB_ERROR, $mode, $level, $debuginfo);
651         }
652     }
653     // }}}
654 }
655 // }}}
656
657 /**
658  * DB_Warning implements a class for reporting portable database
659  * warning messages.
660  *
661  * @package  DB
662  * @author Stig Bakken <ssb@fast.no>
663  * @deprecated
664  */
665 class DB_Warning extends PEAR_Error
666 {
667     /**
668      * DB_Warning constructor.
669      *
670      * @param mixed   $code      DB error code, or string with error message.
671      * @param integer $mode      what "error mode" to operate in
672      * @param integer $level     what error level to use for $mode == PEAR_ERROR_TRIGGER
673      * @param mmixed  $debuginfo additional debug info, such as the last query
674      *
675      * @access public
676      *
677      * @see PEAR_Error
678      */
679
680     function DB_Warning($code = DB_WARNING, $mode = PEAR_ERROR_RETURN,
681             $level = E_USER_NOTICE, $debuginfo = null)
682     {
683         if (is_int($code)) {
684             $this->PEAR_Error('DB Warning: ' . DB::errorMessage($code), $code, $mode, $level, $debuginfo);
685         } else {
686             $this->PEAR_Error("DB Warning: $code", 0, $mode, $level, $debuginfo);
687         }
688     }
689 }
690
691 /**
692  * This class implements a wrapper for a DB result set.
693  * A new instance of this class will be returned by the DB implementation
694  * after processing a query that returns data.
695  *
696  * @package  DB
697  * @author Stig Bakken <ssb@php.net>
698  */
699
700 class DB_result
701 {
702     // {{{ properties
703
704     var $dbh;
705     var $result;
706     var $row_counter = null;
707     /**
708     * for limit queries, the row to start fetching
709     * @var integer
710     */
711     var $limit_from  = null;
712
713     /**
714     * for limit queries, the number of rows to fetch
715     * @var integer
716     */
717     var $limit_count = null;
718
719     // }}}
720     // {{{ constructor
721     /**
722      * DB_result constructor.
723      * @param resource &$dbh   DB object reference
724      * @param resource $result result resource id
725      */
726
727     function DB_result(&$dbh, $result, $options = array())
728     {
729         $this->dbh = &$dbh;
730         $this->result = $result;
731         $this->limit_from  = isset($options['limit_from'])  ? $options['limit_from']  : null;
732         $this->limit_count = isset($options['limit_count']) ? $options['limit_count'] : null;
733         $this->limit_type  = $dbh->features['limit'];
734         $this->autofree    = $dbh->options['autofree'];
735         $this->fetchmode   = $dbh->fetchmode;
736         $this->fetchmode_object_class = $dbh->fetchmode_object_class;
737     }
738
739     // }}}
740     // {{{ fetchRow()
741     /**
742      * Fetch and return a row of data (it uses driver->fetchInto for that)
743      * @param int $fetchmode format of fetched row
744      * @param int $rownum    the row number to fetch
745      *
746      * @return  array a row of data, NULL on no more rows or PEAR_Error on error
747      *
748      * @access public
749      */
750     function fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
751     {
752         if ($fetchmode === DB_FETCHMODE_DEFAULT) {
753             $fetchmode = $this->fetchmode;
754         }
755         if ($fetchmode === DB_FETCHMODE_OBJECT) {
756             $fetchmode = DB_FETCHMODE_ASSOC;
757             $object_class = $this->fetchmode_object_class;
758         }
759         if ($this->limit_from !== null) {
760             if ($this->row_counter === null) {
761                 $this->row_counter = $this->limit_from;
762                 // Skip rows
763                 if ($this->limit_type == false) {
764                     $i = 0;
765                     while ($i++ < $this->limit_from) {
766                         $this->dbh->fetchInto($this->result, $arr, $fetchmode);
767                     }
768                 }
769             }
770             if ($this->row_counter >= (
771                     $this->limit_from + $this->limit_count))
772             {
773                 return null;
774             }
775             if ($this->limit_type == 'emulate') {
776                 $rownum = $this->row_counter;
777             }
778
779             $this->row_counter++;
780         }
781         $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
782         if ($res !== DB_OK) {
783             if ($res == null && $this->autofree) {
784                 $this->free();
785             }
786             return $res;
787         }
788         if (isset($object_class)) {
789             // default mode specified in DB_common::fetchmode_object_class property
790             if ($object_class == 'stdClass') {
791                 $ret = (object) $arr;
792             } else {
793                 $ret =& new $object_class($arr);
794             }
795             return $ret;
796         }
797         return $arr;
798     }
799
800     // }}}
801     // {{{ fetchInto()
802     /**
803      * Fetch a row of data into an existing variable.
804      *
805      * @param  mixed   &$arr     reference to data containing the row
806      * @param  integer $fetchmod format of fetched row
807      * @param  integer $rownum   the row number to fetch
808      *
809      * @return  mixed  DB_OK on success, NULL on no more rows or
810      *                 a DB_Error object on error
811      *
812      * @access public
813      */
814     function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
815     {
816         if ($fetchmode === DB_FETCHMODE_DEFAULT) {
817             $fetchmode = $this->fetchmode;
818         }
819         if ($fetchmode === DB_FETCHMODE_OBJECT) {
820             $fetchmode = DB_FETCHMODE_ASSOC;
821             $object_class = $this->fetchmode_object_class;
822         }
823         if ($this->limit_from !== null) {
824             if ($this->row_counter === null) {
825                 $this->row_counter = $this->limit_from;
826                 // Skip rows
827                 if ($this->limit_type == false) {
828                     $i = 0;
829                     while ($i++ < $this->limit_from) {
830                         $this->dbh->fetchInto($this->result, $arr, $fetchmode);
831                     }
832                 }
833             }
834             if ($this->row_counter >= (
835                     $this->limit_from + $this->limit_count))
836             {
837                 return null;
838             }
839             if ($this->limit_type == 'emulate') {
840                 $rownum = $this->row_counter;
841             }
842
843             $this->row_counter++;
844         }
845         $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
846         if (($res === DB_OK) && isset($object_class)) {
847             // default mode specified in DB_common::fetchmode_object_class property
848             if ($object_class == 'stdClass') {
849                 $arr = (object) $arr;
850             } else {
851                 $arr = new $object_class($arr);
852             }
853         } elseif ($res == null && $this->autofree) {
854             $this->free();
855         }
856         return $res;
857     }
858
859     // }}}
860     // {{{ numCols()
861     /**
862      * Get the the number of columns in a result set.
863      *
864      * @return int the number of columns, or a DB error
865      *
866      * @access public
867      */
868     function numCols()
869     {
870         return $this->dbh->numCols($this->result);
871     }
872
873     // }}}
874     // {{{ numRows()
875     /**
876      * Get the number of rows in a result set.
877      *
878      * @return int the number of rows, or a DB error
879      *
880      * @access public
881      */
882     function numRows()
883     {
884         return $this->dbh->numRows($this->result);
885     }
886
887     // }}}
888     // {{{ nextResult()
889     /**
890      * Get the next result if a batch of queries was executed.
891      *
892      * @return bool true if a new result is available or false if not.
893      *
894      * @access public
895      */
896     function nextResult()
897     {
898         return $this->dbh->nextResult($this->result);
899     }
900
901     // }}}
902     // {{{ free()
903     /**
904      * Frees the resources allocated for this result set.
905      * @return  int error code
906      *
907      * @access public
908      */
909     function free()
910     {
911         $err = $this->dbh->freeResult($this->result);
912         if(DB::isError($err)) {
913             return $err;
914         }
915         $this->result = false;
916         return true;
917     }
918
919     // }}}
920     // {{{ tableInfo()
921     /**
922     * @deprecated
923     */
924     function tableInfo($mode = null)
925     {
926         return $this->dbh->tableInfo($this->result, $mode);
927     }
928
929     // }}}
930     // {{{ getRowCounter()
931     /**
932     * returns the actual row number
933     * @return integer
934     */
935     function getRowCounter()
936     {
937         return $this->row_counter;
938     }
939     // }}}
940 }
941 // }}}
942
943 // {{{ class DB_Row
944 /**
945 * Pear DB Row Object
946 * @see DB_common::setFetchMode()
947 */
948 class DB_row
949 {
950     // {{{ constructor
951     /**
952     * constructor
953     *
954     * @param resource row data as array
955     */
956     function DB_row(&$arr)
957     {
958         for (reset($arr); $key = key($arr); next($arr)) {
959             $this->$key = &$arr[$key];
960         }
961     }
962
963     // }}}
964 }
965 // }}}
966
967 ?>