MediaWiki REL1_34
populateArchiveRevId.php
Go to the documentation of this file.
1<?php
26
27require_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 );
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";
297require_once RUN_MAINTENANCE_IF_MAIN;
getDB()
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.
const RUN_MAINTENANCE_IF_MAIN
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
output( $out, $channel=null)
Throw some output to the user.
getBatchSize()
Returns batch size.
addDescription( $text)
Set the description text.
setBatchSize( $s=0)
Set the batch size.
Maintenance script that populares archive.ar_rev_id in old rows.
doDBUpdates()
Do the actual work.
getUpdateKey()
Get the update key name to go in the update log table.
static array null $dummyRev
Dummy revision row.
static checkMysqlAutoIncrementBug(IDatabase $dbw)
Check for (and work around) a MySQL auto-increment bug.
static reassignArRevIds(IDatabase $dbw, array $arIds, array $conds=[])
Assign new ar_rev_ids to a set of ar_ids.
static makeDummyRevisionRow(IDatabase $dbw)
Construct a dummy revision table row to use for reserving IDs.
static isNewInstall(IDatabase $dbw)
__construct()
Default constructor.
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:740
const EDIT_SUPPRESS_RC
Definition Defines.php:144
const EDIT_NEW
Definition Defines.php:141
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
rollback( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Rollback a transaction previously started using begin()
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
doAtomicSection( $fname, callable $callback, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Perform an atomic section of reversable SQL statements from a callback.
affectedRows()
Get the number of rows affected by the last write query.
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
getType()
Get the type of the DBMS (e.g.
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
insertId()
Get the inserted value of an auto-increment row.
const DB_MASTER
Definition defines.php:26