MediaWiki REL1_35
populateArchiveRevId.php
Go to the documentation of this file.
1<?php
27
28require_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;
301require_once RUN_MAINTENANCE_IF_MAIN;
getDB()
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.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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:758
@newable Stable to extend
const EDIT_SUPPRESS_RC
Definition Defines.php:145
const EDIT_NEW
Definition Defines.php:142
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 all rows in a table that match a condition.
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
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.
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
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:29