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