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