data)) { // If the data is permanent or we're not enforcing a minimum cache lifetime // always return the cached data. if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { $cache->data = db_decode_blob($cache->data); } // If enforcing a minimum cache lifetime, validate that the data is // currently valid for this user before we return it by making sure the // cache entry was created before the timestamp in the current session's // cache timer. The cache variable is loaded into the $user object by // sess_read() in session.inc. else { if ($user->cache > $cache->created) { // This cache data is too old and thus not valid for us, ignore it. return 0; } else { $cache->data = db_decode_blob($cache->data); } } return $cache; } return 0; } /** * Store data in the persistent cache. * * The persistent cache is split up into four database * tables. Contributed modules can add additional tables. * * 'cache_page': This table stores generated pages for anonymous * users. This is the only table affected by the page cache setting on * the administrator panel. * * 'cache_menu': Stores the cachable part of the users' menus. * * 'cache_filter': Stores filtered pieces of content. This table is * periodically cleared of stale entries by cron. * * 'cache': Generic cache storage table. * * The reasons for having several tables are as follows: * * - smaller tables allow for faster selects and inserts * - we try to put fast changing cache items and rather static * ones into different tables. The effect is that only the fast * changing tables will need a lot of writes to disk. The more * static tables will also be better cachable with MySQL's query cache * * @param $cid * The cache ID of the data to store. * @param $table * The table $table to store the data in. Valid core values are 'cache_filter', * 'cache_menu', 'cache_page', or 'cache'. * @param $data * The data to store in the cache. Complex data types must be serialized first. * @param $expire * One of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. * @param $headers * A string containing HTTP header information for cached pages. */ function cache_set($cid, $table = 'cache', $data, $expire = CACHE_PERMANENT, $headers = NULL) { db_lock_table($table); db_query("UPDATE {%s} SET data = %b, created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $table, $data, time(), $expire, $headers, $cid); if (!db_affected_rows()) { @db_query("INSERT INTO {%s} (cid, data, created, expire, headers) VALUES ('%s', %b, %d, %d, '%s')", $table, $cid, $data, time(), $expire, $headers); } db_unlock_tables(); } /** * * Expire data from the cache. If called without arguments, expirable * entries will be cleared from the cache_page table. * * @param $cid * If set, the cache ID to delete. Otherwise, all cache entries that can * expire are deleted. * * @param $table * If set, the table $table to delete from. Mandatory * argument if $cid is set. * * @param $wildcard * If set to TRUE, the $cid is treated as a substring * to match rather than a complete ID. The match is a right hand * match. If '*' is given as $cid, the table $table will be emptied. */ function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) { global $user; if (!isset($cid) && !isset($table)) { cache_clear_all(NULL, 'cache_page'); return; } if (empty($cid)) { if (variable_get('cache_lifetime', 0)) { // We store the time in the current user's $user->cache variable which // will be saved into the sessions table by sess_write(). We then // simulate that the cache was flushed for this user by not returning // cached data that was cached before the timestamp. $user->cache = time(); $cache_flush = variable_get('cache_flush', 0); if ($cache_flush == 0) { // This is the first request to clear the cache, start a timer. variable_set('cache_flush', time()); } else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) { // Clear the cache for everyone, cache_flush_delay seconds have // passed since the first request to clear the cache. db_query("DELETE FROM {%s} WHERE expire != %d AND expire < %d", $table, CACHE_PERMANENT, time()); variable_set('cache_flush', 0); } } else { // No minimum cache lifetime, flush all temporary cache entries now. db_query("DELETE FROM {%s} WHERE expire != %d AND expire < %d", $table, CACHE_PERMANENT, time()); } } else { if ($wildcard) { if ($cid == '*') { db_query("TRUNCATE TABLE {%s}", $table); } else { db_query("DELETE FROM {%s} WHERE cid LIKE '%s%%'", $table, $cid); } } else { db_query("DELETE FROM {%s} WHERE cid = '%s'", $table, $cid); } } } ====================== | // | Sebastian Bergmann | // | Chuck Hagenbuch | // +----------------------------------------------------------------------+ // // $Id: db.php,v 1.8 2005/01/26 09:42:30 dufuz Exp $ require_once 'DB.php'; require_once 'Cache/Container.php'; /** * PEAR/DB Cache Container. * * WARNING: Other systems might or might not support certain datatypes of * the tables shown. As far as I know there's no large binary * type in SQL-92 or SQL-99. Postgres seems to lack any * BLOB or TEXT type, for MS-SQL you could use IMAGE, don't know * about other databases. Please add sugestions for other databases to * the inline docs. * * 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. * * For _MySQL_ you need this DB table: * * CREATE TABLE cache ( * id CHAR(32) NOT null DEFAULT '', * cachegroup VARCHAR(127) NOT null DEFAULT '', * cachedata BLOB NOT null DEFAULT '', * userdata VARCHAR(255) NOT null DEFAULT '', * expires INT(9) NOT null DEFAULT 0, * * changed TIMESTAMP(14) NOT null, * * INDEX (expires), * PRIMARY KEY (id, cachegroup) * ) * * @author Sebastian Bergmann * @version $Id: db.php,v 1.8 2005/01/26 09:42:30 dufuz Exp $ * @package Cache */ class Cache_Container_db extends Cache_Container { /** * Name of the DB table to store caching data * * @see Cache_Container_file::$filename_prefix */ var $cache_table = ''; /** * PEAR DB dsn to use. * * @var string */ var $dsn = ''; /** * PEAR DB object * * @var object PEAR_DB */ var $db; function Cache_Container_db($options) { if (!is_array($options) || !isset($options['dsn'])) { return new Cache_Error('No dsn specified!', __FILE__, __LINE__); } $this->setOptions($options, array_merge($this->allowed_options, array('dsn', 'cache_table'))); if (!$this->dsn) { return new Cache_Error('No dsn specified!', __FILE__, __LINE__); } $this->db = DB::connect($this->dsn, true); if (PEAR::isError($this->db)) { return new Cache_Error('DB::connect failed: ' . DB::errorMessage($this->db), __FILE__, __LINE__); } $this->db->setFetchMode(DB_FETCHMODE_ASSOC); } function fetch($id, $group) { $query = sprintf("SELECT cachedata, userdata, expires FROM %s WHERE id = '%s' AND cachegroup = '%s'", $this->cache_table, addslashes($id), addslashes($group) ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } $row = $res->fetchRow(); if (is_array($row)) { $data = array($row['expires'], $this->decode($row['cachedata']), $row['userdata']); } else { $data = array(null, null, null); } // last used required by the garbage collection // WARNING: might be MySQL specific $query = sprintf("UPDATE %s SET changed = (NOW() + 0) WHERE id = '%s' AND cachegroup = '%s'", $this->cache_table, addslashes($id), addslashes($group) ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } return $data; } /** * Stores a dataset. * * WARNING: we use the SQL command REPLACE INTO this might be * MySQL specific. As MySQL is very popular the method should * work fine for 95% of you. */ function save($id, $data, $expires, $group, $userdata) { $this->flushPreload($id, $group); $query = sprintf("REPLACE INTO %s (userdata, cachedata, expires, id, cachegroup) VALUES ('%s', '%s', %d, '%s', '%s')", $this->cache_table, addslashes($userdata), addslashes($this->encode($data)), $this->getExpiresAbsolute($expires) , addslashes($id), addslashes($group) ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res) , __FILE__, __LINE__); } } function remove($id, $group) { $this->flushPreload($id, $group); $query = sprintf("DELETE FROM %s WHERE id = '%s' and cachegroup = '%s'", $this->cache_table, addslashes($id), addslashes($group) ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } } function flush($group = '') { $this->flushPreload(); if ($group) { $query = sprintf("DELETE FROM %s WHERE cachegroup = '%s'", $this->cache_table, addslashes($group)); } else { $query = sprintf("DELETE FROM %s", $this->cache_table); } $res = $this->db->query($query); if (PEAR::isError($res)) return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } function idExists($id, $group) { $query = sprintf("SELECT id FROM %s WHERE ID = '%s' AND cachegroup = '%s'", $this->cache_table, addslashes($id), addslashes($group) ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } $row = $res->fetchRow(); if (is_array($row)) { return true; } return false; } function garbageCollection($maxlifetime) { $this->flushPreload(); $query = sprintf('DELETE FROM %s WHERE (expires <= %d AND expires > 0) OR changed <= %d', $this->cache_table, time(), time() - $maxlifetime ); $res = $this->db->query($query); $query = sprintf('select sum(length(cachedata)) as CacheSize from %s', $this->cache_table ); $cachesize = $this->db->GetOne($query); if (PEAR::isError($cachesize)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($cachesize), __FILE__, __LINE__); } //if cache is to big. if ($cachesize > $this->highwater) { //find the lowwater mark. $query = sprintf('select length(cachedata) as size, changed from %s order by changed DESC', $this->cache_table ); $res = $this->db->query($query); if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } $numrows = $this->db->numRows($res); $keep_size = 0; while ($keep_size < $this->lowwater && $numrows--) { $entry = $res->fetchRow(DB_FETCHMODE_ASSOC); $keep_size += $entry['size']; } //delete all entries, which were changed before the "lowwwater mark" $query = sprintf('delete from %s where changed <= '.($entry['changed'] ? $entry['changed'] : 0), $this->cache_table ); $res = $this->db->query($query); } if (PEAR::isError($res)) { return new Cache_Error('DB::query failed: ' . DB::errorMessage($res), __FILE__, __LINE__); } } } ?> ================= ". time(). " or CACHEEXPIRATION=0)" ); if ($res && mysql_num_rows($res)) { if ($row = mysql_fetch_array($res)) { // restore data into global scope from found row $GLOBALS["jpcachedata_gzdata"] = $row["GZDATA"]; $GLOBALS["jpcachedata_datasize"] = $row["DATASIZE"]; $GLOBALS["jpcachedata_datacrc"] = $row["DATACRC"]; return true; } } return false; } /* jpcache_write() * * Will (try to) write out the cachedata to the db */ function jpcache_write($gzdata, $datasize, $datacrc) { $dbtable = $GLOBALS["JPCACHE_DB_TABLE"]; // XXX: Later on, maybe implement locking mechanism inhere. // Check if it already exists $res = jpcache_db_query("select CACHEEXPIRATION from $dbtable". " where CACHEKEY='". addslashes($GLOBALS["jpcache_key"]). "'" ); if (!$res || mysql_num_rows($res) < 1) { // Key not found, so insert $res = jpcache_db_query("insert into $dbtable". " (CACHEKEY, CACHEEXPIRATION, GZDATA,". " DATASIZE, DATACRC) values ('". addslashes($GLOBALS["jpcache_key"]). "',". (($GLOBALS["JPCACHE_TIME"] != 0) ? (time()+$GLOBALS["JPCACHE_TIME"]) : 0). ",'". addslashes($gzdata). "', $datasize, $datacrc)" ); // This fails with unique-key violation when another thread has just // inserted the same key. Just continue, as the result is (almost) // the same. } else { // Key found, so update $res = jpcache_db_query("update $dbtable set CACHEEXPIRATION=". (($GLOBALS["JPCACHE_TIME"] != 0) ? (time()+$GLOBALS["JPCACHE_TIME"]) : 0). ", GZDATA='". addslashes($gzdata). "', DATASIZE=$datasize, DATACRC=$datacrc where". " CACHEKEY='". addslashes($GLOBALS["jpcache_key"]). "'" ); // This might be an update too much, but it shouldn't matter } } /* jpcache_do_gc() * * Performs the actual garbagecollection */ function jpcache_do_gc() { jpcache_db_query("delete from ". $GLOBALS["JPCACHE_DB_TABLE"]. " where CACHEEXPIRATION<=". time(). " and CACHEEXPIRATION!=0" ); // Are we allowed to do an optimize table-call? // As noted, first check if this works on your mysql-installation! if ($GLOBALS["JPCACHE_OPTIMIZE"]) { jpcache_db_query("OPTIMIZE TABLE ".$GLOBALS["JPCACHE_DB_TABLE"]); } } /* jpcache_do_start() * * Additional code that is executed before main jpcache-code kicks in. */ function jpcache_do_start() { // Connect to db jpcache_db_connect(); } /* jpcache_do_end() * * Additional code that is executed after caching has been performed, * but just before output is returned. No new output can be added! */ function jpcache_do_end() { // Disconnect from db jpcache_db_disconnect(); } ?> # phpMyAdmin MySQL-Dump # http://www.htmlwizard.net/phpMyAdmin/ # # Host: localhost:3300 Database : jpcache # -------------------------------------------------------- # # Table structure for table 'CACHEDATA' # CREATE TABLE CACHEDATA ( CACHEKEY varchar(255) NOT NULL, CACHEEXPIRATION int(11) NOT NULL, GZDATA blob, DATASIZE int(11), DATACRC int(11), PRIMARY KEY (CACHEKEY) );