Merge "Document that IContextSource::getTitle can return null"
[lhc/web/wiklou.git] / includes / objectcache / SqlBagOStuff.php
index b6bf3f8..e6a8c45 100644 (file)
@@ -70,7 +70,7 @@ class SqlBagOStuff extends BagOStuff {
         *                  distributed across all tables by key hash. This is for
         *                  MySQL bugs 61735 and 61736.
         *
-        * @param $params array
+        * @param array $params
         */
        public function __construct( $params ) {
                if ( isset( $params['servers'] ) ) {
@@ -101,7 +101,7 @@ class SqlBagOStuff extends BagOStuff {
        /**
         * Get a connection to the specified database
         *
-        * @param $serverIndex integer
+        * @param int $serverIndex
         * @return DatabaseBase
         */
        protected function getDB( $serverIndex ) {
@@ -114,8 +114,8 @@ class SqlBagOStuff extends BagOStuff {
 
                        # Don't keep timing out trying to connect for each call if the DB is down
                        if ( isset( $this->connFailureErrors[$serverIndex] )
-                               && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 )
-                       {
+                               && ( time() - $this->connFailureTimes[$serverIndex] ) < 60
+                       {
                                throw $this->connFailureErrors[$serverIndex];
                        }
 
@@ -155,8 +155,8 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Get the server index and table name for a given key
-        * @param $key string
-        * @return Array: server index and table name
+        * @param string $key
+        * @return array Server index and table name
         */
        protected function getTableByKey( $key ) {
                if ( $this->shards > 1 ) {
@@ -178,7 +178,7 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Get the table name for a given shard index
-        * @param $index int
+        * @param int $index
         * @return string
         */
        protected function getTableNameByShard( $index ) {
@@ -192,8 +192,8 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $key string
-        * @param $casToken[optional] mixed
+        * @param string $key
+        * @param mixed $casToken [optional]
         * @return mixed
         */
        public function get( $key, &$casToken = null ) {
@@ -206,8 +206,8 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $keys array
-        * @return Array
+        * @param array $keys
+        * @return array
         */
        public function getMulti( array $keys ) {
                $values = array(); // array of (key => value)
@@ -248,13 +248,13 @@ class SqlBagOStuff extends BagOStuff {
                                        $db = $this->getDB( $row->serverIndex );
                                        if ( $this->isExpired( $db, $row->exptime ) ) { // MISS
                                                $this->debug( "get: key has expired, deleting" );
-                                               $db->begin( __METHOD__ );
+                                               $db->commit( __METHOD__, 'flush' );
                                                # Put the expiry time in the WHERE condition to avoid deleting a
                                                # newly-inserted value
                                                $db->delete( $row->tableName,
                                                        array( 'keyname' => $key, 'exptime' => $row->exptime ),
                                                        __METHOD__ );
-                                               $db->commit( __METHOD__ );
+                                               $db->commit( __METHOD__, 'flush' );
                                                $values[$key] = false;
                                        } else { // HIT
                                                $values[$key] = $this->unserialize( $db->decodeBlob( $row->value ) );
@@ -272,9 +272,79 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $key string
-        * @param $value mixed
-        * @param $exptime int
+        * @param array $data
+        * @param int $expiry
+        * @return bool
+        */
+       public function setMulti( array $data, $expiry = 0 ) {
+               $keysByTable = array();
+               foreach ( $data as $key => $value ) {
+                       list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+                       $keysByTable[$serverIndex][$tableName][] = $key;
+               }
+
+               $this->garbageCollect(); // expire old entries if any
+
+               $result = true;
+               $exptime = (int)$expiry;
+               foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+                       try {
+                               $db = $this->getDB( $serverIndex );
+                       } catch ( DBError $e ) {
+                               $this->handleWriteError( $e, $serverIndex );
+                               $result = false;
+                               continue;
+                       }
+
+                       if ( $exptime < 0 ) {
+                               $exptime = 0;
+                       }
+
+                       if ( $exptime == 0 ) {
+                               $encExpiry = $this->getMaxDateTime( $db );
+                       } else {
+                               if ( $exptime < 3.16e8 ) { # ~10 years
+                                       $exptime += time();
+                               }
+                               $encExpiry = $db->timestamp( $exptime );
+                       }
+                       foreach ( $serverKeys as $tableName => $tableKeys ) {
+                               $rows = array();
+                               foreach ( $tableKeys as $key ) {
+                                       $rows[] = array(
+                                               'keyname' => $key,
+                                               'value' => $db->encodeBlob( $this->serialize( $data[$key] ) ),
+                                               'exptime' => $encExpiry,
+                                       );
+                               }
+
+                               try {
+                                       $db->commit( __METHOD__, 'flush' );
+                                       $db->replace(
+                                               $tableName,
+                                               array( 'keyname' ),
+                                               $rows,
+                                               __METHOD__
+                                       );
+                                       $db->commit( __METHOD__, 'flush' );
+                               } catch ( DBError $e ) {
+                                       $this->handleWriteError( $e, $serverIndex );
+                                       $result = false;
+                               }
+
+                       }
+
+               }
+
+               return $result;
+       }
+
+
+
+       /**
+        * @param string $key
+        * @param mixed $value
+        * @param int $exptime
         * @return bool
         */
        public function set( $key, $value, $exptime = 0 ) {
@@ -296,7 +366,7 @@ class SqlBagOStuff extends BagOStuff {
 
                                $encExpiry = $db->timestamp( $exptime );
                        }
-                       $db->begin( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                        // (bug 24425) use a replace if the db supports it instead of
                        // delete/insert to avoid clashes with conflicting keynames
                        $db->replace(
@@ -307,7 +377,7 @@ class SqlBagOStuff extends BagOStuff {
                                        'value' => $db->encodeBlob( $this->serialize( $value ) ),
                                        'exptime' => $encExpiry
                                ), __METHOD__ );
-                       $db->commit( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                } catch ( DBError $e ) {
                        $this->handleWriteError( $e, $serverIndex );
                        return false;
@@ -317,10 +387,10 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $casToken mixed
-        * @param $key string
-        * @param $value mixed
-        * @param $exptime int
+        * @param mixed $casToken
+        * @param string $key
+        * @param mixed $value
+        * @param int $exptime
         * @return bool
         */
        public function cas( $casToken, $key, $value, $exptime = 0 ) {
@@ -341,7 +411,7 @@ class SqlBagOStuff extends BagOStuff {
                                }
                                $encExpiry = $db->timestamp( $exptime );
                        }
-                       $db->begin( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                        // (bug 24425) use a replace if the db supports it instead of
                        // delete/insert to avoid clashes with conflicting keynames
                        $db->update(
@@ -357,31 +427,31 @@ class SqlBagOStuff extends BagOStuff {
                                ),
                                __METHOD__
                        );
-                       $db->commit( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                } catch ( DBQueryError $e ) {
                        $this->handleWriteError( $e, $serverIndex );
 
                        return false;
                }
 
-               return (bool) $db->affectedRows();
+               return (bool)$db->affectedRows();
        }
 
        /**
-        * @param $key string
-        * @param $time int
+        * @param string $key
+        * @param int $time
         * @return bool
         */
        public function delete( $key, $time = 0 ) {
                list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
                try {
                        $db = $this->getDB( $serverIndex );
-                       $db->begin( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                        $db->delete(
                                $tableName,
                                array( 'keyname' => $key ),
                                __METHOD__ );
-                       $db->commit( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                } catch ( DBError $e ) {
                        $this->handleWriteError( $e, $serverIndex );
                        return false;
@@ -391,8 +461,8 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $key string
-        * @param $step int
+        * @param string $key
+        * @param int $step
         * @return int|null
         */
        public function incr( $key, $step = 1 ) {
@@ -400,7 +470,7 @@ class SqlBagOStuff extends BagOStuff {
                try {
                        $db = $this->getDB( $serverIndex );
                        $step = intval( $step );
-                       $db->begin( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                        $row = $db->selectRow(
                                $tableName,
                                array( 'value', 'exptime' ),
@@ -409,14 +479,14 @@ class SqlBagOStuff extends BagOStuff {
                                array( 'FOR UPDATE' ) );
                        if ( $row === false ) {
                                // Missing
-                               $db->commit( __METHOD__ );
+                               $db->commit( __METHOD__, 'flush' );
 
                                return null;
                        }
                        $db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ );
                        if ( $this->isExpired( $db, $row->exptime ) ) {
                                // Expired, do not reinsert
-                               $db->commit( __METHOD__ );
+                               $db->commit( __METHOD__, 'flush' );
 
                                return null;
                        }
@@ -434,7 +504,7 @@ class SqlBagOStuff extends BagOStuff {
                                // Race condition. See bug 28611
                                $newValue = null;
                        }
-                       $db->commit( __METHOD__ );
+                       $db->commit( __METHOD__, 'flush' );
                } catch ( DBError $e ) {
                        $this->handleWriteError( $e, $serverIndex );
                        return null;
@@ -444,7 +514,7 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
-        * @param $exptime string
+        * @param string $exptime
         * @return bool
         */
        protected function isExpired( $db, $exptime ) {
@@ -452,6 +522,7 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        /**
+        * @param DatabaseBase $db
         * @return string
         */
        protected function getMaxDateTime( $db ) {
@@ -485,8 +556,8 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Delete objects from the database which expire before a certain date.
-        * @param $timestamp string
-        * @param $progressCallback bool|callback
+        * @param string $timestamp
+        * @param bool|callable $progressCallback
         * @return bool
         */
        public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
@@ -524,7 +595,7 @@ class SqlBagOStuff extends BagOStuff {
                                                        $maxExpTime = $row->exptime;
                                                }
 
-                                               $db->begin( __METHOD__ );
+                                               $db->commit( __METHOD__, 'flush' );
                                                $db->delete(
                                                        $this->getTableNameByShard( $i ),
                                                        array(
@@ -533,7 +604,7 @@ class SqlBagOStuff extends BagOStuff {
                                                                'keyname' => $keys
                                                        ),
                                                        __METHOD__ );
-                                               $db->commit( __METHOD__ );
+                                               $db->commit( __METHOD__, 'flush' );
 
                                                if ( $progressCallback ) {
                                                        if ( intval( $totalSeconds ) === 0 ) {
@@ -566,9 +637,9 @@ class SqlBagOStuff extends BagOStuff {
                        try {
                                $db = $this->getDB( $serverIndex );
                                for ( $i = 0; $i < $this->shards; $i++ ) {
-                                       $db->begin( __METHOD__ );
+                                       $db->commit( __METHOD__, 'flush' );
                                        $db->delete( $this->getTableNameByShard( $i ), '*', __METHOD__ );
-                                       $db->commit( __METHOD__ );
+                                       $db->commit( __METHOD__, 'flush' );
                                }
                        } catch ( DBError $e ) {
                                $this->handleWriteError( $e, $serverIndex );
@@ -583,7 +654,7 @@ class SqlBagOStuff extends BagOStuff {
         * On typical message and page data, this can provide a 3X decrease
         * in storage requirements.
         *
-        * @param $data mixed
+        * @param mixed $data
         * @return string
         */
        protected function serialize( &$data ) {
@@ -598,7 +669,7 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Unserialize and, if necessary, decompress an object.
-        * @param $serial string
+        * @param string $serial
         * @return mixed
         */
        protected function unserialize( $serial ) {
@@ -619,6 +690,9 @@ class SqlBagOStuff extends BagOStuff {
 
        /**
         * Handle a DBError which occurred during a read operation.
+        *
+        * @param DBError $exception
+        * @param int $serverIndex
         */
        protected function handleReadError( DBError $exception, $serverIndex ) {
                if ( $exception instanceof DBConnectionError ) {
@@ -626,14 +700,19 @@ class SqlBagOStuff extends BagOStuff {
                }
                wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
                if ( $exception instanceof DBConnectionError ) {
+                       $this->setLastError( BagOStuff::ERR_UNREACHABLE );
                        wfDebug( __METHOD__ . ": ignoring connection error\n" );
                } else {
+                       $this->setLastError( BagOStuff::ERR_UNEXPECTED );
                        wfDebug( __METHOD__ . ": ignoring query error\n" );
                }
        }
 
        /**
         * Handle a DBQueryError which occurred during a write operation.
+        *
+        * @param DBError $exception
+        * @param int $serverIndex
         */
        protected function handleWriteError( DBError $exception, $serverIndex ) {
                if ( $exception instanceof DBConnectionError ) {
@@ -646,14 +725,19 @@ class SqlBagOStuff extends BagOStuff {
                }
                wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
                if ( $exception instanceof DBConnectionError ) {
+                       $this->setLastError( BagOStuff::ERR_UNREACHABLE );
                        wfDebug( __METHOD__ . ": ignoring connection error\n" );
                } else {
+                       $this->setLastError( BagOStuff::ERR_UNEXPECTED );
                        wfDebug( __METHOD__ . ": ignoring query error\n" );
                }
        }
 
        /**
         * Mark a server down due to a DBConnectionError exception
+        *
+        * @param DBError $exception
+        * @param int $serverIndex
         */
        protected function markServerDown( $exception, $serverIndex ) {
                if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
@@ -677,19 +761,17 @@ class SqlBagOStuff extends BagOStuff {
        public function createTables() {
                for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
                        $db = $this->getDB( $serverIndex );
-                       if ( $db->getType() !== 'mysql'
-                               || version_compare( $db->getServerVersion(), '4.1.0', '<' ) )
-                       {
+                       if ( $db->getType() !== 'mysql' ) {
                                throw new MWException( __METHOD__ . ' is not supported on this DB server' );
                        }
 
                        for ( $i = 0; $i < $this->shards; $i++ ) {
-                               $db->begin( __METHOD__ );
+                               $db->commit( __METHOD__, 'flush' );
                                $db->query(
                                        'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
                                        ' LIKE ' . $db->tableName( 'objectcache' ),
                                        __METHOD__ );
-                               $db->commit( __METHOD__ );
+                               $db->commit( __METHOD__, 'flush' );
                        }
                }
        }