Update core usage of getLanguageName[s].
[lhc/web/wiklou.git] / includes / installer / PostgresUpdater.php
1 <?php
2 /**
3 * PostgreSQL-specific updater.
4 *
5 * @file
6 * @ingroup Deployment
7 */
8
9 /**
10 * Class for handling updates to Postgres databases.
11 *
12 * @ingroup Deployment
13 * @since 1.17
14 */
15
16 class PostgresUpdater extends DatabaseUpdater {
17
18 /**
19 * @var DatabasePostgres
20 */
21 protected $db;
22
23 /**
24 * @todo FIXME: Postgres should use sequential updates like Mysql, Sqlite
25 * and everybody else. It never got refactored like it should've.
26 * @return array
27 */
28 protected function getCoreUpdateList() {
29 return array(
30 # new sequences
31 array( 'addSequence', 'logging_log_id_seq' ),
32 array( 'addSequence', 'page_restrictions_pr_id_seq' ),
33
34 # renamed sequences
35 array( 'renameSequence', 'ipblocks_ipb_id_val', 'ipblocks_ipb_id_seq' ),
36 array( 'renameSequence', 'rev_rev_id_val', 'revision_rev_id_seq' ),
37 array( 'renameSequence', 'text_old_id_val', 'text_old_id_seq' ),
38 array( 'renameSequence', 'category_id_seq', 'category_cat_id_seq' ),
39 array( 'renameSequence', 'rc_rc_id_seq', 'recentchanges_rc_id_seq' ),
40 array( 'renameSequence', 'log_log_id_seq', 'logging_log_id_seq' ),
41 array( 'renameSequence', 'pr_id_val', 'page_restrictions_pr_id_seq' ),
42 array( 'renameSequence', 'us_id_seq', 'uploadstash_us_id_seq' ),
43
44 # new tables
45 array( 'addTable', 'category', 'patch-category.sql' ),
46 array( 'addTable', 'page', 'patch-page.sql' ),
47 array( 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ),
48 array( 'addTable', 'page_props', 'patch-page_props.sql' ),
49 array( 'addTable', 'page_restrictions', 'patch-page_restrictions.sql' ),
50 array( 'addTable', 'profiling', 'patch-profiling.sql' ),
51 array( 'addTable', 'protected_titles', 'patch-protected_titles.sql' ),
52 array( 'addTable', 'redirect', 'patch-redirect.sql' ),
53 array( 'addTable', 'updatelog', 'patch-updatelog.sql' ),
54 array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
55 array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
56 array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
57 array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
58 array( 'addTable', 'log_search', 'patch-log_search.sql' ),
59 array( 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ),
60 array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
61 array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
62 array( 'addTable', 'msg_resource_links','patch-msg_resource_links.sql' ),
63 array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
64 array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
65 array( 'addTable', 'user_former_groups','patch-user_former_groups.sql' ),
66
67 # Needed before new field
68 array( 'convertArchive2' ),
69
70 # new fields
71 array( 'addPgField', 'archive', 'ar_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
72 array( 'addPgField', 'archive', 'ar_len', 'INTEGER' ),
73 array( 'addPgField', 'archive', 'ar_page_id', 'INTEGER' ),
74 array( 'addPgField', 'archive', 'ar_parent_id', 'INTEGER' ),
75 array( 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''"),
76 array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0"),
77 array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'"),
78 array( 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ),
79 array( 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ),
80 array( 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ),
81 array( 'addPgField', 'ipblocks', 'ipb_by_text', "TEXT NOT NULL DEFAULT ''" ),
82 array( 'addPgField', 'ipblocks', 'ipb_block_email', 'SMALLINT NOT NULL DEFAULT 0' ),
83 array( 'addPgField', 'ipblocks', 'ipb_create_account', 'SMALLINT NOT NULL DEFAULT 1' ),
84 array( 'addPgField', 'ipblocks', 'ipb_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
85 array( 'addPgField', 'ipblocks', 'ipb_enable_autoblock', 'SMALLINT NOT NULL DEFAULT 1' ),
86 array( 'addPgField', 'filearchive', 'fa_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
87 array( 'addPgField', 'logging', 'log_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
88 array( 'addPgField', 'logging', 'log_id', "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq')" ),
89 array( 'addPgField', 'logging', 'log_params', 'TEXT' ),
90 array( 'addPgField', 'mwuser', 'user_editcount', 'INTEGER' ),
91 array( 'addPgField', 'mwuser', 'user_newpass_time', 'TIMESTAMPTZ' ),
92 array( 'addPgField', 'oldimage', 'oi_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
93 array( 'addPgField', 'oldimage', 'oi_major_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
94 array( 'addPgField', 'oldimage', 'oi_media_type', 'TEXT' ),
95 array( 'addPgField', 'oldimage', 'oi_metadata', "BYTEA NOT NULL DEFAULT ''" ),
96 array( 'addPgField', 'oldimage', 'oi_minor_mime', "TEXT NOT NULL DEFAULT 'unknown'" ),
97 array( 'addPgField', 'oldimage', 'oi_sha1', "TEXT NOT NULL DEFAULT ''" ),
98 array( 'addPgField', 'page_restrictions', 'pr_id', "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq')" ),
99 array( 'addPgField', 'profiling', 'pf_memory', 'NUMERIC(18,10) NOT NULL DEFAULT 0' ),
100 array( 'addPgField', 'recentchanges', 'rc_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
101 array( 'addPgField', 'recentchanges', 'rc_log_action', 'TEXT' ),
102 array( 'addPgField', 'recentchanges', 'rc_log_type', 'TEXT' ),
103 array( 'addPgField', 'recentchanges', 'rc_logid', 'INTEGER NOT NULL DEFAULT 0' ),
104 array( 'addPgField', 'recentchanges', 'rc_new_len', 'INTEGER' ),
105 array( 'addPgField', 'recentchanges', 'rc_old_len', 'INTEGER' ),
106 array( 'addPgField', 'recentchanges', 'rc_params', 'TEXT' ),
107 array( 'addPgField', 'redirect', 'rd_interwiki', 'TEXT NULL' ),
108 array( 'addPgField', 'redirect', 'rd_fragment', 'TEXT NULL' ),
109 array( 'addPgField', 'revision', 'rev_deleted', 'SMALLINT NOT NULL DEFAULT 0' ),
110 array( 'addPgField', 'revision', 'rev_len', 'INTEGER' ),
111 array( 'addPgField', 'revision', 'rev_parent_id', 'INTEGER DEFAULT NULL' ),
112 array( 'addPgField', 'site_stats', 'ss_active_users', "INTEGER DEFAULT '-1'" ),
113 array( 'addPgField', 'user_newtalk', 'user_last_timestamp', 'TIMESTAMPTZ' ),
114 array( 'addPgField', 'logging', 'log_user_text', "TEXT NOT NULL DEFAULT ''" ),
115 array( 'addPgField', 'logging', 'log_page', 'INTEGER' ),
116 array( 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''"),
117 array( 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''"),
118 array( 'addPgField', 'revision', 'rev_sha1', "TEXT NOT NULL DEFAULT ''" ),
119 array( 'addPgField', 'archive', 'ar_sha1', "TEXT NOT NULL DEFAULT ''" ),
120 array( 'addPgField', 'uploadstash', 'us_chunk_inx', "INTEGER NULL" ),
121 array( 'addPgField', 'job', 'job_timestamp', "TIMESTAMPTZ" ),
122
123 # type changes
124 array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
125 array( 'changeField', 'archive', 'ar_minor_edit', 'smallint', 'ar_minor_edit::smallint DEFAULT 0' ),
126 array( 'changeField', 'filearchive', 'fa_deleted', 'smallint', '' ),
127 array( 'changeField', 'filearchive', 'fa_height', 'integer', '' ),
128 array( 'changeField', 'filearchive', 'fa_metadata', 'bytea', "decode(fa_metadata,'escape')" ),
129 array( 'changeField', 'filearchive', 'fa_size', 'integer', '' ),
130 array( 'changeField', 'filearchive', 'fa_width', 'integer', '' ),
131 array( 'changeField', 'filearchive', 'fa_storage_group', 'text', '' ),
132 array( 'changeField', 'filearchive', 'fa_storage_key', 'text', '' ),
133 array( 'changeField', 'image', 'img_metadata', 'bytea', "decode(img_metadata,'escape')" ),
134 array( 'changeField', 'image', 'img_size', 'integer', '' ),
135 array( 'changeField', 'image', 'img_width', 'integer', '' ),
136 array( 'changeField', 'image', 'img_height', 'integer', '' ),
137 array( 'changeField', 'interwiki', 'iw_local', 'smallint', 'iw_local::smallint DEFAULT 0' ),
138 array( 'changeField', 'interwiki', 'iw_trans', 'smallint', 'iw_trans::smallint DEFAULT 0' ),
139 array( 'changeField', 'ipblocks', 'ipb_auto', 'smallint', 'ipb_auto::smallint DEFAULT 0' ),
140 array( 'changeField', 'ipblocks', 'ipb_anon_only', 'smallint', "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0" ),
141 array( 'changeField', 'ipblocks', 'ipb_create_account', 'smallint', "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1" ),
142 array( 'changeField', 'ipblocks', 'ipb_enable_autoblock', 'smallint', "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1" ),
143 array( 'changeField', 'ipblocks', 'ipb_block_email', 'smallint', "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0" ),
144 array( 'changeField', 'ipblocks', 'ipb_address', 'text', 'ipb_address::text' ),
145 array( 'changeField', 'ipblocks', 'ipb_deleted', 'smallint', 'ipb_deleted::smallint DEFAULT 0' ),
146 array( 'changeField', 'mwuser', 'user_token', 'text', '' ),
147 array( 'changeField', 'mwuser', 'user_email_token', 'text', '' ),
148 array( 'changeField', 'objectcache', 'keyname', 'text', '' ),
149 array( 'changeField', 'oldimage', 'oi_height', 'integer', '' ),
150 array( 'changeField', 'oldimage', 'oi_metadata', 'bytea', "decode(img_metadata,'escape')" ),
151 array( 'changeField', 'oldimage', 'oi_size', 'integer', '' ),
152 array( 'changeField', 'oldimage', 'oi_width', 'integer', '' ),
153 array( 'changeField', 'page', 'page_is_redirect', 'smallint', 'page_is_redirect::smallint DEFAULT 0' ),
154 array( 'changeField', 'page', 'page_is_new', 'smallint', 'page_is_new::smallint DEFAULT 0' ),
155 array( 'changeField', 'querycache', 'qc_value', 'integer', '' ),
156 array( 'changeField', 'querycachetwo', 'qcc_value', 'integer', '' ),
157 array( 'changeField', 'recentchanges', 'rc_bot', 'smallint', 'rc_bot::smallint DEFAULT 0' ),
158 array( 'changeField', 'recentchanges', 'rc_deleted', 'smallint', '' ),
159 array( 'changeField', 'recentchanges', 'rc_minor', 'smallint', 'rc_minor::smallint DEFAULT 0' ),
160 array( 'changeField', 'recentchanges', 'rc_new', 'smallint', 'rc_new::smallint DEFAULT 0' ),
161 array( 'changeField', 'recentchanges', 'rc_type', 'smallint', 'rc_type::smallint DEFAULT 0' ),
162 array( 'changeField', 'recentchanges', 'rc_patrolled', 'smallint', 'rc_patrolled::smallint DEFAULT 0' ),
163 array( 'changeField', 'revision', 'rev_deleted', 'smallint', 'rev_deleted::smallint DEFAULT 0' ),
164 array( 'changeField', 'revision', 'rev_minor_edit', 'smallint', 'rev_minor_edit::smallint DEFAULT 0' ),
165 array( 'changeField', 'templatelinks', 'tl_namespace', 'smallint', 'tl_namespace::smallint' ),
166 array( 'changeField', 'user_newtalk', 'user_ip', 'text', 'host(user_ip)' ),
167
168 # null changes
169 array( 'changeNullableField', 'oldimage', 'oi_bits', 'NULL' ),
170 array( 'changeNullableField', 'oldimage', 'oi_timestamp', 'NULL' ),
171 array( 'changeNullableField', 'oldimage', 'oi_major_mime', 'NULL' ),
172 array( 'changeNullableField', 'oldimage', 'oi_minor_mime', 'NULL' ),
173
174 array( 'checkOiDeleted' ),
175
176 # New indexes
177 array( 'addPgIndex', 'archive', 'archive_user_text', '(ar_user_text)' ),
178 array( 'addPgIndex', 'image', 'img_sha1', '(img_sha1)' ),
179 array( 'addPgIndex', 'oldimage', 'oi_sha1', '(oi_sha1)' ),
180 array( 'addPgIndex', 'page', 'page_mediawiki_title', '(page_title) WHERE page_namespace = 8' ),
181 array( 'addPgIndex', 'pagelinks', 'pagelinks_title', '(pl_title)' ),
182 array( 'addPgIndex', 'revision', 'rev_text_id_idx', '(rev_text_id)' ),
183 array( 'addPgIndex', 'recentchanges', 'rc_timestamp_bot', '(rc_timestamp) WHERE rc_bot = 0' ),
184 array( 'addPgIndex', 'templatelinks', 'templatelinks_from', '(tl_from)' ),
185 array( 'addPgIndex', 'watchlist', 'wl_user', '(wl_user)' ),
186 array( 'addPgIndex', 'logging', 'logging_user_type_time', '(log_user, log_type, log_timestamp)' ),
187 array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
188 array( 'addPgIndex', 'iwlinks', 'iwl_prefix_title_from', '(iwl_prefix, iwl_title, iwl_from)' ),
189 array( 'addPgIndex', 'job', 'job_timestamp_idx', '(job_timestamp)' ),
190
191 array( 'checkOiNameConstraint' ),
192 array( 'checkPageDeletedTrigger' ),
193 array( 'checkRcCurIdNullable' ),
194 array( 'checkPagelinkUniqueIndex' ),
195 array( 'checkRevUserFkey' ),
196 array( 'checkIpbAdress' ),
197 array( 'checkIwlPrefix' ),
198
199 # All FK columns should be deferred
200 array( 'changeFkeyDeferrable', 'archive', 'ar_user', 'mwuser(user_id) ON DELETE SET NULL' ),
201 array( 'changeFkeyDeferrable', 'categorylinks', 'cl_from', 'page(page_id) ON DELETE CASCADE' ),
202 array( 'changeFkeyDeferrable', 'externallinks', 'el_from', 'page(page_id) ON DELETE CASCADE' ),
203 array( 'changeFkeyDeferrable', 'filearchive', 'fa_deleted_user', 'mwuser(user_id) ON DELETE SET NULL' ),
204 array( 'changeFkeyDeferrable', 'filearchive', 'fa_user', 'mwuser(user_id) ON DELETE SET NULL' ),
205 array( 'changeFkeyDeferrable', 'image', 'img_user', 'mwuser(user_id) ON DELETE SET NULL' ),
206 array( 'changeFkeyDeferrable', 'imagelinks', 'il_from', 'page(page_id) ON DELETE CASCADE' ),
207 array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_by', 'mwuser(user_id) ON DELETE CASCADE' ),
208 array( 'changeFkeyDeferrable', 'ipblocks', 'ipb_user', 'mwuser(user_id) ON DELETE SET NULL' ),
209 array( 'changeFkeyDeferrable', 'langlinks', 'll_from', 'page(page_id) ON DELETE CASCADE' ),
210 array( 'changeFkeyDeferrable', 'logging', 'log_user', 'mwuser(user_id) ON DELETE SET NULL' ),
211 array( 'changeFkeyDeferrable', 'oldimage', 'oi_name', 'image(img_name) ON DELETE CASCADE ON UPDATE CASCADE' ),
212 array( 'changeFkeyDeferrable', 'oldimage', 'oi_user', 'mwuser(user_id) ON DELETE SET NULL' ),
213 array( 'changeFkeyDeferrable', 'pagelinks', 'pl_from', 'page(page_id) ON DELETE CASCADE' ),
214 array( 'changeFkeyDeferrable', 'page_props', 'pp_page', 'page (page_id) ON DELETE CASCADE' ),
215 array( 'changeFkeyDeferrable', 'page_restrictions', 'pr_page', 'page(page_id) ON DELETE CASCADE' ),
216 array( 'changeFkeyDeferrable', 'protected_titles', 'pt_user', 'mwuser(user_id) ON DELETE SET NULL' ),
217 array( 'changeFkeyDeferrable', 'recentchanges', 'rc_cur_id', 'page(page_id) ON DELETE SET NULL' ),
218 array( 'changeFkeyDeferrable', 'recentchanges', 'rc_user', 'mwuser(user_id) ON DELETE SET NULL' ),
219 array( 'changeFkeyDeferrable', 'redirect', 'rd_from', 'page(page_id) ON DELETE CASCADE' ),
220 array( 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ),
221 array( 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ),
222 array( 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ),
223 array( 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ),
224 array( 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ),
225 array( 'changeFkeyDeferrable', 'user_properties', 'up_user', 'mwuser(user_id) ON DELETE CASCADE' ),
226 array( 'changeFkeyDeferrable', 'watchlist', 'wl_user', 'mwuser(user_id) ON DELETE CASCADE' ),
227
228 # end
229 array( 'tsearchFixes' ),
230 );
231 }
232
233 protected function getOldGlobalUpdates() {
234 global $wgExtNewTables, $wgExtPGNewFields, $wgExtPGAlteredFields, $wgExtNewIndexes;
235
236 $updates = array();
237
238 # Add missing extension tables
239 foreach ( $wgExtNewTables as $tableRecord ) {
240 $updates[] = array(
241 'addTable', $tableRecord[0], $tableRecord[1], true
242 );
243 }
244
245 # Add missing extension fields
246 foreach ( $wgExtPGNewFields as $fieldRecord ) {
247 $updates[] = array(
248 'addPgField', $fieldRecord[0], $fieldRecord[1],
249 $fieldRecord[2]
250 );
251 }
252
253 # Change altered columns
254 foreach ( $wgExtPGAlteredFields as $fieldRecord ) {
255 $updates[] = array(
256 'changeField', $fieldRecord[0], $fieldRecord[1],
257 $fieldRecord[2]
258 );
259 }
260
261 # Add missing extension indexes
262 foreach ( $wgExtNewIndexes as $fieldRecord ) {
263 $updates[] = array(
264 'addPgExtIndex', $fieldRecord[0], $fieldRecord[1],
265 $fieldRecord[2]
266 );
267 }
268
269 return $updates;
270 }
271
272 protected function describeTable( $table ) {
273 global $wgDBmwschema;
274 $q = <<<END
275 SELECT attname, attnum FROM pg_namespace, pg_class, pg_attribute
276 WHERE pg_class.relnamespace = pg_namespace.oid
277 AND attrelid=pg_class.oid AND attnum > 0
278 AND relname=%s AND nspname=%s
279 END;
280 $res = $this->db->query( sprintf( $q,
281 $this->db->addQuotes( $table ),
282 $this->db->addQuotes( $wgDBmwschema ) ) );
283 if ( !$res ) {
284 return null;
285 }
286
287 $cols = array();
288 foreach ( $res as $r ) {
289 $cols[] = array(
290 "name" => $r[0],
291 "ord" => $r[1],
292 );
293 }
294 return $cols;
295 }
296
297 function describeIndex( $idx ) {
298 global $wgDBmwschema;
299
300 // first fetch the key (which is a list of columns ords) and
301 // the table the index applies to (an oid)
302 $q = <<<END
303 SELECT indkey, indrelid FROM pg_namespace, pg_class, pg_index
304 WHERE nspname=%s
305 AND pg_class.relnamespace = pg_namespace.oid
306 AND relname=%s
307 AND indexrelid=pg_class.oid
308 END;
309 $res = $this->db->query(
310 sprintf(
311 $q,
312 $this->db->addQuotes( $wgDBmwschema ),
313 $this->db->addQuotes( $idx )
314 )
315 );
316 if ( !$res ) {
317 return null;
318 }
319 if ( !( $r = $this->db->fetchRow( $res ) ) ) {
320 return null;
321 }
322
323 $indkey = $r[0];
324 $relid = intval( $r[1] );
325 $indkeys = explode( ' ', $indkey );
326
327 $colnames = array();
328 foreach ( $indkeys as $rid ) {
329 $query = <<<END
330 SELECT attname FROM pg_class, pg_attribute
331 WHERE attrelid=$relid
332 AND attnum=%d
333 AND attrelid=pg_class.oid
334 END;
335 $r2 = $this->db->query( sprintf( $query, $rid ) );
336 if ( !$r2 ) {
337 return null;
338 }
339 if ( !( $row2 = $this->db->fetchRow( $r2 ) ) ) {
340 return null;
341 }
342 $colnames[] = $row2[0];
343 }
344
345 return $colnames;
346 }
347
348 function fkeyDeltype( $fkey ) {
349 global $wgDBmwschema;
350 $q = <<<END
351 SELECT confdeltype FROM pg_constraint, pg_namespace
352 WHERE connamespace=pg_namespace.oid
353 AND nspname=%s
354 AND conname=%s;
355 END;
356 $r = $this->db->query(
357 sprintf(
358 $q,
359 $this->db->addQuotes( $wgDBmwschema ),
360 $this->db->addQuotes( $fkey )
361 )
362 );
363 if ( !( $row = $this->db->fetchRow( $r ) ) ) {
364 return null;
365 }
366 return $row[0];
367 }
368
369 function ruleDef( $table, $rule ) {
370 global $wgDBmwschema;
371 $q = <<<END
372 SELECT definition FROM pg_rules
373 WHERE schemaname = %s
374 AND tablename = %s
375 AND rulename = %s
376 END;
377 $r = $this->db->query(
378 sprintf(
379 $q,
380 $this->db->addQuotes( $wgDBmwschema ),
381 $this->db->addQuotes( $table ),
382 $this->db->addQuotes( $rule )
383 )
384 );
385 $row = $this->db->fetchRow( $r );
386 if ( !$row ) {
387 return null;
388 }
389 $d = $row[0];
390 return $d;
391 }
392
393 protected function addSequence( $ns ) {
394 if ( !$this->db->sequenceExists( $ns ) ) {
395 $this->output( "Creating sequence $ns\n" );
396 $this->db->query( "CREATE SEQUENCE $ns" );
397 }
398 }
399
400 protected function renameSequence( $old, $new ) {
401 if ( $this->db->sequenceExists( $new ) ) {
402 $this->output( "WARNING sequence $new already exists\n" );
403 return;
404 }
405 if ( $this->db->sequenceExists( $old ) ) {
406 $this->output( "Renaming sequence $old to $new\n" );
407 $this->db->query( "ALTER SEQUENCE $old RENAME TO $new" );
408 }
409 }
410
411 protected function renameTable( $old, $new ) {
412 if ( $this->db->tableExists( $old ) ) {
413 $this->output( "Renaming table $old to $new\n" );
414 $old = $this->db->addQuotes( $old );
415 $this->db->query( "ALTER TABLE $old RENAME TO $new" );
416 }
417 }
418
419 protected function addPgField( $table, $field, $type ) {
420 $fi = $this->db->fieldInfo( $table, $field );
421 if ( !is_null( $fi ) ) {
422 $this->output( "... column '$table.$field' already exists\n" );
423 return;
424 } else {
425 $this->output( "Adding column '$table.$field'\n" );
426 $this->db->query( "ALTER TABLE $table ADD $field $type" );
427 }
428 }
429
430 protected function changeField( $table, $field, $newtype, $default ) {
431 $fi = $this->db->fieldInfo( $table, $field );
432 if ( is_null( $fi ) ) {
433 $this->output( "... error: expected column $table.$field to exist\n" );
434 exit( 1 );
435 }
436
437 if ( $fi->type() === $newtype )
438 $this->output( "... column '$table.$field' is already of type '$newtype'\n" );
439 else {
440 $this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
441 $sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
442 if ( strlen( $default ) ) {
443 $res = array();
444 if ( preg_match( '/DEFAULT (.+)/', $default, $res ) ) {
445 $sqldef = "ALTER TABLE $table ALTER $field SET DEFAULT $res[1]";
446 $this->db->query( $sqldef );
447 $default = preg_replace( '/\s*DEFAULT .+/', '', $default );
448 }
449 $sql .= " USING $default";
450 }
451 $this->db->begin( __METHOD__ );
452 $this->db->query( $sql );
453 $this->db->commit( __METHOD__ );
454 }
455 }
456
457 protected function changeNullableField( $table, $field, $null ) {
458 $fi = $this->db->fieldInfo( $table, $field );
459 if ( is_null( $fi ) ) {
460 $this->output( "... error: expected column $table.$field to exist\n" );
461 exit( 1 );
462 }
463 if ( $fi->isNullable() ) {
464 # # It's NULL - does it need to be NOT NULL?
465 if ( 'NOT NULL' === $null ) {
466 $this->output( "Changing '$table.$field' to not allow NULLs\n" );
467 $this->db->query( "ALTER TABLE $table ALTER $field SET NOT NULL" );
468 } else {
469 $this->output( "... column '$table.$field' is already set as NULL\n" );
470 }
471 } else {
472 # # It's NOT NULL - does it need to be NULL?
473 if ( 'NULL' === $null ) {
474 $this->output( "Changing '$table.$field' to allow NULLs\n" );
475 $this->db->query( "ALTER TABLE $table ALTER $field DROP NOT NULL" );
476 }
477 else {
478 $this->output( "... column '$table.$field' is already set as NOT NULL\n" );
479 }
480 }
481 }
482
483 public function addPgIndex( $table, $index, $type ) {
484 if ( $this->db->indexExists( $table, $index ) ) {
485 $this->output( "... index '$index' on table '$table' already exists\n" );
486 } else {
487 $this->output( "Creating index '$index' on table '$table' $type\n" );
488 $this->db->query( "CREATE INDEX $index ON $table $type" );
489 }
490 }
491
492 public function addPgExtIndex( $table, $index, $type ) {
493 if ( $this->db->indexExists( $table, $index ) ) {
494 $this->output( "... index '$index' on table '$table' already exists\n" );
495 } else {
496 $this->output( "Creating index '$index' on table '$table'\n" );
497 if ( preg_match( '/^\(/', $type ) ) {
498 $this->db->query( "CREATE INDEX $index ON $table $type" );
499 } else {
500 $this->applyPatch( $type, true );
501 }
502 }
503 }
504
505 protected function changeFkeyDeferrable( $table, $field, $clause ) {
506 $fi = $this->db->fieldInfo( $table, $field );
507 if ( is_null( $fi ) ) {
508 $this->output( "WARNING! Column '$table.$field' does not exist but it should! Please report this.\n" );
509 return;
510 }
511 if ( $fi->is_deferred() && $fi->is_deferrable() ) {
512 return;
513 }
514 $this->output( "Altering column '$table.$field' to be DEFERRABLE INITIALLY DEFERRED\n" );
515 $conname = $fi->conname();
516 $command = "ALTER TABLE $table DROP CONSTRAINT $conname";
517 $this->db->query( $command );
518 $command = "ALTER TABLE $table ADD CONSTRAINT $conname FOREIGN KEY ($field) REFERENCES $clause DEFERRABLE INITIALLY DEFERRED";
519 $this->db->query( $command );
520 }
521
522 protected function convertArchive2() {
523 if ( $this->db->tableExists( "archive2" ) ) {
524 $this->output( "Converting 'archive2' back to normal archive table\n" );
525 if ( $this->db->ruleExists( 'archive', 'archive_insert' ) ) {
526 $this->output( "Dropping rule 'archive_insert'\n" );
527 $this->db->query( 'DROP RULE archive_insert ON archive' );
528 }
529 if ( $this->db->ruleExists( 'archive', 'archive_delete' ) ) {
530 $this->output( "Dropping rule 'archive_delete'\n" );
531 $this->db->query( 'DROP RULE archive_delete ON archive' );
532 }
533 $this->applyPatch( 'patch-remove-archive2.sql' );
534 } else {
535 $this->output( "... obsolete table 'archive2' does not exist\n" );
536 }
537 }
538
539 protected function checkOiDeleted() {
540 if ( $this->db->fieldInfo( 'oldimage', 'oi_deleted' )->type() !== 'smallint' ) {
541 $this->output( "Changing 'oldimage.oi_deleted' to type 'smallint'\n" );
542 $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted DROP DEFAULT" );
543 $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
544 $this->db->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" );
545 } else {
546 $this->output( "... column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
547 }
548 }
549
550 protected function checkOiNameConstraint() {
551 if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascaded" ) ) {
552 $this->output( "... table 'oldimage' has correct cascading delete/update foreign key to image\n" );
553 } else {
554 if ( $this->db->hasConstraint( "oldimage_oi_name_fkey" ) ) {
555 $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
556 }
557 if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascade" ) ) {
558 $this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
559 }
560 $this->output( "Making foreign key on table 'oldimage' (to image) a cascade delete/update\n" );
561 $this->db->query( "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded " .
562 "FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE CASCADE" );
563 }
564 }
565
566 protected function checkPageDeletedTrigger() {
567 if ( !$this->db->triggerExists( 'page', 'page_deleted' ) ) {
568 $this->output( "Adding function and trigger 'page_deleted' to table 'page'\n" );
569 $this->applyPatch( 'patch-page_deleted.sql' );
570 } else {
571 $this->output( "... table 'page' has 'page_deleted' trigger\n" );
572 }
573 }
574
575 protected function checkRcCurIdNullable(){
576 $fi = $this->db->fieldInfo( 'recentchanges', 'rc_cur_id' );
577 if ( !$fi->isNullable() ) {
578 $this->output( "Removing NOT NULL constraint from 'recentchanges.rc_cur_id'\n" );
579 $this->applyPatch( 'patch-rc_cur_id-not-null.sql' );
580 } else {
581 $this->output( "... column 'recentchanges.rc_cur_id' has a NOT NULL constraint\n" );
582 }
583 }
584
585 protected function checkPagelinkUniqueIndex() {
586 $pu = $this->describeIndex( 'pagelink_unique' );
587 if ( !is_null( $pu ) && ( $pu[0] != 'pl_from' || $pu[1] != 'pl_namespace' || $pu[2] != 'pl_title' ) ) {
588 $this->output( "Dropping obsolete version of index 'pagelink_unique index'\n" );
589 $this->db->query( 'DROP INDEX pagelink_unique' );
590 $pu = null;
591 } else {
592 $this->output( "... obsolete version of index 'pagelink_unique index' does not exist\n" );
593 }
594
595 if ( is_null( $pu ) ) {
596 $this->output( "Creating index 'pagelink_unique index'\n" );
597 $this->db->query( 'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' );
598 } else {
599 $this->output( "... index 'pagelink_unique_index' already exists\n" );
600 }
601 }
602
603 protected function checkRevUserFkey() {
604 if ( $this->fkeyDeltype( 'revision_rev_user_fkey' ) == 'r' ) {
605 $this->output( "... constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
606 } else {
607 $this->output( "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT\n" );
608 $this->applyPatch( 'patch-revision_rev_user_fkey.sql' );
609 }
610 }
611
612 protected function checkIpbAdress() {
613 if ( $this->db->indexExists( 'ipblocks', 'ipb_address' ) ) {
614 $this->output( "Removing deprecated index 'ipb_address'...\n" );
615 $this->db->query( 'DROP INDEX ipb_address' );
616 }
617 if ( $this->db->indexExists( 'ipblocks', 'ipb_address_unique' ) ) {
618 $this->output( "... have ipb_address_unique\n" );
619 } else {
620 $this->output( "Adding ipb_address_unique index\n" );
621 $this->applyPatch( 'patch-ipb_address_unique.sql' );
622 }
623 }
624
625 protected function checkIwlPrefix() {
626 if ( $this->db->indexExists( 'iwlinks', 'iwl_prefix' ) ) {
627 $this->output( "Replacing index 'iwl_prefix' with 'iwl_prefix_from_title'...\n" );
628 $this->applyPatch( 'patch-rename-iwl_prefix.sql' );
629 }
630 }
631
632 protected function tsearchFixes() {
633 # Tweak the page_title tsearch2 trigger to filter out slashes
634 # This is create or replace, so harmless to call if not needed
635 $this->output( "Refreshing ts2_page_title()...\n" );
636 $this->applyPatch( 'patch-ts2pagetitle.sql' );
637
638 # If the server is 8.3 or higher, rewrite the tsearch2 triggers
639 # in case they have the old 'default' versions
640 # Gather version numbers in case we need them
641 if ( $this->db->getServerVersion() >= 8.3 ) {
642 $this->output( "Rewriting tsearch2 triggers...\n" );
643 $this->applyPatch( 'patch-tsearch2funcs.sql' );
644 }
645 }
646 }