MediaWiki  master
populateArchiveRevId.php
Go to the documentation of this file.
1 <?php
27 
28 require_once __DIR__ . '/Maintenance.php';
29 
37 
39  private static $dummyRev = null;
40 
41  public function __construct() {
42  parent::__construct();
43  $this->addDescription( 'Populate ar_rev_id in pre-1.5 rows' );
44  $this->setBatchSize( 100 );
45  }
46 
51  public static function isNewInstall( IDatabase $dbw ) {
52  return $dbw->selectRowCount( 'archive', '*', [], __METHOD__ ) === 0 &&
53  $dbw->selectRowCount( 'revision', '*', [], __METHOD__ ) === 1;
54  }
55 
56  protected function getUpdateKey() {
57  return __CLASS__;
58  }
59 
60  protected function doDBUpdates() {
61  $this->output( "Populating ar_rev_id...\n" );
62  $dbw = $this->getDB( DB_MASTER );
64 
65  // Quick exit if there are no rows needing updates.
66  $any = $dbw->selectField(
67  'archive',
68  'ar_id',
69  [ 'ar_rev_id' => null ],
70  __METHOD__
71  );
72  if ( !$any ) {
73  $this->output( "Completed ar_rev_id population, 0 rows updated.\n" );
74  return true;
75  }
76 
77  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
78  $count = 0;
79  while ( true ) {
80  $lbFactory->waitForReplication();
81 
82  $arIds = $dbw->selectFieldValues(
83  'archive',
84  'ar_id',
85  [ 'ar_rev_id' => null ],
86  __METHOD__,
87  [ 'LIMIT' => $this->getBatchSize(), 'ORDER BY' => [ 'ar_id' ] ]
88  );
89  if ( !$arIds ) {
90  $this->output( "Completed ar_rev_id population, $count rows updated.\n" );
91  return true;
92  }
93 
94  $count += self::reassignArRevIds( $dbw, $arIds, [ 'ar_rev_id' => null ] );
95 
96  $min = min( $arIds );
97  $max = max( $arIds );
98  $this->output( " ... $min-$max\n" );
99  }
100  }
101 
112  public static function checkMysqlAutoIncrementBug( IDatabase $dbw ) {
113  if ( $dbw->getType() !== 'mysql' ) {
114  return;
115  }
116 
117  if ( !self::$dummyRev ) {
118  self::$dummyRev = self::makeDummyRevisionRow( $dbw );
119  }
120 
121  $ok = false;
122  while ( !$ok ) {
123  try {
124  $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
125  $dbw->insert( 'revision', self::$dummyRev, $fname );
126  $id = $dbw->insertId();
127  $toDelete = [ $id ];
128 
129  $maxId = max(
130  (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], $fname ),
131  (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], $fname )
132  );
133  if ( $id <= $maxId ) {
134  $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
135  $toDelete[] = $maxId + 1;
136  }
137 
138  $dbw->delete( 'revision', [ 'rev_id' => $toDelete ], $fname );
139  } );
140  $ok = true;
141  } catch ( DBQueryError $e ) {
142  if ( $e->errno != 1062 ) {
143  // 1062 is "duplicate entry", ignore it and retry
144  throw $e;
145  }
146  }
147  }
148  }
149 
157  public static function reassignArRevIds( IDatabase $dbw, array $arIds, array $conds = [] ) {
158  if ( !self::$dummyRev ) {
159  self::$dummyRev = self::makeDummyRevisionRow( $dbw );
160  }
161 
162  $updates = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $arIds ) {
163  // Create new rev_ids by inserting dummy rows into revision and then deleting them.
164  $dbw->insert( 'revision', array_fill( 0, count( $arIds ), self::$dummyRev ), $fname );
165  $revIds = $dbw->selectFieldValues(
166  'revision',
167  'rev_id',
168  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
169  [ 'rev_timestamp' => self::$dummyRev['rev_timestamp'] ],
170  $fname
171  );
172  if ( !is_array( $revIds ) ) {
173  throw new UnexpectedValueException( 'Failed to insert dummy revisions' );
174  }
175  if ( count( $revIds ) !== count( $arIds ) ) {
176  throw new UnexpectedValueException(
177  'Tried to insert ' . count( $arIds ) . ' dummy revisions, but found '
178  . count( $revIds ) . ' matching rows.'
179  );
180  }
181  $dbw->delete( 'revision', [ 'rev_id' => $revIds ], $fname );
182 
183  return array_combine( $arIds, $revIds );
184  } );
185 
186  $count = 0;
187  foreach ( $updates as $arId => $revId ) {
188  $dbw->update(
189  'archive',
190  [ 'ar_rev_id' => $revId ],
191  [ 'ar_id' => $arId ] + $conds,
192  __METHOD__
193  );
194  $count += $dbw->affectedRows();
195  }
196  return $count;
197  }
198 
209  private static function makeDummyRevisionRow( IDatabase $dbw ) {
210  $ts = $dbw->timestamp( '11111111111111' );
211  $rev = null;
212 
213  $mainPage = Title::newMainPage();
214  $pageId = $mainPage ? $mainPage->getArticleID() : null;
215  if ( $pageId ) {
216  $rev = $dbw->selectRow(
217  'revision',
218  '*',
219  [ 'rev_page' => $pageId ],
220  __METHOD__,
221  [ 'ORDER BY' => 'rev_timestamp ASC' ]
222  );
223  }
224 
225  if ( !$rev ) {
226  // No main page? Let's see if there are any revisions at all
227  $rev = $dbw->selectRow(
228  'revision',
229  '*',
230  [],
231  __METHOD__,
232  [ 'ORDER BY' => 'rev_timestamp ASC' ]
233  );
234  }
235  if ( !$rev ) {
236  // Since no revisions are available to copy, generate a dummy
237  // revision to a dummy page, then rollback the commit
238  wfDebug( __METHOD__ . ": No revisions are available to copy" );
239 
240  $dbw->begin( __METHOD__ );
241 
242  // Make a title and revision and insert them
243  $title = Title::newFromText( "PopulateArchiveRevId_4b05b46a81e29" );
244  $page = WikiPage::factory( $title );
245  $updater = $page->newPageUpdater(
246  User::newSystemUser( 'Maintenance script', [ 'steal' => true ] )
247  );
248  $updater->setContent(
249  'main',
250  ContentHandler::makeContent( "Content for dummy rev", $title )
251  );
252  $updater->saveRevision(
253  CommentStoreComment::newUnsavedComment( 'dummy rev summary' ),
255  );
256 
257  // get the revision row just inserted
258  $rev = $dbw->selectRow(
259  'revision',
260  '*',
261  [],
262  __METHOD__,
263  [ 'ORDER BY' => 'rev_timestamp ASC' ]
264  );
265 
266  $dbw->rollback( __METHOD__ );
267  }
268  if ( !$rev ) {
269  // This should never happen.
270  throw new UnexpectedValueException(
271  'No revisions are available to copy, and one couldn\'t be created'
272  );
273  }
274 
275  unset( $rev->rev_id );
276  $rev = (array)$rev;
277  $rev['rev_timestamp'] = $ts;
278  if ( isset( $rev['rev_user'] ) ) {
279  $rev['rev_user'] = 0;
280  $rev['rev_user_text'] = '0.0.0.0';
281  }
282  if ( isset( $rev['rev_comment'] ) ) {
283  $rev['rev_comment'] = 'Dummy row';
284  }
285 
286  $any = $dbw->selectField(
287  'revision',
288  'rev_id',
289  [ 'rev_timestamp' => $ts ],
290  __METHOD__
291  );
292  if ( $any ) {
293  throw new UnexpectedValueException( "... Why does your database contain a revision dated $ts?" );
294  }
295 
296  return $rev;
297  }
298 }
299 
300 $maintClass = PopulateArchiveRevId::class;
301 require_once RUN_MAINTENANCE_IF_MAIN;
RUN_MAINTENANCE_IF_MAIN
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:38
CommentStoreComment\newUnsavedComment
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
Definition: CommentStoreComment.php:67
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:329
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
PopulateArchiveRevId\makeDummyRevisionRow
static makeDummyRevisionRow(IDatabase $dbw)
Construct a dummy revision table row to use for reserving IDs.
Definition: populateArchiveRevId.php:209
PopulateArchiveRevId\$dummyRev
static array null $dummyRev
Dummy revision row.
Definition: populateArchiveRevId.php:39
Wikimedia\Rdbms\IDatabase\rollback
rollback( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Rollback a transaction previously started using begin()
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
Maintenance\addDescription
addDescription( $text)
Set the description text.
Definition: Maintenance.php:327
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:654
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:756
Wikimedia\Rdbms\IDatabase\update
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
PopulateArchiveRevId\__construct
__construct()
Default constructor.
Definition: populateArchiveRevId.php:41
PopulateArchiveRevId\checkMysqlAutoIncrementBug
static checkMysqlAutoIncrementBug(IDatabase $dbw)
Check for (and work around) a MySQL auto-increment bug.
Definition: populateArchiveRevId.php:112
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:156
LoggedUpdateMaintenance
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
Definition: LoggedUpdateMaintenance.php:26
PopulateArchiveRevId
Maintenance script that populares archive.ar_rev_id in old rows.
Definition: populateArchiveRevId.php:36
$title
$title
Definition: testCompression.php:38
PopulateArchiveRevId\doDBUpdates
doDBUpdates()
Do the actual work.
Definition: populateArchiveRevId.php:60
DB_MASTER
const DB_MASTER
Definition: defines.php:26
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:909
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:142
Wikimedia\Rdbms\DBQueryError
Definition: DBQueryError.php:29
PopulateArchiveRevId\reassignArRevIds
static reassignArRevIds(IDatabase $dbw, array $arIds, array $conds=[])
Assign new ar_rev_ids to a set of ar_ids.
Definition: populateArchiveRevId.php:157
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
Wikimedia\Rdbms\IDatabase\selectRowCount
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
Maintenance\getDB
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
Definition: Maintenance.php:1366
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:141
Wikimedia\Rdbms\IDatabase\doAtomicSection
doAtomicSection( $fname, callable $callback, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Perform an atomic section of reversable SQL statements from a callback.
$maintClass
$maintClass
Definition: populateArchiveRevId.php:300
Maintenance\getBatchSize
getBatchSize()
Returns batch size.
Definition: Maintenance.php:366
Wikimedia\Rdbms\IDatabase\selectFieldValues
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
Wikimedia\Rdbms\IDatabase\getType
getType()
Get the type of the DBMS (e.g.
PopulateArchiveRevId\isNewInstall
static isNewInstall(IDatabase $dbw)
Definition: populateArchiveRevId.php:51
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:434
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:144
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
PopulateArchiveRevId\getUpdateKey
getUpdateKey()
Get the update key name to go in the update log table.
Definition: populateArchiveRevId.php:56
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
Wikimedia\Rdbms\IDatabase\begin
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Maintenance\setBatchSize
setBatchSize( $s=0)
Set the batch size.
Definition: Maintenance.php:374