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