182756ab93d16fd1106a7d6f9de80322b46f49cb
[lhc/web/wiklou.git] / includes / BagOStuff.php
1 <?php
2 #
3 # Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
4 # http://www.mediawiki.org/
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # http://www.gnu.org/copyleft/gpl.html
20 /**
21 *
22 * @package MediaWiki
23 */
24
25 /**
26 * Simple generic object store
27 *
28 * interface is intended to be more or less compatible with
29 * the PHP memcached client.
30 *
31 * backends for local hash array and SQL table included:
32 * $bag = new HashBagOStuff();
33 * $bag = new MysqlBagOStuff($tablename); # connect to db first
34 *
35 * @package MediaWiki
36 */
37 class BagOStuff {
38 var $debugmode;
39
40 function BagOStuff() {
41 $this->set_debug( false );
42 }
43
44 function set_debug($bool) {
45 $this->debugmode = $bool;
46 }
47
48 /* *** THE GUTS OF THE OPERATION *** */
49 /* Override these with functional things in subclasses */
50
51 function get($key) {
52 /* stub */
53 return false;
54 }
55
56 function set($key, $value, $exptime=0) {
57 /* stub */
58 return false;
59 }
60
61 function delete($key, $time=0) {
62 /* stub */
63 return false;
64 }
65
66 function lock($key, $timeout = 0) {
67 /* stub */
68 return true;
69 }
70
71 function unlock($key) {
72 /* stub */
73 return true;
74 }
75
76 /* *** Emulated functions *** */
77 /* Better performance can likely be got with custom written versions */
78 function get_multi($keys) {
79 $out = array();
80 foreach($keys as $key)
81 $out[$key] = $this->get($key);
82 return $out;
83 }
84
85 function set_multi($hash, $exptime=0) {
86 foreach($hash as $key => $value)
87 $this->set($key, $value, $exptime);
88 }
89
90 function add($key, $value, $exptime=0) {
91 if( $this->get($key) == false ) {
92 $this->set($key, $value, $exptime);
93 return true;
94 }
95 }
96
97 function add_multi($hash, $exptime=0) {
98 foreach($hash as $key => $value)
99 $this->add($key, $value, $exptime);
100 }
101
102 function delete_multi($keys, $time=0) {
103 foreach($keys as $key)
104 $this->delete($key, $time);
105 }
106
107 function replace($key, $value, $exptime=0) {
108 if( $this->get($key) !== false )
109 $this->set($key, $value, $exptime);
110 }
111
112 function incr($key, $value=1) {
113 if ( !$this->lock($key) ) {
114 return false;
115 }
116 $value = intval($value);
117 if($value < 0) $value = 0;
118
119 $n = false;
120 if( ($n = $this->get($key)) !== false ) {
121 $n += $value;
122 $this->set($key, $n); // exptime?
123 }
124 $this->unlock($key);
125 return $n;
126 }
127
128 function decr($key, $value=1) {
129 if ( !$this->lock($key) ) {
130 return false;
131 }
132 $value = intval($value);
133 if($value < 0) $value = 0;
134
135 $m = false;
136 if( ($n = $this->get($key)) !== false ) {
137 $m = $n - $value;
138 if($m < 0) $m = 0;
139 $this->set($key, $m); // exptime?
140 }
141 $this->unlock($key);
142 return $m;
143 }
144
145 function _debug($text) {
146 if($this->debugmode)
147 wfDebug("BagOStuff debug: $text\n");
148 }
149 }
150
151
152 /**
153 * Functional versions!
154 * @todo document
155 * @package MediaWiki
156 */
157 class HashBagOStuff extends BagOStuff {
158 /*
159 This is a test of the interface, mainly. It stores
160 things in an associative array, which is not going to
161 persist between program runs.
162 */
163 var $bag;
164
165 function HashBagOStuff() {
166 $this->bag = array();
167 }
168
169 function _expire($key) {
170 $et = $this->bag[$key][1];
171 if(($et == 0) || ($et > time()))
172 return false;
173 $this->delete($key);
174 return true;
175 }
176
177 function get($key) {
178 if(!$this->bag[$key])
179 return false;
180 if($this->_expire($key))
181 return false;
182 return $this->bag[$key][0];
183 }
184
185 function set($key,$value,$exptime=0) {
186 if(($exptime != 0) && ($exptime < 3600*24*30))
187 $exptime = time() + $exptime;
188 $this->bag[$key] = array( $value, $exptime );
189 }
190
191 function delete($key,$time=0) {
192 if(!$this->bag[$key])
193 return false;
194 unset($this->bag[$key]);
195 return true;
196 }
197 }
198
199 /*
200 CREATE TABLE objectcache (
201 keyname char(255) binary not null default '',
202 value mediumblob,
203 exptime datetime,
204 unique key (keyname),
205 key (exptime)
206 );
207 */
208
209 /**
210 * @todo document
211 * @abstract
212 * @package MediaWiki
213 */
214 abstract class SqlBagOStuff extends BagOStuff {
215 var $table;
216 var $lastexpireall = 0;
217
218 function SqlBagOStuff($tablename = 'objectcache') {
219 $this->table = $tablename;
220 }
221
222 function get($key) {
223 /* expire old entries if any */
224 $this->garbageCollect();
225
226 $res = $this->_query(
227 "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key);
228 if(!$res) {
229 $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
230 return false;
231 }
232 if($row=$this->_fetchobject($res)) {
233 $this->_debug("get: retrieved data; exp time is " . $row->exptime);
234 return $this->_unserialize($this->_blobdecode($row->value));
235 } else {
236 $this->_debug('get: no matching rows');
237 }
238 return false;
239 }
240
241 function set($key,$value,$exptime=0) {
242 $exptime = intval($exptime);
243 if($exptime < 0) $exptime = 0;
244 if($exptime == 0) {
245 $exp = $this->_maxdatetime();
246 } else {
247 if($exptime < 3600*24*30)
248 $exptime += time();
249 $exp = $this->_fromunixtime($exptime);
250 }
251 $this->delete( $key );
252 $this->_doinsert($this->getTableName(), array(
253 'keyname' => $key,
254 'value' => $this->_blobencode($this->_serialize($value)),
255 'exptime' => $exp
256 ));
257 return true; /* ? */
258 }
259
260 function delete($key,$time=0) {
261 $this->_query(
262 "DELETE FROM $0 WHERE keyname='$1'", $key );
263 return true; /* ? */
264 }
265
266 function getTableName() {
267 return $this->table;
268 }
269
270 function _query($sql) {
271 $reps = func_get_args();
272 $reps[0] = $this->getTableName();
273 // ewwww
274 for($i=0;$i<count($reps);$i++) {
275 $sql = str_replace(
276 '$' . $i,
277 $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i],
278 $sql);
279 }
280 $res = $this->_doquery($sql);
281 if($res == false) {
282 $this->_debug('query failed: ' . $this->_dberror($res));
283 }
284 return $res;
285 }
286
287 function _strencode($str) {
288 /* Protect strings in SQL */
289 return str_replace( "'", "''", $str );
290 }
291 function _blobencode($str) {
292 return $str;
293 }
294 function _blobdecode($str) {
295 return $str;
296 }
297
298 abstract function _doinsert($table, $vals);
299 abstract function _doquery($sql);
300
301 function _freeresult($result) {
302 /* stub */
303 return false;
304 }
305
306 function _dberror($result) {
307 /* stub */
308 return 'unknown error';
309 }
310
311 abstract function _maxdatetime();
312 abstract function _fromunixtime($ts);
313
314 function garbageCollect() {
315 /* Ignore 99% of requests */
316 if ( !mt_rand( 0, 100 ) ) {
317 $nowtime = time();
318 /* Avoid repeating the delete within a few seconds */
319 if ( $nowtime > ($this->lastexpireall + 1) ) {
320 $this->lastexpireall = $nowtime;
321 $this->expireall();
322 }
323 }
324 }
325
326 function expireall() {
327 /* Remove any items that have expired */
328 $now = $this->_fromunixtime( time() );
329 $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" );
330 }
331
332 function deleteall() {
333 /* Clear *all* items from cache table */
334 $this->_query( "DELETE FROM $0" );
335 }
336
337 /**
338 * Serialize an object and, if possible, compress the representation.
339 * On typical message and page data, this can provide a 3X decrease
340 * in storage requirements.
341 *
342 * @param mixed $data
343 * @return string
344 */
345 function _serialize( &$data ) {
346 $serial = serialize( $data );
347 if( function_exists( 'gzdeflate' ) ) {
348 return gzdeflate( $serial );
349 } else {
350 return $serial;
351 }
352 }
353
354 /**
355 * Unserialize and, if necessary, decompress an object.
356 * @param string $serial
357 * @return mixed
358 */
359 function _unserialize( $serial ) {
360 if( function_exists( 'gzinflate' ) ) {
361 $decomp = @gzinflate( $serial );
362 if( false !== $decomp ) {
363 $serial = $decomp;
364 }
365 }
366 $ret = unserialize( $serial );
367 return $ret;
368 }
369 }
370
371 /**
372 * @todo document
373 * @package MediaWiki
374 */
375 class MediaWikiBagOStuff extends SqlBagOStuff {
376 var $tableInitialised = false;
377
378 function _doquery($sql) {
379 $dbw =& wfGetDB( DB_MASTER );
380 return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery');
381 }
382 function _doinsert($t, $v) {
383 $dbw =& wfGetDB( DB_MASTER );
384 return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert');
385 }
386 function _fetchobject($result) {
387 $dbw =& wfGetDB( DB_MASTER );
388 return $dbw->fetchObject($result);
389 }
390 function _freeresult($result) {
391 $dbw =& wfGetDB( DB_MASTER );
392 return $dbw->freeResult($result);
393 }
394 function _dberror($result) {
395 $dbw =& wfGetDB( DB_MASTER );
396 return $dbw->lastError();
397 }
398 function _maxdatetime() {
399 $dbw =& wfGetDB(DB_MASTER);
400 return $dbw->timestamp('9999-12-31 12:59:59');
401 }
402 function _fromunixtime($ts) {
403 $dbw =& wfGetDB(DB_MASTER);
404 return $dbw->timestamp($ts);
405 }
406 function _strencode($s) {
407 $dbw =& wfGetDB( DB_MASTER );
408 return $dbw->strencode($s);
409 }
410 function _blobencode($s) {
411 $dbw =& wfGetDB( DB_MASTER );
412 return $dbw->encodeBlob($s);
413 }
414 function _blobdecode($s) {
415 $dbw =& wfGetDB( DB_MASTER );
416 return $dbw->decodeBlob($s);
417 }
418 function getTableName() {
419 if ( !$this->tableInitialised ) {
420 $dbw =& wfGetDB( DB_MASTER );
421 /* This is actually a hack, we should be able
422 to use Language classes here... or not */
423 if (!$dbw)
424 throw new MWException("Could not connect to database");
425 $this->table = $dbw->tableName( $this->table );
426 $this->tableInitialised = true;
427 }
428 return $this->table;
429 }
430 }
431
432 /**
433 * This is a wrapper for Turck MMCache's shared memory functions.
434 *
435 * You can store objects with mmcache_put() and mmcache_get(), but Turck seems
436 * to use a weird custom serializer that randomly segfaults. So we wrap calls
437 * with serialize()/unserialize().
438 *
439 * The thing I noticed about the Turck serialized data was that unlike ordinary
440 * serialize(), it contained the names of methods, and judging by the amount of
441 * binary data, perhaps even the bytecode of the methods themselves. It may be
442 * that Turck's serializer is faster, so a possible future extension would be
443 * to use it for arrays but not for objects.
444 *
445 * @package MediaWiki
446 */
447 class TurckBagOStuff extends BagOStuff {
448 function get($key) {
449 $val = mmcache_get( $key );
450 if ( is_string( $val ) ) {
451 $val = unserialize( $val );
452 }
453 return $val;
454 }
455
456 function set($key, $value, $exptime=0) {
457 mmcache_put( $key, serialize( $value ), $exptime );
458 return true;
459 }
460
461 function delete($key, $time=0) {
462 mmcache_rm( $key );
463 return true;
464 }
465
466 function lock($key, $waitTimeout = 0 ) {
467 mmcache_lock( $key );
468 return true;
469 }
470
471 function unlock($key) {
472 mmcache_unlock( $key );
473 return true;
474 }
475 }
476
477 /**
478 * This is a wrapper for APC's shared memory functions
479 *
480 * @package MediaWiki
481 */
482
483 class APCBagOStuff extends BagOStuff {
484 function get($key) {
485 $val = apc_fetch($key);
486 return $val;
487 }
488
489 function set($key, $value, $exptime=0) {
490 apc_store($key, $value, $exptime);
491 return true;
492 }
493
494 function delete($key) {
495 apc_delete($key);
496 return true;
497 }
498 }
499
500
501 /**
502 * This is a wrapper for eAccelerator's shared memory functions.
503 *
504 * This is basically identical to the Turck MMCache version,
505 * mostly because eAccelerator is based on Turck MMCache.
506 *
507 * @package MediaWiki
508 */
509 class eAccelBagOStuff extends BagOStuff {
510 function get($key) {
511 $val = eaccelerator_get( $key );
512 if ( is_string( $val ) ) {
513 $val = unserialize( $val );
514 }
515 return $val;
516 }
517
518 function set($key, $value, $exptime=0) {
519 eaccelerator_put( $key, serialize( $value ), $exptime );
520 return true;
521 }
522
523 function delete($key, $time=0) {
524 eaccelerator_rm( $key );
525 return true;
526 }
527
528 function lock($key, $waitTimeout = 0 ) {
529 eaccelerator_lock( $key );
530 return true;
531 }
532
533 function unlock($key) {
534 eaccelerator_unlock( $key );
535 return true;
536 }
537 }
538 ?>