PostgresUpdater.php 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181
  1. <?php
  2. /**
  3. * PostgreSQL-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\DatabasePostgres;
  24. /**
  25. * Class for handling updates to Postgres databases.
  26. *
  27. * @ingroup Deployment
  28. * @since 1.17
  29. */
  30. class PostgresUpdater extends DatabaseUpdater {
  31. /**
  32. * @var DatabasePostgres
  33. */
  34. protected $db;
  35. /**
  36. * @todo FIXME: Postgres should use sequential updates like Mysql, Sqlite
  37. * and everybody else. It never got refactored like it should've.
  38. * @return array
  39. */
  40. protected function getCoreUpdateList() {
  41. return [
  42. # rename tables 1.7.3
  43. # r15791 Change reserved word table names "user" and "text"
  44. [ 'renameTable', 'user', 'mwuser' ],
  45. [ 'renameTable', 'text', 'pagecontent' ],
  46. [ 'renameIndex', 'mwuser', 'user_pkey', 'mwuser_pkey' ],
  47. [ 'renameIndex', 'mwuser', 'user_user_name_key', 'mwuser_user_name_key' ],
  48. [ 'renameIndex', 'pagecontent', 'text_pkey', 'pagecontent_pkey' ],
  49. # renamed sequences
  50. [ 'renameSequence', 'ipblocks_ipb_id_val', 'ipblocks_ipb_id_seq' ],
  51. [ 'renameSequence', 'rev_rev_id_val', 'revision_rev_id_seq' ],
  52. [ 'renameSequence', 'text_old_id_val', 'text_old_id_seq' ],
  53. [ 'renameSequence', 'rc_rc_id_seq', 'recentchanges_rc_id_seq' ],
  54. [ 'renameSequence', 'log_log_id_seq', 'logging_log_id_seq' ],
  55. [ 'renameSequence', 'pr_id_val', 'page_restrictions_pr_id_seq' ],
  56. [ 'renameSequence', 'us_id_seq', 'uploadstash_us_id_seq' ],
  57. # since r58263
  58. [ 'renameSequence', 'category_id_seq', 'category_cat_id_seq' ],
  59. # new sequences if not renamed above
  60. [ 'addSequence', 'logging', false, 'logging_log_id_seq' ],
  61. [ 'addSequence', 'page_restrictions', false, 'page_restrictions_pr_id_seq' ],
  62. [ 'addSequence', 'filearchive', 'fa_id', 'filearchive_fa_id_seq' ],
  63. [ 'addSequence', 'archive', false, 'archive_ar_id_seq' ],
  64. [ 'addSequence', 'externallinks', false, 'externallinks_el_id_seq' ],
  65. [ 'addSequence', 'watchlist', false, 'watchlist_wl_id_seq' ],
  66. [ 'addSequence', 'change_tag', false, 'change_tag_ct_id_seq' ],
  67. [ 'addSequence', 'tag_summary', false, 'tag_summary_ts_id_seq' ],
  68. # new tables
  69. [ 'addTable', 'category', 'patch-category.sql' ],
  70. [ 'addTable', 'page', 'patch-page.sql' ],
  71. [ 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ],
  72. [ 'addTable', 'page_props', 'patch-page_props.sql' ],
  73. [ 'addTable', 'page_restrictions', 'patch-page_restrictions.sql' ],
  74. [ 'addTable', 'profiling', 'patch-profiling.sql' ],
  75. [ 'addTable', 'protected_titles', 'patch-protected_titles.sql' ],
  76. [ 'addTable', 'redirect', 'patch-redirect.sql' ],
  77. [ 'addTable', 'updatelog', 'patch-updatelog.sql' ],
  78. [ 'addTable', 'change_tag', 'patch-change_tag.sql' ],
  79. [ 'addTable', 'tag_summary', 'patch-tag_summary.sql' ],
  80. [ 'addTable', 'valid_tag', 'patch-valid_tag.sql' ],
  81. [ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
  82. [ 'addTable', 'log_search', 'patch-log_search.sql' ],
  83. [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
  84. [ 'addTable', 'iwlinks', 'patch-iwlinks.sql' ],
  85. [ 'addTable', 'module_deps', 'patch-module_deps.sql' ],
  86. [ 'addTable', 'uploadstash', 'patch-uploadstash.sql' ],
  87. [ 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ],
  88. [ 'addTable', 'sites', 'patch-sites.sql' ],
  89. [ 'addTable', 'bot_passwords', 'patch-bot_passwords.sql' ],
  90. # Needed before new field
  91. [ 'convertArchive2' ],
  92. # new fields
  93. [ 'addPgField', 'updatelog', 'ul_value', 'TEXT' ],
  94. [ 'addPgField', 'archive', 'ar_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  95. [ 'addPgField', 'archive', 'ar_len', 'INTEGER' ],
  96. [ 'addPgField', 'archive', 'ar_page_id', 'INTEGER' ],
  97. [ 'addPgField', 'archive', 'ar_parent_id', 'INTEGER' ],
  98. [ 'addPgField', 'archive', 'ar_content_model', 'TEXT' ],
  99. [ 'addPgField', 'archive', 'ar_content_format', 'TEXT' ],
  100. [ 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''" ],
  101. [ 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0" ],
  102. [ 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'" ],
  103. [ 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ],
  104. [ 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ],
  105. [ 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ],
  106. [ 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ],
  107. [ 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ],
  108. [ 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ],
  109. [ 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  110. [ 'addPgField', 'ipblocks', 'ipb_enable_autoblock', 'SMALLINT NOT NULL DEFAULT 1' ],
  111. [ 'addPgField', 'ipblocks', 'ipb_parent_block_id',
  112. 'INTEGER DEFAULT NULL REFERENCES ipblocks(ipb_id) ' .
  113. 'ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED' ],
  114. [ 'addPgField', 'filearchive', 'fa_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  115. [ 'addPgField', 'filearchive', 'fa_sha1', "TEXT NOT NULL DEFAULT ''" ],
  116. [ 'addPgField', 'logging', 'log_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  117. [ 'addPgField', 'logging', 'log_id',
  118. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq')" ],
  119. [ 'addPgField', 'logging', 'log_params', 'TEXT' ],
  120. [ 'addPgField', 'mwuser', 'user_editcount', 'INTEGER' ],
  121. [ 'addPgField', 'mwuser', 'user_newpass_time', 'TIMESTAMPTZ' ],
  122. [ 'addPgField', 'oldimage', 'oi_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  123. [ 'addPgField', 'oldimage', 'oi_major_mime', "TEXT NOT NULL DEFAULT 'unknown'" ],
  124. [ 'addPgField', 'oldimage', 'oi_media_type', 'TEXT' ],
  125. [ 'addPgField', 'oldimage', 'oi_metadata', "BYTEA NOT NULL DEFAULT ''" ],
  126. [ 'addPgField', 'oldimage', 'oi_minor_mime', "TEXT NOT NULL DEFAULT 'unknown'" ],
  127. [ 'addPgField', 'oldimage', 'oi_sha1', "TEXT NOT NULL DEFAULT ''" ],
  128. [ 'addPgField', 'page', 'page_content_model', 'TEXT' ],
  129. [ 'addPgField', 'page_restrictions', 'pr_id',
  130. "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq')" ],
  131. [ 'addPgField', 'profiling', 'pf_memory', 'NUMERIC(18,10) NOT NULL DEFAULT 0' ],
  132. [ 'addPgField', 'recentchanges', 'rc_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  133. [ 'addPgField', 'recentchanges', 'rc_log_action', 'TEXT' ],
  134. [ 'addPgField', 'recentchanges', 'rc_log_type', 'TEXT' ],
  135. [ 'addPgField', 'recentchanges', 'rc_logid', 'INTEGER NOT NULL DEFAULT 0' ],
  136. [ 'addPgField', 'recentchanges', 'rc_new_len', 'INTEGER' ],
  137. [ 'addPgField', 'recentchanges', 'rc_old_len', 'INTEGER' ],
  138. [ 'addPgField', 'recentchanges', 'rc_params', 'TEXT' ],
  139. [ 'addPgField', 'redirect', 'rd_interwiki', 'TEXT NULL' ],
  140. [ 'addPgField', 'redirect', 'rd_fragment', 'TEXT NULL' ],
  141. [ 'addPgField', 'revision', 'rev_deleted', 'SMALLINT NOT NULL DEFAULT 0' ],
  142. [ 'addPgField', 'revision', 'rev_len', 'INTEGER' ],
  143. [ 'addPgField', 'revision', 'rev_parent_id', 'INTEGER DEFAULT NULL' ],
  144. [ 'addPgField', 'revision', 'rev_content_model', 'TEXT' ],
  145. [ 'addPgField', 'revision', 'rev_content_format', 'TEXT' ],
  146. [ 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ],
  147. [ 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ],
  148. [ 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ],
  149. [ 'addPgField', 'logging', 'log_page', 'INTEGER' ],
  150. [ 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''" ],
  151. [ 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''" ],
  152. [ 'addPgField', 'revision', 'rev_sha1', "TEXT NOT NULL DEFAULT ''" ],
  153. [ 'addPgField', 'archive', 'ar_sha1', "TEXT NOT NULL DEFAULT ''" ],
  154. [ 'addPgField', 'uploadstash', 'us_chunk_inx', "INTEGER NULL" ],
  155. [ 'addPgField', 'job', 'job_timestamp', "TIMESTAMPTZ" ],
  156. [ 'addPgField', 'job', 'job_random', "INTEGER NOT NULL DEFAULT 0" ],
  157. [ 'addPgField', 'job', 'job_attempts', "INTEGER NOT NULL DEFAULT 0" ],
  158. [ 'addPgField', 'job', 'job_token', "TEXT NOT NULL DEFAULT ''" ],
  159. [ 'addPgField', 'job', 'job_token_timestamp', "TIMESTAMPTZ" ],
  160. [ 'addPgField', 'job', 'job_sha1', "TEXT NOT NULL DEFAULT ''" ],
  161. [ 'addPgField', 'archive', 'ar_id',
  162. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('archive_ar_id_seq')" ],
  163. [ 'addPgField', 'externallinks', 'el_id',
  164. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('externallinks_el_id_seq')" ],
  165. [ 'addPgField', 'uploadstash', 'us_props', "BYTEA" ],
  166. # type changes
  167. [ 'changeField', 'archive', 'ar_deleted', 'smallint', '' ],
  168. [ 'changeField', 'archive', 'ar_minor_edit', 'smallint',
  169. 'ar_minor_edit::smallint DEFAULT 0' ],
  170. [ 'changeField', 'filearchive', 'fa_deleted', 'smallint', '' ],
  171. [ 'changeField', 'filearchive', 'fa_height', 'integer', '' ],
  172. [ 'changeField', 'filearchive', 'fa_metadata', 'bytea', "decode(fa_metadata,'escape')" ],
  173. [ 'changeField', 'filearchive', 'fa_size', 'integer', '' ],
  174. [ 'changeField', 'filearchive', 'fa_width', 'integer', '' ],
  175. [ 'changeField', 'filearchive', 'fa_storage_group', 'text', '' ],
  176. [ 'changeField', 'filearchive', 'fa_storage_key', 'text', '' ],
  177. [ 'changeField', 'image', 'img_metadata', 'bytea', "decode(img_metadata,'escape')" ],
  178. [ 'changeField', 'image', 'img_size', 'integer', '' ],
  179. [ 'changeField', 'image', 'img_width', 'integer', '' ],
  180. [ 'changeField', 'image', 'img_height', 'integer', '' ],
  181. [ 'changeField', 'interwiki', 'iw_local', 'smallint', 'iw_local::smallint' ],
  182. [ 'changeField', 'interwiki', 'iw_trans', 'smallint', 'iw_trans::smallint DEFAULT 0' ],
  183. [ 'changeField', 'ipblocks', 'ipb_auto', 'smallint', 'ipb_auto::smallint DEFAULT 0' ],
  184. [ 'changeField', 'ipblocks', 'ipb_anon_only', 'smallint',
  185. "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0" ],
  186. [ 'changeField', 'ipblocks', 'ipb_create_account', 'smallint',
  187. "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1" ],
  188. [ 'changeField', 'ipblocks', 'ipb_enable_autoblock', 'smallint',
  189. "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1" ],
  190. [ 'changeField', 'ipblocks', 'ipb_block_email', 'smallint',
  191. "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0" ],
  192. [ 'changeField', 'ipblocks', 'ipb_address', 'text', 'ipb_address::text' ],
  193. [ 'changeField', 'ipblocks', 'ipb_deleted', 'smallint', 'ipb_deleted::smallint DEFAULT 0' ],
  194. [ 'changeField', 'mwuser', 'user_token', 'text', '' ],
  195. [ 'changeField', 'mwuser', 'user_email_token', 'text', '' ],
  196. [ 'changeField', 'objectcache', 'keyname', 'text', '' ],
  197. [ 'changeField', 'oldimage', 'oi_height', 'integer', '' ],
  198. [ 'changeField', 'oldimage', 'oi_metadata', 'bytea', "decode(img_metadata,'escape')" ],
  199. [ 'changeField', 'oldimage', 'oi_size', 'integer', '' ],
  200. [ 'changeField', 'oldimage', 'oi_width', 'integer', '' ],
  201. [ 'changeField', 'page', 'page_is_redirect', 'smallint',
  202. 'page_is_redirect::smallint DEFAULT 0' ],
  203. [ 'changeField', 'page', 'page_is_new', 'smallint', 'page_is_new::smallint DEFAULT 0' ],
  204. [ 'changeField', 'querycache', 'qc_value', 'integer', '' ],
  205. [ 'changeField', 'querycachetwo', 'qcc_value', 'integer', '' ],
  206. [ 'changeField', 'recentchanges', 'rc_bot', 'smallint', 'rc_bot::smallint DEFAULT 0' ],
  207. [ 'changeField', 'recentchanges', 'rc_deleted', 'smallint', '' ],
  208. [ 'changeField', 'recentchanges', 'rc_minor', 'smallint', 'rc_minor::smallint DEFAULT 0' ],
  209. [ 'changeField', 'recentchanges', 'rc_new', 'smallint', 'rc_new::smallint DEFAULT 0' ],
  210. [ 'changeField', 'recentchanges', 'rc_type', 'smallint', 'rc_type::smallint DEFAULT 0' ],
  211. [ 'changeField', 'recentchanges', 'rc_patrolled', 'smallint',
  212. 'rc_patrolled::smallint DEFAULT 0' ],
  213. [ 'changeField', 'revision', 'rev_deleted', 'smallint', 'rev_deleted::smallint DEFAULT 0' ],
  214. [ 'changeField', 'revision', 'rev_minor_edit', 'smallint',
  215. 'rev_minor_edit::smallint DEFAULT 0' ],
  216. [ 'changeField', 'templatelinks', 'tl_namespace', 'smallint', 'tl_namespace::smallint' ],
  217. [ 'changeField', 'user_newtalk', 'user_ip', 'text', 'host(user_ip)' ],
  218. [ 'changeField', 'uploadstash', 'us_image_bits', 'smallint', '' ],
  219. [ 'changeField', 'profiling', 'pf_time', 'float', '' ],
  220. [ 'changeField', 'profiling', 'pf_memory', 'float', '' ],
  221. # null changes
  222. [ 'changeNullableField', 'oldimage', 'oi_bits', 'NULL' ],
  223. [ 'changeNullableField', 'oldimage', 'oi_timestamp', 'NULL' ],
  224. [ 'changeNullableField', 'oldimage', 'oi_major_mime', 'NULL' ],
  225. [ 'changeNullableField', 'oldimage', 'oi_minor_mime', 'NULL' ],
  226. [ 'changeNullableField', 'image', 'img_metadata', 'NOT NULL' ],
  227. [ 'changeNullableField', 'filearchive', 'fa_metadata', 'NOT NULL' ],
  228. [ 'changeNullableField', 'recentchanges', 'rc_cur_id', 'NULL' ],
  229. [ 'changeNullableField', 'recentchanges', 'rc_cur_time', 'NULL' ],
  230. [ 'checkOiDeleted' ],
  231. # New indexes
  232. [ 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ],
  233. [ 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ],
  234. [ 'addPgIndex', 'ipblocks', 'ipb_parent_block_id', '(ipb_parent_block_id)' ],
  235. [ 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ],
  236. [ 'addPgIndex', 'page', 'page_mediawiki_title', '(page_title) WHERE page_namespace = 8' ],
  237. [ 'addPgIndex', 'pagelinks', 'pagelinks_title', '(pl_title)' ],
  238. [ 'addPgIndex', 'page_props', 'pp_propname_page', '(pp_propname, pp_page)' ],
  239. [ 'addPgIndex', 'revision', 'rev_text_id_idx', '(rev_text_id)' ],
  240. [ 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ],
  241. [ 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ],
  242. [ 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ],
  243. [ 'addPgIndex', 'watchlist', 'wl_user_notificationtimestamp',
  244. '(wl_user, wl_notificationtimestamp)' ],
  245. [ 'addPgIndex', 'logging', 'logging_user_type_time',
  246. '(log_user, log_type, log_timestamp)' ],
  247. [ 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ],
  248. [ 'addPgIndex', 'iwlinks', 'iwl_prefix_from_title', '(iwl_prefix, iwl_from, iwl_title)' ],
  249. [ 'addPgIndex', 'iwlinks', 'iwl_prefix_title_from', '(iwl_prefix, iwl_title, iwl_from)' ],
  250. [ 'addPgIndex', 'job', 'job_timestamp_idx', '(job_timestamp)' ],
  251. [ 'addPgIndex', 'job', 'job_sha1', '(job_sha1)' ],
  252. [ 'addPgIndex', 'job', 'job_cmd_token', '(job_cmd, job_token, job_random)' ],
  253. [ 'addPgIndex', 'job', 'job_cmd_token_id', '(job_cmd, job_token, job_id)' ],
  254. [ 'addPgIndex', 'filearchive', 'fa_sha1', '(fa_sha1)' ],
  255. [ 'addPgIndex', 'logging', 'logging_user_text_type_time',
  256. '(log_user_text, log_type, log_timestamp)' ],
  257. [ 'addPgIndex', 'logging', 'logging_user_text_time', '(log_user_text, log_timestamp)' ],
  258. [ 'checkIndex', 'pagelink_unique', [
  259. [ 'pl_from', 'int4_ops', 'btree', 0 ],
  260. [ 'pl_namespace', 'int2_ops', 'btree', 0 ],
  261. [ 'pl_title', 'text_ops', 'btree', 0 ],
  262. ],
  263. 'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' ],
  264. [ 'checkIndex', 'cl_sortkey', [
  265. [ 'cl_to', 'text_ops', 'btree', 0 ],
  266. [ 'cl_sortkey', 'text_ops', 'btree', 0 ],
  267. [ 'cl_from', 'int4_ops', 'btree', 0 ],
  268. ],
  269. 'CREATE INDEX cl_sortkey ON "categorylinks" ' .
  270. 'USING "btree" ("cl_to", "cl_sortkey", "cl_from")' ],
  271. [ 'checkIndex', 'iwl_prefix_title_from', [
  272. [ 'iwl_prefix', 'text_ops', 'btree', 0 ],
  273. [ 'iwl_title', 'text_ops', 'btree', 0 ],
  274. [ 'iwl_from', 'int4_ops', 'btree', 0 ],
  275. ],
  276. 'CREATE INDEX iwl_prefix_title_from ON "iwlinks" ' .
  277. 'USING "btree" ("iwl_prefix", "iwl_title", "iwl_from")' ],
  278. [ 'checkIndex', 'logging_times', [
  279. [ 'log_timestamp', 'timestamptz_ops', 'btree', 0 ],
  280. ],
  281. 'CREATE INDEX "logging_times" ON "logging" USING "btree" ("log_timestamp")' ],
  282. [ 'dropPgIndex', 'oldimage', 'oi_name' ],
  283. [ 'checkIndex', 'oi_name_archive_name', [
  284. [ 'oi_name', 'text_ops', 'btree', 0 ],
  285. [ 'oi_archive_name', 'text_ops', 'btree', 0 ],
  286. ],
  287. 'CREATE INDEX "oi_name_archive_name" ON "oldimage" ' .
  288. 'USING "btree" ("oi_name", "oi_archive_name")' ],
  289. [ 'checkIndex', 'oi_name_timestamp', [
  290. [ 'oi_name', 'text_ops', 'btree', 0 ],
  291. [ 'oi_timestamp', 'timestamptz_ops', 'btree', 0 ],
  292. ],
  293. 'CREATE INDEX "oi_name_timestamp" ON "oldimage" ' .
  294. 'USING "btree" ("oi_name", "oi_timestamp")' ],
  295. [ 'checkIndex', 'page_main_title', [
  296. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  297. ],
  298. 'CREATE INDEX "page_main_title" ON "page" ' .
  299. 'USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 0)' ],
  300. [ 'checkIndex', 'page_mediawiki_title', [
  301. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  302. ],
  303. 'CREATE INDEX "page_mediawiki_title" ON "page" ' .
  304. 'USING "btree" ("page_title" "text_pattern_ops") WHERE ("page_namespace" = 8)' ],
  305. [ 'checkIndex', 'page_project_title', [
  306. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  307. ],
  308. 'CREATE INDEX "page_project_title" ON "page" ' .
  309. 'USING "btree" ("page_title" "text_pattern_ops") ' .
  310. 'WHERE ("page_namespace" = 4)' ],
  311. [ 'checkIndex', 'page_talk_title', [
  312. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  313. ],
  314. 'CREATE INDEX "page_talk_title" ON "page" ' .
  315. 'USING "btree" ("page_title" "text_pattern_ops") ' .
  316. 'WHERE ("page_namespace" = 1)' ],
  317. [ 'checkIndex', 'page_user_title', [
  318. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  319. ],
  320. 'CREATE INDEX "page_user_title" ON "page" ' .
  321. 'USING "btree" ("page_title" "text_pattern_ops") WHERE ' .
  322. '("page_namespace" = 2)' ],
  323. [ 'checkIndex', 'page_utalk_title', [
  324. [ 'page_title', 'text_pattern_ops', 'btree', 0 ],
  325. ],
  326. 'CREATE INDEX "page_utalk_title" ON "page" ' .
  327. 'USING "btree" ("page_title" "text_pattern_ops") ' .
  328. 'WHERE ("page_namespace" = 3)' ],
  329. [ 'checkIndex', 'ts2_page_text', [
  330. [ 'textvector', 'tsvector_ops', 'gist', 0 ],
  331. ],
  332. 'CREATE INDEX "ts2_page_text" ON "pagecontent" USING "gist" ("textvector")' ],
  333. [ 'checkIndex', 'ts2_page_title', [
  334. [ 'titlevector', 'tsvector_ops', 'gist', 0 ],
  335. ],
  336. 'CREATE INDEX "ts2_page_title" ON "page" USING "gist" ("titlevector")' ],
  337. [ 'checkOiNameConstraint' ],
  338. [ 'checkPageDeletedTrigger' ],
  339. [ 'checkRevUserFkey' ],
  340. [ 'dropPgIndex', 'ipblocks', 'ipb_address' ],
  341. [ 'checkIndex', 'ipb_address_unique', [
  342. [ 'ipb_address', 'text_ops', 'btree', 0 ],
  343. [ 'ipb_user', 'int4_ops', 'btree', 0 ],
  344. [ 'ipb_auto', 'int2_ops', 'btree', 0 ],
  345. [ 'ipb_anon_only', 'int2_ops', 'btree', 0 ],
  346. ],
  347. 'CREATE UNIQUE INDEX ipb_address_unique ' .
  348. 'ON ipblocks (ipb_address,ipb_user,ipb_auto,ipb_anon_only)' ],
  349. [ 'checkIwlPrefix' ],
  350. # All FK columns should be deferred
  351. [ 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  352. [ 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ],
  353. [ 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ],
  354. [ 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user',
  355. 'mwuser(user_id) ON DELETE SET NULL' ],
  356. [ 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  357. [ 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  358. [ 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ],
  359. [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ],
  360. [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  361. [ 'changeFkeyDeferrable', 'ipblocks', 'ipb_parent_block_id',
  362. 'ipblocks(ipb_id) ON DELETE SET NULL' ],
  363. [ 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ],
  364. [ 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  365. [ 'changeFkeyDeferrable', 'oldimage', 'oi_name',
  366. 'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ],
  367. [ 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ],
  368. [ 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ],
  369. [ 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ],
  370. [ 'changeFkeyDeferrable', 'page_restrictions', 'pr_page',
  371. 'page(page_id) ON DELETE CASCADE' ],
  372. [ 'changeFkeyDeferrable', 'protected_titles', 'pt_user',
  373. 'mwuser(user_id) ON DELETE SET NULL' ],
  374. [ 'changeFkeyDeferrable', 'recentchanges', 'rc_user',
  375. 'mwuser(user_id) ON DELETE SET NULL' ],
  376. [ 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ],
  377. [ 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ],
  378. [ 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ],
  379. [ 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ],
  380. [ 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ],
  381. [ 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ],
  382. [ 'changeFkeyDeferrable', 'user_properties', 'up_user',
  383. 'mwuser(user_id) ON DELETE CASCADE' ],
  384. [ 'changeFkeyDeferrable', 'watchlist', 'wl_user', 'mwuser(user_id) ON DELETE CASCADE' ],
  385. # r81574
  386. [ 'addInterwikiType' ],
  387. # end
  388. [ 'tsearchFixes' ],
  389. // 1.23
  390. [ 'addPgField', 'recentchanges', 'rc_source', "TEXT NOT NULL DEFAULT ''" ],
  391. [ 'addPgField', 'page', 'page_links_updated', "TIMESTAMPTZ NULL" ],
  392. [ 'addPgField', 'mwuser', 'user_password_expires', 'TIMESTAMPTZ NULL' ],
  393. [ 'changeFieldPurgeTable', 'l10n_cache', 'lc_value', 'bytea',
  394. "replace(lc_value,'\','\\\\')::bytea" ],
  395. // 1.23.9
  396. [ 'rebuildTextSearch' ],
  397. // 1.24
  398. [ 'addPgField', 'page_props', 'pp_sortkey', 'float NULL' ],
  399. [ 'addPgIndex', 'page_props', 'pp_propname_sortkey_page',
  400. '( pp_propname, pp_sortkey, pp_page ) WHERE ( pp_sortkey IS NOT NULL )' ],
  401. [ 'addPgField', 'page', 'page_lang', 'TEXT default NULL' ],
  402. [ 'addPgField', 'pagelinks', 'pl_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ],
  403. [ 'addPgField', 'templatelinks', 'tl_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ],
  404. [ 'addPgField', 'imagelinks', 'il_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ],
  405. // 1.25
  406. [ 'dropTable', 'hitcounter' ],
  407. [ 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ],
  408. [ 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ],
  409. [ 'dropFkey', 'recentchanges', 'rc_cur_id' ],
  410. // 1.27
  411. [ 'dropTable', 'msg_resource_links' ],
  412. [ 'dropTable', 'msg_resource' ],
  413. [
  414. 'addPgField', 'watchlist', 'wl_id',
  415. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('watchlist_wl_id_seq')"
  416. ],
  417. // 1.28
  418. [ 'addPgIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
  419. '( rc_namespace, rc_type, rc_patrolled, rc_timestamp )' ],
  420. [ 'addPgField', 'change_tag', 'ct_id',
  421. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('change_tag_ct_id_seq')" ],
  422. [ 'addPgField', 'tag_summary', 'ts_id',
  423. "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('tag_summary_ts_id_seq')" ],
  424. // 1.29
  425. [ 'addPgField', 'externallinks', 'el_index_60', "BYTEA NOT NULL DEFAULT ''" ],
  426. [ 'addPgIndex', 'externallinks', 'el_index_60', '( el_index_60, el_id )' ],
  427. [ 'addPgIndex', 'externallinks', 'el_from_index_60', '( el_from, el_index_60, el_id )' ],
  428. [ 'addPgField', 'user_groups', 'ug_expiry', "TIMESTAMPTZ NULL" ],
  429. [ 'addPgIndex', 'user_groups', 'user_groups_expiry', '( ug_expiry )' ],
  430. // 1.30
  431. [ 'addPgEnumValue', 'media_type', '3D' ],
  432. [ 'setDefault', 'revision', 'rev_comment', '' ],
  433. [ 'changeNullableField', 'revision', 'rev_comment', 'NOT NULL', true ],
  434. [ 'setDefault', 'archive', 'ar_comment', '' ],
  435. [ 'changeNullableField', 'archive', 'ar_comment', 'NOT NULL', true ],
  436. [ 'addPgField', 'archive', 'ar_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
  437. [ 'setDefault', 'ipblocks', 'ipb_reason', '' ],
  438. [ 'addPgField', 'ipblocks', 'ipb_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
  439. [ 'setDefault', 'image', 'img_description', '' ],
  440. [ 'setDefault', 'oldimage', 'oi_description', '' ],
  441. [ 'changeNullableField', 'oldimage', 'oi_description', 'NOT NULL', true ],
  442. [ 'addPgField', 'oldimage', 'oi_description_id', 'INTEGER NOT NULL DEFAULT 0' ],
  443. [ 'setDefault', 'filearchive', 'fa_deleted_reason', '' ],
  444. [ 'changeNullableField', 'filearchive', 'fa_deleted_reason', 'NOT NULL', true ],
  445. [ 'addPgField', 'filearchive', 'fa_deleted_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
  446. [ 'setDefault', 'filearchive', 'fa_description', '' ],
  447. [ 'addPgField', 'filearchive', 'fa_description_id', 'INTEGER NOT NULL DEFAULT 0' ],
  448. [ 'setDefault', 'recentchanges', 'rc_comment', '' ],
  449. [ 'changeNullableField', 'recentchanges', 'rc_comment', 'NOT NULL', true ],
  450. [ 'addPgField', 'recentchanges', 'rc_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
  451. [ 'setDefault', 'logging', 'log_comment', '' ],
  452. [ 'changeNullableField', 'logging', 'log_comment', 'NOT NULL', true ],
  453. [ 'addPgField', 'logging', 'log_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
  454. [ 'setDefault', 'protected_titles', 'pt_reason', '' ],
  455. [ 'changeNullableField', 'protected_titles', 'pt_reason', 'NOT NULL', true ],
  456. [ 'addPgField', 'protected_titles', 'pt_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
  457. [ 'addTable', 'comment', 'patch-comment-table.sql' ],
  458. // This field was added in 1.31, but is put here so it can be used by 'migrateComments'
  459. [ 'addPgField', 'image', 'img_description_id', 'INTEGER NOT NULL DEFAULT 0' ],
  460. [ 'migrateComments' ],
  461. [ 'addIndex', 'site_stats', 'site_stats_pkey', 'patch-site_stats-pk.sql' ],
  462. [ 'addTable', 'ip_changes', 'patch-ip_changes.sql' ],
  463. // 1.31
  464. [ 'addTable', 'slots', 'patch-slots-table.sql' ],
  465. [ 'dropPgIndex', 'slots', 'slot_role_inherited' ],
  466. [ 'dropPgField', 'slots', 'slot_inherited' ],
  467. [ 'addPgField', 'slots', 'slot_origin', 'INTEGER NOT NULL' ],
  468. [
  469. 'addPgIndex',
  470. 'slots',
  471. 'slot_revision_origin_role',
  472. '( slot_revision_id, slot_origin, slot_role_id )',
  473. ],
  474. [ 'addTable', 'content', 'patch-content-table.sql' ],
  475. [ 'addTable', 'content_models', 'patch-content_models-table.sql' ],
  476. [ 'addTable', 'slot_roles', 'patch-slot_roles-table.sql' ],
  477. [ 'migrateArchiveText' ],
  478. [ 'addTable', 'actor', 'patch-actor-table.sql' ],
  479. [ 'setDefault', 'revision', 'rev_user', 0 ],
  480. [ 'setDefault', 'revision', 'rev_user_text', '' ],
  481. [ 'setDefault', 'archive', 'ar_user', 0 ],
  482. [ 'changeNullableField', 'archive', 'ar_user', 'NOT NULL', true ],
  483. [ 'setDefault', 'archive', 'ar_user_text', '' ],
  484. [ 'addPgField', 'archive', 'ar_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  485. [ 'addPgIndex', 'archive', 'archive_actor', '( ar_actor )' ],
  486. [ 'setDefault', 'ipblocks', 'ipb_by', 0 ],
  487. [ 'addPgField', 'ipblocks', 'ipb_by_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  488. [ 'setDefault', 'image', 'img_user', 0 ],
  489. [ 'changeNullableField', 'image', 'img_user', 'NOT NULL', true ],
  490. [ 'setDefault', 'image', 'img_user_text', '' ],
  491. [ 'addPgField', 'image', 'img_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  492. [ 'setDefault', 'oldimage', 'oi_user', 0 ],
  493. [ 'changeNullableField', 'oldimage', 'oi_user', 'NOT NULL', true ],
  494. [ 'setDefault', 'oldimage', 'oi_user_text', '' ],
  495. [ 'addPgField', 'oldimage', 'oi_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  496. [ 'setDefault', 'filearchive', 'fa_user', 0 ],
  497. [ 'changeNullableField', 'filearchive', 'fa_user', 'NOT NULL', true ],
  498. [ 'setDefault', 'filearchive', 'fa_user_text', '' ],
  499. [ 'addPgField', 'filearchive', 'fa_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  500. [ 'setDefault', 'recentchanges', 'rc_user', 0 ],
  501. [ 'changeNullableField', 'recentchanges', 'rc_user', 'NOT NULL', true ],
  502. [ 'setDefault', 'recentchanges', 'rc_user_text', '' ],
  503. [ 'addPgField', 'recentchanges', 'rc_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  504. [ 'setDefault', 'logging', 'log_user', 0 ],
  505. [ 'changeNullableField', 'logging', 'log_user', 'NOT NULL', true ],
  506. [ 'addPgField', 'logging', 'log_actor', 'INTEGER NOT NULL DEFAULT 0' ],
  507. [ 'addPgIndex', 'logging', 'logging_actor_time_backwards', '( log_timestamp, log_actor )' ],
  508. [ 'addPgIndex', 'logging', 'logging_actor_type_time', '( log_actor, log_type, log_timestamp )' ],
  509. [ 'addPgIndex', 'logging', 'logging_actor_time', '( log_actor, log_timestamp )' ],
  510. [ 'migrateActors' ],
  511. [ 'modifyTable', 'site_stats', 'patch-site_stats-modify.sql' ],
  512. [ 'populateArchiveRevId' ],
  513. [ 'dropPgIndex', 'recentchanges', 'rc_namespace_title' ],
  514. [
  515. 'addPgIndex',
  516. 'recentchanges',
  517. 'rc_namespace_title_timestamp', '( rc_namespace, rc_title, rc_timestamp )'
  518. ],
  519. [ 'setSequenceOwner', 'mwuser', 'user_id', 'user_user_id_seq' ],
  520. [ 'setSequenceOwner', 'actor', 'actor_id', 'actor_actor_id_seq' ],
  521. [ 'setSequenceOwner', 'page', 'page_id', 'page_page_id_seq' ],
  522. [ 'setSequenceOwner', 'revision', 'rev_id', 'revision_rev_id_seq' ],
  523. [ 'setSequenceOwner', 'ip_changes', 'ipc_rev_id', 'ip_changes_ipc_rev_id_seq' ],
  524. [ 'setSequenceOwner', 'pagecontent', 'old_id', 'text_old_id_seq' ],
  525. [ 'setSequenceOwner', 'comment', 'comment_id', 'comment_comment_id_seq' ],
  526. [ 'setSequenceOwner', 'page_restrictions', 'pr_id', 'page_restrictions_pr_id_seq' ],
  527. [ 'setSequenceOwner', 'archive', 'ar_id', 'archive_ar_id_seq' ],
  528. [ 'setSequenceOwner', 'content', 'content_id', 'content_content_id_seq' ],
  529. [ 'setSequenceOwner', 'slot_roles', 'role_id', 'slot_roles_role_id_seq' ],
  530. [ 'setSequenceOwner', 'content_models', 'model_id', 'content_models_model_id_seq' ],
  531. [ 'setSequenceOwner', 'externallinks', 'el_id', 'externallinks_el_id_seq' ],
  532. [ 'setSequenceOwner', 'ipblocks', 'ipb_id', 'ipblocks_ipb_id_seq' ],
  533. [ 'setSequenceOwner', 'filearchive', 'fa_id', 'filearchive_fa_id_seq' ],
  534. [ 'setSequenceOwner', 'uploadstash', 'us_id', 'uploadstash_us_id_seq' ],
  535. [ 'setSequenceOwner', 'recentchanges', 'rc_id', 'recentchanges_rc_id_seq' ],
  536. [ 'setSequenceOwner', 'watchlist', 'wl_id', 'watchlist_wl_id_seq' ],
  537. [ 'setSequenceOwner', 'logging', 'log_id', 'logging_log_id_seq' ],
  538. [ 'setSequenceOwner', 'job', 'job_id', 'job_job_id_seq' ],
  539. [ 'setSequenceOwner', 'category', 'cat_id', 'category_cat_id_seq' ],
  540. [ 'setSequenceOwner', 'change_tag', 'ct_id', 'change_tag_ct_id_seq' ],
  541. [ 'setSequenceOwner', 'tag_summary', 'ts_id', 'tag_summary_ts_id_seq' ],
  542. [ 'setSequenceOwner', 'sites', 'site_id', 'sites_site_id_seq' ],
  543. ];
  544. }
  545. protected function getOldGlobalUpdates() {
  546. global $wgExtNewTables, $wgExtPGNewFields, $wgExtPGAlteredFields, $wgExtNewIndexes;
  547. $updates = [];
  548. # Add missing extension tables
  549. foreach ( $wgExtNewTables as $tableRecord ) {
  550. $updates[] = [
  551. 'addTable', $tableRecord[0], $tableRecord[1], true
  552. ];
  553. }
  554. # Add missing extension fields
  555. foreach ( $wgExtPGNewFields as $fieldRecord ) {
  556. $updates[] = [
  557. 'addPgField', $fieldRecord[0], $fieldRecord[1],
  558. $fieldRecord[2]
  559. ];
  560. }
  561. # Change altered columns
  562. foreach ( $wgExtPGAlteredFields as $fieldRecord ) {
  563. $updates[] = [
  564. 'changeField', $fieldRecord[0], $fieldRecord[1],
  565. $fieldRecord[2]
  566. ];
  567. }
  568. # Add missing extension indexes
  569. foreach ( $wgExtNewIndexes as $fieldRecord ) {
  570. $updates[] = [
  571. 'addPgExtIndex', $fieldRecord[0], $fieldRecord[1],
  572. $fieldRecord[2]
  573. ];
  574. }
  575. return $updates;
  576. }
  577. protected function describeTable( $table ) {
  578. $q = <<<END
  579. SELECT attname, attnum FROM pg_namespace, pg_class, pg_attribute
  580. WHERE pg_class.relnamespace = pg_namespace.oid
  581. AND attrelid=pg_class.oid AND attnum > 0
  582. AND relname=%s AND nspname=%s
  583. END;
  584. $res = $this->db->query( sprintf( $q,
  585. $this->db->addQuotes( $table ),
  586. $this->db->addQuotes( $this->db->getCoreSchema() ) ) );
  587. if ( !$res ) {
  588. return null;
  589. }
  590. $cols = [];
  591. foreach ( $res as $r ) {
  592. $cols[] = [
  593. "name" => $r[0],
  594. "ord" => $r[1],
  595. ];
  596. }
  597. return $cols;
  598. }
  599. function describeIndex( $idx ) {
  600. // first fetch the key (which is a list of columns ords) and
  601. // the table the index applies to (an oid)
  602. $q = <<<END
  603. SELECT indkey, indrelid FROM pg_namespace, pg_class, pg_index
  604. WHERE nspname=%s
  605. AND pg_class.relnamespace = pg_namespace.oid
  606. AND relname=%s
  607. AND indexrelid=pg_class.oid
  608. END;
  609. $res = $this->db->query(
  610. sprintf(
  611. $q,
  612. $this->db->addQuotes( $this->db->getCoreSchema() ),
  613. $this->db->addQuotes( $idx )
  614. )
  615. );
  616. if ( !$res ) {
  617. return null;
  618. }
  619. $r = $this->db->fetchRow( $res );
  620. if ( !$r ) {
  621. return null;
  622. }
  623. $indkey = $r[0];
  624. $relid = intval( $r[1] );
  625. $indkeys = explode( ' ', $indkey );
  626. $colnames = [];
  627. foreach ( $indkeys as $rid ) {
  628. $query = <<<END
  629. SELECT attname FROM pg_class, pg_attribute
  630. WHERE attrelid=$relid
  631. AND attnum=%d
  632. AND attrelid=pg_class.oid
  633. END;
  634. $r2 = $this->db->query( sprintf( $query, $rid ) );
  635. if ( !$r2 ) {
  636. return null;
  637. }
  638. $row2 = $this->db->fetchRow( $r2 );
  639. if ( !$row2 ) {
  640. return null;
  641. }
  642. $colnames[] = $row2[0];
  643. }
  644. return $colnames;
  645. }
  646. function fkeyDeltype( $fkey ) {
  647. $q = <<<END
  648. SELECT confdeltype FROM pg_constraint, pg_namespace
  649. WHERE connamespace=pg_namespace.oid
  650. AND nspname=%s
  651. AND conname=%s;
  652. END;
  653. $r = $this->db->query(
  654. sprintf(
  655. $q,
  656. $this->db->addQuotes( $this->db->getCoreSchema() ),
  657. $this->db->addQuotes( $fkey )
  658. )
  659. );
  660. $row = $this->db->fetchRow( $r );
  661. if ( !$row ) {
  662. return null;
  663. }
  664. return $row[0];
  665. }
  666. function ruleDef( $table, $rule ) {
  667. $q = <<<END
  668. SELECT definition FROM pg_rules
  669. WHERE schemaname = %s
  670. AND tablename = %s
  671. AND rulename = %s
  672. END;
  673. $r = $this->db->query(
  674. sprintf(
  675. $q,
  676. $this->db->addQuotes( $this->db->getCoreSchema() ),
  677. $this->db->addQuotes( $table ),
  678. $this->db->addQuotes( $rule )
  679. )
  680. );
  681. $row = $this->db->fetchRow( $r );
  682. if ( !$row ) {
  683. return null;
  684. }
  685. $d = $row[0];
  686. return $d;
  687. }
  688. protected function addSequence( $table, $pkey, $ns ) {
  689. if ( !$this->db->sequenceExists( $ns ) ) {
  690. $this->output( "Creating sequence $ns\n" );
  691. if ( $pkey !== false ) {
  692. $this->db->query( "CREATE SEQUENCE $ns OWNED BY $table.$pkey" );
  693. $this->setDefault( $table, $pkey, '"nextval"(\'"' . $ns . '"\'::"regclass")' );
  694. } else {
  695. $this->db->query( "CREATE SEQUENCE $ns" );
  696. }
  697. }
  698. }
  699. protected function dropSequence( $table, $ns ) {
  700. if ( $this->db->sequenceExists( $ns ) ) {
  701. $this->output( "Dropping sequence $ns\n" );
  702. $this->db->query( "DROP SEQUENCE $ns CASCADE" );
  703. }
  704. }
  705. protected function renameSequence( $old, $new ) {
  706. if ( $this->db->sequenceExists( $new ) ) {
  707. $this->output( "...sequence $new already exists.\n" );
  708. return;
  709. }
  710. if ( $this->db->sequenceExists( $old ) ) {
  711. $this->output( "Renaming sequence $old to $new\n" );
  712. $this->db->query( "ALTER SEQUENCE $old RENAME TO $new" );
  713. }
  714. }
  715. protected function setSequenceOwner( $table, $pkey, $seq ) {
  716. if ( $this->db->sequenceExists( $seq ) ) {
  717. $this->output( "Setting sequence $seq owner to $table.$pkey\n" );
  718. $this->db->query( "ALTER SEQUENCE $seq OWNED BY $table.$pkey" );
  719. }
  720. }
  721. protected function renameTable( $old, $new, $patch = false ) {
  722. if ( $this->db->tableExists( $old ) ) {
  723. $this->output( "Renaming table $old to $new\n" );
  724. $old = $this->db->realTableName( $old, "quoted" );
  725. $new = $this->db->realTableName( $new, "quoted" );
  726. $this->db->query( "ALTER TABLE $old RENAME TO $new" );
  727. if ( $patch !== false ) {
  728. $this->applyPatch( $patch );
  729. }
  730. }
  731. }
  732. protected function renameIndex(
  733. $table, $old, $new, $skipBothIndexExistWarning = false, $a = false, $b = false
  734. ) {
  735. // First requirement: the table must exist
  736. if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
  737. $this->output( "...skipping: '$table' table doesn't exist yet.\n" );
  738. return;
  739. }
  740. // Second requirement: the new index must be missing
  741. if ( $this->db->indexExists( $table, $new, __METHOD__ ) ) {
  742. $this->output( "...index $new already set on $table table.\n" );
  743. if ( !$skipBothIndexExistWarning
  744. && $this->db->indexExists( $table, $old, __METHOD__ )
  745. ) {
  746. $this->output( "...WARNING: $old still exists, despite it has been " .
  747. "renamed into $new (which also exists).\n" .
  748. " $old should be manually removed if not needed anymore.\n" );
  749. }
  750. return;
  751. }
  752. // Third requirement: the old index must exist
  753. if ( !$this->db->indexExists( $table, $old, __METHOD__ ) ) {
  754. $this->output( "...skipping: index $old doesn't exist.\n" );
  755. return;
  756. }
  757. $this->db->query( "ALTER INDEX $old RENAME TO $new" );
  758. }
  759. protected function dropPgField( $table, $field ) {
  760. $fi = $this->db->fieldInfo( $table, $field );
  761. if ( is_null( $fi ) ) {
  762. $this->output( "...$table table does not contain $field field.\n" );
  763. return;
  764. } else {
  765. $this->output( "Dropping column '$table.$field'\n" );
  766. $this->db->query( "ALTER TABLE $table DROP COLUMN $field" );
  767. }
  768. }
  769. protected function addPgField( $table, $field, $type ) {
  770. $fi = $this->db->fieldInfo( $table, $field );
  771. if ( !is_null( $fi ) ) {
  772. $this->output( "...column '$table.$field' already exists\n" );
  773. return;
  774. } else {
  775. $this->output( "Adding column '$table.$field'\n" );
  776. $this->db->query( "ALTER TABLE $table ADD $field $type" );
  777. }
  778. }
  779. protected function changeField( $table, $field, $newtype, $default ) {
  780. $fi = $this->db->fieldInfo( $table, $field );
  781. if ( is_null( $fi ) ) {
  782. $this->output( "...ERROR: expected column $table.$field to exist\n" );
  783. exit( 1 );
  784. }
  785. if ( $fi->type() === $newtype ) {
  786. $this->output( "...column '$table.$field' is already of type '$newtype'\n" );
  787. } else {
  788. $this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
  789. $sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
  790. if ( strlen( $default ) ) {
  791. $res = [];
  792. if ( preg_match( '/DEFAULT (.+)/', $default, $res ) ) {
  793. $sqldef = "ALTER TABLE $table ALTER $field SET DEFAULT $res[1]";
  794. $this->db->query( $sqldef );
  795. $default = preg_replace( '/\s*DEFAULT .+/', '', $default );
  796. }
  797. $sql .= " USING $default";
  798. }
  799. $this->db->query( $sql );
  800. }
  801. }
  802. protected function changeFieldPurgeTable( $table, $field, $newtype, $default ) {
  803. # # For a cache table, empty it if the field needs to be changed, because the old contents
  804. # # may be corrupted. If the column is already the desired type, refrain from purging.
  805. $fi = $this->db->fieldInfo( $table, $field );
  806. if ( is_null( $fi ) ) {
  807. $this->output( "...ERROR: expected column $table.$field to exist\n" );
  808. exit( 1 );
  809. }
  810. if ( $fi->type() === $newtype ) {
  811. $this->output( "...column '$table.$field' is already of type '$newtype'\n" );
  812. } else {
  813. $this->output( "Purging data from cache table '$table'\n" );
  814. $this->db->query( "DELETE from $table" );
  815. $this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
  816. $sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
  817. if ( strlen( $default ) ) {
  818. $res = [];
  819. if ( preg_match( '/DEFAULT (.+)/', $default, $res ) ) {
  820. $sqldef = "ALTER TABLE $table ALTER $field SET DEFAULT $res[1]";
  821. $this->db->query( $sqldef );
  822. $default = preg_replace( '/\s*DEFAULT .+/', '', $default );
  823. }
  824. $sql .= " USING $default";
  825. }
  826. $this->db->query( $sql );
  827. }
  828. }
  829. protected function setDefault( $table, $field, $default ) {
  830. $info = $this->db->fieldInfo( $table, $field );
  831. if ( $info->defaultValue() !== $default ) {
  832. $this->output( "Changing '$table.$field' default value\n" );
  833. $this->db->query( "ALTER TABLE $table ALTER $field SET DEFAULT "
  834. . $this->db->addQuotes( $default ) );
  835. }
  836. }
  837. protected function changeNullableField( $table, $field, $null, $update = false ) {
  838. $fi = $this->db->fieldInfo( $table, $field );
  839. if ( is_null( $fi ) ) {
  840. $this->output( "...ERROR: expected column $table.$field to exist\n" );
  841. exit( 1 );
  842. }
  843. if ( $fi->isNullable() ) {
  844. # # It's NULL - does it need to be NOT NULL?
  845. if ( 'NOT NULL' === $null ) {
  846. $this->output( "Changing '$table.$field' to not allow NULLs\n" );
  847. if ( $update ) {
  848. $this->db->query( "UPDATE $table SET $field = DEFAULT WHERE $field IS NULL" );
  849. }
  850. $this->db->query( "ALTER TABLE $table ALTER $field SET NOT NULL" );
  851. } else {
  852. $this->output( "...column '$table.$field' is already set as NULL\n" );
  853. }
  854. } else {
  855. # # It's NOT NULL - does it need to be NULL?
  856. if ( 'NULL' === $null ) {
  857. $this->output( "Changing '$table.$field' to allow NULLs\n" );
  858. $this->db->query( "ALTER TABLE $table ALTER $field DROP NOT NULL" );
  859. } else {
  860. $this->output( "...column '$table.$field' is already set as NOT NULL\n" );
  861. }
  862. }
  863. }
  864. public function addPgIndex( $table, $index, $type ) {
  865. if ( $this->db->indexExists( $table, $index ) ) {
  866. $this->output( "...index '$index' on table '$table' already exists\n" );
  867. } else {
  868. $this->output( "Creating index '$index' on table '$table' $type\n" );
  869. $this->db->query( "CREATE INDEX $index ON $table $type" );
  870. }
  871. }
  872. public function addPgExtIndex( $table, $index, $type ) {
  873. if ( $this->db->indexExists( $table, $index ) ) {
  874. $this->output( "...index '$index' on table '$table' already exists\n" );
  875. } else {
  876. if ( preg_match( '/^\(/', $type ) ) {
  877. $this->output( "Creating index '$index' on table '$table'\n" );
  878. $this->db->query( "CREATE INDEX $index ON $table $type" );
  879. } else {
  880. $this->applyPatch( $type, true, "Creating index '$index' on table '$table'" );
  881. }
  882. }
  883. }
  884. /**
  885. * Add a value to an existing PostgreSQL enum type
  886. * @since 1.31
  887. * @param string $type Type name. Must be in the core schema.
  888. * @param string $value Value to add.
  889. */
  890. public function addPgEnumValue( $type, $value ) {
  891. $row = $this->db->selectRow(
  892. [
  893. 't' => 'pg_catalog.pg_type',
  894. 'n' => 'pg_catalog.pg_namespace',
  895. 'e' => 'pg_catalog.pg_enum',
  896. ],
  897. [ 't.typname', 't.typtype', 'e.enumlabel' ],
  898. [
  899. 't.typname' => $type,
  900. 'n.nspname' => $this->db->getCoreSchema(),
  901. ],
  902. __METHOD__,
  903. [],
  904. [
  905. 'n' => [ 'JOIN', 't.typnamespace = n.oid' ],
  906. 'e' => [ 'LEFT JOIN', [ 'e.enumtypid = t.oid', 'e.enumlabel' => $value ] ],
  907. ]
  908. );
  909. if ( !$row ) {
  910. $this->output( "...Type $type does not exist, skipping modify enum.\n" );
  911. } elseif ( $row->typtype !== 'e' ) {
  912. $this->output( "...Type $type does not seem to be an enum, skipping modify enum.\n" );
  913. } elseif ( $row->enumlabel === $value ) {
  914. $this->output( "...Enum type $type already contains value '$value'.\n" );
  915. } else {
  916. $this->output( "...Adding value '$value' to enum type $type.\n" );
  917. $etype = $this->db->addIdentifierQuotes( $type );
  918. $evalue = $this->db->addQuotes( $value );
  919. $this->db->query( "ALTER TYPE $etype ADD VALUE $evalue" );
  920. }
  921. }
  922. protected function dropFkey( $table, $field ) {
  923. $fi = $this->db->fieldInfo( $table, $field );
  924. if ( is_null( $fi ) ) {
  925. $this->output( "WARNING! Column '$table.$field' does not exist but it should! " .
  926. "Please report this.\n" );
  927. return;
  928. }
  929. $conname = $fi->conname();
  930. if ( $fi->conname() ) {
  931. $this->output( "Dropping foreign key constraint on '$table.$field'\n" );
  932. $conclause = "CONSTRAINT \"$conname\"";
  933. $command = "ALTER TABLE $table DROP CONSTRAINT $conname";
  934. $this->db->query( $command );
  935. } else {
  936. $this->output( "...foreign key constraint on '$table.$field' already does not exist\n" );
  937. };
  938. }
  939. protected function changeFkeyDeferrable( $table, $field, $clause ) {
  940. $fi = $this->db->fieldInfo( $table, $field );
  941. if ( is_null( $fi ) ) {
  942. $this->output( "WARNING! Column '$table.$field' does not exist but it should! " .
  943. "Please report this.\n" );
  944. return;
  945. }
  946. if ( $fi->is_deferred() && $fi->is_deferrable() ) {
  947. return;
  948. }
  949. $this->output( "Altering column '$table.$field' to be DEFERRABLE INITIALLY DEFERRED\n" );
  950. $conname = $fi->conname();
  951. if ( $fi->conname() ) {
  952. $conclause = "CONSTRAINT \"$conname\"";
  953. $command = "ALTER TABLE $table DROP CONSTRAINT $conname";
  954. $this->db->query( $command );
  955. } else {
  956. $this->output( "Column '$table.$field' does not have a foreign key " .
  957. "constraint, will be added\n" );
  958. $conclause = "";
  959. }
  960. $command =
  961. "ALTER TABLE $table ADD $conclause " .
  962. "FOREIGN KEY ($field) REFERENCES $clause DEFERRABLE INITIALLY DEFERRED";
  963. $this->db->query( $command );
  964. }
  965. protected function convertArchive2() {
  966. if ( $this->db->tableExists( "archive2" ) ) {
  967. if ( $this->db->ruleExists( 'archive', 'archive_insert' ) ) {
  968. $this->output( "Dropping rule 'archive_insert'\n" );
  969. $this->db->query( 'DROP RULE archive_insert ON archive' );
  970. }
  971. if ( $this->db->ruleExists( 'archive', 'archive_delete' ) ) {
  972. $this->output( "Dropping rule 'archive_delete'\n" );
  973. $this->db->query( 'DROP RULE archive_delete ON archive' );
  974. }
  975. $this->applyPatch(
  976. 'patch-remove-archive2.sql',
  977. false,
  978. "Converting 'archive2' back to normal archive table"
  979. );
  980. } else {
  981. $this->output( "...obsolete table 'archive2' does not exist\n" );
  982. }
  983. }
  984. protected function checkOiDeleted() {
  985. if ( $this->db->fieldInfo( 'oldimage', 'oi_deleted' )->type() !== 'smallint' ) {
  986. $this->output( "Changing 'oldimage.oi_deleted' to type 'smallint'\n" );
  987. $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted DROP DEFAULT" );
  988. $this->db->query(
  989. "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
  990. $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" );
  991. } else {
  992. $this->output( "...column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
  993. }
  994. }
  995. protected function checkOiNameConstraint() {
  996. if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascaded" ) ) {
  997. $this->output( "...table 'oldimage' has correct cascading delete/update " .
  998. "foreign key to image\n" );
  999. } else {
  1000. if ( $this->db->hasConstraint( "oldimage_oi_name_fkey" ) ) {
  1001. $this->db->query(
  1002. "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
  1003. }
  1004. if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascade" ) ) {
  1005. $this->db->query(
  1006. "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
  1007. }
  1008. $this->output( "Making foreign key on table 'oldimage' (to image) a cascade delete/update\n" );
  1009. $this->db->query(
  1010. "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded " .
  1011. "FOREIGN KEY (oi_name) REFERENCES image(img_name) " .
  1012. "ON DELETE CASCADE ON UPDATE CASCADE" );
  1013. }
  1014. }
  1015. protected function checkPageDeletedTrigger() {
  1016. if ( !$this->db->triggerExists( 'page', 'page_deleted' ) ) {
  1017. $this->applyPatch(
  1018. 'patch-page_deleted.sql',
  1019. false,
  1020. "Adding function and trigger 'page_deleted' to table 'page'"
  1021. );
  1022. } else {
  1023. $this->output( "...table 'page' has 'page_deleted' trigger\n" );
  1024. }
  1025. }
  1026. protected function dropPgIndex( $table, $index ) {
  1027. if ( $this->db->indexExists( $table, $index ) ) {
  1028. $this->output( "Dropping obsolete index '$index'\n" );
  1029. $this->db->query( "DROP INDEX \"" . $index . "\"" );
  1030. }
  1031. }
  1032. protected function checkIndex( $index, $should_be, $good_def ) {
  1033. $pu = $this->db->indexAttributes( $index );
  1034. if ( !empty( $pu ) && $pu != $should_be ) {
  1035. $this->output( "Dropping obsolete version of index '$index'\n" );
  1036. $this->db->query( "DROP INDEX \"" . $index . "\"" );
  1037. $pu = [];
  1038. } else {
  1039. $this->output( "...no need to drop index '$index'\n" );
  1040. }
  1041. if ( empty( $pu ) ) {
  1042. $this->output( "Creating index '$index'\n" );
  1043. $this->db->query( $good_def );
  1044. } else {
  1045. $this->output( "...index '$index' exists\n" );
  1046. }
  1047. }
  1048. protected function checkRevUserFkey() {
  1049. if ( $this->fkeyDeltype( 'revision_rev_user_fkey' ) == 'r' ) {
  1050. $this->output( "...constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
  1051. } else {
  1052. $this->applyPatch(
  1053. 'patch-revision_rev_user_fkey.sql',
  1054. false,
  1055. "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT"
  1056. );
  1057. }
  1058. }
  1059. protected function checkIwlPrefix() {
  1060. if ( $this->db->indexExists( 'iwlinks', 'iwl_prefix' ) ) {
  1061. $this->applyPatch(
  1062. 'patch-rename-iwl_prefix.sql',
  1063. false,
  1064. "Replacing index 'iwl_prefix' with 'iwl_prefix_title_from'"
  1065. );
  1066. }
  1067. }
  1068. protected function addInterwikiType() {
  1069. $this->applyPatch( 'patch-add_interwiki.sql', false, "Refreshing add_interwiki()" );
  1070. }
  1071. protected function tsearchFixes() {
  1072. # Tweak the page_title tsearch2 trigger to filter out slashes
  1073. # This is create or replace, so harmless to call if not needed
  1074. $this->applyPatch( 'patch-ts2pagetitle.sql', false, "Refreshing ts2_page_title()" );
  1075. # If the server is 8.3 or higher, rewrite the tsearch2 triggers
  1076. # in case they have the old 'default' versions
  1077. # Gather version numbers in case we need them
  1078. if ( $this->db->getServerVersion() >= 8.3 ) {
  1079. $this->applyPatch( 'patch-tsearch2funcs.sql', false, "Rewriting tsearch2 triggers" );
  1080. }
  1081. }
  1082. protected function rebuildTextSearch() {
  1083. if ( $this->updateRowExists( 'patch-textsearch_bug66650.sql' ) ) {
  1084. $this->output( "...T68650 already fixed or not applicable.\n" );
  1085. return;
  1086. };
  1087. $this->applyPatch( 'patch-textsearch_bug66650.sql', false,
  1088. 'Rebuilding text search for T68650' );
  1089. }
  1090. }