123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <?php
- /**
- * Populate ar_rev_id in pre-1.5 rows
- *
- * 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
- * @ingroup Maintenance
- */
- use Wikimedia\Rdbms\DBQueryError;
- use Wikimedia\Rdbms\IDatabase;
- require_once __DIR__ . '/Maintenance.php';
- /**
- * Maintenance script that populares archive.ar_rev_id in old rows
- *
- * @ingroup Maintenance
- * @since 1.31
- */
- class PopulateArchiveRevId extends LoggedUpdateMaintenance {
- /** @var array|null Dummy revision row */
- private static $dummyRev = null;
- public function __construct() {
- parent::__construct();
- $this->addDescription( 'Populate ar_rev_id in pre-1.5 rows' );
- $this->setBatchSize( 100 );
- }
- protected function getUpdateKey() {
- return __CLASS__;
- }
- protected function doDBUpdates() {
- $this->output( "Populating ar_rev_id...\n" );
- $dbw = $this->getDB( DB_MASTER );
- self::checkMysqlAutoIncrementBug( $dbw );
- // Quick exit if there are no rows needing updates.
- $any = $dbw->selectField(
- 'archive',
- 'ar_id',
- [ 'ar_rev_id' => null ],
- __METHOD__
- );
- if ( !$any ) {
- $this->output( "Completed ar_rev_id population, 0 rows updated.\n" );
- return true;
- }
- $count = 0;
- while ( true ) {
- wfWaitForSlaves();
- $arIds = $dbw->selectFieldValues(
- 'archive',
- 'ar_id',
- [ 'ar_rev_id' => null ],
- __METHOD__,
- [ 'LIMIT' => $this->getBatchSize(), 'ORDER BY' => [ 'ar_id' ] ]
- );
- if ( !$arIds ) {
- $this->output( "Completed ar_rev_id population, $count rows updated.\n" );
- return true;
- }
- $count += self::reassignArRevIds( $dbw, $arIds, [ 'ar_rev_id' => null ] );
- $min = min( $arIds );
- $max = max( $arIds );
- $this->output( " ... $min-$max\n" );
- }
- }
- /**
- * Check for (and work around) a MySQL auto-increment bug
- *
- * (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34
- * don't save the auto-increment value to disk, so on server restart it
- * might reuse IDs from deleted revisions. We can fix that with an insert
- * with an explicit rev_id value, if necessary.
- *
- * @param IDatabase $dbw
- */
- public static function checkMysqlAutoIncrementBug( IDatabase $dbw ) {
- if ( $dbw->getType() !== 'mysql' ) {
- return;
- }
- if ( !self::$dummyRev ) {
- self::$dummyRev = self::makeDummyRevisionRow( $dbw );
- }
- $ok = false;
- while ( !$ok ) {
- try {
- $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
- $dbw->insert( 'revision', self::$dummyRev, $fname );
- $id = $dbw->insertId();
- $toDelete[] = $id;
- $maxId = max(
- (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ ),
- (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], __METHOD__ )
- );
- if ( $id <= $maxId ) {
- $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
- $toDelete[] = $maxId + 1;
- }
- $dbw->delete( 'revision', [ 'rev_id' => $toDelete ], $fname );
- } );
- $ok = true;
- } catch ( DBQueryError $e ) {
- if ( $e->errno != 1062 ) { // 1062 is "duplicate entry", ignore it and retry
- throw $e;
- }
- }
- }
- }
- /**
- * Assign new ar_rev_ids to a set of ar_ids.
- * @param IDatabase $dbw
- * @param int[] $arIds
- * @param array $conds Extra conditions for the update
- * @return int Number of updated rows
- */
- public static function reassignArRevIds( IDatabase $dbw, array $arIds, array $conds = [] ) {
- if ( !self::$dummyRev ) {
- self::$dummyRev = self::makeDummyRevisionRow( $dbw );
- }
- $updates = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $arIds ) {
- // Create new rev_ids by inserting dummy rows into revision and then deleting them.
- $dbw->insert( 'revision', array_fill( 0, count( $arIds ), self::$dummyRev ), $fname );
- $revIds = $dbw->selectFieldValues(
- 'revision',
- 'rev_id',
- [ 'rev_timestamp' => self::$dummyRev['rev_timestamp'] ],
- $fname
- );
- if ( !is_array( $revIds ) ) {
- throw new UnexpectedValueException( 'Failed to insert dummy revisions' );
- }
- if ( count( $revIds ) !== count( $arIds ) ) {
- throw new UnexpectedValueException(
- 'Tried to insert ' . count( $arIds ) . ' dummy revisions, but found '
- . count( $revIds ) . ' matching rows.'
- );
- }
- $dbw->delete( 'revision', [ 'rev_id' => $revIds ], $fname );
- return array_combine( $arIds, $revIds );
- } );
- $count = 0;
- foreach ( $updates as $arId => $revId ) {
- $dbw->update(
- 'archive',
- [ 'ar_rev_id' => $revId ],
- [ 'ar_id' => $arId ] + $conds,
- __METHOD__
- );
- $count += $dbw->affectedRows();
- }
- return $count;
- }
- /**
- * Construct a dummy revision table row to use for reserving IDs
- *
- * The row will have a wildly unlikely timestamp, and possibly a generic
- * user and comment, but will otherwise be derived from a revision on the
- * wiki's main page or some other revision in the database.
- *
- * @param IDatabase $dbw
- * @return array
- */
- private static function makeDummyRevisionRow( IDatabase $dbw ) {
- $ts = $dbw->timestamp( '11111111111111' );
- $rev = null;
- $mainPage = Title::newMainPage();
- $pageId = $mainPage ? $mainPage->getArticleId() : null;
- if ( $pageId ) {
- $rev = $dbw->selectRow(
- 'revision',
- '*',
- [ 'rev_page' => $pageId ],
- __METHOD__,
- [ 'ORDER BY' => 'rev_timestamp ASC' ]
- );
- }
- if ( !$rev ) {
- // No main page? Let's see if there are any revisions at all
- $rev = $dbw->selectRow(
- 'revision',
- '*',
- [],
- __METHOD__,
- [ 'ORDER BY' => 'rev_timestamp ASC' ]
- );
- }
- if ( !$rev ) {
- throw new UnexpectedValueException( 'No revisions are available to copy' );
- }
- unset( $rev->rev_id );
- $rev = (array)$rev;
- $rev['rev_timestamp'] = $ts;
- if ( isset( $rev['rev_user'] ) ) {
- $rev['rev_user'] = 0;
- $rev['rev_user_text'] = '0.0.0.0';
- }
- if ( isset( $rev['rev_comment'] ) ) {
- $rev['rev_comment'] = 'Dummy row';
- }
- $any = $dbw->selectField(
- 'revision',
- 'rev_id',
- [ 'rev_timestamp' => $ts ],
- __METHOD__
- );
- if ( $any ) {
- throw new UnexpectedValueException( "... Why does your database contain a revision dated $ts?" );
- }
- return $rev;
- }
- }
- $maintClass = "PopulateArchiveRevId";
- require_once RUN_MAINTENANCE_IF_MAIN;
|