|
// +----------------------------------------------------------------------+
//
// $Id$
require_once 'MDB.php';
require_once 'Cache/Container.php';
/**
* PEAR/MDB Cache Container.
*
* NB: The field 'changed' has no meaning for the Cache itself. It's just there
* because it's a good idea to have an automatically updated timestamp
* field for debugging in all of your tables.
*
* A XML MDB-compliant schema example for the table needed is provided.
* Look at the file "mdb_cache_schema.xml" for that.
*
* ------------------------------------------
* A basic usage example:
* ------------------------------------------
*
* $dbinfo = array(
* 'database' => 'dbname',
* 'phptype' => 'mysql',
* 'username' => 'root',
* 'password' => '',
* 'cache_table' => 'cache'
* );
*
*
* $cache = new Cache('mdb', $dbinfo);
* $id = $cache->generateID('testentry');
*
* if ($data = $cache->get($id)) {
* echo 'Cache hit.
Data: '.$data;
*
* } else {
* $data = 'data of any kind';
* $cache->save($id, $data);
* echo 'Cache miss.
';
* }
*
* ------------------------------------------
*
* @author Lorenzo Alberton
* @version $Id$
* @package Cache
*/
class Cache_Container_mdb extends Cache_Container {
/**
* Name of the MDB table to store caching data
*
* @see Cache_Container_file::$filename_prefix
*/
var $cache_table = '';
/**
* PEAR MDB object
*
* @var object PEAR_MDB
*/
var $db;
/**
* Constructor
*
* @param mixed Array with connection info or dsn string
*/
function Cache_Container_mdb($options)
{
$this->db = &MDB::Connect($options);
if(MDB::isError($this->db)) {
return new Cache_Error('MDB::connect failed: '
. $this->db->getMessage(), __FILE__, __LINE__);
} else {
$this->db->setFetchMode(MDB_FETCHMODE_ASSOC);
}
$this->setOptions($options, array_merge($this->allowed_options,
array('dsn', 'cache_table')));
}
/**
* Fetch in the db the data that matches input parameters
*
* @param string dataset ID
* @param string cache group
* @return mixed dataset value or NULL/Cache_Error on failure
* @access public
*/
function fetch($id, $group)
{
$query = 'SELECT cachedata FROM ' . $this->cache_table
.' WHERE id=' . $this->db->getTextValue($id)
.' AND cachegroup=' . $this->db->getTextValue($group);
if($res = $this->db->query($query)) {
if($this->db->endOfResult($res)) {
//no rows returned
$data = array(NULL, NULL, NULL);
} else {
$clob = $this->db->fetchClob($res,0,'cachedata');
if(!MDB::isError($clob)) {
$cached_data = '';
while(!$this->db->endOfLOB($clob)) {
if(MDB::isError($error =
$this->db->readLob($clob,$data,8000)<0)) {
return new Cache_Error('MDB::query failed: '
. $error->getMessage(), __FILE__, __LINE__);
}
$cached_data .= $data;
}
unset($data);
$this->db->destroyLob($clob);
$this->db->freeResult($res);
//finished fetching LOB, now fetch other fields...
$query = 'SELECT userdata, expires FROM ' . $this->cache_table
.' WHERE id=' . $this->db->getTextValue($id)
.' AND cachegroup=' . $this->db->getTextValue($group);
if($res = $this->db->query($query)) {
$row = $this->db->fetchInto($res);
if (is_array($row)) {
$data = array(
$row['expires'],
$this->decode($cached_data),
$row['userdata']
);
} else {
$data = array(NULL, NULL, NULL);
}
} else {
$data = array(NULL, NULL, NULL);
}
} else {
return new Cache_Error('MDB::query failed: '
. $clob->getMessage(), __FILE__, __LINE__);
}
}
$this->db->freeResult($res);
} else {
//return new Cache_Error('MDB::query failed: '
// . $result->getMessage(), __FILE__, __LINE__);
$data = array(NULL, NULL, NULL);
}
// last used required by the garbage collection
$query = 'UPDATE ' . $this->cache_table
.' SET changed=' . time()
.' WHERE id=' . $this->db->getTextValue($id)
.' AND cachegroup=' . $this->db->getTextValue($group);
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
return $data;
}
/**
* Stores a dataset in the database
*
* If dataset_ID already exists, overwrite it with new data,
* else insert data in a new record.
*
* @param string dataset ID
* @param mixed data to be cached
* @param integer expiration time
* @param string cache group
* @param string userdata
* @access public
*/
function save($id, $data, $expires, $group, $userdata)
{
global $db;
$this->flushPreload($id, $group);
$fields = array(
'id' => array(
'Type' => 'text',
'Value' => $id,
'Key' => true
),
'userdata' => array(
'Type' => 'integer',
'Value' => $userdata,
'Null' => ($userdata ? false : true)
),
'expires' => array(
'Type' => 'integer',
'Value' => $this->getExpiresAbsolute($expires)
),
'cachegroup' => array(
'Type' => 'text',
'Value' => $group
)
);
$result = $this->db->replace($this->cache_table, $fields);
if(MDB::isError($result)) {
//Var_Dump::display($result);
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($result), __FILE__, __LINE__);
}
unset($fields); //end first part of query
$query2 = 'UPDATE ' . $this->cache_table
.' SET cachedata=?'
.' WHERE id='. $this->db->getTextValue($id);
if(($prepared_query = $this->db->prepareQuery($query2))) {
$char_lob = array(
'Error' => '',
'Type' => 'data',
'Data' => $this->encode($data)
);
if(!MDB::isError($clob = $this->db->createLob($char_lob))) {
$this->db->setParamClob($prepared_query,1,$clob,'cachedata');
if(MDB::isError($error=$this->db->executeQuery($prepared_query))) {
return new Cache_Error('MDB::query failed: '
. $error->getMessage() , __FILE__, __LINE__);
}
$this->db->destroyLob($clob);
} else {
// creation of the handler object failed
return new Cache_Error('MDB::query failed: '
. $clob->getMessage() , __FILE__, __LINE__);
}
$this->db->freePreparedQuery($prepared_query);
} else {
//prepared query failed
return new Cache_Error('MDB::query failed: '
. $prepared_query->getMessage() , __FILE__, __LINE__);
}
}
/**
* Removes a dataset from the database
*
* @param string dataset ID
* @param string cache group
*/
function remove($id, $group)
{
$this->flushPreload($id, $group);
$query = 'DELETE FROM ' . $this->cache_table
.' WHERE id=' . $this->db->getTextValue($id)
.' AND cachegroup=' . $this->db->getTextValue($group);
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
}
/**
* Remove all cached data for a certain group, or empty
* the cache table if no group is specified.
*
* @param string cache group
*/
function flush($group = '')
{
$this->flushPreload();
if ($group) {
$query = 'DELETE FROM ' . $this->cache_table
.' WHERE cachegroup=' . $this->db->getTextValue($group);
} else {
$query = 'DELETE FROM ' . $this->cache_table;
}
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
}
/**
* Check if a dataset ID/group exists.
*
* @param string dataset ID
* @param string cache group
* @return boolean
*/
function idExists($id, $group)
{
$query = 'SELECT id FROM ' . $this->cache_table
.' WHERE id=' . $this->db->getTextValue($id)
.' AND cachegroup=' . $this->db->getTextValue($group);
echo $query;
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
$row = $this->db->fetchInto($res);
if (is_array($row)) {
return true;
} else {
return false;
}
}
/**
* Garbage collector.
*
* @param int maxlifetime
*/
function garbageCollection($maxlifetime)
{
$this->flushPreload();
$query = 'DELETE FROM ' . $this->cache_table
.' WHERE (expires <= ' . time()
.' AND expires > 0) OR changed <= '. time() - $maxlifetime;
$res = $this->db->query($query);
$query = 'SELECT sum(length(cachedata)) as CacheSize FROM '
. $this->cache_table;
$cachesize = $this->db->getOne($query);
if (MDB::isError($cachesize)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($cachesize), __FILE__, __LINE__);
}
//if cache is to big.
if ($cachesize > $this->highwater)
{
//find the lowwater mark.
$query = 'SELECT length(cachedata) as size, changed FROM '
. $this->cache_table .' ORDER BY changed DESC';
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
$numrows = $this->db->numRows($res);
$keep_size = 0;
while ($keep_size < $this->lowwater && $numrows--) {
$entry = $this->db->fetchInto($res,MDB_FETCHMODE_ASSOC);
$keep_size += $entry['size'];
}
//delete all entries, which were changed before the "lowwater mark"
$query = 'DELETE FROM ' . $this->cache_table
.' WHERE changed<='.($entry['changed'] ? $entry['changed'] : 0);
$res = $this->db->query($query);
if (MDB::isError($res)) {
return new Cache_Error('MDB::query failed: '
. $this->db->errorMessage($res), __FILE__, __LINE__);
}
}
}
}
?>