Author: ralfbecker
New Revision: 55087
URL: http://svn.stylite.de/viewvc/egroupware?rev=55087&view=rev
Log:
work in progress: using ADOdb 5.20 and move db classes to new api directory
Added:
trunk/egroupware/api/src/Db/ (with props)
trunk/egroupware/api/src/Db.php
- copied, changed from r55085, trunk/phpgwapi/inc/class.egw_db.inc.php
trunk/egroupware/api/src/Db/Backup.php
- copied, changed from r55076, trunk/phpgwapi/inc/class.db_backup.inc.php
trunk/egroupware/api/src/Db/CallbackIterator.php
- copied, changed from r55085, trunk/phpgwapi/inc/class.egw_db.inc.php
trunk/egroupware/api/src/Db/Deprecated.php
- copied, changed from r55085, trunk/phpgwapi/inc/class.egw_db.inc.php
trunk/egroupware/api/src/Db/Schema.php
- copied, changed from r55086, trunk/phpgwapi/inc/class.schema_proc.inc.php
Removed:
trunk/phpgwapi/inc/adodb/
trunk/phpgwapi/inc/class.db_backup.inc.php
trunk/phpgwapi/inc/class.schema_proc.inc.php
Modified:
trunk/admin/inc/class.admin_db_backup.inc.php
trunk/phpgwapi/inc/class.egw_db.inc.php
trunk/setup/db_backup.php
trunk/setup/inc/class.setup_process.inc.php
— trunk/admin/inc/class.admin_db_backup.inc.php (original)
+++ trunk/admin/inc/class.admin_db_backup.inc.php Sat Feb 20 21:27:17 2016
@@ -8,6 +8,8 @@
-
@package admin
-
@version $Id$
*/
+use EGroupware\Api;
class admin_db_backup
{
@@ -21,7 +23,7 @@
*/
function do_backup()
{
Propchange: trunk/egroupware/api/src/Db/
— svn:externals (added)
+++ svn:externals Sat Feb 20 21:27:17 2016
@@ -1,0 +1,4 @@
+#
+# svn externals for api/src/Db
+#
+ADOdb https://github.com/EGroupware/adodb-php/trunk
— trunk/phpgwapi/inc/class.egw_db.inc.php (original)
+++ trunk/egroupware/api/src/Db.php Sat Feb 20 21:27:17 2016
@@ -11,11 +11,18 @@
+namespace EGroupware\Api;
+
+use egw_cache;
+use egw_exception_db_connection;
+use egw_exception_db_invalid_sql;
+use egw_exception_wrong_parameter;
+
if(empty($GLOBALS[‘egw_info’][‘server’][‘db_type’]))
{
$GLOBALS[‘egw_info’][‘server’][‘db_type’] = ‘mysql’;
}
-include_once(EGW_API_INC.’/adodb/adodb.inc.php’);
+include_once(DIR.’/Db/ADOdb/adodb.inc.php’);
/**
- Database abstraction library
@@ -23,12 +30,9 @@
- This allows eGroupWare to use multiple database backends via ADOdb or in future with PDO
-
- 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()
-
- Otherwise you can simply use $GLOBALS[‘egw’]->db or a reference to it.
-
-
- Avoiding next_record() or row() can be done by looping with the recordset returned by query() or select():
-
- a) foreach($db->query(“SELECT * FROM $table”,LINE,FILE) as $row)
-
@@ -51,7 +55,7 @@
/
-class egw_db
+class Db
{
/*
* Fetchmode to fetch only as associative array with $colname => $value pairs
@@ -153,12 +157,6 @@
* @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 $Query_ID = 0;
/**
- Can be used to transparently convert tablenames, eg. ‘mytable’ => ‘otherdb.othertable’
@@ -180,7 +178,7 @@
/**
- db allows sub-queries, true for everything but mysql < 4.1
-
-
- use like: if ($db->capabilities[egw_db::CAPABILITY_SUB_QUERIES]) …
-
- use like: if ($db->capabilities[self::CAPABILITY_SUB_QUERIES]) …
/
const CAPABILITY_SUB_QUERIES = ‘sub_queries’;
/*
@@ -214,7 +212,7 @@
/
const CAPABILITY_CLIENT_ENCODING = ‘client_encoding’;
/*
-
- case insensitiv like statement (in $db->capabilities[egw_db::CAPABILITY_CASE_INSENSITIV_LIKE]), default LIKE, ILIKE for postgres
-
- case insensitiv like statement (in $db->capabilities[self::CAPABILITY_CASE_INSENSITIV_LIKE]), default LIKE, ILIKE for postgres
/
const CAPABILITY_CASE_INSENSITIV_LIKE = ‘case_insensitive_like’;
/*
@@ -226,7 +224,7 @@
-
- MySQL requires to use CAST(%s AS char)!
-
-
- Use as: $sql = sprintf($GLOBALS[‘egw’]->db->capabilities[egw_db::CAPABILITY_CAST_AS_VARCHAR],$expression);
-
- Use as: $sql = sprintf($GLOBALS[‘egw’]->db->capabilities[self::CAPABILITY_CAST_AS_VARCHAR],$expression);
/
const CAPABILITY_CAST_AS_VARCHAR = ‘cast_as_varchar’;
/*
@@ -294,17 +292,6 @@
}
/**
-
- Return the result-object of the last query
-
-
-
@deprecated use the result-object returned by query() or select() direct, so you can use the global db-object and not a clone
-
- */
- function query_id()
- {
-
return $this->Query_ID;
- }
-
- /**
- Open a connection to a database
-
-
@param string $Database name of database to use (optional)
@@ -387,7 +374,7 @@
- To enable this check add the following to your header.inc.php:
-
- require_once(EGW_API_INC.’/class.egw_db.inc.php’);
-
- egw_db::$health_check = array(‘egw_db’, ‘galera_cluster_health’);
-
- self::$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
@@ -683,26 +670,6 @@
}
/**
-
- 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);
-
}
-
-
/**
- Convert a unix timestamp to a rdms specific timestamp
-
-
@param int unix timestamp
@@ -742,17 +709,6 @@
public static function from_bool($val)
{
return $val && $val[0] !== ‘f’; // everthing other then 0 or f[alse] is returned as true
-
}
-
-
/**
-
- Discard the current query result
-
-
-
@deprecated use the result-object returned by query() or select() direct, so you can use the global db-object and not a clone
-
*/
-
function free()
-
{
-
unset($this->Query_ID); // else copying of the db-object does not work
-
$this->Query_ID = 0;
}
/**
@@ -764,12 +720,12 @@
-
@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’]
-
@param array|boolean $inputarr array for binding variables to parameters or false (default)
-
-
@param int $fetchmode =egw_db::FETCH_BOTH egw_db::FETCH_BOTH (default), egw_db::FETCH_ASSOC or egw_db::FETCH_NUM
-
-
@param int $fetchmode =self::FETCH_BOTH self::FETCH_BOTH (default), self::FETCH_ASSOC or self::FETCH_NUM
-
@param boolean $reconnect =true true: try reconnecting if server closes connection, false: dont (mysql only!)
-
@return ADORecordSet or false, if the query fails
-
@throws egw_exception_db_invalid_sql with $this->Link_ID->ErrorNo() as code
*/
- function query($Query_String, $line = ‘’, $file = ‘’, $offset=0, $num_rows=-1, $inputarr=false, $fetchmode=egw_db::FETCH_BOTH, $reconnect=true)
- function query($Query_String, $line = ‘’, $file = ‘’, $offset=0, $num_rows=-1, $inputarr=false, $fetchmode=self::FETCH_BOTH, $reconnect=true)
{
unset($line, $file); // not used anymore
@@ -782,11 +738,6 @@
return False;
}
-
# New query, discard previous result.
-
if ($this->Query_ID)
-
{
-
$this->free();
-
}
if ($this->Link_ID->fetchMode != $fetchmode)
{
$this->Link_ID->SetFetchMode($fetchmode);
@@ -802,11 +753,11 @@
}
if ($num_rows > 0)
{
@@ -816,13 +767,13 @@
{
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");
@@ -833,8 +784,8 @@
"\n$this->Error ($this->Errno)".
($inputarr ? “\nParameters: '”.implode("’,’",$inputarr)."’":’’), $this->Errno);
}
-
elseif(empty($rs->sql)) $rs->sql = $Query_String;
-
return $rs;
}
/**
@@ -854,79 +805,6 @@
}
/**
-
- 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!!!
-
-
-
@deprecated use foreach(query() or foreach(select() to loop over the query using the global db object
-
-
@param int $fetch_mode egw_db::FETCH_BOTH = numerical+assoc keys (eGW default), egw_db::FETCH_ASSOC or egw_db::FETCH_NUM
-
-
@return bool was another row found?
-
*/
-
function next_record($fetch_mode=egw_db::FETCH_BOTH)
-
{
-
if (!$this->Query_ID)
-
{
-
throw new egw_exception_db('next_record called with no query pending.');
-
}
-
if ($this->Row) // first row is already fetched
-
{
-
$this->Query_ID->MoveNext();
-
}
-
++$this->Row;
-
-
$this->Record = $this->Query_ID->fields;
-
-
if ($this->Query_ID->EOF || !$this->Query_ID->RecordCount() || !is_array($this->Record))
-
{
-
return False;
-
}
-
if ($this->capabilities[self::CAPABILITY_NAME_CASE] == 'upper') // maxdb, oracle, ...
-
{
-
switch($fetch_mode)
-
{
-
case egw_db::FETCH_ASSOC:
-
$this->Record = array_change_key_case($this->Record);
-
break;
-
case egw_db::FETCH_NUM:
-
$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;
-
}
-
}
-
// 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);
-
}
-
}
-
return True;
-
}
-
-
/**
-
- Move to position in result set
-
-
-
@deprecated use the result-object returned by query() or select() direct, so you can use the global db-object and not a clone
-
-
@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))
-
{
-
throw new egw_exception_db("seek($pos) failed: resultset has " . $this->num_rows() . " rows");
-
}
-
return True;
-
}
-
-
/**
- Begin Transaction
-
-
@return int/boolean current transaction-id, of false if no connection
@@ -1000,28 +878,6 @@
}
/**
-
-
-
-
@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’)
-
{
-
unset($table, $mode); // not used anymore
-
}
-
-
/**
-
-
-
-
@deprecated not used anymore as it costs to much performance, use transactions if needed
-
-
@return bool True if sucessful, False if fails
-
*/
-
function unlock()
-
{}
-
-
/**
- Get the number of rows affected by last update or delete
-
-
@return int number of rows
@@ -1035,101 +891,6 @@
return False;
}
return $this->Link_ID->Affected_Rows();
-
}
-
-
/**
-
- Number of rows in current result set
-
-
-
@deprecated use the result-object returned by query/select()->NumRows(), so you can use the global db-object and not a clone
-
-
@return int number of rows
-
*/
-
function num_rows()
-
{
-
return $this->Query_ID ? $this->Query_ID->RecordCount() : False;
-
}
-
-
/**
-
- Number of fields in current row
-
-
-
@deprecated use the result-object returned by query() or select() direct, so you can use the global db-object and not a clone
-
-
@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
-
-
-
@deprecated use the result-object returned by query() or select() direct, so you can use the global db-object and not a clone
-
-
@param string|integer $Name name of field or positional index starting from 0
-
-
@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)
-
{
-
return stripslashes($this->Record[$Name]);
-
}
-
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);
-
}
-
-
/**
-
- Returns a query-result-row as an associative array (no numerical keys !!!)
-
-
-
@deprecated use foreach(query() or foreach(select() to loop over the query using the global db object
-
-
@param bool $do_next_record should next_record() be called or not (default not)
-
-
@param string $strip =’’ string to strip of the column-name, default ‘’
-
-
@return array/bool the associative array or False if no (more) result-row is availible
-
*/
-
function row($do_next_record=False,$strip=’’)
-
{
-
if ($do_next_record && !$this->next_record(egw_db::FETCH_ASSOC) || !is_array($this->Record))
-
{
-
return False;
-
}
-
$result = array();
-
foreach($this->Record as $column => $value)
-
{
-
if (!is_numeric($column))
-
{
-
if ($strip) $column = str_replace($strip,'',$column);
-
-
$result[$column] = $value;
-
}
-
}
-
return $result;
}
/**
@@ -1892,7 +1653,7 @@
*
-
@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 $app=null app name or NULL to use $this->app, set via self::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
*/
@@ -2206,10 +1967,10 @@
* @param string $join =null sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
* "LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
* @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
-
-
@param int $fetchmode =self::FETCH_ASSOC self::FETCH_BOTH (default), self::FETCH_ASSOC or self::FETCH_NUM
-
@return ADORecordSet or false, if the query fails
*/
- function select($table,$cols,$where,$line,$file,$offset=False,$append=’’,$app=False,$num_rows=0,$join=’’,$table_def=False,$fetchmode=egw_db::FETCH_ASSOC)
- function select($table,$cols,$where,$line,$file,$offset=False,$append=’’,$app=False,$num_rows=0,$join=’’,$table_def=False,$fetchmode=self::FETCH_ASSOC)
{
if ($this->Debug) echo “db::select(’$table’,”.print_r($cols,True).",".print_r($where,True).",$line,$file,$offset,’$app’,$num_rows,’$join’)
\n";
@@ -2254,10 +2015,10 @@
* @param string $order_by ORDER BY statement for the union
* @param int|bool $offset offset for a limited query or False (default)
* @param int $num_rows number of rows to return if offset set, default 0 = use default in user prefs
-
-
@param int $fetchmode =egw_db::FETCH_ASSOC egw_db::FETCH_BOTH (default), egw_db::FETCH_ASSOC or egw_db::FETCH_NUM
-
-
@param int $fetchmode =self::FETCH_ASSOC self::FETCH_BOTH (default), self::FETCH_ASSOC or self::FETCH_NUM
-
@return ADORecordSet or false, if the query fails
*/
- function union($selects,$line,$file,$order_by=’’,$offset=false,$num_rows=0,$fetchmode=egw_db::FETCH_ASSOC)
- function union($selects,$line,$file,$order_by=’’,$offset=false,$num_rows=0,$fetchmode=self::FETCH_ASSOC)
{
if ($this->Debug) echo “db::union(”.print_r($selects,True).",$line,$file,$order_by,$offset,$num_rows)
\n";
@@ -2302,174 +2063,3 @@
array_combine($keys,$arr) : $arr;
}
}
-/**
-
- Iterator applying a given callback on each element retrived, eg. from a select query
-
-
-
-
- function rows(array $where)
-
-
-
-
-
return new egw_db_callback_iterator($db->select($table, $columns, $where), function($row) use ($prefix)
-
-
-
-
-
-
- foreach(row(array(‘attr’ => ‘value’)) as $row)
-
-
-
-
-
- Example with a key-callback:
-
-
- function rows(array $where)
-
-
-
-
-
return new egw_db_callback_iterator($db->select($table, $columns, $where), function($row) use ($prefix)
-
-
-
-
-
-
-
-
-
- foreach(rows(array(‘attr’ => ‘value’)) as $key => $row)
-
-
-
-
- */
-class egw_db_callback_iterator implements Iterator
-{
- /**
-
- Reference of so_sql class to use it’s db2data method
-
-
- */
- private $callback;
-
- /**
-
- Further parameter for callback
-
-
- */
- private $params = array();
-
- /**
-
- Optional callback, if you want different keys
-
-
- */
- private $key_callback;
-
- /**
-
- Instance of ADOdb record set to iterate
-
-
- */
- private $rs;
-
- /**
-
-
-
- */
- public $total;
-
- /**
-
-
-
-
-
@param callback $callback
-
-
@param array $params =array() additional parameters, row is always first parameter
-
-
@param $key_callback =null optional callback, if you want different keys
- */
- 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
-
-
- */
- 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
-
-
- */
- 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
-
-
- */
- public function valid ()
- {
-
if (is_a($this->rs,'iterator'))
-
{
-
return $this->rs->valid();
-
}
-
return false;
- }
-}
— trunk/phpgwapi/inc/class.db_backup.inc.php (original)
+++ trunk/egroupware/api/src/Db/Backup.php Sat Feb 20 21:27:17 2016
@@ -7,16 +7,25 @@
+namespace EGroupware\Api\Db;
+
+use EGroupware\Api;
+use egw_exception_db_invalid_sql;
+use egw_cache;
+use config;
+use translation;
+use html;
/**
- DB independent backup and restore of EGroupware database
-
- Backing up bool columns now for all databases as 1 or 0, but understanding PostgreSQL ‘t’ or ‘f’ too.
/
-class db_backup
+class Backup
{
/*
- Configuration table.
@@ -25,7 +34,7 @@
/**
- Reference to schema_proc
-
-
-
@var Api\Db\Schema
/
var $schema_proc;
/*
@@ -84,7 +93,7 @@
/**
- Reference to schema_proc’s egw_db object
-
@@ -103,7 +112,7 @@
}
else
{
@@ -151,7 +160,6 @@
}
else // called from eGW
{
@@ -801,7 +809,7 @@
// decode bool columns, they might be ‘t’/‘f’ for old PostgreSQL backups
foreach($bools as $key)
{
@@ -835,7 +843,7 @@
}
elseif (in_array($key, $bools))
{
@@ -869,7 +877,7 @@
$data = base64_encode($data);
break;
case ‘bool’: // we use MySQL 0, 1 in csv, not PostgreSQL ‘t’, ‘f’
-
$data = (int)Api\Db::from_bool($data);
break;
default:
$data = '"'.str_replace(array('\\',"\n","\r",'"'),array('\\\\','\\n','\\r','\\"'),$data).'"';
— trunk/phpgwapi/inc/class.egw_db.inc.php (original)
+++ trunk/egroupware/api/src/Db/CallbackIterator.php Sat Feb 20 21:27:17 2016
@@ -1,6 +1,6 @@
<?php
/**
- * EGroupware API: Database abstraction library
+ * EGroupware API: Database callback iterator
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@@ -11,2297 +11,7 @@
* @version $Id$
*/
-if(empty($GLOBALS['egw_info']['server']['db_type']))
-{
- $GLOBALS['egw_info']['server']['db_type'] = 'mysql';
-}
-include_once(EGW_API_INC.'/adodb/adodb.inc.php');
-
-/**
- * Database abstraction library
- *
- * This allows eGroupWare to use multiple database backends via ADOdb or in future with PDO
- *
- * 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()
- *
- * Otherwise you can simply use $GLOBALS['egw']->db or a reference to it.
- *
- * Avoiding next_record() or row() can be done by looping with the recordset returned by query() or select():
- *
- * a) foreach($db->query("SELECT * FROM $table",__LINE__,__FILE__) as $row)
- *
- * b) foreach($db->select($api_table,'*',$where,__LINE__,__FILE__) as $row)
- *
- * c) foreach($db->select($table,'*',$where,__LINE__,__FILE__,false,'',$app) as $row)
- *
- * To fetch only a single column (of the next row):
- * $cnt = $db->query("SELECT COUNT(*) FROM ...")->fetchColumn($column_num=0);
- *
- * To fetch a next (single) row, you can use:
- * $row = $db->query("SELECT COUNT(*) FROM ...")->fetch($fetchmod=null);
- *
- * 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();
- * $num_config = $this->db->select(config::TABLE,'COUNT(config_name)',false,__LINE__,__FILE__)->fetchColumn();
- * }
- * catch(Exception $e) {
- * echo "Connection to DB failed (".$e->getMessage().")!\n";
- * }
- */
-class egw_db
-{
- /**
- * Fetchmode to fetch only as associative array with $colname => $value pairs
- *
- * 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;
- /**
- * @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 = '';
-
- /**
- * @var string $Database name of database to use
- */
- var $Database = '';
-
- /**
- * @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;
-
- /**
- * Log update querys to error_log, do not run them
- *
- * @var boolean
- */
- var $log_updates = false;
-
- /**
- * @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
- *
- * @var string
- */
- var $query_log;
-
- /**
- * ADOdb connection
- *
- * @var ADOConnection
- */
- 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;
-
- /**
- * 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();
-
- /**
- * 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;
-
- /**
- * 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';
- /**
- * How to cast a column to varchar: CAST(%s AS varchar)
- *
- * MySQL requires to use CAST(%s AS char)!
- *
- * Use as: $sql = sprintf($GLOBALS['egw']->db->capabilities[egw_db::CAPABILITY_CAST_AS_VARCHAR],$expression);
- */
- const CAPABILITY_CAST_AS_VARCHAR = 'cast_as_varchar';
- /**
- * default capabilities will be changed by method set_capabilities($ado_driver,$db_version)
- *
- * should be used with the CAPABILITY_* constants as key
- *
- * @var array
- */
- var $capabilities = array(
- 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',
- self::CAPABILITY_REQUIRE_TRUNCATE_VARCHAR => true,
- self::CAPABILITY_CAST_AS_VARCHAR => 'CAST(%s AS varchar)',
- );
-
- var $prepared_sql = array(); // sql is the index
-
- /**
- * Constructor
- *
- * @param array $db_data =null values for keys 'db_name', 'db_host', 'db_port', 'db_user', 'db_pass', 'db_type'
- */
- 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];
- }
- }
-//if ($GLOBALS['egw_info']['server']['default_domain'] == 'ralfsmacbook.local') $this->query_log = '/tmp/query.log';
- }
-
- /**
- * @param string $query query to be executed (optional)
- */
-
- function db($query = '')
- {
- $this->query($query);
- }
-
- /**
- * @return int current connection id
- */
- function link_id()
- {
- return $this->Link_ID;
- }
-
- /**
- * Return the result-object of the last query
- *
- * @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
- */
- function query_id()
- {
- return $this->Query_ID;
- }
-
- /**
- * 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
- */
- function connect($Database = NULL, $Host = NULL, $Port = NULL, $User = NULL, $Password = NULL,$Type = NULL)
- {
- /* 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'];
- }
- // 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");
- $new_connection = !$this->Link_ID || !$this->Link_ID->IsConnected();
- $this->_connect($host);
- // check if connected node is healty
- if ($new_connection && self::$health_check)
- {
- call_user_func(self::$health_check, $this);
- }
- //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;
- }
- 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
- }
- }
- if (!isset($e))
- {
- $e = new egw_exception_db_connection('No DB host set!');
- }
- throw $e;
- }
-
- /**
- * 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:
- *
- * require_once(EGW_API_INC.'/class.egw_db.inc.php');
- * 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));
- }
- }
-
- /**
- * 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[0] == '@' ? getenv(substr($this->Host, 1)) : $this->Host);
- $num_hosts = count($hosts);
- $n =& egw_cache::getSession(__CLASS__, $this->Host);
- if (!isset($n)) $n = 0;
-
- if ($next && ++$n >= $num_hosts+2)
- {
- $n = 0; // start search again with default on next request
- $ret = false;
- }
- else
- {
- $ret = $hosts[$n % $num_hosts];
- }
- //error_log(__METHOD__."(next=".array2string($next).") n=$n returning ".array2string($ret));
- return $ret;
- }
-
- /**
- * Connect to given host
- *
- * @param string $Host host to connect to
- * @return ADOConnection
- * @throws egw_exception_db_connection
- */
- protected function _connect($Host)
- {
- if (!$this->Link_ID)
- {
- $Database = $User = $Password = $Port = $Type = '';
- foreach(array('Database','User','Password','Port','Type') as $name)
- {
- $$name = $this->$name;
- if (${$name}[0] == '@' && $name != 'Password') $$name = getenv(substr($$name, 1));
- }
- $this->setupType = $php_extension = $Type;
-
- switch($Type) // convert to ADO db-type-names
- {
- case 'pgsql':
- $Type = 'postgres'; // name in ADOdb
- // create our own pgsql connection-string, to allow unix domain soccets if !$Host
- $Host = "dbname=$Database".($Host ? " host=$Host".($Port ? " port=$Port" : '') : '').
- " user=$User".($Password ? " password='".addslashes($Password)."'" : '');
- $User = $Password = $Database = ''; // to indicate $Host is a connection-string
- break;
-
- case 'odbc_mssql':
- $php_extension = 'odbc';
- $Type = 'mssql';
- // fall through
- case 'mssql':
- if ($Port) $Host .= ','.$Port;
- break;
-
- case 'odbc_oracle':
- $php_extension = 'odbc';
- $Type = 'oracle';
- break;
- case 'oracle':
- $php_extension = $Type = 'oci8';
- break;
-
- case 'sapdb':
- $Type = 'maxdb';
- // fall through
- case 'maxdb':
- $Type ='sapdb'; // name in ADOdb
- $php_extension = 'odbc';
- break;
-
- case 'mysqlt':
- case 'mysql':
- // if mysqli is available silently switch to it, mysql extension is deprecated and no longer available in php7+
- if (check_load_extension('mysqli'))
- {
- $php_extension = $Type = 'mysqli';
- }
- else
- {
- $php_extension = 'mysql'; // you can use $this->setupType to determine if it's mysqlt or mysql
- }
- // fall through
- case 'mysqli':
- $this->Type = 'mysql'; // need to be "mysql", so apps can check just for "mysql"!
- // fall through
- default:
- if ($Port) $Host .= ':'.$Port;
- break;
- }
- if (!isset($GLOBALS['egw']->ADOdb) || // we have no connection so far
- (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)))
- {
- if (!check_load_extension($php_extension))
- {
- 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 !!!");
- }
- if (!isset($GLOBALS['egw']->ADOdb)) // use the global object to store the connection
- {
- $this->Link_ID =& $GLOBALS['egw']->ADOdb;
- }
- 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)
- {
- throw new egw_exception_db_connection("No ADOdb support for '$Type' ($this->Type) !!!");
- }
- if ($Type == 'mysqli')
- {
- // set a connection timeout of 1 second, to allow quicker failover to other db-nodes (default is 20s)
- $this->Link_ID->setConnectionParameter(MYSQLI_OPT_CONNECT_TIMEOUT, 1);
- }
- $connect = $GLOBALS['egw_info']['server']['db_persistent'] ? 'PConnect' : 'Connect';
- if (($Ok = $this->Link_ID->$connect($Host, $User, $Password, $Database)))
- {
- $this->ServerInfo = $this->Link_ID->ServerInfo();
- $this->set_capabilities($Type,$this->ServerInfo['version']);
- }
- if (!$Ok)
- {
- $Host = preg_replace('/password=[^ ]+/','password=$Password',$Host); // eg. postgres dsn contains password
- throw new egw_exception_db_connection("ADOdb::$connect($Host, $User, \$Password, $Database) failed.");
- }
- if ($this->Debug)
- {
- echo function_backtrace();
- echo "
new ADOdb connection to $Type://$Host/$Database: Link_ID".($this->Link_ID === $GLOBALS['egw']->ADOdb ? '===' : '!==')."\$GLOBALS[egw]->ADOdb
";
- //echo "
".print_r($this->Link_ID->ServerInfo(),true)."
\n";
- _debug_array($this);
- echo "\$GLOBALS[egw]->db="; _debug_array($GLOBALS[egw]->db);
- }
- if ($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);
- }
- // set our default charset
- $this->Link_ID->SetCharSet($this->Type == 'mysql' ? 'utf8' : 'utf-8');
-
- $new_connection = true;
- }
- else
- {
- $this->Link_ID =& $GLOBALS['egw']->ADOdb;
- }
- }
- if (!$this->Link_ID->isConnected() && !$this->Link_ID->Connect())
- {
- $Host = preg_replace('/password=[^ ]+/','password=$Password',$Host); // eg. postgres dsn contains password
- throw new egw_exception_db_connection("ADOdb::$connect($Host, $User, \$Password, $Database) reconnect failed.");
- }
- // fix due to caching and reusing of connection not correctly set $this->Type == 'mysql'
- if ($this->Type == 'mysqli')
- {
- $this->setupType = $this->Type;
- $this->Type = 'mysql';
- }
- if ($new_connection)
- {
- foreach(get_included_files() as $file)
- {
- if (strpos($file,'adodb') !== false && !in_array($file,(array)$_SESSION['egw_required_files']))
- {
- $_SESSION['egw_required_files'][] = $file;
- //error_log(__METHOD__."() egw_required_files[] = $file");
- }
- }
- }
- //echo "
".print_r($this->Link_ID->ServerInfo(),true)."
\n";
- return $this->Link_ID;
- }
-
- /**
- * 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
- }
-
- /**
- * Magic method called when object get's serialized
- *
- * We do NOT store Link_ID and private_Link_ID, as we need to reconnect anyway.
- * This also ensures reevaluating environment-data or multiple hosts in connection-data!
- *
- * @return array
- */
- function __sleep()
- {
- if (!empty($this->setupType)) $this->Type = $this->setupType; // restore Type eg. to mysqli
-
- $vars = get_object_vars($this);
- unset($vars['Link_ID']);
- unset($vars['privat_Link_ID']);
- return array_keys($vars);
- }
-
- /**
- * changes defaults set in class-var $capabilities depending on db-type and -version
- *
- * @param string $adodb_driver mysql, postgres, mssql, sapdb, oci8
- * @param string $db_version version-number of connected db-server, as reported by ServerInfo
- */
- function set_capabilities($adodb_driver,$db_version)
- {
- switch($adodb_driver)
- {
- case 'mysql':
- case 'mysqlt':
- case 'mysqli':
- $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;
- $this->capabilities[self::CAPABILITY_CAST_AS_VARCHAR] = 'CAST(%s AS char)';
- break;
-
- case 'postgres':
- $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;
- $this->capabilities[self::CAPABILITY_CASE_INSENSITIV_LIKE] = '::text ILIKE';
- $this->capabilities[self::CAPABILIT