The grouping makes at least as much sense as job/, and certainly makes more sense than cache/. With directories named after base classes, it is fairly easy to tell what should go where. The grouping of DeferredUpdates, DataUpdate and CallableUpdate would surely be uncontroversial. The move of SearchUpdate out of search/ demonstrates the conflict between arrangement by module versus arrangement by type, which is the most difficult design question here. I think arrangement by type is more consistent with e.g. the arrangement of the core root, i.e. tests/, resources/, maintenance/, etc. where a given feature will have its files split up into a mostly type-based hierarchy. I also tidied up AutoLoader.php by moving includes/content to the correct location, sorted alphabetically by subdirectory. Verified with AutoLoaderTest. Change-Id: Ib369411d0caca38e72978084aa57348f1b892ed0
152 lines
4.8 KiB
PHP
152 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* Base code for update jobs that put some secondary data extracted
|
|
* from article content into the database.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* @file
|
|
*/
|
|
|
|
/**
|
|
* Abstract base class for update jobs that put some secondary data extracted
|
|
* from article content into the database.
|
|
*
|
|
* @note: subclasses should NOT start or commit transactions in their doUpdate() method,
|
|
* a transaction will automatically be wrapped around the update. Starting another
|
|
* one would break the outer transaction bracket. If need be, subclasses can override
|
|
* the beginTransaction() and commitTransaction() methods.
|
|
*/
|
|
abstract class SqlDataUpdate extends DataUpdate {
|
|
|
|
protected $mDb; //!< Database connection reference
|
|
protected $mOptions; //!< SELECT options to be used (array)
|
|
|
|
private $mHasTransaction; //!< bool whether a transaction is open on this object (internal use only!)
|
|
protected $mUseTransaction; //!< bool whether this update should be wrapped in a transaction
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param bool $withTransaction whether this update should be wrapped in a transaction (default: true).
|
|
* A transaction is only started if no transaction is already in progress,
|
|
* see beginTransaction() for details.
|
|
**/
|
|
public function __construct( $withTransaction = true ) {
|
|
global $wgAntiLockFlags;
|
|
|
|
parent::__construct();
|
|
|
|
if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
|
|
$this->mOptions = array();
|
|
} else {
|
|
$this->mOptions = array( 'FOR UPDATE' );
|
|
}
|
|
|
|
// @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
|
|
$this->mDb = wfGetDB( DB_MASTER );
|
|
|
|
$this->mWithTransaction = $withTransaction;
|
|
$this->mHasTransaction = false;
|
|
}
|
|
|
|
/**
|
|
* Begin a database transaction, if $withTransaction was given as true in the constructor for this SqlDataUpdate.
|
|
*
|
|
* Because nested transactions are not supported by the Database class, this implementation
|
|
* checks Database::trxLevel() and only opens a transaction if none is already active.
|
|
*/
|
|
public function beginTransaction() {
|
|
if ( !$this->mWithTransaction ) {
|
|
return;
|
|
}
|
|
|
|
// NOTE: nested transactions are not supported, only start a transaction if none is open
|
|
if ( $this->mDb->trxLevel() === 0 ) {
|
|
$this->mDb->begin( get_class( $this ) . '::beginTransaction' );
|
|
$this->mHasTransaction = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Commit the database transaction started via beginTransaction (if any).
|
|
*/
|
|
public function commitTransaction() {
|
|
if ( $this->mHasTransaction ) {
|
|
$this->mDb->commit( get_class( $this ) . '::commitTransaction' );
|
|
$this->mHasTransaction = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abort the database transaction started via beginTransaction (if any).
|
|
*/
|
|
public function abortTransaction() {
|
|
if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
|
|
$this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
|
|
$this->mHasTransaction = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate the cache of a list of pages from a single namespace.
|
|
* This is intended for use by subclasses.
|
|
*
|
|
* @param $namespace Integer
|
|
* @param $dbkeys Array
|
|
*/
|
|
protected function invalidatePages( $namespace, array $dbkeys ) {
|
|
if ( $dbkeys === array() ) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Determine which pages need to be updated
|
|
* This is necessary to prevent the job queue from smashing the DB with
|
|
* large numbers of concurrent invalidations of the same page
|
|
*/
|
|
$now = $this->mDb->timestamp();
|
|
$ids = array();
|
|
$res = $this->mDb->select( 'page', array( 'page_id' ),
|
|
array(
|
|
'page_namespace' => $namespace,
|
|
'page_title' => $dbkeys,
|
|
'page_touched < ' . $this->mDb->addQuotes( $now )
|
|
), __METHOD__
|
|
);
|
|
|
|
foreach ( $res as $row ) {
|
|
$ids[] = $row->page_id;
|
|
}
|
|
|
|
if ( $ids === array() ) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Do the update
|
|
* We still need the page_touched condition, in case the row has changed since
|
|
* the non-locking select above.
|
|
*/
|
|
$this->mDb->update( 'page', array( 'page_touched' => $now ),
|
|
array(
|
|
'page_id' => $ids,
|
|
'page_touched < ' . $this->mDb->addQuotes( $now )
|
|
), __METHOD__
|
|
);
|
|
}
|
|
|
|
}
|