Source for file Query.php
Documentation is available at Query.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Wolfram Kriesing, Paolo Panto, vision:produktion <wk@visionp.de>
// +----------------------------------------------------------------------+
// $Id: Query.php,v 1.52 2004/10/13 16:24:47 quipo Exp $
* this class should be extended
* @author Wolfram Kriesing <wk@visionp.de>
* @var string the name of the primary column
* @var string the current table the class works on
* @var string the name of the sequence for this table
* @var object the db-object, a PEAR::Db-object instance
* @var string the where condition
* @var string the order condition
* @var string the having definition
* @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
* @var string which column to index the result by
* @var string the group-by clause
* @var string type of result to return
var $_resultType = 'none';
* @var array the metadata temporary saved
var $_metadata = array ();
* @var string the rows that shall be selected
* @var string the rows that shall not be selected
* @var array this array saves different modes in which this class works
* i.e. 'raw' means no quoting before saving/updating data
'verbose' => true , // set this to false in a productive environment
// it will produce error-logs if set to true
* this array contains information about the tables
* - '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'
* 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'
var $_tableNameToShortNamePreg = '/^.*_/';
* @var array this array caches queries that have already been built once
* to reduce the execution time
var $_queryCache = array ();
* The object that contains the log-instance
* Some internal data the logging needs
* this is the constructor, as it will be implemented in ZE2 (php5)
* @author Wolfram Kriesing <wk@visionp.de>
* @param object db-object
function __construct($dsn=false, $options=array())
if (!isset($options['autoConnect'])) {
$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 ($this->sequenceName == null) {
$this->sequenceName = $this->table;
// {{{ DB_QueryTool_Query()
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed $dsn DSN string, DSN array or DB object
//$this->__construct($dsn, $options);
if (!isset ($options['autoConnect'])) {
$autoConnect = $options['autoConnect'];
unset ($options['autoConnect']);
if (isset ($options['errorCallback'])) {
unset ($options['errorCallback']);
if (isset ($options['errorSetCallback'])) {
unset ($options['errorSetCallback']);
if (isset ($options['errorLogCallback'])) {
unset ($options['errorLogCallback']);
if ($autoConnect && $dsn) {
* use this method if you want to connect manually
* @param mixed $dsn DSN string, DSN array or MDB object
function connect($dsn, $options=array ())
$res = $this->db = & $dsn;
$res = $this->db = DB ::connect ($dsn, $options);
// FIXXME what shall we do here?
$this->db->setFetchMode (DB_FETCHMODE_ASSOC );
* @return reference to current DB instance
* Setup using an existing connection.
* this also sets the DB_FETCHMODE_ASSOC since this class
* @param object a reference to an existing DB-object
$this->db->setFetchMode (DB_FETCHMODE_ASSOC );
* 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!
* @author Wolfram Kriesing <wk@visionp.de>
* @param integer the id of the element to retreive
* @param string if this is given only one row shall be returned, directly, not an array
* @return mixed (1) an array of the retreived 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
function get($id, $column= '')
if ($column && !strpos($column, ',')) { // if only one column shall be selected
// 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['where'] = $this->_buildWhere ($this->table. '.'. $this->primaryCol. '='. $id);
* gets the data of the given ids
* @author Wolfram Kriesing <wk@visionp.de>
* @param array this is an array of ids to retreive
* @param string the column to search in for
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
// 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). ')');
* get all entries from the DB
* for sorting use setOrder!!!, the last 2 parameters are deprecated
* @author Wolfram Kriesing <wk@visionp.de>
* @param int to start from
* @param int the number of rows to show
* @param string the DB-method to use, i dont know if we should leave this param here ...
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
function getAll($from=0 ,$count=0 ,$method= 'getAll')
$query = array ('limit' => array ($from, $count));
* 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();
* $ids = $table->getCol('id');
* so ids will ba an array with all the id's
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the column that shall be retreived
* @param int to start from
* @param int the number of rows to show
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
function getCol($column=null , $from=0 , $count=0 )
// by using _buildSelect() i can be sure that the table name will not be ambigious
// 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['limit'] = array ($from,$count);
* get the number of entries
* @author Wolfram Kriesing <wk@visionp.de>
* @return mixed an array of the retreived data, or false in case of failure
* when failing an error is set in $this->_error
/* 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
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!!!
$query['select'] = 'COUNT(DISTINCT '. $group. ')';
$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 doenst use setSelect!!!
//$this->setSelect('count(*)');
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
* @author Wolfram Kriesing <wk@visionp.de>
* @return array an empty, or pre-initialized element
// 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) {
* Render the current query and return it as a string.
* @return string the current query
* 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
* 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.
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains the new data that shall be saved in the DB
* @return mixed the data returned by either add or update-method
return $this->add($data);
* update the member data of a data set
* @author Wolfram Kriesing <wk@visionp.de>
* @param array 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
// do only set the 'where' part in $query, if a primary column is given
// if not the default 'where' clause is used
foreach ($newData as $key => $aData) { // quote the data
//$values[] = "{$this->table}.$key=". ($raw ? $aData : $this->db->quote($aData));
$values[] = " $key=". ($raw ? $aData : $this->db->quote ($aData));
$query['set'] = implode(',', $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 a new member in the DB
* @author Wolfram Kriesing <wk@visionp.de>
* @param array contains the new data that shall be saved in the DB
* @return mixed the inserted id on success, or false otherwise
// if no primary col is given, get next sequence value
if ($this->primaryCol) { // do only use the sequence if a primary column is given
// otherwise the data are written as given
// if no primary col is given return true on success
//unset($newData[$this->primaryCol]);
$newData = $this->_quoteArray ($newData);
$query = sprintf( 'INSERT INTO %s (%s) VALUES (%s)',
return $this->execute($query, 'query') ? $id : false;
* adds multiple new members in the DB
* @author Wolfram Kriesing <wk@visionp.de>
* @param array 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
// the inserted ids which will be returned or if no primaryCol is given
// we return true by default
$allData = array (); // each row that will be inserted
foreach ($data as $key => $aData) {
$aData = $this->_quoteArray ($aData);
if ($this->primaryCol) { // do only use the sequence if a primary column is given
// otherwise the data are written as given
$allData[] = '('. implode(', ', $aData). ')';
$query = sprintf( 'INSERT INTO %s (%s) VALUES %s',
return $this->execute($query, 'query') ? $retIds : false;
* removes a member from the DB
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed integer/string - the value of the column that shall be removed
* array - multiple columns that shall be matched, the second parameter will be ommited
* @param string the column to match the data against, only if $data is not an array
function remove($data, $whereCol= '')
//FIXXME check $data if it only contains columns that really exist in the table
foreach ($data as $key => $val) {
$wheres[] = $key. '='. ($raw ? $val : $this->db->quote ($val));
$whereClause = implode(' AND ',$wheres);
$whereClause = $whereCol. '='. ($raw ? $data : $this->db->quote ($data));
$query = sprintf( 'DELETE FROM %s WHERE %s',
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
* @author Wolfram Kriesing <wk@visionp.de>
$query = 'DELETE FROM '. $this->table;
return $this->execute($query, 'query') ? true : false;
* remove the datasets with the given ids
* @author Wolfram Kriesing <wk@visionp.de>
* @param array the ids to remove
$ids = $this->_quoteArray ($ids);
$query = sprintf( 'DELETE FROM %s WHERE %s IN (%s)',
return $this->execute($query, 'query') ? true : false;
* 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
* @author Wolfram Kriesing <wk@visionp.de>
* @param integer the value of the primary key
* @param string the column name of the tables with the foreign keys
* @param object just for convinience, so nobody forgets to call this method
* with at least one object as a parameter
$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
//FIXXXME let $object also simply be a table name
if (!$object->remove ($id, $colName)) {
$this->_errorSet (" Error removing '$colName=$id' from table {$object->table }." );
return ($this->remove($id) ? true : false );
if ($from==0 && $count==0 ) {
$this->_limit = array ($from, $count);
* sets the where condition which is used for the current instance
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
$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');
* gets the where condition which is used for the current instance
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the where condition, this can be complete like 'X=7 AND Y=8'
* only adds a string to the where clause
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where clause to add to the existing one
* @param string the condition for how to concatenate the new where clause
function addWhere($where, $condition= 'AND')
$where = $this->getWhere(). ' '. $condition. ' '. $where;
* 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
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the column to search in for
* @param string the string to search for
// if the column doesnt contain a tablename use the current table name in case it is a defined column
// to prevent ambigious rows
if (strpos($column, '.') === false ) {
if (isset ($meta[$column])) {
$column = $this->table." .$column";
$this->addWhere(" LOWER($column) LIKE $string" , $condition);
* sets the order condition which is used for the current instance
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
* @param boolean sorting order (TRUE => ASC, FALSE => DESC)
function setOrder($orderCondition= '', $desc=false )
$this->_order = $orderCondition . ($desc ? ' DESC' : '');
* Add a order parameter to the query.
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the where condition, this can be complete like 'X=7 AND Y=8'
* @param boolean sorting order (TRUE => ASC, FALSE => DESC)
function addOrder($orderCondition= '', $desc=false )
$order = $orderCondition . ($desc ? ' DESC' : '');
$this->_order = $this->_order. ','. $order;
* gets the order condition which is used for the current instance
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the order condition, this can be complete like 'ID,TIMESTAMP DESC'
* sets the having definition
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @param string the having definition
$this->_having = $having;
* gets the having definition which is used for the current instance
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @return string the having definition
* Extend the current having clause. This is very useful, when you are building
* this clause from different places and dont want to overwrite the currently
* set having clause, but extend it.
* @param string this is a having clause, i.e. 'column' or 'table.column' or 'MAX(column)'
* @param string the connection string, which usually stays the default, which is ',' (a comma)
function addHaving($what= '*', $connectString= ' AND ')
$this->_having = $this->_having. $connectString. $what;
* @author Wolfram Kriesing <wk@visionp.de>
* @param mixed either a string or an array that contains
* the table(s) to join on the current table
* @param string the where clause for the join
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
// 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 ();
/* 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;
* 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
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table(s) to be left-joined
* @param string the where clause for the join
$this->setJoin($table, $where, 'left');
* @param string the table to be left-joined
* @param string the where clause for the join
* @param string the join type
// init value, to prevent E_ALL-warning
if (!isset ($this->_join[$type]) || !$this->_join[$type]) {
$this->_join[$type] = array ();
$this->_join[$type][$table] = $where;
* 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 doesnt allow the use of both
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table(s) to be right-joined
* @param string the where clause for the join
$this->setJoin($table, $where, 'right');
* gets the join-condition
* @param string [null|''|'table'|'tables'|'right'|'left']
* @return array gets the join parameters
// if the user requests all the join data or if the join is empty, return it
if (is_null($what) || empty ($this->_join)) {
foreach ($this->_join as $aJoin) {
case 'right': // return right-join data only
case 'left': // return left join data only
if (count($this->_join[$what])) {
* adds a table and a where clause that shall be used for the join
* setJoin(array(table1,table2),'<where clause1> AND <where clause2>')
* 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 the table to be joined
* @param string the where clause for the join
* @param string the join type
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 (!isset ($this->_join[$type]) || !$this->_join[$type]) {
$this->_join[$type] = array ();
$this->_join[$type][$table] = $where;
* sets the table this class is currently working on
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the table name
* gets the table this class is currently working on
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the table name
* sets the group-by condition
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the group condition
//FIXXME parse the 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');
* gets the group condition which is used for the current instance
* @author Wolfram Kriesing <wk@visionp.de>
* @return string the group condition
* limit the result to return only the columns given in $what
* @param string fields that shall be selected
* 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)
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the string that shall be added to the select-part
* @param string the string to connect the new string with the existing one
function addSelect($what= '*', $connectString= ',')
// if the select string is not empty add the string, otherwise simply set it
$this->_select = $this->_select. $connectString. $what;
$this->_dontSelect = $what;
return $this->_dontSelect;
* reset all the set* settings; with no parameter given, it resets them all
* @author Wolfram Kriesing <wk@visionp.de>
function reset($what=array ())
foreach ($what as $aReset) {
* set mode the class shall work in
* currently we have the modes:
* 'raw' does not quote the data before building the query
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the mode to be set
* @param mixed the value of the mode
* quotes all the data in this array if we are not in raw mode!
function _quoteArray ($data)
foreach ($data as $key => $val) {
$data[$key] = $this->db->quote ($val);
* checks if the columns which are given as the array's indexes really exist
* if not it will be unset anyway
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the actual message, first word should always be the method name,
* to build the message like this: className::methodname
* @param integer the line number
if (!$meta = $this->metadata()) { // if no metadata available, return data as given
foreach ($newData as $colName => $x) {
if (!isset ($meta[$colName])) {
$this->_errorLog(" $method, column {$this->table }. $colName doesnt exist, value was removed before ' $method' ",__LINE__);
unset($newData[$colName]);
// 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
// FIXXME use PEAR-defined field for 'DATA_LENGTH'
if (isset($meta[$colName]['DATA_LENGTH']) &&
($oldLength=strlen($newData[$colName])) > $meta[$colName]['DATA_LENGTH']) {
$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]['DATA_LENGTH']);
* overwrite this method and i.e. print the query $string
* @param string the query mostly
function debug($string){}
// ONLY ORACLE SPECIFIC, not very nice since it is DB dependent, but we need it!!!
* !!!! query COPIED FROM db_oci8.inc - from PHPLIB !!!!
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));
// 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') {
//# 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
//// $table->show_results($this->db->query(see query vvvvvv))
$res = $this->db ->getAll ("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");
//$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 ());
foreach ($res as $key=>$val) {
$res[$key]['name'] = $val['COLUMN_NAME'];
if (!is_object($this->db )) {
$res = $this->db ->tableinfo ($table);
$this->_errorSet ($res->getUserInfo ());
foreach ($res as $key => $val) {
$ret[$val['name']] = $val;
$this->_metadata [$table] = $ret;
// methods for building the query
* @return string the string added after FROM
$join = $this->getJoin ();
if (!$join) { // no join set
// handle the standard join thingy
if (isset($join['default']) && count($join['default'])) {
$from .= ','.implode(',',array_keys($join['default']));
// handle left/right joins
foreach (array('left', 'right') 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;
// {{{ getTableShortName()
* this method 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 the real table name
* @return string the table's short name
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;
* 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 array table names (not the short names!)
* @param boolean if true the table is returned indexed by the shortName
* otherwise indexed by the name
* @return array the tableSpec indexed
function getTableSpec($shortNameIndexed=true, $tables=array())
foreach ($this->tableSpec as $aSpec) {
if (sizeof($tables)==0 || in_array($aSpec['name'],$tables)) {
$newSpec[$aSpec['shortName']] = $aSpec;
$newSpec[$aSpec['name']] = $aSpec;
* build the 'SELECT <what> FROM ... 'for a select
* @author Wolfram Kriesing <wk@visionp.de>
* @param string if given use this string
* @return string the what-clause
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 (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 "<br>";
$selectAllFromTables = array_unique($res[1]); // make the table names unique, so we do it all just once for each table
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
$tables = $selectAllFromTables;
foreach ($tables as $aTable) { // go thru all the tables and get all columns for each, and handle 'dontSelect'
if ($meta = $this->metadata ($aTable)) {
foreach ($meta as $colName => $x) {
// handle the dontSelect's
if (in_array($colName, $dontSelect) || in_array("$aTable.$colName", $dontSelect)) {
// 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
// put double quotes around them, since pgsql doesnt work with single quotes
if ($aTable == $this->table ) {
$cols[$aTable][] = $this->table. ".$colName AS \"$colName\"";
$cols[$aTable][] = "$aTable.$colName AS \"_".$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)) {
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);
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);
// replace all 'column' by '$this->table.column' to prevent ambigious 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",
// 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) {
// dont put the 'AS' behind it if there is already one
if (preg_match("/$aTable.$aCol\s*as/i",$what)) {
// 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);
* @param string $where WHERE clause
* @return string $where WHERE clause after processing
function _buildWhere($where='')
$originalWhere = $this->getWhere ();
$where = $originalWhere.' AND '.$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...
$where = implode(' AND ', $join['default']).' AND '.$where;
$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);
* @author Wolfram Kriesing <wk@visionp.de>
$order = $this->getOrder ();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata ()) {
foreach ($meta as $aCol=>$x) {
$order = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U', "$1{ $this->table }.$aCol$2", $order);
* Build the group-clause, replace 'column' by 'table.column'.
* @return string the rendered group clause
$group = $this->getGroup ();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata ()) {
foreach ($meta as $aCol => $x) {
$group = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U', "$1{ $this->table }.$aCol$2", $group);
* @author Johannes Schaefer <johnschaefer@gmx.de>
* @return string the having clause
$having = $this->getHaving ();
// replace 'column' by '$this->table.column' if the column is defined for $this->table
if ($meta = $this->metadata ()) {
foreach ($meta as $aCol => $x) {
$having = preg_replace('/(^\s*|\s+|,)'.$aCol.'\s*(,)?/U',"$1{ $this->table }.$aCol$2",$having);
* @author Wolfram Kriesing <wk@visionp.de>
* @param array this array contains the elements of the query,
* indexed by their key, which are: 'select','from','where', etc.
function _buildSelectQuery($query=array())
$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 ();
$where = 'WHERE '.$where;
$order = isset($query['order']) ? $query['order'] : $this->_buildOrder ();
$order = 'ORDER BY '.$order;
$group = isset($query['group']) ? $query['group'] : $this->_buildGroup ();
$group = 'GROUP BY '.$group;
$having = isset($query['having']) ? $query['having'] : $this->_buildHaving ();
$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 (),
// $query['limit'] has preference!
$limit = isset ($query['limit']) ? $query['limit'] : $this->_limit;
if (@$limit[1 ]) { // is there a count set?
$queryString=$this->db ->modifyLimitQuery ($queryString,$limit[0 ],$limit[1 ]);
if (DB ::isError ($queryString)) {
$this->_errorSet ('DB_QueryTool::db::modifyLimitQuery failed '. $queryString->getMessage ());
$this->_errorLog ($queryString->getUserInfo ());
// $this->_queryCache[$cacheKey] = $queryString;
// {{{ _buildUpdateQuery()
* this simply builds an update query.
* @param array 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
function _buildUpdateQuery($query=array())
$where = isset($query['where']) ? $query['where'] : $this->_buildWhere ();
$where = 'WHERE '.$where;
$updateString = sprintf('UPDATE %s SET %s %s',
* @author Wolfram Kriesing <wk@visionp.de>
* @param string query method
function execute($query=null, $method='getAll')
$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->writeLog ('start query');
if (DB ::isError ($res = $this->db ->$method($query))) {
$this->writeLog ('end query (failed)');
if ($this->getOption ('verbose')) {
$this->_errorSet ($res->getMessage ());
$this->_errorLog ($res->getMessage ());
$this->_errorLog ($res->getUserInfo (), __LINE__ );
$this->writeLog ('end query');
$res = $this->_makeIndexed ($res);
* Write events to the logfile.
* It does some additional work, like time measuring etc. to
* see some additional info
function writeLog($text='START')
//its still really a quicky.... 'refactor' (nice word) that
if (!isset($this->options ['logfile'])) {
if (!class_exists('Log')) {
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'];
$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 ];
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);
* Return the chosen result type
* @param object reference
function returnResult(&$result)
if ($this->_resultType == 'none') {
//what about allowing other (custom) result types?
switch (strtolower($this->_resultType )) {
case 'object': return new DB_QueryTool_Result_Object($result);
default: return new DB_QueryTool_Result($result);
* @author Wolfram Kriesing <wk@visionp.de>
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)).'\']'; //"
//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) {
eval("\$keyValue = $evalString;"); // get the actual real (string-)key (string if multiple cols are used as index)
$indexedData[$keyValue] = $val;
* 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!
* @author Wolfram Kriesing <wk@visionp.de>
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);
* @author Wolfram Kriesing <wk@visionp.de>
* 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'
function useResult($type='array')
} elseif ($type === false) {
switch (strtolower($type)) {
$this->_resultType = 'array';
require_once 'DB/QueryTool/Result.php';
$this->_resultType = 'object';
require_once 'DB/QueryTool/Result/Object.php';
$this->_resultType = 'none';
// {{{ setErrorCallback()
function setErrorCallback($param='')
$this->setErrorLogCallback ($param);
$this->setErrorSetCallback ($param);
// {{{ setErrorLogCallback()
function setErrorLogCallback($param='')
$errorLogCallback = &PEAR::getStaticProperty('DB_QueryTool','_errorLogCallback');
$errorLogCallback = $param;
// {{{ setErrorSetCallback()
function setErrorSetCallback($param='')
$errorSetCallback = &PEAR::getStaticProperty('DB_QueryTool','_errorSetCallback');
$errorSetCallback = $param;
* sets error log and adds additional info
* @author Wolfram Kriesing <wk@visionp.de>
* @param string the actual message, first word should always be the method name,
* to build the message like this: className::methodname
* @param integer the line number
function _errorLog($msg, $line='unknown')
$this->_errorHandler ('log', $msg, $line);
if ($this->getOption('verbose') == true)
$this->_errorLog(get_class($this)."::$msg ($line)");
if ($this->_errorLogCallback)
call_user_func($this->_errorLogCallback, $msg);
function _errorSet($msg, $line='unknown')
$this->_errorHandler ('set', $msg, $line);
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)");
$msg = get_class($this)."::$msg ($line)";
$logOrSet = ucfirst($logOrSet);
$callback = &PEAR::getStaticProperty('DB_QueryTool','_error'.$logOrSet.'Callback');
call_user_func($callback, $msg);
Documentation generated on Mon, 11 Mar 2019 13:57:13 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|