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