4 * Class to store objects in the database
8 class SqlBagOStuff
extends BagOStuff
{
20 var $lastExpireAll = 0;
21 var $purgePeriod = 100;
24 * Constructor. Parameters are:
25 * - server: A server info structure in the format required by each
26 * element in $wgDBServers.
28 * - purgePeriod: The average number of object cache requests in between
29 * garbage collection operations, where expired entries
30 * are removed from the database. Or in other words, the
31 * reciprocal of the probability of purging on any given
32 * request. If this is set to zero, purging will never be
35 * @param $params array
37 public function __construct( $params ) {
38 if ( isset( $params['server'] ) ) {
39 $this->serverInfo
= $params['server'];
40 $this->serverInfo
['load'] = 1;
42 if ( isset( $params['purgePeriod'] ) ) {
43 $this->purgePeriod
= intval( $params['purgePeriod'] );
48 * @return DatabaseBase
50 protected function getDB() {
51 if ( !isset( $this->db
) ) {
52 # If server connection info was given, use that
53 if ( $this->serverInfo
) {
54 $this->lb
= new LoadBalancer( array(
55 'servers' => array( $this->serverInfo
) ) );
56 $this->db
= $this->lb
->getConnection( DB_MASTER
);
57 $this->db
->clearFlag( DBO_TRX
);
59 # We must keep a separate connection to MySQL in order to avoid deadlocks
60 # However, SQLite has an opposite behaviour.
61 # @todo Investigate behaviour for other databases
62 if ( wfGetDB( DB_MASTER
)->getType() == 'sqlite' ) {
63 $this->db
= wfGetDB( DB_MASTER
);
65 $this->lb
= wfGetLBFactory()->newMainLB();
66 $this->db
= $this->lb
->getConnection( DB_MASTER
);
67 $this->db
->clearFlag( DBO_TRX
);
75 public function get( $key ) {
76 # expire old entries if any
77 $this->garbageCollect();
79 $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
80 array( 'keyname' => $key ), __METHOD__
);
83 $this->debug( 'get: no matching rows' );
87 $this->debug( "get: retrieved data; expiry time is " . $row->exptime
);
89 if ( $this->isExpired( $row->exptime
) ) {
90 $this->debug( "get: key has expired, deleting" );
93 # Put the expiry time in the WHERE condition to avoid deleting a
94 # newly-inserted value
95 $db->delete( 'objectcache',
98 'exptime' => $row->exptime
101 } catch ( DBQueryError
$e ) {
102 $this->handleWriteError( $e );
108 return $this->unserialize( $db->decodeBlob( $row->value
) );
111 public function set( $key, $value, $exptime = 0 ) {
112 $db = $this->getDB();
113 $exptime = intval( $exptime );
115 if ( $exptime < 0 ) {
119 if ( $exptime == 0 ) {
120 $encExpiry = $this->getMaxDateTime();
122 if ( $exptime < 3.16e8
) { # ~10 years
126 $encExpiry = $db->timestamp( $exptime );
130 // (bug 24425) use a replace if the db supports it instead of
131 // delete/insert to avoid clashes with conflicting keynames
132 $db->replace( 'objectcache', array( 'keyname' ),
135 'value' => $db->encodeBlob( $this->serialize( $value ) ),
136 'exptime' => $encExpiry
139 } catch ( DBQueryError
$e ) {
140 $this->handleWriteError( $e );
148 public function delete( $key, $time = 0 ) {
149 $db = $this->getDB();
153 $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__
);
155 } catch ( DBQueryError
$e ) {
156 $this->handleWriteError( $e );
164 public function incr( $key, $step = 1 ) {
165 $db = $this->getDB();
166 $step = intval( $step );
170 $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
171 array( 'keyname' => $key ), __METHOD__
, array( 'FOR UPDATE' ) );
172 if ( $row === false ) {
178 $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__
);
179 if ( $this->isExpired( $row->exptime
) ) {
180 // Expired, do not reinsert
186 $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value
) ) );
187 $newValue = $oldValue +
$step;
188 $db->insert( 'objectcache',
191 'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
192 'exptime' => $row->exptime
193 ), __METHOD__
, 'IGNORE' );
195 if ( $db->affectedRows() == 0 ) {
196 // Race condition. See bug 28611
200 } catch ( DBQueryError
$e ) {
201 $this->handleWriteError( $e );
209 public function keys() {
210 $db = $this->getDB();
211 $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__
);
214 foreach ( $res as $row ) {
215 $result[] = $row->keyname
;
221 protected function isExpired( $exptime ) {
222 return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX
, $exptime ) < time();
225 protected function getMaxDateTime() {
226 if ( time() > 0x7fffffff ) {
227 return $this->getDB()->timestamp( 1 << 62 );
229 return $this->getDB()->timestamp( 0x7fffffff );
233 protected function garbageCollect() {
234 if ( !$this->purgePeriod
) {
238 // Only purge on one in every $this->purgePeriod requests.
239 if ( $this->purgePeriod
!== 1 && mt_rand( 0, $this->purgePeriod
- 1 ) ) {
243 // Avoid repeating the delete within a few seconds
244 if ( $now > ( $this->lastExpireAll +
1 ) ) {
245 $this->lastExpireAll
= $now;
250 public function expireAll() {
251 $db = $this->getDB();
252 $now = $db->timestamp();
256 $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__
);
258 } catch ( DBQueryError
$e ) {
259 $this->handleWriteError( $e );
263 public function deleteAll() {
264 $db = $this->getDB();
268 $db->delete( 'objectcache', '*', __METHOD__
);
270 } catch ( DBQueryError
$e ) {
271 $this->handleWriteError( $e );
276 * Serialize an object and, if possible, compress the representation.
277 * On typical message and page data, this can provide a 3X decrease
278 * in storage requirements.
283 protected function serialize( &$data ) {
284 $serial = serialize( $data );
286 if ( function_exists( 'gzdeflate' ) ) {
287 return gzdeflate( $serial );
294 * Unserialize and, if necessary, decompress an object.
295 * @param $serial string
298 protected function unserialize( $serial ) {
299 if ( function_exists( 'gzinflate' ) ) {
300 $decomp = @gzinflate
( $serial );
302 if ( false !== $decomp ) {
307 $ret = unserialize( $serial );
313 * Handle a DBQueryError which occurred during a write operation.
314 * Ignore errors which are due to a read-only database, rethrow others.
316 protected function handleWriteError( $exception ) {
317 $db = $this->getDB();
319 if ( !$db->wasReadOnlyError() ) {
325 } catch ( DBQueryError
$e ) {
328 wfDebug( __METHOD__
. ": ignoring query error\n" );
329 $db->ignoreErrors( false );
334 * Backwards compatibility alias
336 class MediaWikiBagOStuff
extends SqlBagOStuff
{ }