1<?php 2// +----------------------------------------------------------------------+ 3// | PEAR :: Cache | 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// | Authors: Ulf Wendel <ulf.wendel@phpdoc.de> | 16// | Sebastian Bergmann <sb@sebastian-bergmann.de> | 17// | Chuck Hagenbuch <chuck@horde.org> | 18// +----------------------------------------------------------------------+ 19// 20// $Id: db.php 178288 2005-01-26 09:42:30Z dufuz $ 21 22require_once 'DB.php'; 23require_once 'Cache/Container.php'; 24 25/** 26* PEAR/DB Cache Container. 27* 28* WARNING: Other systems might or might not support certain datatypes of 29* the tables shown. As far as I know there's no large binary 30* type in SQL-92 or SQL-99. Postgres seems to lack any 31* BLOB or TEXT type, for MS-SQL you could use IMAGE, don't know 32* about other databases. Please add sugestions for other databases to 33* the inline docs. 34* 35* The field 'changed' has no meaning for the Cache itself. It's just there 36* because it's a good idea to have an automatically updated timestamp 37* field for debugging in all of your tables. 38* 39* For _MySQL_ you need this DB table: 40* 41* CREATE TABLE cache ( 42* id CHAR(32) NOT null DEFAULT '', 43* cachegroup VARCHAR(127) NOT null DEFAULT '', 44* cachedata BLOB NOT null DEFAULT '', 45* userdata VARCHAR(255) NOT null DEFAULT '', 46* expires INT(9) NOT null DEFAULT 0, 47* 48* changed TIMESTAMP(14) NOT null, 49* 50* INDEX (expires), 51* PRIMARY KEY (id, cachegroup) 52* ) 53* 54* @author Sebastian Bergmann <sb@sebastian-bergmann.de> 55* @version $Id: db.php 178288 2005-01-26 09:42:30Z dufuz $ 56* @package Cache 57*/ 58class Cache_Container_db extends Cache_Container 59{ 60 61 /** 62 * Name of the DB table to store caching data 63 * 64 * @see Cache_Container_file::$filename_prefix 65 */ 66 var $cache_table = ''; 67 68 /** 69 * PEAR DB dsn to use. 70 * 71 * @var string 72 */ 73 var $dsn = ''; 74 75 /** 76 * PEAR DB object 77 * 78 * @var object PEAR_DB 79 */ 80 var $db; 81 82 function Cache_Container_db($options) 83 { 84 if (!is_array($options) || !isset($options['dsn'])) { 85 return new Cache_Error('No dsn specified!', __FILE__, __LINE__); 86 } 87 88 $this->setOptions($options, array_merge($this->allowed_options, array('dsn', 'cache_table'))); 89 90 if (!$this->dsn) { 91 return new Cache_Error('No dsn specified!', __FILE__, __LINE__); 92 } 93 $this->db = DB::connect($this->dsn, true); 94 if (PEAR::isError($this->db)) { 95 return new Cache_Error('DB::connect failed: ' . DB::errorMessage($this->db), __FILE__, __LINE__); 96 } 97 $this->db->setFetchMode(DB_FETCHMODE_ASSOC); 98 } 99 100 function fetch($id, $group) 101 { 102 $query = sprintf("SELECT cachedata, userdata, expires FROM %s WHERE id = '%s' AND cachegroup = '%s'", 103 $this->cache_table, 104 addslashes($id), 105 addslashes($group) 106 ); 107 108 $res = $this->db->query($query); 109 110 if (PEAR::isError($res)) { 111 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 112 } 113 $row = $res->fetchRow(); 114 if (is_array($row)) { 115 $data = array($row['expires'], $this->decode($row['cachedata']), $row['userdata']); 116 } else { 117 $data = array(null, null, null); 118 } 119 // last used required by the garbage collection 120 // WARNING: might be MySQL specific 121 $query = sprintf("UPDATE %s SET changed = (NOW() + 0) WHERE id = '%s' AND cachegroup = '%s'", 122 $this->cache_table, 123 addslashes($id), 124 addslashes($group) 125 ); 126 127 $res = $this->db->query($query); 128 129 if (PEAR::isError($res)) { 130 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 131 } 132 return $data; 133 } 134 135 /** 136 * Stores a dataset. 137 * 138 * WARNING: we use the SQL command REPLACE INTO this might be 139 * MySQL specific. As MySQL is very popular the method should 140 * work fine for 95% of you. 141 */ 142 function save($id, $data, $expires, $group, $userdata) 143 { 144 $this->flushPreload($id, $group); 145 146 $query = sprintf("REPLACE INTO %s (userdata, cachedata, expires, id, cachegroup) VALUES ('%s', '%s', %d, '%s', '%s')", 147 $this->cache_table, 148 addslashes($userdata), 149 addslashes($this->encode($data)), 150 $this->getExpiresAbsolute($expires) , 151 addslashes($id), 152 addslashes($group) 153 ); 154 155 $res = $this->db->query($query); 156 157 if (PEAR::isError($res)) { 158 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res) , __FILE__, __LINE__); 159 } 160 } 161 162 function remove($id, $group) 163 { 164 $this->flushPreload($id, $group); 165 166 $query = sprintf("DELETE FROM %s WHERE id = '%s' and cachegroup = '%s'", 167 $this->cache_table, 168 addslashes($id), 169 addslashes($group) 170 ); 171 172 $res = $this->db->query($query); 173 174 if (PEAR::isError($res)) { 175 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 176 } 177 } 178 179 function flush($group = '') 180 { 181 $this->flushPreload(); 182 183 if ($group) { 184 $query = sprintf("DELETE FROM %s WHERE cachegroup = '%s'", $this->cache_table, addslashes($group)); 185 } else { 186 $query = sprintf("DELETE FROM %s", $this->cache_table); 187 } 188 189 $res = $this->db->query($query); 190 191 if (PEAR::isError($res)) 192 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 193 } 194 195 function idExists($id, $group) 196 { 197 $query = sprintf("SELECT id FROM %s WHERE ID = '%s' AND cachegroup = '%s'", 198 $this->cache_table, 199 addslashes($id), 200 addslashes($group) 201 ); 202 203 $res = $this->db->query($query); 204 205 if (PEAR::isError($res)) { 206 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 207 } 208 $row = $res->fetchRow(); 209 210 if (is_array($row)) { 211 return true; 212 } 213 return false; 214 } 215 216 function garbageCollection($maxlifetime) 217 { 218 $this->flushPreload(); 219 220 $query = sprintf('DELETE FROM %s WHERE (expires <= %d AND expires > 0) OR changed <= %d', 221 $this->cache_table, 222 time(), 223 time() - $maxlifetime 224 ); 225 226 $res = $this->db->query($query); 227 228 $query = sprintf('select sum(length(cachedata)) as CacheSize from %s', 229 $this->cache_table 230 ); 231 $cachesize = $this->db->GetOne($query); 232 if (PEAR::isError($cachesize)) { 233 return new Cache_Error('DB::query failed: ' . DB::errorMessage($cachesize), __FILE__, __LINE__); 234 } 235 236 //if cache is to big. 237 if ($cachesize > $this->highwater) { 238 //find the lowwater mark. 239 $query = sprintf('select length(cachedata) as size, changed from %s order by changed DESC', 240 $this->cache_table 241 ); 242 $res = $this->db->query($query); 243 if (PEAR::isError($res)) { 244 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 245 } 246 247 $numrows = $this->db->numRows($res); 248 $keep_size = 0; 249 while ($keep_size < $this->lowwater && $numrows--) { 250 $entry = $res->fetchRow(DB_FETCHMODE_ASSOC); 251 $keep_size += $entry['size']; 252 } 253 254 //delete all entries, which were changed before the "lowwwater mark" 255 $query = sprintf('delete from %s where changed <= '.($entry['changed'] ? $entry['changed'] : 0), 256 $this->cache_table 257 ); 258 $res = $this->db->query($query); 259 } 260 261 if (PEAR::isError($res)) { 262 return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); 263 } 264 } 265 266} 267?> 268