MediaWiki  REL1_22
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         return $ret;
00046     }
00047 
00057     public function batchFetchFromURLs( array $urls ) {
00058         $batched = $inverseUrlMap = array();
00059         foreach ( $urls as $url ) {
00060             list( $cluster, $id, $itemID ) = $this->parseURL( $url );
00061             $batched[$cluster][$id][] = $itemID;
00062             // false $itemID gets cast to int, but should be ok
00063             // since we do === from the $itemID in $batched
00064             $inverseUrlMap[$cluster][$id][$itemID] = $url;
00065         }
00066         $ret = array();
00067         foreach ( $batched as $cluster => $batchByCluster ) {
00068             $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
00069             foreach ( $res as $id => $blob ) {
00070                 foreach ( $batchByCluster[$id] as $itemID ) {
00071                     $url = $inverseUrlMap[$cluster][$id][$itemID];
00072                     if ( $itemID === false ) {
00073                         $ret[$url] = $blob;
00074                     } else {
00075                         $ret[$url] = $blob->getItem( $itemID );
00076                     }
00077                 }
00078             }
00079         }
00080         return $ret;
00081     }
00082 
00086     public function store( $cluster, $data ) {
00087         $dbw = $this->getMaster( $cluster );
00088         $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
00089         $dbw->insert( $this->getTable( $dbw ),
00090             array( 'blob_id' => $id, 'blob_text' => $data ),
00091             __METHOD__ );
00092         $id = $dbw->insertId();
00093         if ( !$id ) {
00094             throw new MWException( __METHOD__ . ': no insert ID' );
00095         }
00096         if ( $dbw->getFlag( DBO_TRX ) ) {
00097             $dbw->commit( __METHOD__ );
00098         }
00099         return "DB://$cluster/$id";
00100     }
00101 
00108     function &getLoadBalancer( $cluster ) {
00109         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00110 
00111         return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
00112     }
00113 
00120     function &getSlave( $cluster ) {
00121         global $wgDefaultExternalStore;
00122 
00123         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00124         $lb =& $this->getLoadBalancer( $cluster );
00125 
00126         if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
00127             wfDebug( "read only external store" );
00128             $lb->allowLagged( true );
00129         } else {
00130             wfDebug( "writable external store" );
00131         }
00132 
00133         return $lb->getConnection( DB_SLAVE, array(), $wiki );
00134     }
00135 
00142     function &getMaster( $cluster ) {
00143         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00144         $lb =& $this->getLoadBalancer( $cluster );
00145         return $lb->getConnection( DB_MASTER, array(), $wiki );
00146     }
00147 
00154     function getTable( &$db ) {
00155         $table = $db->getLBInfo( 'blobs table' );
00156         if ( is_null( $table ) ) {
00157             $table = 'blobs';
00158         }
00159         return $table;
00160     }
00161 
00172     function &fetchBlob( $cluster, $id, $itemID ) {
00179         static $externalBlobCache = array();
00180 
00181         $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
00182         if ( isset( $externalBlobCache[$cacheID] ) ) {
00183             wfDebugLog( 'ExternalStoreDB-cache',
00184                 "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
00185             return $externalBlobCache[$cacheID];
00186         }
00187 
00188         wfDebugLog( 'ExternalStoreDB-cache',
00189             "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
00190 
00191         $dbr =& $this->getSlave( $cluster );
00192         $ret = $dbr->selectField( $this->getTable( $dbr ),
00193             'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00194         if ( $ret === false ) {
00195             wfDebugLog( 'ExternalStoreDB',
00196                 "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
00197             // Try the master
00198             $dbw =& $this->getMaster( $cluster );
00199             $ret = $dbw->selectField( $this->getTable( $dbw ),
00200                 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00201             if ( $ret === false ) {
00202                 wfDebugLog( 'ExternalStoreDB',
00203                     "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
00204             }
00205         }
00206         if ( $itemID !== false && $ret !== false ) {
00207             // Unserialise object; caller extracts item
00208             $ret = unserialize( $ret );
00209         }
00210 
00211         $externalBlobCache = array( $cacheID => &$ret );
00212         return $ret;
00213     }
00214 
00222     function batchFetchBlobs( $cluster, array $ids ) {
00223         $dbr = $this->getSlave( $cluster );
00224         $res = $dbr->select( $this->getTable( $dbr ),
00225             array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
00226         $ret = array();
00227         if ( $res !== false ) {
00228             $this->mergeBatchResult( $ret, $ids, $res );
00229         }
00230         if ( $ids ) {
00231             wfDebugLog( __CLASS__, __METHOD__ .
00232                 " master fallback on '$cluster' for: " .
00233                 implode( ',', array_keys( $ids ) ) . "\n" );
00234             // Try the master
00235             $dbw = $this->getMaster( $cluster );
00236             $res = $dbw->select( $this->getTable( $dbr ),
00237                 array( 'blob_id', 'blob_text' ),
00238                 array( 'blob_id' => array_keys( $ids ) ),
00239                 __METHOD__ );
00240             if ( $res === false ) {
00241                 wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'\n" );
00242             } else {
00243                 $this->mergeBatchResult( $ret, $ids, $res );
00244             }
00245         }
00246         if ( $ids ) {
00247             wfDebugLog( __CLASS__, __METHOD__ .
00248                 " master on '$cluster' failed locating items: " .
00249                 implode( ',', array_keys( $ids ) ) . "\n" );
00250         }
00251         return $ret;
00252     }
00253 
00260     private function mergeBatchResult( array &$ret, array &$ids, $res ) {
00261         foreach ( $res as $row ) {
00262             $id = $row->blob_id;
00263             $itemIDs = $ids[$id];
00264             unset( $ids[$id] ); // to track if everything is found
00265             if ( count( $itemIDs ) === 1 && reset( $itemIDs ) === false ) {
00266                 // single result stored per blob
00267                 $ret[$id] = $row->blob_text;
00268             } else {
00269                 // multi result stored per blob
00270                 $ret[$id] = unserialize( $row->blob_text );
00271             }
00272         }
00273     }
00274 
00275     protected function parseURL( $url ) {
00276         $path = explode( '/', $url );
00277         return array(
00278             $path[2], // cluster
00279             $path[3], // id
00280             isset( $path[4] ) ? $path[4] : false // itemID
00281         );
00282     }
00283 }