thomascube
2006-05-01 6204390af16bcf50f82da61a1aefc2ad0c0adf94
commit | author | age
b076a4 1 <?php
T 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
7 // | Stig. S. Bakken, Lukas Smith                                         |
8 // | All rights reserved.                                                 |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
11 // | API as well as database abstraction for PHP applications.            |
12 // | This LICENSE is in the BSD license style.                            |
13 // |                                                                      |
14 // | Redistribution and use in source and binary forms, with or without   |
15 // | modification, are permitted provided that the following conditions   |
16 // | are met:                                                             |
17 // |                                                                      |
18 // | Redistributions of source code must retain the above copyright       |
19 // | notice, this list of conditions and the following disclaimer.        |
20 // |                                                                      |
21 // | Redistributions in binary form must reproduce the above copyright    |
22 // | notice, this list of conditions and the following disclaimer in the  |
23 // | documentation and/or other materials provided with the distribution. |
24 // |                                                                      |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission.                                                  |
29 // |                                                                      |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41 // | POSSIBILITY OF SUCH DAMAGE.                                          |
42 // +----------------------------------------------------------------------+
43 // | Author: Lukas Smith <smith@pooteeweet.org>                           |
44 // +----------------------------------------------------------------------+
45 //
46 // $Id$
47 //
48
49 /**
50  * MDB2 MySQL driver
51  *
52  * @package MDB2
53  * @category Database
54  * @author  Lukas Smith <smith@pooteeweet.org>
55  */
56 class MDB2_Driver_mysql extends MDB2_Driver_Common
57 {
58     // {{{ properties
59     var $escape_quotes = "\\";
60
61     // }}}
62     // {{{ constructor
63
64     /**
65     * Constructor
66     */
67     function __construct()
68     {
69         parent::__construct();
70
71         $this->phptype = 'mysql';
72         $this->dbsyntax = 'mysql';
73
74         $this->supported['sequences'] = 'emulated';
75         $this->supported['indexes'] = true;
76         $this->supported['affected_rows'] = true;
77         $this->supported['transactions'] = false;
78         $this->supported['summary_functions'] = true;
79         $this->supported['order_by_text'] = true;
80         $this->supported['current_id'] = 'emulated';
81         $this->supported['limit_queries'] = true;
82         $this->supported['LOBs'] = true;
83         $this->supported['replace'] = true;
84         $this->supported['sub_selects'] = 'emulated';
85         $this->supported['auto_increment'] = true;
86         $this->supported['primary_key'] = true;
87
88         $this->options['default_table_type'] = null;
89     }
90
91     // }}}
92     // {{{ errorInfo()
93
94     /**
95      * This method is used to collect information about an error
96      *
97      * @param integer $error
98      * @return array
99      * @access public
100      */
101     function errorInfo($error = null)
102     {
103         if ($this->connection) {
104             $native_code = @mysql_errno($this->connection);
105             $native_msg  = @mysql_error($this->connection);
106         } else {
107             $native_code = @mysql_errno();
108             $native_msg  = @mysql_error();
109         }
110         if (is_null($error)) {
111             static $ecode_map;
112             if (empty($ecode_map)) {
113                 $ecode_map = array(
114                     1004 => MDB2_ERROR_CANNOT_CREATE,
115                     1005 => MDB2_ERROR_CANNOT_CREATE,
116                     1006 => MDB2_ERROR_CANNOT_CREATE,
117                     1007 => MDB2_ERROR_ALREADY_EXISTS,
118                     1008 => MDB2_ERROR_CANNOT_DROP,
119                     1022 => MDB2_ERROR_ALREADY_EXISTS,
120                     1044 => MDB2_ERROR_ACCESS_VIOLATION,
121                     1046 => MDB2_ERROR_NODBSELECTED,
122                     1048 => MDB2_ERROR_CONSTRAINT,
123                     1049 => MDB2_ERROR_NOSUCHDB,
124                     1050 => MDB2_ERROR_ALREADY_EXISTS,
125                     1051 => MDB2_ERROR_NOSUCHTABLE,
126                     1054 => MDB2_ERROR_NOSUCHFIELD,
127                     1061 => MDB2_ERROR_ALREADY_EXISTS,
128                     1062 => MDB2_ERROR_ALREADY_EXISTS,
129                     1064 => MDB2_ERROR_SYNTAX,
130                     1091 => MDB2_ERROR_NOT_FOUND,
131                     1100 => MDB2_ERROR_NOT_LOCKED,
132                     1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
133                     1142 => MDB2_ERROR_ACCESS_VIOLATION,
134                     1146 => MDB2_ERROR_NOSUCHTABLE,
135                     1216 => MDB2_ERROR_CONSTRAINT,
136                     1217 => MDB2_ERROR_CONSTRAINT,
137                 );
138             }
139             if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
140                 $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
141                 $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
142                 $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
143             } else {
144                 // Doing this in case mode changes during runtime.
145                 $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
146                 $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
147                 $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
148             }
149             if (isset($ecode_map[$native_code])) {
150                 $error = $ecode_map[$native_code];
151             }
152         }
153         return array($error, $native_code, $native_msg);
154     }
155
156     // }}}
157     // {{{ escape()
158
159     /**
160      * Quotes a string so it can be safely used in a query. It will quote
161      * the text so it can safely be used within a query.
162      *
163      * @param string $text the input string to quote
164      * @return string quoted string
165      * @access public
166      */
167     function escape($text)
168     {
169         return @mysql_real_escape_string($text);
170     }
171
172     // }}}
173     // {{{ quoteIdentifier()
174
175     /**
176      * Quote a string so it can be safely used as a table or column name
177      *
178      * Quoting style depends on which database driver is being used.
179      *
180      * MySQL can't handle the backtick character (<kbd>`</kbd>) in
181      * table or column names.
182      *
183      * @param string $str  identifier name to be quoted
184      *
185      * @return string  quoted identifier string
186      *
187      * @access public
188      * @internal
189      */
190     function quoteIdentifier($str)
191     {
192         return '`' . $str . '`';
193     }
194
195     // }}}
196     // {{{ beginTransaction()
197
198     /**
199      * Start a transaction.
200      *
201      * @return mixed MDB2_OK on success, a MDB2 error on failure
202      * @access public
203      */
204     function beginTransaction()
205     {
206         $this->debug('starting transaction', 'beginTransaction');
207         if (!$this->supports('transactions')) {
208             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
209                 'beginTransaction: transactions are not in use');
210         }
211         if ($this->in_transaction) {
212             return MDB2_OK;  //nothing to do
213         }
214         if (!$this->destructor_registered && $this->opened_persistent) {
215             $this->destructor_registered = true;
216             register_shutdown_function('MDB2_closeOpenTransactions');
217         }
218         $result = $this->_doQuery('SET AUTOCOMMIT = 0', true);
219         if (PEAR::isError($result)) {
220             return $result;
221         }
222         $this->in_transaction = true;
223         return MDB2_OK;
224     }
225
226     // }}}
227     // {{{ commit()
228
229     /**
230      * Commit the database changes done during a transaction that is in
231      * progress.
232      *
233      * @return mixed MDB2_OK on success, a MDB2 error on failure
234      * @access public
235      */
236     function commit()
237     {
238         $this->debug('commit transaction', 'commit');
239         if (!$this->supports('transactions')) {
240             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
241                 'commit: transactions are not in use');
242         }
243         if (!$this->in_transaction) {
244             return $this->raiseError(MDB2_ERROR, null, null,
245                 'commit: transaction changes are being auto committed');
246         }
247         $result = $this->_doQuery('COMMIT', true);
248         if (PEAR::isError($result)) {
249             return $result;
250         }
251         $result = $this->_doQuery('SET AUTOCOMMIT = 1', true);
252         if (PEAR::isError($result)) {
253             return $result;
254         }
255         $this->in_transaction = false;
256         return MDB2_OK;
257     }
258
259     // }}}
260     // {{{ rollback()
261
262     /**
263      * Cancel any database changes done during a transaction that is in
264      * progress.
265      *
266      * @return mixed MDB2_OK on success, a MDB2 error on failure
267      * @access public
268      */
269     function rollback()
270     {
271         $this->debug('rolling back transaction', 'rollback');
272         if (!$this->supports('transactions')) {
273             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
274                 'rollback: transactions are not in use');
275         }
276         if (!$this->in_transaction) {
277             return $this->raiseError(MDB2_ERROR, null, null,
278                 'rollback: transactions can not be rolled back when changes are auto committed');
279         }
280         $result = $this->_doQuery('ROLLBACK', true);
281         if (PEAR::isError($result)) {
282             return $result;
283         }
284         $result = $this->_doQuery('SET AUTOCOMMIT = 1', true);
285         if (PEAR::isError($result)) {
286             return $result;
287         }
288         $this->in_transaction = false;
289         return MDB2_OK;
290
291     }
292
293     // }}}
294     // {{{ connect()
295
296     /**
297      * Connect to the database
298      *
299      * @return true on success, MDB2 Error Object on failure
300      */
301     function connect()
302     {
303         if (is_resource($this->connection)) {
304             if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
305                 && $this->opened_persistent == $this->options['persistent']
306             ) {
307                 return MDB2_OK;
308             }
309             $this->disconnect(false);
310         }
311
312         if (!PEAR::loadExtension($this->phptype)) {
313             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
314                 'connect: extension '.$this->phptype.' is not compiled into PHP');
315         }
316
317         $params = array();
318         if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') {
319             $params[0] = ':' . $this->dsn['socket'];
320         } else {
321             $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec']
322                          : 'localhost';
323             if ($this->dsn['port']) {
324                 $params[0].= ':' . $this->dsn['port'];
325             }
326         }
327         $params[] = $this->dsn['username'] ? $this->dsn['username'] : null;
328         $params[] = $this->dsn['password'] ? $this->dsn['password'] : null;
329         if (!$this->options['persistent']) {
330             if (isset($this->dsn['new_link'])
331                 && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true)
332             ) {
333                 $params[] = true;
334             } else {
335                 $params[] = false;
336             }
337         }
338         if (version_compare(phpversion(), '4.3.0', '>=')) {
339             $params[] = isset($this->dsn['client_flags'])
340                 ? $this->dsn['client_flags'] : null;
341         }
342
343         $connect_function = $this->options['persistent'] ? 'mysql_pconnect' : 'mysql_connect';
344
345         @ini_set('track_errors', true);
346         $php_errormsg = '';
347         $connection = @call_user_func_array($connect_function, $params);
348         @ini_restore('track_errors');
349         if (!$connection) {
350             if (($err = @mysql_error()) != '') {
351                 return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $err);
352             } else {
353                 return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $php_errormsg);
354             }
355         }
356
357         $this->connection = $connection;
358         $this->connected_dsn = $this->dsn;
359         $this->connected_database_name = '';
360         $this->opened_persistent = $this->options['persistent'];
361         $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
362
363         $this->supported['transactions'] = false;
364         if ($this->options['default_table_type']) {
365             switch (strtoupper($this->options['default_table_type'])) {
366             case 'BERKELEYDB':
367                 $this->options['default_table_type'] = 'BDB';
368             case 'BDB':
369             case 'INNODB':
370             case 'GEMINI':
371                 $this->supported['transactions'] = true;
372                 break;
373             case 'HEAP':
374             case 'ISAM':
375             case 'MERGE':
376             case 'MRG_MYISAM':
377             case 'MYISAM':
378                 break;
379             default:
380                 $this->warnings[] = $default_table_type.
381                     ' is not a supported default table type';
382             }
383         }
384
385         if ($this->options['use_transactions'] && !$this->supports('transactions')) {
386             $this->warnings[] = $this->options['default_table_type'].
387                 ' is not a transaction-safe default table type; switched to INNODB';
388             $this->options['default_table_type'] = 'INNODB';
389             $this->supported['transactions'] = true;
390         }
391
392         return MDB2_OK;
393     }
394
395     // }}}
396     // {{{ disconnect()
397
398     /**
399      * Log out and disconnect from the database.
400      *
401      * @return mixed true on success, false if not connected and error
402      *                object on error
403      * @access public
404      */
405     function disconnect($force = true)
406     {
407         if (is_resource($this->connection)) {
408             if (!$this->opened_persistent || $force) {
409                 @mysql_close($this->connection);
410             }
411             $this->connection = 0;
412         }
413         return MDB2_OK;
414     }
415
416     // }}}
417     // {{{ _doQuery()
418
419     /**
420      * Execute a query
421      * @param string $query  query
422      * @param boolean $isManip  if the query is a manipulation query
423      * @param resource $connection
424      * @param string $database_name
425      * @return result or error object
426      * @access protected
427      */
428     function _doQuery($query, $isManip = false, $connection = null, $database_name = null)
429     {
430         $this->last_query = $query;
431         $this->debug($query, 'query');
432         if ($this->options['disable_query']) {
433             if ($isManip) {
434                 return 0;
435             }
436             return null;
437         }
438
439         if (is_null($connection)) {
440             $err = $this->connect();
441             if (PEAR::isError($err)) {
442                 return $err;
443             }
444             $connection = $this->connection;
445         }
446         if (is_null($database_name)) {
447             $database_name = $this->database_name;
448         }
449
450         if ($database_name) {
451             if ($database_name != $this->connected_database_name) {
452                 if (!@mysql_select_db($database_name, $connection)) {
453                     return $this->raiseError();
454                 }
455                 $this->connected_database_name = $database_name;
456             }
457         }
458
459         $function = $this->options['result_buffering']
460             ? 'mysql_query' : 'mysql_unbuffered_query';
461         $result = @$function($query, $connection);
462         if (!$result) {
463             return $this->raiseError();
464         }
465
466         if ($isManip) {
467             return @mysql_affected_rows($connection);
468         }
469         return $result;
470     }
471
472     // }}}
473     // {{{ _modifyQuery()
474
475     /**
476      * Changes a query string for various DBMS specific reasons
477      *
478      * @param string $query  query to modify
479      * @return the new (modified) query
480      * @access protected
481      */
482     function _modifyQuery($query, $isManip, $limit, $offset)
483     {
484         if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
485             // "DELETE FROM table" gives 0 affected rows in MySQL.
486             // This little hack lets you know how many rows were deleted.
487             if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
488                 $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
489                                       'DELETE FROM \1 WHERE 1=1', $query);
490             }
491         }
492         if ($limit > 0
493             && !preg_match('/LIMIT\s*\d(\s*(,|OFFSET)\s*\d+)?/i', $query)
494         ) {
495             $query = rtrim($query);
496             if (substr($query, -1) == ';') {
497                 $query = substr($query, 0, -1);
498             }
499             if ($isManip) {
500                 return $query . " LIMIT $limit";
501             } else {
502                 return $query . " LIMIT $offset, $limit";
503             }
504         }
505         return $query;
506     }
507
508     // }}}
509     // {{{ replace()
510
511     /**
512      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
513      * query, except that if there is already a row in the table with the same
514      * key field values, the REPLACE query just updates its values instead of
515      * inserting a new row.
516      *
517      * The REPLACE type of query does not make part of the SQL standards. Since
518      * practically only MySQL implements it natively, this type of query is
519      * emulated through this method for other DBMS using standard types of
520      * queries inside a transaction to assure the atomicity of the operation.
521      *
522      * @access public
523      *
524      * @param string $table name of the table on which the REPLACE query will
525      *  be executed.
526      * @param array $fields associative array that describes the fields and the
527      *  values that will be inserted or updated in the specified table. The
528      *  indexes of the array are the names of all the fields of the table. The
529      *  values of the array are also associative arrays that describe the
530      *  values and other properties of the table fields.
531      *
532      *  Here follows a list of field properties that need to be specified:
533      *
534      *    value:
535      *          Value to be assigned to the specified field. This value may be
536      *          of specified in database independent type format as this
537      *          function can perform the necessary datatype conversions.
538      *
539      *    Default:
540      *          this property is required unless the Null property
541      *          is set to 1.
542      *
543      *    type
544      *          Name of the type of the field. Currently, all types Metabase
545      *          are supported except for clob and blob.
546      *
547      *    Default: no type conversion
548      *
549      *    null
550      *          Boolean property that indicates that the value for this field
551      *          should be set to null.
552      *
553      *          The default value for fields missing in INSERT queries may be
554      *          specified the definition of a table. Often, the default value
555      *          is already null, but since the REPLACE may be emulated using
556      *          an UPDATE query, make sure that all fields of the table are
557      *          listed in this function argument array.
558      *
559      *    Default: 0
560      *
561      *    key
562      *          Boolean property that indicates that this field should be
563      *          handled as a primary key or at least as part of the compound
564      *          unique index of the table that will determine the row that will
565      *          updated if it exists or inserted a new row otherwise.
566      *
567      *          This function will fail if no key field is specified or if the
568      *          value of a key field is set to null because fields that are
569      *          part of unique index they may not be null.
570      *
571      *    Default: 0
572      *
573      * @return mixed MDB2_OK on success, a MDB2 error on failure
574      */
575     function replace($table, $fields)
576     {
577         $count = count($fields);
578         $query = $values = '';
579         $keys = $colnum = 0;
580         for (reset($fields); $colnum < $count; next($fields), $colnum++) {
581             $name = key($fields);
582             if ($colnum > 0) {
583                 $query .= ',';
584                 $values.= ',';
585             }
586             $query.= $name;
587             if (isset($fields[$name]['null']) && $fields[$name]['null']) {
588                 $value = 'NULL';
589             } else {
590                 $value = $this->quote($fields[$name]['value'], $fields[$name]['type']);
591             }
592             $values.= $value;
593             if (isset($fields[$name]['key']) && $fields[$name]['key']) {
594                 if ($value === 'NULL') {
595                     return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
596                         'replace: key value '.$name.' may not be NULL');
597                 }
598                 $keys++;
599             }
600         }
601         if ($keys == 0) {
602             return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
603                 'replace: not specified which fields are keys');
604         }
605         $query = "REPLACE INTO $table ($query) VALUES ($values)";
606         $this->last_query = $query;
607         $this->debug($query, 'query');
608         return $this->_doQuery($query, true);
609     }
610
611     // }}}
612     // {{{ nextID()
613
614     /**
615      * returns the next free id of a sequence
616      *
617      * @param string $seq_name name of the sequence
618      * @param boolean $ondemand when true the seqence is
619      *                          automatic created, if it
620      *                          not exists
621      *
622      * @return mixed MDB2 Error Object or id
623      * @access public
624      */
625     function nextID($seq_name, $ondemand = true)
626     {
627         $sequence_name = $this->getSequenceName($seq_name);
628         $query = "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (NULL)";
629         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
630         $result = $this->_doQuery($query, true);
631         $this->popExpect();
632         if (PEAR::isError($result)) {
633             if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
634                 $this->loadModule('Manager');
635                 // Since we are creating the sequence on demand
636                 // we know the first id = 1 so initialize the
637                 // sequence at 2
638                 $result = $this->manager->createSequence($seq_name, 2);
639                 if (PEAR::isError($result)) {
640                     return $this->raiseError(MDB2_ERROR, null, null,
641                         'nextID: on demand sequence '.$seq_name.' could not be created');
642                 } else {
643                     // First ID of a newly created sequence is 1
644                     return 1;
645                 }
646             }
647             return $result;
648         }
649         $value = $this->queryOne('SELECT LAST_INSERT_ID()', 'integer');
650         if (is_numeric($value)) {
651             $query = "DELETE FROM $sequence_name WHERE ".$this->options['seqcol_name']." < $value";
652             $result = $this->_doQuery($query, true);
653             if (PEAR::isError($result)) {
654                 $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
655             }
656         }
657         return $value;
658     }
659
660     // }}}
661     // {{{ lastInsertID()
662
663     /**
664      * returns the autoincrement ID if supported or $id
665      *
666      * @param mixed $id value as returned by getBeforeId()
667      * @param string $table name of the table into which a new row was inserted
668      * @return mixed MDB2 Error Object or id
669      * @access public
670      */
671     function lastInsertID($table = null, $field = null)
672     {
673         return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer');
674     }
675
676     // }}}
677     // {{{ currID()
678
679     /**
680      * returns the current id of a sequence
681      *
682      * @param string $seq_name name of the sequence
683      * @return mixed MDB2 Error Object or id
684      * @access public
685      */
686     function currID($seq_name)
687     {
688         $sequence_name = $this->getSequenceName($seq_name);
689         $query = "SELECT MAX(".$this->options['seqcol_name'].") FROM $sequence_name";
690         return $this->queryOne($query, 'integer');
691     }
692 }
693
694 class MDB2_Result_mysql extends MDB2_Result_Common
695 {
696     // }}}
697     // {{{ fetchRow()
698
699     /**
700      * Fetch a row and insert the data into an existing array.
701      *
702      * @param int       $fetchmode  how the array data should be indexed
703      * @param int    $rownum    number of the row where the data can be found
704      * @return int data array on success, a MDB2 error on failure
705      * @access public
706      */
707     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
708     {
709         if (!is_null($rownum)) {
710             $seek = $this->seek($rownum);
711             if (PEAR::isError($seek)) {
712                 return $seek;
713             }
714         }
715         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
716             $fetchmode = $this->db->fetchmode;
717         }
718         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
719             $row = @mysql_fetch_assoc($this->result);
720             if (is_array($row)
721                 && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
722             ) {
723                 $row = array_change_key_case($row, $this->db->options['field_case']);
724             }
725         } else {
726            $row = @mysql_fetch_row($this->result);
727         }
728
729         if (!$row) {
730             if (is_null($this->result)) {
731                 $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
732                     'fetchRow: resultset has already been freed');
733                 return $err;
734             }
735             $null = null;
736             return $null;
737         }
738         if ($this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) {
739             $this->db->_fixResultArrayValues($row, MDB2_PORTABILITY_EMPTY_TO_NULL);
740         }
741         if (!empty($this->values)) {
742             $this->_assignBindColumns($row);
743         }
744         if (!empty($this->types)) {
745             $row = $this->db->datatype->convertResultRow($this->types, $row);
746         }
747         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
748             $object_class = $this->db->options['fetch_class'];
749             if ($object_class == 'stdClass') {
750                 $row = (object) $row;
751             } else {
752                 $row = &new $object_class($row);
753             }
754         }
755         ++$this->rownum;
756         return $row;
757     }
758
759     // }}}
760     // {{{ _getColumnNames()
761
762     /**
763      * Retrieve the names of columns returned by the DBMS in a query result.
764      *
765      * @return mixed                an associative array variable
766      *                              that will hold the names of columns. The
767      *                              indexes of the array are the column names
768      *                              mapped to lower case and the values are the
769      *                              respective numbers of the columns starting
770      *                              from 0. Some DBMS may not return any
771      *                              columns when the result set does not
772      *                              contain any rows.
773      *
774      *                              a MDB2 error on failure
775      * @access private
776      */
777     function _getColumnNames()
778     {
779         $columns = array();
780         $numcols = $this->numCols();
781         if (PEAR::isError($numcols)) {
782             return $numcols;
783         }
784         for ($column = 0; $column < $numcols; $column++) {
785             $column_name = @mysql_field_name($this->result, $column);
786             $columns[$column_name] = $column;
787         }
788         if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
789             $columns = array_change_key_case($columns, $this->db->options['field_case']);
790         }
791         return $columns;
792     }
793
794     // }}}
795     // {{{ numCols()
796
797     /**
798      * Count the number of columns returned by the DBMS in a query result.
799      *
800      * @return mixed integer value with the number of columns, a MDB2 error
801      *                       on failure
802      * @access public
803      */
804     function numCols()
805     {
806         $cols = @mysql_num_fields($this->result);
807         if (is_null($cols)) {
808             if (is_null($this->result)) {
809                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
810                     'numCols: resultset has already been freed');
811             }
812             return $this->db->raiseError();
813         }
814         return $cols;
815     }
816
817     // }}}
818     // {{{ free()
819
820     /**
821      * Free the internal resources associated with result.
822      *
823      * @return boolean true on success, false if result is invalid
824      * @access public
825      */
826     function free()
827     {
828         $free = @mysql_free_result($this->result);
829         if (!$free) {
830             if (is_null($this->result)) {
831                 return MDB2_OK;
832             }
833             return $this->db->raiseError();
834         }
835         $this->result = null;
836         return MDB2_OK;
837     }
838 }
839
840 class MDB2_BufferedResult_mysql extends MDB2_Result_mysql
841 {
842     // }}}
843     // {{{ seek()
844
845     /**
846     * seek to a specific row in a result set
847     *
848     * @param int    $rownum    number of the row where the data can be found
849     * @return mixed MDB2_OK on success, a MDB2 error on failure
850     * @access public
851     */
852     function seek($rownum = 0)
853     {
854         if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) {
855             if (is_null($this->result)) {
856                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
857                     'seek: resultset has already been freed');
858             }
859             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
860                 'seek: tried to seek to an invalid row number ('.$rownum.')');
861         }
862         $this->rownum = $rownum - 1;
863         return MDB2_OK;
864     }
865
866     // }}}
867     // {{{ valid()
868
869     /**
870     * check if the end of the result set has been reached
871     *
872     * @return mixed true or false on sucess, a MDB2 error on failure
873     * @access public
874     */
875     function valid()
876     {
877         $numrows = $this->numRows();
878         if (PEAR::isError($numrows)) {
879             return $numrows;
880         }
881         return $this->rownum < ($numrows - 1);
882     }
883
884     // }}}
885     // {{{ numRows()
886
887     /**
888     * returns the number of rows in a result object
889     *
890     * @return mixed MDB2 Error Object or the number of rows
891     * @access public
892     */
893     function numRows()
894     {
895         $rows = @mysql_num_rows($this->result);
896         if (is_null($rows)) {
897             if (is_null($this->result)) {
898                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
899                     'numRows: resultset has already been freed');
900             }
901             return $this->raiseError();
902         }
903         return $rows;
904     }
905 }
906
907 class MDB2_Statement_mysql extends MDB2_Statement_Common
908 {
909
910 }
911 ?>