00001 <?php
00009 class BacklinkCache {
00010 var $partitionCache = array();
00011 var $fullResultCache = array();
00012 var $title;
00013 var $db;
00014
00015 const CACHE_EXPIRY = 3600;
00016
00020 function __construct( $title ) {
00021 $this->title = $title;
00022 }
00023
00027 function clear() {
00028 $this->partitionCache = array();
00029 $this->fullResultCache = array();
00030 unset( $this->db );
00031 }
00032
00036 public function setDB( $db ) {
00037 $this->db = $db;
00038 }
00039
00040 protected function getDB() {
00041 if ( !isset( $this->db ) ) {
00042 $this->db = wfGetDB( DB_SLAVE );
00043 }
00044 return $this->db;
00045 }
00046
00054 public function getLinks( $table, $startId = false, $endId = false ) {
00055 wfProfileIn( __METHOD__ );
00056
00057 $fromField = $this->getPrefix( $table ) . '_from';
00058
00059 if ( $startId || $endId ) {
00060
00061 wfDebug( __METHOD__ . ": from DB (uncacheable range)\n" );
00062 $conds = $this->getConditions( $table );
00063
00064
00065 if ( $startId ) {
00066 $conds[] = "$fromField >= " . intval( $startId );
00067 }
00068 if ( $endId ) {
00069 $conds[] = "$fromField <= " . intval( $endId );
00070 }
00071 $res = $this->getDB()->select(
00072 array( $table, 'page' ),
00073 array( 'page_namespace', 'page_title', 'page_id' ),
00074 $conds,
00075 __METHOD__,
00076 array(
00077 'STRAIGHT_JOIN',
00078 'ORDER BY' => $fromField
00079 ) );
00080 $ta = TitleArray::newFromResult( $res );
00081 wfProfileOut( __METHOD__ );
00082 return $ta;
00083 }
00084
00085 if ( !isset( $this->fullResultCache[$table] ) ) {
00086 wfDebug( __METHOD__ . ": from DB\n" );
00087 $res = $this->getDB()->select(
00088 array( $table, 'page' ),
00089 array( 'page_namespace', 'page_title', 'page_id' ),
00090 $this->getConditions( $table ),
00091 __METHOD__,
00092 array(
00093 'STRAIGHT_JOIN',
00094 'ORDER BY' => $fromField,
00095 ) );
00096 $this->fullResultCache[$table] = $res;
00097 }
00098 $ta = TitleArray::newFromResult( $this->fullResultCache[$table] );
00099 wfProfileOut( __METHOD__ );
00100 return $ta;
00101 }
00102
00106 protected function getPrefix( $table ) {
00107 static $prefixes = array(
00108 'pagelinks' => 'pl',
00109 'imagelinks' => 'il',
00110 'categorylinks' => 'cl',
00111 'templatelinks' => 'tl',
00112 'redirect' => 'rd',
00113 );
00114
00115 if ( isset( $prefixes[$table] ) ) {
00116 return $prefixes[$table];
00117 } else {
00118 throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
00119 }
00120 }
00121
00125 protected function getConditions( $table ) {
00126 $prefix = $this->getPrefix( $table );
00127
00128 switch ( $table ) {
00129 case 'pagelinks':
00130 case 'templatelinks':
00131 case 'redirect':
00132 $conds = array(
00133 "{$prefix}_namespace" => $this->title->getNamespace(),
00134 "{$prefix}_title" => $this->title->getDBkey(),
00135 "page_id={$prefix}_from"
00136 );
00137 break;
00138 case 'imagelinks':
00139 $conds = array(
00140 'il_to' => $this->title->getDBkey(),
00141 'page_id=il_from'
00142 );
00143 break;
00144 case 'categorylinks':
00145 $conds = array(
00146 'cl_to' => $this->title->getDBkey(),
00147 'page_id=cl_from',
00148 );
00149 break;
00150 default:
00151 throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
00152 }
00153 return $conds;
00154 }
00155
00159 public function getNumLinks( $table ) {
00160 if ( isset( $this->fullResultCache[$table] ) ) {
00161 return $this->fullResultCache[$table]->numRows();
00162 }
00163
00164 if ( isset( $this->partitionCache[$table] ) ) {
00165 $entry = reset( $this->partitionCache[$table] );
00166 return $entry['numRows'];
00167 }
00168
00169 $titleArray = $this->getLinks( $table );
00170 return $titleArray->count();
00171 }
00172
00182 public function partition( $table, $batchSize ) {
00183
00184 if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
00185 wfDebug( __METHOD__ . ": got from partition cache\n" );
00186 return $this->partitionCache[$table][$batchSize]['batches'];
00187 }
00188
00189 $this->partitionCache[$table][$batchSize] = false;
00190 $cacheEntry =& $this->partitionCache[$table][$batchSize];
00191
00192
00193 if ( isset( $this->fullResultCache[$table] ) ) {
00194 $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
00195 wfDebug( __METHOD__ . ": got from full result cache\n" );
00196 return $cacheEntry['batches'];
00197 }
00198
00199
00200 global $wgMemc;
00201 $memcKey = wfMemcKey(
00202 'backlinks',
00203 md5( $this->title->getPrefixedDBkey() ),
00204 $table,
00205 $batchSize
00206 );
00207 $memcValue = $wgMemc->get( $memcKey );
00208
00209 if ( is_array( $memcValue ) ) {
00210 $cacheEntry = $memcValue;
00211 wfDebug( __METHOD__ . ": got from memcached $memcKey\n" );
00212 return $cacheEntry['batches'];
00213 }
00214
00215 $this->getLinks( $table );
00216 $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
00217
00218 $wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
00219 wfDebug( __METHOD__ . ": got from database\n" );
00220 return $cacheEntry['batches'];
00221 }
00222
00226 protected function partitionResult( $res, $batchSize ) {
00227 $batches = array();
00228 $numRows = $res->numRows();
00229 $numBatches = ceil( $numRows / $batchSize );
00230
00231 for ( $i = 0; $i < $numBatches; $i++ ) {
00232 if ( $i == 0 ) {
00233 $start = false;
00234 } else {
00235 $rowNum = intval( $numRows * $i / $numBatches );
00236 $res->seek( $rowNum );
00237 $row = $res->fetchObject();
00238 $start = $row->page_id;
00239 }
00240
00241 if ( $i == $numBatches - 1 ) {
00242 $end = false;
00243 } else {
00244 $rowNum = intval( $numRows * ( $i + 1 ) / $numBatches );
00245 $res->seek( $rowNum );
00246 $row = $res->fetchObject();
00247 $end = $row->page_id - 1;
00248 }
00249
00250 # Sanity check order
00251 if ( $start && $end && $start > $end ) {
00252 throw new MWException( __METHOD__ . ': Internal error: query result out of order' );
00253 }
00254
00255 $batches[] = array( $start, $end );
00256 }
00257 return array( 'numRows' => $numRows, 'batches' => $batches );
00258 }
00259 }