Merge "Skin: Avoid redirect=no for links to non-redirects"
[lhc/web/wiklou.git] / maintenance / populateArchiveRevId.php
1 <?php
2 /**
3 * Populate ar_rev_id in pre-1.5 rows
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 */
23
24 use Wikimedia\Rdbms\IDatabase;
25
26 require_once __DIR__ . '/Maintenance.php';
27
28 /**
29 * Maintenance script that populares archive.ar_rev_id in old rows
30 *
31 * @ingroup Maintenance
32 * @since 1.31
33 */
34 class PopulateArchiveRevId extends LoggedUpdateMaintenance {
35
36 /** @var array|null Dummy revision row */
37 private static $dummyRev = null;
38
39 public function __construct() {
40 parent::__construct();
41 $this->addDescription( 'Populate ar_rev_id in pre-1.5 rows' );
42 $this->setBatchSize( 100 );
43 }
44
45 protected function getUpdateKey() {
46 return __CLASS__;
47 }
48
49 protected function doDBUpdates() {
50 $this->output( "Populating ar_rev_id...\n" );
51 $dbw = $this->getDB( DB_MASTER );
52
53 // Quick exit if there are no rows needing updates.
54 $any = $dbw->selectField(
55 'archive',
56 'ar_id',
57 [ 'ar_rev_id' => null ],
58 __METHOD__
59 );
60 if ( !$any ) {
61 $this->output( "Completed ar_rev_id population, 0 rows updated.\n" );
62 return true;
63 }
64
65 $count = 0;
66 while ( true ) {
67 wfWaitForSlaves();
68
69 $arIds = $dbw->selectFieldValues(
70 'archive',
71 'ar_id',
72 [ 'ar_rev_id' => null ],
73 __METHOD__,
74 [ 'LIMIT' => $this->getBatchSize(), 'ORDER BY' => [ 'ar_id' ] ]
75 );
76 if ( !$arIds ) {
77 $this->output( "Completed ar_rev_id population, $count rows updated.\n" );
78 return true;
79 }
80
81 $count += self::reassignArRevIds( $dbw, $arIds, [ 'ar_rev_id' => null ] );
82
83 $min = min( $arIds );
84 $max = max( $arIds );
85 $this->output( " ... $min-$max\n" );
86 }
87 }
88
89 /**
90 * Assign new ar_rev_ids to a set of ar_ids.
91 * @param IDatabase $dbw
92 * @param int[] $arIds
93 * @param array $conds Extra conditions for the update
94 * @return int Number of updated rows
95 */
96 public static function reassignArRevIds( IDatabase $dbw, array $arIds, array $conds = [] ) {
97 if ( !self::$dummyRev ) {
98 self::$dummyRev = self::makeDummyRevisionRow( $dbw );
99 }
100
101 $updates = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $arIds ) {
102 // Create new rev_ids by inserting dummy rows into revision and then deleting them.
103 $dbw->insert( 'revision', array_fill( 0, count( $arIds ), self::$dummyRev ), $fname );
104 $revIds = $dbw->selectFieldValues(
105 'revision',
106 'rev_id',
107 [ 'rev_timestamp' => self::$dummyRev['rev_timestamp'] ],
108 $fname
109 );
110 if ( !is_array( $revIds ) ) {
111 throw new UnexpectedValueException( 'Failed to insert dummy revisions' );
112 }
113 if ( count( $revIds ) !== count( $arIds ) ) {
114 throw new UnexpectedValueException(
115 'Tried to insert ' . count( $arIds ) . ' dummy revisions, but found '
116 . count( $revIds ) . ' matching rows.'
117 );
118 }
119 $dbw->delete( 'revision', [ 'rev_id' => $revIds ], $fname );
120
121 return array_combine( $arIds, $revIds );
122 } );
123
124 $count = 0;
125 foreach ( $updates as $arId => $revId ) {
126 $dbw->update(
127 'archive',
128 [ 'ar_rev_id' => $revId ],
129 [ 'ar_id' => $arId ] + $conds,
130 __METHOD__
131 );
132 $count += $dbw->affectedRows();
133 }
134 return $count;
135 }
136
137 /**
138 * Construct a dummy revision table row to use for reserving IDs
139 *
140 * The row will have a wildly unlikely timestamp, and possibly a generic
141 * user and comment, but will otherwise be derived from a revision on the
142 * wiki's main page or some other revision in the database.
143 *
144 * @param IDatabase $dbw
145 * @return array
146 */
147 private static function makeDummyRevisionRow( IDatabase $dbw ) {
148 $ts = $dbw->timestamp( '11111111111111' );
149 $rev = null;
150
151 $mainPage = Title::newMainPage();
152 $pageId = $mainPage ? $mainPage->getArticleId() : null;
153 if ( $pageId ) {
154 $rev = $dbw->selectRow(
155 'revision',
156 '*',
157 [ 'rev_page' => $pageId ],
158 __METHOD__,
159 [ 'ORDER BY' => 'rev_timestamp ASC' ]
160 );
161 }
162
163 if ( !$rev ) {
164 // No main page? Let's see if there are any revisions at all
165 $rev = $dbw->selectRow(
166 'revision',
167 '*',
168 [],
169 __METHOD__,
170 [ 'ORDER BY' => 'rev_timestamp ASC' ]
171 );
172 }
173 if ( !$rev ) {
174 throw new UnexpectedValueException( 'No revisions are available to copy' );
175 }
176
177 unset( $rev->rev_id );
178 $rev = (array)$rev;
179 $rev['rev_timestamp'] = $ts;
180 if ( isset( $rev['rev_user'] ) ) {
181 $rev['rev_user'] = 0;
182 $rev['rev_user_text'] = '0.0.0.0';
183 }
184 if ( isset( $rev['rev_comment'] ) ) {
185 $rev['rev_comment'] = 'Dummy row';
186 }
187
188 $any = $dbw->selectField(
189 'revision',
190 'rev_id',
191 [ 'rev_timestamp' => $ts ],
192 __METHOD__
193 );
194 if ( $any ) {
195 throw new UnexpectedValueException( "... Why does your database contain a revision dated $ts?" );
196 }
197
198 return $rev;
199 }
200 }
201
202 $maintClass = "PopulateArchiveRevId";
203 require_once RUN_MAINTENANCE_IF_MAIN;