1<?php 2// +----------------------------------------------------------------------+ 3// | PEAR :: Cache :: MDB Container | 4// +----------------------------------------------------------------------+ 5// | Copyright (c) 1997-2003 The PHP Group | 6// +----------------------------------------------------------------------+ 7// | This source file is subject to version 2.0 of the PHP license, | 8// | that is bundled with this package in the file LICENSE, and is | 9// | available at through the world-wide-web at | 10// | http://www.php.net/license/2_02.txt. | 11// | If you did not receive a copy of the PHP license and are unable to | 12// | obtain it through the world-wide-web, please send a note to | 13// | license@php.net so we can mail you a copy immediately. | 14// +----------------------------------------------------------------------+ 15// | Note: This is a MDB-oriented rewrite of Cache/Container/db.php. | 16// | Thanks to Lukas Smith for his patience in answering my questions | 17// +----------------------------------------------------------------------+ 18// | Author: Lorenzo Alberton <l.alberton at quipo.it> | 19// +----------------------------------------------------------------------+ 20// 21// $Id: mdb.php 174777 2004-12-15 09:09:33Z dufuz $ 22 23require_once 'MDB.php'; 24require_once 'Cache/Container.php'; 25 26/** 27* PEAR/MDB Cache Container. 28* 29* NB: The field 'changed' has no meaning for the Cache itself. It's just there 30* because it's a good idea to have an automatically updated timestamp 31* field for debugging in all of your tables. 32* 33* A XML MDB-compliant schema example for the table needed is provided. 34* Look at the file "mdb_cache_schema.xml" for that. 35* 36* ------------------------------------------ 37* A basic usage example: 38* ------------------------------------------ 39* 40* $dbinfo = array( 41* 'database' => 'dbname', 42* 'phptype' => 'mysql', 43* 'username' => 'root', 44* 'password' => '', 45* 'cache_table' => 'cache' 46* ); 47* 48* 49* $cache = new Cache('mdb', $dbinfo); 50* $id = $cache->generateID('testentry'); 51* 52* if ($data = $cache->get($id)) { 53* echo 'Cache hit.<br />Data: '.$data; 54* 55* } else { 56* $data = 'data of any kind'; 57* $cache->save($id, $data); 58* echo 'Cache miss.<br />'; 59* } 60* 61* ------------------------------------------ 62* 63* @author Lorenzo Alberton <l.alberton at quipo.it> 64* @version $Id: mdb.php 174777 2004-12-15 09:09:33Z dufuz $ 65* @package Cache 66*/ 67class Cache_Container_mdb extends Cache_Container 68{ 69 70 /** 71 * Name of the MDB table to store caching data 72 * 73 * @see Cache_Container_file::$filename_prefix 74 */ 75 var $cache_table = ''; 76 77 /** 78 * PEAR MDB object 79 * 80 * @var object PEAR_MDB 81 */ 82 var $db; 83 84 /** 85 * Constructor 86 * 87 * @param mixed Array with connection info or dsn string 88 */ 89 function Cache_Container_mdb($options) 90 { 91 $this->db = &MDB::Connect($options); 92 if (MDB::isError($this->db)) { 93 return new Cache_Error('MDB::connect failed: ' 94 . $this->db->getMessage(), __FILE__, __LINE__); 95 } else { 96 $this->db->setFetchMode(MDB_FETCHMODE_ASSOC); 97 } 98 $this->setOptions($options, array_merge($this->allowed_options, 99 array('dsn', 'cache_table'))); 100 } 101 102 /** 103 * Fetch in the db the data that matches input parameters 104 * 105 * @param string dataset ID 106 * @param string cache group 107 * @return mixed dataset value or null/Cache_Error on failure 108 * @access public 109 */ 110 function fetch($id, $group) 111 { 112 $query = 'SELECT cachedata FROM ' . $this->cache_table 113 .' WHERE id=' . $this->db->getTextValue($id) 114 .' AND cachegroup=' . $this->db->getTextValue($group); 115 if ($res = $this->db->query($query)) { 116 if ($this->db->endOfResult($res)) { 117 //no rows returned 118 $data = array(null, null, null); 119 } else { 120 $clob = $this->db->fetchClob($res,0,'cachedata'); 121 if (!MDB::isError($clob)) { 122 $cached_data = ''; 123 while(!$this->db->endOfLOB($clob)) { 124 if (MDB::isError($error = 125 $this->db->readLob($clob,$data,8000)<0)) { 126 return new Cache_Error('MDB::query failed: ' 127 . $error->getMessage(), __FILE__, __LINE__); 128 } 129 $cached_data .= $data; 130 } 131 unset($data); 132 $this->db->destroyLob($clob); 133 $this->db->freeResult($res); 134 135 //finished fetching LOB, now fetch other fields... 136 $query = 'SELECT userdata, expires FROM ' . $this->cache_table 137 .' WHERE id=' . $this->db->getTextValue($id) 138 .' AND cachegroup=' . $this->db->getTextValue($group); 139 if ($res = $this->db->query($query)) { 140 $row = $this->db->fetchInto($res); 141 if (is_array($row)) { 142 $data = array( 143 $row['expires'], 144 $this->decode($cached_data), 145 $row['userdata'] 146 ); 147 } else { 148 $data = array(null, null, null); 149 } 150 } else { 151 $data = array(null, null, null); 152 } 153 } else { 154 return new Cache_Error('MDB::query failed: ' 155 . $clob->getMessage(), __FILE__, __LINE__); 156 } 157 } 158 $this->db->freeResult($res); 159 } else { 160 //return new Cache_Error('MDB::query failed: ' 161 // . $result->getMessage(), __FILE__, __LINE__); 162 $data = array(null, null, null); 163 } 164 165 // last used required by the garbage collection 166 $query = 'UPDATE ' . $this->cache_table 167 .' SET changed=' . time() 168 .' WHERE id=' . $this->db->getTextValue($id) 169 .' AND cachegroup=' . $this->db->getTextValue($group); 170 171 $res = $this->db->query($query); 172 if (MDB::isError($res)) { 173 return new Cache_Error('MDB::query failed: ' 174 . $this->db->errorMessage($res), __FILE__, __LINE__); 175 } 176 return $data; 177 } 178 179 /** 180 * Stores a dataset in the database 181 * 182 * If dataset_ID already exists, overwrite it with new data, 183 * else insert data in a new record. 184 * 185 * @param string dataset ID 186 * @param mixed data to be cached 187 * @param integer expiration time 188 * @param string cache group 189 * @param string userdata 190 * @access public 191 */ 192 function save($id, $data, $expires, $group, $userdata) 193 { 194 global $db; 195 $this->flushPreload($id, $group); 196 197 $fields = array( 198 'id' => array( 199 'Type' => 'text', 200 'Value' => $id, 201 'Key' => true 202 ), 203 'userdata' => array( 204 'Type' => 'integer', 205 'Value' => $userdata, 206 'null' => ($userdata ? false : true) 207 ), 208 'expires' => array( 209 'Type' => 'integer', 210 'Value' => $this->getExpiresAbsolute($expires) 211 ), 212 'cachegroup' => array( 213 'Type' => 'text', 214 'Value' => $group 215 ) 216 ); 217 218 $result = $this->db->replace($this->cache_table, $fields); 219 220 if (MDB::isError($result)) { 221 //Var_Dump::display($result); 222 return new Cache_Error('MDB::query failed: ' 223 . $this->db->errorMessage($result), __FILE__, __LINE__); 224 } 225 unset($fields); //end first part of query 226 $query2 = 'UPDATE ' . $this->cache_table 227 .' SET cachedata=?' 228 .' WHERE id='. $this->db->getTextValue($id); 229 230 if (($prepared_query = $this->db->prepareQuery($query2))) { 231 $char_lob = array( 232 'Error' => '', 233 'Type' => 'data', 234 'Data' => $this->encode($data) 235 ); 236 if (!MDB::isError($clob = $this->db->createLob($char_lob))) { 237 $this->db->setParamClob($prepared_query,1,$clob,'cachedata'); 238 if(MDB::isError($error=$this->db->executeQuery($prepared_query))) { 239 return new Cache_Error('MDB::query failed: ' 240 . $error->getMessage() , __FILE__, __LINE__); 241 } 242 $this->db->destroyLob($clob); 243 } else { 244 // creation of the handler object failed 245 return new Cache_Error('MDB::query failed: ' 246 . $clob->getMessage() , __FILE__, __LINE__); 247 } 248 $this->db->freePreparedQuery($prepared_query); 249 } else { 250 //prepared query failed 251 return new Cache_Error('MDB::query failed: ' 252 . $prepared_query->getMessage() , __FILE__, __LINE__); 253 } 254 } 255 256 /** 257 * Removes a dataset from the database 258 * 259 * @param string dataset ID 260 * @param string cache group 261 */ 262 function remove($id, $group) 263 { 264 $this->flushPreload($id, $group); 265 266 $query = 'DELETE FROM ' . $this->cache_table 267 .' WHERE id=' . $this->db->getTextValue($id) 268 .' AND cachegroup=' . $this->db->getTextValue($group); 269 270 $res = $this->db->query($query); 271 if (MDB::isError($res)) { 272 return new Cache_Error('MDB::query failed: ' 273 . $this->db->errorMessage($res), __FILE__, __LINE__); 274 } 275 } 276 277 /** 278 * Remove all cached data for a certain group, or empty 279 * the cache table if no group is specified. 280 * 281 * @param string cache group 282 */ 283 function flush($group = '') 284 { 285 $this->flushPreload(); 286 287 if ($group) { 288 $query = 'DELETE FROM ' . $this->cache_table 289 .' WHERE cachegroup=' . $this->db->getTextValue($group); 290 } else { 291 $query = 'DELETE FROM ' . $this->cache_table; 292 } 293 294 $res = $this->db->query($query); 295 if (MDB::isError($res)) { 296 return new Cache_Error('MDB::query failed: ' 297 . $this->db->errorMessage($res), __FILE__, __LINE__); 298 } 299 } 300 301 /** 302 * Check if a dataset ID/group exists. 303 * 304 * @param string dataset ID 305 * @param string cache group 306 * @return boolean 307 */ 308 function idExists($id, $group) 309 { 310 $query = 'SELECT id FROM ' . $this->cache_table 311 .' WHERE id=' . $this->db->getTextValue($id) 312 .' AND cachegroup=' . $this->db->getTextValue($group); 313 echo $query; 314 $res = $this->db->query($query); 315 if (MDB::isError($res)) { 316 return new Cache_Error('MDB::query failed: ' 317 . $this->db->errorMessage($res), __FILE__, __LINE__); 318 } 319 $row = $this->db->fetchInto($res); 320 321 if (is_array($row)) { 322 return true; 323 } 324 return false; 325 } 326 327 /** 328 * Garbage collector. 329 * 330 * @param int maxlifetime 331 */ 332 function garbageCollection($maxlifetime) 333 { 334 $this->flushPreload(); 335 $query = 'DELETE FROM ' . $this->cache_table 336 .' WHERE (expires <= ' . time() 337 .' AND expires > 0) OR changed <= '. time() - $maxlifetime; 338 339 $res = $this->db->query($query); 340 341 $query = 'SELECT sum(length(cachedata)) as CacheSize FROM ' 342 . $this->cache_table; 343 344 $cachesize = $this->db->getOne($query); 345 if (MDB::isError($cachesize)) { 346 return new Cache_Error('MDB::query failed: ' 347 . $this->db->errorMessage($cachesize), __FILE__, __LINE__); 348 } 349 //if cache is to big. 350 if ($cachesize > $this->highwater) { 351 //find the lowwater mark. 352 $query = 'SELECT length(cachedata) as size, changed FROM ' 353 . $this->cache_table .' ORDER BY changed DESC'; 354 355 $res = $this->db->query($query); 356 if (MDB::isError($res)) { 357 return new Cache_Error('MDB::query failed: ' 358 . $this->db->errorMessage($res), __FILE__, __LINE__); 359 } 360 $numrows = $this->db->numRows($res); 361 $keep_size = 0; 362 while ($keep_size < $this->lowwater && $numrows--) { 363 $entry = $this->db->fetchInto($res,MDB_FETCHMODE_ASSOC); 364 $keep_size += $entry['size']; 365 } 366 367 //delete all entries, which were changed before the "lowwater mark" 368 $query = 'DELETE FROM ' . $this->cache_table 369 .' WHERE changed<='.($entry['changed'] ? $entry['changed'] : 0); 370 371 $res = $this->db->query($query); 372 if (MDB::isError($res)) { 373 return new Cache_Error('MDB::query failed: ' 374 . $this->db->errorMessage($res), __FILE__, __LINE__); 375 } 376 } 377 } 378 379} 380?>