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

Revision 20764, 62.2 KB checked in by nanasess, 13 years ago (diff)

#601 (コピーライトの更新)

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