2005-06-19 14:43:00 +02:00
< ? php
2008-03-06 12:42:21 +01:00
/**
2012-06-29 11:05:22 +02:00
* EGroupware API : Database abstraction library
2008-03-06 12:42:21 +01:00
*
* @ link http :// www . egroupware . org
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package api
* @ subpackage db
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2014-12-10 16:04:03 +01:00
* @ copyright ( c ) 2003 - 14 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-03-06 12:42:21 +01:00
* @ version $Id $
*/
2012-06-29 11:05:22 +02:00
if ( empty ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'db_type' ]))
{
$GLOBALS [ 'egw_info' ][ 'server' ][ 'db_type' ] = 'mysql' ;
}
include_once ( EGW_API_INC . '/adodb/adodb.inc.php' );
2008-03-06 12:42:21 +01:00
/**
* Database abstraction library
*
2008-03-09 15:22:02 +01:00
* This allows eGroupWare to use multiple database backends via ADOdb or in future with PDO
2008-06-07 10:24:18 +02:00
*
2008-03-06 12:42:21 +01:00
* You only need to clone the global database object $GLOBALS [ 'egw' ] -> db if :
* - you use the old methods f (), next_record (), row (), num_fields (), num_rows ()
* - you access an application table ( non phpgwapi ) and you want to call set_app ()
2008-06-07 10:24:18 +02:00
*
2008-03-06 12:42:21 +01:00
* Otherwise you can simply use $GLOBALS [ 'egw' ] -> db or a reference to it .
2008-06-07 10:24:18 +02:00
*
2008-03-06 12:42:21 +01:00
* Avoiding next_record () or row () can be done by looping with the recordset returned by query () or select () :
2008-06-07 10:24:18 +02:00
*
2008-03-06 12:42:21 +01:00
* a ) foreach ( $db -> query ( " SELECT * FROM $table " , __LINE__ , __FILE__ ) as $row )
2008-06-07 10:24:18 +02:00
*
2008-03-06 12:42:21 +01:00
* b ) foreach ( $db -> select ( $api_table , '*' , $where , __LINE__ , __FILE__ ) as $row )
*
* c ) foreach ( $db -> select ( $table , '*' , $where , __LINE__ , __FILE__ , false , '' , $app ) as $row )
2008-03-09 15:22:02 +01:00
*
2009-03-25 10:03:36 +01:00
* To fetch only a single column ( of the next row ) :
* $cnt = $db -> query ( " SELECT COUNT(*) FROM ... " ) -> fetchColumn ( $column_num = 0 );
2008-03-09 15:22:02 +01:00
*
* To fetch a next ( single ) row , you can use :
* $row = $db -> query ( " SELECT COUNT(*) FROM ... " ) -> fetch ( $fetchmod = null );
2008-10-25 07:36:25 +02:00
*
* egw_db allows to use exceptions to catch sql - erros , not existing tables or failure to connect to the database , eg .:
* try {
* $this -> db -> connect ();
2009-06-08 18:21:14 +02:00
* $num_config = $this -> db -> select ( config :: TABLE , 'COUNT(config_name)' , false , __LINE__ , __FILE__ ) -> fetchColumn ();
2008-10-25 07:36:25 +02:00
* }
* catch ( Exception $e ) {
* echo " Connection to DB failed ( " . $e -> getMessage () . " )! \n " ;
* }
2008-03-06 12:42:21 +01:00
*/
class egw_db
{
2008-03-09 15:22:02 +01:00
/**
* Fetchmode to fetch only as associative array with $colname => $value pairs
2008-06-07 10:24:18 +02:00
*
2008-03-09 15:22:02 +01:00
* Use the FETCH_ * constants to be compatible , if we replace ADOdb ...
*/
const FETCH_ASSOC = ADODB_FETCH_ASSOC ;
/**
* Fetchmode to fetch only as ( numeric indexed ) array : array ( $val1 , $val2 , ... )
*/
const FETCH_NUM = ADODB_FETCH_NUM ;
/**
* Fetchmode to have both numeric and column - name indexes
*/
const FETCH_BOTH = ADODB_FETCH_BOTH ;
2008-03-06 12:42:21 +01:00
/**
* @ var string $type translated database type : mysqlt + mysqli ==> mysql , same for odbc - types
*/
var $Type = '' ;
/**
* @ var string $type database type as defined in the header . inc . php , eg . mysqlt
*/
var $setupType = '' ;
/**
* @ var string $Host database host to connect to
*/
var $Host = '' ;
/**
* @ var string $Port port number of database to connect to
*/
var $Port = '' ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* @ var string $Database name of database to use
*/
var $Database = '' ;
2005-06-19 14:43:00 +02:00
2005-11-14 09:36:39 +01:00
/**
2008-03-06 12:42:21 +01:00
* @ var string $User name of database user
*/
var $User = '' ;
/**
* @ var string $Password password for database user
*/
var $Password = '' ;
/**
* @ var int $Debug enable debuging - 0 no , 1 yes
*/
var $Debug = 0 ;
2013-06-12 18:57:44 +02:00
/**
* Log update querys to error_log , do not run them
*
* @ var boolean
*/
var $log_updates = false ;
2008-03-06 12:42:21 +01:00
/**
* @ var array $Record current record
*/
var $Record = array ();
/**
* @ var int row number for current record
*/
var $Row ;
/**
* @ var int $Errno internal rdms error number for last error
*/
var $Errno = 0 ;
/**
* @ var string descriptive text from last error
*/
var $Error = '' ;
/**
* eGW ' s own query log , independent of the db - type , eg . / tmp / query . log
2005-06-19 14:43:00 +02:00
*
2008-03-06 12:42:21 +01:00
* @ var string
*/
var $query_log ;
/**
* ADOdb connection
2005-06-19 14:43:00 +02:00
*
2008-03-06 12:42:21 +01:00
* @ var ADOConnection
2005-06-19 14:43:00 +02:00
*/
2008-03-06 12:42:21 +01:00
var $Link_ID = 0 ;
/**
* ADOdb connection
*
* @ var ADOConnection
*/
var $privat_Link_ID = False ; // do we use a privat Link_ID or a reference to the global ADOdb object
/**
* ADOdb record set of the current query
*
* @ var ADORecordSet
*/
var $Query_ID = 0 ;
/**
2008-06-07 10:24:18 +02:00
* Can be used to transparently convert tablenames , eg . 'mytable' => 'otherdb.othertable'
*
* Can be set eg . at the * end * of header . inc . php .
* Only works with new egw_db methods ( select , insert , update , delete ) not query !
*
* @ var array
*/
static $tablealiases = array ();
2015-07-15 22:47:49 +02:00
/**
* Callback to check if selected node is healty / should be used
*
* @ var callback throwing egw_exception_db_connection , if connected node should NOT be used
*/
static $health_check ;
2008-06-07 10:24:18 +02:00
/**
* db allows sub - queries , true for everything but mysql < 4.1
*
* use like : if ( $db -> capabilities [ egw_db :: CAPABILITY_SUB_QUERIES ]) ...
*/
const CAPABILITY_SUB_QUERIES = 'sub_queries' ;
/**
* db allows union queries , true for everything but mysql < 4.0
*/
const CAPABILITY_UNION = 'union' ;
/**
* db allows an outer join , will be set eg . for postgres
*/
const CAPABILITY_OUTER_JOIN = 'outer_join' ;
/**
* db is able to use DISTINCT on text or blob columns
*/
const CAPABILITY_DISTINCT_ON_TEXT = 'distinct_on_text' ;
/**
* DB is able to use LIKE on text columns
*/
const CAPABILITY_LIKE_ON_TEXT = 'like_on_text' ;
/**
* DB allows ORDER on text columns
*
* boolean or string for sprintf for a cast ( eg . ' CAST ( % s AS varchar )
*/
const CAPABILITY_ORDER_ON_TEXT = 'order_on_text' ;
/**
* case of returned column - and table - names : upper , lower ( pgSql ), preserv ( MySQL )
*/
const CAPABILITY_NAME_CASE = 'name_case' ;
/**
* does DB supports a changeable client - encoding
*/
const CAPABILITY_CLIENT_ENCODING = 'client_encoding' ;
/**
* case insensitiv like statement ( in $db -> capabilities [ egw_db :: CAPABILITY_CASE_INSENSITIV_LIKE ]), default LIKE , ILIKE for postgres
*/
const CAPABILITY_CASE_INSENSITIV_LIKE = 'case_insensitive_like' ;
/**
* DB requires varchar columns to be truncated to the max . size ( eg . Postgres )
*/
const CAPABILITY_REQUIRE_TRUNCATE_VARCHAR = 'require_truncate_varchar' ;
2011-03-18 15:18:24 +01:00
/**
* How to cast a column to varchar : CAST ( % s AS varchar )
*
* MySQL requires to use CAST ( % s AS char ) !
*
2011-03-20 13:49:22 +01:00
* Use as : $sql = sprintf ( $GLOBALS [ 'egw' ] -> db -> capabilities [ egw_db :: CAPABILITY_CAST_AS_VARCHAR ], $expression );
2011-03-18 15:18:24 +01:00
*/
2011-03-20 13:49:22 +01:00
const CAPABILITY_CAST_AS_VARCHAR = 'cast_as_varchar' ;
2008-06-07 10:24:18 +02:00
/**
* default capabilities will be changed by method set_capabilities ( $ado_driver , $db_version )
*
* should be used with the CAPABILITY_ * constants as key
*
* @ var array
2008-03-06 12:42:21 +01:00
*/
var $capabilities = array (
2008-06-07 10:24:18 +02:00
self :: CAPABILITY_SUB_QUERIES => true ,
self :: CAPABILITY_UNION => true ,
self :: CAPABILITY_OUTER_JOIN => false ,
self :: CAPABILITY_DISTINCT_ON_TEXT => true ,
self :: CAPABILITY_LIKE_ON_TEXT => true ,
self :: CAPABILITY_ORDER_ON_TEXT => true ,
self :: CAPABILITY_NAME_CASE => 'upper' ,
self :: CAPABILITY_CLIENT_ENCODING => false ,
self :: CAPABILITY_CASE_INSENSITIV_LIKE => 'LIKE' ,
2011-08-22 12:16:07 +02:00
self :: CAPABILITY_REQUIRE_TRUNCATE_VARCHAR => true ,
2011-03-20 13:49:22 +01:00
self :: CAPABILITY_CAST_AS_VARCHAR => 'CAST(%s AS varchar)' ,
2008-06-07 10:24:18 +02:00
);
2008-03-06 12:42:21 +01:00
var $prepared_sql = array (); // sql is the index
2008-08-07 22:38:57 +02:00
/**
* Constructor
*
2015-02-02 21:13:19 +01:00
* @ param array $db_data = null values for keys 'db_name' , 'db_host' , 'db_port' , 'db_user' , 'db_pass' , 'db_type'
2008-08-07 22:38:57 +02:00
*/
function __construct ( array $db_data = null )
{
if ( ! is_null ( $db_data ))
{
foreach ( array (
'Database' => 'db_name' ,
'Host' => 'db_host' ,
'Port' => 'db_port' ,
'User' => 'db_user' ,
'Password' => 'db_pass' ,
'Type' => 'db_type' ,
) as $var => $key )
{
$this -> $var = $db_data [ $key ];
}
}
2011-04-10 17:10:07 +02:00
//if ($GLOBALS['egw_info']['server']['default_domain'] == 'ralfsmacbook.local') $this->query_log = '/tmp/query.log';
2008-08-07 22:38:57 +02:00
}
2008-03-06 12:42:21 +01:00
/**
* @ param string $query query to be executed ( optional )
*/
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
function db ( $query = '' )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$this -> query ( $query );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
/**
* @ return int current connection id
*/
function link_id ()
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return $this -> Link_ID ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
/**
2008-03-08 22:31:12 +01:00
* Return the result - object of the last query
2008-06-07 10:24:18 +02:00
*
2008-03-08 22:31:12 +01:00
* @ deprecated use the result - object returned by query () or select () direct , so you can use the global db - object and not a clone
* @ return ADORecordSet
*/
2008-03-06 12:42:21 +01:00
function query_id ()
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return $this -> Query_ID ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
/**
2015-07-06 13:15:14 +02:00
* Open a connection to a database
*
* @ param string $Database name of database to use ( optional )
* @ param string $Host database host to connect to ( optional )
* @ param string $Port database port to connect to ( optional )
* @ param string $User name of database user ( optional )
* @ param string $Password password for database user ( optional )
* @ param string $Type type of database ( optional )
* @ throws egw_exception_db_connection
* @ return ADOConnection
*/
2008-03-06 12:42:21 +01:00
function connect ( $Database = NULL , $Host = NULL , $Port = NULL , $User = NULL , $Password = NULL , $Type = NULL )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
/* Handle defaults */
if ( ! is_null ( $Database ) && $Database )
{
$this -> Database = $Database ;
}
if ( ! is_null ( $Host ) && $Host )
{
$this -> Host = $Host ;
}
if ( ! is_null ( $Port ) && $Port )
{
$this -> Port = $Port ;
}
if ( ! is_null ( $User ) && $User )
{
$this -> User = $User ;
}
if ( ! is_null ( $Password ) && $Password )
{
$this -> Password = $Password ;
}
if ( ! is_null ( $Type ) && $Type )
{
$this -> Type = $Type ;
}
elseif ( ! $this -> Type )
{
$this -> Type = $GLOBALS [ 'egw_info' ][ 'server' ][ 'db_type' ];
}
2015-07-06 13:15:14 +02:00
// on connection failure re-try with an other host
// remembering in session which host we used last time
$use_host_from_session = true ;
while (( $host = $this -> get_host ( ! $use_host_from_session )))
{
try {
//error_log(__METHOD__."() this->Host(s)=$this->Host, n=$n --> host=$host");
2015-07-14 11:44:39 +02:00
$new_connection = ! $this -> Link_ID || ! $this -> Link_ID -> IsConnected ();
$this -> _connect ( $host );
2015-07-15 22:47:49 +02:00
// check if connected node is healty
if ( $new_connection && self :: $health_check )
2015-07-14 11:44:39 +02:00
{
2015-07-15 22:47:49 +02:00
call_user_func ( self :: $health_check , $this );
2015-07-14 11:44:39 +02:00
}
//error_log(__METHOD__."() host=$host, new_connection=$new_connection, this->Type=$this->Type, this->Host=$this->Host, wsrep_local_state=".array2string($state));
return $this -> Link_ID ;
2015-07-06 13:15:14 +02:00
}
catch ( egw_exception_db_connection $e ) {
_egw_log_exception ( $e );
$this -> disconnect (); // force a new connect
$this -> Type = $this -> setupType ; // get set to "mysql" for "mysqli"
$use_host_from_session = false ; // re-try with next host from list
}
}
throw $e ;
}
2015-07-15 22:47:49 +02:00
/**
* Check if just connected Galera cluster node is healthy / fully operational
*
* A node in state " Donor/Desynced " will block updates at the end of a SST .
* Therefore we try to avoid that node , if we have an alternative .
*
* To enable this check add the following to your header . inc . php :
*
2015-07-19 10:35:27 +02:00
* require_once ( EGW_API_INC . '/class.egw_db.inc.php' );
2015-07-15 22:47:49 +02:00
* egw_db :: $health_check = array ( 'egw_db' , 'galera_cluster_health' );
*
* @ param egw_db $db already connected egw_db instance to check
* @ throws egw_exception_db_connection if node should NOT be used
*/
static function galera_cluster_health ( egw_db $db )
{
if (( $state = $db -> query ( " SHOW STATUS WHERE Variable_name in ('wsrep_cluster_size','wsrep_local_state','wsrep_local_state_comment') " ) -> GetAssoc ()))
{
if ( $state [ 'wsrep_local_state_comment' ] == 'Synced' ||
// if we have only 2 nodes (2. one starting), we can only use the donor
$state [ 'wsrep_local_state_comment' ] == 'Donor/Desynced' &&
$state [ 'wsrep_cluster_size' ] == 2 ) return ;
throw new egw_exception_db_connection ( 'Node is NOT Synced! ' . array2string ( $state ));
}
}
2015-07-06 13:15:14 +02:00
/**
* Get one of multiple ( semicolon - separated ) DB - hosts to use
*
* Which host to use is cached in session , default is first one .
*
* @ param boolean $next = false true : move to next host
* @ return boolean | string hostname or false , if already number - of - hosts plus 2 times called with $next == true
*/
public function get_host ( $next = false )
{
$hosts = explode ( ';' , $this -> Host );
$num_hosts = count ( $hosts );
$n =& egw_cache :: getSession ( __CLASS__ , $this -> Host );
if ( ! isset ( $n )) $n = 0 ;
if ( $next && ++ $n >= $num_hosts + 2 )
{
2015-07-19 10:35:27 +02:00
$n = 0 ; // start search again with default on next request
$ret = false ;
2015-07-06 13:15:14 +02:00
}
2015-07-19 10:35:27 +02:00
else
{
$ret = $hosts [ $n % $num_hosts ];
}
//error_log(__METHOD__."(next=".array2string($next).") n=$n returning ".array2string($ret));
return $ret ;
2015-07-06 13:15:14 +02:00
}
/**
* Connect to given host
*
* @ param string $Host host to connect to
* @ return ADOConnection
* @ throws egw_exception_db_connection
*/
protected function _connect ( $Host )
{
2008-03-06 12:42:21 +01:00
if ( ! $this -> Link_ID )
{
2015-07-06 13:15:14 +02:00
foreach ( array ( 'Database' , 'User' , 'Password' ) as $name )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$$name = $this -> $name ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$this -> setupType = $php_extension = $type = $this -> Type ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
switch ( $this -> Type ) // convert to ADO db-type-names
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
case 'pgsql' :
$type = 'postgres' ; // name in ADOdb
// create our own pgsql connection-string, to allow unix domain soccets if !$Host
$Host = " dbname= $this->Database " . ( $this -> Host ? " host= $this->Host " . ( $this -> Port ? " port= $this->Port " : '' ) : '' ) .
" user= $this->User " . ( $this -> Password ? " password=' " . addslashes ( $this -> Password ) . " ' " : '' );
$User = $Password = $Database = '' ; // to indicate $Host is a connection-string
break ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
case 'odbc_mssql' :
$php_extension = 'odbc' ;
$this -> Type = 'mssql' ;
// fall through
case 'mssql' :
if ( $this -> Port ) $Host .= ',' . $this -> Port ;
break ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
case 'odbc_oracle' :
$php_extension = 'odbc' ;
$this -> Type = 'oracle' ;
break ;
case 'oracle' :
$php_extension = $type = 'oci8' ;
break ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
case 'sapdb' :
$this -> Type = 'maxdb' ;
// fall through
case 'maxdb' :
$type = 'sapdb' ; // name in ADOdb
$php_extension = 'odbc' ;
break ;
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
case 'mysqlt' :
$php_extension = 'mysql' ; // you can use $this->setupType to determine if it's mysqlt or mysql
// fall through
case 'mysqli' :
$this -> Type = 'mysql' ;
// fall through
default :
if ( $this -> Port ) $Host .= ':' . $this -> Port ;
break ;
}
2008-06-27 11:44:48 +02:00
if ( ! isset ( $GLOBALS [ 'egw' ] -> ADOdb ) || // we have no connection so far
2008-03-06 12:42:21 +01:00
( is_object ( $GLOBALS [ 'egw' ] -> db ) && // we connect to a different db, then the global one
( $this -> Type != $GLOBALS [ 'egw' ] -> db -> Type ||
$this -> Database != $GLOBALS [ 'egw' ] -> db -> Database ||
$this -> User != $GLOBALS [ 'egw' ] -> db -> User ||
$this -> Host != $GLOBALS [ 'egw' ] -> db -> Host ||
$this -> Port != $GLOBALS [ 'egw' ] -> db -> Port )))
{
2009-04-20 14:43:44 +02:00
if ( ! check_load_extension ( $php_extension ))
2008-03-06 12:42:21 +01:00
{
2013-03-05 11:50:43 +01:00
throw new egw_exception_db_connection ( " Necessary php database support for $this->Type ( " . PHP_SHLIB_PREFIX . $php_extension . '.' . PHP_SHLIB_SUFFIX . " ) not loaded and can't be loaded, exiting !!! " );
2005-06-19 14:43:00 +02:00
}
2008-06-27 11:44:48 +02:00
if ( ! isset ( $GLOBALS [ 'egw' ] -> ADOdb )) // use the global object to store the connection
2005-06-19 14:43:00 +02:00
{
2008-06-27 11:44:48 +02:00
$this -> Link_ID =& $GLOBALS [ 'egw' ] -> ADOdb ;
2008-03-06 12:42:21 +01:00
}
else
{
$this -> privat_Link_ID = True ; // remember that we use a privat Link_ID for disconnect
}
$this -> Link_ID = ADONewConnection ( $type );
if ( ! $this -> Link_ID )
{
2013-03-05 11:50:43 +01:00
throw new egw_exception_db_connection ( " No ADOdb support for ' $type ' ( $this->Type ) !!! " );
2008-03-06 12:42:21 +01:00
}
$connect = $GLOBALS [ 'egw_info' ][ 'server' ][ 'db_persistent' ] ? 'PConnect' : 'Connect' ;
2013-05-26 11:32:41 +02:00
if (( $Ok = $this -> Link_ID -> $connect ( $Host , $User , $Password , $Database )))
2008-03-06 12:42:21 +01:00
{
$this -> ServerInfo = $this -> Link_ID -> ServerInfo ();
$this -> set_capabilities ( $type , $this -> ServerInfo [ 'version' ]);
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( ! $Ok )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$Host = preg_replace ( '/password=[^ ]+/' , 'password=$Password' , $Host ); // eg. postgres dsn contains password
2013-03-05 11:50:43 +01:00
throw new egw_exception_db_connection ( " ADOdb:: $connect ( $Host , $User , \$ Password, $Database ) failed. " );
2008-03-06 12:42:21 +01:00
}
if ( $this -> Debug )
{
echo function_backtrace ();
echo " <p>new ADOdb connection to $this->Type :// $this->Host / $this->Database : Link_ID " . ( $this -> Link_ID === $GLOBALS [ 'egw' ] -> ADOdb ? '===' : '!==' ) . " \$ GLOBALS[egw]->ADOdb</p> " ;
//echo "<p>".print_r($this->Link_ID->ServerInfo(),true)."</p>\n";
_debug_array ( $this );
echo " \$ GLOBALS[egw]->db= " ; _debug_array ( $GLOBALS [ egw ] -> db );
}
if ( $this -> Type == 'mssql' )
{
// this is the format ADOdb expects
$this -> Link_ID -> Execute ( 'SET DATEFORMAT ymd' );
// sets the limit to the maximum
ini_set ( 'mssql.textlimit' , 2147483647 );
ini_set ( 'mssql.sizelimit' , 2147483647 );
2005-06-19 14:43:00 +02:00
}
2008-03-21 21:11:59 +01:00
$new_connection = true ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
else
{
2008-06-27 11:44:48 +02:00
$this -> Link_ID =& $GLOBALS [ 'egw' ] -> ADOdb ;
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
}
2013-06-20 15:30:58 +02:00
if ( ! $this -> Link_ID -> isConnected () && ! $this -> Link_ID -> Connect ())
{
$Host = preg_replace ( '/password=[^ ]+/' , 'password=$Password' , $this -> Host ); // eg. postgres dsn contains password
throw new egw_exception_db_connection ( " ADOdb:: $connect ( $Host , $this->User , \$ Password, $this->Database ) reconnect failed. " );
}
2005-06-19 14:43:00 +02:00
2008-08-13 07:20:23 +02:00
if ( $new_connection )
2008-03-21 21:11:59 +01:00
{
foreach ( get_included_files () as $file )
{
if ( strpos ( $file , 'adodb' ) !== false && ! in_array ( $file ,( array ) $_SESSION [ 'egw_required_files' ]))
{
$_SESSION [ 'egw_required_files' ][] = $file ;
2008-08-13 07:20:23 +02:00
//error_log(__METHOD__."() egw_required_files[] = $file");
2008-03-21 21:11:59 +01:00
}
}
}
2008-03-06 12:42:21 +01:00
//echo "<p>".print_r($this->Link_ID->ServerInfo(),true)."</p>\n";
return $this -> Link_ID ;
}
2008-06-07 10:24:18 +02:00
2008-03-21 21:11:59 +01:00
/**
* Magic method to re - connect with the database , if the object get ' s restored from the session
*/
function __wakeup ()
{
$this -> connect (); // we need to re-connect
}
2008-03-06 12:42:21 +01:00
/**
* changes defaults set in class - var $capabilities depending on db - type and - version
*
2015-02-02 21:13:19 +01:00
* @ param string $adodb_driver mysql , postgres , mssql , sapdb , oci8
2008-03-06 12:42:21 +01:00
* @ param string $db_version version - number of connected db - server , as reported by ServerInfo
*/
function set_capabilities ( $adodb_driver , $db_version )
{
switch ( $adodb_driver )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
case 'mysql' :
case 'mysqlt' :
case 'mysqli' :
2008-06-07 10:24:18 +02:00
$this -> capabilities [ self :: CAPABILITY_SUB_QUERIES ] = ( float ) $db_version >= 4.1 ;
$this -> capabilities [ self :: CAPABILITY_UNION ] = ( float ) $db_version >= 4.0 ;
$this -> capabilities [ self :: CAPABILITY_NAME_CASE ] = 'preserv' ;
$this -> capabilities [ self :: CAPABILITY_CLIENT_ENCODING ] = ( float ) $db_version >= 4.1 ;
2011-03-20 13:49:22 +01:00
$this -> capabilities [ self :: CAPABILITY_CAST_AS_VARCHAR ] = 'CAST(%s AS char)' ;
2008-03-06 12:42:21 +01:00
break ;
case 'postgres' :
2008-06-07 10:24:18 +02:00
$this -> capabilities [ self :: CAPABILITY_NAME_CASE ] = 'lower' ;
$this -> capabilities [ self :: CAPABILITY_CLIENT_ENCODING ] = ( float ) $db_version >= 7.4 ;
$this -> capabilities [ self :: CAPABILITY_OUTER_JOIN ] = true ;
2010-06-22 18:56:16 +02:00
$this -> capabilities [ self :: CAPABILITY_CASE_INSENSITIV_LIKE ] = '::text ILIKE' ;
2008-06-07 10:24:18 +02:00
$this -> capabilities [ self :: CAPABILITY_REQUIRE_TRUNCATE_VARCHAR ] = true ;
2008-03-06 12:42:21 +01:00
break ;
case 'mssql' :
2008-06-07 10:24:18 +02:00
$this -> capabilities [ self :: CAPABILITY_DISTINCT_ON_TEXT ] = false ;
$this -> capabilities [ self :: CAPABILITY_ORDER_ON_TEXT ] = 'CAST (%s AS varchar)' ;
2008-03-06 12:42:21 +01:00
break ;
case 'maxdb' : // if Lim ever changes it to maxdb ;-)
case 'sapdb' :
2008-06-07 10:24:18 +02:00
$this -> capabilities [ self :: CAPABILITY_DISTINCT_ON_TEXT ] = false ;
$this -> capabilities [ self :: CAPABILITY_LIKE_ON_TEXT ] = $db_version >= 7.6 ;
$this -> capabilities [ self :: CAPABILITY_ORDER_ON_TEXT ] = false ;
2008-03-06 12:42:21 +01:00
break ;
}
//echo "db::set_capabilities('$adodb_driver',$db_version)"; _debug_array($this->capabilities);
}
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
/**
* Close a connection to a database
*/
function disconnect ()
{
if ( ! $this -> privat_Link_ID )
{
unset ( $GLOBALS [ 'egw' ] -> ADOdb );
}
unset ( $this -> Link_ID );
$this -> Link_ID = 0 ;
2015-07-07 14:41:11 +02:00
if ( ! empty ( $this -> setupType )) $this -> type = $this -> setupType ;
2008-03-06 12:42:21 +01:00
}
2005-11-20 16:40:28 +01:00
2008-03-06 12:42:21 +01:00
/**
* Escape strings before sending them to the database
*
* @ deprecated use quote ( $value , $type = '' ) instead
* @ param string $str the string to be escaped
* @ return string escaped sting
*/
function db_addslashes ( $str )
{
if ( ! isset ( $str ) || $str == '' )
{
return '' ;
}
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
}
return $this -> Link_ID -> addq ( $str );
}
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
/**
* Convert a unix timestamp to a rdms specific timestamp
*
* @ param int unix timestamp
* @ return string rdms specific timestamp
*/
function to_timestamp ( $epoch )
{
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
// the substring is needed as the string is already in quotes
return substr ( $this -> Link_ID -> DBTimeStamp ( $epoch ), 1 , - 1 );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Convert a rdms specific timestamp to a unix timestamp
*
* @ param string rdms specific timestamp
* @ return int unix timestamp
*/
function from_timestamp ( $timestamp )
{
if ( ! $this -> Link_ID && ! $this -> connect ())
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return $this -> Link_ID -> UnixTimeStamp ( $timestamp );
}
/**
* convert a rdbms specific boolean value
*
* @ param string $val boolean value in db - specfic notation
* @ return boolean
*/
2014-06-24 09:25:35 +02:00
public static function from_bool ( $val )
2008-03-06 12:42:21 +01:00
{
2009-05-30 09:03:57 +02:00
return $val && $val [ 0 ] !== 'f' ; // everthing other then 0 or f[alse] is returned as true
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
2008-03-08 22:31:12 +01:00
* Discard the current query result
2008-06-07 10:24:18 +02:00
*
2008-03-08 22:31:12 +01:00
* @ deprecated use the result - object returned by query () or select () direct , so you can use the global db - object and not a clone
*/
2008-03-06 12:42:21 +01:00
function free ()
{
unset ( $this -> Query_ID ); // else copying of the db-object does not work
$this -> Query_ID = 0 ;
}
/**
* Execute a query
*
* @ param string $Query_String the query to be executed
* @ param int $line the line method was called from - use __LINE__
* @ param string $file the file method was called from - use __FILE__
* @ param int $offset row to start from , default 0
* @ param int $num_rows number of rows to return ( optional ), default - 1 = all , 0 will use $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'maxmatchs' ]
2015-02-02 21:13:19 +01:00
* @ param array | boolean $inputarr array for binding variables to parameters or false ( default )
2014-09-22 12:23:17 +02:00
* @ param int $fetchmode = egw_db :: FETCH_BOTH egw_db :: FETCH_BOTH ( default ), egw_db :: FETCH_ASSOC or egw_db :: FETCH_NUM
* @ param boolean $reconnect = true true : try reconnecting if server closes connection , false : dont ( mysql only ! )
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
2014-09-22 12:23:17 +02:00
* @ throws egw_exception_db_invalid_sql with $this -> Link_ID -> ErrorNo () as code
2008-03-06 12:42:21 +01:00
*/
2014-09-22 12:23:17 +02:00
function query ( $Query_String , $line = '' , $file = '' , $offset = 0 , $num_rows =- 1 , $inputarr = false , $fetchmode = egw_db :: FETCH_BOTH , $reconnect = true )
2008-03-06 12:42:21 +01:00
{
2014-06-25 14:55:16 +02:00
unset ( $line , $file ); // not used anymore
2008-03-06 12:42:21 +01:00
if ( $Query_String == '' )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return 0 ;
}
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
# New query, discard previous result.
if ( $this -> Query_ID )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$this -> free ();
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( $this -> Link_ID -> fetchMode != $fetchmode )
{
$this -> Link_ID -> SetFetchMode ( $fetchmode );
}
if ( ! $num_rows )
{
$num_rows = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'maxmatchs' ];
}
2013-06-12 18:57:44 +02:00
if ( $this -> log_updates && stripos ( $Query_String , 'SELECT' ) !== 0 )
{
error_log ( $Query_String );
return 0 ;
}
2008-03-06 12:42:21 +01:00
if ( $num_rows > 0 )
{
$this -> Query_ID = $this -> Link_ID -> SelectLimit ( $Query_String , $num_rows ,( int ) $offset , $inputarr );
}
else
{
$this -> Query_ID = $this -> Link_ID -> Execute ( $Query_String , $inputarr );
}
$this -> Row = 0 ;
$this -> Errno = $this -> Link_ID -> ErrorNo ();
$this -> Error = $this -> Link_ID -> ErrorMsg ();
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
if ( $this -> query_log && ( $f = @ fopen ( $this -> query_log , 'a+' )))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
fwrite ( $f , '[' . ( isset ( $GLOBALS [ 'egw_setup' ]) ? $GLOBALS [ 'egw_setup' ] -> ConfigDomain : $GLOBALS [ 'egw_info' ][ 'user' ][ 'domain' ]) . '] ' );
fwrite ( $f , date ( 'Y-m-d H:i:s ' ) . $Query_String . ( $inputarr ? " \n " . print_r ( $inputarr , true ) : '' ) . " \n " );
if ( ! $this -> Query_ID )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
fwrite ( $f , " *** Error $this->Errno : $this->Error\n " . function_backtrace () . " \n " );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
fclose ( $f );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( ! $this -> Query_ID )
2005-11-20 16:40:28 +01:00
{
2014-09-22 12:23:17 +02:00
if ( $reconnect && $this -> Type == 'mysql' && $this -> Errno == 2006 ) // Server has gone away
{
$this -> disconnect ();
return $this -> query ( $Query_String , $line , $file , $offset , $num_rows , $inputarr , $fetchmode , false );
}
2013-03-05 11:50:43 +01:00
throw new egw_exception_db_invalid_sql ( " Invalid SQL: " . ( is_array ( $Query_String ) ? $Query_String [ 0 ] : $Query_String ) .
2013-06-28 12:50:42 +02:00
" \n $this->Error ( $this->Errno ) " .
2014-09-22 12:23:17 +02:00
( $inputarr ? " \n Parameters: ' " . implode ( " ',' " , $inputarr ) . " ' " : '' ), $this -> Errno );
2005-11-20 16:40:28 +01:00
}
2015-08-13 14:47:52 +02:00
elseif ( empty ( $this -> Query_ID -> sql )) $this -> Query_ID -> sql = $Query_String ;
2008-03-06 12:42:21 +01:00
return $this -> Query_ID ;
}
/**
* Execute a query with limited result set
*
* @ param string $Query_String the query to be executed
* @ param int $offset row to start from , default 0
* @ param int $line the line method was called from - use __LINE__
* @ param string $file the file method was called from - use __FILE__
* @ param int $num_rows number of rows to return ( optional ), default - 1 = all , 0 will use $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'maxmatchs' ]
2015-02-02 21:13:19 +01:00
* @ param array | boolean $inputarr array for binding variables to parameters or false ( default )
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
function limit_query ( $Query_String , $offset , $line = '' , $file = '' , $num_rows = '' , $inputarr = false )
{
return $this -> query ( $Query_String , $line , $file , $offset , $num_rows , $inputarr );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Move to the next row in the results set
*
* Specifying a fetch_mode only works for newly fetched rows , the first row always gets fetched by query !!!
*
2008-03-08 22:31:12 +01:00
* @ deprecated use foreach ( query () or foreach ( select () to loop over the query using the global db object
2008-03-09 15:22:02 +01:00
* @ param int $fetch_mode egw_db :: FETCH_BOTH = numerical + assoc keys ( eGW default ), egw_db :: FETCH_ASSOC or egw_db :: FETCH_NUM
2008-03-06 12:42:21 +01:00
* @ return bool was another row found ?
*/
2008-03-09 15:22:02 +01:00
function next_record ( $fetch_mode = egw_db :: FETCH_BOTH )
2008-03-06 12:42:21 +01:00
{
if ( ! $this -> Query_ID )
{
2013-03-05 11:50:43 +01:00
throw new egw_exception_db ( 'next_record called with no query pending.' );
2008-03-06 12:42:21 +01:00
}
if ( $this -> Row ) // first row is already fetched
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$this -> Query_ID -> MoveNext ();
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
++ $this -> Row ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
$this -> Record = $this -> Query_ID -> fields ;
if ( $this -> Query_ID -> EOF || ! $this -> Query_ID -> RecordCount () || ! is_array ( $this -> Record ))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
}
2008-06-07 10:24:18 +02:00
if ( $this -> capabilities [ self :: CAPABILITY_NAME_CASE ] == 'upper' ) // maxdb, oracle, ...
2008-03-06 12:42:21 +01:00
{
switch ( $fetch_mode )
2005-06-19 14:43:00 +02:00
{
2008-03-09 15:22:02 +01:00
case egw_db :: FETCH_ASSOC :
2008-03-06 12:42:21 +01:00
$this -> Record = array_change_key_case ( $this -> Record );
break ;
2008-03-09 15:22:02 +01:00
case egw_db :: FETCH_NUM :
2008-03-06 12:42:21 +01:00
$this -> Record = array_values ( $this -> Record );
break ;
default :
$this -> Record = array_change_key_case ( $this -> Record );
if ( ! isset ( $this -> Record [ 0 ]))
{
$this -> Record += array_values ( $this -> Record );
}
break ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
}
2008-03-06 15:20:47 +01:00
// fix the result if it was fetched ASSOC and now NUM OR BOTH is required, as default for select() is now ASSOC
elseif ( $this -> Link_ID -> fetchMode != $fetch_mode )
{
if ( ! isset ( $this -> Record [ 0 ]))
{
$this -> Record += array_values ( $this -> Record );
}
}
2008-03-06 12:42:21 +01:00
return True ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Move to position in result set
*
2008-03-08 22:31:12 +01:00
* @ deprecated use the result - object returned by query () or select () direct , so you can use the global db - object and not a clone
2008-03-06 12:42:21 +01:00
* @ param int $pos required row ( optional ), default first row
* @ return boolean true if sucessful or false if not found
*/
function seek ( $pos = 0 )
{
if ( ! $this -> Query_ID || ! $this -> Query_ID -> Move ( $this -> Row = $pos ))
{
2013-03-05 11:50:43 +01:00
throw new egw_exception_db ( " seek( $pos ) failed: resultset has " . $this -> num_rows () . " rows " );
2008-03-06 12:42:21 +01:00
}
return True ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Begin Transaction
*
* @ return int / boolean current transaction - id , of false if no connection
*/
function transaction_begin ()
{
if ( ! $this -> Link_ID && ! $this -> connect ())
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
}
//return $this->Link_ID->BeginTrans();
return $this -> Link_ID -> StartTrans ();
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Complete the transaction
*
* @ return bool True if sucessful , False if fails
*/
function transaction_commit ()
{
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
}
//return $this->Link_ID->CommitTrans();
return $this -> Link_ID -> CompleteTrans ();
}
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
/**
* Rollback the current transaction
*
* @ return bool True if sucessful , False if fails
*/
function transaction_abort ()
{
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
//return $this->Link_ID->RollbackTrans();
return $this -> Link_ID -> FailTrans ();
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Find the primary key of the last insertion on the current db connection
*
* @ param string $table name of table the insert was performed on
* @ param string $field the autoincrement primary key of the table
* @ return int the id , - 1 if fails
*/
function get_last_insert_id ( $table , $field )
{
if ( ! $this -> Link_ID && ! $this -> connect ())
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
2005-06-19 14:43:00 +02:00
}
2008-06-07 10:24:18 +02:00
if ( self :: $tablealiases && isset ( self :: $tablealiases [ $table ]))
{
$table = self :: $tablealiases [ $table ];
}
2008-03-06 12:42:21 +01:00
$id = $this -> Link_ID -> PO_Insert_ID ( $table , $field ); // simulates Insert_ID with "SELECT MAX($field) FROM $table" if not native availible
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
if ( $id === False ) // function not supported
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
echo " <p>db::get_last_insert_id(table=' $table ',field=' $field ') not yet implemented for db-type ' $this->Type ' OR no insert operation before</p> \n " ;
2011-04-10 17:10:07 +02:00
echo '<p>' . function_backtrace () . " </p> \n " ;
2008-03-06 12:42:21 +01:00
return - 1 ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return $id ;
}
/**
* Lock a table
*
* @ deprecated not used anymore as it costs to much performance , use transactions if needed
* @ param string $table name of table to lock
* @ param string $mode type of lock required ( optional ), default write
* @ return bool True if sucessful , False if fails
*/
function lock ( $table , $mode = 'write' )
2014-06-25 14:55:16 +02:00
{
unset ( $table , $mode ); // not used anymore
}
2008-03-06 12:42:21 +01:00
/**
* Unlock a table
*
* @ deprecated not used anymore as it costs to much performance , use transactions if needed
* @ return bool True if sucessful , False if fails
*/
function unlock ()
{}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Get the number of rows affected by last update or delete
*
* @ return int number of rows
*/
function affected_rows ()
{
2013-06-12 18:57:44 +02:00
if ( $this -> log_updates ) return 0 ;
2008-03-06 12:42:21 +01:00
if ( ! $this -> Link_ID && ! $this -> connect ())
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return $this -> Link_ID -> Affected_Rows ();
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Number of rows in current result set
*
2008-03-09 15:22:02 +01:00
* @ deprecated use the result - object returned by query / select () -> NumRows (), so you can use the global db - object and not a clone
2008-03-06 12:42:21 +01:00
* @ return int number of rows
*/
function num_rows ()
{
return $this -> Query_ID ? $this -> Query_ID -> RecordCount () : False ;
}
/**
* Number of fields in current row
*
2008-03-08 22:31:12 +01:00
* @ deprecated use the result - object returned by query () or select () direct , so you can use the global db - object and not a clone
2008-03-06 12:42:21 +01:00
* @ return int number of fields
*/
function num_fields ()
{
return $this -> Query_ID ? $this -> Query_ID -> FieldCount () : False ;
}
/**
* @ deprecated use num_rows ()
*/
function nf ()
{
return $this -> num_rows ();
}
/**
* @ deprecated use print num_rows ()
*/
function np ()
{
print $this -> num_rows ();
}
/**
* Return the value of a column
*
2008-03-09 15:22:02 +01:00
* @ deprecated use the result - object returned by query () or select () direct , so you can use the global db - object and not a clone
2015-02-02 21:13:19 +01:00
* @ param string | integer $Name name of field or positional index starting from 0
2008-03-06 12:42:21 +01:00
* @ param bool $strip_slashes string escape chars from field ( optional ), default false
* depricated param , as correctly quoted values dont need any stripslashes !
* @ return string the field value
*/
function f ( $Name , $strip_slashes = False )
{
if ( $strip_slashes )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return stripslashes ( $this -> Record [ $Name ]);
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return $this -> Record [ $Name ];
}
/**
* Print the value of a field
*
* @ param string $Name name of field to print
* @ param bool $strip_slashes string escape chars from field ( optional ), default false
* depricated param , as correctly quoted values dont need any stripslashes !
*/
function p ( $Name , $strip_slashes = True )
{
print $this -> f ( $Name , $strip_slashes );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Returns a query - result - row as an associative array ( no numerical keys !!! )
*
2008-03-08 22:31:12 +01:00
* @ deprecated use foreach ( query () or foreach ( select () to loop over the query using the global db object
2008-03-06 12:42:21 +01:00
* @ param bool $do_next_record should next_record () be called or not ( default not )
2015-02-02 21:13:19 +01:00
* @ param string $strip = '' string to strip of the column - name , default ''
2008-03-06 12:42:21 +01:00
* @ return array / bool the associative array or False if no ( more ) result - row is availible
*/
function row ( $do_next_record = False , $strip = '' )
{
2008-03-09 15:22:02 +01:00
if ( $do_next_record && ! $this -> next_record ( egw_db :: FETCH_ASSOC ) || ! is_array ( $this -> Record ))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return False ;
}
2009-07-18 14:33:52 +02:00
$result = array ();
foreach ( $this -> Record as $column => $value )
{
if ( ! is_numeric ( $column ))
{
if ( $strip ) $column = str_replace ( $strip , '' , $column );
$result [ $column ] = $value ;
}
}
return $result ;
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Get description of a table
*
* Beside the column - name all other data depends on the db - type !!!
*
* @ param string $table name of table to describe
* @ param bool $full optional , default False summary information , True full information
* @ return array table meta data
*/
function metadata ( $table = '' , $full = false )
{
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
}
$columns = $this -> Link_ID -> MetaColumns ( $table );
//$columns = $this->Link_ID->MetaColumnsSQL($table);
//echo "<b>metadata</b>('$table')=<pre>\n".print_r($columns,True)."</pre>\n";
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
$metadata = array ();
$i = 0 ;
foreach ( $columns as $column )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
// for backwards compatibilty (depreciated)
2014-06-25 14:55:16 +02:00
$flags = null ;
2008-03-06 12:42:21 +01:00
if ( $column -> auto_increment ) $flags .= " auto_increment " ;
if ( $column -> primary_key ) $flags .= " primary_key " ;
if ( $column -> binary ) $flags .= " binary " ;
$metadata [ $i ] = array (
'table' => $table ,
'name' => $column -> name ,
'type' => $column -> type ,
'len' => $column -> max_length ,
'flags' => $flags , // for backwards compatibilty (depreciated) used by JiNN atm
'not_null' => $column -> not_null ,
'auto_increment' => $column -> auto_increment ,
'primary_key' => $column -> primary_key ,
'binary' => $column -> binary ,
'has_default' => $column -> has_default ,
'default' => $column -> default_value ,
);
$metadata [ $i ][ 'table' ] = $table ;
if ( $full )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$metadata [ 'meta' ][ $column -> name ] = $i ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
++ $i ;
}
if ( $full )
{
$metadata [ 'num_fields' ] = $i ;
}
return $metadata ;
}
/**
2014-06-25 14:43:11 +02:00
* Get a list of table names in the current database
*
2015-02-02 21:13:19 +01:00
* @ param boolean $just_name = false true return array of table - names , false return old format
2014-06-25 14:43:11 +02:00
* @ return array list of the tables
*/
function table_names ( $just_name = false )
2008-03-06 12:42:21 +01:00
{
if ( ! $this -> Link_ID ) $this -> connect ();
if ( ! $this -> Link_ID )
{
return False ;
}
$result = array ();
$tables = $this -> Link_ID -> MetaTables ( 'TABLES' );
if ( is_array ( $tables ))
{
foreach ( $tables as $table )
2005-06-19 14:43:00 +02:00
{
2008-06-07 10:24:18 +02:00
if ( $this -> capabilities [ self :: CAPABILITY_NAME_CASE ] == 'upper' )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$table = strtolower ( $table );
2005-06-19 14:43:00 +02:00
}
2014-06-25 14:43:11 +02:00
$result [] = $just_name ? $table : array (
2008-03-06 12:42:21 +01:00
'table_name' => $table ,
'tablespace_name' => $this -> Database ,
'database' => $this -> Database
);
2005-06-19 14:43:00 +02:00
}
}
2008-03-06 12:42:21 +01:00
return $result ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Return a list of indexes in current database
*
* @ return array list of indexes
*/
function index_names ()
{
$indices = array ();
if ( $this -> Type != 'pgsql' )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
echo " <p>db::index_names() not yet implemented for db-type ' $this->Type '</p> \n " ;
return $indices ;
}
$this -> query ( " SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relkind ='i' ORDER BY relname " );
while ( $this -> next_record ())
{
$indices [] = array (
'index_name' => $this -> f ( 0 ),
'tablespace_name' => $this -> Database ,
'database' => $this -> Database ,
);
}
return $indices ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Returns an array containing column names that are the primary keys of $tablename .
*
* @ return array of columns
*/
function pkey_columns ( $tablename )
{
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
}
return $this -> Link_ID -> MetaPrimaryKeys ( $tablename );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Create a new database
*
* @ param string $adminname name of database administrator user ( optional )
* @ param string $adminpasswd password for the database administrator user ( optional )
* @ param string $charset default charset for the database
2015-02-02 21:13:19 +01:00
* @ param string $grant_host = 'localhost' host / ip of the webserver
2008-03-06 12:42:21 +01:00
*/
2008-11-18 20:58:11 +01:00
function create_database ( $adminname = '' , $adminpasswd = '' , $charset = '' , $grant_host = 'localhost' )
2008-03-06 12:42:21 +01:00
{
$currentUser = $this -> User ;
$currentPassword = $this -> Password ;
$currentDatabase = $this -> Database ;
2005-06-19 14:43:00 +02:00
2015-07-07 14:41:11 +02:00
if ( $adminname != '' )
{
$this -> User = $adminname ;
$this -> Password = $adminpasswd ;
$this -> Database = $this -> Type == 'pgsql' ? 'template1' : 'mysql' ;
}
$this -> disconnect ();
2008-03-06 12:42:21 +01:00
$sqls = array ();
switch ( $this -> Type )
{
case 'pgsql' :
$sqls [] = " CREATE DATABASE $currentDatabase " ;
break ;
case 'mysql' :
2015-07-07 14:41:11 +02:00
case 'mysqli' :
case 'mysqlt' :
2008-03-06 12:42:21 +01:00
$create = " CREATE DATABASE ` $currentDatabase ` " ;
if ( $charset && isset ( $this -> Link_ID -> charset2mysql [ $charset ]) && ( float ) $this -> ServerInfo [ 'version' ] >= 4.1 )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$create .= ' DEFAULT CHARACTER SET ' . $this -> Link_ID -> charset2mysql [ $charset ] . ';' ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$sqls [] = $create ;
2008-11-18 20:58:11 +01:00
$sqls [] = " GRANT ALL ON ` $currentDatabase `.* TO $currentUser @' $grant_host ' IDENTIFIED BY " . $this -> quote ( $currentPassword );
2008-03-06 12:42:21 +01:00
break ;
default :
echo " <p>db::create_database(user=' $adminname ', \$ pw) not yet implemented for DB-type ' $this->Type '</p> \n " ;
break ;
}
foreach ( $sqls as $sql )
{
$this -> query ( $sql , __LINE__ , __FILE__ );
}
$this -> disconnect ();
$this -> User = $currentUser ;
$this -> Password = $currentPassword ;
$this -> Database = $currentDatabase ;
$this -> connect ();
}
/**
* concat a variable number of strings together , to be used in a query
*
* Example : $db -> concat ( $db -> quote ( 'Hallo ' ), 'username' ) would return
* for mysql " concat('Hallo ',username) " or " 'Hallo ' || username " for postgres
* @ param string $str1 already quoted stringliteral or column - name , variable number of arguments
* @ return string to be used in a query
*/
2014-06-25 14:55:16 +02:00
function concat ( /*$str1, ...*/ )
2008-03-06 12:42:21 +01:00
{
$args = func_get_args ();
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
}
return call_user_func_array ( array ( & $this -> Link_ID , 'concat' ), $args );
}
2008-06-07 10:24:18 +02:00
2013-06-28 12:50:42 +02:00
/**
* Concat grouped values of an expression with optional order and separator
*
* @ param string $expr column - name or expression optional prefixed with " DISTINCT "
2015-02-02 21:13:19 +01:00
* @ param string $order_by = '' optional order
* @ param string $separator = ',' optional separator , default is comma
2013-11-08 20:54:08 +01:00
* @ return string | boolean false if not supported by dbms
2013-06-28 12:50:42 +02:00
*/
function group_concat ( $expr , $order_by = '' , $separator = ',' )
{
switch ( $this -> Type )
{
case 'mysql' :
$sql = 'GROUP_CONCAT(' . $expr ;
if ( $order_by ) $sql .= ' ORDER BY ' . $order_by ;
if ( $separator != ',' ) $sql .= ' SEPARATOR ' . $this -> quote ( $separator );
$sql .= ')' ;
break ;
case 'pgsql' : // requires for Postgresql < 8.4 to have a custom ARRAY_AGG method installed!
2013-11-08 20:54:08 +01:00
if ( $this -> Type == 'pgsql' && $this -> ServerInfo [ 'version' ] < 8.4 )
{
return false ;
}
2013-06-28 12:50:42 +02:00
$sql = 'ARRAY_TO_STRING(ARRAY_AGG(' . $expr ;
if ( $order_by ) $sql .= ' ORDER BY ' . $order_by ;
$sql .= '), ' . $this -> quote ( $separator ) . ')' ;
break ;
default : // probably gives an sql error anyway
2013-11-08 20:54:08 +01:00
return false ;
2013-06-28 12:50:42 +02:00
}
return $sql ;
}
2010-09-09 11:06:13 +02:00
/**
* Convert a DB specific timestamp in a unix timestamp stored as integer , like MySQL : UNIX_TIMESTAMP ( ts )
*
* @ param string $expr name of an integer column or integer expression
* @ return string SQL expression of type timestamp
*/
function unix_timestamp ( $expr )
{
switch ( $this -> Type )
{
case 'mysql' :
return " UNIX_TIMESTAMP( $expr ) " ;
case 'pgsql' :
2012-07-10 15:50:45 +02:00
return " EXTRACT(EPOCH FROM CAST( $expr AS TIMESTAMP)) " ;
2010-09-09 11:06:13 +02:00
case 'mssql' :
return " DATEDIFF(second,'1970-01-01',( $expr )) " ;
}
}
2008-03-06 12:42:21 +01:00
/**
* Convert a unix timestamp stored as integer in the db into a db timestamp , like MySQL : FROM_UNIXTIME ( ts )
*
* @ param string $expr name of an integer column or integer expression
* @ return string SQL expression of type timestamp
*/
function from_unixtime ( $expr )
{
switch ( $this -> Type )
{
case 'mysql' :
return " FROM_UNIXTIME( $expr ) " ;
2009-05-30 09:03:57 +02:00
case 'pgsql' :
2012-07-10 15:50:45 +02:00
return " (TIMESTAMP WITH TIME ZONE 'epoch' + ( $expr ) * INTERVAL '1 sec') " ;
2008-03-06 12:42:21 +01:00
case 'mssql' : // we use date(,0) as we store server-time
return " DATEADD(second,( $expr ),' " . date ( 'Y-m-d H:i:s' , 0 ) . " ') " ;
}
return false ;
}
/**
* format a timestamp as string , like MySQL : DATE_FORMAT ( ts )
*
* Please note : only a subset of the MySQL formats are implemented
*
* @ param string $expr name of a timestamp column or timestamp expression
* @ param string $format format specifier like '%Y-%m-%d %H:%i:%s' or '%V%X' ( '%v%x' ) weeknumber & year with Sunday ( Monday ) as first day
* @ return string SQL expression of type timestamp
*/
function date_format ( $expr , $format )
{
switch ( $this -> Type )
{
case 'mysql' :
return " DATE_FORMAT( $expr ,' $format ') " ;
case 'pgsql' :
$format = str_replace (
array ( '%Y' , '%y' , '%m' , '%d' , '%H' , '%h' , '%i' , '%s' , '%V' , '%v' , '%X' , '%x' ),
array ( 'YYYY' , 'YY' , 'MM' , 'DD' , 'HH24' , 'HH' , 'MI' , 'SS' , 'IW' , 'IW' , 'YYYY' , 'YYYY' ),
$format );
return " TO_CHAR( $expr ,' $format ') " ;
case 'mssql' :
$from = $to = array ();
foreach ( array ( '%Y' => 'yyyy' , '%y' => 'yy' , '%m' => 'mm' , '%d' => 'dd' , '%H' => 'hh' , '%i' => 'mi' , '%s' => 'ss' , '%V' => 'wk' , '%v' => 'wk' , '%X' => 'yyyy' , '%x' => 'yyyy' ) as $f => $t )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$from [] = $f ;
$to [] = " '+DATEPART( $t ,( $expr ))+' " ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$from [] = " ''+ " ; $to [] = '' ;
$from [] = " +'' " ; $to [] = '' ;
return str_replace ( $from , $to , $format );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return false ;
}
2010-10-28 10:47:52 +02:00
2015-03-23 09:38:30 +01:00
/**
* Cast a column or sql expression to integer , necessary at least for postgreSQL or MySQL for sorting
*
* @ param string $expr
* @ return string
*/
function to_double ( $expr )
{
switch ( $this -> Type )
{
case 'pgsql' :
return $expr . '::double' ;
case 'mysql' :
return 'CAST(' . $expr . ' AS DECIMAL(24,3))' ;
}
return $expr ;
}
2010-09-10 09:11:22 +02:00
/**
* Cast a column or sql expression to integer , necessary at least for postgreSQL
2010-10-28 10:47:52 +02:00
*
2010-09-10 09:11:22 +02:00
* @ param string $expr
* @ return string
*/
function to_int ( $expr )
{
switch ( $this -> Type )
{
case 'pgsql' :
return $expr . '::integer' ;
2015-03-23 09:38:30 +01:00
case 'mysql' :
return 'CAST(' . $expr . ' AS SIGNED)' ;
2010-09-10 09:11:22 +02:00
}
return $expr ;
}
2005-06-19 14:43:00 +02:00
2010-10-28 10:47:52 +02:00
/**
* Cast a column or sql expression to varchar , necessary at least for postgreSQL
*
* @ param string $expr
* @ return string
*/
function to_varchar ( $expr )
{
switch ( $this -> Type )
{
case 'pgsql' :
return 'CAST(' . $expr . ' AS varchar)' ;
}
return $expr ;
}
2008-03-06 12:42:21 +01:00
/**
* Correctly Quote Identifiers like table - or colmnnames for use in SQL - statements
*
* This is mostly copy & paste from adodb ' s datadict class
2015-02-02 21:13:19 +01:00
* @ param string $_name
2008-03-06 12:42:21 +01:00
* @ return string quoted string
*/
2015-02-02 21:13:19 +01:00
function name_quote ( $_name = NULL )
2008-03-06 12:42:21 +01:00
{
2015-02-02 21:13:19 +01:00
if ( ! is_string ( $_name ))
{
return false ;
2005-06-19 14:43:00 +02:00
}
2015-02-02 21:13:19 +01:00
$name = trim ( $_name );
2008-03-06 12:42:21 +01:00
if ( ! $this -> Link_ID && ! $this -> connect ())
2005-06-19 14:43:00 +02:00
{
2015-02-02 21:13:19 +01:00
return false ;
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
$quote = $this -> Link_ID -> nameQuote ;
2015-02-02 21:13:19 +01:00
$type = $this -> Type ;
2005-06-19 14:43:00 +02:00
2015-02-02 21:13:19 +01:00
// if name is of the form `name`, remove MySQL quotes and leave it to automatic below
if ( $name [ 0 ] === '`' && substr ( $name , - 1 ) === '`' )
{
$name = substr ( $name , 1 , - 1 );
2008-03-06 12:42:21 +01:00
}
2015-02-02 21:13:19 +01:00
$quoted = array_map ( function ( $name ) use ( $quote , $type )
2012-03-08 07:20:21 +01:00
{
2015-02-02 21:13:19 +01:00
// if name contains special characters, quote it
// always quote for postgreSQL, as this is the only way to support mixed case names
2015-03-30 10:28:52 +02:00
if ( preg_match ( '/\W/' , $name ) || $type == 'pgsql' && preg_match ( '/[A-Z]+/' , $name ) || $name == 'index' )
2015-02-02 21:13:19 +01:00
{
return $quote . $name . $quote ;
}
return $name ;
}, explode ( '.' , $name ));
2008-03-06 12:42:21 +01:00
2015-02-02 21:13:19 +01:00
return implode ( '.' , $quoted );
2008-03-06 12:42:21 +01:00
}
/**
* Escape values before sending them to the database - prevents SQL injection and SQL errors ; - )
*
* Please note that the quote function already returns necessary quotes : quote ( 'Hello' ) === " 'Hello' " .
* Int and Auto types are casted to int : quote ( '1' , 'int' ) === 1 , quote ( '' , 'int' ) === 0 , quote ( 'Hello' , 'int' ) === 0
* Arrays of id 's stored in strings: quote(array(1,2,3),' string ') === "' 1 , 2 , 3 ' "
*
* @ param mixed $value the value to be escaped
2015-02-02 21:13:19 +01:00
* @ param string | boolean $type = false string the type of the db - column , default False === varchar
* @ param boolean $not_null = true is column NOT NULL , default true , else php null values are written as SQL NULL
* @ param int $length = null length of the varchar column , to truncate it if the database requires it ( eg . Postgres )
* @ param string $glue = ',' used to glue array values together for the string type
2008-03-06 12:42:21 +01:00
* @ return string escaped sting
*/
function quote ( $value , $type = False , $not_null = true , $length = null , $glue = ',' )
{
if ( $this -> Debug ) echo " <p>db::quote( " . ( is_null ( $value ) ? 'NULL' : " ' $value ' " ) . " ,' $type ',' $not_null ')</p> \n " ;
if ( ! $not_null && is_null ( $value )) // writing unset php-variables and those set to NULL now as SQL NULL
{
return 'NULL' ;
}
switch ( $type )
{
case 'int' :
2009-10-08 18:20:37 +02:00
// if DateTime object given, convert it to a unix timestamp (NOT converting the timezone!)
2011-09-26 11:52:43 +02:00
if ( is_object ( $value ) && ( $value instanceof DateTime ))
2009-10-08 18:20:37 +02:00
{
2011-09-26 11:52:43 +02:00
return ( $value instanceof egw_time ) ? $value -> format ( 'ts' ) : egw_time :: to ( $value , 'ts' );
2009-10-08 18:20:37 +02:00
}
2008-06-07 10:24:18 +02:00
case 'auto' :
// atm. (php5.2) php has only 32bit integers, it converts everything else to float.
2008-03-06 12:42:21 +01:00
// Casting it to int gives a negative number instead of the big 64bit integer!
// There for we have to keep it as float by using round instead the int cast.
return is_float ( $value ) ? round ( $value ) : ( int ) $value ;
case 'bool' :
if ( $this -> Type == 'mysql' ) // maybe it's not longer necessary with mysql5
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return $value ? 1 : 0 ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
return $value ? 'true' : 'false' ;
case 'float' :
case 'decimal' :
return ( double ) $value ;
}
if ( ! $this -> Link_ID && ! $this -> connect ())
{
return False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
switch ( $type )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
case 'blob' :
switch ( $this -> Link_ID -> blobEncodeType )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
case 'C' : // eg. postgres
return " ' " . $this -> Link_ID -> BlobEncode ( $value ) . " ' " ;
case 'I' :
return $this -> Link_ID -> BlobEncode ( $value );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
break ; // handled like strings
case 'date' :
2009-10-08 18:20:37 +02:00
// if DateTime object given, convert it (NOT converting the timezone!)
2011-09-26 11:52:43 +02:00
if ( is_object ( $value ) && ( $value instanceof DateTime ))
2009-10-08 18:20:37 +02:00
{
return $this -> Link_ID -> qstr ( $value -> format ( 'Y-m-d' ));
}
2008-03-06 12:42:21 +01:00
return $this -> Link_ID -> DBDate ( $value );
case 'timestamp' :
2009-10-08 18:20:37 +02:00
// if DateTime object given, convert it (NOT converting the timezone!)
2011-09-26 11:52:43 +02:00
if ( is_object ( $value ) && ( $value instanceof DateTime ))
2009-10-08 18:20:37 +02:00
{
return $this -> Link_ID -> qstr ( $value -> format ( 'Y-m-d H:i:s' ));
}
2008-03-06 12:42:21 +01:00
return $this -> Link_ID -> DBTimeStamp ( $value );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( is_array ( $value ))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$value = implode ( $glue , $value );
2005-06-19 14:43:00 +02:00
}
2012-08-13 16:42:55 +02:00
// only truncate string if length given and <= 255
// to not unnecessary truncate varchar(>255) as PostgreSQL uses text anyway and MySQL truncates itself silently (unless strict mode!)
2014-02-03 20:57:50 +01:00
if ( ! is_null ( $length ) && $length <= 255 && mb_strlen ( $value ) > $length )
2005-06-19 14:43:00 +02:00
{
2014-02-03 20:57:50 +01:00
$value = mb_substr ( $value , 0 , $length );
2005-06-19 14:43:00 +02:00
}
2011-10-25 09:49:54 +02:00
// casting boolean explicitly to string, as ADODB_postgres64::qstr() has an unwanted special handling
// for boolean types, causing it to return "true" or "false" and not a quoted string like "'1'"!
if ( is_bool ( $value )) $value = ( string ) $value ;
2008-03-06 12:42:21 +01:00
return $this -> Link_ID -> qstr ( $value );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Implodes an array of column - value pairs for the use in sql - querys .
* All data is run through quote ( does either addslashes () or ( int )) - prevents SQL injunction and SQL errors ; - ) .
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param string $glue in most cases this will be either ',' or ' AND ' , depending you your query
* @ param array $array column - name / value pairs , if the value is an array all its array - values will be quoted
* according to the type of the column , and the whole array with be formatted like ( val1 , val2 , ... )
* If $use_key == True , an ' IN ' instead a '=' is used . Good for category - or user - lists .
* If the key is numerical ( no key given in the array - definition ) the value is used as is , eg .
* array ( 'visits=visits+1' ) gives just " visits=visits+1 " ( no quoting at all !!! )
2015-02-02 21:13:19 +01:00
* @ param boolean | string $use_key If $use_key === True a " $key = " prefix each value ( default ), typically set to False
2008-03-06 12:42:21 +01:00
* or 'VALUES' for insert querys , on 'VALUES' " (key1,key2,...) VALUES (val1,val2,...) " is returned
2015-02-02 21:13:19 +01:00
* @ param array | boolean $only if set to an array only colums which are set ( as data !!! ) are written
2008-03-06 12:42:21 +01:00
* typicaly used to form a WHERE - clause from the primary keys .
* If set to True , only columns from the colum_definitons are written .
2015-02-02 21:13:19 +01:00
* @ param array | boolean $column_definitions this can be set to the column - definitions - array
2008-03-06 12:42:21 +01:00
* of your table ( $tables_baseline [ $table ][ 'fd' ] of the setup / tables_current . inc . php file ) .
* If its set , the column - type - data determinates if ( int ) or addslashes is used .
* @ return string SQL
*/
function column_data_implode ( $glue , $array , $use_key = True , $only = False , $column_definitions = False )
{
if ( ! is_array ( $array )) // this allows to give an SQL-string for delete or update
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
return $array ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( ! $column_definitions )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$column_definitions = $this -> column_definitions ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( $this -> Debug ) echo " <p>db::column_data_implode(' $glue ', " . print_r ( $array , True ) . " ,' $use_key ', " . print_r ( $only , True ) . " ,<pre> " . print_r ( $column_definitions , True ) . " </pre> \n " ;
2006-04-05 17:14:59 +02:00
2008-03-06 12:42:21 +01:00
// do we need to truncate varchars to their max length (INSERT and UPDATE on Postgres)
2008-06-07 10:24:18 +02:00
$truncate_varchar = $glue == ',' && $this -> capabilities [ self :: CAPABILITY_REQUIRE_TRUNCATE_VARCHAR ];
2006-04-05 17:14:59 +02:00
2008-03-06 12:42:21 +01:00
$keys = $values = array ();
foreach ( $array as $key => $data )
2006-04-05 17:14:59 +02:00
{
2013-11-11 09:21:54 +01:00
if ( is_int ( $key ) && $use_key !== 'VALUES' || ! $only || $only === True && isset ( $column_definitions [ $key ]) ||
2008-03-06 12:42:21 +01:00
is_array ( $only ) && in_array ( $key , $only ))
2006-04-05 17:14:59 +02:00
{
2008-03-06 12:42:21 +01:00
$keys [] = $this -> name_quote ( $key );
2006-04-05 17:14:59 +02:00
2014-12-10 16:04:03 +01:00
$col = $key ;
// fix "table.column" expressions, to not trigger exception, if column alone would work
2008-03-06 12:42:21 +01:00
if ( ! is_int ( $key ) && is_array ( $column_definitions ) && ! isset ( $column_definitions [ $key ]))
{
2014-12-10 16:04:03 +01:00
if ( strpos ( $key , '.' ) !== false ) list (, $col ) = explode ( '.' , $key );
if ( ! isset ( $column_definitions [ $col ]))
{
throw new egw_exception_db_invalid_sql ( " db::column_data_implode(' $glue ', " . print_r ( $array , True ) . " ,' $use_key ', " . print_r ( $only , True ) . " ,<pre> " . print_r ( $column_definitions , True ) . " </pre><b>nothing known about column ' $key '!</b> " );
}
2008-03-06 12:42:21 +01:00
}
2014-12-10 16:04:03 +01:00
$column_type = is_array ( $column_definitions ) ? @ $column_definitions [ $col ][ 'type' ] : False ;
$not_null = is_array ( $column_definitions ) && isset ( $column_definitions [ $col ][ 'nullable' ]) ? ! $column_definitions [ $col ][ 'nullable' ] : false ;
2008-06-07 10:24:18 +02:00
2008-03-06 12:42:21 +01:00
if ( $truncate_varchar )
{
2015-08-13 14:47:52 +02:00
$maxlength = in_array ( $column_definitions [ $col ][ 'type' ], array ( 'varchar' , 'ascii' )) ? $column_definitions [ $col ][ 'precision' ] : null ;
2008-03-06 12:42:21 +01:00
}
2010-03-04 18:18:30 +01:00
// dont use IN ( ), if there's only one value, it's slower for MySQL
if ( is_array ( $data ) && count ( $data ) == 1 )
{
$data = array_shift ( $data );
}
2008-03-06 12:42:21 +01:00
if ( is_array ( $data ))
{
$or_null = '' ;
foreach ( $data as $k => $v )
2006-04-05 17:14:59 +02:00
{
2008-03-06 12:42:21 +01:00
if ( ! $not_null && $use_key === True && is_null ( $v ))
{
$or_null = $this -> name_quote ( $key ) . ' IS NULL)' ;
unset ( $data [ $k ]);
continue ;
}
$data [ $k ] = $this -> quote ( $v , $column_type , $not_null , $maxlength );
2006-04-05 17:14:59 +02:00
}
2014-04-15 10:15:23 +02:00
$values [] = ( $or_null ? '(' : '' ) . ( ! count ( $data ) ?
// empty array on insert/update, store as NULL, or if not allowed whatever value NULL is casted to
$this -> quote ( null , $column_type , $not_null ) :
2008-03-06 12:42:21 +01:00
( $use_key === True ? $this -> name_quote ( $key ) . ' IN ' : '' ) .
'(' . implode ( ',' , $data ) . ')' . ( $or_null ? ' OR ' : '' )) . $or_null ;
}
elseif ( is_int ( $key ) && $use_key === True )
{
2015-04-24 15:50:33 +02:00
if ( empty ( $data )) continue ; // would give SQL error
2008-03-06 12:42:21 +01:00
$values [] = $data ;
}
elseif ( $glue != ',' && $use_key === True && ! $not_null && is_null ( $data ))
{
$values [] = $this -> name_quote ( $key ) . ' IS NULL' ;
}
else
{
$values [] = ( $use_key === True ? $this -> name_quote ( $key ) . '=' : '' ) . $this -> quote ( $data , $column_type , $not_null , $maxlength );
}
2006-04-05 17:14:59 +02:00
}
}
2008-03-06 12:42:21 +01:00
return ( $use_key === 'VALUES' ? '(' . implode ( ',' , $keys ) . ') VALUES (' : '' ) .
implode ( $glue , $values ) . ( $use_key === 'VALUES' ? ')' : '' );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Sets the default column - definitions for use with column_data_implode ()
*
* @ author RalfBecker < at > outdoor - training . de
*
2015-02-02 21:13:19 +01:00
* @ param array | boolean $column_definitions this can be set to the column - definitions - array
2008-03-06 12:42:21 +01:00
* of your table ( $tables_baseline [ $table ][ 'fd' ] of the setup / tables_current . inc . php file ) .
* If its set , the column - type - data determinates if ( int ) or addslashes is used .
*/
function set_column_definitions ( $column_definitions = False )
{
$this -> column_definitions = $column_definitions ;
}
2008-06-07 10:24:18 +02:00
2008-03-22 13:32:24 +01:00
/**
* Application name used by the API
*
*/
const API_APPNAME = 'phpgwapi' ;
/**
* Default app , if no app specified in select , insert , delete , ...
*
* @ var string
*/
private $app = self :: API_APPNAME ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Sets the application in which the db - class looks for table - defintions
*
* Used by table_definitions , insert , update , select , expression and delete . If the app is not set via set_app ,
* it need to be set for these functions on every call
*
* @ param string $app the app - name
*/
function set_app ( $app )
{
2008-03-22 13:32:24 +01:00
if ( $this === $GLOBALS [ 'egw' ] -> db && $app != self :: API_APPNAME )
{
// prevent that anyone switches the global db object to an other app
throw new egw_exception_wrong_parameter ( 'You are not allowed to call set_app for $GLOBALS[egw]->db or a refence to it, you have to clone it!' );
}
2008-03-06 12:42:21 +01:00
$this -> app = $app ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* reads the table - definitions from the app ' s setup / tables_current . inc . php file
*
2012-04-05 09:05:22 +02:00
* The already read table - definitions are shared between all db - instances via a static var .
2008-03-06 12:42:21 +01:00
*
* @ author RalfBecker < at > outdoor - training . de
*
2012-04-12 15:25:27 +02:00
* @ param bool | string $app name of the app or default False to use the app set by db :: set_app or the current app ,
* true to search the already loaded table - definitions for $table and then search all existing apps for it
* @ param bool | string $table if set return only defintions of that table , else return all defintions
2008-03-06 12:42:21 +01:00
* @ return mixed array with table - defintions or False if file not found
*/
function get_table_definitions ( $app = False , $table = False )
{
2012-04-12 15:25:27 +02:00
static $all_app_data = array ();
if ( $app === true && $table )
2008-03-06 12:42:21 +01:00
{
2012-04-05 09:05:22 +02:00
foreach ( $all_app_data as $app => & $app_data )
2005-06-19 14:43:00 +02:00
{
2012-04-05 09:05:22 +02:00
if ( isset ( $app_data [ $table ]))
2008-03-06 12:42:21 +01:00
{
2012-04-05 09:05:22 +02:00
return $app_data [ $table ];
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
}
2012-04-12 15:25:27 +02:00
// $table not found in loaded apps, check not yet loaded ones
foreach ( scandir ( EGW_INCLUDE_ROOT ) as $app )
{
if ( $app [ 0 ] == '.' || ! is_dir ( EGW_INCLUDE_ROOT . '/' . $app ) || isset ( $all_app_data [ $app ]))
{
continue ;
}
$tables_current = EGW_INCLUDE_ROOT . " / $app /setup/tables_current.inc.php " ;
if ( !@ file_exists ( $tables_current ))
{
$all_app_data [ $app ] = False ;
}
else
{
2014-06-25 14:55:16 +02:00
$phpgw_baseline = null ;
2012-04-12 15:25:27 +02:00
include ( $tables_current );
$all_app_data [ $app ] =& $phpgw_baseline ;
unset ( $phpgw_baseline );
if ( isset ( $all_app_data [ $app ][ $table ]))
{
return $all_app_data [ $app ][ $table ];
}
}
}
2008-03-06 12:42:21 +01:00
$app = false ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( ! $app )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$app = $this -> app ? $this -> app : $GLOBALS [ 'egw_info' ][ 'flags' ][ 'currentapp' ];
}
2012-04-05 09:05:22 +02:00
$app_data =& $all_app_data [ $app ];
if ( ! isset ( $app_data ))
2008-03-06 12:42:21 +01:00
{
$tables_current = EGW_INCLUDE_ROOT . " / $app /setup/tables_current.inc.php " ;
if ( !@ file_exists ( $tables_current ))
2005-06-19 14:43:00 +02:00
{
2012-04-05 09:05:22 +02:00
return $app_data = False ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
include ( $tables_current );
2012-04-05 09:05:22 +02:00
$app_data =& $phpgw_baseline ;
2008-03-06 12:42:21 +01:00
unset ( $phpgw_baseline );
}
2012-04-05 09:05:22 +02:00
if ( $table && ( ! $app_data || ! isset ( $app_data [ $table ])))
2008-03-06 12:42:21 +01:00
{
2008-03-10 22:32:13 +01:00
if ( $this -> Debug ) echo " <p>!!!get_table_definitions( $app , $table ) failed!!!</p> \n " ;
2008-03-06 12:42:21 +01:00
return False ;
}
2008-03-10 22:32:13 +01:00
if ( $this -> Debug ) echo " <p>get_table_definitions( $app , $table ) succeeded</p> \n " ;
2012-04-05 09:05:22 +02:00
return $table ? $app_data [ $table ] : $app_data ;
2008-03-06 12:42:21 +01:00
}
2005-06-19 14:43:00 +02:00
2009-05-30 08:58:48 +02:00
/**
* Get specified attribute ( default comment ) of a colum or whole definition ( if $attribute === null )
*
* Can be used static , in which case the global db object is used ( $GLOBALS [ 'egw' ] -> db ) and $app should be specified
*
* @ param string $column name of column
* @ param string $table name of table
* @ param string $app = null app name or NULL to use $this -> app , set via egw_db :: set_app ()
* @ param string $attribute = 'comment' what field to return , NULL for array with all fields , default 'comment' to return the comment
* @ return string | array NULL if table or column or attribute not found
*/
/* static */ function get_column_attribute ( $column , $table , $app = null , $attribute = 'comment' )
{
2014-06-25 14:55:16 +02:00
static $cached_columns = null , $cached_table = null ; // some caching
2009-05-30 08:58:48 +02:00
if ( $cached_table !== $table || is_null ( $cached_columns ))
{
$db = isset ( $this ) ? $this : $GLOBALS [ 'egw' ] -> db ;
$table_def = $db -> get_table_definitions ( $app , $table );
$cached_columns = is_array ( $table_def ) ? $table_def [ 'fd' ] : false ;
}
if ( $cached_columns === false ) return null ;
return is_null ( $attribute ) ? $cached_columns [ $column ] : $cached_columns [ $column ][ $attribute ];
}
2008-03-06 12:42:21 +01:00
/**
* Insert a row of data into a table or updates it if $where is given , all data is quoted according to it ' s type
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param string $table name of the table
* @ param array $data with column - name / value pairs
* @ param mixed $where string with where clause or array with column - name / values pairs to check if a row with that keys already exists , or false for an unconditional insert
* if the row exists db :: update is called else a new row with $date merged with $where gets inserted ( data has precedence )
* @ param int $line line - number to pass to query
* @ param string $file file - name to pass to query
2015-02-02 21:13:19 +01:00
* @ param string | boolean $app string with name of app or False to use the current - app
2008-03-06 12:42:21 +01:00
* @ param bool $use_prepared_statement use a prepared statement
2015-02-02 21:13:19 +01:00
* @ param array | bool $table_def use this table definition . If False , the table definition will be read from tables_baseline
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
function insert ( $table , $data , $where , $line , $file , $app = False , $use_prepared_statement = false , $table_def = False )
{
if ( $this -> Debug ) echo " <p>db::insert(' $table ', " . print_r ( $data , True ) . " , " . print_r ( $where , True ) . " , $line , $file ,' $app ')</p> \n " ;
2007-06-20 20:47:14 +02:00
2008-03-06 12:42:21 +01:00
if ( ! $table_def ) $table_def = $this -> get_table_definitions ( $app , $table );
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
$sql_append = '' ;
$cmd = 'INSERT' ;
if ( is_array ( $where ) && count ( $where ))
{
switch ( $this -> Type )
{
case 'sapdb' : case 'maxdb' :
$sql_append = ' UPDATE DUPLICATES' ;
break ;
case 'mysql' :
// use replace if primary keys are included
if ( count ( array_intersect ( array_keys ( $where ),( array ) $table_def [ 'pk' ])) == count ( $table_def [ 'pk' ]))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$cmd = 'REPLACE' ;
break ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
// fall through !!!
default :
$this -> select ( $table , 'count(*)' , $where , $line , $file );
if ( $this -> next_record () && $this -> f ( 0 ))
2005-06-19 14:43:00 +02:00
{
2014-10-07 23:04:58 +02:00
return !! $this -> update ( $table , $data , $where , $line , $file , $app , $use_prepared_statement , $table_def );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
break ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
// the checked values need to be inserted too, value in data has precedence, also cant insert sql strings (numerical id)
foreach ( $where as $column => $value )
2006-04-23 16:50:48 +02:00
{
2015-04-24 15:50:33 +02:00
if ( ! is_numeric ( $column ) && ! isset ( $data [ $column ]) &&
// skip auto-id of 0 or NULL, as PostgreSQL does NOT create an auto-id, if they are given
! ( ! $value && count ( $table_def [ 'pk' ]) == 1 && $column == $table_def [ 'pk' ][ 0 ]))
2006-04-23 16:50:48 +02:00
{
2008-03-06 12:42:21 +01:00
$data [ $column ] = $value ;
2006-04-23 16:50:48 +02:00
}
}
2008-03-06 12:42:21 +01:00
}
2008-06-07 10:24:18 +02:00
if ( self :: $tablealiases && isset ( self :: $tablealiases [ $table ]))
{
$table = self :: $tablealiases [ $table ];
}
2008-03-06 12:42:21 +01:00
$inputarr = false ;
2012-06-10 14:38:35 +02:00
if ( isset ( $data [ 0 ]) && is_array ( $data [ 0 ])) // multiple data rows
{
if ( $where ) throw new egw_exception_wrong_parameter ( 'Can NOT use $where together with multiple data rows in $data!' );
$sql = " $cmd INTO $table " ;
foreach ( $data as $k => $d )
{
if ( ! $k )
{
$sql .= $this -> column_data_implode ( ',' , $d , 'VALUES' , true , $table_def [ 'fd' ]);
}
else
{
$sql .= " , \n ( " . $this -> column_data_implode ( ',' , $d , false , true , $table_def [ 'fd' ]) . ')' ;
}
}
$sql .= $sql_append ;
}
elseif ( $use_prepared_statement && $this -> Link_ID -> _bindInputArray ) // eg. MaxDB
2008-03-06 12:42:21 +01:00
{
$this -> Link_ID -> Param ( false ); // reset param-counter
$cols = array_keys ( $data );
foreach ( $cols as $k => $col )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
if ( ! isset ( $table_def [ 'fd' ][ $col ])) // ignore columns not in this table
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
unset ( $cols [ $k ]);
continue ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$params [] = $this -> Link_ID -> Param ( $col );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$sql = " $cmd INTO $table ( " . implode ( ',' , $cols ) . ') VALUES (' . implode ( ',' , $params ) . ')' . $sql_append ;
// check if we already prepared that statement
if ( ! isset ( $this -> prepared_sql [ $sql ]))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
$this -> prepared_sql [ $sql ] = $this -> Link_ID -> Prepare ( $sql );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$sql = $this -> prepared_sql [ $sql ];
$inputarr = & $data ;
}
else
{
$sql = " $cmd INTO $table " . $this -> column_data_implode ( ',' , $data , 'VALUES' , true , $table_def [ 'fd' ]) . $sql_append ;
}
if ( $this -> Debug ) echo " <p>db::insert(' $table ', " . print_r ( $data , True ) . " , " . print_r ( $where , True ) . " , $line , $file ,' $app ') sql=' $sql '</p> \n " ;
return $this -> query ( $sql , $line , $file , 0 , - 1 , $inputarr );
}
/**
* Updates the data of one or more rows in a table , all data is quoted according to it ' s type
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param string $table name of the table
* @ param array $data with column - name / value pairs
* @ param array $where column - name / values pairs and ' ed together for the where clause
* @ param int $line line - number to pass to query
* @ param string $file file - name to pass to query
2015-02-02 21:13:19 +01:00
* @ param string | boolean $app string with name of app or False to use the current - app
2008-03-06 12:42:21 +01:00
* @ param bool $use_prepared_statement use a prepared statement
2015-02-02 21:13:19 +01:00
* @ param array | bool $table_def use this table definition . If False , the table definition will be read from tables_baseline
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
function update ( $table , $data , $where , $line , $file , $app = False , $use_prepared_statement = false , $table_def = False )
{
if ( $this -> Debug ) echo " <p>db::update(' $table ', " . print_r ( $data , true ) . ',' . print_r ( $where , true ) . " , $line , $file ,' $app ')</p> \n " ;
if ( ! $table_def ) $table_def = $this -> get_table_definitions ( $app , $table );
$blobs2update = array ();
// SapDB/MaxDB cant update LONG columns / blob's: if a blob-column is included in the update we remember it in $blobs2update
// and remove it from $data
switch ( $this -> Type )
{
case 'sapdb' :
case 'maxdb' :
if ( $use_prepared_statement ) break ;
// check if data contains any LONG columns
foreach ( $data as $col => $val )
2005-12-02 11:41:50 +01:00
{
2008-03-06 12:42:21 +01:00
switch ( $table_def [ 'fd' ][ $col ][ 'type' ])
2005-12-02 11:41:50 +01:00
{
2008-03-06 12:42:21 +01:00
case 'text' :
case 'longtext' :
case 'blob' :
$blobs2update [ $col ] = & $data [ $col ];
unset ( $data [ $col ]);
break ;
2005-12-02 11:41:50 +01:00
}
2006-03-29 08:58:56 +02:00
}
2008-03-06 12:42:21 +01:00
break ;
}
2015-02-02 21:13:19 +01:00
$where_str = $this -> column_data_implode ( ' AND ' , $where , True , true , $table_def [ 'fd' ]);
2008-03-06 12:42:21 +01:00
2008-06-07 10:24:18 +02:00
if ( self :: $tablealiases && isset ( self :: $tablealiases [ $table ]))
{
$table = self :: $tablealiases [ $table ];
}
2008-03-06 12:42:21 +01:00
if ( count ( $data ))
{
2005-06-19 14:43:00 +02:00
$inputarr = false ;
if ( $use_prepared_statement && $this -> Link_ID -> _bindInputArray ) // eg. MaxDB
{
2006-03-29 08:58:56 +02:00
$this -> Link_ID -> Param ( false ); // reset param-counter
2008-03-06 12:42:21 +01:00
foreach ( $data as $col => $val )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
if ( ! isset ( $table_def [ 'fd' ][ $col ])) continue ; // ignore columns not in this table
$params [] = $this -> name_quote ( $col ) . '=' . $this -> Link_ID -> Param ( $col );
2005-06-19 14:43:00 +02:00
}
2015-02-02 21:13:19 +01:00
$sql = " UPDATE $table SET " . implode ( ',' , $params ) . ' WHERE ' . $where_str ;
2005-06-19 14:43:00 +02:00
// check if we already prepared that statement
if ( ! isset ( $this -> prepared_sql [ $sql ]))
{
$this -> prepared_sql [ $sql ] = $this -> Link_ID -> Prepare ( $sql );
}
$sql = $this -> prepared_sql [ $sql ];
$inputarr = & $data ;
}
else
{
2008-03-06 12:42:21 +01:00
$sql = " UPDATE $table SET " .
2015-02-02 21:13:19 +01:00
$this -> column_data_implode ( ',' , $data , True , true , $table_def [ 'fd' ]) . ' WHERE ' . $where_str ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
$ret = $this -> query ( $sql , $line , $file , 0 , - 1 , $inputarr );
2012-05-14 21:10:45 +02:00
if ( $this -> Debug ) echo " <p>db::query(' $sql ', $line , $file )</p> \n " ;
2008-03-06 12:42:21 +01:00
}
// if we have any blobs to update, we do so now
if (( $ret || ! count ( $data )) && count ( $blobs2update ))
{
foreach ( $blobs2update as $col => $val )
2005-06-19 14:43:00 +02:00
{
2015-02-02 21:13:19 +01:00
$ret = $this -> Link_ID -> UpdateBlob ( $table , $col , $val , $where_str , $table_def [ 'fd' ][ $col ][ 'type' ] == 'blob' ? 'BLOB' : 'CLOB' );
if ( $this -> Debug ) echo " <p>adodb::UpdateBlob(' $table ',' $col ',' $val ',' $where_str ') = ' $ret '</p> \n " ;
if ( ! $ret ) throw new egw_exception_db_invalid_sql ( " Error in UpdateBlob( $table , $col , \$ val, $where_str ) " , $line , $file );
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
}
return $ret ;
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Deletes one or more rows in table , all data is quoted according to it ' s type
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param string $table name of the table
* @ param array $where column - name / values pairs and ' ed together for the where clause
* @ param int $line line - number to pass to query
* @ param string $file file - name to pass to query
2015-02-02 21:13:19 +01:00
* @ param string | boolean $app string with name of app or False to use the current - app
* @ param array | bool $table_def use this table definition . If False , the table definition will be read from tables_baseline
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
function delete ( $table , $where , $line , $file , $app = False , $table_def = False )
{
if ( ! $table_def ) $table_def = $this -> get_table_definitions ( $app , $table );
2008-06-07 10:24:18 +02:00
if ( self :: $tablealiases && isset ( self :: $tablealiases [ $table ]))
{
$table = self :: $tablealiases [ $table ];
}
2008-03-06 12:42:21 +01:00
$sql = " DELETE FROM $table WHERE " .
$this -> column_data_implode ( ' AND ' , $where , True , False , $table_def [ 'fd' ]);
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
return $this -> query ( $sql , $line , $file );
}
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
/**
* Formats and quotes a sql expression to be used eg . as where - clause
*
* The function has a variable number of arguments , from which the expession gets constructed
* eg . db :: expression ( 'my_table' , '(' , array ( 'name' => " test'ed " , 'lang' => 'en' ), ') OR ' , array ( 'owner' => array ( '' , 4 , 10 )))
* gives " (name='test \ 'ed' AND lang='en') OR 'owner' IN (0,4,5,6,10) " if name , lang are strings and owner is an integer
*
2015-02-02 21:13:19 +01:00
* @ param string | array $table_def table - name or definition array
2008-03-06 12:42:21 +01:00
* @ param mixed $args variable number of arguments of the following types :
* string : get ' s as is into the result
* array : column - name / value pairs : the value gets quoted according to the type of the column and prefixed
* with column - name = , multiple pairs are AND ' ed together , see db :: column_data_implode
* bool : If False or is_null ( $arg ) : the next 2 ( ! ) arguments gets ignored
*
* Please note : As the function has a variable number of arguments , you CAN NOT add further parameters !!!
*
* @ return string the expression generated from the arguments
*/
2014-06-25 14:55:16 +02:00
function expression ( $table_def /*,$args, ...*/ )
2008-03-06 12:42:21 +01:00
{
2008-03-13 20:30:01 +01:00
if ( ! is_array ( $table_def )) $table_def = $this -> get_table_definitions ( true , $table_def );
2008-03-06 12:42:21 +01:00
$sql = '' ;
$ignore_next = 0 ;
foreach ( func_get_args () as $n => $arg )
{
if ( $n < 1 ) continue ; // table-name
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
if ( $ignore_next )
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
-- $ignore_next ;
continue ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
if ( is_null ( $arg )) $arg = False ;
switch ( gettype ( $arg ))
2005-06-19 14:43:00 +02:00
{
2008-03-06 12:42:21 +01:00
case 'string' :
$sql .= $arg ;
break ;
case 'boolean' :
$ignore_next += ! $arg ? 2 : 0 ;
break ;
case 'array' :
$sql .= $this -> column_data_implode ( ' AND ' , $arg , True , False , $table_def [ 'fd' ]);
break ;
2005-06-19 14:43:00 +02:00
}
2008-03-06 12:42:21 +01:00
}
return $sql ;
}
/**
* Selects one or more rows in table depending on where , all data is quoted according to it ' s type
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param string $table name of the table
2015-02-02 21:13:19 +01:00
* @ param array | string $cols string or array of column - names / select - expressions
* @ param array | string $where string or array with column - name / values pairs AND ' ed together for the where clause
2008-03-06 12:42:21 +01:00
* @ param int $line line - number to pass to query
* @ param string $file file - name to pass to query
2015-02-02 21:13:19 +01:00
* @ param int | bool $offset offset for a limited query or False ( default )
2008-03-06 12:42:21 +01:00
* @ param string $append string to append to the end of the query , eg . ORDER BY ...
2015-02-02 21:13:19 +01:00
* @ param string | boolean $app string with name of app or False to use the current - app
2008-03-06 12:42:21 +01:00
* @ param int $num_rows number of rows to return if offset set , default 0 = use default in user prefs
2015-02-02 21:13:19 +01:00
* @ param string $join = null sql to do a join , added as is after the table - name , eg . " , table2 WHERE x=y " or
2008-03-06 12:42:21 +01:00
* " LEFT JOIN table2 ON (x=y) " , Note : there ' s no quoting done on $join !
2015-02-02 21:13:19 +01:00
* @ param array | bool $table_def use this table definition . If False , the table definition will be read from tables_baseline
* @ param int $fetchmode = egw_db :: FETCH_ASSOC egw_db :: FETCH_BOTH ( default ), egw_db :: FETCH_ASSOC or egw_db :: FETCH_NUM
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
2008-03-09 15:22:02 +01:00
function select ( $table , $cols , $where , $line , $file , $offset = False , $append = '' , $app = False , $num_rows = 0 , $join = '' , $table_def = False , $fetchmode = egw_db :: FETCH_ASSOC )
2008-03-06 12:42:21 +01:00
{
if ( $this -> Debug ) echo " <p>db::select(' $table ', " . print_r ( $cols , True ) . " , " . print_r ( $where , True ) . " , $line , $file , $offset ,' $app ', $num_rows ,' $join ')</p> \n " ;
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
if ( ! $table_def ) $table_def = $this -> get_table_definitions ( $app , $table );
if ( is_array ( $cols ))
{
$cols = implode ( ',' , $cols );
}
if ( is_array ( $where ))
{
$where = $this -> column_data_implode ( ' AND ' , $where , True , False , $table_def [ 'fd' ]);
}
2008-06-07 10:24:18 +02:00
if ( self :: $tablealiases && isset ( self :: $tablealiases [ $table ]))
{
$table = self :: $tablealiases [ $table ];
}
2008-03-06 12:42:21 +01:00
$sql = " SELECT $cols FROM $table $join " ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
// if we have a where clause, we need to add it together with the WHERE statement, if thats not in the join
if ( $where ) $sql .= ( strpos ( $join , " WHERE " ) !== false ) ? ' AND (' . $where . ')' : ' WHERE ' . $where ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
if ( $append ) $sql .= ' ' . $append ;
2005-06-19 14:43:00 +02:00
2008-03-06 12:42:21 +01:00
if ( $this -> Debug ) echo " <p>sql=' $sql '</p> " ;
2006-03-29 08:58:56 +02:00
2008-03-06 12:42:21 +01:00
if ( $line === false && $file === false ) // call by union, to return the sql rather then run the query
{
return $sql ;
}
2008-03-06 14:33:38 +01:00
return $this -> query ( $sql , $line , $file , $offset , $offset === False ? - 1 : ( int ) $num_rows , false , $fetchmode );
2008-03-06 12:42:21 +01:00
}
2006-03-03 13:01:10 +01:00
2008-03-06 12:42:21 +01:00
/**
* Does a union over multiple selects
*
* @ author RalfBecker < at > outdoor - training . de
*
* @ param array $selects array of selects , each select is an array with the possible keys / parameters : table , cols , where , append , app , join , table_def
* For further info about parameters see the definition of the select function , beside table , cols and where all other params are optional
* @ param int $line line - number to pass to query
* @ param string $file file - name to pass to query
* @ param string $order_by ORDER BY statement for the union
2015-02-02 21:13:19 +01:00
* @ param int | bool $offset offset for a limited query or False ( default )
2008-03-06 12:42:21 +01:00
* @ param int $num_rows number of rows to return if offset set , default 0 = use default in user prefs
2015-02-02 21:13:19 +01:00
* @ param int $fetchmode = egw_db :: FETCH_ASSOC egw_db :: FETCH_BOTH ( default ), egw_db :: FETCH_ASSOC or egw_db :: FETCH_NUM
2008-03-06 12:42:21 +01:00
* @ return ADORecordSet or false , if the query fails
*/
2008-03-09 15:22:02 +01:00
function union ( $selects , $line , $file , $order_by = '' , $offset = false , $num_rows = 0 , $fetchmode = egw_db :: FETCH_ASSOC )
2008-03-06 12:42:21 +01:00
{
if ( $this -> Debug ) echo " <p>db::union( " . print_r ( $selects , True ) . " , $line , $file , $order_by , $offset , $num_rows )</p> \n " ;
2006-03-03 13:01:10 +01:00
2014-06-25 14:55:16 +02:00
$union = array ();
2008-03-06 12:42:21 +01:00
foreach ( $selects as $select )
{
2014-06-25 14:55:16 +02:00
$union [] = call_user_func_array ( array ( $this , 'select' ), array (
2008-03-06 12:42:21 +01:00
$select [ 'table' ],
$select [ 'cols' ],
$select [ 'where' ],
false , // line
false , // file
false , // offset
$select [ 'append' ],
$select [ 'app' ],
0 , // num_rows,
$select [ 'join' ],
$select [ 'table_def' ],
));
2005-06-19 14:43:00 +02:00
}
2014-11-10 15:19:43 +01:00
$sql = count ( $union ) > 1 ? '(' . implode ( " ) \n UNION \n ( " , $union ) . ')' : 'SELECT DISTINCT' . substr ( $union [ 0 ], 6 );
2008-03-06 12:42:21 +01:00
if ( $order_by ) $sql .= ( ! stristr ( $order_by , 'ORDER BY' ) ? " \n ORDER BY " : '' ) . $order_by ;
if ( $this -> Debug ) echo " <p>sql=' $sql '</p> " ;
2008-03-09 15:22:02 +01:00
return $this -> query ( $sql , $line , $file , $offset , $offset === False ? - 1 : ( int ) $num_rows , false , $fetchmode );
2005-06-19 14:43:00 +02:00
}
2008-06-07 10:24:18 +02:00
2008-03-08 22:31:12 +01:00
/**
* Strip eg . a prefix from the keys of an array
*
* @ param array $arr
2015-02-02 21:13:19 +01:00
* @ param string | array $strip
2008-03-08 22:31:12 +01:00
* @ return array
*/
static function strip_array_keys ( $arr , $strip )
{
$keys = array_keys ( $arr );
return array_walk ( $keys , create_function ( '&$v,$k,$strip' , '$v = str_replace($strip,\'\',$v);' ), $strip ) ?
array_combine ( $keys , $arr ) : $arr ;
}
2008-03-06 12:42:21 +01:00
}
2013-11-08 20:54:08 +01:00
/**
* Iterator applying a given callback on each element retrived , eg . from a select query
*
* Example usage :
*
* function rows ( array $where )
* {
* global $db , $table , $columns , $prefix ;
*
* return new egw_db_callback_iterator ( $db -> select ( $table , $columns , $where ), function ( $row ) use ( $prefix )
* {
* return egw_db :: strip_array_keys ( $row , $prefix );
* });
* }
*
* foreach ( row ( array ( 'attr' => 'value' )) as $row )
* {
* // $row keys have prefix removed, or whatever you implement in callback
* }
*
* Example with a key - callback :
*
* function rows ( array $where )
* {
* global $db , $table , $columns , $prefix ;
*
* return new egw_db_callback_iterator ( $db -> select ( $table , $columns , $where ), function ( $row ) use ( $prefix )
* {
* return egw_db :: strip_array_keys ( $row , $prefix );
* }, array (), function ( $row )
* {
* return $row [ 'id' ];
* });
* }
*
* foreach ( rows ( array ( 'attr' => 'value' )) as $key => $row )
* {
* // $key is now value of column 'id', $row as above
* }
*
*/
class egw_db_callback_iterator implements Iterator
{
/**
* Reference of so_sql class to use it ' s db2data method
*
* @ var callback
*/
private $callback ;
/**
* Further parameter for callback
*
* @ var array
*/
private $params = array ();
/**
* Optional callback , if you want different keys
*
* @ var callback
*/
private $key_callback ;
/**
* Instance of ADOdb record set to iterate
*
* @ var Iterator
*/
private $rs ;
/**
* Total count of entries
*
* @ var int
*/
public $total ;
/**
* Constructor
*
* @ param Traversable $rs
* @ param callback $callback
2015-02-02 21:13:19 +01:00
* @ param array $params = array () additional parameters , row is always first parameter
* @ param $key_callback = null optional callback , if you want different keys
2013-11-08 20:54:08 +01:00
*/
public function __construct ( Traversable $rs , $callback , $params = array (), $key_callback = null )
{
$this -> callback = $callback ;
$this -> params = $params ;
$this -> key_callback = $key_callback ;
if ( is_a ( $rs , 'IteratorAggregate' ))
{
$this -> rs = $rs -> getIterator ();
}
else
{
$this -> rs = $rs ;
}
}
/**
* Return the current element
*
* @ return array
*/
public function current ()
{
if ( is_a ( $this -> rs , 'iterator' ))
{
$params = $this -> params ;
array_unshift ( $params , $this -> rs -> current ());
return call_user_func_array ( $this -> callback , $params );
}
return null ;
}
/**
* Return the key of the current element
*
* @ return int
*/
public function key ()
{
if ( is_a ( $this -> rs , 'iterator' ))
{
return $this -> key_callback ?
call_user_func ( $this -> key_callback , $this -> rs -> current ()) :
$this -> rs -> key ();
}
return 0 ;
}
/**
* Move forward to next element ( called after each foreach loop )
*/
public function next ()
{
if ( is_a ( $this -> rs , 'iterator' ))
{
return $this -> rs -> next ();
}
}
/**
* Rewind the Iterator to the first element ( called at beginning of foreach loop )
*/
public function rewind ()
{
if ( is_a ( $this -> rs , 'iterator' ))
{
return $this -> rs -> rewind ();
}
}
/**
* Checks if current position is valid
*
* @ return boolean
*/
public function valid ()
{
if ( is_a ( $this -> rs , 'iterator' ))
{
return $this -> rs -> valid ();
}
return false ;
}
}