migrateComments.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. /**
  3. * Migrate comments from pre-1.30 columns to the 'comment' table
  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. use Wikimedia\Rdbms\IDatabase;
  24. require_once __DIR__ . '/Maintenance.php';
  25. /**
  26. * Maintenance script that migrates comments from pre-1.30 columns to the
  27. * 'comment' table
  28. *
  29. * @ingroup Maintenance
  30. */
  31. class MigrateComments extends LoggedUpdateMaintenance {
  32. public function __construct() {
  33. parent::__construct();
  34. $this->addDescription( 'Migrates comments from pre-1.30 columns to the \'comment\' table' );
  35. $this->setBatchSize( 100 );
  36. }
  37. protected function getUpdateKey() {
  38. return __CLASS__;
  39. }
  40. protected function updateSkippedMessage() {
  41. return 'comments already migrated.';
  42. }
  43. protected function doDBUpdates() {
  44. global $wgCommentTableSchemaMigrationStage;
  45. if ( $wgCommentTableSchemaMigrationStage < MIGRATION_WRITE_NEW ) {
  46. $this->output(
  47. "...cannot update while \$wgCommentTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
  48. );
  49. return false;
  50. }
  51. $this->migrateToTemp(
  52. 'revision', 'rev_id', 'rev_comment', 'revcomment_rev', 'revcomment_comment_id'
  53. );
  54. $this->migrate( 'archive', 'ar_id', 'ar_comment' );
  55. $this->migrate( 'ipblocks', 'ipb_id', 'ipb_reason' );
  56. $this->migrateToTemp(
  57. 'image', 'img_name', 'img_description', 'imgcomment_name', 'imgcomment_description_id'
  58. );
  59. $this->migrate( 'oldimage', [ 'oi_name', 'oi_timestamp' ], 'oi_description' );
  60. $this->migrate( 'filearchive', 'fa_id', 'fa_deleted_reason' );
  61. $this->migrate( 'filearchive', 'fa_id', 'fa_description' );
  62. $this->migrate( 'recentchanges', 'rc_id', 'rc_comment' );
  63. $this->migrate( 'logging', 'log_id', 'log_comment' );
  64. $this->migrate( 'protected_titles', [ 'pt_namespace', 'pt_title' ], 'pt_reason' );
  65. return true;
  66. }
  67. /**
  68. * Fetch comment IDs for a set of comments
  69. * @param IDatabase $dbw
  70. * @param array &$comments Keys are comment names, values will be set to IDs.
  71. * @return int Count of added comments
  72. */
  73. private function loadCommentIDs( IDatabase $dbw, array &$comments ) {
  74. $count = 0;
  75. $needComments = $comments;
  76. while ( true ) {
  77. $where = [];
  78. foreach ( $needComments as $need => $dummy ) {
  79. $where[] = $dbw->makeList(
  80. [
  81. 'comment_hash' => CommentStore::hash( $need, null ),
  82. 'comment_text' => $need,
  83. ],
  84. LIST_AND
  85. );
  86. }
  87. $res = $dbw->select(
  88. 'comment',
  89. [ 'comment_id', 'comment_text' ],
  90. [
  91. $dbw->makeList( $where, LIST_OR ),
  92. 'comment_data' => null,
  93. ],
  94. __METHOD__
  95. );
  96. foreach ( $res as $row ) {
  97. $comments[$row->comment_text] = $row->comment_id;
  98. unset( $needComments[$row->comment_text] );
  99. }
  100. if ( !$needComments ) {
  101. break;
  102. }
  103. $dbw->insert(
  104. 'comment',
  105. array_map( function ( $v ) {
  106. return [
  107. 'comment_hash' => CommentStore::hash( $v, null ),
  108. 'comment_text' => $v,
  109. ];
  110. }, array_keys( $needComments ) ),
  111. __METHOD__
  112. );
  113. $count += $dbw->affectedRows();
  114. }
  115. return $count;
  116. }
  117. /**
  118. * Migrate comments in a table.
  119. *
  120. * Assumes any row with the ID field non-zero have already been migrated.
  121. * Assumes the new field name is the same as the old with '_id' appended.
  122. * Blanks the old fields while migrating.
  123. *
  124. * @param string $table Table to migrate
  125. * @param string|string[] $primaryKey Primary key of the table.
  126. * @param string $oldField Old comment field name
  127. */
  128. protected function migrate( $table, $primaryKey, $oldField ) {
  129. $newField = $oldField . '_id';
  130. $primaryKey = (array)$primaryKey;
  131. $pkFilter = array_flip( $primaryKey );
  132. $this->output( "Beginning migration of $table.$oldField to $table.$newField\n" );
  133. wfWaitForSlaves();
  134. $dbw = $this->getDB( DB_MASTER );
  135. $next = '1=1';
  136. $countUpdated = 0;
  137. $countComments = 0;
  138. while ( true ) {
  139. // Fetch the rows needing update
  140. $res = $dbw->select(
  141. $table,
  142. array_merge( $primaryKey, [ $oldField ] ),
  143. [
  144. $newField => 0,
  145. $next,
  146. ],
  147. __METHOD__,
  148. [
  149. 'ORDER BY' => $primaryKey,
  150. 'LIMIT' => $this->getBatchSize(),
  151. ]
  152. );
  153. if ( !$res->numRows() ) {
  154. break;
  155. }
  156. // Collect the distinct comments from those rows
  157. $comments = [];
  158. foreach ( $res as $row ) {
  159. $comments[$row->$oldField] = 0;
  160. }
  161. $countComments += $this->loadCommentIDs( $dbw, $comments );
  162. // Update the existing rows
  163. foreach ( $res as $row ) {
  164. $dbw->update(
  165. $table,
  166. [
  167. $newField => $comments[$row->$oldField],
  168. $oldField => '',
  169. ],
  170. array_intersect_key( (array)$row, $pkFilter ) + [
  171. $newField => 0
  172. ],
  173. __METHOD__
  174. );
  175. $countUpdated += $dbw->affectedRows();
  176. }
  177. // Calculate the "next" condition
  178. $next = '';
  179. $prompt = [];
  180. for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
  181. $field = $primaryKey[$i];
  182. $prompt[] = $row->$field;
  183. $value = $dbw->addQuotes( $row->$field );
  184. if ( $next === '' ) {
  185. $next = "$field > $value";
  186. } else {
  187. $next = "$field > $value OR $field = $value AND ($next)";
  188. }
  189. }
  190. $prompt = implode( ' ', array_reverse( $prompt ) );
  191. $this->output( "... $prompt\n" );
  192. wfWaitForSlaves();
  193. }
  194. $this->output(
  195. "Completed migration, updated $countUpdated row(s) with $countComments new comment(s)\n"
  196. );
  197. }
  198. /**
  199. * Migrate comments in a table to a temporary table.
  200. *
  201. * Assumes any row with the ID field non-zero have already been migrated.
  202. * Assumes the new table is named "{$table}_comment_temp", and it has two
  203. * columns, in order, being the primary key of the original table and the
  204. * comment ID field.
  205. * Blanks the old fields while migrating.
  206. *
  207. * @param string $table Table to migrate
  208. * @param string $primaryKey Primary key of the table.
  209. * @param string $oldField Old comment field name
  210. * @param string $newPrimaryKey Primary key of the new table.
  211. * @param string $newField New comment field name
  212. */
  213. protected function migrateToTemp( $table, $primaryKey, $oldField, $newPrimaryKey, $newField ) {
  214. $newTable = $table . '_comment_temp';
  215. $this->output( "Beginning migration of $table.$oldField to $newTable.$newField\n" );
  216. wfWaitForSlaves();
  217. $dbw = $this->getDB( DB_MASTER );
  218. $next = [];
  219. $countUpdated = 0;
  220. $countComments = 0;
  221. while ( true ) {
  222. // Fetch the rows needing update
  223. $res = $dbw->select(
  224. [ $table, $newTable ],
  225. [ $primaryKey, $oldField ],
  226. [ $newPrimaryKey => null ] + $next,
  227. __METHOD__,
  228. [
  229. 'ORDER BY' => $primaryKey,
  230. 'LIMIT' => $this->getBatchSize(),
  231. ],
  232. [ $newTable => [ 'LEFT JOIN', "{$primaryKey}={$newPrimaryKey}" ] ]
  233. );
  234. if ( !$res->numRows() ) {
  235. break;
  236. }
  237. // Collect the distinct comments from those rows
  238. $comments = [];
  239. foreach ( $res as $row ) {
  240. $comments[$row->$oldField] = 0;
  241. }
  242. $countComments += $this->loadCommentIDs( $dbw, $comments );
  243. // Update rows
  244. $inserts = [];
  245. $updates = [];
  246. foreach ( $res as $row ) {
  247. $inserts[] = [
  248. $newPrimaryKey => $row->$primaryKey,
  249. $newField => $comments[$row->$oldField]
  250. ];
  251. $updates[] = $row->$primaryKey;
  252. }
  253. $this->beginTransaction( $dbw, __METHOD__ );
  254. $dbw->insert( $newTable, $inserts, __METHOD__ );
  255. $dbw->update( $table, [ $oldField => '' ], [ $primaryKey => $updates ], __METHOD__ );
  256. $countUpdated += $dbw->affectedRows();
  257. $this->commitTransaction( $dbw, __METHOD__ );
  258. // Calculate the "next" condition
  259. $next = [ $primaryKey . ' > ' . $dbw->addQuotes( $row->$primaryKey ) ];
  260. $this->output( "... {$row->$primaryKey}\n" );
  261. }
  262. $this->output(
  263. "Completed migration, updated $countUpdated row(s) with $countComments new comment(s)\n"
  264. );
  265. }
  266. }
  267. $maintClass = MigrateComments::class;
  268. require_once RUN_MAINTENANCE_IF_MAIN;