Use compression in the objectcache table if zlib support is available.
[lhc/web/wiklou.git] / includes / ObjectCache.php
index 4ccf30a..7689b94 100644 (file)
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 # http://www.gnu.org/copyleft/gpl.html
-
-# Simple generic object store
-# interface is intended to be more or less compatible with
-# the PHP memcached client.
-#
-# backends for local hash array and SQL table included:
-#  $bag = new HashBagOStuff();
-#  $bag = new MysqlBagOStuff($tablename); # connect to db first
-
-class /* abstract */ BagOStuff {
+/**
+ *
+ * @package MediaWiki
+ */
+/**
+ * Simple generic object store
+ *
+ * interface is intended to be more or less compatible with
+ * the PHP memcached client.
+ *
+ * backends for local hash array and SQL table included:
+ * $bag = new HashBagOStuff();
+ * $bag = new MysqlBagOStuff($tablename); # connect to db first
+ *
+ * @package MediaWiki
+ * @abstract
+ */
+class BagOStuff {
        var $debugmode;
        
        function BagOStuff() {
-               set_debug( false );
+               $this->set_debug( false );
        }
        
        function set_debug($bool) {
@@ -56,6 +65,16 @@ class /* abstract */ BagOStuff {
                return false;
        }
        
+       function lock($key, $timeout = 0) {
+               /* stub */
+               return true;
+       }
+
+       function unlock($key) {
+               /* stub */
+               return true;
+       }
+       
        /* *** Emulated functions *** */
        /* Better performance can likely be got with custom written versions */
        function get_multi($keys) {
@@ -93,27 +112,36 @@ class /* abstract */ BagOStuff {
        }
        
        function incr($key, $value=1) {
+               if ( !$this->lock($key) ) {
+                       return false;
+               }
                $value = intval($value);
                if($value < 0) $value = 0;
+
+               $n = false;
                if( ($n = $this->get($key)) !== false ) {
-                       $this->set($key, $n+$value); // exptime?
-                       return $n+$value;
-               } else {
-                       return false;
+                       $n += $value;
+                       $this->set($key, $n); // exptime?
                }
+               $this->unlock($key);
+               return $n;
        }
        
        function decr($key, $value=1) {
+               if ( !$this->lock($key) ) {
+                       return false;
+               }
                $value = intval($value);
                if($value < 0) $value = 0;
+
+               $m = false;
                if( ($n = $this->get($key)) !== false ) {
                        $m = $n - $value;
                        if($m < 0) $m = 0;
                        $this->set($key, $m); // exptime?
-                       return $m;
-               } else {
-                       return false;
                }
+               $this->unlock($key);
+               return $m;
        }
        
        function _debug($text) {
@@ -123,7 +151,11 @@ class /* abstract */ BagOStuff {
 }
 
 
-/* Functional versions! */
+/**
+ * Functional versions!
+ * @todo document
+ * @package MediaWiki
+ */
 class HashBagOStuff extends BagOStuff {
        /*
           This is a test of the interface, mainly. It stores
@@ -175,9 +207,16 @@ CREATE TABLE objectcache (
   key (exptime)
 );
 */
-class /* abstract */ SqlBagOStuff extends BagOStuff {
+
+/**
+ * @todo document
+ * @abstract
+ * @package MediaWiki
+ */
+class SqlBagOStuff extends BagOStuff {
        var $table;
-       function SqlBagOStuff($tablename = "objectcache") {
+
+       function SqlBagOStuff($tablename = 'objectcache') {
                $this->table = $tablename;
        }
        
@@ -193,9 +232,9 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
                }
                if($row=$this->_fetchobject($res)) {
                        $this->_debug("get: retrieved data; exp time is " . $row->exptime);
-                       return unserialize($row->value);
+                       return $this->_unserialize($row->value);
                } else {
-                       $this->_debug("get: no matching rows");
+                       $this->_debug('get: no matching rows');
                }
                return false;
        }
@@ -213,7 +252,7 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
                $this->delete( $key );
                $this->_query(
                        "INSERT INTO $0 (keyname,value,exptime) VALUES('$1','$2','$exp')",
-                       $key, serialize(&$value));
+                       $key, $this->_serialize($value));
                return true; /* ? */
        }
        
@@ -223,19 +262,23 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
                return true; /* ? */
        }
        
+       function getTableName() {
+               return $this->table;
+       }
+       
        function _query($sql) {
                $reps = func_get_args();
-               $reps[0] = $this->table;
+               $reps[0] = $this->getTableName();
                // ewwww
                for($i=0;$i<count($reps);$i++) {
                        $sql = str_replace(
-                               "$" . $i,
+                               '$' . $i,
                                $this->_strencode($reps[$i]),
                                $sql);
                }
                $res = $this->_doquery($sql);
                if($res == false) {
-                       $this->_debug("query failed: " . $this->_dberror($res));
+                       $this->_debug('query failed: ' . $this->_dberror($res));
                }
                return $res;
        }
@@ -246,11 +289,11 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
        }
        
        function _doquery($sql) {
-               die( "abstract function SqlBagOStuff::_doquery() must be defined" );
+               die( 'abstract function SqlBagOStuff::_doquery() must be defined' );
        }
        
        function _fetchrow($res) {
-               die( "abstract function SqlBagOStuff::_fetchrow() must be defined" );
+               die( 'abstract function SqlBagOStuff::_fetchrow() must be defined' );
        }
        
        function _freeresult($result) {
@@ -260,15 +303,15 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
        
        function _dberror($result) {
                /* stub */
-               return "unknown error";
+               return 'unknown error';
        }
        
        function _maxdatetime() {
-               die( "abstract function SqlBagOStuff::_maxdatetime() must be defined" );
+               die( 'abstract function SqlBagOStuff::_maxdatetime() must be defined' );
        }
        
        function _fromunixtime() {
-               die( "abstract function SqlBagOStuff::_fromunixtime() must be defined" );
+               die( 'abstract function SqlBagOStuff::_fromunixtime() must be defined' );
        }
        
        function expireall() {
@@ -281,30 +324,125 @@ class /* abstract */ SqlBagOStuff extends BagOStuff {
                /* Clear *all* items from cache table */
                $this->_query( "DELETE FROM $0" );
        }
+       
+       /**
+        * Serialize an object and, if possible, compress the representation.
+        * On typical message and page data, this can provide a 3X decrease
+        * in storage requirements.
+        *
+        * @param mixed $data
+        * @return string
+        */
+       function _serialize( &$data ) {
+               $serial = serialize( $data );
+               if( function_exists( 'gzdeflate' ) ) {
+                       return gzdeflate( $serial );
+               } else {
+                       return $serial;
+               }
+       }
+       
+       /**
+        * Unserialize and, if necessary, decompress an object.
+        * @param string $serial
+        * @return mixed
+        */
+       function &_unserialize( $serial ) {
+               if( function_exists( 'gzinflate' ) ) {
+                       $decomp = @gzinflate( $serial );
+                       if( false !== $decomp ) {
+                               $serial = $decomp;
+                       }
+               }
+               return unserialize( $serial );
+       }
 }
 
+/**
+ * @todo document
+ * @package MediaWiki
+ */
 class MediaWikiBagOStuff extends SqlBagOStuff {
+       var $tableInitialised = false;
+
        function _doquery($sql) {
-               return wfQuery($sql, DB_READ, "MediaWikiBagOStuff:_doquery");
+               $dbw =& wfGetDB( DB_MASTER );
+               return $dbw->query($sql, 'MediaWikiBagOStuff:_doquery');
        }
        function _fetchobject($result) {
-               return wfFetchObject($result);
+               $dbw =& wfGetDB( DB_MASTER );
+               return $dbw->fetchObject($result);
        }
        function _freeresult($result) {
-               return wfFreeResult($result);
+               $dbw =& wfGetDB( DB_MASTER );
+               return $dbw->freeResult($result);
        }
        function _dberror($result) {
-               return wfLastError();
+               $dbw =& wfGetDB( DB_MASTER );
+               return $dbw->lastError();
        }
        function _maxdatetime() {
-               return "9999-12-31 12:59:59";
+               return '9999-12-31 12:59:59';
        }
        function _fromunixtime($ts) {
-               return gmdate( "Y-m-d H:i:s", $ts );
+               return gmdate( 'Y-m-d H:i:s', $ts );
        }
        function _strencode($s) {
-               return wfStrEncode($s);
+               $dbw =& wfGetDB( DB_MASTER );
+               return $dbw->strencode($s);
+       }
+       function getTableName() {
+               if ( !$this->tableInitialised ) {
+                       $dbw =& wfGetDB( DB_MASTER );
+                       $this->table = $dbw->tableName( $this->table );
+                       $this->tableInitialised = true;
+               }
+               return $this->table;
        }
 }
 
+/**
+ * This is a wrapper for Turck MMCache's shared memory functions. 
+ * 
+ * You can store objects with mmcache_put() and mmcache_get(), but Turck seems 
+ * to use a weird custom serializer that randomly segfaults. So we wrap calls 
+ * with serialize()/unserialize().
+ * 
+ * The thing I noticed about the Turck serialized data was that unlike ordinary
+ * serialize(), it contained the names of methods, and judging by the amount of 
+ * binary data, perhaps even the bytecode of the methods themselves. It may be 
+ * that Turck's serializer is faster, so a possible future extension would be 
+ * to use it for arrays but not for objects.
+ *
+ * @package MediaWiki
+ */
+class TurckBagOStuff extends BagOStuff {
+       function get($key) {
+               $val = mmcache_get( $key );
+               if ( is_string( $val ) ) {
+                       $val = unserialize( $val );
+               }
+               return $val;
+       }
+
+       function set($key, $value, $exptime=0) {
+               mmcache_put( $key, serialize( $value ), $exptime );
+               return true;
+       }
+       
+       function delete($key, $time=0) {
+               mmcache_rm( $key );
+               return true;
+       }
+
+       function lock($key, $waitTimeout = 0 ) {
+               mmcache_lock( $key );
+               return true;
+       }
+
+       function unlock($key) {
+               mmcache_unlock( $key );
+               return true;
+       }
+}      
 ?>