3 * Cache for article titles (prefixed DB keys) and ids linked from one source
11 # These are used in incrementalSetup()
12 define ('LINKCACHE_GOOD', 0);
13 define ('LINKCACHE_BAD', 1);
14 define ('LINKCACHE_IMAGE', 2);
15 define ('LINKCACHE_PAGE', 3);
22 // Increment $mClassVer whenever old serialized versions of this class
23 // becomes incompatible with the new version.
24 /* private */ var $mClassVer = 3;
26 /* private */ var $mPageLinks;
27 /* private */ var $mGoodLinks, $mBadLinks, $mActive;
28 /* private */ var $mImageLinks, $mCategoryLinks;
29 /* private */ var $mPreFilled, $mOldGoodLinks, $mOldBadLinks;
30 /* private */ var $mForUpdate;
32 /* private */ function getKey( $title ) {
34 return $wgDBname.':lc:title:'.$title;
37 function LinkCache() {
38 $this->mActive
= true;
39 $this->mPreFilled
= false;
40 $this->mForUpdate
= false;
41 $this->mPageLinks
= array();
42 $this->mGoodLinks
= array();
43 $this->mBadLinks
= array();
44 $this->mImageLinks
= array();
45 $this->mCategoryLinks
= array();
46 $this->mOldGoodLinks
= array();
47 $this->mOldBadLinks
= array();
51 * General accessor to get/set whether SELECT FOR UPDATE should be used
53 function forUpdate( $update = NULL ) {
54 return wfSetVar( $this->mForUpdate
, $update );
57 function getGoodLinkID( $title ) {
58 if ( array_key_exists( $title, $this->mGoodLinks
) ) {
59 return $this->mGoodLinks
[$title];
65 function isBadLink( $title ) {
66 return array_key_exists( $title, $this->mBadLinks
);
69 function addGoodLinkObj( $id, $title ) {
70 if ( $this->mActive
) {
71 $dbkey = $title->getPrefixedDbKey();
72 $this->mGoodLinks
[$dbkey] = $id;
73 $this->mPageLinks
[$dbkey] = $title;
77 function addBadLinkObj( $title ) {
78 $dbkey = $title->getPrefixedDbKey();
79 if ( $this->mActive
&& ( ! $this->isBadLink( $dbkey ) ) ) {
80 $this->mBadLinks
[$dbkey] = 1;
81 $this->mPageLinks
[$dbkey] = $title;
85 function addImageLink( $title ) {
86 if ( $this->mActive
) { $this->mImageLinks
[$title] = 1; }
89 function addImageLinkObj( $nt ) {
90 if ( $this->mActive
) { $this->mImageLinks
[$nt->getDBkey()] = 1; }
93 function addCategoryLink( $title, $sortkey ) {
94 if ( $this->mActive
) { $this->mCategoryLinks
[$title] = $sortkey; }
97 function addCategoryLinkObj( &$nt, $sortkey ) {
98 $this->addCategoryLink( $nt->getDBkey(), $sortkey );
101 function clearBadLink( $title ) {
102 unset( $this->mBadLinks
[$title] );
103 $this->clearLink( $title );
106 function clearLink( $title ) {
107 global $wgMemc, $wgLinkCacheMemcached;
108 if( $wgLinkCacheMemcached )
109 $wgMemc->delete( $this->getKey( $title ) );
112 function suspend() { $this->mActive
= false; }
113 function resume() { $this->mActive
= true; }
114 function getPageLinks() { return $this->mPageLinks
; }
115 function getGoodLinks() { return $this->mGoodLinks
; }
116 function getBadLinks() { return array_keys( $this->mBadLinks
); }
117 function getImageLinks() { return $this->mImageLinks
; }
118 function getCategoryLinks() { return $this->mCategoryLinks
; }
120 function addLink( $title ) {
121 $nt = Title
::newFromDBkey( $title );
123 return $this->addLinkObj( $nt );
129 function addLinkObj( &$nt ) {
130 global $wgMemc, $wgLinkCacheMemcached, $wgAntiLockFlags;
131 $title = $nt->getPrefixedDBkey();
132 if ( $this->isBadLink( $title ) ) { return 0; }
133 $id = $this->getGoodLinkID( $title );
134 if ( 0 != $id ) { return $id; }
136 $fname = 'LinkCache::addLinkObj';
137 wfProfileIn( $fname );
139 $ns = $nt->getNamespace();
140 $t = $nt->getDBkey();
142 if ( '' == $title ) {
143 wfProfileOut( $fname );
148 if( $wgLinkCacheMemcached )
149 $id = $wgMemc->get( $key = $this->getKey( $title ) );
150 if( ! is_integer( $id ) ) {
151 if ( $this->mForUpdate
) {
152 $db =& wfGetDB( DB_MASTER
);
153 if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK
) ) {
154 $options = array( 'FOR UPDATE' );
157 $db =& wfGetDB( DB_SLAVE
);
161 $id = $db->selectField( 'page', 'page_id', array( 'page_namespace' => $ns, 'page_title' => $t ), $fname, $options );
165 if( $wgLinkCacheMemcached )
166 $wgMemc->add( $key, $id, 3600*24 );
170 $this->addBadLinkObj( $nt );
172 $this->addGoodLinkObj( $id, $nt );
174 wfProfileOut( $fname );
179 * Bulk-check the pagelinks and page arrays for existence info.
180 * @param Title $fromtitle
182 function preFill( &$fromtitle ) {
183 global $wgEnablePersistentLC;
185 $fname = 'LinkCache::preFill';
186 wfProfileIn( $fname );
189 $id = $fromtitle->getArticleID();
193 wfDebug( "$fname - got id 0 for title '" . $fromtitle->getPrefixedDBkey() . "'\n" );
194 wfProfileOut( $fname );
198 if ( $wgEnablePersistentLC ) {
199 if( $this->fillFromLinkscc( $id ) ){
200 wfProfileOut( $fname );
204 if ( $this->mForUpdate
) {
205 $db =& wfGetDB( DB_MASTER
);
206 $options = 'FOR UPDATE';
208 $db =& wfGetDB( DB_SLAVE
);
212 $page = $db->tableName( 'page' );
213 $pagelinks = $db->tableName( 'pagelinks' );
215 $sql = "SELECT page_id,pl_namespace,pl_title
218 ON pl_namespace=page_namespace AND pl_title=page_title
219 WHERE pl_from=$id $options";
220 $res = $db->query( $sql, $fname );
221 while( $s = $db->fetchObject( $res ) ) {
222 $title = Title
::makeTitle( $s->pl_namespace
, $s->pl_title
);
224 $this->addGoodLinkObj( $s->page_id
, $title );
226 $this->addBadLinkObj( $title );
229 $this->mOldPageLinks
= $this->mPageLinks
;
230 $this->mOldBadLinks
= $this->mBadLinks
;
231 $this->mOldGoodLinks
= $this->mGoodLinks
;
232 $this->mPreFilled
= true;
234 if ( $wgEnablePersistentLC ) {
235 $this->saveToLinkscc( $id );
237 wfProfileOut( $fname );
240 function getGoodAdditions() {
241 return array_diff( $this->mGoodLinks
, $this->mOldGoodLinks
);
244 function getBadAdditions() {
245 #wfDebug( "mOldBadLinks: " . implode( ', ', array_keys( $this->mOldBadLinks ) ) . "\n" );
246 #wfDebug( "mBadLinks: " . implode( ', ', array_keys( $this->mBadLinks ) ) . "\n" );
247 return array_values( array_diff( array_keys( $this->mBadLinks
), array_keys( $this->mOldBadLinks
) ) );
250 function getImageAdditions() {
251 return array_diff_assoc( $this->mImageLinks
, $this->mOldImageLinks
);
254 function getGoodDeletions() {
255 return array_diff( $this->mOldGoodLinks
, $this->mGoodLinks
);
258 function getBadDeletions() {
259 return array_values( array_diff( array_keys( $this->mOldBadLinks
), array_keys( $this->mBadLinks
) ));
262 function getImageDeletions() {
263 return array_diff_assoc( $this->mOldImageLinks
, $this->mImageLinks
);
266 function getPageAdditions() {
267 $set = array_diff( array_keys( $this->mPageLinks
), array_keys( $this->mOldPageLinks
) );
269 foreach( $set as $key ) {
270 $out[$key] = $this->mPageLinks
[$key];
275 function getPageDeletions() {
276 $set = array_diff( array_keys( $this->mOldPageLinks
), array_keys( $this->mPageLinks
) );
278 foreach( $set as $key ) {
279 $out[$key] = $this->mOldPageLinks
[$key];
286 * @param $which is one of the LINKCACHE_xxx constants
287 * @param $del,$add are the incremental update arrays which will be filled.
289 * @return Returns whether or not it's worth doing the incremental version.
291 * For example, if [[List of mathematical topics]] was blanked,
292 * it would take a long, long time to do incrementally.
294 function incrementalSetup( $which, &$del, &$add ) {
295 if ( ! $this->mPreFilled
) {
301 $old =& $this->mOldGoodLinks
;
302 $cur =& $this->mGoodLinks
;
303 $del = $this->getGoodDeletions();
304 $add = $this->getGoodAdditions();
307 $old =& $this->mOldBadLinks
;
308 $cur =& $this->mBadLinks
;
309 $del = $this->getBadDeletions();
310 $add = $this->getBadAdditions();
313 $old =& $this->mOldPageLinks
;
314 $cur =& $this->mPageLinks
;
315 $del = $this->getPageDeletions();
316 $add = $this->getPageAdditions();
318 default: # LINKCACHE_IMAGE
326 * Clears cache but leaves old preFill copies alone
329 $this->mPageLinks
= array();
330 $this->mGoodLinks
= array();
331 $this->mBadLinks
= array();
332 $this->mImageLinks
= array();
338 function fillFromLinkscc( $id ){
339 $fname = 'LinkCache::fillFromLinkscc';
342 if ( $this->mForUpdate
) {
343 $db =& wfGetDB( DB_MASTER
);
344 $options = 'FOR UPDATE';
346 $db =& wfGetDB( DB_SLAVE
);
349 $raw = $db->selectField( 'linkscc', 'lcc_cacheobj', array( 'lcc_pageid' => $id ), $fname, $options );
350 if ( $raw === false ) {
355 if( function_exists( 'gzuncompress' ) )
356 $cacheobj = @gzuncompress
( $raw );
358 if($cacheobj == FALSE){
361 $cc = @unserialize
( $cacheobj );
362 if( isset( $cc->mClassVer
) and ($cc->mClassVer
== $this->mClassVer
) ){
363 $this->mOldPageLinks
= $this->mPageLinks
= $cc->mPageLinks
;
364 $this->mOldGoodLinks
= $this->mGoodLinks
= $cc->mGoodLinks
;
365 $this->mOldBadLinks
= $this->mBadLinks
= $cc->mBadLinks
;
366 $this->mPreFilled
= true;
377 function saveToLinkscc( $pid ){
378 global $wgCompressedPersistentLC;
379 if( $wgCompressedPersistentLC and function_exists( 'gzcompress' ) ) {
380 $ser = gzcompress( serialize( $this ), 3 );
382 $ser = serialize( $this );
384 $db =& wfGetDB( DB_MASTER
);
385 $db->replace( 'linkscc', array( 'lcc_pageid' ), array( 'lcc_pageid' => $pid, 'lcc_cacheobj' => $ser ) );
389 * Delete linkscc rows which link to here
390 * @param $title The page linked to
393 function linksccClearLinksTo( $title ){
394 global $wgEnablePersistentLC;
395 if ( $wgEnablePersistentLC ) {
396 $fname = 'LinkCache::linksccClearLinksTo';
397 $pid = intval( $pid );
398 $dbw =& wfGetDB( DB_MASTER
);
399 # Delete linkscc rows which link to here
400 $dbw->deleteJoin( 'linkscc', 'pagelinks', 'lcc_pageid', 'pl_from',
402 'pl_namespace' => $title->getNamespace(),
403 'pl-title' => $title->getDbKey() ),
405 # Delete linkscc row representing this page
406 $dbw->delete( 'linkscc', array( 'lcc_pageid' => $pid ), $fname);
412 * @param $pid is a page id
415 function linksccClearPage( $pid ){
416 global $wgEnablePersistentLC;
417 if ( $wgEnablePersistentLC ) {
418 $pid = intval( $pid );
419 $dbw =& wfGetDB( DB_MASTER
);
420 $dbw->delete( 'linkscc', array( 'lcc_pageid' => $pid ) );
426 * Class representing a list of titles
427 * The execute() method checks them all for existence and adds them to a LinkCache object
429 * @package MediaWikki
434 * 2-d array, first index namespace, second index dbkey, value arbitrary
438 function LinkBatch( $arr = array() ) {
439 foreach( $arr as $item ) {
440 $this->addObj( $item );
444 function addObj( $title ) {
445 $this->add( $title->getNamespace(), $title->getDBkey() );
448 function add( $ns, $dbkey ) {
452 if ( !array_key_exists( $ns, $this->data
) ) {
453 $this->data
[$ns] = array();
456 $this->data
[$ns][$dbkey] = 1;
459 function execute( &$cache ) {
460 $fname = 'LinkBatch::execute';
461 $namespaces = array();
463 if ( !count( $this->data
) ) {
467 wfProfileIn( $fname );
470 // This is very similar to Parser::replaceLinkHolders
471 $dbr = wfGetDB( DB_SLAVE
);
472 $page = $dbr->tableName( 'page' );
473 $sql = "SELECT page_id, page_namespace, page_title FROM $page WHERE "
474 . $this->constructSet( 'page', $dbr );
477 $res = $dbr->query( $sql, $fname );
480 // For each returned entry, add it to the list of good links, and remove it from $remaining
482 $remaining = $this->data
;
483 while ( $row = $dbr->fetchObject( $res ) ) {
484 $title = Title
::makeTitle( $row->page_namespace
, $row->page_title
);
485 $cache->addGoodLinkObj( $row->page_id
, $title );
486 unset( $remaining[$row->page_namespace
][$row->page_title
] );
488 $dbr->freeResult( $res );
490 // The remaining links in $data are bad links, register them as such
491 foreach ( $remaining as $ns => $dbkeys ) {
492 foreach ( $dbkeys as $dbkey => $nothing ) {
493 $title = Title
::makeTitle( $ns, $dbkey );
494 $cache->addBadLinkObj( $title );
498 wfProfileOut( $fname );
502 * Construct a WHERE clause which will match all the given titles.
503 * Give the appropriate table's field name prefix ('page', 'pl', etc).
505 * @param string $prefix
509 function constructSet( $prefix, $db ) {
512 foreach ( $this->data
as $ns => $dbkeys ) {
513 if ( !count( $dbkeys ) ) {
522 $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN (";
525 foreach( $dbkeys as $dbkey => $nothing ) {
531 $sql .= $db->addQuotes( $dbkey );