source: branches/version-2_13-dev/data/module/MDB2/Driver/mysql.php @ 23022

Revision 23022, 63.1 KB checked in by Seasoft, 11 years ago (diff)

#2322 (セッションのGC処理がエラーとなる)

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