]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/mysql.php
Remove svn:keywords
[SourceForge/phpwiki.git] / lib / pear / DB / mysql.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2004 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 // | Author: Stig Bakken <ssb@php.net>                                    |
17 // | Maintainer: Daniel Convissor <danielc@php.net>                       |
18 // +----------------------------------------------------------------------+
19
20 // XXX legend:
21 //
22 // XXX ERRORMSG: The error message from the mysql function should
23 //               be registered here.
24 //
25 // TODO/wishlist:
26 // longReadlen
27 // binmode
28
29 require_once 'DB/common.php';
30
31 /**
32  * Database independent query interface definition for PHP's MySQL
33  * extension.
34  *
35  * This is for MySQL versions 4.0 and below.
36  *
37  * @package  DB
38  * @version 
39  * @category Database
40  * @author   Stig Bakken <ssb@php.net>
41  */
42 class DB_mysql extends DB_common
43 {
44     // {{{ properties
45
46     var $connection;
47     var $phptype, $dbsyntax;
48     var $prepare_tokens = array();
49     var $prepare_types = array();
50     var $num_rows = array();
51     var $transaction_opcount = 0;
52     var $autocommit = true;
53     var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */
54     var $_db = false;
55
56     // }}}
57     // {{{ constructor
58
59     /**
60      * DB_mysql constructor.
61      *
62      * @access public
63      */
64     function DB_mysql()
65     {
66         $this->DB_common();
67         $this->phptype = 'mysql';
68         $this->dbsyntax = 'mysql';
69         $this->features = array(
70             'prepare' => false,
71             'pconnect' => true,
72             'transactions' => true,
73             'limit' => 'alter'
74         );
75         $this->errorcode_map = array(
76             1004 => DB_ERROR_CANNOT_CREATE,
77             1005 => DB_ERROR_CANNOT_CREATE,
78             1006 => DB_ERROR_CANNOT_CREATE,
79             1007 => DB_ERROR_ALREADY_EXISTS,
80             1008 => DB_ERROR_CANNOT_DROP,
81             1022 => DB_ERROR_ALREADY_EXISTS,
82             1046 => DB_ERROR_NODBSELECTED,
83             1050 => DB_ERROR_ALREADY_EXISTS,
84             1051 => DB_ERROR_NOSUCHTABLE,
85             1054 => DB_ERROR_NOSUCHFIELD,
86             1062 => DB_ERROR_ALREADY_EXISTS,
87             1064 => DB_ERROR_SYNTAX,
88             1100 => DB_ERROR_NOT_LOCKED,
89             1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
90             1146 => DB_ERROR_NOSUCHTABLE,
91             1048 => DB_ERROR_CONSTRAINT,
92             1216 => DB_ERROR_CONSTRAINT
93         );
94     }
95
96     // }}}
97     // {{{ connect()
98
99     /**
100      * Connect to a database and log in as the specified user.
101      *
102      * @param $dsn the data source name (see DB::parseDSN for syntax)
103      * @param $persistent (optional) whether the connection should
104      *        be persistent
105      * @access public
106      * @return int DB_OK on success, a DB error on failure
107      */
108     function connect($dsninfo, $persistent = false)
109     {
110         if (!DB::assertExtension('mysql')) {
111             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
112         }
113         $this->dsn = $dsninfo;
114         if ($dsninfo['protocol'] && $dsninfo['protocol'] == 'unix') {
115             $dbhost = ':' . $dsninfo['socket'];
116         } else {
117             $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
118             if ($dsninfo['port']) {
119                 $dbhost .= ':' . $dsninfo['port'];
120             }
121         }
122
123         $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
124
125         if ($dbhost && $dsninfo['username'] && isset($dsninfo['password'])) {
126             $conn = @$connect_function($dbhost, $dsninfo['username'],
127                                        $dsninfo['password']);
128         } elseif ($dbhost && $dsninfo['username']) {
129             $conn = @$connect_function($dbhost, $dsninfo['username']);
130         } elseif ($dbhost) {
131             $conn = @$connect_function($dbhost);
132         } else {
133             $conn = false;
134         }
135         if (!$conn) {
136             if (($err = @mysql_error()) != '') {
137                 return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
138                                          null, $err);
139             } elseif (empty($php_errormsg)) {
140                 return $this->raiseError(DB_ERROR_CONNECT_FAILED);
141             } else {
142                 return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
143                                          null, $php_errormsg);
144             }
145         }
146
147         if ($dsninfo['database']) {
148             if (!@mysql_select_db($dsninfo['database'], $conn)) {
149                switch(mysql_errno($conn)) {
150                         case 1049:
151                             return $this->raiseError(DB_ERROR_NOSUCHDB, null, null,
152                                                      null, @mysql_error($conn));
153                         case 1044:
154                              return $this->raiseError(DB_ERROR_ACCESS_VIOLATION, null, null,
155                                                       null, @mysql_error($conn));
156                         default:
157                             return $this->raiseError(DB_ERROR, null, null,
158                                                      null, @mysql_error($conn));
159                     }
160             }
161             // fix to allow calls to different databases in the same script
162             $this->_db = $dsninfo['database'];
163         }
164
165         $this->connection = $conn;
166         return DB_OK;
167     }
168
169     // }}}
170     // {{{ disconnect()
171
172     /**
173      * Log out and disconnect from the database.
174      *
175      * @access public
176      *
177      * @return bool true on success, false if not connected.
178      */
179     function disconnect()
180     {
181         $ret = @mysql_close($this->connection);
182         $this->connection = null;
183         return $ret;
184     }
185
186     // }}}
187     // {{{ simpleQuery()
188
189     /**
190      * Send a query to MySQL and return the results as a MySQL resource
191      * identifier.
192      *
193      * @param the SQL query
194      *
195      * @access public
196      *
197      * @return mixed returns a valid MySQL result for successful SELECT
198      * queries, DB_OK for other successful queries.  A DB error is
199      * returned on failure.
200      */
201     function simpleQuery($query)
202     {
203         $ismanip = DB::isManip($query);
204         $this->last_query = $query;
205         $query = $this->modifyQuery($query);
206         if ($this->_db) {
207             if (!@mysql_select_db($this->_db, $this->connection)) {
208                 return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
209             }
210         }
211         if (!$this->autocommit && $ismanip) {
212             if ($this->transaction_opcount == 0) {
213                 $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
214                 $result = @mysql_query('BEGIN', $this->connection);
215                 if (!$result) {
216                     return $this->mysqlRaiseError();
217                 }
218             }
219             $this->transaction_opcount++;
220         }
221         $result = @mysql_query($query, $this->connection);
222         if (!$result) {
223             return $this->mysqlRaiseError();
224         }
225         if (is_resource($result)) {
226             $numrows = $this->numrows($result);
227             if (is_object($numrows)) {
228                 return $numrows;
229             }
230             $this->num_rows[(int)$result] = $numrows;
231             return $result;
232         }
233         return DB_OK;
234     }
235
236     // }}}
237     // {{{ nextResult()
238
239     /**
240      * Move the internal mysql result pointer to the next available result
241      *
242      * This method has not been implemented yet.
243      *
244      * @param a valid sql result resource
245      *
246      * @access public
247      *
248      * @return false
249      */
250     function nextResult($result)
251     {
252         return false;
253     }
254
255     // }}}
256     // {{{ fetchInto()
257
258     /**
259      * Fetch a row and insert the data into an existing array.
260      *
261      * Formating of the array and the data therein are configurable.
262      * See DB_result::fetchInto() for more information.
263      *
264      * @param resource $result query result identifier
265      * @param array    $arr    (reference) array where data from the row
266      *                            should be placed
267      * @param int $fetchmode how the resulting array should be indexed
268      * @param int $rownum    the row number to fetch
269      *
270      * @return mixed DB_OK on success, null when end of result set is
271      *               reached or on failure
272      *
273      * @see DB_result::fetchInto()
274      * @access private
275      */
276     function fetchInto($result, &$arr, $fetchmode, $rownum=null)
277     {
278         if ($rownum !== null) {
279             if (!@mysql_data_seek($result, $rownum)) {
280                 return null;
281             }
282         }
283         if ($fetchmode & DB_FETCHMODE_ASSOC) {
284             $arr = @mysql_fetch_array($result, MYSQL_ASSOC);
285             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
286                 $arr = array_change_key_case($arr, CASE_LOWER);
287             }
288         } else {
289             $arr = @mysql_fetch_row($result);
290         }
291         if (!$arr) {
292             // See: http://bugs.php.net/bug.php?id=22328
293             // for why we can't check errors on fetching
294             return null;
295             /*
296             $errno = @mysql_errno($this->connection);
297             if (!$errno) {
298                 return null;
299             }
300             return $this->mysqlRaiseError($errno);
301             */
302         }
303         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
304             /*
305              * Even though this DBMS already trims output, we do this because
306              * a field might have intentional whitespace at the end that
307              * gets removed by DB_PORTABILITY_RTRIM under another driver.
308              */
309             $this->_rtrimArrayValues($arr);
310         }
311         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
312             $this->_convertNullArrayValuesToEmpty($arr);
313         }
314         return DB_OK;
315     }
316
317     // }}}
318     // {{{ freeResult()
319
320     /**
321      * Free the internal resources associated with $result.
322      *
323      * @param $result MySQL result identifier
324      *
325      * @access public
326      *
327      * @return bool true on success, false if $result is invalid
328      */
329     function freeResult($result)
330     {
331         unset($this->num_rows[(int)$result]);
332         return @mysql_free_result($result);
333     }
334
335     // }}}
336     // {{{ numCols()
337
338     /**
339      * Get the number of columns in a result set.
340      *
341      * @param $result MySQL result identifier
342      *
343      * @access public
344      *
345      * @return int the number of columns per row in $result
346      */
347     function numCols($result)
348     {
349         $cols = @mysql_num_fields($result);
350
351         if (!$cols) {
352             return $this->mysqlRaiseError();
353         }
354
355         return $cols;
356     }
357
358     // }}}
359     // {{{ numRows()
360
361     /**
362      * Get the number of rows in a result set.
363      *
364      * @param $result MySQL result identifier
365      *
366      * @access public
367      *
368      * @return int the number of rows in $result
369      */
370     function numRows($result)
371     {
372         $rows = @mysql_num_rows($result);
373         if ($rows === null) {
374             return $this->mysqlRaiseError();
375         }
376         return $rows;
377     }
378
379     // }}}
380     // {{{ autoCommit()
381
382     /**
383      * Enable/disable automatic commits
384      */
385     function autoCommit($onoff = false)
386     {
387         // XXX if $this->transaction_opcount > 0, we should probably
388         // issue a warning here.
389         $this->autocommit = $onoff ? true : false;
390         return DB_OK;
391     }
392
393     // }}}
394     // {{{ commit()
395
396     /**
397      * Commit the current transaction.
398      */
399     function commit()
400     {
401         if ($this->transaction_opcount > 0) {
402             if ($this->_db) {
403                 if (!@mysql_select_db($this->_db, $this->connection)) {
404                     return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
405                 }
406             }
407             $result = @mysql_query('COMMIT', $this->connection);
408             $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
409             $this->transaction_opcount = 0;
410             if (!$result) {
411                 return $this->mysqlRaiseError();
412             }
413         }
414         return DB_OK;
415     }
416
417     // }}}
418     // {{{ rollback()
419
420     /**
421      * Roll back (undo) the current transaction.
422      */
423     function rollback()
424     {
425         if ($this->transaction_opcount > 0) {
426             if ($this->_db) {
427                 if (!@mysql_select_db($this->_db, $this->connection)) {
428                     return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
429                 }
430             }
431             $result = @mysql_query('ROLLBACK', $this->connection);
432             $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
433             $this->transaction_opcount = 0;
434             if (!$result) {
435                 return $this->mysqlRaiseError();
436             }
437         }
438         return DB_OK;
439     }
440
441     // }}}
442     // {{{ affectedRows()
443
444     /**
445      * Gets the number of rows affected by the data manipulation
446      * query.  For other queries, this function returns 0.
447      *
448      * @return number of rows affected by the last query
449      */
450     function affectedRows()
451     {
452         if (DB::isManip($this->last_query)) {
453             return @mysql_affected_rows($this->connection);
454         } else {
455             return 0;
456         }
457      }
458
459     // }}}
460     // {{{ errorNative()
461
462     /**
463      * Get the native error code of the last error (if any) that
464      * occured on the current connection.
465      *
466      * @access public
467      *
468      * @return int native MySQL error code
469      */
470     function errorNative()
471     {
472         return @mysql_errno($this->connection);
473     }
474
475     // }}}
476     // {{{ nextId()
477
478     /**
479      * Returns the next free id in a sequence
480      *
481      * @param string  $seq_name name of the sequence
482      * @param boolean $ondemand when true, the seqence is automatically
483      *                           created if it does not exist
484      *
485      * @return int the next id number in the sequence.  DB_Error if problem.
486      *
487      * @internal
488      * @see DB_common::nextID()
489      * @access public
490      */
491     function nextId($seq_name, $ondemand = true)
492     {
493         $seqname = $this->getSequenceName($seq_name);
494         do {
495             $repeat = 0;
496             $this->pushErrorHandling(PEAR_ERROR_RETURN);
497             $result = $this->query("UPDATE ${seqname} ".
498                                    'SET id=LAST_INSERT_ID(id+1)');
499             $this->popErrorHandling();
500             if ($result == DB_OK) {
501                 /** COMMON CASE **/
502                 $id = @mysql_insert_id($this->connection);
503                 if ($id != 0) {
504                     return $id;
505                 }
506                 /** EMPTY SEQ TABLE **/
507                 // Sequence table must be empty for some reason, so fill it and return 1
508                 // Obtain a user-level lock
509                 $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
510                 if (DB::isError($result)) {
511                     return $this->raiseError($result);
512                 }
513                 if ($result == 0) {
514                     // Failed to get the lock, bail with a DB_ERROR_NOT_LOCKED error
515                     return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
516                 }
517
518                 // add the default value
519                 $result = $this->query("REPLACE INTO ${seqname} VALUES (0)");
520                 if (DB::isError($result)) {
521                     return $this->raiseError($result);
522                 }
523
524                 // Release the lock
525                 $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
526                 if (DB::isError($result)) {
527                     return $this->raiseError($result);
528                 }
529                 // We know what the result will be, so no need to try again
530                 return 1;
531
532             /** ONDEMAND TABLE CREATION **/
533             } elseif ($ondemand && DB::isError($result) &&
534                 $result->getCode() == DB_ERROR_NOSUCHTABLE)
535             {
536                 $result = $this->createSequence($seq_name);
537                 if (DB::isError($result)) {
538                     return $this->raiseError($result);
539                 } else {
540                     $repeat = 1;
541                 }
542
543             /** BACKWARDS COMPAT **/
544             } elseif (DB::isError($result) &&
545                       $result->getCode() == DB_ERROR_ALREADY_EXISTS)
546             {
547                 // see _BCsequence() comment
548                 $result = $this->_BCsequence($seqname);
549                 if (DB::isError($result)) {
550                     return $this->raiseError($result);
551                 }
552                 $repeat = 1;
553             }
554         } while ($repeat);
555
556         return $this->raiseError($result);
557     }
558
559     // }}}
560     // {{{ createSequence()
561
562     /**
563      * Creates a new sequence
564      *
565      * @param string $seq_name name of the new sequence
566      *
567      * @return int DB_OK on success.  A DB_Error object is returned if
568      *              problems arise.
569      *
570      * @internal
571      * @see DB_common::createSequence()
572      * @access public
573      */
574     function createSequence($seq_name)
575     {
576         $seqname = $this->getSequenceName($seq_name);
577         $res = $this->query("CREATE TABLE ${seqname} ".
578                             '(id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'.
579                             ' PRIMARY KEY(id))');
580         if (DB::isError($res)) {
581             return $res;
582         }
583         // insert yields value 1, nextId call will generate ID 2
584         $res = $this->query("INSERT INTO ${seqname} VALUES(0)");
585         if (DB::isError($res)) {
586             return $res;
587         }
588         // so reset to zero
589         return $this->query("UPDATE ${seqname} SET id = 0;");
590     }
591
592     // }}}
593     // {{{ dropSequence()
594
595     /**
596      * Deletes a sequence
597      *
598      * @param string $seq_name name of the sequence to be deleted
599      *
600      * @return int DB_OK on success.  DB_Error if problems.
601      *
602      * @internal
603      * @see DB_common::dropSequence()
604      * @access public
605      */
606     function dropSequence($seq_name)
607     {
608         return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
609     }
610
611     // }}}
612     // {{{ _BCsequence()
613
614     /**
615      * Backwards compatibility with old sequence emulation implementation
616      * (clean up the dupes)
617      *
618      * @param  string $seqname The sequence name to clean up
619      * @return mixed  DB_Error or true
620      */
621     function _BCsequence($seqname)
622     {
623         // Obtain a user-level lock... this will release any previous
624         // application locks, but unlike LOCK TABLES, it does not abort
625         // the current transaction and is much less frequently used.
626         $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
627         if (DB::isError($result)) {
628             return $result;
629         }
630         if ($result == 0) {
631             // Failed to get the lock, can't do the conversion, bail
632             // with a DB_ERROR_NOT_LOCKED error
633             return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
634         }
635
636         $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
637         if (DB::isError($highest_id)) {
638             return $highest_id;
639         }
640         // This should kill all rows except the highest
641         // We should probably do something if $highest_id isn't
642         // numeric, but I'm at a loss as how to handle that...
643         $result = $this->query("DELETE FROM ${seqname} WHERE id <> $highest_id");
644         if (DB::isError($result)) {
645             return $result;
646         }
647
648         // If another thread has been waiting for this lock,
649         // it will go thru the above procedure, but will have no
650         // real effect
651         $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
652         if (DB::isError($result)) {
653             return $result;
654         }
655         return true;
656     }
657
658     // }}}
659     // {{{ quoteIdentifier()
660
661     /**
662      * Quote a string so it can be safely used as a table or column name
663      *
664      * Quoting style depends on which database driver is being used.
665      *
666      * MySQL can't handle the backtick character (<kbd>`</kbd>) in
667      * table or column names.
668      *
669      * @param string $str identifier name to be quoted
670      *
671      * @return string quoted identifier string
672      *
673      * @since 1.6.0
674      * @access public
675      * @internal
676      */
677     function quoteIdentifier($str)
678     {
679         return '`' . $str . '`';
680     }
681
682     // }}}
683     // {{{ quote()
684
685     /**
686      * @deprecated  Deprecated in release 1.6.0
687      * @internal
688      */
689     function quote($str) {
690         return $this->quoteSmart($str);
691     }
692
693     // }}}
694     // {{{ escapeSimple()
695
696     /**
697      * Escape a string according to the current DBMS's standards
698      *
699      * @param string $str the string to be escaped
700      *
701      * @return string the escaped string
702      *
703      * @internal
704      */
705     function escapeSimple($str) {
706         if (function_exists('mysql_real_escape_string')) {
707             return @mysql_real_escape_string($str, $this->connection);
708         } else {
709             return @mysql_escape_string($str);
710         }
711     }
712
713     // }}}
714     // {{{ modifyQuery()
715
716     function modifyQuery($query)
717     {
718         if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
719             // "DELETE FROM table" gives 0 affected rows in MySQL.
720             // This little hack lets you know how many rows were deleted.
721             if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
722                 $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
723                                       'DELETE FROM \1 WHERE 1=1', $query);
724             }
725         }
726         return $query;
727     }
728
729     // }}}
730     // {{{ modifyLimitQuery()
731
732     function modifyLimitQuery($query, $from, $count)
733     {
734         if (DB::isManip($query)) {
735             return $query . " LIMIT $count";
736         } else {
737             return $query . " LIMIT $from, $count";
738         }
739     }
740
741     // }}}
742     // {{{ mysqlRaiseError()
743
744     /**
745      * Gather information about an error, then use that info to create a
746      * DB error object and finally return that object.
747      *
748      * @param integer $errno PEAR error number (usually a DB constant) if
749      *                          manually raising an error
750      * @return object DB error object
751      * @see DB_common::errorCode()
752      * @see DB_common::raiseError()
753      */
754     function mysqlRaiseError($errno = null)
755     {
756         if ($errno === null) {
757             if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
758                 $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
759                 $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
760                 $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
761             } else {
762                 // Doing this in case mode changes during runtime.
763                 $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
764                 $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
765                 $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
766             }
767             $errno = $this->errorCode(mysql_errno($this->connection));
768         }
769         return $this->raiseError($errno, null, null, null,
770                                  @mysql_errno($this->connection) . ' ** ' .
771                                  @mysql_error($this->connection));
772     }
773
774     // }}}
775     // {{{ tableInfo()
776
777     /**
778      * Returns information about a table or a result set.
779      *
780      * @param object|string $result DB_result object from a query or a
781      *                                string containing the name of a table
782      * @param  int   $mode a valid tableInfo mode
783      * @return array an associative array with the information requested
784      *                or an error object if something is wrong
785      * @access public
786      * @internal
787      * @see DB_common::tableInfo()
788      */
789     function tableInfo($result, $mode = null) {
790         if (isset($result->result)) {
791             /*
792              * Probably received a result object.
793              * Extract the result resource identifier.
794              */
795             $id = $result->result;
796             $got_string = false;
797         } elseif (is_string($result)) {
798             /*
799              * Probably received a table name.
800              * Create a result resource identifier.
801              */
802             $id = @mysql_list_fields($this->dsn['database'],
803                                      $result, $this->connection);
804             $got_string = true;
805         } else {
806             /*
807              * Probably received a result resource identifier.
808              * Copy it.
809              * Deprecated.  Here for compatibility only.
810              */
811             $id = $result;
812             $got_string = false;
813         }
814
815         if (!is_resource($id)) {
816             return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
817         }
818
819         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
820             $case_func = 'strtolower';
821         } else {
822             $case_func = 'strval';
823         }
824
825         $count = @mysql_num_fields($id);
826
827         // made this IF due to performance (one if is faster than $count if's)
828         if (!$mode) {
829             for ($i=0; $i<$count; $i++) {
830                 $res[$i]['table'] = $case_func(@mysql_field_table($id, $i));
831                 $res[$i]['name']  = $case_func(@mysql_field_name($id, $i));
832                 $res[$i]['type']  = @mysql_field_type($id, $i);
833                 $res[$i]['len']   = @mysql_field_len($id, $i);
834                 $res[$i]['flags'] = @mysql_field_flags($id, $i);
835             }
836         } else { // full
837             $res['num_fields']= $count;
838
839             for ($i=0; $i<$count; $i++) {
840                 $res[$i]['table'] = $case_func(@mysql_field_table($id, $i));
841                 $res[$i]['name']  = $case_func(@mysql_field_name($id, $i));
842                 $res[$i]['type']  = @mysql_field_type($id, $i);
843                 $res[$i]['len']   = @mysql_field_len($id, $i);
844                 $res[$i]['flags'] = @mysql_field_flags($id, $i);
845
846                 if ($mode & DB_TABLEINFO_ORDER) {
847                     $res['order'][$res[$i]['name']] = $i;
848                 }
849                 if ($mode & DB_TABLEINFO_ORDERTABLE) {
850                     $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
851                 }
852             }
853         }
854
855         // free the result only if we were called on a table
856         if ($got_string) {
857             @mysql_free_result($id);
858         }
859         return $res;
860     }
861
862     // }}}
863     // {{{ getSpecialQuery()
864
865     /**
866      * Returns the query needed to get some backend info
867      * @param  string $type What kind of info you want to retrieve
868      * @return string The SQL query string
869      */
870     function getSpecialQuery($type)
871     {
872         switch ($type) {
873             case 'tables':
874                 return 'SHOW TABLES';
875             case 'views':
876                 return DB_ERROR_NOT_CAPABLE;
877             case 'users':
878                 $sql = 'select distinct User from user';
879                 if ($this->dsn['database'] != 'mysql') {
880                     $dsn = $this->dsn;
881                     $dsn['database'] = 'mysql';
882                     if (DB::isError($db = DB::connect($dsn))) {
883                         return $db;
884                     }
885                     $sql = $db->getCol($sql);
886                     $db->disconnect();
887                     // XXX Fixme the mysql driver should take care of this
888                     if (!@mysql_select_db($this->dsn['database'], $this->connection)) {
889                         return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
890                     }
891                 }
892                 return $sql;
893             case 'databases':
894                 return 'SHOW DATABASES';
895             default:
896                 return null;
897         }
898     }
899
900     // }}}
901
902 }
903
904 /*
905  * Local variables:
906  * tab-width: 4
907  * c-basic-offset: 4
908  * End:
909  */
910
911 ?>