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