MediaWiki REL1_39
populateArchiveRevId.php
Go to the documentation of this file.
1<?php
28
29require_once __DIR__ . '/Maintenance.php';
30
38
40 private static $dummyRev = null;
41
42 public function __construct() {
43 parent::__construct();
44 $this->addDescription( 'Populate ar_rev_id in pre-1.5 rows' );
45 $this->setBatchSize( 100 );
46 }
47
52 public static function isNewInstall( IDatabase $dbw ) {
53 return $dbw->selectRowCount( 'archive', '*', [], __METHOD__ ) === 0 &&
54 $dbw->selectRowCount( 'revision', '*', [], __METHOD__ ) === 1;
55 }
56
57 protected function getUpdateKey() {
58 return __CLASS__;
59 }
60
61 protected function doDBUpdates() {
62 $this->output( "Populating ar_rev_id...\n" );
63 $dbw = $this->getDB( DB_PRIMARY );
65
66 // Quick exit if there are no rows needing updates.
67 $any = $dbw->selectField(
68 'archive',
69 'ar_id',
70 [ 'ar_rev_id' => null ],
71 __METHOD__
72 );
73 if ( !$any ) {
74 $this->output( "Completed ar_rev_id population, 0 rows updated.\n" );
75 return true;
76 }
77
78 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
79 $count = 0;
80 while ( true ) {
81 $lbFactory->waitForReplication();
82
83 $arIds = $dbw->selectFieldValues(
84 'archive',
85 'ar_id',
86 [ 'ar_rev_id' => null ],
87 __METHOD__,
88 [ 'LIMIT' => $this->getBatchSize(), 'ORDER BY' => [ 'ar_id' ] ]
89 );
90 if ( !$arIds ) {
91 $this->output( "Completed ar_rev_id population, $count rows updated.\n" );
92 return true;
93 }
94
95 $count += self::reassignArRevIds( $dbw, $arIds, [ 'ar_rev_id' => null ] );
96
97 $min = min( $arIds );
98 $max = max( $arIds );
99 $this->output( " ... $min-$max\n" );
100 }
101 }
102
113 public static function checkMysqlAutoIncrementBug( IDatabase $dbw ) {
114 if ( $dbw->getType() !== 'mysql' ) {
115 return;
116 }
117
118 if ( !self::$dummyRev ) {
119 self::$dummyRev = self::makeDummyRevisionRow( $dbw );
120 }
121
122 $ok = false;
123 while ( !$ok ) {
124 try {
125 $dbw->doAtomicSection( __METHOD__, static function ( IDatabase $dbw, $fname ) {
126 $dbw->insert( 'revision', self::$dummyRev, $fname );
127 $id = $dbw->insertId();
128 $toDelete = [ $id ];
129
130 $maxId = max(
131 (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], $fname ),
132 (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], $fname )
133 );
134 if ( $id <= $maxId ) {
135 $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
136 $toDelete[] = $maxId + 1;
137 }
138
139 $dbw->delete( 'revision', [ 'rev_id' => $toDelete ], $fname );
140 } );
141 $ok = true;
142 } catch ( DBQueryError $e ) {
143 if ( $e->errno != 1062 ) {
144 // 1062 is "duplicate entry", ignore it and retry
145 throw $e;
146 }
147 }
148 }
149 }
150
158 public static function reassignArRevIds( IDatabase $dbw, array $arIds, array $conds = [] ) {
159 if ( !self::$dummyRev ) {
160 self::$dummyRev = self::makeDummyRevisionRow( $dbw );
161 }
162
163 $updates = $dbw->doAtomicSection( __METHOD__, static function ( IDatabase $dbw, $fname ) use ( $arIds ) {
164 // Create new rev_ids by inserting dummy rows into revision and then deleting them.
165 $dbw->insert( 'revision', array_fill( 0, count( $arIds ), self::$dummyRev ), $fname );
166 $revIds = $dbw->selectFieldValues(
167 'revision',
168 'rev_id',
169 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
170 [ 'rev_timestamp' => self::$dummyRev['rev_timestamp'] ],
171 $fname
172 );
173 if ( count( $revIds ) !== count( $arIds ) ) {
174 throw new UnexpectedValueException(
175 'Tried to insert ' . count( $arIds ) . ' dummy revisions, but found '
176 . count( $revIds ) . ' matching rows.'
177 );
178 }
179 $dbw->delete( 'revision', [ 'rev_id' => $revIds ], $fname );
180
181 return array_combine( $arIds, $revIds );
182 } );
183
184 $count = 0;
185 foreach ( $updates as $arId => $revId ) {
186 $dbw->update(
187 'archive',
188 [ 'ar_rev_id' => $revId ],
189 [ 'ar_id' => $arId ] + $conds,
190 __METHOD__
191 );
192 $count += $dbw->affectedRows();
193 }
194 return $count;
195 }
196
207 private static function makeDummyRevisionRow( IDatabase $dbw ) {
208 $ts = $dbw->timestamp( '11111111111111' );
209 $rev = null;
210
211 $mainPage = Title::newMainPage();
212 $pageId = $mainPage ? $mainPage->getArticleID() : null;
213 if ( $pageId ) {
214 $rev = $dbw->selectRow(
215 'revision',
216 '*',
217 [ 'rev_page' => $pageId ],
218 __METHOD__,
219 [ 'ORDER BY' => 'rev_timestamp ASC' ]
220 );
221 }
222
223 if ( !$rev ) {
224 // No main page? Let's see if there are any revisions at all
225 $rev = $dbw->selectRow(
226 'revision',
227 '*',
228 [],
229 __METHOD__,
230 [ 'ORDER BY' => 'rev_timestamp ASC' ]
231 );
232 }
233 if ( !$rev ) {
234 // Since no revisions are available to copy, generate a dummy
235 // revision to a dummy page, then rollback the commit
236 wfDebug( __METHOD__ . ": No revisions are available to copy" );
237
238 $dbw->begin( __METHOD__ );
239
240 // Make a title and revision and insert them
241 $title = Title::newFromText( "PopulateArchiveRevId_4b05b46a81e29" );
242 $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
243 $page->newPageUpdater(
245 )
246 ->setContent(
247 SlotRecord::MAIN,
248 ContentHandler::makeContent( "Content for dummy rev", $title )
249 )
250 ->saveRevision(
251 CommentStoreComment::newUnsavedComment( 'dummy rev summary' ),
253 );
254
255 // get the revision row just inserted
256 $rev = $dbw->selectRow(
257 'revision',
258 '*',
259 [],
260 __METHOD__,
261 [ 'ORDER BY' => 'rev_timestamp ASC' ]
262 );
263
264 $dbw->rollback( __METHOD__ );
265 }
266 if ( !$rev ) {
267 // This should never happen.
268 throw new UnexpectedValueException(
269 'No revisions are available to copy, and one couldn\'t be created'
270 );
271 }
272
273 unset( $rev->rev_id );
274 $rev = (array)$rev;
275 $rev['rev_timestamp'] = $ts;
276 if ( isset( $rev['rev_user'] ) ) {
277 $rev['rev_user'] = 0;
278 $rev['rev_user_text'] = '0.0.0.0';
279 }
280 if ( isset( $rev['rev_comment'] ) ) {
281 $rev['rev_comment'] = 'Dummy row';
282 }
283
284 $any = $dbw->selectField(
285 'revision',
286 'rev_id',
287 [ 'rev_timestamp' => $ts ],
288 __METHOD__
289 );
290 if ( $any ) {
291 throw new UnexpectedValueException( "... Why does your database contain a revision dated $ts?" );
292 }
293
294 return $rev;
295 }
296}
297
298$maintClass = PopulateArchiveRevId::class;
299require_once RUN_MAINTENANCE_IF_MAIN;
getDB()
const EDIT_SUPPRESS_RC
Definition Defines.php:129
const EDIT_NEW
Definition Defines.php:126
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
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)
Service locator for MediaWiki core services.
Value object representing a content slot associated with a page revision.
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 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 isNewInstall(IDatabase $dbw)
__construct()
Default constructor.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:370
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:806
const MAINTENANCE_SCRIPT_USER
Username used for various maintenance scripts.
Definition User.php:116
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
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 reversible 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.
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 RDBMS type of the server (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 row(s) into a table, in the provided order.
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.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
const DB_PRIMARY
Definition defines.php:28