* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* Class the manages updates of *_link tables as well as similar extension-managed tables
*
*
* See docs/deferred.txt
*/
-class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
+class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
// @todo make members protected, but make sure extensions don't break
/** @var int Page ID of the article linked from */
*/
private $linkDeletions = null;
+ /**
+ * @var null|array Added properties if calculated.
+ */
+ private $propertyInsertions = null;
+
+ /**
+ * @var null|array Deleted properties if calculated.
+ */
+ private $propertyDeletions = null;
+
/**
* @var User|null
*/
private $user;
+ /** @var IDatabase */
+ private $db;
+
/**
* Constructor
*
* @throws MWException
*/
function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) {
- // Implicit transactions are disabled as they interfere with batching
- parent::__construct( false );
+ parent::__construct();
$this->mTitle = $title;
$this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE );
* @note: this is managed by DeferredUpdates::execute(). Do not run this in a transaction.
*/
public function doUpdate() {
- // Make sure all links update threads see the changes of each other.
- // This handles the case when updates have to batched into several COMMITs.
- $scopedLock = self::acquirePageLock( $this->mDb, $this->mId );
+ if ( $this->ticket ) {
+ // Make sure all links update threads see the changes of each other.
+ // This handles the case when updates have to batched into several COMMITs.
+ $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
+ }
Hooks::run( 'LinksUpdate', [ &$this ] );
$this->doIncrementalUpdate();
- // Commit and release the lock
+ // Commit and release the lock (if set)
ScopedCallback::consume( $scopedLock );
// Run post-commit hooks without DBO_TRX
- $this->mDb->onTransactionIdle( function() {
- Hooks::run( 'LinksUpdateComplete', [ &$this ] );
- } );
+ $this->getDB()->onTransactionIdle(
+ function () {
+ Hooks::run( 'LinksUpdateComplete', [ &$this, $this->ticket ] );
+ },
+ __METHOD__
+ );
}
/**
# Page properties
$existing = $this->getExistingProperties();
- $propertiesDeletes = $this->getPropertyDeletions( $existing );
- $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
+ $this->propertyDeletions = $this->getPropertyDeletions( $existing );
+ $this->incrTableUpdate( 'page_props', 'pp', $this->propertyDeletions,
$this->getPropertyInsertions( $existing ) );
# Invalidate the necessary pages
- $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
+ $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existing );
+ $changed = $this->propertyDeletions + $this->propertyInsertions;
$this->invalidateProperties( $changed );
# Refresh links of all pages including this page
* @param array $cats
*/
function invalidateCategories( $cats ) {
- PurgeJobUtils::invalidatePages( $this->mDb, NS_CATEGORY, array_keys( $cats ) );
+ PurgeJobUtils::invalidatePages( $this->getDB(), NS_CATEGORY, array_keys( $cats ) );
}
/**
* @param array $images
*/
function invalidateImageDescriptions( $images ) {
- PurgeJobUtils::invalidatePages( $this->mDb, NS_FILE, array_keys( $images ) );
+ PurgeJobUtils::invalidatePages( $this->getDB(), NS_FILE, array_keys( $images ) );
}
/**
* @param array $insertions Rows to insert
*/
private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
- $bSize = RequestContext::getMain()->getConfig()->get( 'UpdateRowsPerQuery' );
- $factory = wfGetLBFactory();
+ $services = MediaWikiServices::getInstance();
+ $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
+ $factory = $services->getDBLoadBalancerFactory();
if ( $table === 'page_props' ) {
$fromField = 'pp_page';
foreach ( $deletionBatches as $deletionBatch ) {
$deleteWheres[] = [
$fromField => $this->mId,
- $this->mDb->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
+ $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
];
}
} else {
}
foreach ( $deleteWheres as $deleteWhere ) {
- $this->mDb->delete( $table, $deleteWhere, __METHOD__ );
+ $this->getDB()->delete( $table, $deleteWhere, __METHOD__ );
$factory->commitAndWaitForReplication(
- __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ]
);
}
$insertBatches = array_chunk( $insertions, $bSize );
foreach ( $insertBatches as $insertBatch ) {
- $this->mDb->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
+ $this->getDB()->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
$factory->commitAndWaitForReplication(
- __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ]
);
}
foreach ( $diffs as $url => $dummy ) {
foreach ( wfMakeUrlIndexes( $url ) as $index ) {
$arr[] = [
- 'el_id' => $this->mDb->nextSequenceValue( 'externallinks_el_id_seq' ),
+ 'el_id' => $this->getDB()->nextSequenceValue( 'externallinks_el_id_seq' ),
'el_from' => $this->mId,
'el_to' => $url,
'el_index' => $index,
'cl_from' => $this->mId,
'cl_to' => $name,
'cl_sortkey' => $sortkey,
- 'cl_timestamp' => $this->mDb->timestamp(),
+ 'cl_timestamp' => $this->getDB()->timestamp(),
'cl_sortkey_prefix' => $prefix,
'cl_collation' => $wgCategoryCollation,
'cl_type' => $type,
* @return array
*/
private function getExistingLinks() {
- $res = $this->mDb->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
- [ 'pl_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
+ [ 'pl_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
if ( !isset( $arr[$row->pl_namespace] ) ) {
* @return array
*/
private function getExistingTemplates() {
- $res = $this->mDb->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
- [ 'tl_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
+ [ 'tl_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
if ( !isset( $arr[$row->tl_namespace] ) ) {
* @return array
*/
private function getExistingImages() {
- $res = $this->mDb->select( 'imagelinks', [ 'il_to' ],
- [ 'il_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
+ [ 'il_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
$arr[$row->il_to] = 1;
* @return array
*/
private function getExistingExternals() {
- $res = $this->mDb->select( 'externallinks', [ 'el_to' ],
- [ 'el_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
+ [ 'el_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
$arr[$row->el_to] = 1;
* @return array
*/
private function getExistingCategories() {
- $res = $this->mDb->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
- [ 'cl_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
+ [ 'cl_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
$arr[$row->cl_to] = $row->cl_sortkey_prefix;
* @return array
*/
private function getExistingInterlangs() {
- $res = $this->mDb->select( 'langlinks', [ 'll_lang', 'll_title' ],
- [ 'll_from' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
+ [ 'll_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
$arr[$row->ll_lang] = $row->ll_title;
* Get an array of existing inline interwiki links, as a 2-D array
* @return array (prefix => array(dbkey => 1))
*/
- protected function getExistingInterwikis() {
- $res = $this->mDb->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
- [ 'iwl_from' => $this->mId ], __METHOD__, $this->mOptions );
+ private function getExistingInterwikis() {
+ $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
+ [ 'iwl_from' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
if ( !isset( $arr[$row->iwl_prefix] ) ) {
* @return array Array of property names and values
*/
private function getExistingProperties() {
- $res = $this->mDb->select( 'page_props', [ 'pp_propname', 'pp_value' ],
- [ 'pp_page' => $this->mId ], __METHOD__, $this->mOptions );
+ $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
+ [ 'pp_page' => $this->mId ], __METHOD__ );
$arr = [];
foreach ( $res as $row ) {
$arr[$row->pp_propname] = $row->pp_value;
return $result;
}
+ /**
+ * Fetch page properties added by this LinksUpdate.
+ * Only available after the update is complete.
+ * @since 1.28
+ * @return null|array
+ */
+ public function getAddedProperties() {
+ return $this->propertyInsertions;
+ }
+
+ /**
+ * Fetch page properties removed by this LinksUpdate.
+ * Only available after the update is complete.
+ * @since 1.28
+ * @return null|array
+ */
+ public function getRemovedProperties() {
+ return $this->propertyDeletions;
+ }
+
/**
* Update links table freshness
*/
- protected function updateLinksTimestamp() {
+ private function updateLinksTimestamp() {
if ( $this->mId ) {
// The link updates made here only reflect the freshness of the parser output
$timestamp = $this->mParserOutput->getCacheTime();
- $this->mDb->update( 'page',
- [ 'page_links_updated' => $this->mDb->timestamp( $timestamp ) ],
+ $this->getDB()->update( 'page',
+ [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
[ 'page_id' => $this->mId ],
__METHOD__
);
}
}
+ /**
+ * @return IDatabase
+ */
+ private function getDB() {
+ if ( !$this->db ) {
+ $this->db = wfGetDB( DB_MASTER );
+ }
+
+ return $this->db;
+ }
+
public function getAsJobSpecification() {
if ( $this->user ) {
$userInfo = [
}
return [
- 'wiki' => $this->mDb->getWikiID(),
+ 'wiki' => $this->getDB()->getWikiID(),
'job' => new JobSpecification(
'refreshLinksPrioritized',
[