PostgresUpdater.php 54 KB

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