MediaWiki  REL1_23
ExternalStoreDB.php
Go to the documentation of this file.
00001 <?php
00031 class ExternalStoreDB extends ExternalStoreMedium {
00038     public function fetchFromURL( $url ) {
00039         list( $cluster, $id, $itemID ) = $this->parseURL( $url );
00040         $ret = $this->fetchBlob( $cluster, $id, $itemID );
00041 
00042         if ( $itemID !== false && $ret !== false ) {
00043             return $ret->getItem( $itemID );
00044         }
00045 
00046         return $ret;
00047     }
00048 
00058     public function batchFetchFromURLs( array $urls ) {
00059         $batched = $inverseUrlMap = array();
00060         foreach ( $urls as $url ) {
00061             list( $cluster, $id, $itemID ) = $this->parseURL( $url );
00062             $batched[$cluster][$id][] = $itemID;
00063             // false $itemID gets cast to int, but should be ok
00064             // since we do === from the $itemID in $batched
00065             $inverseUrlMap[$cluster][$id][$itemID] = $url;
00066         }
00067         $ret = array();
00068         foreach ( $batched as $cluster => $batchByCluster ) {
00069             $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
00071             foreach ( $res as $id => $blob ) {
00072                 foreach ( $batchByCluster[$id] as $itemID ) {
00073                     $url = $inverseUrlMap[$cluster][$id][$itemID];
00074                     if ( $itemID === false ) {
00075                         $ret[$url] = $blob;
00076                     } else {
00077                         $ret[$url] = $blob->getItem( $itemID );
00078                     }
00079                 }
00080             }
00081         }
00082 
00083         return $ret;
00084     }
00085 
00089     public function store( $cluster, $data ) {
00090         $dbw = $this->getMaster( $cluster );
00091         $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
00092         $dbw->insert( $this->getTable( $dbw ),
00093             array( 'blob_id' => $id, 'blob_text' => $data ),
00094             __METHOD__ );
00095         $id = $dbw->insertId();
00096         if ( !$id ) {
00097             throw new MWException( __METHOD__ . ': no insert ID' );
00098         }
00099         if ( $dbw->getFlag( DBO_TRX ) ) {
00100             $dbw->commit( __METHOD__ );
00101         }
00102 
00103         return "DB://$cluster/$id";
00104     }
00105 
00112     function getLoadBalancer( $cluster ) {
00113         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00114 
00115         return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
00116     }
00117 
00124     function getSlave( $cluster ) {
00125         global $wgDefaultExternalStore;
00126 
00127         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00128         $lb = $this->getLoadBalancer( $cluster );
00129 
00130         if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
00131             wfDebug( "read only external store\n" );
00132             $lb->allowLagged( true );
00133         } else {
00134             wfDebug( "writable external store\n" );
00135         }
00136 
00137         return $lb->getConnection( DB_SLAVE, array(), $wiki );
00138     }
00139 
00146     function getMaster( $cluster ) {
00147         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00148         $lb = $this->getLoadBalancer( $cluster );
00149 
00150         return $lb->getConnection( DB_MASTER, array(), $wiki );
00151     }
00152 
00159     function getTable( $db ) {
00160         $table = $db->getLBInfo( 'blobs table' );
00161         if ( is_null( $table ) ) {
00162             $table = 'blobs';
00163         }
00164 
00165         return $table;
00166     }
00167 
00178     function fetchBlob( $cluster, $id, $itemID ) {
00185         static $externalBlobCache = array();
00186 
00187         $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
00188         if ( isset( $externalBlobCache[$cacheID] ) ) {
00189             wfDebugLog( 'ExternalStoreDB-cache',
00190                 "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
00191 
00192             return $externalBlobCache[$cacheID];
00193         }
00194 
00195         wfDebugLog( 'ExternalStoreDB-cache',
00196             "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
00197 
00198         $dbr = $this->getSlave( $cluster );
00199         $ret = $dbr->selectField( $this->getTable( $dbr ),
00200             'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00201         if ( $ret === false ) {
00202             wfDebugLog( 'ExternalStoreDB',
00203                 "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
00204             // Try the master
00205             $dbw = $this->getMaster( $cluster );
00206             $ret = $dbw->selectField( $this->getTable( $dbw ),
00207                 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00208             if ( $ret === false ) {
00209                 wfDebugLog( 'ExternalStoreDB',
00210                     "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
00211             }
00212         }
00213         if ( $itemID !== false && $ret !== false ) {
00214             // Unserialise object; caller extracts item
00215             $ret = unserialize( $ret );
00216         }
00217 
00218         $externalBlobCache = array( $cacheID => $ret );
00219 
00220         return $ret;
00221     }
00222 
00231     function batchFetchBlobs( $cluster, array $ids ) {
00232         $dbr = $this->getSlave( $cluster );
00233         $res = $dbr->select( $this->getTable( $dbr ),
00234             array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
00235         $ret = array();
00236         if ( $res !== false ) {
00237             $this->mergeBatchResult( $ret, $ids, $res );
00238         }
00239         if ( $ids ) {
00240             wfDebugLog( __CLASS__, __METHOD__ .
00241                 " master fallback on '$cluster' for: " .
00242                 implode( ',', array_keys( $ids ) ) );
00243             // Try the master
00244             $dbw = $this->getMaster( $cluster );
00245             $res = $dbw->select( $this->getTable( $dbr ),
00246                 array( 'blob_id', 'blob_text' ),
00247                 array( 'blob_id' => array_keys( $ids ) ),
00248                 __METHOD__ );
00249             if ( $res === false ) {
00250                 wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
00251             } else {
00252                 $this->mergeBatchResult( $ret, $ids, $res );
00253             }
00254         }
00255         if ( $ids ) {
00256             wfDebugLog( __CLASS__, __METHOD__ .
00257                 " master on '$cluster' failed locating items: " .
00258                 implode( ',', array_keys( $ids ) ) );
00259         }
00260 
00261         return $ret;
00262     }
00263 
00270     private function mergeBatchResult( array &$ret, array &$ids, $res ) {
00271         foreach ( $res as $row ) {
00272             $id = $row->blob_id;
00273             $itemIDs = $ids[$id];
00274             unset( $ids[$id] ); // to track if everything is found
00275             if ( count( $itemIDs ) === 1 && reset( $itemIDs ) === false ) {
00276                 // single result stored per blob
00277                 $ret[$id] = $row->blob_text;
00278             } else {
00279                 // multi result stored per blob
00280                 $ret[$id] = unserialize( $row->blob_text );
00281             }
00282         }
00283     }
00284 
00285     protected function parseURL( $url ) {
00286         $path = explode( '/', $url );
00287 
00288         return array(
00289             $path[2], // cluster
00290             $path[3], // id
00291             isset( $path[4] ) ? $path[4] : false // itemID
00292         );
00293     }
00294 }