Merge "Clarify userrights-conflict"
[lhc/web/wiklou.git] / includes / externalstore / ExternalStoreDB.php
index 196e7f2..8042430 100644 (file)
  */
 class ExternalStoreDB extends ExternalStoreMedium {
        /**
-        * The URL returned is of the form of the form DB://cluster/id
+        * The provided URL is in the form of DB://cluster/id
         * or DB://cluster/id/itemid for concatened storage.
         *
         * @see ExternalStoreMedium::fetchFromURL()
         */
        public function fetchFromURL( $url ) {
-               $path = explode( '/', $url );
-               $cluster = $path[2];
-               $id = $path[3];
-               if ( isset( $path[4] ) ) {
-                       $itemID = $path[4];
-               } else {
-                       $itemID = false;
-               }
-
+               list( $cluster, $id, $itemID ) = $this->parseURL( $url );
                $ret =& $this->fetchBlob( $cluster, $id, $itemID );
 
                if ( $itemID !== false && $ret !== false ) {
@@ -53,6 +45,41 @@ class ExternalStoreDB extends ExternalStoreMedium {
                return $ret;
        }
 
+       /**
+        * Fetch data from given external store URLs.
+        * The provided URLs are in the form of DB://cluster/id
+        * or DB://cluster/id/itemid for concatened storage.
+        *
+        * @param array $urls An array of external store URLs
+        * @return array A map from url to stored content. Failed results
+        *     are not represented.
+        */
+       public function batchFetchFromURLs( array $urls ) {
+               $batched = $inverseUrlMap = array();
+               foreach ( $urls as $url ) {
+                       list( $cluster, $id, $itemID ) = $this->parseURL( $url );
+                       $batched[$cluster][$id][] = $itemID;
+                       // false $itemID gets cast to int, but should be ok
+                       // since we do === from the $itemID in $batched
+                       $inverseUrlMap[$cluster][$id][$itemID] = $url;
+               }
+               $ret = array();
+               foreach ( $batched as $cluster => $batchByCluster ) {
+                       $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
+                       foreach ( $res as $id => $blob ) {
+                               foreach ( $batchByCluster[$id] as $itemID ) {
+                                       $url = $inverseUrlMap[$cluster][$id][$itemID];
+                                       if ( $itemID === false ) {
+                                               $ret[$url] = $blob;
+                                       } else {
+                                               $ret[$url] = $blob->getItem( $itemID );
+                                       }
+                               }
+                       }
+               }
+               return $ret;
+       }
+
        /**
         * @see ExternalStoreMedium::store()
         */
@@ -64,7 +91,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                        __METHOD__ );
                $id = $dbw->insertId();
                if ( !$id ) {
-                       throw new MWException( __METHOD__.': no insert ID' );
+                       throw new MWException( __METHOD__ . ': no insert ID' );
                }
                if ( $dbw->getFlag( DBO_TRX ) ) {
                        $dbw->commit( __METHOD__ );
@@ -152,7 +179,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                static $externalBlobCache = array();
 
                $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
-               if( isset( $externalBlobCache[$cacheID] ) ) {
+               if ( isset( $externalBlobCache[$cacheID] ) ) {
                        wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
                        return $externalBlobCache[$cacheID];
                }
@@ -166,11 +193,11 @@ class ExternalStoreDB extends ExternalStoreMedium {
                        // Try the master
                        $dbw =& $this->getMaster( $cluster );
                        $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
-                       if( $ret === false) {
+                       if ( $ret === false ) {
                                wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
                        }
                }
-               if( $itemID !== false && $ret !== false ) {
+               if ( $itemID !== false && $ret !== false ) {
                        // Unserialise object; caller extracts item
                        $ret = unserialize( $ret );
                }
@@ -178,4 +205,65 @@ class ExternalStoreDB extends ExternalStoreMedium {
                $externalBlobCache = array( $cacheID => &$ret );
                return $ret;
        }
+
+       /**
+        * Fetch multiple blob items out of the database
+        *
+        * @param string $cluster A cluster name valid for use with LBFactory
+        * @param array $ids A map from the blob_id's to look for to the requested itemIDs in the blobs
+        * @return array A map from the blob_id's requested to their content.  Unlocated ids are not represented
+        */
+       function batchFetchBlobs( $cluster, array $ids ) {
+               $dbr = $this->getSlave( $cluster );
+               $res = $dbr->select( $this->getTable( $dbr ), array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
+               $ret = array();
+               if ( $res !== false ) {
+                       $this->mergeBatchResult( $ret, $ids, $res );
+               }
+               if ( $ids ) {
+                       wfDebugLog( __CLASS__, __METHOD__ . " master fallback on '$cluster' for: " . implode( ',', array_keys( $ids ) ) . "\n" );
+                       // Try the master
+                       $dbw = $this->getMaster( $cluster );
+                       $res = $dbw->select( $this->getTable( $dbr ), array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
+                       if ( $res === false ) {
+                               wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'\n" );
+                       } else {
+                               $this->mergeBatchResult( $ret, $ids, $res );
+                       }
+               }
+               if ( $ids ) {
+                       wfDebugLog( __CLASS__, __METHOD__ . " master on '$cluster' failed locating items: " . implode( ',', array_keys( $ids ) ) . "\n" );
+               }
+               return $ret;
+       }
+
+       /**
+        * Helper function for self::batchFetchBlobs for merging master/slave results
+        * @param array &$ret Current self::batchFetchBlobs return value
+        * @param array &$ids Map from blob_id to requested itemIDs
+        * @param mixed $res DB result from DatabaseBase::select
+        */
+       private function mergeBatchResult( array &$ret, array &$ids, $res ) {
+               foreach ( $res as $row ) {
+                       $id = $row->blob_id;
+                       $itemIDs = $ids[$id];
+                       unset( $ids[$id] ); // to track if everything is found
+                       if ( count( $itemIDs ) === 1 && reset( $itemIDs ) === false ) {
+                               // single result stored per blob
+                               $ret[$id] = $row->blob_text;
+                       } else {
+                               // multi result stored per blob
+                               $ret[$id] = unserialize( $row->blob_text );
+                       }
+               }
+       }
+
+       protected function parseURL( $url ) {
+               $path = explode( '/', $url );
+               return array(
+                       $path[2], // cluster
+                       $path[3], // id
+                       isset( $path[4] ) ? $path[4] : false // itemID
+               );
+       }
 }