thomascube
2006-05-01 6204390af16bcf50f82da61a1aefc2ad0c0adf94
commit | author | age
c9462d 1 <?php
S 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
7 // | Stig. S. Bakken, Lukas Smith                                         |
8 // | All rights reserved.                                                 |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
11 // | API as well as database abstraction for PHP applications.            |
12 // | This LICENSE is in the BSD license style.                            |
13 // |                                                                      |
14 // | Redistribution and use in source and binary forms, with or without   |
15 // | modification, are permitted provided that the following conditions   |
16 // | are met:                                                             |
17 // |                                                                      |
18 // | Redistributions of source code must retain the above copyright       |
19 // | notice, this list of conditions and the following disclaimer.        |
20 // |                                                                      |
21 // | Redistributions in binary form must reproduce the above copyright    |
22 // | notice, this list of conditions and the following disclaimer in the  |
23 // | documentation and/or other materials provided with the distribution. |
24 // |                                                                      |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission.                                                  |
29 // |                                                                      |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41 // | POSSIBILITY OF SUCH DAMAGE.                                          |
42 // +----------------------------------------------------------------------+
43 // | Author: Lukas Smith <smith@pooteeweet.org>                           |
44 // +----------------------------------------------------------------------+
45 //
46 // $Id$
47 //
48
49 /**
50  * MDB2 MySQL driver
51  *
52  * @package MDB2
53  * @category Database
54  * @author  Lukas Smith <smith@pooteeweet.org>
55  */
56 class MDB2_Driver_mysqli extends MDB2_Driver_Common
57 {
58     // {{{ properties
59     var $escape_quotes = "\\";
60
61     // }}}
62     // {{{ constructor
63
64     /**
65     * Constructor
66     */
67     function __construct()
68     {
69         parent::__construct();
70
71         $this->phptype = 'mysqli';
72         $this->dbsyntax = 'mysqli';
73
74         $this->supported['sequences'] = 'emulated';
75         $this->supported['indexes'] = true;
76         $this->supported['affected_rows'] = true;
77         $this->supported['transactions'] = false;
78         $this->supported['summary_functions'] = true;
79         $this->supported['order_by_text'] = true;
80         $this->supported['current_id'] = 'emulated';
81         $this->supported['limit_queries'] = true;
82         $this->supported['LOBs'] = true;
83         $this->supported['replace'] = true;
84         $this->supported['sub_selects'] = false;
85         $this->supported['auto_increment'] = true;
86         $this->supported['primary_key'] = true;
87
88         $this->options['default_table_type'] = null;
89     }
90
91     // }}}
92     // {{{ errorInfo()
93
94     /**
95      * This method is used to collect information about an error
96      *
97      * @param integer $error
98      * @return array
99      * @access public
100      */
101     function errorInfo($error = null)
102     {
103         if ($this->connection) {
104             $native_code = @mysqli_errno($this->connection);
105             $native_msg  = @mysqli_error($this->connection);
106         } else {
107             $native_code = @mysqli_errno();
108             $native_msg  = @mysqli_error();
109         }
110         if (is_null($error)) {
111             static $ecode_map;
112             if (empty($ecode_map)) {
113                 $ecode_map = array(
114                     1004 => MDB2_ERROR_CANNOT_CREATE,
115                     1005 => MDB2_ERROR_CANNOT_CREATE,
116                     1006 => MDB2_ERROR_CANNOT_CREATE,
117                     1007 => MDB2_ERROR_ALREADY_EXISTS,
118                     1008 => MDB2_ERROR_CANNOT_DROP,
119                     1022 => MDB2_ERROR_ALREADY_EXISTS,
120                     1044 => MDB2_ERROR_ACCESS_VIOLATION,
121                     1046 => MDB2_ERROR_NODBSELECTED,
122                     1048 => MDB2_ERROR_CONSTRAINT,
123                     1049 => MDB2_ERROR_NOSUCHDB,
124                     1050 => MDB2_ERROR_ALREADY_EXISTS,
125                     1051 => MDB2_ERROR_NOSUCHTABLE,
126                     1054 => MDB2_ERROR_NOSUCHFIELD,
127                     1061 => MDB2_ERROR_ALREADY_EXISTS,
128                     1062 => MDB2_ERROR_ALREADY_EXISTS,
129                     1064 => MDB2_ERROR_SYNTAX,
130                     1091 => MDB2_ERROR_NOT_FOUND,
131                     1100 => MDB2_ERROR_NOT_LOCKED,
132                     1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
133                     1142 => MDB2_ERROR_ACCESS_VIOLATION,
134                     1146 => MDB2_ERROR_NOSUCHTABLE,
135                     1216 => MDB2_ERROR_CONSTRAINT,
136                     1217 => MDB2_ERROR_CONSTRAINT,
137                 );
138             }
139             if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
140                 $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
141                 $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
142                 $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
143             } else {
144                 // Doing this in case mode changes during runtime.
145                 $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
146                 $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
147                 $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
148             }
149             if (isset($ecode_map[$native_code])) {
150                 $error = $ecode_map[$native_code];
151             }
152         }
153         return array($error, $native_code, $native_msg);
154     }
155
156     // }}}
157     // {{{ escape()
158
159     /**
160      * Quotes a string so it can be safely used in a query. It will quote
161      * the text so it can safely be used within a query.
162      *
163      * @param string $text the input string to quote
164      * @return string quoted string
165      * @access public
166      */
167     function escape($text)
168     {
169         return @mysqli_escape_string($this->connection, $text);
170     }
171
172     // }}}
173     // {{{ quoteIdentifier()
174
175     /**
176      * Quote a string so it can be safely used as a table or column name
177      *
178      * Quoting style depends on which database driver is being used.
179      *
180      * MySQL can't handle the backtick character (<kbd>`</kbd>) in
181      * table or column names.
182      *
183      * @param string $str  identifier name to be quoted
184      *
185      * @return string  quoted identifier string
186      *
187      * @access public
188      * @internal
189      */
190     function quoteIdentifier($str)
191     {
192         return '`' . $str . '`';
193     }
194
195     // }}}
196     // {{{ beginTransaction()
197
198     /**
199      * Start a transaction.
200      *
201      * @return mixed MDB2_OK on success, a MDB2 error on failure
202      * @access public
203      */
204     function beginTransaction()
205     {
206         $this->debug('starting transaction', 'beginTransaction');
207         if (!$this->supports('transactions')) {
208             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
209                 'beginTransaction: transactions are not in use');
210         }
211         if ($this->in_transaction) {
212             return MDB2_OK;  //nothing to do
213         }
214         $result = $this->_doQuery('SET AUTOCOMMIT = 0', true);
215         if (PEAR::isError($result)) {
216             return $result;
217         }
218         $this->in_transaction = true;
219         return MDB2_OK;
220     }
221
222     // }}}
223     // {{{ commit()
224
225     /**
226      * Commit the database changes done during a transaction that is in
227      * progress.
228      *
229      * @return mixed MDB2_OK on success, a MDB2 error on failure
230      * @access public
231      */
232     function commit()
233     {
234         $this->debug('commit transaction', 'commit');
235         if (!$this->supports('transactions')) {
236             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
237                 'commit: transactions are not in use');
238         }
239         if (!$this->in_transaction) {
240             return $this->raiseError(MDB2_ERROR, null, null,
241                 'commit: transaction changes are being auto committed');
242         }
243         $result = $this->_doQuery('COMMIT', true);
244         if (PEAR::isError($result)) {
245             return $result;
246         }
247         $result = $this->_doQuery('SET AUTOCOMMIT = 1', true);
248         if (PEAR::isError($result)) {
249             return $result;
250         }
251         $this->in_transaction = false;
252         return MDB2_OK;
253     }
254
255     // }}}
256     // {{{ rollback()
257
258     /**
259      * Cancel any database changes done during a transaction that is in
260      * progress.
261      *
262      * @return mixed MDB2_OK on success, a MDB2 error on failure
263      * @access public
264      */
265     function rollback()
266     {
267         $this->debug('rolling back transaction', 'rollback');
268         if (!$this->supports('transactions')) {
269             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
270                 'rollback: transactions are not in use');
271         }
272         if (!$this->in_transaction) {
273             return $this->raiseError(MDB2_ERROR, null, null,
274                 'rollback: transactions can not be rolled back when changes are auto committed');
275         }
276         $result = $this->_doQuery('ROLLBACK', true);
277         if (PEAR::isError($result)) {
278             return $result;
279         }
280         $result = $this->_doQuery('SET AUTOCOMMIT = 1', true);
281         if (PEAR::isError($result)) {
282             return $result;
283         }
284         $this->in_transaction = false;
285         return MDB2_OK;
286
287     }
288
289     // }}}
290     // {{{ connect()
291
292     /**
293      * Connect to the database
294      *
295      * @return true on success, MDB2 Error Object on failure
296      */
297     function connect()
298     {
299         if (is_object($this->connection)) {
300             if (count(array_diff($this->connected_dsn, $this->dsn)) == 0) {
301                 return MDB2_OK;
302             }
303             $this->connection = 0;
304         }
305
306         if (!PEAR::loadExtension($this->phptype)) {
307             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
308                 'connect: extension '.$this->phptype.' is not compiled into PHP');
309         }
310
311         @ini_set('track_errors', true);
312         $php_errormsg = '';
313
314         if ($this->options['ssl'] === true) {
315             $init = @mysqli_init();
316             @mysqli_ssl_set(
317                 $init,
318                 empty($this->dsn['key'])    ? null : $this->dsn['key'],
319                 empty($this->dsn['cert'])   ? null : $this->dsn['cert'],
320                 empty($this->dsn['ca'])     ? null : $this->dsn['ca'],
321                 empty($this->dsn['capath']) ? null : $this->dsn['capath'],
322                 empty($this->dsn['cipher']) ? null : $this->dsn['cipher']
323             );
324             if ($connection = @mysqli_real_connect(
325                     $init,
326                     $this->dsn['hostspec'],
327                     $this->dsn['username'],
328                     $this->dsn['password'],
329                     $this->database_name,
330                     $this->dsn['port'],
331                     $this->dsn['socket']))
332             {
333                 $connection = $init;
334             }
335         } else {
336             $connection = @mysqli_connect(
337                 $this->dsn['hostspec'],
338                 $this->dsn['username'],
339                 $this->dsn['password'],
340                 $this->database_name,
341                 $this->dsn['port'],
342                 $this->dsn['socket']
343             );
344         }
345
346         @ini_restore('track_errors');
347
348         if (!$connection) {
349             if (($err = @mysqli_connect_error()) != '') {
350                 return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $err);
351             } else {
352                 return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $php_errormsg);
353             }
354         }
355
356         $this->connection = $connection;
357         $this->connected_dsn = $this->dsn;
358         $this->connected_database_name = $this->database_name;
359         $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
360
361         $this->supported['transactions'] = false;
362         if ($this->options['default_table_type']) {
363             switch (strtoupper($this->options['default_table_type'])) {
364             case 'BERKELEYDB':
365                 $this->options['default_table_type'] = 'BDB';
366             case 'BDB':
367             case 'INNODB':
368             case 'GEMINI':
369                 $this->supported['transactions'] = true;
370                 break;
371             case 'HEAP':
372             case 'ISAM':
373             case 'MERGE':
374             case 'MRG_MYISAM':
375             case 'MYISAM':
376                 break;
377             default:
378                 $this->warnings[] = $default_table_type.
379                     ' is not a supported default table type';
380             }
381         }
382
383         if ($this->options['use_transactions'] && !$this->supports('transactions')) {
384             $this->warnings[] = $this->options['default_table_type'].
385                 ' is not a transaction-safe default table type; switched to INNODB';
386             $this->options['default_table_type'] = 'INNODB';
387             $this->supported['transactions'] = true;
388         }
389
390         return MDB2_OK;
391     }
392
393     // }}}
394     // {{{ disconnect()
395
396     /**
397      * Log out and disconnect from the database.
398      *
399      * @return mixed true on success, false if not connected and error
400      *                object on error
401      * @access public
402      */
403     function disconnect($force = true)
404     {
405         if (is_object($this->connection)) {
406             if ($force) {
407                 @mysqli_close($this->connection);
408             }
409             $this->connection = 0;
410         }
411         return MDB2_OK;
412     }
413
414     // }}}
415     // {{{ _doQuery()
416
417     /**
418      * Execute a query
419      * @param string $query  query
420      * @param boolean $isManip  if the query is a manipulation query
421      * @param resource $connection
422      * @param string $database_name
423      * @return result or error object
424      * @access protected
425      */
426     function _doQuery($query, $isManip = false, $connection = null, $database_name = null)
427     {
428         $this->last_query = $query;
429         $this->debug($query, 'query');
430         if ($this->options['disable_query']) {
431             if ($isManip) {
432                 return 0;
433             }
434             return null;
435         }
436
437         if (is_null($connection)) {
438             $err = $this->connect();
439             if (PEAR::isError($err)) {
440                 return $err;
441             }
442             $connection = $this->connection;
443         }
444         if (is_null($database_name)) {
445             $database_name = $this->database_name;
446         }
447
448         if ($database_name) {
449             if ($database_name != $this->connected_database_name) {
450                 if (!@mysqli_select_db($connection, $database_name)) {
451                     return $this->raiseError();
452                 }
453                 $this->connected_database_name = $database_name;
454             }
455         }
456
457         $function = $this->options['result_buffering']
458             ? 'mysqli_query' : 'mysqli_unbuffered_query';
459         $result = @$function($connection, $query);
460         if (!$result) {
461             return $this->raiseError();
462         }
463
464         if ($isManip) {
465             return @mysqli_affected_rows($connection);
466         }
467         return $result;
468     }
469
470     // }}}
471     // {{{ _modifyQuery()
472
473     /**
474      * Changes a query string for various DBMS specific reasons
475      *
476      * @param string $query  query to modify
477      * @return the new (modified) query
478      * @access protected
479      */
480     function _modifyQuery($query, $isManip, $limit, $offset)
481     {
482         if ($limit > 0
483             && !preg_match('/LIMIT\s*\d(\s*(,|OFFSET)\s*\d+)?/i', $query)
484         ) {
485             $query = rtrim($query);
486             if (substr($query, -1) == ';') {
487                 $query = substr($query, 0, -1);
488             }
489             if ($isManip) {
490                 return $query . " LIMIT $limit";
491             } else {
492                 return $query . " LIMIT $offset, $limit";
493             }
494         }
495         return $query;
496     }
497
498     // }}}
499     // {{{ prepare()
500
501     /**
502      * Prepares a query for multiple execution with execute().
503      * With some database backends, this is emulated.
504      * prepare() requires a generic query as string like
505      * 'INSERT INTO numbers VALUES(?,?)' or
506      * 'INSERT INTO numbers VALUES(:foo,:bar)'.
507      * The ? and :[a-zA-Z] and  are placeholders which can be set using
508      * bindParam() and the query can be send off using the execute() method.
509      *
510      * @param string $query the query to prepare
511      * @param mixed   $types  array that contains the types of the placeholders
512      * @param mixed   $result_types  array that contains the types of the columns in
513      *                        the result set
514      * @return mixed resource handle for the prepared query on success, a MDB2
515      *        error on failure
516      * @access public
517      * @see bindParam, execute
518      */
519     function &prepare($query, $types = null, $result_types = null)
520     {
521         $isManip = MDB2::isManip($query);
522         $query = $this->_modifyQuery($query, $isManip, $this->row_limit, $this->row_offset);
523         $this->debug($query, 'prepare');
524         $placeholder_type_guess = $placeholder_type = null;
525         $question = '?';
526         $colon = ':';
527         $position = 0;
528         while ($position < strlen($query)) {
529             $q_position = strpos($query, $question, $position);
530             $c_position = strpos($query, $colon, $position);
531             if ($q_position && $c_position) {
532                 $p_position = min($q_position, $c_position);
533             } elseif ($q_position) {
534                 $p_position = $q_position;
535             } elseif ($c_position) {
536                 $p_position = $c_position;
537             } else {
538                 break;
539             }
540             if (is_null($placeholder_type)) {
541                 $placeholder_type_guess = $query[$p_position];
542             }
543             if (is_int($quote = strpos($query, "'", $position)) && $quote < $p_position) {
544                 if (!is_int($end_quote = strpos($query, "'", $quote + 1))) {
545                     $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
546                         'prepare: query with an unterminated text string specified');
547                     return $err;
548                 }
549                 switch ($this->escape_quotes) {
550                 case '':
551                 case "'":
552                     $position = $end_quote + 1;
553                     break;
554                 default:
555                     if ($end_quote == $quote + 1) {
556                         $position = $end_quote + 1;
557                     } else {
558                         if ($query[$end_quote-1] == $this->escape_quotes) {
559                             $position = $end_quote;
560                         } else {
561                             $position = $end_quote + 1;
562                         }
563                     }
564                     break;
565                 }
566             } elseif ($query[$position] == $placeholder_type_guess) {
567                 if ($placeholder_type_guess == '?') {
568                     break;
569                 }
570                 if (is_null($placeholder_type)) {
571                     $placeholder_type = $query[$p_position];
572                     $question = $colon = $placeholder_type;
573                 }
574                 $name = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query);
575                 if ($name === '') {
576                     $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
577                         'prepare: named parameter with an empty name');
578                     return $err;
579                 }
580                 $query = substr_replace($query, '?', $position, strlen($name)+1);
581                 $position = $p_position + 1;
582             } else {
583                 $position = $p_position;
584             }
585         }
586         $statement = @mysqli_prepare($this->connection, $query);
587         $class_name = 'MDB2_Statement_'.$this->phptype;
588         $obj =& new $class_name($this, $statement, $query, $types, $result_types);
589         return $obj;
590     }
591
592     // }}}
593     // {{{ replace()
594
595     /**
596      * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
597      * query, except that if there is already a row in the table with the same
598      * key field values, the REPLACE query just updates its values instead of
599      * inserting a new row.
600      *
601      * The REPLACE type of query does not make part of the SQL standards. Since
602      * practically only MySQL implements it natively, this type of query is
603      * emulated through this method for other DBMS using standard types of
604      * queries inside a transaction to assure the atomicity of the operation.
605      *
606      * @access public
607      *
608      * @param string $table name of the table on which the REPLACE query will
609      *  be executed.
610      * @param array $fields associative array that describes the fields and the
611      *  values that will be inserted or updated in the specified table. The
612      *  indexes of the array are the names of all the fields of the table. The
613      *  values of the array are also associative arrays that describe the
614      *  values and other properties of the table fields.
615      *
616      *  Here follows a list of field properties that need to be specified:
617      *
618      *    value:
619      *          Value to be assigned to the specified field. This value may be
620      *          of specified in database independent type format as this
621      *          function can perform the necessary datatype conversions.
622      *
623      *    Default:
624      *          this property is required unless the Null property
625      *          is set to 1.
626      *
627      *    type
628      *          Name of the type of the field. Currently, all types Metabase
629      *          are supported except for clob and blob.
630      *
631      *    Default: no type conversion
632      *
633      *    null
634      *          Boolean property that indicates that the value for this field
635      *          should be set to null.
636      *
637      *          The default value for fields missing in INSERT queries may be
638      *          specified the definition of a table. Often, the default value
639      *          is already null, but since the REPLACE may be emulated using
640      *          an UPDATE query, make sure that all fields of the table are
641      *          listed in this function argument array.
642      *
643      *    Default: 0
644      *
645      *    key
646      *          Boolean property that indicates that this field should be
647      *          handled as a primary key or at least as part of the compound
648      *          unique index of the table that will determine the row that will
649      *          updated if it exists or inserted a new row otherwise.
650      *
651      *          This function will fail if no key field is specified or if the
652      *          value of a key field is set to null because fields that are
653      *          part of unique index they may not be null.
654      *
655      *    Default: 0
656      *
657      * @return mixed MDB2_OK on success, a MDB2 error on failure
658      */
659     function replace($table, $fields)
660     {
661         $count = count($fields);
662         $query = $values = '';
663         $keys = $colnum = 0;
664         for (reset($fields); $colnum < $count; next($fields), $colnum++) {
665             $name = key($fields);
666             if ($colnum > 0) {
667                 $query .= ',';
668                 $values.= ',';
669             }
670             $query.= $name;
671             if (isset($fields[$name]['null']) && $fields[$name]['null']) {
672                 $value = 'NULL';
673             } else {
674                 $value = $this->quote($fields[$name]['value'], $fields[$name]['type']);
675             }
676             $values.= $value;
677             if (isset($fields[$name]['key']) && $fields[$name]['key']) {
678                 if ($value === 'NULL') {
679                     return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
680                         'replace: key value '.$name.' may not be NULL');
681                 }
682                 $keys++;
683             }
684         }
685         if ($keys == 0) {
686             return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
687                 'replace: not specified which fields are keys');
688         }
689         $query = "REPLACE INTO $table ($query) VALUES ($values)";
690         $this->last_query = $query;
691         $this->debug($query, 'query');
692         return $this->_doQuery($query, true);
693     }
694
695     // }}}
696     // {{{ nextID()
697
698     /**
699      * returns the next free id of a sequence
700      *
701      * @param string $seq_name name of the sequence
702      * @param boolean $ondemand when true the seqence is
703      *                          automatic created, if it
704      *                          not exists
705      *
706      * @return mixed MDB2 Error Object or id
707      * @access public
708      */
709     function nextID($seq_name, $ondemand = true)
710     {
711         $sequence_name = $this->getSequenceName($seq_name);
712         $query = "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (NULL)";
713         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
714         $result = $this->_doQuery($query, true);
715         $this->popExpect();
716         if (PEAR::isError($result)) {
717             if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
718                 $this->loadModule('Manager');
719                 // Since we are creating the sequence on demand
720                 // we know the first id = 1 so initialize the
721                 // sequence at 2
722                 $result = $this->manager->createSequence($seq_name, 2);
723                 if (PEAR::isError($result)) {
724                     return $this->raiseError(MDB2_ERROR, null, null,
725                         'nextID: on demand sequence '.$seq_name.' could not be created');
726                 } else {
727                     // First ID of a newly created sequence is 1
728                     return 1;
729                 }
730             }
731             return $result;
732         }
733         $value = @mysqli_insert_id($this->connection);
734         if (is_numeric($value)) {
735             $query = "DELETE FROM $sequence_name WHERE ".$this->options['seqcol_name']." < $value";
736             $result = $this->_doQuery($query, true);
737             if (PEAR::isError($result)) {
738                 $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
739             }
740         }
741         return $value;
742     }
743
744     // }}}
745     // {{{ lastInsertID()
746
747     /**
748      * returns the autoincrement ID if supported or $id
749      *
750      * @param mixed $id value as returned by getBeforeId()
751      * @param string $table name of the table into which a new row was inserted
752      * @return mixed MDB2 Error Object or id
753      * @access public
754      */
755     function lastInsertID($table = null, $field = null)
756     {
757         $value = @mysqli_insert_id($this->connection);
758         if (!$value) {
759             return $this->raiseError();
760         }
761         return $value;
762     }
763
764     // }}}
765     // {{{ currID()
766
767     /**
768      * returns the current id of a sequence
769      *
770      * @param string $seq_name name of the sequence
771      * @return mixed MDB2 Error Object or id
772      * @access public
773      */
774     function currID($seq_name)
775     {
776         $sequence_name = $this->getSequenceName($seq_name);
777         $query = "SELECT MAX(".$this->options['seqcol_name'].") FROM $sequence_name";
778         return $this->queryOne($query, 'integer');
779     }
780 }
781
782 class MDB2_Result_mysqli extends MDB2_Result_Common
783 {
784     // }}}
785     // {{{ fetchRow()
786
787     /**
788      * Fetch a row and insert the data into an existing array.
789      *
790      * @param int       $fetchmode  how the array data should be indexed
791      * @param int    $rownum    number of the row where the data can be found
792      * @return int data array on success, a MDB2 error on failure
793      * @access public
794      */
795     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
796     {
797         if (!is_null($rownum)) {
798             $seek = $this->seek($rownum);
799             if (PEAR::isError($seek)) {
800                 return $seek;
801             }
802         }
803         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
804             $fetchmode = $this->db->fetchmode;
805         }
806         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
807             $row = @mysqli_fetch_assoc($this->result);
808             if (is_array($row)
809                 && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
810             ) {
811                 $row = array_change_key_case($row, $this->db->options['field_case']);
812             }
813         } else {
814            $row = @mysqli_fetch_row($this->result);
815         }
816
817         if (!$row) {
818             if (is_null($this->result)) {
819                 $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
820                     'fetchRow: resultset has already been freed');
821                 return $err;
822             }
823             $null = null;
824             return $null;
825         }
826         if ($this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) {
827             $this->db->_fixResultArrayValues($row, MDB2_PORTABILITY_EMPTY_TO_NULL);
828         }
829         if (!empty($this->values)) {
830             $this->_assignBindColumns($row);
831         }
832         if (!empty($this->types)) {
833             $row = $this->db->datatype->convertResultRow($this->types, $row);
834         }
835         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
836             $object_class = $this->db->options['fetch_class'];
837             if ($object_class == 'stdClass') {
838                 $row = (object) $row;
839             } else {
840                 $row = &new $object_class($row);
841             }
842         }
843         ++$this->rownum;
844         return $row;
845     }
846
847     // }}}
848     // {{{ _getColumnNames()
849
850     /**
851      * Retrieve the names of columns returned by the DBMS in a query result.
852      *
853      * @return mixed                an associative array variable
854      *                              that will hold the names of columns. The
855      *                              indexes of the array are the column names
856      *                              mapped to lower case and the values are the
857      *                              respective numbers of the columns starting
858      *                              from 0. Some DBMS may not return any
859      *                              columns when the result set does not
860      *                              contain any rows.
861      *
862      *                              a MDB2 error on failure
863      * @access private
864      */
865     function _getColumnNames()
866     {
867         $columns = array();
868         $numcols = $this->numCols();
869         if (PEAR::isError($numcols)) {
870             return $numcols;
871         }
872         for ($column = 0; $column < $numcols; $column++) {
873             $column_info = @mysqli_fetch_field_direct($this->result, $column);
874             $columns[$column_info->name] = $column;
875         }
876         if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
877             $columns = array_change_key_case($columns, $this->db->options['field_case']);
878         }
879         return $columns;
880     }
881
882     // }}}
883     // {{{ numCols()
884
885     /**
886      * Count the number of columns returned by the DBMS in a query result.
887      *
888      * @return mixed integer value with the number of columns, a MDB2 error
889      *                       on failure
890      * @access public
891      */
892     function numCols()
893     {
894         $cols = @mysqli_num_fields($this->result);
895         if (is_null($cols)) {
896             if (is_null($this->result)) {
897                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
898                     'numCols: resultset has already been freed');
899             }
900             return $this->db->raiseError();
901         }
902         return $cols;
903     }
904
905     // }}}
906     // {{{ free()
907
908     /**
909      * Free the internal resources associated with result.
910      *
911      * @return boolean true on success, false if result is invalid
912      * @access public
913      */
914     function free()
915     {
916         @mysqli_free_result($this->result);
917         $this->result = null;
918         return MDB2_OK;
919     }
920 }
921
922 class MDB2_BufferedResult_mysqli extends MDB2_Result_mysqli
923 {
924     // }}}
925     // {{{ seek()
926
927     /**
928     * seek to a specific row in a result set
929     *
930     * @param int    $rownum    number of the row where the data can be found
931     * @return mixed MDB2_OK on success, a MDB2 error on failure
932     * @access public
933     */
934     function seek($rownum = 0)
935     {
936         if ($this->rownum != ($rownum - 1) && !@mysqli_data_seek($this->result, $rownum)) {
937             if (is_null($this->result)) {
938                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
939                     'seek: resultset has already been freed');
940             }
941             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
942                 'seek: tried to seek to an invalid row number ('.$rownum.')');
943         }
944         $this->rownum = $rownum - 1;
945         return MDB2_OK;
946     }
947
948     // }}}
949     // {{{ valid()
950
951     /**
952     * check if the end of the result set has been reached
953     *
954     * @return mixed true or false on sucess, a MDB2 error on failure
955     * @access public
956     */
957     function valid()
958     {
959         $numrows = $this->numRows();
960         if (PEAR::isError($numrows)) {
961             return $numrows;
962         }
963         return $this->rownum < ($numrows - 1);
964     }
965
966     // }}}
967     // {{{ numRows()
968
969     /**
970     * returns the number of rows in a result object
971     *
972     * @return mixed MDB2 Error Object or the number of rows
973     * @access public
974     */
975     function numRows()
976     {
977         $rows = @mysqli_num_rows($this->result);
978         if (is_null($rows)) {
979             if (is_null($this->result)) {
980                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
981                     'numRows: resultset has already been freed');
982             }
983             return $this->raiseError();
984         }
985         return $rows;
986     }
987 }
988
989 class MDB2_Statement_mysqli extends MDB2_Statement_Common
990 {
991     // {{{ _execute()
992
993     /**
994      * Execute a prepared query statement helper method.
995      *
996      * @param mixed $result_class string which specifies which result class to use
997      * @param mixed $result_wrap_class string which specifies which class to wrap results in
998      * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
999      * @access private
1000      */
1001     function &_execute($result_class = true, $result_wrap_class = false)
1002     {
1003         $isManip = MDB2::isManip($this->query);
1004         $this->db->last_query = $this->query;
1005         $this->db->debug($this->query, 'execute');
1006         if ($this->db->getOption('disable_query')) {
1007             if ($isManip) {
1008                 $return = 0;
1009                 return $return;
1010             }
1011             $null = null;
1012             return $null;
1013         }
1014
1015         $connected = $this->db->connect();
1016         if (PEAR::isError($connected)) {
1017             return $connected;
1018         }
1019
1020         if (!empty($this->values)) {
1021             $parameters = array(0 => $this->statement, 1 => '');
1022             $i = 0;
1023             foreach ($this->values as $parameter => $value) {
1024                 $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1025                 $close = false;
1026                 if ($type == 'clob' || $type == 'blob') {
1027                     if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1028                         $close = true;
1029                         if ($match[1] == 'file://') {
1030                             $value = $match[2];
1031                         }
1032                         $value = @fopen($value, 'r');
1033                     }
1034                 }
1035                 if (is_resource($value)) {
1036                     while (!@feof($value)) {
1037                         $data = @fread($value, $this->db->options['lob_buffer_length']);
1038                         @mysqli_stmt_send_long_data($this->statement, $i, $data);
1039                     }
1040                     if ($close) {
1041                         @fclose($value);
1042                     }
1043                     $parameters[] = null;
1044                     $parameters[1].= 'b';
1045                 } elseif ($type == 'clob' || $type == 'blob') {
1046                     do {
1047                         $data = substr($value, 0, $this->db->options['lob_buffer_length']);
1048                         $value = substr($value, $this->db->options['lob_buffer_length']);
1049                         @mysqli_stmt_send_long_data($this->statement, $i, $data);
1050                     } while ($value);
1051                     $parameters[] = null;
1052                     $parameters[1].= 'b';
1053                 } else {
1054                     $parameters[] = $this->db->quote($value, $type, false);
1055                     $parameters[1].= $this->db->datatype->mapPrepareDatatype($type);
1056                 }
1057                 ++$i;
1058             }
1059             $result = @call_user_func_array('mysqli_stmt_bind_param', $parameters);
1060             if ($result === false) {
1061                 $err =& $this->db->raiseError();
1062                 return $err;
1063             }
1064         }
1065
1066         if (!@mysqli_stmt_execute($this->statement)) {
1067             $err =& $this->db->raiseError();
1068             return $err;
1069         }
1070
1071         if (!mysqli_stmt_result_metadata($this->statement)) {
1072             $result = @mysqli_stmt_affected_rows($this->statement);
1073             return $result;
1074         }
1075
1076         if ($this->db->options['result_buffering']) {
1077             mysqli_stmt_store_result($this->statement);
1078         }
1079
1080         $result &= $this->db->_wrapResult($result, $this->types,
1081             $result_class, $result_wrap_class);
1082         return $result;
1083     }
1084
1085     // }}}
1086
1087     // }}}
1088
1089     // }}}
1090     // {{{ free()
1091
1092     /**
1093      * Release resources allocated for the specified prepared query.
1094      *
1095      * @return mixed MDB2_OK on success, a MDB2 error on failure
1096      * @access public
1097      */
1098     function free()
1099     {
1100         if (!@mysqli_stmt_close($this->statement)) {
1101             return $this->db->raiseError();
1102         }
1103         return MDB2_OK;
1104     }
1105 }
1106 ?>