]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/installer/MysqlUpdater.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / installer / MysqlUpdater.php
1 <?php
2 /**
3  * MySQL-specific updater.
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 Deployment
22  */
23 use Wikimedia\Rdbms\Field;
24 use Wikimedia\Rdbms\MySQLField;
25 use MediaWiki\MediaWikiServices;
26
27 /**
28  * Mysql update list and mysql-specific update functions.
29  *
30  * @ingroup Deployment
31  * @since 1.17
32  */
33 class MysqlUpdater extends DatabaseUpdater {
34         protected function getCoreUpdateList() {
35                 return [
36                         [ 'disableContentHandlerUseDB' ],
37
38                         // 1.2
39                         [ 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ],
40                         [ 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ],
41                         [ 'doInterwikiUpdate' ],
42                         [ 'doIndexUpdate' ],
43                         [ 'addField', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ],
44                         [ 'addIndex', 'recentchanges', 'new_name_timestamp', 'patch-rc-newindex.sql' ],
45
46                         // 1.3
47                         [ 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ],
48                         [ 'addTable', 'querycache', 'patch-querycache.sql' ],
49                         [ 'addTable', 'objectcache', 'patch-objectcache.sql' ],
50                         [ 'addTable', 'categorylinks', 'patch-categorylinks.sql' ],
51                         [ 'doOldLinksUpdate' ],
52                         [ 'doFixAncientImagelinks' ],
53                         [ 'addField', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ],
54
55                         // 1.4
56                         [ 'addIndex', 'image', 'PRIMARY', 'patch-image_name_primary.sql' ],
57                         [ 'addField', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ],
58                         [ 'addField', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ],
59                         [ 'addTable', 'logging', 'patch-logging.sql' ],
60                         [ 'addField', 'user', 'user_token', 'patch-user_token.sql' ],
61                         [ 'addField', 'watchlist', 'wl_notificationtimestamp', 'patch-email-notification.sql' ],
62                         [ 'doWatchlistUpdate' ],
63                         [ 'dropField', 'user', 'user_emailauthenticationtimestamp',
64                                 'patch-email-authentication.sql' ],
65
66                         // 1.5
67                         [ 'doSchemaRestructuring' ],
68                         [ 'addField', 'logging', 'log_params', 'patch-log_params.sql' ],
69                         [ 'checkBin', 'logging', 'log_title', 'patch-logging-title.sql', ],
70                         [ 'addField', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ],
71                         [ 'addField', 'page', 'page_len', 'patch-page_len.sql' ],
72                         [ 'dropField', 'revision', 'inverse_timestamp', 'patch-inverse_timestamp.sql' ],
73                         [ 'addField', 'revision', 'rev_text_id', 'patch-rev_text_id.sql' ],
74                         [ 'addField', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ],
75                         [ 'addField', 'image', 'img_width', 'patch-img_width.sql' ],
76                         [ 'addField', 'image', 'img_metadata', 'patch-img_metadata.sql' ],
77                         [ 'addField', 'user', 'user_email_token', 'patch-user_email_token.sql' ],
78                         [ 'addField', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ],
79                         [ 'doNamespaceSize' ],
80                         [ 'addField', 'image', 'img_media_type', 'patch-img_media_type.sql' ],
81                         [ 'doPagelinksUpdate' ],
82                         [ 'dropField', 'image', 'img_type', 'patch-drop_img_type.sql' ],
83                         [ 'doUserUniqueUpdate' ],
84                         [ 'doUserGroupsUpdate' ],
85                         [ 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ],
86                         [ 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ],
87                         [ 'addTable', 'transcache', 'patch-transcache.sql' ],
88                         [ 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ],
89
90                         // 1.6
91                         [ 'doWatchlistNull' ],
92                         [ 'addIndex', 'logging', 'times', 'patch-logging-times-index.sql' ],
93                         [ 'addField', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ],
94                         [ 'doPageRandomUpdate' ],
95                         [ 'addField', 'user', 'user_registration', 'patch-user_registration.sql' ],
96                         [ 'doTemplatelinksUpdate' ],
97                         [ 'addTable', 'externallinks', 'patch-externallinks.sql' ],
98                         [ 'addTable', 'job', 'patch-job.sql' ],
99                         [ 'addField', 'site_stats', 'ss_images', 'patch-ss_images.sql' ],
100                         [ 'addTable', 'langlinks', 'patch-langlinks.sql' ],
101                         [ 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ],
102                         [ 'addTable', 'filearchive', 'patch-filearchive.sql' ],
103                         [ 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ],
104                         [ 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ],
105                         [ 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ],
106
107                         // 1.9
108                         [ 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ],
109                         [ 'addTable', 'redirect', 'patch-redirect.sql' ],
110                         [ 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ],
111                         [ 'addField', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ],
112                         [ 'doBacklinkingIndicesUpdate' ],
113                         [ 'addField', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ],
114                         [ 'addField', 'user', 'user_editcount', 'patch-user_editcount.sql' ],
115
116                         // 1.10
117                         [ 'doRestrictionsUpdate' ],
118                         [ 'addField', 'logging', 'log_id', 'patch-log_id.sql' ],
119                         [ 'addField', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ],
120                         [ 'addField', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ],
121                         [ 'addField', 'revision', 'rev_len', 'patch-rev_len.sql' ],
122                         [ 'addField', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ],
123                         [ 'addField', 'logging', 'log_deleted', 'patch-log_deleted.sql' ],
124                         [ 'addField', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ],
125                         [ 'addField', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ],
126                         [ 'addField', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ],
127                         [ 'addField', 'archive', 'ar_len', 'patch-ar_len.sql' ],
128
129                         // 1.11
130                         [ 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ],
131                         [ 'doCategorylinksIndicesUpdate' ],
132                         [ 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ],
133                         [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ],
134                         [ 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ],
135                         [ 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ],
136                         [ 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ],
137                         [ 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ],
138
139                         // 1.12
140                         [ 'addTable', 'protected_titles', 'patch-protected_titles.sql' ],
141
142                         // 1.13
143                         [ 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
144                         [ 'addTable', 'page_props', 'patch-page_props.sql' ],
145                         [ 'addTable', 'updatelog', 'patch-updatelog.sql' ],
146                         [ 'addTable', 'category', 'patch-category.sql' ],
147                         [ 'doCategoryPopulation' ],
148                         [ 'addField', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql' ],
149                         [ 'addField', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql' ],
150                         [ 'doPopulateParentId' ],
151                         [ 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ],
152                         [ 'doMaybeProfilingMemoryUpdate' ],
153                         [ 'doFilearchiveIndicesUpdate' ],
154
155                         // 1.14
156                         [ 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ],
157                         [ 'doActiveUsersInit' ],
158                         [ 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ],
159
160                         // 1.15
161                         [ 'addTable', 'change_tag', 'patch-change_tag.sql' ],
162                         [ 'addTable', 'tag_summary', 'patch-tag_summary.sql' ],
163                         [ 'addTable', 'valid_tag', 'patch-valid_tag.sql' ],
164
165                         // 1.16
166                         [ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
167                         [ 'addTable', 'log_search', 'patch-log_search.sql' ],
168                         [ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
169                         # listed separately from the previous update because 1.16 was released without this update
170                         [ 'doLogUsertextPopulation' ],
171                         [ 'doLogSearchPopulation' ],
172                         [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
173                         [ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ],
174                         [ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ],
175                         [ 'doUpdateTranscacheField' ],
176                         [ 'doUpdateMimeMinorField' ],
177
178                         // 1.17
179                         [ 'addTable', 'iwlinks', 'patch-iwlinks.sql' ],
180                         [ 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ],
181                         [ 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ],
182                         [ 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ],
183                         [ 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ],
184                         [ 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ],
185                         [ 'doClFieldsUpdate' ],
186                         [ 'addTable', 'module_deps', 'patch-module_deps.sql' ],
187                         [ 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ],
188                         [ 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ],
189                         [ 'doLangLinksLengthUpdate' ],
190
191                         // 1.18
192                         [ 'doUserNewTalkTimestampNotNull' ],
193                         [ 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ],
194                         [ 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ],
195                         [ 'addTable', 'uploadstash', 'patch-uploadstash.sql' ],
196                         [ 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ],
197
198                         // 1.19
199                         [ 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql' ],
200                         [ 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ],
201                         [ 'doMigrateUserOptions' ],
202                         [ 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ],
203                         [ 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ],
204                         [ 'addIndex', 'page', 'page_redirect_namespace_len',
205                                 'patch-page_redirect_namespace_len.sql' ],
206                         [ 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ],
207                         [ 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ],
208
209                         // 1.20
210                         [ 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ],
211                         [ 'addField', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id.sql' ],
212                         [ 'addIndex', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id-index.sql' ],
213                         [ 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ],
214
215                         // 1.21
216                         [ 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ],
217                         [ 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ],
218                         [ 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ],
219                         [ 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ],
220                         [ 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ],
221                         [ 'enableContentHandlerUseDB' ],
222                         [ 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ],
223                         [ 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ],
224                         [ 'addTable', 'sites', 'patch-sites.sql' ],
225                         [ 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ],
226                         [ 'addField', 'job', 'job_token', 'patch-job_token.sql' ],
227                         [ 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ],
228                         [ 'doEnableProfiling' ],
229                         [ 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ],
230                         [ 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ],
231                         [ 'modifyField', 'user_former_groups', 'ufg_group',
232                                 'patch-ufg_group-length-increase-255.sql' ],
233                         [ 'addIndex', 'page_props', 'pp_propname_page',
234                                 'patch-page_props-propname-page-index.sql' ],
235                         [ 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ],
236
237                         // 1.22
238                         [ 'doIwlinksIndexNonUnique' ],
239                         [ 'addIndex', 'iwlinks', 'iwl_prefix_from_title',
240                                 'patch-iwlinks-from-title-index.sql' ],
241                         [ 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ],
242                         [ 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ],
243
244                         // 1.23
245                         [ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
246                         [ 'addIndex', 'logging', 'log_user_text_type_time',
247                                 'patch-logging_user_text_type_time_index.sql' ],
248                         [ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
249                         [ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
250                         [ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
251
252                         // 1.24
253                         [ 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ],
254                         [ 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ],
255                         [ 'addIndex', 'watchlist', 'wl_user_notificationtimestamp',
256                                 'patch-watchlist-user-notificationtimestamp-index.sql' ],
257                         [ 'addField', 'page', 'page_lang', 'patch-page_lang.sql' ],
258                         [ 'addField', 'pagelinks', 'pl_from_namespace', 'patch-pl_from_namespace.sql' ],
259                         [ 'addField', 'templatelinks', 'tl_from_namespace', 'patch-tl_from_namespace.sql' ],
260                         [ 'addField', 'imagelinks', 'il_from_namespace', 'patch-il_from_namespace.sql' ],
261                         [ 'modifyField', 'image', 'img_major_mime',
262                                 'patch-img_major_mime-chemical.sql' ],
263                         [ 'modifyField', 'oldimage', 'oi_major_mime',
264                                 'patch-oi_major_mime-chemical.sql' ],
265                         [ 'modifyField', 'filearchive', 'fa_major_mime',
266                                 'patch-fa_major_mime-chemical.sql' ],
267
268                         // 1.25
269                         // note this patch covers other _comment and _description fields too
270                         [ 'doExtendCommentLengths' ],
271
272                         // 1.26
273                         [ 'dropTable', 'hitcounter' ],
274                         [ 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ],
275                         [ 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ],
276
277                         // 1.27
278                         [ 'dropTable', 'msg_resource_links' ],
279                         [ 'dropTable', 'msg_resource' ],
280                         [ 'addTable', 'bot_passwords', 'patch-bot_passwords.sql' ],
281                         [ 'addField', 'watchlist', 'wl_id', 'patch-watchlist-wl_id.sql' ],
282                         [ 'dropIndex', 'categorylinks', 'cl_collation', 'patch-kill-cl_collation_index.sql' ],
283                         [ 'addIndex', 'categorylinks', 'cl_collation_ext',
284                                 'patch-add-cl_collation_ext_index.sql' ],
285                         [ 'doCollationUpdate' ],
286
287                         // 1.28
288                         [ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
289                                 'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
290                         [ 'doRevisionPageRevIndexNonUnique' ],
291                         [ 'doNonUniquePlTlIl' ],
292                         [ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
293                         [ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
294                         [ 'modifyField', 'recentchanges', 'rc_ip', 'patch-rc_ip_modify.sql' ],
295                         [ 'addIndex', 'archive', 'usertext_timestamp', 'patch-rename-ar_usertext_timestamp.sql' ],
296
297                         // 1.29
298                         [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
299                         [ 'dropIndex', 'user_groups', 'ug_user_group', 'patch-user_groups-primary-key.sql' ],
300                         [ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
301                         [ 'addIndex', 'image', 'img_user_timestamp', 'patch-image-user-index-2.sql' ],
302
303                         // 1.30
304                         [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
305                         [ 'addTable', 'ip_changes', 'patch-ip_changes.sql' ],
306                         [ 'renameIndex', 'categorylinks', 'cl_from', 'PRIMARY', false,
307                                 'patch-categorylinks-fix-pk.sql' ],
308                         [ 'renameIndex', 'templatelinks', 'tl_from', 'PRIMARY', false,
309                                 'patch-templatelinks-fix-pk.sql' ],
310                         [ 'renameIndex', 'pagelinks', 'pl_from', 'PRIMARY', false, 'patch-pagelinks-fix-pk.sql' ],
311                         [ 'renameIndex', 'text', 'old_id', 'PRIMARY', false, 'patch-text-fix-pk.sql' ],
312                         [ 'renameIndex', 'imagelinks', 'il_from', 'PRIMARY', false, 'patch-imagelinks-fix-pk.sql' ],
313                         [ 'renameIndex', 'iwlinks', 'iwl_from', 'PRIMARY', false, 'patch-iwlinks-fix-pk.sql' ],
314                         [ 'renameIndex', 'langlinks', 'll_from', 'PRIMARY', false, 'patch-langlinks-fix-pk.sql' ],
315                         [ 'renameIndex', 'log_search', 'ls_field_val', 'PRIMARY', false, 'patch-log_search-fix-pk.sql' ],
316                         [ 'renameIndex', 'module_deps', 'md_module_skin', 'PRIMARY', false,
317                                 'patch-module_deps-fix-pk.sql' ],
318                         [ 'renameIndex', 'objectcache', 'keyname', 'PRIMARY', false, 'patch-objectcache-fix-pk.sql' ],
319                         [ 'renameIndex', 'querycache_info', 'qci_type', 'PRIMARY', false,
320                                 'patch-querycache_info-fix-pk.sql' ],
321                         [ 'renameIndex', 'site_stats', 'ss_row_id', 'PRIMARY', false, 'patch-site_stats-fix-pk.sql' ],
322                         [ 'renameIndex', 'transcache', 'tc_url_idx', 'PRIMARY', false, 'patch-transcache-fix-pk.sql' ],
323                         [ 'renameIndex', 'user_former_groups', 'ufg_user_group', 'PRIMARY', false,
324                                 'patch-user_former_groups-fix-pk.sql' ],
325                         [ 'renameIndex', 'user_properties', 'user_properties_user_property', 'PRIMARY', false,
326                                 'patch-user_properties-fix-pk.sql' ],
327                         [ 'addTable', 'comment', 'patch-comment-table.sql' ],
328                         [ 'migrateComments' ],
329                         [ 'renameIndex', 'l10n_cache', 'lc_lang_key', 'PRIMARY', false,
330                                 'patch-l10n_cache-primary-key.sql' ],
331                         [ 'doUnsignedSyncronisation' ],
332                 ];
333         }
334
335         /**
336          * 1.4 betas were missing the 'binary' marker from logging.log_title,
337          * which causes a collation mismatch error on joins in MySQL 4.1.
338          *
339          * @param string $table Table name
340          * @param string $field Field name to check
341          * @param string $patchFile Path to the patch to correct the field
342          * @return bool
343          */
344         protected function checkBin( $table, $field, $patchFile ) {
345                 if ( !$this->doTable( $table ) ) {
346                         return true;
347                 }
348
349                 /** @var MySQLField $fieldInfo */
350                 $fieldInfo = $this->db->fieldInfo( $table, $field );
351                 if ( $fieldInfo->isBinary() ) {
352                         $this->output( "...$table table has correct $field encoding.\n" );
353                 } else {
354                         $this->applyPatch( $patchFile, false, "Fixing $field encoding on $table table" );
355                 }
356         }
357
358         /**
359          * Check whether an index contain a field
360          *
361          * @param string $table Table name
362          * @param string $index Index name to check
363          * @param string $field Field that should be in the index
364          * @return bool
365          */
366         protected function indexHasField( $table, $index, $field ) {
367                 if ( !$this->doTable( $table ) ) {
368                         return true;
369                 }
370
371                 $info = $this->db->indexInfo( $table, $index, __METHOD__ );
372                 if ( $info ) {
373                         foreach ( $info as $row ) {
374                                 if ( $row->Column_name == $field ) {
375                                         $this->output( "...index $index on table $table includes field $field.\n" );
376
377                                         return true;
378                                 }
379                         }
380                 }
381                 $this->output( "...index $index on table $table has no field $field; added.\n" );
382
383                 return false;
384         }
385
386         /**
387          * Check that interwiki table exists; if it doesn't source it
388          */
389         protected function doInterwikiUpdate() {
390                 global $IP;
391
392                 if ( !$this->doTable( 'interwiki' ) ) {
393                         return true;
394                 }
395
396                 if ( $this->db->tableExists( "interwiki", __METHOD__ ) ) {
397                         $this->output( "...already have interwiki table\n" );
398
399                         return;
400                 }
401
402                 $this->applyPatch( 'patch-interwiki.sql', false, 'Creating interwiki table' );
403                 $this->applyPatch(
404                         "$IP/maintenance/interwiki.sql",
405                         true,
406                         'Adding default interwiki definitions'
407                 );
408         }
409
410         /**
411          * Check that proper indexes are in place
412          */
413         protected function doIndexUpdate() {
414                 $meta = $this->db->fieldInfo( 'recentchanges', 'rc_timestamp' );
415                 if ( $meta === false ) {
416                         throw new MWException( 'Missing rc_timestamp field of recentchanges table. Should not happen.' );
417                 }
418                 if ( $meta->isMultipleKey() ) {
419                         $this->output( "...indexes seem up to 20031107 standards.\n" );
420
421                         return;
422                 }
423
424                 $this->applyPatch( 'patch-indexes.sql', true, "Updating indexes to 20031107" );
425         }
426
427         protected function doOldLinksUpdate() {
428                 $cl = $this->maintenance->runChild( 'ConvertLinks' );
429                 $cl->execute();
430         }
431
432         protected function doFixAncientImagelinks() {
433                 $info = $this->db->fieldInfo( 'imagelinks', 'il_from' );
434                 if ( !$info || $info->type() !== 'string' ) {
435                         $this->output( "...il_from OK\n" );
436
437                         return;
438                 }
439
440                 $applied = $this->applyPatch(
441                         'patch-fix-il_from.sql',
442                         false,
443                         'Fixing ancient broken imagelinks table.'
444                 );
445
446                 if ( $applied ) {
447                         $this->output( "NOTE: you will have to run maintenance/refreshLinks.php after this." );
448                 }
449         }
450
451         /**
452          * Check if we need to add talk page rows to the watchlist
453          */
454         function doWatchlistUpdate() {
455                 $talk = $this->db->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', __METHOD__ );
456                 $nontalk = $this->db->selectField(
457                         'watchlist',
458                         'count(*)',
459                         'NOT (wl_namespace & 1)',
460                         __METHOD__
461                 );
462                 if ( $talk == $nontalk ) {
463                         $this->output( "...watchlist talk page rows already present.\n" );
464
465                         return;
466                 }
467
468                 $this->output( "Adding missing watchlist talk page rows... " );
469                 $this->db->insertSelect( 'watchlist', 'watchlist',
470                         [
471                                 'wl_user' => 'wl_user',
472                                 'wl_namespace' => 'wl_namespace | 1',
473                                 'wl_title' => 'wl_title',
474                                 'wl_notificationtimestamp' => 'wl_notificationtimestamp'
475                         ], [ 'NOT (wl_namespace & 1)' ], __METHOD__, 'IGNORE' );
476                 $this->output( "done.\n" );
477
478                 $this->output( "Adding missing watchlist subject page rows... " );
479                 $this->db->insertSelect( 'watchlist', 'watchlist',
480                         [
481                                 'wl_user' => 'wl_user',
482                                 'wl_namespace' => 'wl_namespace & ~1',
483                                 'wl_title' => 'wl_title',
484                                 'wl_notificationtimestamp' => 'wl_notificationtimestamp'
485                         ], [ 'wl_namespace & 1' ], __METHOD__, 'IGNORE' );
486                 $this->output( "done.\n" );
487         }
488
489         function doSchemaRestructuring() {
490                 if ( $this->db->tableExists( 'page', __METHOD__ ) ) {
491                         $this->output( "...page table already exists.\n" );
492
493                         return;
494                 }
495
496                 $this->output( "...converting from cur/old to page/revision/text DB structure.\n" );
497                 $this->output( wfTimestamp( TS_DB ) );
498                 $this->output( "......checking for duplicate entries.\n" );
499
500                 list( $cur, $old, $page, $revision, $text ) = $this->db->tableNamesN(
501                         'cur',
502                         'old',
503                         'page',
504                         'revision',
505                         'text'
506                 );
507
508                 $rows = $this->db->query( "
509                         SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
510                         FROM $cur
511                         GROUP BY cur_title, cur_namespace
512                         HAVING c>1",
513                         __METHOD__
514                 );
515
516                 if ( $rows->numRows() > 0 ) {
517                         $this->output( wfTimestamp( TS_DB ) );
518                         $this->output( "......<b>Found duplicate entries</b>\n" );
519                         $this->output( sprintf( "<b>      %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
520                         $duplicate = [];
521                         foreach ( $rows as $row ) {
522                                 if ( !isset( $duplicate[$row->cur_namespace] ) ) {
523                                         $duplicate[$row->cur_namespace] = [];
524                                 }
525
526                                 $duplicate[$row->cur_namespace][] = $row->cur_title;
527                                 $this->output( sprintf(
528                                         "      %-60s %3s %5s\n",
529                                         $row->cur_title, $row->cur_namespace,
530                                         $row->c
531                                 ) );
532                         }
533                         $sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
534                         $firstCond = true;
535                         foreach ( $duplicate as $ns => $titles ) {
536                                 if ( $firstCond ) {
537                                         $firstCond = false;
538                                 } else {
539                                         $sql .= ' OR ';
540                                 }
541                                 $sql .= "( cur_namespace = {$ns} AND cur_title in (";
542                                 $first = true;
543                                 foreach ( $titles as $t ) {
544                                         if ( $first ) {
545                                                 $sql .= $this->db->addQuotes( $t );
546                                                 $first = false;
547                                         } else {
548                                                 $sql .= ', ' . $this->db->addQuotes( $t );
549                                         }
550                                 }
551                                 $sql .= ") ) \n";
552                         }
553                         # By sorting descending, the most recent entry will be the first in the list.
554                         # All following entries will be deleted by the next while-loop.
555                         $sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC';
556
557                         $rows = $this->db->query( $sql, __METHOD__ );
558
559                         $prev_title = $prev_namespace = false;
560                         $deleteId = [];
561
562                         foreach ( $rows as $row ) {
563                                 if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
564                                         $deleteId[] = $row->cur_id;
565                                 }
566                                 $prev_title = $row->cur_title;
567                                 $prev_namespace = $row->cur_namespace;
568                         }
569                         $sql = "DELETE FROM $cur WHERE cur_id IN ( " . implode( ',', $deleteId ) . ')';
570                         $this->db->query( $sql, __METHOD__ );
571                         $this->output( wfTimestamp( TS_DB ) );
572                         $this->output( "......<b>Deleted</b> " . $this->db->affectedRows() . " records.\n" );
573                 }
574
575                 $this->output( wfTimestamp( TS_DB ) );
576                 $this->output( "......Creating tables.\n" );
577                 $this->db->query( "CREATE TABLE $page (
578                         page_id int(8) unsigned NOT NULL auto_increment,
579                         page_namespace int NOT NULL,
580                         page_title varchar(255) binary NOT NULL,
581                         page_restrictions tinyblob NOT NULL,
582                         page_is_redirect tinyint(1) unsigned NOT NULL default '0',
583                         page_is_new tinyint(1) unsigned NOT NULL default '0',
584                         page_random real unsigned NOT NULL,
585                         page_touched char(14) binary NOT NULL default '',
586                         page_latest int(8) unsigned NOT NULL,
587                         page_len int(8) unsigned NOT NULL,
588
589                         PRIMARY KEY page_id (page_id),
590                         UNIQUE INDEX name_title (page_namespace,page_title),
591                         INDEX (page_random),
592                         INDEX (page_len)
593                         ) ENGINE=InnoDB", __METHOD__ );
594                 $this->db->query( "CREATE TABLE $revision (
595                         rev_id int(8) unsigned NOT NULL auto_increment,
596                         rev_page int(8) unsigned NOT NULL,
597                         rev_comment tinyblob NOT NULL,
598                         rev_user int(5) unsigned NOT NULL default '0',
599                         rev_user_text varchar(255) binary NOT NULL default '',
600                         rev_timestamp char(14) binary NOT NULL default '',
601                         rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
602                         rev_deleted tinyint(1) unsigned NOT NULL default '0',
603                         rev_len int(8) unsigned,
604                         rev_parent_id int(8) unsigned default NULL,
605                         PRIMARY KEY rev_page_id (rev_page, rev_id),
606                         UNIQUE INDEX rev_id (rev_id),
607                         INDEX rev_timestamp (rev_timestamp),
608                         INDEX page_timestamp (rev_page,rev_timestamp),
609                         INDEX user_timestamp (rev_user,rev_timestamp),
610                         INDEX usertext_timestamp (rev_user_text,rev_timestamp)
611                         ) ENGINE=InnoDB", __METHOD__ );
612
613                 $this->output( wfTimestamp( TS_DB ) );
614                 $this->output( "......Locking tables.\n" );
615                 $this->db->query(
616                         "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE",
617                         __METHOD__
618                 );
619
620                 $maxold = intval( $this->db->selectField( 'old', 'max(old_id)', '', __METHOD__ ) );
621                 $this->output( wfTimestamp( TS_DB ) );
622                 $this->output( "......maxold is {$maxold}\n" );
623
624                 $this->output( wfTimestamp( TS_DB ) );
625                 global $wgLegacySchemaConversion;
626                 if ( $wgLegacySchemaConversion ) {
627                         // Create HistoryBlobCurStub entries.
628                         // Text will be pulled from the leftover 'cur' table at runtime.
629                         $this->output( "......Moving metadata from cur; using blob references to text in cur table.\n" );
630                         $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')";
631                         $cur_flags = "'object'";
632                 } else {
633                         // Copy all cur text in immediately: this may take longer but avoids
634                         // having to keep an extra table around.
635                         $this->output( "......Moving text from cur.\n" );
636                         $cur_text = 'cur_text';
637                         $cur_flags = "''";
638                 }
639                 $this->db->query(
640                         "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user,
641                                 old_user_text, old_timestamp, old_minor_edit, old_flags)
642                         SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text,
643                                 cur_timestamp, cur_minor_edit, $cur_flags
644                         FROM $cur",
645                         __METHOD__
646                 );
647
648                 $this->output( wfTimestamp( TS_DB ) );
649                 $this->output( "......Setting up revision table.\n" );
650                 $this->db->query(
651                         "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user,
652                                 rev_user_text, rev_timestamp, rev_minor_edit)
653                         SELECT old_id, cur_id, old_comment, old_user, old_user_text,
654                                 old_timestamp, old_minor_edit
655                         FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title",
656                         __METHOD__
657                 );
658
659                 $this->output( wfTimestamp( TS_DB ) );
660                 $this->output( "......Setting up page table.\n" );
661                 $this->db->query(
662                         "INSERT INTO $page (page_id, page_namespace, page_title,
663                                 page_restrictions, page_is_redirect, page_is_new, page_random,
664                                 page_touched, page_latest, page_len)
665                         SELECT cur_id, cur_namespace, cur_title, cur_restrictions,
666                                 cur_is_redirect, cur_is_new, cur_random, cur_touched, rev_id, LENGTH(cur_text)
667                         FROM $cur,$revision
668                         WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}",
669                         __METHOD__
670                 );
671
672                 $this->output( wfTimestamp( TS_DB ) );
673                 $this->output( "......Unlocking tables.\n" );
674                 $this->db->query( "UNLOCK TABLES", __METHOD__ );
675
676                 $this->output( wfTimestamp( TS_DB ) );
677                 $this->output( "......Renaming old.\n" );
678                 $this->db->query( "ALTER TABLE $old RENAME TO $text", __METHOD__ );
679
680                 $this->output( wfTimestamp( TS_DB ) );
681                 $this->output( "...done.\n" );
682         }
683
684         protected function doNamespaceSize() {
685                 $tables = [
686                         'page' => 'page',
687                         'archive' => 'ar',
688                         'recentchanges' => 'rc',
689                         'watchlist' => 'wl',
690                         'querycache' => 'qc',
691                         'logging' => 'log',
692                 ];
693                 foreach ( $tables as $table => $prefix ) {
694                         $field = $prefix . '_namespace';
695
696                         $tablename = $this->db->tableName( $table );
697                         $result = $this->db->query( "SHOW COLUMNS FROM $tablename LIKE '$field'", __METHOD__ );
698                         $info = $this->db->fetchObject( $result );
699
700                         if ( substr( $info->Type, 0, 3 ) == 'int' ) {
701                                 $this->output( "...$field is already a full int ($info->Type).\n" );
702                         } else {
703                                 $this->output( "Promoting $field from $info->Type to int... " );
704                                 $this->db->query( "ALTER TABLE $tablename MODIFY $field int NOT NULL", __METHOD__ );
705                                 $this->output( "done.\n" );
706                         }
707                 }
708         }
709
710         protected function doPagelinksUpdate() {
711                 if ( $this->db->tableExists( 'pagelinks', __METHOD__ ) ) {
712                         $this->output( "...already have pagelinks table.\n" );
713
714                         return;
715                 }
716
717                 $this->applyPatch(
718                         'patch-pagelinks.sql',
719                         false,
720                         'Converting links and brokenlinks tables to pagelinks'
721                 );
722
723                 global $wgContLang;
724                 foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
725                         if ( $ns == 0 ) {
726                                 continue;
727                         }
728
729                         $this->output( "Cleaning up broken links for namespace $ns... " );
730                         $this->db->update( 'pagelinks',
731                                 [
732                                         'pl_namespace' => $ns,
733                                         "pl_title = TRIM(LEADING {$this->db->addQuotes( "$name:" )} FROM pl_title)",
734                                 ],
735                                 [
736                                         'pl_namespace' => 0,
737                                         'pl_title' . $this->db->buildLike( "$name:", $this->db->anyString() ),
738                                 ],
739                                 __METHOD__
740                         );
741                         $this->output( "done.\n" );
742                 }
743         }
744
745         protected function doUserUniqueUpdate() {
746                 if ( !$this->doTable( 'user' ) ) {
747                         return true;
748                 }
749
750                 $duper = new UserDupes( $this->db, [ $this, 'output' ] );
751                 if ( $duper->hasUniqueIndex() ) {
752                         $this->output( "...already have unique user_name index.\n" );
753
754                         return;
755                 }
756
757                 if ( !$duper->clearDupes() ) {
758                         $this->output( "WARNING: This next step will probably fail due to unfixed duplicates...\n" );
759                 }
760                 $this->applyPatch( 'patch-user_nameindex.sql', false, "Adding unique index on user_name" );
761         }
762
763         protected function doUserGroupsUpdate() {
764                 if ( !$this->doTable( 'user_groups' ) ) {
765                         return true;
766                 }
767
768                 if ( $this->db->tableExists( 'user_groups', __METHOD__ ) ) {
769                         $info = $this->db->fieldInfo( 'user_groups', 'ug_group' );
770                         if ( $info->type() == 'int' ) {
771                                 $oldug = $this->db->tableName( 'user_groups' );
772                                 $newug = $this->db->tableName( 'user_groups_bogus' );
773                                 $this->output( "user_groups table exists but is in bogus intermediate " .
774                                         "format. Renaming to $newug... " );
775                                 $this->db->query( "ALTER TABLE $oldug RENAME TO $newug", __METHOD__ );
776                                 $this->output( "done.\n" );
777
778                                 $this->applyPatch( 'patch-user_groups.sql', false, "Re-adding fresh user_groups table" );
779
780                                 $this->output( "***\n" );
781                                 $this->output( "*** WARNING: You will need to manually fix up user " .
782                                         "permissions in the user_groups\n" );
783                                 $this->output( "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n" );
784                                 $this->output( "***\n" );
785                         } else {
786                                 $this->output( "...user_groups table exists and is in current format.\n" );
787                         }
788
789                         return;
790                 }
791
792                 $this->applyPatch( 'patch-user_groups.sql', false, "Adding user_groups table" );
793
794                 if ( !$this->db->tableExists( 'user_rights', __METHOD__ ) ) {
795                         if ( $this->db->fieldExists( 'user', 'user_rights', __METHOD__ ) ) {
796                                 $this->applyPatch(
797                                         'patch-user_rights.sql',
798                                         false,
799                                         'Upgrading from a 1.3 or older database? Breaking out user_rights for conversion'
800                                 );
801                         } else {
802                                 $this->output( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
803                                 $this->output( "*** You may need to manually configure some sysops by manipulating\n" );
804                                 $this->output( "*** the user_groups table.\n" );
805
806                                 return;
807                         }
808                 }
809
810                 $this->output( "Converting user_rights table to user_groups... " );
811                 $result = $this->db->select( 'user_rights',
812                         [ 'ur_user', 'ur_rights' ],
813                         [ "ur_rights != ''" ],
814                         __METHOD__ );
815
816                 foreach ( $result as $row ) {
817                         $groups = array_unique(
818                                 array_map( 'trim',
819                                         explode( ',', $row->ur_rights ) ) );
820
821                         foreach ( $groups as $group ) {
822                                 $this->db->insert( 'user_groups',
823                                         [
824                                                 'ug_user' => $row->ur_user,
825                                                 'ug_group' => $group ],
826                                         __METHOD__ );
827                         }
828                 }
829                 $this->output( "done.\n" );
830         }
831
832         /**
833          * Make sure wl_notificationtimestamp can be NULL,
834          * and update old broken items.
835          */
836         protected function doWatchlistNull() {
837                 $info = $this->db->fieldInfo( 'watchlist', 'wl_notificationtimestamp' );
838                 if ( !$info ) {
839                         return;
840                 }
841                 if ( $info->isNullable() ) {
842                         $this->output( "...wl_notificationtimestamp is already nullable.\n" );
843
844                         return;
845                 }
846
847                 $this->applyPatch(
848                         'patch-watchlist-null.sql',
849                         false,
850                         'Making wl_notificationtimestamp nullable'
851                 );
852         }
853
854         /**
855          * Set page_random field to a random value where it is equals to 0.
856          *
857          * @see T5946
858          */
859         protected function doPageRandomUpdate() {
860                 $page = $this->db->tableName( 'page' );
861                 $this->db->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", __METHOD__ );
862                 $rows = $this->db->affectedRows();
863
864                 if ( $rows ) {
865                         $this->output( "Set page_random to a random value on $rows rows where it was set to 0\n" );
866                 } else {
867                         $this->output( "...no page_random rows needed to be set\n" );
868                 }
869         }
870
871         protected function doTemplatelinksUpdate() {
872                 if ( $this->db->tableExists( 'templatelinks', __METHOD__ ) ) {
873                         $this->output( "...templatelinks table already exists\n" );
874
875                         return;
876                 }
877
878                 $this->applyPatch( 'patch-templatelinks.sql', false, "Creating templatelinks table" );
879
880                 $this->output( "Populating...\n" );
881                 if ( wfGetLB()->getServerCount() > 1 ) {
882                         // Slow, replication-friendly update
883                         $res = $this->db->select( 'pagelinks', [ 'pl_from', 'pl_namespace', 'pl_title' ],
884                                 [ 'pl_namespace' => NS_TEMPLATE ], __METHOD__ );
885                         $count = 0;
886                         foreach ( $res as $row ) {
887                                 $count = ( $count + 1 ) % 100;
888                                 if ( $count == 0 ) {
889                                         $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
890                                         $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
891                                 }
892                                 $this->db->insert( 'templatelinks',
893                                         [
894                                                 'tl_from' => $row->pl_from,
895                                                 'tl_namespace' => $row->pl_namespace,
896                                                 'tl_title' => $row->pl_title,
897                                         ], __METHOD__
898                                 );
899                         }
900                 } else {
901                         // Fast update
902                         $this->db->insertSelect( 'templatelinks', 'pagelinks',
903                                 [
904                                         'tl_from' => 'pl_from',
905                                         'tl_namespace' => 'pl_namespace',
906                                         'tl_title' => 'pl_title'
907                                 ], [
908                                         'pl_namespace' => 10
909                                 ], __METHOD__
910                         );
911                 }
912                 $this->output( "Done. Please run maintenance/refreshLinks.php for a more " .
913                         "thorough templatelinks update.\n" );
914         }
915
916         protected function doBacklinkingIndicesUpdate() {
917                 if ( !$this->indexHasField( 'pagelinks', 'pl_namespace', 'pl_from' ) ||
918                         !$this->indexHasField( 'templatelinks', 'tl_namespace', 'tl_from' ) ||
919                         !$this->indexHasField( 'imagelinks', 'il_to', 'il_from' )
920                 ) {
921                         $this->applyPatch( 'patch-backlinkindexes.sql', false, "Updating backlinking indices" );
922                 }
923         }
924
925         /**
926          * Adding page_restrictions table, obsoleting page.page_restrictions.
927          * Migrating old restrictions to new table
928          * -- Andrew Garrett, January 2007.
929          */
930         protected function doRestrictionsUpdate() {
931                 if ( $this->db->tableExists( 'page_restrictions', __METHOD__ ) ) {
932                         $this->output( "...page_restrictions table already exists.\n" );
933
934                         return;
935                 }
936
937                 $this->applyPatch(
938                         'patch-page_restrictions.sql',
939                         false,
940                         'Creating page_restrictions table (1/2)'
941                 );
942                 $this->applyPatch(
943                         'patch-page_restrictions_sortkey.sql',
944                         false,
945                         'Creating page_restrictions table (2/2)'
946                 );
947                 $this->output( "done.\n" );
948
949                 $this->output( "Migrating old restrictions to new table...\n" );
950                 $task = $this->maintenance->runChild( 'UpdateRestrictions' );
951                 $task->execute();
952         }
953
954         protected function doCategorylinksIndicesUpdate() {
955                 if ( !$this->indexHasField( 'categorylinks', 'cl_sortkey', 'cl_from' ) ) {
956                         $this->applyPatch( 'patch-categorylinksindex.sql', false, "Updating categorylinks Indices" );
957                 }
958         }
959
960         protected function doCategoryPopulation() {
961                 if ( $this->updateRowExists( 'populate category' ) ) {
962                         $this->output( "...category table already populated.\n" );
963
964                         return;
965                 }
966
967                 $this->output(
968                         "Populating category table, printing progress markers. " .
969                         "For large databases, you\n" .
970                         "may want to hit Ctrl-C and do this manually with maintenance/\n" .
971                         "populateCategory.php.\n"
972                 );
973                 $task = $this->maintenance->runChild( 'PopulateCategory' );
974                 $task->execute();
975                 $this->output( "Done populating category table.\n" );
976         }
977
978         protected function doPopulateParentId() {
979                 if ( !$this->updateRowExists( 'populate rev_parent_id' ) ) {
980                         $this->output(
981                                 "Populating rev_parent_id fields, printing progress markers. For large\n" .
982                                 "databases, you may want to hit Ctrl-C and do this manually with\n" .
983                                 "maintenance/populateParentId.php.\n" );
984
985                         $task = $this->maintenance->runChild( 'PopulateParentId' );
986                         $task->execute();
987                 }
988         }
989
990         protected function doMaybeProfilingMemoryUpdate() {
991                 if ( !$this->doTable( 'profiling' ) ) {
992                         return true;
993                 }
994
995                 if ( !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
996                         return true;
997                 } elseif ( $this->db->fieldExists( 'profiling', 'pf_memory', __METHOD__ ) ) {
998                         $this->output( "...profiling table has pf_memory field.\n" );
999
1000                         return true;
1001                 }
1002
1003                 return $this->applyPatch(
1004                         'patch-profiling-memory.sql',
1005                         false,
1006                         'Adding pf_memory field to table profiling'
1007                 );
1008         }
1009
1010         protected function doFilearchiveIndicesUpdate() {
1011                 $info = $this->db->indexInfo( 'filearchive', 'fa_user_timestamp', __METHOD__ );
1012                 if ( !$info ) {
1013                         $this->applyPatch( 'patch-filearchive-user-index.sql', false, "Updating filearchive indices" );
1014                 }
1015
1016                 return true;
1017         }
1018
1019         protected function doNonUniquePlTlIl() {
1020                 $info = $this->db->indexInfo( 'pagelinks', 'pl_namespace' );
1021                 if ( is_array( $info ) && $info[0]->Non_unique ) {
1022                         $this->output( "...pl_namespace, tl_namespace, il_to indices are already non-UNIQUE.\n" );
1023
1024                         return true;
1025                 }
1026                 if ( $this->skipSchema ) {
1027                         $this->output( "...skipping schema change (making pl_namespace, tl_namespace " .
1028                                 "and il_to indices non-UNIQUE).\n" );
1029
1030                         return false;
1031                 }
1032
1033                 return $this->applyPatch(
1034                         'patch-pl-tl-il-nonunique.sql',
1035                         false,
1036                         'Making pl_namespace, tl_namespace and il_to indices non-UNIQUE'
1037                 );
1038         }
1039
1040         protected function doUpdateMimeMinorField() {
1041                 if ( $this->updateRowExists( 'mime_minor_length' ) ) {
1042                         $this->output( "...*_mime_minor fields are already long enough.\n" );
1043
1044                         return;
1045                 }
1046
1047                 $this->applyPatch(
1048                         'patch-mime_minor_length.sql',
1049                         false,
1050                         'Altering all *_mime_minor fields to 100 bytes in size'
1051                 );
1052         }
1053
1054         protected function doClFieldsUpdate() {
1055                 if ( $this->updateRowExists( 'cl_fields_update' ) ) {
1056                         $this->output( "...categorylinks up-to-date.\n" );
1057
1058                         return;
1059                 }
1060
1061                 $this->applyPatch(
1062                         'patch-categorylinks-better-collation2.sql',
1063                         false,
1064                         'Updating categorylinks (again)'
1065                 );
1066         }
1067
1068         protected function doLangLinksLengthUpdate() {
1069                 $langlinks = $this->db->tableName( 'langlinks' );
1070                 $res = $this->db->query( "SHOW COLUMNS FROM $langlinks LIKE 'll_lang'" );
1071                 $row = $this->db->fetchObject( $res );
1072
1073                 if ( $row && $row->Type == "varbinary(10)" ) {
1074                         $this->applyPatch(
1075                                 'patch-langlinks-ll_lang-20.sql',
1076                                 false,
1077                                 'Updating length of ll_lang in langlinks'
1078                         );
1079                 } else {
1080                         $this->output( "...ll_lang is up-to-date.\n" );
1081                 }
1082         }
1083
1084         protected function doUserNewTalkTimestampNotNull() {
1085                 if ( !$this->doTable( 'user_newtalk' ) ) {
1086                         return true;
1087                 }
1088
1089                 $info = $this->db->fieldInfo( 'user_newtalk', 'user_last_timestamp' );
1090                 if ( $info === false ) {
1091                         return;
1092                 }
1093                 if ( $info->isNullable() ) {
1094                         $this->output( "...user_last_timestamp is already nullable.\n" );
1095
1096                         return;
1097                 }
1098
1099                 $this->applyPatch(
1100                         'patch-user-newtalk-timestamp-null.sql',
1101                         false,
1102                         'Making user_last_timestamp nullable'
1103                 );
1104         }
1105
1106         protected function doIwlinksIndexNonUnique() {
1107                 $info = $this->db->indexInfo( 'iwlinks', 'iwl_prefix_title_from' );
1108                 if ( is_array( $info ) && $info[0]->Non_unique ) {
1109                         $this->output( "...iwl_prefix_title_from index is already non-UNIQUE.\n" );
1110
1111                         return true;
1112                 }
1113                 if ( $this->skipSchema ) {
1114                         $this->output( "...skipping schema change (making iwl_prefix_title_from index non-UNIQUE).\n" );
1115
1116                         return false;
1117                 }
1118
1119                 return $this->applyPatch(
1120                         'patch-iwl_prefix_title_from-non-unique.sql',
1121                         false,
1122                         'Making iwl_prefix_title_from index non-UNIQUE'
1123                 );
1124         }
1125
1126         protected function doUnsignedSyncronisation() {
1127                 $sync = [
1128                         [ 'table' => 'bot_passwords', 'field' => 'bp_user' ],
1129                         [ 'table' => 'change_tag', 'field' => 'ct_log_id' ],
1130                         [ 'table' => 'change_tag', 'field' => 'ct_rev_id' ],
1131                         [ 'table' => 'page_restrictions', 'field' => 'pr_user' ],
1132                         [ 'table' => 'tag_summary', 'field' => 'ts_log_id' ],
1133                         [ 'table' => 'tag_summary', 'field' => 'ts_rev_id' ],
1134                         [ 'table' => 'user_newtalk', 'field' => 'user_id' ],
1135                         [ 'table' => 'user_properties', 'field' => 'up_user' ],
1136                 ];
1137
1138                 foreach ( $sync as $s ) {
1139                         if ( !$this->doTable( $s['table'] ) ) {
1140                                 continue;
1141                         }
1142
1143                         $info = $this->db->fieldInfo( $s['table'], $s['field'] );
1144                         if ( $info === false ) {
1145                                 continue;
1146                         }
1147                         $fullName = "{$s['table']}.{$s['field']}";
1148                         if ( $info->isUnsigned() ) {
1149                                 $this->output( "...$fullName is already unsigned int.\n" );
1150
1151                                 continue;
1152                         }
1153
1154                         $this->applyPatch(
1155                                 "patch-{$s['table']}-{$s['field']}-unsigned.sql",
1156                                 false,
1157                                 "Making $fullName into an unsigned int"
1158                         );
1159                 }
1160
1161                 return true;
1162         }
1163
1164         protected function doRevisionPageRevIndexNonUnique() {
1165                 if ( !$this->doTable( 'revision' ) ) {
1166                         return true;
1167                 } elseif ( !$this->db->indexExists( 'revision', 'rev_page_id' ) ) {
1168                         $this->output( "...rev_page_id index not found on revision.\n" );
1169                         return true;
1170                 }
1171
1172                 if ( !$this->db->indexUnique( 'revision', 'rev_page_id' ) ) {
1173                         $this->output( "...rev_page_id index already non-unique.\n" );
1174                         return true;
1175                 }
1176
1177                 return $this->applyPatch(
1178                         'patch-revision-page-rev-index-nonunique.sql',
1179                         false,
1180                         'Making rev_page_id index non-unique'
1181                 );
1182         }
1183
1184         protected function doExtendCommentLengths() {
1185                 $table = $this->db->tableName( 'revision' );
1186                 $res = $this->db->query( "SHOW COLUMNS FROM $table LIKE 'rev_comment'" );
1187                 $row = $this->db->fetchObject( $res );
1188
1189                 if ( $row && ( $row->Type !== "varbinary(767)" || $row->Default !== "" ) ) {
1190                         $this->applyPatch(
1191                                 'patch-editsummary-length.sql',
1192                                 false,
1193                                 'Extending edit summary lengths (and setting defaults)'
1194                         );
1195                 } else {
1196                         $this->output( '...comment fields are up to date' );
1197                 }
1198         }
1199
1200         public function getSchemaVars() {
1201                 global $wgDBTableOptions;
1202
1203                 $vars = [];
1204                 $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $wgDBTableOptions );
1205                 $vars['wgDBTableOptions'] = str_replace(
1206                         'CHARSET=mysql4',
1207                         'CHARSET=binary',
1208                         $vars['wgDBTableOptions']
1209                 );
1210
1211                 return $vars;
1212         }
1213 }