Add an objectcache table for limited caching when memcached isn't
[lhc/web/wiklou.git] / includes / ObjectCache.php
1 <?php
2 # Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
3 # http://www.mediawiki.org/
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 # http://www.gnu.org/copyleft/gpl.html
19
20 # Simple generic object store
21 # interface is intended to be more or less compatible with
22 # the PHP memcached client.
23 #
24 # backends for local hash array and SQL table included:
25 # $bag = new HashBagOStuff();
26 # $bag = new MysqlBagOStuff($tablename); # connect to db first
27
28 class /* abstract */ BagOStuff {
29 var $debugmode;
30
31 function BagOStuff() {
32 set_debug( false );
33 }
34
35 function set_debug($bool) {
36 $this->debugmode = $bool;
37 }
38
39 /* *** THE GUTS OF THE OPERATION *** */
40 /* Override these with functional things in subclasses */
41
42 function get($key) {
43 /* stub */
44 return false;
45 }
46
47 function set($key, $value, $exptime=0) {
48 /* stub */
49 return false;
50 }
51
52 function delete($key, $time=0) {
53 /* stub */
54 return false;
55 }
56
57 /* *** Emulated functions *** */
58 /* Better performance can likely be got with custom written versions */
59 function get_multi($keys) {
60 $out = array();
61 foreach($keys as $key)
62 $out[$key] = $this->get($key);
63 return $out;
64 }
65
66 function set_multi($hash, $exptime=0) {
67 foreach($hash as $key => $value)
68 $this->set($key, $value, $exptime);
69 }
70
71 function add($key, $value, $exptime=0) {
72 if( $this->get($key) === false )
73 $this->set($key, $value, $exptime);
74 }
75
76 function add_multi($hash, $exptime=0) {
77 foreach($hash as $key => $value)
78 $this->add($key, $value, $exptime);
79 }
80
81 function delete_multi($keys, $time=0) {
82 foreach($keys as $key)
83 $this->delete($key, $time);
84 }
85
86 function replace($key, $value, $exptime=0) {
87 if( $this->get($key) !== false )
88 $this->set($key, $value, $exptime);
89 }
90
91 function incr($key, $value=1) {
92 $value = intval($value);
93 if($value < 0) $value = 0;
94 if( ($n = $this->get($key)) !== false ) {
95 $this->set($key, $n+$value); // exptime?
96 return $n+$value;
97 } else {
98 return false;
99 }
100 }
101
102 function decr($key, $value=1) {
103 $value = intval($value);
104 if($value < 0) $value = 0;
105 if( ($n = $this->get($key)) !== false ) {
106 $m = $n - $value;
107 if($m < 0) $m = 0;
108 $this->set($key, $m); // exptime?
109 return $m;
110 } else {
111 return false;
112 }
113 }
114
115 function _debug($text) {
116 if($this->debugmode)
117 echo "\ndebug: $text\n";
118 }
119 }
120
121
122 /* Functional versions! */
123 class HashBagOStuff extends BagOStuff {
124 /*
125 This is a test of the interface, mainly. It stores
126 things in an associative array, which is not going to
127 persist between program runs.
128 */
129 var $bag;
130
131 function HashBagOStuff() {
132 $this->bag = array();
133 }
134
135 function _expire($key) {
136 $et = $this->bag[$key][1];
137 if(($et == 0) || ($et > time()))
138 return false;
139 $this->delete($key);
140 return true;
141 }
142
143 function get($key) {
144 if(!$this->bag[$key])
145 return false;
146 if($this->_expire($key))
147 return false;
148 return $this->bag[$key][0];
149 }
150
151 function set($key,$value,$exptime=0) {
152 if(($exptime != 0) && ($exptime < 3600*24*30))
153 $exptime = time() + $exptime;
154 $this->bag[$key] = array( $value, $exptime );
155 }
156
157 function delete($key,$time=0) {
158 if(!$this->bag[$key])
159 return false;
160 unset($this->bag[$key]);
161 return true;
162 }
163 }
164
165 /*
166 CREATE TABLE objectcache (
167 keyname char(255) binary not null default '',
168 value mediumblob,
169 exptime datetime,
170 unique key (keyname),
171 key (exptime)
172 );
173 */
174 class /* abstract */ SqlBagOStuff extends BagOStuff {
175 var $table;
176 function SqlBagOStuff($tablename = "objectcache") {
177 $this->table = $tablename;
178 }
179
180 function get($key) {
181 /* expire old entries if any */
182 $this->expireall();
183
184 $res = $this->_query(
185 "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key);
186 if(!$res) {
187 $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
188 return false;
189 }
190 if($arr = $this->_fetchrow($res)) {
191 $this->_debug("get: retrieved data; exp time is " . $arr['exptime']);
192 return unserialize($arr['value']);
193 } else {
194 $this->_debug("get: no matching rows");
195 }
196 return false;
197 }
198
199 function set($key,$value,$exptime=0) {
200 $exptime = intval($exptime);
201 if($exptime < 0) $exptime = 0;
202 if($exptime == 0) {
203 $exp = $this->_maxdatetime();
204 } else {
205 if($exptime < 3600*24*30)
206 $exptime += time();
207 $exp = $this->_fromunixtime($exptime);
208 }
209 $this->delete( $key );
210 $this->_query(
211 "INSERT INTO $0 (keyname,value,exptime) VALUES('$1','$2',$exp)",
212 $key, serialize(&$value));
213 return true; /* ? */
214 }
215
216 function delete($key,$time=0) {
217 $this->_query(
218 "DELETE FROM $0 WHERE keyname='$1'", $key );
219 return true; /* ? */
220 }
221
222 function _query($sql) {
223 $reps = func_get_args();
224 $reps[0] = $this->table;
225 // ewwww
226 for($i=0;$i<count($reps);$i++) {
227 $sql = str_replace(
228 "$" . $i,
229 $this->_strencode($reps[$i]),
230 $sql);
231 }
232 $res = $this->_doquery($sql);
233 if($res === false) {
234 $this->_debug("query failed: " . $this->_dberror($res));
235 }
236 return $res;
237 }
238
239 function _strencode($str) {
240 /* Protect strings in SQL */
241 return str_replace( "'", "''", $str );
242 }
243
244 function _doquery($sql) {
245 die( "abstract function SqlBagOStuff::_doquery() must be defined" );
246 }
247
248 function _fetchrow($res) {
249 die( "abstract function SqlBagOStuff::_fetchrow() must be defined" );
250 }
251
252 function _freeresult($result) {
253 /* stub */
254 return false;
255 }
256
257 function _dberror($result) {
258 /* stub */
259 return "unknown error";
260 }
261
262 function _maxdatetime() {
263 die( "abstract function SqlBagOStuff::_maxdatetime() must be defined" );
264 }
265
266 function _fromunixtime() {
267 die( "abstract function SqlBagOStuff::_fromunixtime() must be defined" );
268 }
269
270 function expireall() {
271 /* Remove any items that have expired */
272 $this->_query( "DELETE FROM $0 WHERE exptime<=NOW()" );
273 }
274
275 function deleteall() {
276 /* Clear *all* items from cache table */
277 $this->_query( "DELETE FROM $0" );
278 }
279 }
280
281 class MysqlBagOStuff extends SqlBagOStuff {
282 function _doquery($sql) {
283 return mysql_query($sql);
284 }
285 function _fetchrow($result) {
286 return mysql_fetch_array($result);
287 }
288 function _freeresult($result) {
289 return mysql_free_result($result);
290 }
291 function _dberror($result) {
292 if($result)
293 return mysql_error($result);
294 else
295 return mysql_error();
296 }
297
298 function _maxdatetime() {
299 return "'9999-12-31 12:59:59'";
300 }
301
302 function _fromunixtime($ts) {
303 return "FROM_UNIXTIME($ts)";
304 }
305
306 function _strencode($s) {
307 return mysql_escape_string($s);
308 }
309 }
310
311 class MediaWikiBagOStuff extends MysqlBagOStuff {
312 function _doquery($sql) {
313 return wfQuery($sql, DB_READ, "MediaWikiBagOStuff:_doquery");
314 }
315 function _freeresult($result) {
316 return wfFreeResult($result);
317 }
318 }
319
320 ?>