source: github/program/lib/MDB2/Driver/mysqli.php @ d1403fd

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since d1403fd was d1403fd, checked in by alecpl <alec@…>, 5 years ago
  • fixed #1485032 and updated MDB2 package+drivers
  • Property mode set to 100644
File size: 67.0 KB
Line 
1<?php
2// vim: set et ts=4 sw=4 fdm=marker:
3// +----------------------------------------------------------------------+
4// | PHP versions 4 and 5                                                 |
5// +----------------------------------------------------------------------+
6// | Copyright (c) 1998-2006 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: mysqli.php,v 1.188 2008/03/13 03:31:55 afz Exp $
47//
48
49/**
50 * MDB2 MySQLi driver
51 *
52 * @package MDB2
53 * @category Database
54 * @author  Lukas Smith <smith@pooteeweet.org>
55 */
56class MDB2_Driver_mysqli extends MDB2_Driver_Common
57{
58    // {{{ properties
59
60    var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\');
61
62    var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`');
63
64    var $sql_comments = array(
65        array('start' => '-- ', 'end' => "\n", 'escape' => false),
66        array('start' => '#', 'end' => "\n", 'escape' => false),
67        array('start' => '/*', 'end' => '*/', 'escape' => false),
68    );
69
70    var $server_capabilities_checked = false;
71
72    var $start_transaction = false;
73
74    var $varchar_max_length = 255;
75
76    // }}}
77    // {{{ constructor
78
79    /**
80     * Constructor
81     */
82    function __construct()
83    {
84        parent::__construct();
85
86        $this->phptype = 'mysqli';
87        $this->dbsyntax = 'mysql';
88
89        $this->supported['sequences'] = 'emulated';
90        $this->supported['indexes'] = true;
91        $this->supported['affected_rows'] = true;
92        $this->supported['transactions'] = false;
93        $this->supported['savepoints'] = false;
94        $this->supported['summary_functions'] = true;
95        $this->supported['order_by_text'] = true;
96        $this->supported['current_id'] = 'emulated';
97        $this->supported['limit_queries'] = true;
98        $this->supported['LOBs'] = true;
99        $this->supported['replace'] = true;
100        $this->supported['sub_selects'] = 'emulated';
101        $this->supported['triggers'] = false;
102        $this->supported['auto_increment'] = true;
103        $this->supported['primary_key'] = true;
104        $this->supported['result_introspection'] = true;
105        $this->supported['prepared_statements'] = 'emulated';
106        $this->supported['identifier_quoting'] = true;
107        $this->supported['pattern_escaping'] = true;
108        $this->supported['new_link'] = true;
109
110        $this->options['DBA_username'] = false;
111        $this->options['DBA_password'] = false;
112        $this->options['default_table_type'] = '';
113        $this->options['multi_query'] = false;
114        $this->options['max_identifiers_length'] = 64;
115
116        $this->_reCheckSupportedOptions();
117    }
118
119    // }}}
120    // {{{ _reCheckSupportedOptions()
121
122    /**
123     * If the user changes certain options, other capabilities may depend
124     * on the new settings, so we need to check them (again).
125     *
126     * @access private
127     */
128    function _reCheckSupportedOptions()
129    {
130        $this->supported['transactions'] = $this->options['use_transactions'];
131        $this->supported['savepoints']   = $this->options['use_transactions'];
132        if ($this->options['default_table_type']) {
133            switch (strtoupper($this->options['default_table_type'])) {
134            case 'BLACKHOLE':
135            case 'MEMORY':
136            case 'ARCHIVE':
137            case 'CSV':
138            case 'HEAP':
139            case 'ISAM':
140            case 'MERGE':
141            case 'MRG_ISAM':
142            case 'ISAM':
143            case 'MRG_MYISAM':
144            case 'MYISAM':
145                $this->supported['savepoints']   = false;
146                $this->supported['transactions'] = false;
147                $this->warnings[] = $this->options['default_table_type'] .
148                    ' is not a supported default table type';
149                break;
150            }
151        }
152    }
153
154    // }}}
155    // {{{ function setOption($option, $value)
156
157    /**
158     * set the option for the db class
159     *
160     * @param   string  option name
161     * @param   mixed   value for the option
162     *
163     * @return  mixed   MDB2_OK or MDB2 Error Object
164     *
165     * @access  public
166     */
167    function setOption($option, $value)
168    {
169        $res = parent::setOption($option, $value);
170        $this->_reCheckSupportedOptions();
171    }
172
173    // }}}
174    // {{{ errorInfo()
175
176    /**
177     * This method is used to collect information about an error
178     *
179     * @param integer $error
180     * @return array
181     * @access public
182     */
183    function errorInfo($error = null)
184    {
185        if ($this->connection) {
186            $native_code = @mysqli_errno($this->connection);
187            $native_msg  = @mysqli_error($this->connection);
188        } else {
189            $native_code = @mysqli_connect_errno();
190            $native_msg  = @mysqli_connect_error();
191        }
192        if (is_null($error)) {
193            static $ecode_map;
194            if (empty($ecode_map)) {
195                $ecode_map = array(
196                    1000 => MDB2_ERROR_INVALID, //hashchk
197                    1001 => MDB2_ERROR_INVALID, //isamchk
198                    1004 => MDB2_ERROR_CANNOT_CREATE,
199                    1005 => MDB2_ERROR_CANNOT_CREATE,
200                    1006 => MDB2_ERROR_CANNOT_CREATE,
201                    1007 => MDB2_ERROR_ALREADY_EXISTS,
202                    1008 => MDB2_ERROR_CANNOT_DROP,
203                    1009 => MDB2_ERROR_CANNOT_DROP,
204                    1010 => MDB2_ERROR_CANNOT_DROP,
205                    1011 => MDB2_ERROR_CANNOT_DELETE,
206                    1022 => MDB2_ERROR_ALREADY_EXISTS,
207                    1029 => MDB2_ERROR_NOT_FOUND,
208                    1032 => MDB2_ERROR_NOT_FOUND,
209                    1044 => MDB2_ERROR_ACCESS_VIOLATION,
210                    1045 => MDB2_ERROR_ACCESS_VIOLATION,
211                    1046 => MDB2_ERROR_NODBSELECTED,
212                    1048 => MDB2_ERROR_CONSTRAINT,
213                    1049 => MDB2_ERROR_NOSUCHDB,
214                    1050 => MDB2_ERROR_ALREADY_EXISTS,
215                    1051 => MDB2_ERROR_NOSUCHTABLE,
216                    1054 => MDB2_ERROR_NOSUCHFIELD,
217                    1060 => MDB2_ERROR_ALREADY_EXISTS,
218                    1061 => MDB2_ERROR_ALREADY_EXISTS,
219                    1062 => MDB2_ERROR_ALREADY_EXISTS,
220                    1064 => MDB2_ERROR_SYNTAX,
221                    1067 => MDB2_ERROR_INVALID,
222                    1072 => MDB2_ERROR_NOT_FOUND,
223                    1086 => MDB2_ERROR_ALREADY_EXISTS,
224                    1091 => MDB2_ERROR_NOT_FOUND,
225                    1100 => MDB2_ERROR_NOT_LOCKED,
226                    1109 => MDB2_ERROR_NOT_FOUND,
227                    1125 => MDB2_ERROR_ALREADY_EXISTS,
228                    1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
229                    1138 => MDB2_ERROR_INVALID,
230                    1142 => MDB2_ERROR_ACCESS_VIOLATION,
231                    1143 => MDB2_ERROR_ACCESS_VIOLATION,
232                    1146 => MDB2_ERROR_NOSUCHTABLE,
233                    1149 => MDB2_ERROR_SYNTAX,
234                    1169 => MDB2_ERROR_CONSTRAINT,
235                    1176 => MDB2_ERROR_NOT_FOUND,
236                    1177 => MDB2_ERROR_NOSUCHTABLE,
237                    1213 => MDB2_ERROR_DEADLOCK,
238                    1216 => MDB2_ERROR_CONSTRAINT,
239                    1217 => MDB2_ERROR_CONSTRAINT,
240                    1227 => MDB2_ERROR_ACCESS_VIOLATION,
241                    1235 => MDB2_ERROR_CANNOT_CREATE,
242                    1299 => MDB2_ERROR_INVALID_DATE,
243                    1300 => MDB2_ERROR_INVALID,
244                    1304 => MDB2_ERROR_ALREADY_EXISTS,
245                    1305 => MDB2_ERROR_NOT_FOUND,
246                    1306 => MDB2_ERROR_CANNOT_DROP,
247                    1307 => MDB2_ERROR_CANNOT_CREATE,
248                    1334 => MDB2_ERROR_CANNOT_ALTER,
249                    1339 => MDB2_ERROR_NOT_FOUND,
250                    1356 => MDB2_ERROR_INVALID,
251                    1359 => MDB2_ERROR_ALREADY_EXISTS,
252                    1360 => MDB2_ERROR_NOT_FOUND,
253                    1363 => MDB2_ERROR_NOT_FOUND,
254                    1365 => MDB2_ERROR_DIVZERO,
255                    1451 => MDB2_ERROR_CONSTRAINT,
256                    1452 => MDB2_ERROR_CONSTRAINT,
257                    1542 => MDB2_ERROR_CANNOT_DROP,
258                    1546 => MDB2_ERROR_CONSTRAINT,
259                    1582 => MDB2_ERROR_CONSTRAINT,
260                    2003 => MDB2_ERROR_CONNECT_FAILED,
261                    2019 => MDB2_ERROR_INVALID,
262                );
263            }
264            if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) {
265                $ecode_map[1022] = MDB2_ERROR_CONSTRAINT;
266                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL;
267                $ecode_map[1062] = MDB2_ERROR_CONSTRAINT;
268            } else {
269                // Doing this in case mode changes during runtime.
270                $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS;
271                $ecode_map[1048] = MDB2_ERROR_CONSTRAINT;
272                $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS;
273            }
274            if (isset($ecode_map[$native_code])) {
275                $error = $ecode_map[$native_code];
276            }
277        }
278        return array($error, $native_code, $native_msg);
279    }
280
281    // }}}
282    // {{{ escape()
283
284    /**
285     * Quotes a string so it can be safely used in a query. It will quote
286     * the text so it can safely be used within a query.
287     *
288     * @param   string  the input string to quote
289     * @param   bool    escape wildcards
290     *
291     * @return  string  quoted string
292     *
293     * @access  public
294     */
295    function escape($text, $escape_wildcards = false)
296    {
297        if ($escape_wildcards) {
298            $text = $this->escapePattern($text);
299        }
300        $connection = $this->getConnection();
301        if (PEAR::isError($connection)) {
302            return $connection;
303        }
304        $text = @mysqli_real_escape_string($connection, $text);
305        return $text;
306    }
307
308    // }}}
309    // {{{ beginTransaction()
310
311    /**
312     * Start a transaction or set a savepoint.
313     *
314     * @param   string  name of a savepoint to set
315     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
316     *
317     * @access  public
318     */
319    function beginTransaction($savepoint = null)
320    {
321        $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
322        $this->_getServerCapabilities();
323        if (!is_null($savepoint)) {
324            if (!$this->supports('savepoints')) {
325                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
326                    'savepoints are not supported', __FUNCTION__);
327            }
328            if (!$this->in_transaction) {
329                return $this->raiseError(MDB2_ERROR_INVALID, null, null,
330                    'savepoint cannot be released when changes are auto committed', __FUNCTION__);
331            }
332            $query = 'SAVEPOINT '.$savepoint;
333            return $this->_doQuery($query, true);
334        } elseif ($this->in_transaction) {
335            return MDB2_OK;  //nothing to do
336        }
337        $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1';
338        $result =& $this->_doQuery($query, true);
339        if (PEAR::isError($result)) {
340            return $result;
341        }
342        $this->in_transaction = true;
343        return MDB2_OK;
344    }
345
346    // }}}
347    // {{{ commit()
348
349    /**
350     * Commit the database changes done during a transaction that is in
351     * progress or release a savepoint. This function may only be called when
352     * auto-committing is disabled, otherwise it will fail. Therefore, a new
353     * transaction is implicitly started after committing the pending changes.
354     *
355     * @param   string  name of a savepoint to release
356     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
357     *
358     * @access  public
359     */
360    function commit($savepoint = null)
361    {
362        $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
363        if (!$this->in_transaction) {
364            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
365                'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
366        }
367        if (!is_null($savepoint)) {
368            if (!$this->supports('savepoints')) {
369                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
370                    'savepoints are not supported', __FUNCTION__);
371            }
372            $server_info = $this->getServerVersion();
373            if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) {
374                return MDB2_OK;
375            }
376            $query = 'RELEASE SAVEPOINT '.$savepoint;
377            return $this->_doQuery($query, true);
378        }
379
380        if (!$this->supports('transactions')) {
381            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
382                'transactions are not supported', __FUNCTION__);
383        }
384
385        $result =& $this->_doQuery('COMMIT', true);
386        if (PEAR::isError($result)) {
387            return $result;
388        }
389        if (!$this->start_transaction) {
390            $query = 'SET AUTOCOMMIT = 0';
391            $result =& $this->_doQuery($query, true);
392            if (PEAR::isError($result)) {
393                return $result;
394            }
395        }
396        $this->in_transaction = false;
397        return MDB2_OK;
398    }
399
400    // }}}
401    // {{{ rollback()
402
403    /**
404     * Cancel any database changes done during a transaction or since a specific
405     * savepoint that is in progress. This function may only be called when
406     * auto-committing is disabled, otherwise it will fail. Therefore, a new
407     * transaction is implicitly started after canceling the pending changes.
408     *
409     * @param   string  name of a savepoint to rollback to
410     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
411     *
412     * @access  public
413     */
414    function rollback($savepoint = null)
415    {
416        $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
417        if (!$this->in_transaction) {
418            return $this->raiseError(MDB2_ERROR_INVALID, null, null,
419                'rollback cannot be done changes are auto committed', __FUNCTION__);
420        }
421        if (!is_null($savepoint)) {
422            if (!$this->supports('savepoints')) {
423                return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
424                    'savepoints are not supported', __FUNCTION__);
425            }
426            $query = 'ROLLBACK TO SAVEPOINT '.$savepoint;
427            return $this->_doQuery($query, true);
428        }
429
430        $query = 'ROLLBACK';
431        $result =& $this->_doQuery($query, true);
432        if (PEAR::isError($result)) {
433            return $result;
434        }
435        if (!$this->start_transaction) {
436            $query = 'SET AUTOCOMMIT = 0';
437            $result =& $this->_doQuery($query, true);
438            if (PEAR::isError($result)) {
439                return $result;
440            }
441        }
442        $this->in_transaction = false;
443        return MDB2_OK;
444    }
445
446    // }}}
447    // {{{ function setTransactionIsolation()
448
449    /**
450     * Set the transacton isolation level.
451     *
452     * @param   string  standard isolation level
453     *                  READ UNCOMMITTED (allows dirty reads)
454     *                  READ COMMITTED (prevents dirty reads)
455     *                  REPEATABLE READ (prevents nonrepeatable reads)
456     *                  SERIALIZABLE (prevents phantom reads)
457     * @return  mixed   MDB2_OK on success, a MDB2 error on failure
458     *
459     * @access  public
460     * @since   2.1.1
461     */
462    function setTransactionIsolation($isolation)
463    {
464        $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
465        if (!$this->supports('transactions')) {
466            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
467                'transactions are not supported', __FUNCTION__);
468        }
469        switch ($isolation) {
470        case 'READ UNCOMMITTED':
471        case 'READ COMMITTED':
472        case 'REPEATABLE READ':
473        case 'SERIALIZABLE':
474            break;
475        default:
476            return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
477                'isolation level is not supported: '.$isolation, __FUNCTION__);
478        }
479
480        $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation";
481        return $this->_doQuery($query, true);
482    }
483
484    // }}}
485    // {{{ _doConnect()
486
487    /**
488     * do the grunt work of the connect
489     *
490     * @return connection on success or MDB2 Error Object on failure
491     * @access protected
492     */
493    function _doConnect($username, $password, $persistent = false)
494    {
495        if (!PEAR::loadExtension($this->phptype)) {
496            return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
497                'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
498        }
499
500        $connection = @mysqli_init();
501        if (!empty($this->dsn['charset']) && defined('MYSQLI_SET_CHARSET_NAME')) {
502            @mysqli_options($connection, MYSQLI_SET_CHARSET_NAME, $this->dsn['charset']);
503        }
504
505        if ($this->options['ssl']) {
506            @mysqli_ssl_set(
507                $connection,
508                empty($this->dsn['key'])    ? null : $this->dsn['key'],
509                empty($this->dsn['cert'])   ? null : $this->dsn['cert'],
510                empty($this->dsn['ca'])     ? null : $this->dsn['ca'],
511                empty($this->dsn['capath']) ? null : $this->dsn['capath'],
512                empty($this->dsn['cipher']) ? null : $this->dsn['cipher']
513            );
514        }
515
516        if (!@mysqli_real_connect(
517            $connection,
518            $this->dsn['hostspec'],
519            $username,
520            $password,
521            $this->database_name,
522            $this->dsn['port'],
523            $this->dsn['socket']
524        )) {
525            if (($err = @mysqli_connect_error()) != '') {
526                return $this->raiseError(null,
527                    null, null, $err, __FUNCTION__);
528            } else {
529                return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
530                    'unable to establish a connection', __FUNCTION__);
531            }
532        }
533
534        if (!empty($this->dsn['charset']) && !defined('MYSQLI_SET_CHARSET_NAME')) {
535            $result = $this->setCharset($this->dsn['charset'], $connection);
536            if (PEAR::isError($result)) {
537                return $result;
538            }
539        }
540
541        return $connection;
542    }
543
544    // }}}
545    // {{{ connect()
546
547    /**
548     * Connect to the database
549     *
550     * @return true on success, MDB2 Error Object on failure
551     */
552    function connect()
553    {
554        if (is_object($this->connection)) {
555            //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0) {
556            if (MDB2::areEquals($this->connected_dsn, $this->dsn)) {
557                return MDB2_OK;
558            }
559            $this->connection = 0;
560        }
561
562        $connection = $this->_doConnect(
563            $this->dsn['username'],
564            $this->dsn['password']
565        );
566        if (PEAR::isError($connection)) {
567            return $connection;
568        }
569
570        $this->connection = $connection;
571        $this->connected_dsn = $this->dsn;
572        $this->connected_database_name = $this->database_name;
573        $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
574
575        $this->_getServerCapabilities();
576
577        return MDB2_OK;
578    }
579
580    // }}}
581    // {{{ setCharset()
582
583    /**
584     * Set the charset on the current connection
585     *
586     * @param string    charset (or array(charset, collation))
587     * @param resource  connection handle
588     *
589     * @return true on success, MDB2 Error Object on failure
590     */
591    function setCharset($charset, $connection = null)
592    {
593        if (is_null($connection)) {
594            $connection = $this->getConnection();
595            if (PEAR::isError($connection)) {
596                return $connection;
597            }
598        }
599        $collation = null;
600        if (is_array($charset) && 2 == count($charset)) {
601            $collation = array_pop($charset);
602            $charset   = array_pop($charset);
603        }
604        $client_info = mysqli_get_client_version();
605        if (OS_WINDOWS && ((40111 > $client_info) ||
606            ((50000 <= $client_info) && (50006 > $client_info)))
607        ) {
608            $query = "SET NAMES '".mysqli_real_escape_string($connection, $charset)."'";
609            if (!is_null($collation)) {
610                $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'";
611            }
612            return $this->_doQuery($query, true, $connection);
613        }
614        if (!$result = mysqli_set_charset($connection, $charset)) {
615            $err =& $this->raiseError(null, null, null,
616                'Could not set client character set', __FUNCTION__);
617            return $err;
618        }
619        return $result;
620    }
621
622    // }}}
623    // {{{ databaseExists()
624
625    /**
626     * check if given database name is exists?
627     *
628     * @param string $name    name of the database that should be checked
629     *
630     * @return mixed true/false on success, a MDB2 error on failure
631     * @access public
632     */
633    function databaseExists($name)
634    {
635        $connection = $this->_doConnect($this->dsn['username'],
636                                        $this->dsn['password']);
637        if (PEAR::isError($connection)) {
638            return $connection;
639        }
640
641        $result = @mysqli_select_db($connection, $name);
642        @mysqli_close($connection);
643
644        return $result;
645    }
646
647    // }}}
648    // {{{ disconnect()
649
650    /**
651     * Log out and disconnect from the database.
652     *
653     * @param  boolean $force if the disconnect should be forced even if the
654     *                        connection is opened persistently
655     * @return mixed true on success, false if not connected and error
656     *                object on error
657     * @access public
658     */
659    function disconnect($force = true)
660    {
661        if (is_object($this->connection)) {
662            if ($this->in_transaction) {
663                $dsn = $this->dsn;
664                $database_name = $this->database_name;
665                $persistent = $this->options['persistent'];
666                $this->dsn = $this->connected_dsn;
667                $this->database_name = $this->connected_database_name;
668                $this->options['persistent'] = $this->opened_persistent;
669                $this->rollback();
670                $this->dsn = $dsn;
671                $this->database_name = $database_name;
672                $this->options['persistent'] = $persistent;
673            }
674
675            if ($force) {
676                @mysqli_close($this->connection);
677            }
678        }
679        return parent::disconnect($force);
680    }
681
682    // }}}
683    // {{{ standaloneQuery()
684
685   /**
686     * execute a query as DBA
687     *
688     * @param string $query the SQL query
689     * @param mixed   $types  array that contains the types of the columns in
690     *                        the result set
691     * @param boolean $is_manip  if the query is a manipulation query
692     * @return mixed MDB2_OK on success, a MDB2 error on failure
693     * @access public
694     */
695    function &standaloneQuery($query, $types = null, $is_manip = false)
696    {
697        $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
698        $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
699        $connection = $this->_doConnect($user, $pass);
700        if (PEAR::isError($connection)) {
701            return $connection;
702        }
703
704        $offset = $this->offset;
705        $limit = $this->limit;
706        $this->offset = $this->limit = 0;
707        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
708       
709        $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name);
710        if (!PEAR::isError($result)) {
711            $result = $this->_affectedRows($connection, $result);
712        }
713
714        @mysqli_close($connection);
715        return $result;
716    }
717
718    // }}}
719    // {{{ _doQuery()
720
721    /**
722     * Execute a query
723     * @param string $query  query
724     * @param boolean $is_manip  if the query is a manipulation query
725     * @param resource $connection
726     * @param string $database_name
727     * @return result or error object
728     * @access protected
729     */
730    function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
731    {
732        $this->last_query = $query;
733        $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
734        if ($result) {
735            if (PEAR::isError($result)) {
736                return $result;
737            }
738            $query = $result;
739        }
740        if ($this->options['disable_query']) {
741            $result = $is_manip ? 0 : null;
742            return $result;
743        }
744
745        if (is_null($connection)) {
746            $connection = $this->getConnection();
747            if (PEAR::isError($connection)) {
748                return $connection;
749            }
750        }
751        if (is_null($database_name)) {
752            $database_name = $this->database_name;
753        }
754
755        if ($database_name) {
756            if ($database_name != $this->connected_database_name) {
757                if (!@mysqli_select_db($connection, $database_name)) {
758                    $err = $this->raiseError(null, null, null,
759                        'Could not select the database: '.$database_name, __FUNCTION__);
760                    return $err;
761                }
762                $this->connected_database_name = $database_name;
763            }
764        }
765
766        if ($this->options['multi_query']) {
767            $result = mysqli_multi_query($connection, $query);
768        } else {
769            $resultmode = $this->options['result_buffering'] ? MYSQLI_USE_RESULT : MYSQLI_USE_RESULT;
770            $result = mysqli_query($connection, $query);
771        }
772
773        if (!$result) {
774            $err =& $this->raiseError(null, null, null,
775                'Could not execute statement', __FUNCTION__);
776            return $err;
777        }
778
779        if ($this->options['multi_query']) {
780            if ($this->options['result_buffering']) {
781                if (!($result = @mysqli_store_result($connection))) {
782                    $err =& $this->raiseError(null, null, null,
783                        'Could not get the first result from a multi query', __FUNCTION__);
784                    return $err;
785                }
786            } elseif (!($result = @mysqli_use_result($connection))) {
787                $err =& $this->raiseError(null, null, null,
788                        'Could not get the first result from a multi query', __FUNCTION__);
789                return $err;
790            }
791        }
792
793        $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
794        return $result;
795    }
796
797    // }}}
798    // {{{ _affectedRows()
799
800    /**
801     * Returns the number of rows affected
802     *
803     * @param resource $result
804     * @param resource $connection
805     * @return mixed MDB2 Error Object or the number of rows affected
806     * @access private
807     */
808    function _affectedRows($connection, $result = null)
809    {
810        if (is_null($connection)) {
811            $connection = $this->getConnection();
812            if (PEAR::isError($connection)) {
813                return $connection;
814            }
815        }
816        return @mysqli_affected_rows($connection);
817    }
818
819    // }}}
820    // {{{ _modifyQuery()
821
822    /**
823     * Changes a query string for various DBMS specific reasons
824     *
825     * @param string $query  query to modify
826     * @param boolean $is_manip  if it is a DML query
827     * @param integer $limit  limit the number of rows
828     * @param integer $offset  start reading from given offset
829     * @return string modified query
830     * @access protected
831     */
832    function _modifyQuery($query, $is_manip, $limit, $offset)
833    {
834        if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
835            // "DELETE FROM table" gives 0 affected rows in MySQL.
836            // This little hack lets you know how many rows were deleted.
837            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
838                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
839                                      'DELETE FROM \1 WHERE 1=1', $query);
840            }
841        }
842        if ($limit > 0
843            && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
844        ) {
845            $query = rtrim($query);
846            if (substr($query, -1) == ';') {
847                $query = substr($query, 0, -1);
848            }
849
850            // LIMIT doesn't always come last in the query
851            // @see http://dev.mysql.com/doc/refman/5.0/en/select.html
852            $after = '';
853            if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) {
854                $after = $matches[0];
855                $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query);
856            } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) {
857               $after = $matches[0];
858               $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query);
859            } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) {
860               $after = $matches[0];
861               $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query);
862            }
863
864            if ($is_manip) {
865                return $query . " LIMIT $limit" . $after;
866            } else {
867                return $query . " LIMIT $offset, $limit" . $after;
868            }
869        }
870        return $query;
871    }
872
873    // }}}
874    // {{{ getServerVersion()
875
876    /**
877     * return version information about the server
878     *
879     * @param bool   $native  determines if the raw version string should be returned
880     * @return mixed array/string with version information or MDB2 error object
881     * @access public
882     */
883    function getServerVersion($native = false)
884    {
885        $connection = $this->getConnection();
886        if (PEAR::isError($connection)) {
887            return $connection;
888        }
889        if ($this->connected_server_info) {
890            $server_info = $this->connected_server_info;
891        } else {
892            $server_info = @mysqli_get_server_info($connection);
893        }
894        if (!$server_info) {
895            return $this->raiseError(null, null, null,
896                'Could not get server information', __FUNCTION__);
897        }
898        // cache server_info
899        $this->connected_server_info = $server_info;
900        if (!$native) {
901            $tmp = explode('.', $server_info, 3);
902            if (isset($tmp[2]) && strpos($tmp[2], '-')) {
903                $tmp2 = explode('-', @$tmp[2], 2);
904            } else {
905                $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null;
906                $tmp2[1] = null;
907            }
908            $server_info = array(
909                'major' => isset($tmp[0]) ? $tmp[0] : null,
910                'minor' => isset($tmp[1]) ? $tmp[1] : null,
911                'patch' => $tmp2[0],
912                'extra' => $tmp2[1],
913                'native' => $server_info,
914            );
915        }
916        return $server_info;
917    }
918
919    // }}}
920    // {{{ _getServerCapabilities()
921
922    /**
923     * Fetch some information about the server capabilities
924     * (transactions, subselects, prepared statements, etc).
925     *
926     * @access private
927     */
928    function _getServerCapabilities()
929    {
930        if (!$this->server_capabilities_checked) {
931            $this->server_capabilities_checked = true;
932
933            //set defaults
934            $this->supported['sub_selects'] = 'emulated';
935            $this->supported['prepared_statements'] = 'emulated';
936            $this->supported['triggers'] = false;
937            $this->start_transaction = false;
938            $this->varchar_max_length = 255;
939
940            $server_info = $this->getServerVersion();
941            if (is_array($server_info)) {
942                $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'];
943           
944                if (!version_compare($server_version, '4.1.0', '<')) {
945                    $this->supported['sub_selects'] = true;
946                    $this->supported['prepared_statements'] = true;
947                }
948
949                // SAVEPOINTS were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB)
950                if (version_compare($server_version, '4.1.0', '>=')) {
951                    if (version_compare($server_version, '4.1.1', '<')) {
952                        $this->supported['savepoints'] = false;
953                    }
954                } elseif (version_compare($server_version, '4.0.14', '<')) {
955                    $this->supported['savepoints'] = false;
956                }
957
958                if (!version_compare($server_version, '4.0.11', '<')) {
959                    $this->start_transaction = true;
960                }
961
962                if (!version_compare($server_version, '5.0.3', '<')) {
963                    $this->varchar_max_length = 65532;
964                }
965
966                if (!version_compare($server_version, '5.0.2', '<')) {
967                    $this->supported['triggers'] = true;
968                }
969            }
970        }
971    }
972
973    // }}}
974    // {{{ function _skipUserDefinedVariable($query, $position)
975
976    /**
977     * Utility method, used by prepare() to avoid misinterpreting MySQL user
978     * defined variables (SELECT @x:=5) for placeholders.
979     * Check if the placeholder is a false positive, i.e. if it is an user defined
980     * variable instead. If so, skip it and advance the position, otherwise
981     * return the current position, which is valid
982     *
983     * @param string $query
984     * @param integer $position current string cursor position
985     * @return integer $new_position
986     * @access protected
987     */
988    function _skipUserDefinedVariable($query, $position)
989    {
990        $found = strpos(strrev(substr($query, 0, $position)), '@');
991        if ($found === false) {
992            return $position;
993        }
994        $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1;
995        $substring = substr($query, $pos, $position - $pos + 2);
996        if (preg_match('/^@\w+\s*:=$/', $substring)) {
997            return $position + 1; //found an user defined variable: skip it
998        }
999        return $position;
1000    }
1001
1002    // }}}
1003    // {{{ prepare()
1004
1005    /**
1006     * Prepares a query for multiple execution with execute().
1007     * With some database backends, this is emulated.
1008     * prepare() requires a generic query as string like
1009     * 'INSERT INTO numbers VALUES(?,?)' or
1010     * 'INSERT INTO numbers VALUES(:foo,:bar)'.
1011     * The ? and :name and are placeholders which can be set using
1012     * bindParam() and the query can be sent off using the execute() method.
1013     * The allowed format for :name can be set with the 'bindname_format' option.
1014     *
1015     * @param string $query the query to prepare
1016     * @param mixed   $types  array that contains the types of the placeholders
1017     * @param mixed   $result_types  array that contains the types of the columns in
1018     *                        the result set or MDB2_PREPARE_RESULT, if set to
1019     *                        MDB2_PREPARE_MANIP the query is handled as a manipulation query
1020     * @param mixed   $lobs   key (field) value (parameter) pair for all lob placeholders
1021     * @return mixed resource handle for the prepared query on success, a MDB2
1022     *        error on failure
1023     * @access public
1024     * @see bindParam, execute
1025     */
1026    function &prepare($query, $types = null, $result_types = null, $lobs = array())
1027    {
1028        if ($this->options['emulate_prepared']
1029            || $this->supported['prepared_statements'] !== true
1030        ) {
1031            $obj =& parent::prepare($query, $types, $result_types, $lobs);
1032            return $obj;
1033        }
1034        $is_manip = ($result_types === MDB2_PREPARE_MANIP);
1035        $offset = $this->offset;
1036        $limit = $this->limit;
1037        $this->offset = $this->limit = 0;
1038        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
1039        $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
1040        if ($result) {
1041            if (PEAR::isError($result)) {
1042                return $result;
1043            }
1044            $query = $result;
1045        }
1046        $placeholder_type_guess = $placeholder_type = null;
1047        $question = '?';
1048        $colon = ':';
1049        $positions = array();
1050        $position = 0;
1051        while ($position < strlen($query)) {
1052            $q_position = strpos($query, $question, $position);
1053            $c_position = strpos($query, $colon, $position);
1054            if ($q_position && $c_position) {
1055                $p_position = min($q_position, $c_position);
1056            } elseif ($q_position) {
1057                $p_position = $q_position;
1058            } elseif ($c_position) {
1059                $p_position = $c_position;
1060            } else {
1061                break;
1062            }
1063            if (is_null($placeholder_type)) {
1064                $placeholder_type_guess = $query[$p_position];
1065            }
1066           
1067            $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
1068            if (PEAR::isError($new_pos)) {
1069                return $new_pos;
1070            }
1071            if ($new_pos != $position) {
1072                $position = $new_pos;
1073                continue; //evaluate again starting from the new position
1074            }
1075           
1076            //make sure this is not part of an user defined variable
1077            $new_pos = $this->_skipUserDefinedVariable($query, $position);
1078            if ($new_pos != $position) {
1079                $position = $new_pos;
1080                continue; //evaluate again starting from the new position
1081            }
1082
1083            if ($query[$position] == $placeholder_type_guess) {
1084                if (is_null($placeholder_type)) {
1085                    $placeholder_type = $query[$p_position];
1086                    $question = $colon = $placeholder_type;
1087                }
1088                if ($placeholder_type == ':') {
1089                    $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
1090                    $parameter = preg_replace($regexp, '\\1', $query);
1091                    if ($parameter === '') {
1092                        $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
1093                            'named parameter name must match "bindname_format" option', __FUNCTION__);
1094                        return $err;
1095                    }
1096                    $positions[$p_position] = $parameter;
1097                    $query = substr_replace($query, '?', $position, strlen($parameter)+1);
1098                } else {
1099                    $positions[$p_position] = count($positions);
1100                }
1101                $position = $p_position + 1;
1102            } else {
1103                $position = $p_position;
1104            }
1105        }
1106        $connection = $this->getConnection();
1107        if (PEAR::isError($connection)) {
1108            return $connection;
1109        }
1110
1111        if (!$is_manip) {
1112            static $prep_statement_counter = 1;
1113            $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand()));
1114            $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']);
1115            $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text');
1116
1117            $statement =& $this->_doQuery($query, true, $connection);
1118            if (PEAR::isError($statement)) {
1119                return $statement;
1120            }
1121            $statement = $statement_name;
1122        } else {
1123            $statement = @mysqli_prepare($connection, $query);
1124            if (!$statement) {
1125                $err =& $this->raiseError(null, null, null,
1126                    'Unable to create prepared statement handle', __FUNCTION__);
1127                return $err;
1128            }
1129        }
1130
1131        $class_name = 'MDB2_Statement_'.$this->phptype;
1132        $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
1133        $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
1134        return $obj;
1135    }
1136
1137    // }}}
1138    // {{{ replace()
1139
1140    /**
1141     * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
1142     * query, except that if there is already a row in the table with the same
1143     * key field values, the REPLACE query just updates its values instead of
1144     * inserting a new row.
1145     *
1146     * The REPLACE type of query does not make part of the SQL standards. Since
1147     * practically only MySQL implements it natively, this type of query is
1148     * emulated through this method for other DBMS using standard types of
1149     * queries inside a transaction to assure the atomicity of the operation.
1150     *
1151     * @access public
1152     *
1153     * @param string $table name of the table on which the REPLACE query will
1154     *  be executed.
1155     * @param array $fields associative array that describes the fields and the
1156     *  values that will be inserted or updated in the specified table. The
1157     *  indexes of the array are the names of all the fields of the table. The
1158     *  values of the array are also associative arrays that describe the
1159     *  values and other properties of the table fields.
1160     *
1161     *  Here follows a list of field properties that need to be specified:
1162     *
1163     *    value:
1164     *          Value to be assigned to the specified field. This value may be
1165     *          of specified in database independent type format as this
1166     *          function can perform the necessary datatype conversions.
1167     *
1168     *    Default:
1169     *          this property is required unless the Null property
1170     *          is set to 1.
1171     *
1172     *    type
1173     *          Name of the type of the field. Currently, all types Metabase
1174     *          are supported except for clob and blob.
1175     *
1176     *    Default: no type conversion
1177     *
1178     *    null
1179     *          Boolean property that indicates that the value for this field
1180     *          should be set to null.
1181     *
1182     *          The default value for fields missing in INSERT queries may be
1183     *          specified the definition of a table. Often, the default value
1184     *          is already null, but since the REPLACE may be emulated using
1185     *          an UPDATE query, make sure that all fields of the table are
1186     *          listed in this function argument array.
1187     *
1188     *    Default: 0
1189     *
1190     *    key
1191     *          Boolean property that indicates that this field should be
1192     *          handled as a primary key or at least as part of the compound
1193     *          unique index of the table that will determine the row that will
1194     *          updated if it exists or inserted a new row otherwise.
1195     *
1196     *          This function will fail if no key field is specified or if the
1197     *          value of a key field is set to null because fields that are
1198     *          part of unique index they may not be null.
1199     *
1200     *    Default: 0
1201     *
1202     * @return mixed MDB2_OK on success, a MDB2 error on failure
1203     */
1204    function replace($table, $fields)
1205    {
1206        $count = count($fields);
1207        $query = $values = '';
1208        $keys = $colnum = 0;
1209        for (reset($fields); $colnum < $count; next($fields), $colnum++) {
1210            $name = key($fields);
1211            if ($colnum > 0) {
1212                $query .= ',';
1213                $values.= ',';
1214            }
1215            $query.= $this->quoteIdentifier($name, true);
1216            if (isset($fields[$name]['null']) && $fields[$name]['null']) {
1217                $value = 'NULL';
1218            } else {
1219                $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
1220                $value = $this->quote($fields[$name]['value'], $type);
1221                if (PEAR::isError($value)) {
1222                    return $value;
1223                }
1224            }
1225            $values.= $value;
1226            if (isset($fields[$name]['key']) && $fields[$name]['key']) {
1227                if ($value === 'NULL') {
1228                    return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1229                        'key value '.$name.' may not be NULL', __FUNCTION__);
1230                }
1231                $keys++;
1232            }
1233        }
1234        if ($keys == 0) {
1235            return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
1236                'not specified which fields are keys', __FUNCTION__);
1237        }
1238
1239        $connection = $this->getConnection();
1240        if (PEAR::isError($connection)) {
1241            return $connection;
1242        }
1243
1244        $table = $this->quoteIdentifier($table, true);
1245        $query = "REPLACE INTO $table ($query) VALUES ($values)";
1246        $result =& $this->_doQuery($query, true, $connection);
1247        if (PEAR::isError($result)) {
1248            return $result;
1249        }
1250        return $this->_affectedRows($connection, $result);
1251    }
1252
1253    // }}}
1254    // {{{ nextID()
1255
1256    /**
1257     * Returns the next free id of a sequence
1258     *
1259     * @param string $seq_name name of the sequence
1260     * @param boolean $ondemand when true the sequence is
1261     *                          automatic created, if it
1262     *                          not exists
1263     *
1264     * @return mixed MDB2 Error Object or id
1265     * @access public
1266     */
1267    function nextID($seq_name, $ondemand = true)
1268    {
1269        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1270        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1271        $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
1272        $this->pushErrorHandling(PEAR_ERROR_RETURN);
1273        $this->expectError(MDB2_ERROR_NOSUCHTABLE);
1274        $result =& $this->_doQuery($query, true);
1275        $this->popExpect();
1276        $this->popErrorHandling();
1277        if (PEAR::isError($result)) {
1278            if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
1279                $this->loadModule('Manager', null, true);
1280                $result = $this->manager->createSequence($seq_name);
1281                if (PEAR::isError($result)) {
1282                    return $this->raiseError($result, null, null,
1283                        'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
1284                } else {
1285                    return $this->nextID($seq_name, false);
1286                }
1287            }
1288            return $result;
1289        }
1290        $value = $this->lastInsertID();
1291        if (is_numeric($value)) {
1292            $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
1293            $result =& $this->_doQuery($query, true);
1294            if (PEAR::isError($result)) {
1295                $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
1296            }
1297        }
1298        return $value;
1299    }
1300
1301    // }}}
1302    // {{{ lastInsertID()
1303
1304    /**
1305     * Returns the autoincrement ID if supported or $id or fetches the current
1306     * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
1307     *
1308     * @param string $table name of the table into which a new row was inserted
1309     * @param string $field name of the field into which a new row was inserted
1310     * @return mixed MDB2 Error Object or id
1311     * @access public
1312     */
1313    function lastInsertID($table = null, $field = null)
1314    {
1315        // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051
1316        return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer');
1317    }
1318
1319    // }}}
1320    // {{{ currID()
1321
1322    /**
1323     * Returns the current id of a sequence
1324     *
1325     * @param string $seq_name name of the sequence
1326     * @return mixed MDB2 Error Object or id
1327     * @access public
1328     */
1329    function currID($seq_name)
1330    {
1331        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
1332        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
1333        $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
1334        return $this->queryOne($query, 'integer');
1335    }
1336}
1337
1338/**
1339 * MDB2 MySQLi result driver
1340 *
1341 * @package MDB2
1342 * @category Database
1343 * @author  Lukas Smith <smith@pooteeweet.org>
1344 */
1345class MDB2_Result_mysqli extends MDB2_Result_Common
1346{
1347    // }}}
1348    // {{{ fetchRow()
1349
1350    /**
1351     * Fetch a row and insert the data into an existing array.
1352     *
1353     * @param int       $fetchmode  how the array data should be indexed
1354     * @param int    $rownum    number of the row where the data can be found
1355     * @return int data array on success, a MDB2 error on failure
1356     * @access public
1357     */
1358    function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
1359    {
1360        if (!is_null($rownum)) {
1361            $seek = $this->seek($rownum);
1362            if (PEAR::isError($seek)) {
1363                return $seek;
1364            }
1365        }
1366        if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
1367            $fetchmode = $this->db->fetchmode;
1368        }
1369        if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
1370            $row = @mysqli_fetch_assoc($this->result);
1371            if (is_array($row)
1372                && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
1373            ) {
1374                $row = array_change_key_case($row, $this->db->options['field_case']);
1375            }
1376        } else {
1377           $row = @mysqli_fetch_row($this->result);
1378        }
1379
1380        if (!$row) {
1381            if ($this->result === false) {
1382                $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1383                    'resultset has already been freed', __FUNCTION__);
1384                return $err;
1385            }
1386            $null = null;
1387            return $null;
1388        }
1389        $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
1390        $rtrim = false;
1391        if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
1392            if (empty($this->types)) {
1393                $mode += MDB2_PORTABILITY_RTRIM;
1394            } else {
1395                $rtrim = true;
1396            }
1397        }
1398        if ($mode) {
1399            $this->db->_fixResultArrayValues($row, $mode);
1400        }
1401        if (!empty($this->types)) {
1402            $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
1403        }
1404        if (!empty($this->values)) {
1405            $this->_assignBindColumns($row);
1406        }
1407        if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
1408            $object_class = $this->db->options['fetch_class'];
1409            if ($object_class == 'stdClass') {
1410                $row = (object) $row;
1411            } else {
1412                $row = &new $object_class($row);
1413            }
1414        }
1415        ++$this->rownum;
1416        return $row;
1417    }
1418
1419    // }}}
1420    // {{{ _getColumnNames()
1421
1422    /**
1423     * Retrieve the names of columns returned by the DBMS in a query result.
1424     *
1425     * @return  mixed   Array variable that holds the names of columns as keys
1426     *                  or an MDB2 error on failure.
1427     *                  Some DBMS may not return any columns when the result set
1428     *                  does not contain any rows.
1429     * @access private
1430     */
1431    function _getColumnNames()
1432    {
1433        $columns = array();
1434        $numcols = $this->numCols();
1435        if (PEAR::isError($numcols)) {
1436            return $numcols;
1437        }
1438        for ($column = 0; $column < $numcols; $column++) {
1439            $column_info = @mysqli_fetch_field_direct($this->result, $column);
1440            $columns[$column_info->name] = $column;
1441        }
1442        if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1443            $columns = array_change_key_case($columns, $this->db->options['field_case']);
1444        }
1445        return $columns;
1446    }
1447
1448    // }}}
1449    // {{{ numCols()
1450
1451    /**
1452     * Count the number of columns returned by the DBMS in a query result.
1453     *
1454     * @return mixed integer value with the number of columns, a MDB2 error
1455     *                       on failure
1456     * @access public
1457     */
1458    function numCols()
1459    {
1460        $cols = @mysqli_num_fields($this->result);
1461        if (is_null($cols)) {
1462            if ($this->result === false) {
1463                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1464                    'resultset has already been freed', __FUNCTION__);
1465            } elseif (is_null($this->result)) {
1466                return count($this->types);
1467            }
1468            return $this->db->raiseError(null, null, null,
1469                'Could not get column count', __FUNCTION__);
1470        }
1471        return $cols;
1472    }
1473
1474    // }}}
1475    // {{{ nextResult()
1476
1477    /**
1478     * Move the internal result pointer to the next available result
1479     *
1480     * @return true on success, false if there is no more result set or an error object on failure
1481     * @access public
1482     */
1483    function nextResult()
1484    {
1485        $connection = $this->db->getConnection();
1486        if (PEAR::isError($connection)) {
1487            return $connection;
1488        }
1489
1490        if (!@mysqli_more_results($connection)) {
1491            return false;
1492        }
1493        if (!@mysqli_next_result($connection)) {
1494            return false;
1495        }
1496        if (!($this->result = @mysqli_use_result($connection))) {
1497            return false;
1498        }
1499        return MDB2_OK;
1500    }
1501
1502    // }}}
1503    // {{{ free()
1504
1505    /**
1506     * Free the internal resources associated with result.
1507     *
1508     * @return boolean true on success, false if result is invalid
1509     * @access public
1510     */
1511    function free()
1512    {
1513        if (is_object($this->result) && $this->db->connection) {
1514            $free = @mysqli_free_result($this->result);
1515            if ($free === false) {
1516                return $this->db->raiseError(null, null, null,
1517                    'Could not free result', __FUNCTION__);
1518            }
1519        }
1520        $this->result = false;
1521        return MDB2_OK;
1522    }
1523}
1524
1525/**
1526 * MDB2 MySQLi buffered result driver
1527 *
1528 * @package MDB2
1529 * @category Database
1530 * @author  Lukas Smith <smith@pooteeweet.org>
1531 */
1532class MDB2_BufferedResult_mysqli extends MDB2_Result_mysqli
1533{
1534    // }}}
1535    // {{{ seek()
1536
1537    /**
1538     * Seek to a specific row in a result set
1539     *
1540     * @param int    $rownum    number of the row where the data can be found
1541     * @return mixed MDB2_OK on success, a MDB2 error on failure
1542     * @access public
1543     */
1544    function seek($rownum = 0)
1545    {
1546        if ($this->rownum != ($rownum - 1) && !@mysqli_data_seek($this->result, $rownum)) {
1547            if ($this->result === false) {
1548                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1549                    'resultset has already been freed', __FUNCTION__);
1550            } elseif (is_null($this->result)) {
1551                return MDB2_OK;
1552            }
1553            return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1554                'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1555        }
1556        $this->rownum = $rownum - 1;
1557        return MDB2_OK;
1558    }
1559
1560    // }}}
1561    // {{{ valid()
1562
1563    /**
1564     * Check if the end of the result set has been reached
1565     *
1566     * @return mixed true or false on sucess, a MDB2 error on failure
1567     * @access public
1568     */
1569    function valid()
1570    {
1571        $numrows = $this->numRows();
1572        if (PEAR::isError($numrows)) {
1573            return $numrows;
1574        }
1575        return $this->rownum < ($numrows - 1);
1576    }
1577
1578    // }}}
1579    // {{{ numRows()
1580
1581    /**
1582     * Returns the number of rows in a result object
1583     *
1584     * @return mixed MDB2 Error Object or the number of rows
1585     * @access public
1586     */
1587    function numRows()
1588    {
1589        $rows = @mysqli_num_rows($this->result);
1590        if (is_null($rows)) {
1591            if ($this->result === false) {
1592                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1593                    'resultset has already been freed', __FUNCTION__);
1594            } elseif (is_null($this->result)) {
1595                return 0;
1596            }
1597            return $this->db->raiseError(null, null, null,
1598                'Could not get row count', __FUNCTION__);
1599        }
1600        return $rows;
1601    }
1602
1603    // }}}
1604    // {{{ nextResult()
1605
1606    /**
1607     * Move the internal result pointer to the next available result
1608     *
1609     * @param a valid result resource
1610     * @return true on success, false if there is no more result set or an error object on failure
1611     * @access public
1612     */
1613    function nextResult()
1614    {
1615        $connection = $this->db->getConnection();
1616        if (PEAR::isError($connection)) {
1617            return $connection;
1618        }
1619
1620        if (!@mysqli_more_results($connection)) {
1621            return false;
1622        }
1623        if (!@mysqli_next_result($connection)) {
1624            return false;
1625        }
1626        if (!($this->result = @mysqli_store_result($connection))) {
1627            return false;
1628        }
1629        return MDB2_OK;
1630    }
1631}
1632
1633/**
1634 * MDB2 MySQLi statement driver
1635 *
1636 * @package MDB2
1637 * @category Database
1638 * @author  Lukas Smith <smith@pooteeweet.org>
1639 */
1640class MDB2_Statement_mysqli extends MDB2_Statement_Common
1641{
1642    // {{{ _execute()
1643
1644    /**
1645     * Execute a prepared query statement helper method.
1646     *
1647     * @param mixed $result_class string which specifies which result class to use
1648     * @param mixed $result_wrap_class string which specifies which class to wrap results in
1649     * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
1650     * @access private
1651     */
1652    function &_execute($result_class = true, $result_wrap_class = false)
1653    {
1654        if (is_null($this->statement)) {
1655            $result =& parent::_execute($result_class, $result_wrap_class);
1656            return $result;
1657        }
1658        $this->db->last_query = $this->query;
1659        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
1660        if ($this->db->getOption('disable_query')) {
1661            $result = $this->is_manip ? 0 : null;
1662            return $result;
1663        }
1664
1665        $connection = $this->db->getConnection();
1666        if (PEAR::isError($connection)) {
1667            return $connection;
1668        }
1669
1670        if (!is_object($this->statement)) {
1671            $query = 'EXECUTE '.$this->statement;
1672        }
1673        if (!empty($this->positions)) {
1674            $parameters = array(0 => $this->statement, 1 => '');
1675            $lobs = array();
1676            $i = 0;
1677            foreach ($this->positions as $parameter) {
1678                if (!array_key_exists($parameter, $this->values)) {
1679                    return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
1680                        'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__);
1681                }
1682                $value = $this->values[$parameter];
1683                $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null;
1684                if (!is_object($this->statement)) {
1685                    if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) {
1686                        if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1687                            if ($match[1] == 'file://') {
1688                                $value = $match[2];
1689                            }
1690                            $value = @fopen($value, 'r');
1691                            $close = true;
1692                        }
1693                        if (is_resource($value)) {
1694                            $data = '';
1695                            while (!@feof($value)) {
1696                                $data.= @fread($value, $this->db->options['lob_buffer_length']);
1697                            }
1698                            if ($close) {
1699                                @fclose($value);
1700                            }
1701                            $value = $data;
1702                        }
1703                    }
1704                    $quoted = $this->db->quote($value, $type);
1705                    if (PEAR::isError($quoted)) {
1706                        return $quoted;
1707                    }
1708                    $param_query = 'SET @'.$parameter.' = '.$quoted;
1709                    $result = $this->db->_doQuery($param_query, true, $connection);
1710                    if (PEAR::isError($result)) {
1711                        return $result;
1712                    }
1713                } else {
1714                    if (is_resource($value) || $type == 'clob' || $type == 'blob') {
1715                        $parameters[] = null;
1716                        $parameters[1].= 'b';
1717                        $lobs[$i] = $parameter;
1718                    } else {
1719                        $quoted = $this->db->quote($value, $type, false);
1720                        if (PEAR::isError($quoted)) {
1721                            return $quoted;
1722                        }
1723                        $parameters[] = $quoted;
1724                        $parameters[1].= $this->db->datatype->mapPrepareDatatype($type);
1725                    }
1726                    ++$i;
1727                }
1728            }
1729
1730            if (!is_object($this->statement)) {
1731                $query.= ' USING @'.implode(', @', array_values($this->positions));
1732            } else {
1733                $result = @call_user_func_array('mysqli_stmt_bind_param', $parameters);
1734                if ($result === false) {
1735                    $err =& $this->db->raiseError(null, null, null,
1736                        'Unable to bind parameters', __FUNCTION__);
1737                    return $err;
1738                }
1739
1740                foreach ($lobs as $i => $parameter) {
1741                    $value = $this->values[$parameter];
1742                    $close = false;
1743                    if (!is_resource($value)) {
1744                        $close = true;
1745                        if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) {
1746                            if ($match[1] == 'file://') {
1747                                $value = $match[2];
1748                            }
1749                            $value = @fopen($value, 'r');
1750                        } else {
1751                            $fp = @tmpfile();
1752                            @fwrite($fp, $value);
1753                            @rewind($fp);
1754                            $value = $fp;
1755                        }
1756                    }
1757                    while (!@feof($value)) {
1758                        $data = @fread($value, $this->db->options['lob_buffer_length']);
1759                        @mysqli_stmt_send_long_data($this->statement, $i, $data);
1760                    }
1761                    if ($close) {
1762                        @fclose($value);
1763                    }
1764                }
1765            }
1766        }
1767
1768        if (!is_object($this->statement)) {
1769            $result = $this->db->_doQuery($query, $this->is_manip, $connection);
1770            if (PEAR::isError($result)) {
1771                return $result;
1772            }
1773
1774            if ($this->is_manip) {
1775                $affected_rows = $this->db->_affectedRows($connection, $result);
1776                return $affected_rows;
1777            }
1778
1779            $result =& $this->db->_wrapResult($result, $this->result_types,
1780                $result_class, $result_wrap_class, $this->limit, $this->offset);
1781        } else {
1782            if (!@mysqli_stmt_execute($this->statement)) {
1783                $err =& $this->db->raiseError(null, null, null,
1784                    'Unable to execute statement', __FUNCTION__);
1785                return $err;
1786            }
1787
1788            if ($this->is_manip) {
1789                $affected_rows = @mysqli_stmt_affected_rows($this->statement);
1790                return $affected_rows;
1791            }
1792
1793            if ($this->db->options['result_buffering']) {
1794                @mysqli_stmt_store_result($this->statement);
1795            }
1796
1797            $result =& $this->db->_wrapResult($this->statement, $this->result_types,
1798                $result_class, $result_wrap_class, $this->limit, $this->offset);
1799        }
1800
1801        $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
1802        return $result;
1803    }
1804
1805    // }}}
1806    // {{{ free()
1807
1808    /**
1809     * Release resources allocated for the specified prepared query.
1810     *
1811     * @return mixed MDB2_OK on success, a MDB2 error on failure
1812     * @access public
1813     */
1814    function free()
1815    {
1816        if (is_null($this->positions)) {
1817            return $this->db->raiseError(MDB2_ERROR, null, null,
1818                'Prepared statement has already been freed', __FUNCTION__);
1819        }
1820        $result = MDB2_OK;
1821
1822        if (is_object($this->statement)) {
1823            if (!@mysqli_stmt_close($this->statement)) {
1824                $result = $this->db->raiseError(null, null, null,
1825                    'Could not free statement', __FUNCTION__);
1826            }
1827        } elseif (!is_null($this->statement)) {
1828            $connection = $this->db->getConnection();
1829            if (PEAR::isError($connection)) {
1830                return $connection;
1831            }
1832
1833            $query = 'DEALLOCATE PREPARE '.$this->statement;
1834            $result = $this->db->_doQuery($query, true, $connection);
1835        }
1836
1837        parent::free();
1838        return $result;
1839   }
1840}
1841?>
Note: See TracBrowser for help on using the repository browser.