Current File : //opt/RZphp73/includes/DB/QueryTool/Query.php |
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains the DB_QueryTool_Query class
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2007 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: Query.php,v 1.77 2008/01/12 13:08:27 quipo Exp $
* @link http://pear.php.net/package/DB_QueryTool
*/
/**
* require the PEAR and DB classes
*/
require_once 'PEAR.php';
require_once 'DB.php';
/**
* DB_QueryTool_Query class
*
* This class should be extended
*
* @category Database
* @package DB_QueryTool
* @author Wolfram Kriesing <wk@visionp.de>
* @author Paolo Panto <wk@visionp.de>
* @author Lorenzo Alberton <l dot alberton at quipo dot it>
* @copyright 2003-2007 Wolfram Kriesing, Paolo Panto, Lorenzo Alberton
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @link http://pear.php.net/package/DB_QueryTool
*/
class DB_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::DB 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 string type of result to return
* @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();
// }}}
// {{{ __construct()
/**
* this is the constructor, as it will be implemented in ZE2 (php5)
*
* @param object $dsn db-object
* @param array $options options array
*
* @return void
* @version 2002/04/02
* @author Wolfram Kriesing <wk@visionp.de>
* @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);
}
//we would need to parse the dsn first ... i dont feel like now :-)
// oracle has all column names in upper case
//FIXXXME make the class work only with upper case when we work with oracle
//if ($this->db->phptype=='oci8' && !$this->primaryCol) {
// $this->primaryCol = 'ID';
//}
if (is_null($this->sequenceName)) {
$this->sequenceName = $this->table;
}
}
*/
// }}}
// {{{ DB_QueryTool_Query()
/**
* Constructor
*
* @param mixed $dsn DSN string, DSN array or DB object
* @param array $options database options
*
* @version 2002/04/02
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function DB_QueryTool_Query($dsn = false, $options = array())
{
//$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);
}
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 DB object
* @param array $options database options
*
* @return void
*/
function connect($dsn, $options = array())
{
if (is_object($dsn)) {
$res = $this->db =& $dsn;
} else {
$res = $this->db = DB::connect($dsn, $options);
}
if (PEAR::isError($res)) {
// FIXXME what shall we do here?
$this->_errorLog($res->getUserInfo());
} else {
$this->db->setFetchMode(DB_FETCHMODE_ASSOC);
}
}
// }}}
// {{{ getDbInstance()
/**
* Get the current DB instance
*
* @return reference to current DB instance
*/
function &getDbInstance()
{
return $this->db;
}
// }}}
// {{{ setDbInstance()
/**
* Setup using an existing connection.
* this also sets the DB_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->db->setFetchMode(DB_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
* @version 2002/03/05
* @author Wolfram Kriesing <wk@visionp.de>
* @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->_quoteIdentifier($this->table).'.'.$this->_quoteIdentifier($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
* @version 2002/04/23
* @author Wolfram Kriesing <wk@visionp.de>
* @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, 'getOne');
}
// }}}
// {{{ 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 DB-method to use, i dont know if we should leave this param here ...
*
* @return mixed an array of the retrieved data, or false in case of failure
* when failing an error is set in $this->_error
* @version 2002/03/05
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function getAll($from = 0, $count = 0, $method = 'getAll')
{
$query = array();
if ($count) {
$query = array('limit' => array($from, $count));
//echo '<br/>'.$this->_buildSelectQuery($query).'<br/>';
}
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
* @version 2003/02/25
* @author Wolfram Kriesing <wk@visionp.de>
* @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 ($res === false) ? 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
* @version 2002/04/02
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version2002/04/05
* @author Wolfram Kriesing <wk@visionp.de>
* @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 void
* @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 string $float fload value as string
*
* @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 seperator
}
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 string
* @param array $array array to implode
*
* @return string
* @access private
*/
function _localeSafeImplode($glue, $array)
{
$str = '';
foreach ($array as $value) {
if ($str !== '') {
$str .= $glue;
}
$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
* @version 2002/03/11
* @author Wolfram Kriesing <wk@visionp.de>
* @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 mixed true on success, or false otherwise
* @version 2002/03/06
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function update($newData)
{
$query = array();
$raw = $this->getOption('raw');
// 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])) {
$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);
//FIXXXME _buildUpdateQuery() seems to take joins into account, whcih is bullshit here
$updateString = $this->_buildUpdateQuery($query);
//print '$updateString = '.$updateString;
return $this->execute($updateString, 'query') ? true : false;
}
// }}}
// {{{ 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
* @version 2002/04/02
* @author Wolfram Kriesing <wk@visionp.de>
* @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->db->nextId($this->sequenceName);
$newData[$this->primaryCol] = (int)$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)
);
//echo $query;
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
* @version 2002/07/17
* @author Wolfram Kriesing <wk@visionp.de>
* @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
$retIds[] = $id = (int)$this->db->nextId($this->sequenceName);
$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;
} else {
$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
* @version 2002/04/08
* @author Wolfram Kriesing <wk@visionp.de>
* @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]
* @version 2002/06/17
* @author Wolfram Kriesing <wk@visionp.de>
* @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 name of the column containing the ids (default: PK)
*
* @return resultSet or false on error [execute() result]
* @version 2002/04/24
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2002/04/08
* @author Wolfram Kriesing <wk@visionp.de>
* @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;
}
}
return ($this->remove($id) ? true : false);
}
// }}}
// {{{ 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
* @version 2002/04/16
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function setWhere($whereCondition = '')
{
$this->_where = $whereCondition;
//FIXXME parse the where condition and replace ambigious column names,
// such as "name='Deutschland'" with "country.name='Deutschland'"
// then the users dont 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'
* @version 2002/04/22
* @author Wolfram Kriesing <wk@visionp.de>
* @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 $connectString the condition for how to concatenate the
* new WHERE clause to the existing one [default AND]
*
* @return void
* @version 2002/07/22
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function addWhere($where, $connectString = 'AND')
{
if ($this->getWhere()) {
$where = $this->getWhere().' '.$connectString.' '.$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
* LOWER(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
* @version 2002/08/14
* @author Wolfram Kriesing <wk@visionp.de>
* @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);
}
}
$string = $this->db->quoteSmart('%'.str_replace(' ', '%', strtolower($string)).'%');
$this->addWhere("LOWER($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
* @version 2002/05/16
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2003/05/28
* @author Wolfram Kriesing <wk@visionp.de>
* @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'
* @version 2002/05/16
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function getOrder()
{
return $this->_order;
}
// }}}
// {{{ setHaving()
/**
* sets the HAVING definition
*
* @param string $having the HAVING definition
*
* @return void
* @version 2003/06/05
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @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
* @version 2003/06/05
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @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 [default ' AND ']
*
* @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 string|array $table the table(s) to join on the current table
* @param string $where the where clause for the join
* @param string $joinType type of the table join
*
* @return void
* @version 2002/06/10
* @author Wolfram Kriesing <wk@visionp.de>
* @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 doenst 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
* @version 2002/07/22
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2002/09/04
* @author Wolfram Kriesing <wk@visionp.de>
* @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),'<where clause1> AND <where clause2>')
* you can also call
* setJoin(table1,'<where clause1>')
* addJoin(table2,'<where clause2>')
* 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,'<where clause1>')
* // results in ... FROM $this->table LEFT JOIN table ON <where clause1>
* addJoin(table2,'<where clause2>')
* // results in ... FROM $this->table,table2 LEFT JOIN table ON <where clause1> WHERE <where clause2>
*
* @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
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function setTable($table)
{
$this->table = $table;
}
// }}}
// {{{ getTable()
/**
* gets the table this class is currently working on
*
* @return string the table name
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function getTable()
{
return $this->table;
}
// }}}
// {{{ setGroup()
/**
* sets the group-by condition
*
* @param string $group the group condition
*
* @return void
* @version 2002/07/22
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2002/07/22
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* 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
* @version 2003/01/08
* @author Wolfram Kriesing <wk@visionp.de>
* @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()
/**
* Do not select the given column
*
* @param string $what column to ignore
*
* @return void
* @access public
*/
function setDontSelect($what = '')
{
$this->_dontSelect = $what;
}
// }}}
// {{{ getDontSelect()
/**
* Get the column(s) to be ignored
*
* @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 clauses to reset [select|dontSelect|group|having|limit
* |where|index|order|join|leftJoin|rightJoin]
*
* @return void
* @version 2002/09/16
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2002/09/17
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function setOption($option, $value)
{
$this->options[strtolower($option)] = $value;
}
// }}}
// {{{ getOption()
/**
* Get the option value
*
* @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 var to quote
*
* @return string quoted identifier
* @access private
*/
function _quoteIdentifier($var)
{
if (!$this->getOption('raw') && $this->db->phptype != 'ibase') {
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 data to quote
*
* @return array
* @access private
*/
function _quoteArray($data)
{
if (!$this->getOption('raw')) {
foreach ($data as $key => $val) {
$data[$key] = $this->db->quoteSmart($val);
}
}
return $data;
}
// }}}
// {{{ _quote()
/**
* quotes all the data in this array|string if we are not in raw mode!
*
* @param mixed $data data to quote
*
* @return mixed
* @access private
*/
function _quote($data)
{
if ($this->getOption('raw')) {
return $data;
}
switch (gettype($data)) {
case 'array':
return $this->_quoteArray($data);
default:
return $this->db->quoteSmart($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 data whose keys needs checking
* @param string $method method name
*
* @return array
* @version 2002/04/16
* @author Wolfram Kriesing <wk@visionp.de>
* @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]['DATA_LENGTH'].' 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 name
*
* @return resultSet or false on error
* @version 2001/09
* @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->getAll($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());
return false;
}
foreach ($res as $key => $val) {
$res[$key]['name'] = $val['COLUMN_NAME'];
}
} else {
if (!is_object($this->db)) {
return false;
}
$res = $this->db->tableinfo($table);
if (PEAR::isError($res)) {
//var_dump($res);
//echo '<div style="border:1px solid red; background: yellow">'.$res->getUserInfo().'<br/><pre>'; print_r(debug_backtrace()); echo '</pre></div>';
$this->_errorSet($res->getUserInfo());
return false;
}
}
$ret = array();
foreach ($res as $key => $val) {
$ret[$val['name']] = $val;
}
$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 comma-separated field list
* @param string $table table name
*
* @return string
* @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] = $this->_quoteIdentifier($table).'.'.$this->_quoteIdentifier($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 doesn't 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 '.$this->_quoteIdentifier($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'].'<br />';
return $tableSpec[$table]['shortName'];
}
$possibleTableShortName = preg_replace($this->_tableNameToShortNamePreg, '', $table);
//print "$table ... $possibleTableShortName<br />";
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 <what> FROM ... 'for a select
*
* @param string $what if given use this string
*
* @return string the what-clause
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @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 build 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 (false !== strpos($what, '*')) {
// 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);
//echo '<pre>'; print "$what ... "; print_r($res); print "</pre><hr />";
$selectAllFromTables = array_unique($res[1]); // make the table names unique, so we do it all just once for each table
//echo '<pre>'; print "$what ... "; print_r($selectAllFromTables); print "</pre><hr />";
$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;
}
//echo '<br />'; print_r($tables);
$cols = array();
foreach ($tables as $aTable) { // go thru all the tables and get all columns for each, and handle 'dontSelect'
//echo '<br />$this->metadata('.$aTable.')';
//echo '<br /><pre>';var_dump($this->metadata($aTable)); echo '</pre>';
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) {
$cols[$aTable][] = $this->table. '.' .$colName . ' AS '. $this->_quoteIdentifier($colName);
} else {
//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
$short_alias = ($this->db->phptype == 'ibase' ? 't_' : '_') . $this->getTableShortName($aTable) .'_'. $colName;
$cols[$aTable][] = $aTable. '.' .$colName . ' AS '. $this->_quoteIdentifier($short_alias);
}
}
}
}
// 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<sizeof($columns); $i++) {
$column = trim($columns[$i]);
// Uppercasing "as"
$column = str_replace(' as ', ' AS ', $column);
if (strpos($column, ' AS ') !== false) {
$column = explode(' AS ', $column);
if ((strpos($column[0], '(') !== false) || (strpos($column[0], ')') !== false)) {
//do not quote function calls, COUNT(), etc.
} elseif (strpos($column[0], '.') !== false) {
$column[0] = explode('.', $column[0]);
$column[0][0] = $this->_quoteIdentifier($column[0][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[0] = $this->_quoteIdentifier($column[0]);
$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
* @version 2007/01/10
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @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
* @version 2007/01/10
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @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
* @version 2007/01/10
* @author Lorenzo Alberton <l.alberton@quipo.it>
* @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() or not
*
* @return string $querystring or false on error
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @access private
*/
function _buildSelectQuery($query = array(), $isCalledViaGetCount = false)
{
/*FIXXXME finish this
$cacheKey = md5(serialize(????));
if (isset($this->_queryCache[$cacheKey])) {
$this->_errorLog('using cached query',__LINE__);
return $this->_queryCache[$cacheKey];
}
*/
$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?
$queryString = $this->db->modifyLimitQuery($queryString, $limit[0], $limit[1]);
//echo '<pre>'; var_dump($queryString); echo '</pre>';
if (PEAR::isError($queryString)) {
$this->_errorSet('DB_QueryTool::db::modifyLimitQuery failed '.$queryString->getMessage());
$this->_errorLog($queryString->getUserInfo());
return false;
}
}
// $this->_queryCache[$cacheKey] = $queryString;
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 query to execute
* @param string $method method name
*
* @return resultSet or false on error
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @access public
*/
function execute($query = null, $method = 'getAll')
{
$this->writeLog();
if (is_null($query)) {
$query = $this->_buildSelectQuery();
}
$this->writeLog('query built: '.$query);
// FIXXME on ORACLE this doesnt work, since we return joined columns as _TABLE_COLNAME and the _ in front
// doesnt work on oracle, add a letter before it!!!
$this->_lastQuery = $query;
$this->debug($query);
$this->writeLog('start query');
if (PEAR::isError($res = $this->db->$method($query))) {
$this->writeLog('end query (failed)');
if ($this->getOption('verbose')) {
$this->_errorSet($res->getMessage());
} else {
$this->_errorLog($res->getMessage());
}
$this->_errorLog($res->getUserInfo(), __LINE__);
return false;
} else {
$this->writeLog('end query');
}
$res = $this->_makeIndexed($res);
return $res;
}
// }}}
// {{{ writeLog()
/**
* Write events to the logfile.
* It does some additional work, like time measuring etc. to
* see some additional info
*
* @param string $text text to log
*
* @return void
* @access public
*/
function writeLog($text = 'START')
{
//its 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->getAll("SHOW STATUS like 'Bytes_sent'");
$bytesSent = $bytesSent[0]['Value'];
}
if ($text === 'START') {
$startTime = split(' ', microtime());
$this->_logData['startTime'] = $startTime[1] + $startTime[0];
}
if ($text === 'start query') {
$this->_logData['startBytesSent'] = $bytesSent;
$startTime = split(' ', microtime());
$this->_logData['startQueryTime'] = $startTime[1] + $startTime[0];
return;
}
if ($text === 'end query') {
$text .= ' result size: '.((int)$bytesSent-(int)$this->_logData['startBytesSent']).' bytes';
$endTime = split(' ', microtime());
$endTime = $endTime[1] + $endTime[0];
$text .= ', took: '.(($endTime - $this->_logData['startQueryTime'])).' seconds';
}
if (strpos($text, 'query built') === 0) {
$endTime = split(' ', 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 = split(' ', 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]
* @version 2004/04/28
* @access public
*/
function returnResult($result)
{
if ($this->_resultType == 'none') {
return $result;
}
if ($result === false) {
return false;
}
//what about allowing other (custom) result types?
switch (strtolower($this->_resultType)) {
case 'object':
return new DB_QueryTool_Result_Object($result);
case 'array':
default:
return new DB_QueryTool_Result($result);
}
}
// }}}
// {{{ _makeIndexed()
/**
* Make the data indexed
*
* @param mixed &$data data
*
* @return mixed $data or array $indexedData
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @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 index key
*
* @return void
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @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 the index
*
* @return string index
* @version 2002/07/11
* @author Wolfram Kriesing <wk@visionp.de>
* @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
* @version 2004/04/28
* @access public
* @access public
*/
function useResult($type = 'array')
{
if ($type === true) {
$type = 'array';
} elseif ($type === false) {
$type = 'none';
}
switch (strtolower($type)) {
case 'array':
$this->_resultType = 'array';
include_once 'DB/QueryTool/Result.php';
break;
case 'object':
$this->_resultType = 'object';
include_once 'DB/QueryTool/Result/Object.php';
break;
default:
$this->_resultType = 'none';
}
}
// }}}
// {{{ setErrorCallback()
/**
* set both callbacks
*
* @param string $param callback
*
* @return void
* @access public
*/
function setErrorCallback($param = '')
{
$this->setErrorLogCallback($param);
$this->setErrorSetCallback($param);
}
// }}}
// {{{ setErrorLogCallback()
/**
* Set the name of the error log callback function
*
* @param string $param callback
*
* @return void
*/
function setErrorLogCallback($param = '')
{
$errorLogCallback = &PEAR::getStaticProperty('DB_QueryTool', '_errorLogCallback');
$errorLogCallback = $param;
}
// }}}
// {{{ setErrorSetCallback()
/**
* Set the name of the error log callback function
*
* @param string $param callback
*
* @return void
*/
function setErrorSetCallback($param = '')
{
$errorSetCallback = &PEAR::getStaticProperty('DB_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
* @version 2002/04/16
* @author Wolfram Kriesing <wk@visionp.de>
* @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 the error message and line
*
* @param string $msg message
* @param string $line line number
*
* @return void
*/
function _errorSet($msg, $line = 'unknown')
{
$this->_errorHandler('set', $msg, $line);
}
// }}}
// {{{ _errorHandler()
/**
* Set the error handler
*
* @param boolean $logOrSet whether to log or set
* @param string $msg message
* @param string $line 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('DB_QueryTool', '_error'.$logOrSet.'Callback');
//var_dump($callback);
//if ($callback)
// call_user_func($callback, $msg);
//else
// ?????
}
// }}}
}
?>