| // | | // +----------------------------------------------------------------------+ // // Based on code from the PHP CVS repository. The only modifications made // have been modification of the include paths. // rcs_id('$Id: common.php,v 1.1 2002-01-28 04:01:57 dairiki Exp $'); rcs_id('From Pear CVS: Id: common.php,v 1.78 2002/01/19 07:46:24 cox Exp'); // // Base class for DB implementations. // /** * DB_common is a base class for DB implementations, and must be * inherited by all such. */ class DB_common extends PEAR { // {{{ properties /** * assoc of capabilities for this DB implementation * $features['limit'] => 'emulate' => emulate with fetch row by number * 'alter' => alter the query * false => skip rows * @var array */ var $features; /** * assoc mapping native error codes to DB ones * @var array */ var $errorcode_map; /** * DB type (mysql, oci8, odbc etc.) * @var string */ var $type; /** * @var string */ var $prepare_tokens; /** * @var string */ var $prepare_types; /** * @var string */ var $prepared_queries; /** * @var integer */ var $prepare_maxstmt = 0; /** * @var string */ var $last_query = ''; /** * @var integer */ var $fetchmode = DB_FETCHMODE_ORDERED; /** * @var string */ var $fetchmode_object_class = 'stdClass'; /** * $options["persistent"] -> boolean persistent connection true|false? * $options["optimize"] -> string 'performance' or 'portability' * $options["debug"] -> integer numeric debug level * @var array */ var $options = array( 'persistent' => false, 'optimize' => 'performance', 'debug' => 0 ); /** * DB handle * @var resource */ var $dbh; // }}} // {{{ toString() /** * String conversation * * @return string * @access private */ function toString() { $info = get_class($this); $info .= ": (phptype=" . $this->phptype . ", dbsyntax=" . $this->dbsyntax . ")"; if ($this->connection) { $info .= " [connected]"; } return $info; } // }}} // {{{ constructor /** * Constructor */ function DB_common() { $this->PEAR('DB_Error'); $this->features = array(); $this->errorcode_map = array(); $this->fetchmode = DB_FETCHMODE_ORDERED; } // }}} // {{{ quoteString() /** * Quotes a string so it can be safely used within string delimiters * in a query (preserved for compatibility issues, quote() is preffered). * * @return string quoted string * @access public * @see quote() */ function quoteString($string) { $string = $this->quote($string); if ($string{0} == "'") { return substr($string, 1, -1); } return $string; } /** * Quotes a string so it can be safely used in a query. It will return * the string with single quotes around. Other backend quote styles * should override this method. * * @param string $string the input string to quote * * @return string The NULL string or the string quotes * in magic_quote_sybase style */ function quote($string) { return ($string === null) ? 'NULL' : "'".str_replace("'", "''", $string)."'"; } // }}} // {{{ provides() /** * Tell whether a DB implementation or its backend extension * supports a given feature. * * @param array $feature name of the feature (see the DB class doc) * @return bool whether this DB implementation supports $feature * @access public */ function provides($feature) { return $this->features[$feature]; } // }}} // {{{ errorCode() /** * Map native error codes to DB's portable ones. Requires that * the DB implementation's constructor fills in the $errorcode_map * property. * * @param mixed $nativecode the native error code, as returned by the backend * database extension (string or integer) * * @return int a portable DB error code, or FALSE if this DB * implementation has no mapping for the given error code. * * @access public */ function errorCode($nativecode) { if (isset($this->errorcode_map[$nativecode])) { return $this->errorcode_map[$nativecode]; } //php_error(E_WARNING, get_class($this)."::errorCode: no mapping for $nativecode"); // Fall back to DB_ERROR if there was no mapping. return DB_ERROR; } // }}} // {{{ errorMessage() /** * Map a DB error code to a textual message. This is actually * just a wrapper for DB::errorMessage(). * * @param integer $dbcode the DB error code * * @return string the corresponding error message, of FALSE * if the error code was unknown * * @access public */ function errorMessage($dbcode) { return DB::errorMessage($this->errorcode_map[$dbcode]); } // }}} // {{{ raiseError() /** * This method is used to communicate an error and invoke error * callbacks etc. Basically a wrapper for PEAR::raiseError * without the message string. * * @param mixed integer error code, or a PEAR error object (all * other parameters are ignored if this parameter is * an object * * @param int error mode, see PEAR_Error docs * * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the * error level (E_USER_NOTICE etc). If error mode is * PEAR_ERROR_CALLBACK, this is the callback function, * either as a function name, or as an array of an * object and method name. For other error modes this * parameter is ignored. * * @param string Extra debug information. Defaults to the last * query and native error code. * * @param mixed Native error code, integer or string depending the * backend. * * @return object a PEAR error object * * @access public * @see PEAR_Error */ function &raiseError($code = DB_ERROR, $mode = null, $options = null, $userinfo = null, $nativecode = null) { // The error is yet a DB error object if (is_object($code)) { return PEAR::raiseError($code, null, null, null, null, null, true); } if ($userinfo === null) { $userinfo = $this->last_query; } if ($nativecode) { $userinfo .= " [nativecode=$nativecode]"; } return PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'DB_Error', true); } // }}} // {{{ setFetchMode() /** * Sets which fetch mode should be used by default on queries * on this connection. * * @param integer $fetchmode DB_FETCHMODE_ORDERED or * DB_FETCHMODE_ASSOC, possibly bit-wise OR'ed with * DB_FETCHMODE_FLIPPED. * * @param string $object_class The class of the object * to be returned by the fetch methods when * the DB_FETCHMODE_OBJECT mode is selected. * If no class is specified by default a cast * to object from the assoc array row will be done. * There is also the posibility to use and extend the * 'DB_Row' class. * * @see DB_FETCHMODE_ORDERED * @see DB_FETCHMODE_ASSOC * @see DB_FETCHMODE_FLIPPED * @see DB_FETCHMODE_OBJECT * @see DB_Row::DB_Row() * @access public */ function setFetchMode($fetchmode, $object_class = null) { switch ($fetchmode) { case DB_FETCHMODE_OBJECT: if ($object_class) { $this->fetchmode_object_class = $object_class; } case DB_FETCHMODE_ORDERED: case DB_FETCHMODE_ASSOC: $this->fetchmode = $fetchmode; break; default: return $this->raiseError('invalid fetchmode mode'); } } // }}} // {{{ setOption() /** * set the option for the db class * * @param string $option option name * @param mixed $value value for the option * * @return mixed DB_OK or DB_Error */ function setOption($option, $value) { if (isset($this->options[$option])) { $this->options[$option] = $value; return DB_OK; } return $this->raiseError("unknown option $option"); } // }}} // {{{ getOption() /** * returns the value of an option * * @param string $option option name * * @return mixed the option value */ function getOption($option) { if (isset($this->options[$option])) { return $this->options[$option]; } return $this->raiseError("unknown option $option"); } // }}} // {{{ prepare() /** * Prepares a query for multiple execution with execute(). * With some database backends, this is emulated. * prepare() requires a generic query as string like * "INSERT INTO numbers VALUES(?,?,?)". The ? are wildcards. * Types of wildcards: * ? - a quoted scalar value, i.e. strings, integers * & - requires a file name, the content of the file * insert into the query (i.e. saving binary data * in a db) * ! - value is inserted 'as is' * * @param string the query to prepare * * @return resource handle for the query * * @access public * @see execute */ function prepare($query) { $tokens = split("[\&\?\!]", $query); $token = 0; $types = array(); for ($i = 0; $i < strlen($query); $i++) { switch ($query[$i]) { case '?': $types[$token++] = DB_PARAM_SCALAR; break; case '&': $types[$token++] = DB_PARAM_OPAQUE; break; case '!': $types[$token++] = DB_PARAM_MISC; break; } } $this->prepare_tokens[] = &$tokens; end($this->prepare_tokens); $k = key($this->prepare_tokens); $this->prepare_types[$k] = $types; $this->prepared_queries[$k] = &$query; return $k; } // }}} // {{{ execute() /** * Executes a prepared SQL query * With execute() the generic query of prepare is * assigned with the given data array. The values * of the array inserted into the query in the same * order like the array order * * @param resource $stmt query handle from prepare() * @param array $data numeric array containing the * data to insert into the query * * @return mixed a new DB_Result or a DB_Error when fail * * @access public * @see prepare() */ function execute($stmt, $data = false) { $realquery = $this->executeEmulateQuery($stmt, $data); if (DB::isError($realquery)) { return $realquery; } $result = $this->simpleQuery($realquery); if (DB::isError($result) || $result === DB_OK) { return $result; } else { return new DB_result($this, $result); } } // }}} // {{{ executeEmulateQuery() /** * Emulates the execute statement, when not supported * * @param resource $stmt query handle from prepare() * @param array $data numeric array containing the * data to insert into the query * * @return mixed a string containing the real query run when emulating * prepare/execute. A DB error code is returned on failure. * * @access private * @see execute() */ function executeEmulateQuery($stmt, $data = false) { $p = &$this->prepare_tokens; if (!isset($this->prepare_tokens[$stmt]) || !is_array($this->prepare_tokens[$stmt]) || !sizeof($this->prepare_tokens[$stmt])) { return $this->raiseError(DB_ERROR_INVALID); } $qq = &$this->prepare_tokens[$stmt]; $qp = sizeof($qq) - 1; if ((!$data && $qp > 0) || (!is_array($data) && $qp > 1) || (is_array($data) && $qp > sizeof($data))) { $this->last_query = $this->prepared_queries[$stmt]; return $this->raiseError(DB_ERROR_NEED_MORE_DATA); } $realquery = $qq[0]; for ($i = 0; $i < $qp; $i++) { $type = $this->prepare_types[$stmt][$i]; if ($type == DB_PARAM_OPAQUE) { if (is_array($data)) { $fp = fopen($data[$i], 'r'); } else { $fp = fopen($data, 'r'); } $pdata = ''; if ($fp) { while (($buf = fread($fp, 4096)) != false) { $pdata .= $buf; } } } else { if (is_array($data)) { $pdata = &$data[$i]; } else { $pdata = &$data; } } $realquery .= ($type != DB_PARAM_MISC) ? $this->quote($pdata) : $pdata; $realquery .= $qq[$i + 1]; } return $realquery; } // }}} // {{{ executeMultiple() /** * This function does several execute() calls on the same * statement handle. $data must be an array indexed numerically * from 0, one execute call is done for every "row" in the array. * * If an error occurs during execute(), executeMultiple() does not * execute the unfinished rows, but rather returns that error. * * @param resource $stmt query handle from prepare() * @param array $data numeric array containing the * data to insert into the query * * @return mixed DB_OK or DB_Error * * @access public * @see prepare(), execute() */ function executeMultiple( $stmt, &$data ) { for($i = 0; $i < sizeof( $data ); $i++) { $res = $this->execute($stmt, $data[$i]); if (DB::isError($res)) { return $res; } } return DB_OK; } // }}} // {{{ modifyQuery() /** * This method is used by backends to alter queries for various * reasons. It is defined here to assure that all implementations * have this method defined. * * @param string $query query to modify * * @return the new (modified) query * * @access private */ function modifyQuery($query) { return $query; } // }}} // {{{ modifyLimitQuery() /** * This method is used by backends to alter limited queries * * @param string $query query to modify * @param integer $from the row to start to fetching * @param integer $count the numbers of rows to fetch * * @return the new (modified) query * * @access private */ function modifyLimitQuery($query, $from, $count) { return $query; } // }}} // {{{ query() /** * Send a query to the database and return any results with a * DB_result object. * * @access public * * @param string $query the SQL query or the statement to prepare * @param string $params the data to be added to the query * @return mixed a DB_result object or DB_OK on success, a DB * error on failure * * @see DB::isError * @see DB_common::prepare * @see DB_common::execute */ function &query($query, $params = array()) { if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } return $this->execute($sth, $params); } else { $result = $this->simpleQuery($query); if (DB::isError($result) || $result === DB_OK) { return $result; } else { return new DB_result($this, $result); } } } // }}} // {{{ limitQuery() /** * Generates a limited query * *EXPERIMENTAL* * * @param string $query query * @param integer $from the row to start to fetching * @param integer $count the numbers of rows to fetch * * @return mixed a DB_Result object or a DB_Error * * @access public */ function limitQuery($query, $from, $count) { $query = $this->modifyLimitQuery($query, $from, $count); $result = $this->simpleQuery($query); if (DB::isError($result) || $result === DB_OK) { return $result; } else { $res_obj =& new DB_result($this, $result); $res_obj->limit_from = $from; $res_obj->limit_count = $count; return $res_obj; } } // }}} // {{{ getOne() /** * Fetch the first column of the first row of data returned from * a query. Takes care of doing the query and freeing the results * when finished. * * @param string $query the SQL query * @param array $params if supplied, prepare/execute will be used * with this array as execute parameters * * @return mixed DB_Error or the returned value of the query * * @access public */ function &getOne($query, $params = array()) { settype($params, "array"); if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } $res = $this->execute($sth, $params); } else { $res = $this->query($query); } if (DB::isError($res)) { return $res; } $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); if ($err !== DB_OK) { return $err; } $res->free(); if (isset($sth)) { $this->freeResult($sth); } return $row[0]; } // }}} // {{{ getRow() /** * Fetch the first row of data returned from a query. Takes care * of doing the query and freeing the results when finished. * * @param string $query the SQL query * @param integer $fetchmode the fetch mode to use * @param array $params array if supplied, prepare/execute will be used * with this array as execute parameters * @access public * @return array the first row of results as an array indexed from * 0, or a DB error code. */ function &getRow($query, $params = null, $fetchmode = DB_FETCHMODE_DEFAULT) { // compat check, the params and fetchmode parameters used to // have the opposite order if (!is_array($params)) { if (is_array($fetchmode)) { $tmp = $params; $params = $fetchmode; $fetchmode = $tmp; } elseif ($params !== null) { $fetchmode = $params; $params = null; } } $params = (empty($params)) ? array() : $params; $fetchmode = (empty($fetchmode)) ? DB_FETCHMODE_DEFAULT : $fetchmode; settype($params, 'array'); if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } $res = $this->execute($sth, $params); } else { $res = $this->query($query); } if (DB::isError($res)) { return $res; } $err = $res->fetchInto($row, $fetchmode); if ($err !== DB_OK) { return $err; } $res->free(); if (isset($sth)) { $this->freeResult($sth); } return $row; } // }}} // {{{ getCol() /** * Fetch a single column from a result set and return it as an * indexed array. * * @param string $query the SQL query * * @param mixed $col which column to return (integer [column number, * starting at 0] or string [column name]) * * @param array $params array if supplied, prepare/execute will be used * with this array as execute parameters * @access public * * @return array an indexed array with the data from the first * row at index 0, or a DB error code. */ function &getCol($query, $col = 0, $params = array()) { settype($params, "array"); if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } $res = $this->execute($sth, $params); } else { $res = $this->query($query); } if (DB::isError($res)) { return $res; } $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; $ret = array(); while (is_array($row = $res->fetchRow($fetchmode))) { $ret[] = $row[$col]; } if (DB::isError($row)) { $ret = $row; } $res->free(); if (isset($sth)) { $this->freeResult($sth); } return $ret; } // }}} // {{{ getAssoc() /** * Fetch the entire result set of a query and return it as an * associative array using the first column as the key. * * If the result set contains more than two columns, the value * will be an array of the values from column 2-n. If the result * set contains only two columns, the returned value will be a * scalar with the value of the second column (unless forced to an * array with the $force_array parameter). A DB error code is * returned on errors. If the result set contains fewer than two * columns, a DB_ERROR_TRUNCATED error is returned. * * For example, if the table "mytable" contains: * * ID TEXT DATE * -------------------------------- * 1 'one' 944679408 * 2 'two' 944679408 * 3 'three' 944679408 * * Then the call getAssoc('SELECT id,text FROM mytable') returns: * array( * '1' => 'one', * '2' => 'two', * '3' => 'three', * ) * * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: * array( * '1' => array('one', '944679408'), * '2' => array('two', '944679408'), * '3' => array('three', '944679408') * ) * * If the more than one row occurs with the same value in the * first column, the last row overwrites all previous ones by * default. Use the $group parameter if you don't want to * overwrite like this. Example: * * getAssoc('SELECT category,id,name FROM mytable', false, null, DB_FETCHMODE_ASSOC, true) returns: * array( * '1' => array(array('id' => '4', 'name' => 'number four'), * array('id' => '6', 'name' => 'number six') * ), * '9' => array(array('id' => '4', 'name' => 'number four'), * array('id' => '6', 'name' => 'number six') * ) * ) * * Keep in mind that database functions in PHP usually return string * values for results regardless of the database's internal type. * * @param string $query the SQL query * * @param boolean $force_array used only when the query returns * exactly two columns. If true, the values of the returned array * will be one-element arrays instead of scalars. * * @param array $params array if supplied, prepare/execute will be used * with this array as execute parameters * * @param boolean $group if true, the values of the returned array * is wrapped in another array. If the same * key value (in the first column) repeats * itself, the values will be appended to * this array instead of overwriting the * existing values. * * @access public * * @return array associative array with results from the query. */ function &getAssoc($query, $force_array = false, $params = array(), $fetchmode = DB_FETCHMODE_ORDERED, $group = false) { settype($params, "array"); if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } $res = $this->execute($sth, $params); } else { $res = $this->query($query); } if (DB::isError($res)) { return $res; } $cols = $res->numCols(); if ($cols < 2) { return $this->raiseError(DB_ERROR_TRUNCATED); } $results = array(); if ($cols > 2 || $force_array) { // return array values // XXX this part can be optimized if ($fetchmode == DB_FETCHMODE_ASSOC) { while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { reset($row); $key = current($row); unset($row[key($row)]); if ($group) { $results[$key][] = $row; } else { $results[$key] = $row; } } } else { while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { // we shift away the first element to get // indices running from 0 again $key = array_shift($row); if ($group) { $results[$key][] = $row; } else { $results[$key] = $row; } } } if (DB::isError($row)) { $results = $row; } } else { // return scalar values while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { if ($group) { $results[$row[0]][] = $row[1]; } else { $results[$row[0]] = $row[1]; } } if (DB::isError($row)) { $results = $row; } } $res->free(); if (isset($sth)) { $this->freeResult($sth); } return $results; } // }}} // {{{ getAll() /** * Fetch all the rows returned from a query. * * @param string $query the SQL query * * @param array $params array if supplied, prepare/execute will be used * with this array as execute parameters * @param integer $fetchmode the fetch mode to use * * @access public * @return array an nested array, or a DB error */ function &getAll($query, $params = null, $fetchmode = DB_FETCHMODE_DEFAULT) { // compat check, the params and fetchmode parameters used to // have the opposite order if (!is_array($params)) { if (is_array($fetchmode)) { $tmp = $params; $params = $fetchmode; $fetchmode = $tmp; } elseif ($params !== null) { $fetchmode = $params; $params = null; } } $params = (empty($params)) ? array() : $params; $fetchmode = (empty($fetchmode)) ? DB_FETCHMODE_DEFAULT : $fetchmode; settype($params, "array"); if (sizeof($params) > 0) { $sth = $this->prepare($query); if (DB::isError($sth)) { return $sth; } $res = $this->execute($sth, $params); } else { $res = $this->query($query); } if (DB::isError($res)) { return $res; } $results = array(); $this->pushErrorHandling(PEAR_ERROR_RETURN); while (DB_OK === $res->fetchInto($row, $fetchmode)) { if ($fetchmode & DB_FETCHMODE_FLIPPED) { foreach ($row as $key => $val) { $results[$key][] = $val; } } else { $results[] = $row; } } $this->popErrorHandling(); $res->free(); if (isset($sth)) { $this->freeResult($sth); } if (DB::isError($row)) { return $this->raiseError($row); } return $results; } // }}} // {{{ autoCommit() /** * enable automatic Commit * * @param boolean $onoff * @return mixed DB_Error * * @access public */ function autoCommit($onoff=false) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ commit() /** * starts a Commit * * @return mixed DB_Error * * @access public */ function commit() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ rollback() /** * starts a rollback * * @return mixed DB_Error * * @access public */ function rollback() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ numRows() /** * returns the number of rows in a result object * * @param object DB_Result the result object to check * * @return mixed DB_Error or the number of rows * * @access public */ function numRows($result) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ affectedRows() /** * returns the affected rows of a query * * @return mixed DB_Error or number of rows * * @access public */ function affectedRows() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ errorNative() /** * returns an errormessage, provides by the database * * @return mixed DB_Error or message * * @access public */ function errorNative() { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ nextId() /** * returns the next free id of a sequence * * @param string $seq_name name of the sequence * @param boolean $ondemand when true the seqence is * automatic created, if it * not exists * * @return mixed DB_Error or id */ function nextId($seq_name, $ondemand = true) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ createSequence() /** * creates a new sequence * * @param string $seq_name name of the new sequence * * @return mixed DB_Error * * @access public */ function createSequence($seq_name) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ dropSequence() /** * deletes a sequence * * @param string $seq_name name of the sequence * * @return mixed DB_Error * * @access public */ function dropSequence($seq_name) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ tableInfo() /** * returns meta data about the result set * * @param object DB_Result $result the result object to analyse * @param mixed $mode depends on implementation * * @return mixed DB_Error * * @access public */ function tableInfo($result, $mode = null) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); } // }}} // {{{ getTables() /** * @deprecated */ function getTables() { return $this->getListOf('tables'); } // }}} // {{{ getListOf() /** * list internal DB info * valid values for $type are db dependent, * often: databases, users, view, functions * * @param string $type type of requested info * * @return mixed DB_Error or the requested data * * @access public */ function getListOf($type) { $sql = $this->getSpecialQuery($type); if ($sql === null) { // No support return $this->raiseError(DB_ERROR_UNSUPPORTED); } elseif (is_int($sql) || DB::isError($sql)) { // Previous error return $this->raiseError($sql); } elseif (is_array($sql)) { // Already the result return $sql; } return $this->getCol($sql); // Launch this query } // }}} } ?>