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