3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * The PEAR DB driver for PHP's msql extension
7 * for interacting with Mini SQL databases
9 * PHP's mSQL extension did weird things with NULL values prior to PHP
10 * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
15 * LICENSE: This source file is subject to version 3.0 of the PHP license
16 * that is available through the world-wide-web at the following URI:
17 * http://www.php.net/license/3_0.txt. If you did not receive a copy of
18 * the PHP License and are unable to obtain it through the web, please
19 * send a note to license@php.net so we can mail you a copy immediately.
23 * @author Daniel Convissor <danielc@php.net>
24 * @copyright 1997-2007 The PHP Group
25 * @license http://www.php.net/license/3_0.txt PHP License 3.0
27 * @link http://pear.php.net/package/DB
31 * Obtain the DB_common class so it can be extended from
33 //require_once 'DB/common.php';
34 require_once 'common.php';
37 * The methods PEAR DB uses to interact with PHP's msql extension
38 * for interacting with Mini SQL databases
40 * These methods overload the ones declared in DB_common.
42 * PHP's mSQL extension did weird things with NULL values prior to PHP
43 * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
48 * @author Daniel Convissor <danielc@php.net>
49 * @copyright 1997-2007 The PHP Group
50 * @license http://www.php.net/license/3_0.txt PHP License 3.0
51 * @version Release: 1.9.2
52 * @link http://pear.php.net/package/DB
53 * @since Class not functional until Release 1.7.0
55 class DB_msql extends DB_common
60 * The DB driver type (mysql, oci8, odbc, etc.)
63 public $phptype = 'msql';
66 * The database syntax variant to be used (db2, access, etc.), if any
69 public $dbsyntax = 'msql';
72 * The capabilities of this DB implementation
74 * The 'new_link' element contains the PHP version that first provided
75 * new_link support for this DBMS. Contains false if it's unsupported.
77 * Meaning of the 'limit' element:
78 * + 'emulate' = emulate with fetch row by number
79 * + 'alter' = alter the query
84 public $features = array(
91 'transactions' => false,
95 * A mapping of native error codes to DB error codes
98 public $errorcode_map = array();
101 * The raw database connection created by PHP
107 * The DSN information for connecting to a database
110 public $dsn = array();
114 * The query result resource created by PHP
116 * Used to make affectedRows() work. Only contains the result for
117 * data manipulation queries. Contains false for other queries.
129 * This constructor calls <kbd>parent::__construct()</kbd>
133 public function __construct()
135 parent::__construct();
142 * Connect to the database server, log in and open the database
144 * Don't call this method directly. Use DB::connect() instead.
146 * Example of how to connect:
148 * require_once 'DB.php';
150 * // $dsn = 'msql://hostname/dbname'; // use a TCP connection
151 * $dsn = 'msql:///dbname'; // use a socket
153 * 'portability' => DB_PORTABILITY_ALL,
156 * $db = DB::connect($dsn, $options);
157 * if ((new PEAR)->isError($db)) {
158 * die($db->getMessage());
162 * @param array $dsn the data source name
163 * @param bool $persistent should the connection be persistent?
167 public function connect($dsn, $persistent = false)
169 if (!PEAR::loadExtension('msql')) {
170 return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
174 if ($dsn['dbsyntax']) {
175 $this->dbsyntax = $dsn['dbsyntax'];
179 if ($dsn['hostspec']) {
180 $params[] = $dsn['port']
181 ? $dsn['hostspec'] . ',' . $dsn['port']
185 $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
187 $ini = ini_get('track_errors');
190 $this->connection = @call_user_func_array(
195 @ini_set('track_errors', 1);
196 $this->connection = @call_user_func_array(
200 @ini_set('track_errors', $ini);
203 if (!$this->connection) {
204 if (($err = @msql_error()) != '') {
205 return $this->raiseError(
206 DB_ERROR_CONNECT_FAILED,
213 return $this->raiseError(
214 DB_ERROR_CONNECT_FAILED,
223 if (!@msql_select_db($dsn['database'], $this->connection)) {
224 return $this->msqlRaiseError();
233 * Produces a DB_Error object regarding the current problem
235 * @param int $errno if the error is being manually raised pass a
236 * DB_ERROR* constant here. If this isn't passed
237 * the error information gathered from the DBMS.
239 * @return object the DB_Error object
241 * @see DB_common::raiseError(),
242 * DB_msql::errorNative(), DB_msql::errorCode()
244 public function msqlRaiseError($errno = null)
246 $native = $this->errorNative();
247 if ($errno === null) {
248 $errno = $this->errorCode($native);
250 return $this->raiseError($errno, null, null, null, $native);
257 * Gets the DBMS' native error message produced by the last query
259 * @return string the DBMS' error message
261 public function errorNative()
263 return @msql_error();
271 * Determines PEAR::DB error code from the database's text error message
273 * @param string $errormsg the error message returned from the database
275 * @return integer the error number from a DB_ERROR* constant
277 public function errorCode($errormsg)
279 static $error_regexps;
281 // PHP 5.2+ prepends the function name to $php_errormsg, so we need
282 // this hack to work around it, per bug #9599.
283 $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg);
285 if (!isset($error_regexps)) {
286 $error_regexps = array(
287 '/^Access to database denied/i'
288 => DB_ERROR_ACCESS_VIOLATION,
290 => DB_ERROR_ALREADY_EXISTS,
291 '/^Bad order field/i'
293 '/^Bad type for comparison/i'
295 '/^Can\'t perform LIKE on/i'
297 '/^Can\'t use TEXT fields in LIKE comparison/i'
299 '/^Couldn\'t create temporary table/i'
300 => DB_ERROR_CANNOT_CREATE,
301 '/^Error creating table file/i'
302 => DB_ERROR_CANNOT_CREATE,
303 '/^Field .* cannot be null$/i'
304 => DB_ERROR_CONSTRAINT_NOT_NULL,
305 '/^Index (field|condition) .* cannot be null$/i'
307 '/^Invalid date format/i'
308 => DB_ERROR_INVALID_DATE,
309 '/^Invalid time format/i'
311 '/^Literal value for .* is wrong type$/i'
312 => DB_ERROR_INVALID_NUMBER,
313 '/^No Database Selected/i'
314 => DB_ERROR_NODBSELECTED,
315 '/^No value specified for field/i'
316 => DB_ERROR_VALUE_COUNT_ON_ROW,
317 '/^Non unique value for unique index/i'
318 => DB_ERROR_CONSTRAINT,
319 '/^Out of memory for temporary table/i'
320 => DB_ERROR_CANNOT_CREATE,
321 '/^Permission denied/i'
322 => DB_ERROR_ACCESS_VIOLATION,
323 '/^Reference to un-selected table/i'
327 '/^Table .* exists$/i'
328 => DB_ERROR_ALREADY_EXISTS,
329 '/^Unknown database/i'
330 => DB_ERROR_NOSUCHDB,
332 => DB_ERROR_NOSUCHFIELD,
333 '/^Unknown (index|system variable)/i'
334 => DB_ERROR_NOT_FOUND,
336 => DB_ERROR_NOSUCHTABLE,
337 '/^Unqualified field/i'
342 foreach ($error_regexps as $regexp => $code) {
343 if (preg_match($regexp, $errormsg)) {
354 * Disconnects from the database server
356 * @return bool TRUE on success, FALSE on failure
358 public function disconnect()
360 $ret = @msql_close($this->connection);
361 $this->connection = null;
369 * Sends a query to the database server
371 * @param string the SQL query string
373 * @return mixed + a PHP result resrouce for successful SELECT queries
374 * + the DB_OK constant for other successful queries
375 * + a DB_Error object on failure
377 public function simpleQuery($query)
379 $this->last_query = $query;
380 $query = $this->modifyQuery($query);
381 $result = @msql_query($query, $this->connection);
383 return $this->msqlRaiseError();
385 // Determine which queries that should return data, and which
386 // should return an error code only.
387 if ($this->_checkManip($query)) {
388 $this->_result = $result;
391 $this->_result = false;
400 * Move the internal msql result pointer to the next available result
402 * @param a valid fbsql result resource
406 * @return true if a result is available otherwise return false
408 public function nextResult($result)
417 * Places a row from the result set into the given array
419 * Formating of the array and the data therein are configurable.
420 * See DB_result::fetchInto() for more information.
422 * This method is not meant to be called directly. Use
423 * DB_result::fetchInto() instead. It can't be declared "protected"
424 * because DB_result is a separate object.
426 * PHP's mSQL extension did weird things with NULL values prior to PHP
427 * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
430 * @param resource $result the query result resource
431 * @param array $arr the referenced array to put the data in
432 * @param int $fetchmode how the resulting array should be indexed
433 * @param int $rownum the row number to fetch (0 = first row)
435 * @return mixed DB_OK on success, NULL when the end of a result set is
436 * reached or on failure
438 * @see DB_result::fetchInto()
440 public function fetchInto($result, &$arr, $fetchmode, $rownum = null)
442 if ($rownum !== null) {
443 if (!@msql_data_seek($result, $rownum)) {
447 if ($fetchmode & DB_FETCHMODE_ASSOC) {
448 $arr = @msql_fetch_array($result, MSQL_ASSOC);
449 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
450 $arr = array_change_key_case($arr, CASE_LOWER);
453 $arr = @msql_fetch_row($result);
458 if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
459 $this->_rtrimArrayValues($arr);
461 if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
462 $this->_convertNullArrayValuesToEmpty($arr);
471 * Deletes the result set and frees the memory occupied by the result set
473 * This method is not meant to be called directly. Use
474 * DB_result::free() instead. It can't be declared "protected"
475 * because DB_result is a separate object.
477 * @param resource $result PHP's query result resource
479 * @return bool TRUE on success, FALSE if $result is invalid
481 * @see DB_result::free()
483 public function freeResult($result)
485 return is_resource($result) ? msql_free_result($result) : false;
492 * Gets the number of columns in a result set
494 * This method is not meant to be called directly. Use
495 * DB_result::numCols() instead. It can't be declared "protected"
496 * because DB_result is a separate object.
498 * @param resource $result PHP's query result resource
502 * @see DB_result::numCols()
504 public function numCols($result)
506 $cols = @msql_num_fields($result);
508 return $this->msqlRaiseError();
514 // {{{ createSequence()
517 * Gets the number of rows in a result set
519 * This method is not meant to be called directly. Use
520 * DB_result::numRows() instead. It can't be declared "protected"
521 * because DB_result is a separate object.
523 * @param resource $result PHP's query result resource
527 * @see DB_result::numRows()
529 public function numRows($result)
531 $rows = @msql_num_rows($result);
532 if ($rows === false) {
533 return $this->msqlRaiseError();
539 // {{{ dropSequence()
542 * Determines the number of rows affected by a data maniuplation query
544 * 0 is returned for queries that don't manipulate data.
546 * @return int the number of rows. A DB_Error object on failure.
548 public function affectedRows()
550 if (!$this->_result) {
553 return msql_affected_rows($this->_result);
557 // {{{ quoteIdentifier()
560 * Returns the next free id in a sequence
562 * @param string $seq_name name of the sequence
563 * @param boolean $ondemand when true, the seqence is automatically
564 * created if it does not exist
567 * A DB_Error object on failure.
569 * @see DB_common::nextID(), DB_common::getSequenceName(),
570 * DB_msql::createSequence(), DB_msql::dropSequence()
572 public function nextId($seq_name, $ondemand = true)
574 $seqname = $this->getSequenceName($seq_name);
577 $this->pushErrorHandling(PEAR_ERROR_RETURN);
578 $result = $this->query("SELECT _seq FROM ${seqname}");
579 $this->popErrorHandling();
580 if ($ondemand && DB::isError($result) &&
581 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
583 $this->pushErrorHandling(PEAR_ERROR_RETURN);
584 $result = $this->createSequence($seq_name);
585 $this->popErrorHandling();
586 if (DB::isError($result)) {
587 return $this->raiseError($result);
593 if (DB::isError($result)) {
594 return $this->raiseError($result);
596 $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
605 * Creates a new sequence
607 * Also creates a new table to associate the sequence with. Uses
608 * a separate table to ensure portability with other drivers.
610 * @param string $seq_name name of the new sequence
612 * @return int DB_OK on success. A DB_Error object on failure.
614 * @see DB_common::createSequence(), DB_common::getSequenceName(),
615 * DB_msql::nextID(), DB_msql::dropSequence()
617 public function createSequence($seq_name)
619 $seqname = $this->getSequenceName($seq_name);
620 $res = $this->query('CREATE TABLE ' . $seqname
621 . ' (id INTEGER NOT NULL)');
622 if (DB::isError($res)) {
625 $res = $this->query("CREATE SEQUENCE ON ${seqname}");
630 // {{{ escapeSimple()
635 * @param string $seq_name name of the sequence to be deleted
637 * @return int DB_OK on success. A DB_Error object on failure.
639 * @see DB_common::dropSequence(), DB_common::getSequenceName(),
640 * DB_msql::nextID(), DB_msql::createSequence()
642 public function dropSequence($seq_name)
644 return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
648 // {{{ msqlRaiseError()
651 * mSQL does not support delimited identifiers
653 * @param string $str the identifier name to be quoted
655 * @return object a DB_Error object
657 * @see DB_common::quoteIdentifier()
658 * @since Method available since Release 1.7.0
660 public function quoteIdentifier($str)
662 return $this->raiseError(DB_ERROR_UNSUPPORTED);
669 * Formats a float value for use within a query in a locale-independent
672 * @param float the float value to be quoted.
673 * @return string the quoted string.
674 * @see DB_common::quoteSmart()
675 * @since Method available since release 1.7.8.
677 public function quoteFloat($float)
679 return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
686 * Escapes a string according to the current DBMS's standards
688 * @param string $str the string to be escaped
690 * @return string the escaped string
692 * @see DB_common::quoteSmart()
693 * @since Method available since Release 1.7.0
695 public function escapeSimple($str)
697 return addslashes($str);
704 * Returns information about a table or a result set
706 * @param object|string $result DB_result object from a query or a
707 * string containing the name of a table.
708 * While this also accepts a query result
709 * resource identifier, this behavior is
711 * @param int $mode a valid tableInfo mode
713 * @return array|object
714 * A DB_Error object on failure.
716 * @see DB_common::setOption()
718 public function tableInfo($result, $mode = null)
720 if (is_string($result)) {
722 * Probably received a table name.
723 * Create a result resource identifier.
726 "SELECT * FROM $result",
730 } elseif (isset($result->result)) {
732 * Probably received a result object.
733 * Extract the result resource identifier.
735 $id = $result->result;
739 * Probably received a result resource identifier.
741 * Deprecated. Here for compatibility only.
747 if (!is_resource($id)) {
748 return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
751 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
752 $case_func = 'strtolower';
754 $case_func = 'strval';
757 $count = @msql_num_fields($id);
761 $res['num_fields'] = $count;
764 for ($i = 0; $i < $count; $i++) {
765 $tmp = @msql_fetch_field($id);
768 if ($tmp->not_null) {
769 $flags .= 'not_null ';
772 $flags .= 'unique_key ';
774 $flags = trim($flags);
777 'table' => $case_func($tmp->table),
778 'name' => $case_func($tmp->name),
779 'type' => $tmp->type,
780 'len' => msql_field_len($id, $i),
784 if ($mode & DB_TABLEINFO_ORDER) {
785 $res['order'][$res[$i]['name']] = $i;
787 if ($mode & DB_TABLEINFO_ORDERTABLE) {
788 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
792 // free the result only if we were called on a table
794 @msql_free_result($id);
800 // {{{ getSpecialQuery()
803 * Obtain a list of a given type of objects
805 * @param string $type the kind of objects you want to retrieve
807 * @return array|object
810 * @see DB_common::getListOf()
812 public function getSpecialQuery($type)
816 $id = @msql_list_dbs($this->connection);
819 $id = @msql_list_tables(
820 $this->dsn['database'],
828 return $this->msqlRaiseError();
831 while ($row = @msql_fetch_row($id)) {