芝麻web文件管理V1.00
编辑当前文件:/home/strato/chroot/opt/RZphp80/includes/MDB/QueryTool/Query.php
* @copyright 2004-2008 Lorenzo Alberton * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) * @version CVS: $Id: Query.php 322096 2012-01-11 21:00:42Z danielc $ * @link http://pear.php.net/package/MDB_QueryTool */ /** * require the PEAR and MDB classes */ require_once 'PEAR.php'; /** * MDB_QueryTool_Query class * * This class should be extended * * @category Database * @package MDB_QueryTool * @author Lorenzo Alberton
* @copyright 2004-2008 Lorenzo Alberton * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) * @link http://pear.php.net/package/MDB_QueryTool */ class MDB_QueryTool_Query { // {{{ class vars /** * @var string the name of the primary column */ var $primaryCol = 'id'; /** * @var string the current table the class works on */ var $table = ''; /** * @var string the name of the sequence for this table */ var $sequenceName = null; /** * @var object the db-object, a PEAR::MDB[2] object instance */ var $db = null; /** * @var string the where condition * @access private */ var $_where = ''; /** * @var string the order condition * @access private */ var $_order = ''; /** * @var string the having definition * @access private */ var $_having = ''; /** * @var array contains the join content * the key is the join type, for now we have 'default' and 'left' * inside each key 'table' contains the table * key 'where' contains the where clause for the join * @access private */ var $_join = array(); /** * @var string which column to index the result by * @access private */ var $_index = null; /** * @var string the group-by clause * @access private */ var $_group = ''; /** * @var array the limit * @access private */ var $_limit = array(); /** * @var boolean if to use the MDB_QueryTool_Result as a result or not * @access private */ var $_resultType = 'none'; /** * @var array the metadata temporary saved * @access private */ var $_metadata = array(); /** * @var string (?) * @access private */ var $_lastQuery = null; /** * @var string the rows that shall be selected * @access private */ var $_select = '*'; /** * @var string the rows that shall not be selected * @access private */ var $_dontSelect = ''; /** * @var array this array saves different modes in which this class works * i.e. 'raw' means no quoting before saving/updating data * @access private */ var $options = array( 'raw' => false, 'verbose' => true, // set this to false in a productive environment // it will produce error-logs if set to true 'useCache' => false, 'logFile' => false, ); /** * this array contains information about the tables * those are * - 'name' => the real table name * - 'shortName' => the short name used, so that when moving the table i.e. * onto a provider's db and u have to rename the tables to * longer names this name will be relevant, i.e. when * autoJoining, i.e. a table name on your local machine is: * 'user' but online it has to be 'applName_user' then the * shortName will be used to determine if a column refers to * another table, if the colName is 'user_id', it knows the * shortName 'user' refers to the table 'applName_user' */ var $tableSpec = array(); /** * this is the regular expression that shall be used to find a table's shortName * in a column name, the string found by using this regular expression will be removed * from the column name and it will be checked if it is a table name * i.e. the default '/_id$/' would find the table name 'user' from the column name 'user_id' * * @access private */ var $_tableNameToShortNamePreg = '/^.*_/'; /** * @var array this array caches queries that have already been built once * to reduce the execution time * @access private */ var $_queryCache = array(); /** * The object that contains the log-instance * @access private */ var $_logObject = null; /** * Some internal data the logging needs * @access private */ var $_logData = array(); /** * MDB version * @access private */ var $_MDBversion = 1; /** * Name of class to return when object is used * @access private */ var $_returnclass = 'MDB_QueryTool_Result_Row'; // }}} // {{{ __construct() /** * this is the constructor, as it will be implemented in ZE2 (php5) * * @param object db-object * @param array options array * @access public */ /* function __construct($dsn=false, $options=array()) { if (!isset($options['autoConnect'])) { $autoConnect = true; } else { $autoConnect = $options['autoConnect']; } if (isset($options['errorCallback'])) { $this->setErrorCallback($options['errorCallback']); } if (isset($options['errorSetCallback'])) { $this->setErrorSetCallback($options['errorSetCallback']); } if (isset($options['errorLogCallback'])) { $this->setErrorLogCallback($options['errorLogCallback']); } if ($autoConnect && $dsn) { $this->connect($dsn, $options); } if (is_null($this->sequenceName)) { $this->sequenceName = $this->table; } } */ // }}} // {{{ MDB_QueryTool_Query() /** * Constructor * * @param mixed $dsn DSN string, DSN array or MDB object * @param array $options db options * @param integer $MDBversion MDB version (1=MDB, 2=MDB2) * * @access public */ function MDB_QueryTool_Query($dsn = false, $options = array(), $MDBversion = 1) { //$this->__construct($dsn, $options); if (!isset($options['autoConnect'])) { $autoConnect = true; } else { $autoConnect = $options['autoConnect']; unset($options['autoConnect']); } if (isset($options['errorCallback'])) { $this->setErrorCallback($options['errorCallback']); unset($options['errorCallback']); } if (isset($options['errorSetCallback'])) { $this->setErrorSetCallback($options['errorSetCallback']); unset($options['errorSetCallback']); } if (isset($options['errorLogCallback'])) { $this->setErrorLogCallback($options['errorLogCallback']); unset($options['errorLogCallback']); } if ($autoConnect && $dsn) { $this->connect($dsn, $options, $MDBversion); } if (is_null($this->sequenceName)) { $this->sequenceName = $this->table; } } // }}} // {{{ connect() /** * use this method if you want to connect manually * * @param mixed $dsn DSN string, DSN array or MDB object * @param array $options db options * @param integer $MDBversion MDB version (1=MDB, 2=MDB2) * * @return void */ function connect($dsn, $options = array(), $MDBversion = 1) { if (is_object($dsn)) { $res =& $dsn; } elseif ($MDBversion == 2) { include_once 'MDB2.php'; if (!isset($options['idxname_format'])) { $options['idxname_format'] = '%s'; } $res = &MDB2::connect($dsn, $options); } else { include_once 'MDB.php'; $res = &MDB::connect($dsn, $options); } if (PEAR::isError($res)) { // FIXXME what shall we do here? $this->_errorLog($res->getUserInfo()); return; } $this->setDbInstance($res); } // }}} // {{{ getDbInstance() /** * Get a MDB[2] object instance * * @return object MDB Object * @access public */ function &getDbInstance() { return $this->db; } // }}} // {{{ setDbInstance() /** * Setup using an existing connection. * this also sets the MDB[2]_FETCHMODE_ASSOC since this class * needs this to be set! * * @param object &$dbh a reference to an existing DB-object * * @return void */ function setDbInstance(&$dbh) { $this->db =& $dbh; $this->_MDBversion = is_a($this->db, 'mdb2_driver_common') ? 2 : 1; if ($this->_MDBversion == 2) { $this->db->setFetchMode(MDB2_FETCHMODE_ASSOC); } else { $this->db->setFetchMode(MDB_FETCHMODE_ASSOC); } } // }}} // {{{ get() /** * get the data of a single entry * if the second parameter is only one column the result will be returned * directly not as an array! * * @param integer $id the id of the element to retrieve * @param string $column if this is given only one row shall be returned, * directly, not an array * * @return mixed (1) an array of the retrieved data * (2) if the second parameter is given and its only one column, * only this column's data will be returned * (3) false in case of failure * @access public */ function get($id, $column = '') { if (is_string($id)) { $id = trim($id); } $column = trim($column); $table = $this->table; $getMethod = 'getRow'; if ($column && !strpos($column, ',')) { // if only one column shall be selected $getMethod = 'getOne'; } // we dont use 'setSelect' here, since this changes the setup of the class, we // build the query directly // if $column is '' then _buildSelect selects '*' anyway, so that's the same behaviour as before $query['select'] = $this->_buildSelect($column); $query['where'] = $this->_buildWhere($this->table.'.'.$this->primaryCol.'='.$this->_quote($id)); $queryString = $this->_buildSelectQuery($query); return $this->returnResult($this->execute($queryString, $getMethod)); } // }}} // {{{ getMultiple() /** * gets the data of the given ids * * @param array $ids this is an array of ids to retrieve * @param string $column the column to search in for * * @return mixed an array of the retrieved data, or false in case of failure * when failing an error is set in $this->_error * @access public */ function getMultiple($ids, $column = '') { $col = $this->primaryCol; if ($column) { $col = $column; } // FIXXME if $ids has no table.col syntax and we are using joins, // the table better be put in front!!! $ids = $this->_quoteArray($ids); $query['where'] = $this->_buildWhere($col.' IN ('.implode(',', $ids).')'); $queryString = $this->_buildSelectQuery($query); return $this->returnResult($this->execute($queryString)); } // }}} // {{{ getOne() /** * get the first value of the first row * * @return mixed (1) a scalar value in case of success * (2) false in case of failure * @access public */ function getOne() { $queryString = $this->getQueryString(); return $this->execute($queryString, 'queryOne'); } // }}} // {{{ getAll() /** * get all entries from the DB * for sorting use setOrder!!!, the last 2 parameters are deprecated * * @param int $from to start from * @param int $count the number of rows to show * @param string $method the MDB[2]-method to use * * @return mixed an array of the retrieved data, or false in case of failure * when failing an error is set in $this->_error * @access public */ function getAll($from=0, $count=0, $method='getAll') { $query = array(); if ($count) { $query = array('limit' => array($from, $count)); } return $this->returnResult($this->execute($this->_buildSelectQuery($query), $method)); } // }}} // {{{ getCol() /** * this method only returns one column, so the result will be a one dimensional array * this does also mean that using setSelect() should be set to *one* column, the one you want to * have returned a most common use case for this could be: * $table->setSelect('id'); * $ids = $table->getCol(); * OR * $ids = $table->getCol('id'); * so ids will be an array with all the id's * * @param string $column the column that shall be retrieved * @param int $from to start from * @param int $count the number of rows to show * * @return mixed an array of the retrieved data, or false in case of failure * when failing an error is set in $this->_error * @access public */ function getCol($column = null, $from = 0, $count = 0) { $query = array(); if (!is_null($column)) { // by using _buildSelect() I can be sure that the table name will not be ambiguous // i.e. in a join, where all the joined tables have a col 'id' // _buildSelect() will put the proper table name in front in case there is none $query['select'] = $this->_buildSelect(trim($column)); } if ($count) { $query['limit'] = array($from, $count); } $res = $this->returnResult($this->execute($this->_buildSelectQuery($query), 'getCol')); return (false === $res) ? array() : $res; } // }}} // {{{ getCount() /** * get the number of entries * * @return mixed an array of the retrieved data, or false in case of failure * when failing an error is set in $this->_error * @access public */ function getCount() { /* the following query works on mysql SELECT count(DISTINCT image.id) FROM image2tree RIGHT JOIN image ON image.id = image2tree.image_id the reason why this is needed - i just wanted to get the number of rows that do exist if the result is grouped by image.id the following query is what i tried first, but that returns the number of rows that have been grouped together for each image.id SELECT count(*) FROM image2tree RIGHT JOIN image ON image.id = image2tree.image_id GROUP BY image.id so that's why we do the following, i am not sure if that is standard SQL and absolutley correct!!! */ //FIXXME see comment above if this is absolutely correct!!! if ($group = $this->_buildGroup()) { $query['select'] = 'COUNT(DISTINCT '.$group.')'; $query['group'] = ''; } else { $query['select'] = 'COUNT(*)'; } $query['order'] = ''; // order is not of importance and might freak up // the special group-handling up there, since the order-col is not be known /* # FIXXME use the following line, but watch out, then it has to be used # in every method, or this value will be used always, simply try calling # getCount and getAll afterwards, getAll will return the count :-) # if getAll doesn't use setSelect!!! */ //$this->setSelect('COUNT(*)'); $queryString = $this->_buildSelectQuery($query, true); return ($res = $this->execute($queryString, 'getOne')) ? $res : 0; } // }}} // {{{ getDefaultValues() /** * return an empty element where all the array elements do already exist * corresponding to the columns in the DB * * @return array an empty, or pre-initialized element * @access public */ function getDefaultValues() { $ret = array(); // here we read all the columns from the DB and initialize them // with '' to prevent PHP-warnings in case we use error_reporting=E_ALL foreach ($this->metadata() as $aCol => $x) { $ret[$aCol] = ''; } return $ret; } // }}} // {{{ getEmptyElement() /** * this is just for BC * * @return array * @deprecated */ function getEmptyElement() { $this->getDefaultValues(); } // }}} // {{{ getQueryString() /** * Render the current query and return it as a string. * * @return string the current query */ function getQueryString() { $ret = $this->_buildSelectQuery(); if (is_string($ret)) { $ret = trim($ret); } return $ret; } // }}} // {{{ _floatToStringNoLocale() /** * If a double number was "localized", restore its decimal separator to "." * * @param float $float number to make safe for SQL * * @return string * @see http://pear.php.net/bugs/bug.php?id=3021 * @access private */ function _floatToStringNoLocale($float) { $precision = strlen($float) - strlen(intval($float)); if ($precision) { --$precision; // don't count decimal separator } return number_format($float, $precision, '.', ''); } // }}} // {{{ _localeSafeImplode() /** * New "implode()" implementation to bypass float locale formatting: * the SQL decimal separator is and must be ".". Always. * * @param string $glue glue * @param array $array data array * * @return string * @access private */ function _localeSafeImplode($glue, $array) { $str = ''; foreach ($array as $value) { if ($str !== '') { $str .= $glue; } if (is_null($value)) { $value = 'NULL'; } elseif ($value === '') { $value = "''"; } $str .= is_double($value) ? $this->_floatToStringNoLocale($value) : $value; } return $str; } // }}} // {{{ save() /** * save data, calls either update or add * if the primaryCol is given in the data this method knows that the * data passed to it are meant to be updated (call 'update'), otherwise it will * call the method 'add'. * If you dont like this behaviour simply stick with the methods 'add' * and 'update' and ignore this one here. * This method is very useful when you have validation checks that have to * be done for both adding and updating, then you can simply overwrite this * method and do the checks in here, and both cases will be validated first. * * @param array $data contains the new data that shall be saved in the DB * * @return mixed the data returned by either add or update-method * @access public */ function save($data) { if (isset($data[$this->primaryCol]) && $data[$this->primaryCol]) { return $this->update($data); } return $this->add($data); } // }}} // {{{ update() /** * update the member data of a data set * * @param array $newData contains the new data that shall be saved in the DB * the id has to be given in the field with the key 'ID' * * @return boolean true on success, or false otherwise * @access public */ function update($newData) { $query = array(); // do only set the 'where' part in $query, if a primary column is given // if not the default 'where' clause is used if (isset($newData[$this->primaryCol])) { //$this->_errorSet('Error updating the new member.'); //return false; $query['where'] = $this->_quoteIdentifier($this->primaryCol).'='.$this->_quote($newData[$this->primaryCol]); } $newData = $this->_checkColumns($newData, 'update'); $values = array(); foreach ($newData as $key => $aData) { // quote the data //$values[] = "{$this->table}.$key=" . $this->_quote($aData); $values[] = $this->_quoteIdentifier($key) . '=' . $this->_quote($aData); } $query['set'] = $this->_localeSafeImplode(',', $values); $updateString = $this->_buildUpdateQuery($query); return $this->execute($updateString, 'query') ? true : false; } // }}} // {{{ _getNextId() /** * get the next available PK id * * @return integer * @access private */ function _getNextId() { if ($this->_MDBversion == 2) { // first, try with getBeforeId(), so when the RDBM supports // AUTO_INCREMENT no sequence table should be created $this->db->loadModule('Extended', null, false); $id = $this->db->extended->getBeforeID($this->table, $this->primaryCol, true, false); if (is_null($id) || $id === 'NULL' || PEAR::isError($id)) { //fallback to nextID() $id = $this->db->nextID($this->sequenceName); } } else { $id = $this->db->nextId($this->sequenceName); } return $id; } // }}} // {{{ add() /** * add a new member in the DB * * @param array $newData contains the new data that shall be saved in the DB * * @return mixed the inserted id on success, or false otherwise * @access public */ function add($newData) { // if no primary col is given, get next sequence value if (empty($newData[$this->primaryCol])) { if ($this->primaryCol) { // do only use the sequence if a primary column is given // otherwise the data are written as given $id = $this->_getNextId(); $newData[$this->primaryCol] = $id; } else { // if no primary col is given return true on success $id = true; } } else { $id = $newData[$this->primaryCol]; } //unset($newData[$this->primaryCol]); $newData = $this->_checkColumns($newData, 'add'); $newData = $this->_quoteArray($newData); //quoting the columns $tmpData = array(); foreach ($newData as $key=>$val) { $tmpData[$this->_quoteIdentifier($key)] = $val; } $newData = $tmpData; unset($tmpData); $query = sprintf( 'INSERT INTO %s (%s) VALUES (%s)', $this->table, implode(', ', array_keys($newData)), $this->_localeSafeImplode(', ', $newData) ); return $this->execute($query, 'query') ? $id : false; } // }}} // {{{ addMultiple() /** * adds multiple new members in the DB * * @param array $data contains an array of new data that shall be saved in the DB * the key-value pairs have to be the same for all the data!!! * * @return mixed the inserted IDs on success, or false otherwise * @access public */ function addMultiple($data) { if (!sizeof($data)) { return false; } $ret = true; // the inserted ids which will be returned or if no primaryCol is given // we return true by default $retIds = $this->primaryCol ? array() : true; $dbtype = $this->db->phptype; if ($dbtype == 'mysql') { // Optimise for MySQL $allData = array(); // each row that will be inserted foreach ($data as $key => $aData) { $aData = $this->_checkColumns($aData, 'add'); $aData = $this->_quoteArray($aData); if (empty($aData[$this->primaryCol])) { if ($this->primaryCol) { // do only use the sequence if a primary column is given // otherwise the data are written as given $id = $this->_getNextId(); $retIds[] = $id; $aData[$this->primaryCol] = $id; } } else { $retIds[] = $aData[$this->primaryCol]; } $allData[] = '('.$this->_localeSafeImplode(', ', $aData).')'; } //quoting the columns $tmpData = array(); foreach ($aData as $key => $val) { $tmpData[$this->_quoteIdentifier($key)] = $val; } $newData = $tmpData; unset($tmpData); $query = sprintf('INSERT INTO %s (%s) VALUES %s', $this->table, implode(', ', array_keys($aData)) , $this->_localeSafeImplode(', ', $allData) ); return $this->execute($query, 'query') ? $retIds : false; } //Executing for every entry the add method foreach ($data as $entity) { if ($ret) { $res = $this->add($entity); if (!$res) { $ret = false; } elseif (is_array($retIds)) { $retIds[] = $res; } } } //Setting the return value to the array with ids if ($ret) { $ret = $retIds; } return $ret; } // }}} // {{{ remove() /** * removes a member from the DB * * @param mixed $data - integer/string: the value of the column that * shall be removed * - array: multiple columns that shall be matched, * the second parameter will be ignored * @param string $whereCol the column to match the data against, only if * $data is not an array * * @return boolean * @access public */ function remove($data, $whereCol = '') { //$raw = $this->getOption('raw'); if (is_array($data)) { //FIXXME check $data if it only contains columns that really exist in the table $wheres = array(); foreach ($data as $key => $val) { if (is_null($val)) { $wheres[] = $this->_quoteIdentifier($key) .' IS NULL'; } else { $wheres[] = $this->_quoteIdentifier($key) .'='. $this->_quote($val); } } $whereClause = implode(' AND ', $wheres); } else { if (empty($whereCol)) { $whereCol = $this->primaryCol; } $whereClause = $this->_quoteIdentifier($whereCol).'='. $this->_quote($data); } $query = 'DELETE FROM '. $this->table .' WHERE '. $whereClause; return $this->execute($query, 'query') ? true : false; // i think this method should return the ID's that it removed, // this way we could simply use the result for further actions that depend // on those id ... or? make stuff easier, see ignaz::imail::remove } // }}} // {{{ removeAll() /** * empty a table * * @return resultSet or false on error [execute() result] * @access public */ function removeAll() { $query = 'DELETE FROM ' . $this->table; return $this->execute($query, 'query') ? true : false; } // }}} // {{{ removeMultiple() /** * remove the datasets with the given IDs * * @param array $ids the IDs to remove * @param string $colName the column to match the data against * * @return resultSet or false on error [execute() result] * @access public */ function removeMultiple($ids, $colName = '') { if (empty($colName)) { $colName = $this->primaryCol; } $ids = $this->_quoteArray($ids); $query = sprintf( 'DELETE FROM %s WHERE %s IN (%s)', $this->table, $colName, $this->_localeSafeImplode(',', $ids) ); return $this->execute($query, 'query') ? true : false; } // }}} // {{{ removePrimary() /** * removes a member from the DB and calls the remove methods of the given objects * so all rows in another table that refer to this table are erased too * * @param integer $id the value of the primary key * @param string $colName the column name of the tables with the foreign keys * @param object $atLeastOneObject just for convinience, so nobody forgets * to call this method with at least one * object as a parameter * * @return boolean * @access public */ function removePrimary($id, $colName, $atLeastOneObject) { $argCounter = 2; // we have 2 parameters that need to be given at least // func_get_arg returns false and a warning if there are no more parameters, so // we suppress the warning and check for false while ($object = @func_get_arg($argCounter++)) { //FIXXXME let $object also simply be a table name if (!$object->remove($id, $colName)) { //FIXXXME do this better $this->_errorSet("Error removing '$colName=$id' from table {$object->table}."); return false; } } if (!$this->remove($id)) { return false; } return true; } // }}} // {{{ setLimit() /** * sets query limits * * @param integer $from start index * @param integer $count number of results * * @return void * @access public */ function setLimit($from = 0, $count = 0) { if (0 == $from && 0 == $count) { $this->_limit = array(); } else { $this->_limit = array($from, $count); } } // }}} // {{{ getLimit() /** * gets query limits * * @return array (start index, number of results) * @access public */ function getLimit() { return $this->_limit; } // }}} // {{{ setWhere() /** * Sets the WHERE condition which is used for the current instance * * @param string $whereCondition the WHERE condition, this can be complete * like 'X=7 AND Y=8' * * @return void * @access public */ function setWhere($whereCondition='') { $this->_where = $whereCondition; // FIXXME parse the WHERE condition and replace ambiguous column names, // such as "name='Deutschland'" with "country.name='Deutschland'" // then the users don't have to write that explicitly and can use the same // name as in the setOrder i.e. setOrder('name,_net_name,_netPrefix_prefix'); } // }}} // {{{ getWhere() /** * Gets the WHERE condition which is used for the current instance * * @return string the WHERE condition, this can be complete like 'X=7 AND Y=8' * @access public */ function getWhere() { return $this->_where; } // }}} // {{{ addWhere() /** * Only adds a string to the WHERE clause * * @param string $where the WHERE clause to add to the existing one * @param string $condition the condition for how to concatenate the new * WHERE clause to the existing one * * @return void * @access public */ function addWhere($where, $condition = 'AND') { if ($this->getWhere()) { $where = $this->getWhere().' '.$condition.' '.$where; } $this->setWhere($where); } // }}} // {{{ addWhereSearch() /** * add a WHERE-LIKE clause which works like a search for the given string * i.e. calling it like this: * $this->addWhereSearch('name', 'otto hans') * produces a WHERE clause like this one * UPPER(name) LIKE "%OTTO%HANS%" * so the search finds the given string * * @param string $column the column to search in for * @param string $string the string to search for * @param string $connectString the condition for how to concatenate the * new WHERE clause to the existing one [default AND] * * @return void * @access public */ function addWhereSearch($column, $string, $connectString = 'AND') { // if the column doesn't contain a tablename use the current table name // in case it is a defined column to prevent ambiguous rows if (strpos($column, '.') === false) { $meta = $this->metadata(); if (isset($meta[$column])) { $column = $this->table .'.'. trim($column); } } //ibase doesn't have the LOWER() function, so using UPPER() here... $string = $this->_quote('%'.str_replace(' ', '%', strtoupper($string)).'%'); $this->addWhere("UPPER($column) LIKE $string", $connectString); } // }}} // {{{ setOrder() /** * sets the ORDER BY condition which is used for the current instance * * @param string $orderCondition the ORDER BY condition * @param boolean $desc sorting order (TRUE => ASC, FALSE => DESC) * * @return void * @access public */ function setOrder($orderCondition='', $desc=false) { $this->_order = $orderCondition . ($desc ? ' DESC' : ''); } // }}} // {{{ addOrder() /** * Add a ORDER BY parameter to the query. * * @param string $orderCondition the ORDER BY condition * @param boolean $desc sorting order (TRUE => ASC, FALSE => DESC) * * @return void * @access public */ function addOrder($orderCondition = '', $desc = false) { $order = $orderCondition . ($desc ? ' DESC' : ''); if ($this->_order) { $this->_order = $this->_order .','. $order; } else { $this->_order = $order; } } // }}} // {{{ getOrder() /** * gets the ORDER BY condition which is used for the current instance * * @return string the ORDER BY condition, this can be complete like 'ID,TIMESTAMP DESC' * @access public */ function getOrder() { return $this->_order; } // }}} // {{{ setHaving() /** * sets the HAVING definition * * @param string $having the HAVING definition * * @return void * @access public */ function setHaving($having = '') { $this->_having = $having; } // }}} // {{{ getHaving() /** * gets the HAVING definition which is used for the current instance * * @return string the HAVING definition * @access public */ function getHaving() { return $this->_having; } // }}} // {{{ addHaving() /** * Extend the current HAVING clause. This is very useful, when you are building * this clause from different places and don't want to overwrite the currently * set HAVING clause, but extend it. * * @param string $what this is a HAVING clause, i.e. 'column' or * 'table.column' or 'MAX(column)' * @param string $connectString the connection string, which usually stays * the default, which is ',' (a comma) * * @return void * @access public */ function addHaving($what = '*', $connectString = ' AND ') { if ($this->_having) { $this->_having = $this->_having . $connectString . $what; } else { $this->_having = $what; } } // }}} // {{{ setJoin() /** * sets a join-condition * * @param mixed $table either a string or an array that contains * the table(s) to join on the current table * @param string $where the WHERE clause for the join * @param string $joinType the JOIN type * * @return void * @access public */ function setJoin($table = null, $where = null, $joinType = 'default') { //FIXXME make it possible to pass a table name as a string like this too 'user u' // where u is the string that can be used to refer to this table in a where/order // or whatever condition // this way it will be possible to join tables with itself, like setJoin(array('user u', 'user u1')) // this wouldnt work yet, but for doing so we would need to change the _build methods too!!! // because they use getJoin('tables') and this simply returns all the tables in use // but don't take care of the mentioned syntax if (is_null($table) || is_null($where)) { // remove the join if not sufficient parameters are given $this->_join[$joinType] = array(); return; } /* this causes problems if we use the order-by, since it doesn't know the name to order it by ... :-) // replace the table names with the internal name used for the join // this way we can also join one table multiple times if it will be implemented one day $this->_join[$table] = preg_replace('/'.$table.'/','j1',$where); */ $this->_join[$joinType][$table] = $where; } // }}} // {{{ setLeftJoin() /** * if you do a left join on $this->table you will get all entries * from $this->table, also if there are no entries for them in the joined table * if both parameters are not given the left-join will be removed * NOTE: be sure to only use either a right or a left join * * @param string $table the table(s) to be left-joined * @param string $where the where clause for the join * * @return void * @access public */ function setLeftJoin($table = null, $where = null) { $this->setJoin($table, $where, 'left'); } // }}} // {{{ addLeftJoin() /** * Add a LEFT JOIN clause * * @param string $table the table(s) to be left-joined * @param string $where the where clause for the join * @param string $type join type * * @return void * @access public */ function addLeftJoin($table, $where, $type = 'left') { // init value, to prevent E_ALL-warning if (!isset($this->_join[$type]) || !$this->_join[$type]) { $this->_join[$type] = array(); } $this->_join[$type][$table] = $where; } // }}} // {{{ setRightJoin() /** * see setLeftJoin for further explaination on what a left/right join is * NOTE: be sure to only use either a right or a left join * //FIXXME check if the above sentence is necessary and if SQL doesn't * //allow the use of both * * @param string $table the table(s) to be right-joined * @param string $where the where clause for the join * * @return void * @see setLeftJoin() * @access public */ function setRightJoin($table = null, $where = null) { $this->setJoin($table, $where, 'right'); } // }}} // {{{ getJoin() /** * gets the join-condition * * @param string $what [null|''|'table'|'tables'|'right'|'left'|'inner'] * * @return array gets the join parameters * @access public */ function getJoin($what = null) { // if the user requests all the join data or if the join is empty, return it if (is_null($what) || empty($this->_join)) { return $this->_join; } $ret = array(); switch (strtolower($what)) { case 'table': case 'tables': foreach ($this->_join as $aJoin) { if (count($aJoin)) { $ret = array_merge($ret, array_keys($aJoin)); } } break; case 'inner': // return inner-join data only case 'right': // return right-join data only case 'left': // return left join data only default: if (isset($this->_join[$what]) && count($this->_join[$what])) { $ret = array_merge($ret, $this->_join[$what]); } break; } return $ret; } // }}} // {{{ addJoin() /** * adds a table and a where clause that shall be used for the join * instead of calling * setJoin(array(table1,table2),'
AND
') * you can also call * setJoin(table1,'
') * addJoin(table2,'
') * or where it makes more sense is to build a query which is made out of a * left join and a standard join * setLeftJoin(table1,'
') * // results in ... FROM $this->table LEFT JOIN table ON
* addJoin(table2,'
') * // results in ... FROM $this->table,table2 LEFT JOIN table ON
WHERE
* * @param string $table the table to be joined * @param string $where the where clause for the join * @param string $type the join type * * @return void * @access public */ function addJoin($table, $where, $type = 'default') { if ($table == $this->table) { return; //skip. Self joins are not supported. } // init value, to prevent E_ALL-warning if (!array_key_exists($type, $this->_join)) { $this->_join[$type] = array(); } $this->_join[$type][$table] = $where; } // }}} // {{{ setTable() /** * sets the table this class is currently working on * * @param string $table the table name * * @return void * @access public */ function setTable($table) { $this->table = $table; } // }}} // {{{ getTable() /** * gets the table this class is currently working on * * @return string the table name * @access public */ function getTable() { return $this->table; } // }}} // {{{ setGroup() /** * sets the group-by condition * * @param string $group the group condition * * @return void * @access public */ function setGroup($group = '') { $this->_group = $group; //FIXXME parse the condition and replace ambiguous column names, // such as "name='Deutschland'" with "country.name='Deutschland'" // then the users don't have to write that explicitly and can use the same // name as in the setOrder i.e. setOrder('name,_net_name,_netPrefix_prefix'); } // }}} // {{{ getGroup() /** * gets the group condition which is used for the current instance * * @return string the group condition * @access public */ function getGroup() { return $this->_group; } // }}} // {{{ setSelect() /** * limit the result to return only the columns given in $what * * @param string $what fields that shall be selected * * @return void * @access public */ function setSelect($what = '*') { $this->_select = $what; } // }}} // {{{ addSelect() /** * add a string to the select-part of the query and connects it to an existing * string using the $connectString, which by default is a comma. * (SELECT xxx FROM - xxx is the select-part of a query) * * @param string $what the string that shall be added to the select-part * @param string $connectString the string to connect the new string with the existing one * * @return void * @access public */ function addSelect($what = '*', $connectString = ',') { // if the select string is not empty add the string, otherwise simply set it if ($this->_select) { $this->_select = $this->_select . $connectString . $what; } else { $this->_select = $what; } } // }}} // {{{ getSelect() /** * Get the SELECT clause * * @return string * @access public */ function getSelect() { return $this->_select; } // }}} // {{{ setDontSelect() /** * Set the columns that shall not be selected * * @param string $what what should NOT be selected * * @return void * @access public */ function setDontSelect($what = '') { $this->_dontSelect = $what; } // }}} // {{{ getDontSelect() /** * Get the columns that shall not be selected * * @return string * @access public */ function getDontSelect() { return $this->_dontSelect; } // }}} // {{{ reset() /** * reset all the set* settings; with no parameter given, it resets them all. * * @param array $what settings to reset * * @return void * @access public */ function reset($what = array()) { if (!sizeof($what)) { $what = array( 'select', 'dontSelect', 'group', 'having', 'limit', 'where', 'index', 'order', 'join', 'leftJoin', 'rightJoin' ); } foreach ($what as $aReset) { $this->{'set'.ucfirst($aReset)}(); } } // }}} // {{{ setOption() /** * set mode the class shall work in * currently we have the modes: * 'raw' does not quote the data before building the query * * @param string $option the mode to be set * @param mixed $value the value of the mode * * @return void * @access public */ function setOption($option, $value) { $this->options[strtolower($option)] = $value; if (strtolower($option) == 'raw') { //Setting the quoting $this->db->setOption('quote_identifier', !$this->options['raw']); } } // }}} // {{{ getOption() /** * Get the given option * * @param string $option name of the option to retrieve * * @return string value of the option * @access public */ function getOption($option) { return $this->options[strtolower($option)]; } // }}} // {{{ _quoteIdentifier() /** * Quotes an identifier (table or field name). This wrapper is needed to * comply with the $raw parameter and to override DB_ibase::quoteIdentifier(). * * @param string $var identifier to quote * * @return string quoted identifier * @access private */ function _quoteIdentifier($var) { if (!$this->getOption('raw')) { return $this->db->quoteIdentifier($var); } return $var; } // }}} // {{{ _quoteArray() /** * quotes all the data in this array if we are not in raw mode! * * @param array $data array of data to quote * * @return array * @access private */ function _quoteArray($data) { if (!$this->getOption('raw')) { //check added for gain in speed if $this->raw==true foreach ($data as $key => $val) { $data[$key] = $this->_quote($val); } } return $data; } // }}} // {{{ _quote() /** * quotes all the data in this array if we are not in raw mode! * * @param mixed $data array of data to quote * * @return mixed * @access private */ function _quote($data) { if ($this->getOption('raw')) { return $data; } if ($this->_MDBversion == 2) { switch(gettype($data)) { case 'array': return $this->_quoteArray($data); default: return $this->db->quote($data); } } else { switch(gettype($data)) { case 'array': return $this->_quoteArray($data); case 'boolean': return $this->db->getBooleanValue($data); case 'double': return $this->db->getFloatValue($data); case 'integer': return $this->db->getIntegerValue($data); case 'string': //if 'string' or 'unknown', quote as text default: return $this->db->getTextValue($data); } } } // }}} // {{{ _checkColumns() /** * checks if the columns which are given as the array's indexes really exist * if not it will be unset anyway * * @param array $newData array of data to save * @param integer $method method name * * @return array * @access public */ function _checkColumns($newData, $method = 'unknown') { if (!$meta = $this->metadata()) { // if no metadata available, return data as given return $newData; } foreach ($newData as $colName => $x) { if (!isset($meta[$colName])) { $this->_errorLog("$method, column {$this->table}.$colName doesn't exist, value was removed before '$method'", __LINE__); unset($newData[$colName]); } else { // if the current column exists, check the length too, not to write content that is too long // prevent DB-errors here // do only check the data length if this field is given if (isset($meta[$colName]['len']) && ($meta[$colName]['len'] != -1) && (($oldLength = strlen($newData[$colName])) > $meta[$colName]['len']) && !is_numeric($newData[$colName]) ) { $this->_errorLog("_checkColumns, had to trim column '$colName' from $oldLength to ". $meta[$colName]['len'].' characters.', __LINE__); $newData[$colName] = substr($newData[$colName], 0, $meta[$colName]['len']); } } } return $newData; } // }}} // {{{ debug() /** * overwrite this method and i.e. print the query $string * to see the final query * * @param string $string the query mostly * * @return void * @access public */ function debug($string) { } // // // ONLY ORACLE SPECIFIC, not very nice since it is DB dependent, but we need it!!! // // // }}} // {{{ metadata() /** * !!!! query COPIED FROM db_oci8.inc - from PHPLIB !!!! * * @param string $table table to get the metadata for * * @return resultSet or false on error * @see db_oci8.inc - PHPLIB * @access public */ function metadata($table = '') { // is there an alias in the table name, then we have something like this: 'user ua' // cut of the alias and return the table name if (strpos($table, ' ') !== false) { $split = explode(' ', trim($table)); $table = $split[0]; } $full = false; if (empty($table)) { $table = $this->table; } // to prevent multiple selects for the same metadata if (isset($this->_metadata[$table])) { return $this->_metadata[$table]; } // FIXXXME use oci8 implementation of newer PEAR::DB-version if ($this->db->phptype == 'oci8') { $count = 0; $id = 0; $res = array(); //# This is a RIGHT OUTER JOIN: "(+)", if you want to see, what //# this query results try the following: //// $table = new Table; $this->db = new my_DB_Sql; // you have to make //// // your own class //// $table->show_results($this->db->query(see query vvvvvv)) //// $query = "SELECT T.column_name, T.table_name, T.data_type, T.data_length, T.data_precision, T.data_scale, T.nullable, T.char_col_decl_length, I.index_name FROM ALL_TAB_COLUMNS T, ALL_IND_COLUMNS I WHERE T.column_name = I.column_name (+) AND T.table_name = I.table_name (+) AND T.table_name = UPPER('$table') ORDER BY T.column_id"; $res = $this->db->queryAll($query); if (PEAR::isError($res)) { //$this->_errorSet( $res->getMessage() ); // i think we only need to log here, since this method is never used // directly for the user's functionality, which means if it fails it // is most probably an appl error $this->_errorLog($res->getUserInfo($res)); return false; } foreach ($res as $key => $val) { $val = array_change_key_case($val, CASE_LOWER); $res[$key]['name'] = $val['column_name']; } } else { if (!is_object($this->db)) { return false; } if (PEAR::isError($this->db)) { return false; } if ($this->_MDBversion == 2) { $this->db->loadModule('Reverse'); $res = $this->db->reverse->tableInfo($table); } else { $res = $this->db->tableInfo($table); } if (PEAR::isError($res)) { $this->_errorSet($res->getUserInfo()); return false; } } $ret = array(); foreach ($res as $key => $val) { $ret[$val['name']] = $val; } if ($this->_MDBversion == 2) { if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { $ret = array_change_key_case($ret, $this->db->options['field_case']); } } else { if ($this->db->options['optimize'] == 'portability') { $ret = array_change_key_case($ret, CASE_LOWER); } } $this->_metadata[$table] = $ret; return $ret; } // }}} // // methods for building the query // // {{{ _prependTableName() /** * replace 'column' by 'table.column' if the column is defined for the table * * @param string $fieldlist list of comma-separated fields * @param string $table table name * * @return string $fieldlist * @see http://pear.php.net/bugs/bug.php?id=9734 * @access private */ function _prependTableName($fieldlist, $table) { if (!$meta = $this->metadata($table)) { return $fieldlist; } $fields = explode(',', $fieldlist); foreach (array_keys($meta) as $column) { //$fieldlist = preg_replace('/(^\s*|\s+|,)'.$column.'\s*(,)?/U', "$1{$table}.$column$2", $fieldlist); $pattern = '/^'.$column.'\b.*/U'; foreach (array_keys($fields) as $k) { $fields[$k] = trim($fields[$k]); if (!strpos($fields[$k], '.') && preg_match($pattern, $fields[$k])) { $fields[$k] = $table.'.'.$fields[$k]; } } } return implode(',', $fields); } // }}} // {{{ _buildFrom() /** * build the from string * * @return string the string added after FROM * @access private */ function _buildFrom() { $this_table = $from = $this->_quoteIdentifier($this->table); $join = $this->getJoin(); if (!$join) { // no join set return $from; } // handle the standard join thingy if (isset($join['default']) && count($join['default'])) { foreach (array_keys($join['default']) as $joined_tbl) { $from .= ','.$this->_quoteIdentifier($joined_tbl); } } // handle left/right/inner joins foreach (array('left', 'right', 'inner') as $joinType) { if (isset($join[$joinType]) && count($join[$joinType])) { foreach ($join[$joinType] as $table => $condition) { // replace the _TABLENAME_COLUMNNAME by TABLENAME.COLUMNNAME // since oracle doesnt work with the _TABLENAME_COLUMNNAME which // I think is strange // FIXXME i think this should become deprecated since the // setWhere should not be used like this: '_table_column' but 'table.column' $regExp = '/_('.$table.')_([^\s]+)/'; $where = preg_replace($regExp, '$1.$2', $condition); // add the table name before any column that has no table prefix // since this might cause "unambiguous column" errors if ($meta = $this->metadata()) { foreach ($meta as $aCol=>$x) { // this covers the LIKE,IN stuff: 'name LIKE "%you%"' 'id IN (2,3,4,5)' $condition = preg_replace('/\s'.$aCol.'\s/', " {$this->table}.$aCol ", $condition); // replace also the column names which are behind a '=' // and do this also if the aCol is at the end of the where clause // that's what the $ is for $condition = preg_replace('/=\s*'.$aCol.'(\s|$)/', "={$this->table}.$aCol ", $condition); // replace if colName is first and possibly also if at the beginning of the where-string $condition = preg_replace('/(^\s*|\s+)'.$aCol.'\s*=/', "$1{$this->table}.$aCol=", $condition); } } $from .= ' '.strtoupper($joinType).' JOIN '.$table.' ON '.$condition; } } } return $from; } // }}} // {{{ getTableShortName() /** * Gets the short name for a table * * get the short name for a table, this is needed to properly build the * 'AS' parts in the select query * * @param string $table the real table name * * @return string the table's short name * @access public */ function getTableShortName($table) { $tableSpec = $this->getTableSpec(false); if (isset($tableSpec[$table]['shortName']) && $tableSpec[$table]['shortName']) { //print "$table ... ".$tableSpec[$table]['shortName'].'
'; return $tableSpec[$table]['shortName']; } $possibleTableShortName = preg_replace($this->_tableNameToShortNamePreg, '', $table); //print "$table ... $possibleTableShortName
"; return $possibleTableShortName; } // }}} // {{{ getTableSpec() /** * gets the tableSpec either indexed by the short name or the name * returns the array for the tables given as parameter or if no * parameter given for all tables that exist in the tableSpec * * @param boolean $shortNameIndexed if true the table is returned indexed by the * shortName, otherwise indexed by the name * @param array $tables table names (not the short names!) * * @return array the tableSpec indexed * @access public */ function getTableSpec($shortNameIndexed = true, $tables = array()) { $newSpec = array(); foreach ($this->tableSpec as $aSpec) { if (0 == sizeof($tables) || in_array($aSpec['name'], $tables)) { if ($shortNameIndexed) { $newSpec[$aSpec['shortName']] = $aSpec; } else { $newSpec[$aSpec['name']] = $aSpec; } } } return $newSpec; } // }}} // {{{ _buildSelect() /** * build the 'SELECT
FROM ... 'for a select * * @param string $what if given use this string * * @return string the SELECT clause * @access private */ function _buildSelect($what = null) { // what has preference, that means if what is set it is used // this is only because the methods like 'get' pass an individually // built value, which is supposed to be used, but usually it's generically // built using the 'getSelect' values if (empty($what) && $this->getSelect()) { $what = $this->getSelect(); } // // replace all the '*' by the real column names, and take care of the // dontSelect-columns! // $dontSelect = $this->getDontSelect(); $dontSelect = $dontSelect ? explode(',', $dontSelect) : array(); // make sure dontSelect is an array // here we will replace all the '*' and 'table.*' by all the columns that this table // contains. we do this so we can easily apply the 'dontSelect' values. // and so we can also handle queries like: 'SELECT *,count() FROM ' and 'SELECT table.*,x FROM ' too if (strpos($what, '*') !== false) { // subpattern 1 get all the table names, that are written like this: 'table.*' including '*' // for '*' the tablename will be '' preg_match_all('/([^,]*)(\.)?\*\s*(,|$)/U', $what, $res); //print "$what ... "; print_r($res); print '
'; $selectAllFromTables = array_unique($res[1]); // make the table names unique, so we do it all just once for each table $tables = array(); if (in_array('', $selectAllFromTables)) { // was there a '*' ? // get all the tables that we need to process, depending on if joined or not $tables = $this->getJoin() ? array_merge($this->getJoin('tables'), array($this->table)) : // get the joined tables and this->table array($this->table); // create an array with only this->table } else { $tables = $selectAllFromTables; } $cols = array(); // go thru all the tables and get all columns for each, and handle 'dontSelect' foreach ($tables as $aTable) { if ($meta = $this->metadata($aTable)) { foreach ($meta as $colName => $x) { // handle the dontSelect's if (in_array($colName, $dontSelect) || in_array("$aTable.$colName", $dontSelect) ) { continue; } // build the AS clauses // put " around them to enable use of reserved words, // i.e. SELECT table.option as option FROM... // and 'option' actually is a reserved word, at least in mysql // but don't do this for ibase because it doesn't work! if ($aTable == $this->table) { if ($this->db->phptype == 'ibase') { $cols[$aTable][] = $this->table. '.' .$colName . ' AS '. $colName; } else { $cols[$aTable][] = $this->table. '.' .$colName . ' AS '. $this->_quoteIdentifier($colName); } } else { if ($this->db->phptype == 'ibase') { //$cols[$aTable][] = $aTable. '.' .$colName . ' AS _'. $this->getTableShortName($aTable) .'_'. $colName; //with ibase, don't quote aliases, //and prepend the joined table cols alias with "t_" //because an alias starting with just "_" triggers //an "invalid token" error $cols[$aTable][] = $aTable. '.' .$colName . ' AS t_'. $this->getTableShortName($aTable) .'_'. $colName; } else { $cols[$aTable][] = $aTable. '.' .$colName . ' AS '. $this->_quoteIdentifier('_'. $this->getTableShortName($aTable) .'_'. $colName); } } } } } // put the extracted select back in the $what // that means replace 'table.*' by the i.e. 'table.id AS _table_id' // or if it is the table of this class replace 'table.id AS id' if (in_array('', $selectAllFromTables)) { $allCols = array(); foreach ($cols as $aTable) { $allCols[] = implode(',', $aTable); } $what = preg_replace('/(^|,)\*($|,)/', '$1'.implode(',', $allCols).'$2', $what); // remove all the 'table.*' since we have selected all anyway (because there was a '*' in the select) $what = preg_replace('/[^,]*(\.)?\*\s*(,|$)/U', '', $what); } else { foreach ($cols as $tableName => $aTable) { if (is_array($aTable) && sizeof($aTable)) { // replace all the 'table.*' by their select of each column $what = preg_replace('/(^|,)\s*'.$tableName.'\.\*\s*($|,)/', '$1'.implode(',', $aTable).'$2', $what); } } } } if ($this->getJoin()) { // replace all 'column' by '$this->table.column' to prevent ambiguous errors $metadata = $this->metadata(); if (is_array($metadata)) { foreach ($metadata as $aCol => $x) { // handle ',id as xid,MAX(id),id' etc. // FIXXME do this better!!! $what = preg_replace("/(^|,|\()(\s*)$aCol(\)|\s|,|as|$)/i", // $2 is actually just to keep the spaces, is not really // necessary, but this way the test works independent of this functionality here "$1$2{$this->table}.$aCol$3", $what ); } } // replace all 'joinedTable.columnName' by '_joinedTable_columnName' // this actually only has an effect if there was no 'table.*' for 'table' // if that was there, then it has already been done before foreach ($this->getJoin('tables') as $aTable) { if ($meta = $this->metadata($aTable)) { foreach ($meta as $aCol => $x) { // don't put the 'AS' behind it if there is already one if (preg_match("/$aTable.$aCol\s*as/i", $what)) { continue; } // this covers a ' table.colName ' surrounded by spaces, and replaces it by ' table.colName AS _table_colName' $what = preg_replace('/\s'.$aTable.'.'.$aCol.'\s/', " $aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol ", $what); // replace also the column names which are behind a ',' // and do this also if the aCol is at the end that's what the $ is for $what = preg_replace('/,\s*'.$aTable.'.'.$aCol.'(,|\s|$)/', ",$aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol$1", $what); // replace if colName is first and possibly also if at the beginning of the where-string $what = preg_replace('/(^\s*|\s+)'.$aTable.'.'.$aCol.'\s*,/', "$1$aTable.$aCol AS _".$this->getTableShortName($aTable)."_$aCol,", $what); } } } } // quotations of columns $columns = explode(',', $what); $identifier = substr($this->_quoteIdentifier(''), 0, 1); for ($i=0; $i
_quoteIdentifier($column[1]); } $n_cols = count($column); if ($n_cols > 2) { //multiple "AS", Example: CAST(col AS type) AS alias $open_parentheses = substr_count($column[0], '(') - substr_count($column[0], ')'); $open_parentheses += substr_count($column[1], '(') - substr_count($column[1], ')'); for ($k=2; $k<$n_cols; $k++) { if (!$open_parentheses) { $column[$k] = $this->_quoteIdentifier($column[$k]); } $open_parentheses += substr_count($column[$k], '(') - substr_count($column[$k], ')'); } } } elseif (strpos($column[0], '.') !== false) { $column[0] = explode('.', $column[0]); $column[0][1] = $this->_quoteIdentifier($column[0][1]); $column[0] = implode('.', $column[0]); } else { $column[0] = $this->_quoteIdentifier($column[0]); } $column = implode(' AS ', $column); } else { if ((strpos($column, '(') !== false) || (strpos($column, ')') !== false)) { //do not quote function calls, COUNT(), etc. } elseif (strpos($column, '.') !== false) { $column = explode('.', $column); $column[1] = $this->_quoteIdentifier($column[1]); $column = implode('.', $column); } else { $column = $this->_quoteIdentifier($column); } } // Clean up if a function was used in the query if (substr($column, -2) == ')'.$identifier) { $column = substr($column, 0, -2).$identifier.')'; // Some like spaces in the function while (strpos($column, ' '.$identifier) !== false) { $column = str_replace(' '.$identifier, $identifier.' ', $column); } } $columns[$i] = $column; } return implode(',', $columns); } // }}} // {{{ _buildWhere() /** * Build WHERE clause * * @param string $where WHERE clause * * @return string $where WHERE clause after processing * @access private */ function _buildWhere($where='') { $where = trim($where); $originalWhere = $this->getWhere(); if ($originalWhere) { if (!empty($where)) { $where = $originalWhere.' AND '.$where; } else { $where = $originalWhere; } } $where = trim($where); if ($join = $this->getJoin()) { // is join set? // only those where conditions in the default-join have to be added here // left-join conditions are added behind 'ON', the '_buildJoin()' does that if (isset($join['default']) && count($join['default'])) { // we have to add this join-where clause here // since at least in mysql a query like: select * from tableX JOIN tableY ON ... // doesnt work, may be that's even SQL-standard... if (!empty($where)) { $where = implode(' AND ', $join['default']).' AND '.$where; } else { $where = implode(' AND ', $join['default']); } } // replace the _TABLENAME_COLUMNNAME by TABLENAME.COLUMNNAME // since oracle doesnt work with the _TABLENAME_COLUMNNAME which i think is strange // FIXXME i think this should become deprecated since the setWhere should not be used like this: '_table_column' but 'table.column' $regExp = '/_('.implode('|', $this->getJoin('tables')).')_([^\s]+)/'; $where = preg_replace($regExp, '$1.$2', $where); // add the table name before any column that has no table prefix // since this might cause "unambigious column" errors if ($meta = $this->metadata()) { foreach ($meta as $aCol => $x) { // this covers the LIKE,IN stuff: 'name LIKE "%you%"' 'id IN (2,3,4,5)' $where = preg_replace('/\s'.$aCol.'\s/', " {$this->table}.$aCol ", $where); // replace also the column names which are behind a '=' // and do this also if the aCol is at the end of the where clause // that's what the $ is for $where = preg_replace('/([=<>])\s*'.$aCol.'(\s|$)/', "$1{$this->table}.$aCol ", $where); // replace if colName is first and possibly also if at the beginning of the where-string $where = preg_replace('/(^\s*|\s+)'.$aCol.'\s*([=<>])/', "$1{$this->table}.$aCol$2", $where); } } } return $where; } // }}} // {{{ _buildOrder() /** * Build the "ORDER BY" clause, replace 'column' by 'table.column'. * * @return string the rendered "ORDER BY" clause * @access private */ function _buildOrder() { return $this->_prependTableName($this->getOrder(), $this->table); } // }}} // {{{ _buildGroup() /** * Build the "GROUP BY" clause, replace 'column' by 'table.column'. * * @return string the rendered "GROUP BY" clause * @access private */ function _buildGroup() { return $this->_prependTableName($this->getGroup(), $this->table); } // }}} // {{{ _buildHaving() /** * Build the "HAVING" clause, replace 'column' by 'table.column'. * * @return string the rendered "HAVING" clause * @access private */ function _buildHaving() { return $this->_prependTableName($this->getHaving(), $this->table); } // }}} // {{{ _buildSelectQuery() /** * Build the "SELECT" query * * @param array $query this array contains the elements of * the query, indexed by their key, * which are: 'select','from','where', etc. * @param boolean $isCalledViaGetCount whether this method is called via getCount() * * @return string $querystring or false on error * @access private */ function _buildSelectQuery($query=array(), $isCalledViaGetCount = false) { $where = isset($query['where']) ? $query['where'] : $this->_buildWhere(); if ($where) { $where = 'WHERE '.$where; } $order = isset($query['order']) ? $query['order'] : $this->_buildOrder(); if ($order) { $order = 'ORDER BY '.$order; } $group = isset($query['group']) ? $query['group'] : $this->_buildGroup(); if ($group) { $group = 'GROUP BY '.$group; } $having = isset($query['having']) ? $query['having'] : $this->_buildHaving(); if ($having) { $having = 'HAVING '.$having; } $queryString = sprintf( 'SELECT %s FROM %s %s %s %s %s', isset($query['select']) ? $query['select'] : $this->_buildSelect(), isset($query['from']) ? $query['from'] : $this->_buildFrom(), $where, $group, $having, $order ); // $query['limit'] has preference! $limit = isset($query['limit']) ? $query['limit'] : $this->_limit; if (!$isCalledViaGetCount && !empty($limit[1])) { // is there a count set? if ($this->_MDBversion == 2) { $func = 'setLimit'; $error = $this->db->setLimit($limit[1], $limit[0]); } else { $func = 'setSelectedRowRange'; $error = $this->db->setSelectedRowRange($limit[0], $limit[1]); } if (PEAR::isError($error)) { $this->_errorSet('MDB_QueryTool_Common::_buildSelectQuery '.$func.' failed '.$error->getMessage()); $this->_errorLog($error->getUserInfo()); return false; } } return $queryString; } // }}} // {{{ _buildUpdateQuery() /** * this simply builds an update query. * * @param array $query the parameter array might contain the following indexes * - 'where': the where clause to be added, i.e. * UPDATE table SET x=1 WHERE y=0 * here the 'where' part simply would be 'y=0' * - 'set': the actual data to be updated in the example above, * that would be 'x=1' * * @return string the resulting query * @access private */ function _buildUpdateQuery($query = array()) { $where = isset($query['where']) ? $query['where'] : $this->_buildWhere(); if ($where) { $where = 'WHERE '.$where; } $updateString = sprintf('UPDATE %s SET %s %s', $this->table, $query['set'], $where ); return $updateString; } // }}} // {{{ execute() /** * Execute the query * * @param string $query querystring * @param string $method method * * @return resultSet or false on error * @access public */ function execute($query = null, $method = 'queryAll') { $this->writeLog(); if (is_null($query)) { $query = $this->_buildSelectQuery(); } $this->writeLog('query built: '.$query); // FIXXME on ORACLE this doesn't work, since we return joined columns as // _TABLE_COLNAME and the _ in front doesn't work on oracle, add a letter before it!!! $this->_lastQuery = $query; $this->debug($query); $this->writeLog('start query'); if ((substr($method, 0, 3) == 'get') && ($method != 'getAssoc')) { $method = 'query'.substr($method, 3); } if (PEAR::isError($res = $this->db->$method($query))) { $this->writeLog('end query (failed)'); if ($this->getOption('verbose')) { //$this->_errorSet($this->db->errorMessage($res->getCode())); $this->_errorSet($res->getMessage() .'-'. $res->getUserInfo()); } else { //$this->_errorLog($this->db->errorMessage($res->getCode())); $this->_errorLog($res->getMessage()); } $this->_errorLog($res->getUserInfo(), __LINE__); return false; } else { $this->writeLog('end query'); } return $this->_makeIndexed($res); } // }}} // {{{ writeLog() /** * Write events to the logfile. * * It does some additional work, like time measuring etc. to * see some additional info * * @param string $text log message * * @return void * @access public */ function writeLog($text = 'START') { //it's still really a quicky.... 'refactor' (nice word) that if (!isset($this->options['logfile'])) { return; } include_once 'Log.php'; if (!class_exists('Log')) { return; } if (!$this->_logObject) { $this->_logObject =& Log::factory('file', $this->options['logfile']); } if ($text === 'start query' || $text === 'end query') { $bytesSent = $this->db->queryAll("SHOW STATUS like 'Bytes_sent'"); $bytesSent = $bytesSent[0]['Value']; } if ($text === 'START') { $startTime = explode(' ', microtime()); $this->_logData['startTime'] = $startTime[1] + $startTime[0]; } if ($text === 'start query') { $this->_logData['startBytesSent'] = $bytesSent; $startTime = explode(' ', microtime()); $this->_logData['startQueryTime'] = $startTime[1] + $startTime[0]; return; } if ($text === 'end query') { $text .= ' result size: '.((int)$bytesSent-(int)$this->_logData['startBytesSent']).' bytes'; $endTime = explode(' ', microtime()); $endTime = $endTime[1] + $endTime[0]; $text .= ', took: '.(($endTime - $this->_logData['startQueryTime'])).' seconds'; } if (strpos($text, 'query built') === 0) { $endTime = explode(' ', microtime()); $endTime = $endTime[1] + $endTime[0]; $this->writeLog('query building took: '.(($endTime - $this->_logData['startTime'])).' seconds'); } $this->_logObject->log($text); if (strpos($text, 'end query') === 0) { $endTime = explode(' ', microtime()); $endTime = $endTime[1] + $endTime[0]; $text = 'time over all: '.(($endTime - $this->_logData['startTime'])).' seconds'; $this->_logObject->log($text); } } // }}} // {{{ returnResult() /** * Return the chosen result type * * @param object $result object reference * * @return mixed [boolean, array or object] * @access public */ function returnResult($result) { if ('none' == $this->_resultType) { return $result; } if (false === $result) { return false; } //what about allowing other (custom) result types? switch (strtolower($this->_resultType)) { case 'object': return new MDB_QueryTool_Result_Object($result, $this); case 'array': default: return new MDB_QueryTool_Result($result); } } // }}} // {{{ setReturnClass() /** * Sets the name of the class to use as result object * * @param string $name class name * * @return boolean true on success, false otherwise * @access public */ function setReturnClass($name) { $test = new $name(array(), $this); if (is_a($test, 'MDB_QueryTool_Result_Row')) { $this->_returnclass = $name; return true; } return false; } // }}} // {{{ _makeIndexed() /** * Make data indexed * * @param mixed &$data data to index * * @return mixed $data or array $indexedData * @access public */ function &_makeIndexed(&$data) { // we can only return an indexed result if the result has a number of columns if (is_array($data) && sizeof($data) && $key=$this->getIndex()) { // build the string to evaluate which might be made up out of multiple indexes of a result-row $evalString = '$val[\''.implode('\'].\',\'.$val[\'', explode(',', $key)) .'\']'; //" $indexedData = array(); //FIXXME actually we also need to check ONCE if $val is an array, // so to say if $data is 2-dimensional foreach ($data as $val) { // get the actual real (string-)key (string if multiple cols are used as index) eval("\$keyValue = $evalString;"); $indexedData[$keyValue] = $val; } unset($data); return $indexedData; } return $data; } // }}} // {{{ setIndex() /** * format the result to be indexed by $key * NOTE: be careful, when using this you should be aware, that if you * use an index which's value appears multiple times you may loose data * since a key cant exist multiple times!! * the result for a result to be indexed by a key(=columnName) * (i.e. 'relationtoMe') which's values are 'brother' and 'sister' * or alike normally returns this: * $res['brother'] = array('name'=>'xxx') * $res['sister'] = array('name'=>'xxx') * but if the column 'relationtoMe' contains multiple entries for 'brother' * then the returned dataset will only contain one brother, since the * value from the column 'relationtoMe' is used * and which 'brother' you get depends on a lot of things, like the sortorder, * how the db saves the data, and whatever else * * you can also set indexes which depend on 2 columns, simply pass the parameters like * 'table1.id,table2.id' it will be used as a string for indexing the result * and the index will be built using the 2 values given, so a possible * index might be '1,2' or '2108,29389' this way you can access data which * have 2 primary keys. Be sure to remember that the index is a string! * * @param string $key key to use as index * * @return void * @access public */ function setIndex($key=null) { if ($this->getJoin()) { // is join set? // replace TABLENAME.COLUMNNAME by _TABLENAME_COLUMNNAME // since this is only the result-keys can be used for indexing :-) $regExp = '/('. implode('|', $this->getJoin('tables')) .')\.([^\s]+)/'; $key = preg_replace($regExp, '_$1_$2', $key); // remove the table name if it is in front of '<$this->table>.columnname' // since the key doesnt contain it neither if ($meta = $this->metadata()) { foreach ($meta as $aCol => $x) { $key = preg_replace('/'.$this->table.'\.'.$aCol.'/', $aCol, $key); } } } $this->_index = $key; } // }}} // {{{ getIndex() /** * get index * * @return string index * @access public */ function getIndex() { return $this->_index; } // }}} // {{{ useResult() /** * Choose the type of the returned result * * @param string $type ['array' | 'object' | 'none'] * For BC reasons, $type=true is equal to 'array', * $type=false is equal to 'none' * * @return void * @access public */ function useResult($type = 'array') { if (true === $type) { $type = 'array'; } elseif (false === $type) { $type = 'none'; } switch (strtolower($type)) { case 'array': $this->_resultType = 'array'; include_once 'MDB/QueryTool/Result.php'; break; case 'object': $this->_resultType = 'object'; include_once 'MDB/QueryTool/Result/Object.php'; break; default: $this->_resultType = 'none'; } } // }}} // {{{ setErrorCallback() /** * set both callbacks * * @param string $param callback function name * * @return void * @access public */ function setErrorCallback($param = '') { $this->setErrorLogCallback($param); $this->setErrorSetCallback($param); } // }}} // {{{ setErrorLogCallback() /** * set error log callback * * @param string $param callback function name * * @return void */ function setErrorLogCallback($param = '') { $errorLogCallback = &PEAR::getStaticProperty('MDB_QueryTool', '_errorLogCallback'); $errorLogCallback = $param; } // }}} // {{{ setErrorSetCallback() /** * set error set callback * * @param string $param callback function name * * @return void */ function setErrorSetCallback($param = '') { $errorSetCallback = &PEAR::getStaticProperty('MDB_QueryTool', '_errorSetCallback'); $errorSetCallback = $param; } // }}} // {{{ _errorLog() /** * sets error log and adds additional info * * @param string $msg the actual message, first word should always be the method name, * to build the message like this: className::methodname * @param integer $line the line number * * @return void * @access private */ function _errorLog($msg, $line = 'unknown') { $this->_errorHandler('log', $msg, $line); /* if ($this->getOption('verbose') == true) { $this->_errorLog( get_class($this)."::$msg ($line)"); return; } if ($this->_errorLogCallback) { call_user_func( $this->_errorLogCallback , $msg ); } */ } // }}} // {{{ _errorSet() /** * set error handler * * @param string $msg the actual message * @param integer $line the line number * * @return void */ function _errorSet($msg, $line = 'unknown') { $this->_errorHandler('set', $msg, $line); } // }}} // {{{ _errorHandler() /** * set error handler * * @param string $logOrSet log or set * @param string $msg the actual message * @param integer $line the line number * * @return void */ function _errorHandler($logOrSet, $msg, $line = 'unknown') { /* what did i do this for? if ($this->getOption('verbose') == true) { $this->_errorHandler($logOrSet, get_class($this)."::$msg ($line)"); return; } */ $msg = get_class($this)."::$msg ($line)"; $logOrSet = ucfirst($logOrSet); $callback = &PEAR::getStaticProperty('MDB_QueryTool', '_error' . $logOrSet . 'Callback'); if ($callback) { call_user_func($callback, $msg); } //else { // ????? //} } // }}} // {{{ newEntity() /** * Returns a new entity including an instance to QueryTool * * @return new entity * @access public */ function newEntity() { if (strtolower($this->_resultType) == 'object') { $result = new MDB_QueryTool_Result_Object(array(), $this); return $result->newEntity(); } } // }}} } ?>