Index: trunk/phase3/maintenance/ibm_db2/tables.sql |
— | — | @@ -0,0 +1,604 @@ |
| 2 | +-- DB2
|
| 3 | +
|
| 4 | +-- SQL to create the initial tables for the MediaWiki database.
|
| 5 | +-- This is read and executed by the install script; you should
|
| 6 | +-- not have to run it by itself unless doing a manual install.
|
| 7 | +-- This is the IBM DB2 version.
|
| 8 | +-- For information about each table, please see the notes in maintenance/tables.sql
|
| 9 | +-- Please make sure all dollar-quoting uses $mw$ at the start of the line
|
| 10 | +-- TODO: Change CHAR/SMALLINT to BOOL (still used in a non-bool fashion in PHP code)
|
| 11 | +
|
| 12 | +
|
| 13 | +
|
| 14 | +
|
| 15 | +CREATE SEQUENCE user_user_id_seq AS INTEGER START WITH 0 INCREMENT BY 1;
|
| 16 | +CREATE TABLE mwuser ( -- replace reserved word 'user'
|
| 17 | + user_id INTEGER NOT NULL PRIMARY KEY, -- DEFAULT nextval('user_user_id_seq'),
|
| 18 | + user_name VARCHAR(255) NOT NULL UNIQUE,
|
| 19 | + user_real_name VARCHAR(255),
|
| 20 | + user_password clob(1K),
|
| 21 | + user_newpassword clob(1K),
|
| 22 | + user_newpass_time TIMESTAMP,
|
| 23 | + user_token VARCHAR(255),
|
| 24 | + user_email VARCHAR(255),
|
| 25 | + user_email_token VARCHAR(255),
|
| 26 | + user_email_token_expires TIMESTAMP,
|
| 27 | + user_email_authenticated TIMESTAMP,
|
| 28 | + user_options CLOB(64K),
|
| 29 | + user_touched TIMESTAMP,
|
| 30 | + user_registration TIMESTAMP,
|
| 31 | + user_editcount INTEGER
|
| 32 | +);
|
| 33 | +CREATE INDEX user_email_token_idx ON mwuser (user_email_token);
|
| 34 | +
|
| 35 | +-- Create a dummy user to satisfy fk contraints especially with revisions
|
| 36 | +INSERT INTO mwuser
|
| 37 | + VALUES (NEXTVAL FOR user_user_id_seq,'Anonymous','', NULL,NULL,CURRENT_TIMESTAMP,NULL, NULL,NULL,NULL,NULL, NULL,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0);
|
| 38 | +
|
| 39 | +CREATE TABLE user_groups (
|
| 40 | + ug_user INTEGER REFERENCES mwuser(user_id) ON DELETE CASCADE,
|
| 41 | + ug_group VARCHAR(255) NOT NULL
|
| 42 | +);
|
| 43 | +CREATE UNIQUE INDEX user_groups_unique ON user_groups (ug_user, ug_group);
|
| 44 | +
|
| 45 | +CREATE TABLE user_newtalk (
|
| 46 | + user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
|
| 47 | + user_ip VARCHAR(255),
|
| 48 | + user_last_timestamp TIMESTAMP
|
| 49 | +);
|
| 50 | +CREATE INDEX user_newtalk_id_idx ON user_newtalk (user_id);
|
| 51 | +CREATE INDEX user_newtalk_ip_idx ON user_newtalk (user_ip);
|
| 52 | +
|
| 53 | +
|
| 54 | +CREATE SEQUENCE page_page_id_seq;
|
| 55 | +CREATE TABLE page (
|
| 56 | + page_id INTEGER NOT NULL PRIMARY KEY, -- DEFAULT NEXT VALUE FOR user_user_id_seq,
|
| 57 | + page_namespace SMALLINT NOT NULL,
|
| 58 | + page_title VARCHAR(255) NOT NULL,
|
| 59 | + page_restrictions clob(1K),
|
| 60 | + page_counter BIGINT NOT NULL DEFAULT 0,
|
| 61 | + page_is_redirect SMALLINT NOT NULL DEFAULT 0,
|
| 62 | + page_is_new SMALLINT NOT NULL DEFAULT 0,
|
| 63 | + page_random NUMERIC(15,14) NOT NULL,
|
| 64 | + page_touched TIMESTAMP,
|
| 65 | + page_latest INTEGER NOT NULL, -- FK?
|
| 66 | + page_len INTEGER NOT NULL
|
| 67 | +);
|
| 68 | +CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
|
| 69 | +--CREATE INDEX page_main_title ON page (page_title) WHERE page_namespace = 0;
|
| 70 | +--CREATE INDEX page_talk_title ON page (page_title) WHERE page_namespace = 1;
|
| 71 | +--CREATE INDEX page_user_title ON page (page_title) WHERE page_namespace = 2;
|
| 72 | +--CREATE INDEX page_utalk_title ON page (page_title) WHERE page_namespace = 3;
|
| 73 | +--CREATE INDEX page_project_title ON page (page_title) WHERE page_namespace = 4;
|
| 74 | +CREATE INDEX page_random_idx ON page (page_random);
|
| 75 | +CREATE INDEX page_len_idx ON page (page_len);
|
| 76 | +
|
| 77 | +--CREATE FUNCTION page_deleted() RETURNS TRIGGER LANGUAGE plpgsql AS
|
| 78 | +--$mw$
|
| 79 | +--BEGIN
|
| 80 | +--DELETE FROM recentchanges WHERE rc_namespace = OLD.page_namespace AND rc_title = OLD.page_title;
|
| 81 | +--RETURN NULL;
|
| 82 | +--END;
|
| 83 | +--$mw$;
|
| 84 | +
|
| 85 | +--CREATE TRIGGER page_deleted AFTER DELETE ON page
|
| 86 | +-- FOR EACH ROW EXECUTE PROCEDURE page_deleted();
|
| 87 | +
|
| 88 | +CREATE SEQUENCE rev_rev_id_val;
|
| 89 | +CREATE TABLE revision (
|
| 90 | + rev_id INTEGER NOT NULL UNIQUE, --DEFAULT nextval('rev_rev_id_val'),
|
| 91 | + rev_page INTEGER REFERENCES page (page_id) ON DELETE CASCADE,
|
| 92 | + rev_text_id INTEGER, -- FK
|
| 93 | + rev_comment clob(1K), -- changed from VARCHAR(255)
|
| 94 | + rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT,
|
| 95 | + rev_user_text VARCHAR(255) NOT NULL,
|
| 96 | + rev_timestamp TIMESTAMP NOT NULL,
|
| 97 | + rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
|
| 98 | + rev_deleted SMALLINT NOT NULL DEFAULT 0,
|
| 99 | + rev_len INTEGER,
|
| 100 | + rev_parent_id INTEGER
|
| 101 | +);
|
| 102 | +CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
|
| 103 | +CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
|
| 104 | +CREATE INDEX rev_timestamp_idx ON revision (rev_timestamp);
|
| 105 | +CREATE INDEX rev_user_idx ON revision (rev_user);
|
| 106 | +CREATE INDEX rev_user_text_idx ON revision (rev_user_text);
|
| 107 | +
|
| 108 | +
|
| 109 | +CREATE SEQUENCE text_old_id_val;
|
| 110 | +CREATE TABLE pagecontent ( -- replaces reserved word 'text'
|
| 111 | + old_id INTEGER NOT NULL,
|
| 112 | + --PRIMARY KEY DEFAULT nextval('text_old_id_val'),
|
| 113 | + old_text CLOB(16M),
|
| 114 | + old_flags clob(1K)
|
| 115 | +);
|
| 116 | +
|
| 117 | +CREATE SEQUENCE pr_id_val;
|
| 118 | +CREATE TABLE page_restrictions (
|
| 119 | + pr_id INTEGER NOT NULL UNIQUE,
|
| 120 | + --DEFAULT nextval('pr_id_val'),
|
| 121 | + pr_page INTEGER NOT NULL
|
| 122 | + --(used to be nullable)
|
| 123 | + REFERENCES page (page_id) ON DELETE CASCADE,
|
| 124 | + pr_type VARCHAR(255) NOT NULL,
|
| 125 | + pr_level VARCHAR(255) NOT NULL,
|
| 126 | + pr_cascade SMALLINT NOT NULL,
|
| 127 | + pr_user INTEGER,
|
| 128 | + pr_expiry TIMESTAMP,
|
| 129 | + PRIMARY KEY (pr_page, pr_type)
|
| 130 | +);
|
| 131 | +--ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (pr_page,pr_type);
|
| 132 | +
|
| 133 | +CREATE TABLE page_props (
|
| 134 | + pp_page INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
|
| 135 | + pp_propname VARCHAR(255) NOT NULL,
|
| 136 | + pp_value CLOB(64K) NOT NULL,
|
| 137 | + PRIMARY KEY (pp_page,pp_propname)
|
| 138 | +);
|
| 139 | +--ALTER TABLE page_props ADD CONSTRAINT page_props_pk PRIMARY KEY (pp_page,pp_propname);
|
| 140 | +CREATE INDEX page_props_propname ON page_props (pp_propname);
|
| 141 | +
|
| 142 | +
|
| 143 | +
|
| 144 | +CREATE TABLE archive (
|
| 145 | + ar_namespace SMALLINT NOT NULL,
|
| 146 | + ar_title VARCHAR(255) NOT NULL,
|
| 147 | + ar_text CLOB(16M),
|
| 148 | + ar_page_id INTEGER,
|
| 149 | + ar_parent_id INTEGER,
|
| 150 | + ar_comment clob(1K),
|
| 151 | + ar_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 152 | + ar_user_text VARCHAR(255) NOT NULL,
|
| 153 | + ar_timestamp TIMESTAMP NOT NULL,
|
| 154 | + ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
|
| 155 | + ar_flags clob(1K),
|
| 156 | + ar_rev_id INTEGER,
|
| 157 | + ar_text_id INTEGER,
|
| 158 | + ar_deleted SMALLINT NOT NULL DEFAULT 0,
|
| 159 | + ar_len INTEGER
|
| 160 | +);
|
| 161 | +CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
|
| 162 | +CREATE INDEX archive_user_text ON archive (ar_user_text);
|
| 163 | +
|
| 164 | +
|
| 165 | +
|
| 166 | +CREATE TABLE redirect (
|
| 167 | + rd_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 168 | + rd_namespace SMALLINT NOT NULL,
|
| 169 | + rd_title VARCHAR(255) NOT NULL
|
| 170 | +);
|
| 171 | +CREATE INDEX redirect_ns_title ON redirect (rd_namespace,rd_title,rd_from);
|
| 172 | +
|
| 173 | +
|
| 174 | +CREATE TABLE pagelinks (
|
| 175 | + pl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 176 | + pl_namespace SMALLINT NOT NULL,
|
| 177 | + pl_title VARCHAR(255) NOT NULL
|
| 178 | +);
|
| 179 | +CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title);
|
| 180 | +
|
| 181 | +CREATE TABLE templatelinks (
|
| 182 | + tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 183 | + tl_namespace SMALLINT NOT NULL,
|
| 184 | + tl_title VARCHAR(255) NOT NULL
|
| 185 | +);
|
| 186 | +CREATE UNIQUE INDEX templatelinks_unique ON templatelinks (tl_namespace,tl_title,tl_from);
|
| 187 | +
|
| 188 | +CREATE TABLE imagelinks (
|
| 189 | + il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 190 | + il_to VARCHAR(255) NOT NULL
|
| 191 | +);
|
| 192 | +CREATE UNIQUE INDEX il_from ON imagelinks (il_to,il_from);
|
| 193 | +
|
| 194 | +CREATE TABLE categorylinks (
|
| 195 | + cl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 196 | + cl_to VARCHAR(255) NOT NULL,
|
| 197 | + cl_sortkey VARCHAR(255),
|
| 198 | + cl_timestamp TIMESTAMP NOT NULL
|
| 199 | +);
|
| 200 | +CREATE UNIQUE INDEX cl_from ON categorylinks (cl_from, cl_to);
|
| 201 | +CREATE INDEX cl_sortkey ON categorylinks (cl_to, cl_sortkey, cl_from);
|
| 202 | +
|
| 203 | +
|
| 204 | +
|
| 205 | +CREATE TABLE externallinks (
|
| 206 | + el_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
|
| 207 | + el_to VARCHAR(255) NOT NULL,
|
| 208 | + el_index VARCHAR(255) NOT NULL
|
| 209 | +);
|
| 210 | +CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
|
| 211 | +CREATE INDEX externallinks_index ON externallinks (el_index);
|
| 212 | +
|
| 213 | +CREATE TABLE langlinks (
|
| 214 | + ll_from INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
|
| 215 | + ll_lang VARCHAR(255),
|
| 216 | + ll_title VARCHAR(255)
|
| 217 | +);
|
| 218 | +CREATE UNIQUE INDEX langlinks_unique ON langlinks (ll_from,ll_lang);
|
| 219 | +CREATE INDEX langlinks_lang_title ON langlinks (ll_lang,ll_title);
|
| 220 | +
|
| 221 | +
|
| 222 | +CREATE TABLE site_stats (
|
| 223 | + ss_row_id INTEGER NOT NULL UNIQUE,
|
| 224 | + ss_total_views INTEGER DEFAULT 0,
|
| 225 | + ss_total_edits INTEGER DEFAULT 0,
|
| 226 | + ss_good_articles INTEGER DEFAULT 0,
|
| 227 | + ss_total_pages INTEGER DEFAULT -1,
|
| 228 | + ss_users INTEGER DEFAULT -1,
|
| 229 | + ss_admins INTEGER DEFAULT -1,
|
| 230 | + ss_images INTEGER DEFAULT 0
|
| 231 | +);
|
| 232 | +
|
| 233 | +CREATE TABLE hitcounter (
|
| 234 | + hc_id BIGINT NOT NULL
|
| 235 | +);
|
| 236 | +
|
| 237 | +CREATE SEQUENCE ipblocks_ipb_id_val;
|
| 238 | +CREATE TABLE ipblocks (
|
| 239 | + ipb_id INTEGER NOT NULL PRIMARY KEY,
|
| 240 | + --DEFAULT nextval('ipblocks_ipb_id_val'),
|
| 241 | + ipb_address VARCHAR(255),
|
| 242 | + ipb_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 243 | + ipb_by INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
|
| 244 | + ipb_by_text VARCHAR(255) NOT NULL DEFAULT '',
|
| 245 | + ipb_reason VARCHAR(255) NOT NULL,
|
| 246 | + ipb_timestamp TIMESTAMP NOT NULL,
|
| 247 | + ipb_auto SMALLINT NOT NULL DEFAULT 0,
|
| 248 | + ipb_anon_only SMALLINT NOT NULL DEFAULT 0,
|
| 249 | + ipb_create_account SMALLINT NOT NULL DEFAULT 1,
|
| 250 | + ipb_enable_autoblock SMALLINT NOT NULL DEFAULT 1,
|
| 251 | + ipb_expiry TIMESTAMP NOT NULL,
|
| 252 | + ipb_range_start VARCHAR(255),
|
| 253 | + ipb_range_end VARCHAR(255),
|
| 254 | + ipb_deleted SMALLINT NOT NULL DEFAULT 0,
|
| 255 | + ipb_block_email SMALLINT NOT NULL DEFAULT 0
|
| 256 | +
|
| 257 | +);
|
| 258 | +CREATE INDEX ipb_address ON ipblocks (ipb_address);
|
| 259 | +CREATE INDEX ipb_user ON ipblocks (ipb_user);
|
| 260 | +CREATE INDEX ipb_range ON ipblocks (ipb_range_start,ipb_range_end);
|
| 261 | +
|
| 262 | +
|
| 263 | +
|
| 264 | +CREATE TABLE image (
|
| 265 | + img_name VARCHAR(255) NOT NULL PRIMARY KEY,
|
| 266 | + img_size INTEGER NOT NULL,
|
| 267 | + img_width INTEGER NOT NULL,
|
| 268 | + img_height INTEGER NOT NULL,
|
| 269 | + img_metadata CLOB(16M) NOT NULL DEFAULT '',
|
| 270 | + img_bits SMALLINT,
|
| 271 | + img_media_type VARCHAR(255),
|
| 272 | + img_major_mime VARCHAR(255) DEFAULT 'unknown',
|
| 273 | + img_minor_mime VARCHAR(255) DEFAULT 'unknown',
|
| 274 | + img_description clob(1K) NOT NULL DEFAULT '',
|
| 275 | + img_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 276 | + img_user_text VARCHAR(255) NOT NULL DEFAULT '',
|
| 277 | + img_timestamp TIMESTAMP,
|
| 278 | + img_sha1 VARCHAR(255) NOT NULL DEFAULT ''
|
| 279 | +);
|
| 280 | +CREATE INDEX img_size_idx ON image (img_size);
|
| 281 | +CREATE INDEX img_timestamp_idx ON image (img_timestamp);
|
| 282 | +CREATE INDEX img_sha1 ON image (img_sha1);
|
| 283 | +
|
| 284 | +CREATE TABLE oldimage (
|
| 285 | + oi_name VARCHAR(255) NOT NULL,
|
| 286 | + oi_archive_name VARCHAR(255) NOT NULL,
|
| 287 | + oi_size INTEGER NOT NULL,
|
| 288 | + oi_width INTEGER NOT NULL,
|
| 289 | + oi_height INTEGER NOT NULL,
|
| 290 | + oi_bits SMALLINT NOT NULL,
|
| 291 | + oi_description clob(1K),
|
| 292 | + oi_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 293 | + oi_user_text VARCHAR(255) NOT NULL,
|
| 294 | + oi_timestamp TIMESTAMP NOT NULL,
|
| 295 | + oi_metadata CLOB(16M) NOT NULL DEFAULT '',
|
| 296 | + oi_media_type VARCHAR(255) ,
|
| 297 | + oi_major_mime VARCHAR(255) NOT NULL DEFAULT 'unknown',
|
| 298 | + oi_minor_mime VARCHAR(255) NOT NULL DEFAULT 'unknown',
|
| 299 | + oi_deleted SMALLINT NOT NULL DEFAULT 0,
|
| 300 | + oi_sha1 VARCHAR(255) NOT NULL DEFAULT '',
|
| 301 | + FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE
|
| 302 | +);
|
| 303 | +--ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascade FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE;
|
| 304 | +CREATE INDEX oi_name_timestamp ON oldimage (oi_name,oi_timestamp);
|
| 305 | +CREATE INDEX oi_name_archive_name ON oldimage (oi_name,oi_archive_name);
|
| 306 | +CREATE INDEX oi_sha1 ON oldimage (oi_sha1);
|
| 307 | +
|
| 308 | +
|
| 309 | +CREATE SEQUENCE filearchive_fa_id_seq;
|
| 310 | +CREATE TABLE filearchive (
|
| 311 | + fa_id INTEGER NOT NULL PRIMARY KEY,
|
| 312 | + --PRIMARY KEY DEFAULT nextval('filearchive_fa_id_seq'),
|
| 313 | + fa_name VARCHAR(255) NOT NULL,
|
| 314 | + fa_archive_name VARCHAR(255),
|
| 315 | + fa_storage_group VARCHAR(255),
|
| 316 | + fa_storage_key VARCHAR(255),
|
| 317 | + fa_deleted_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 318 | + fa_deleted_timestamp TIMESTAMP NOT NULL,
|
| 319 | + fa_deleted_reason VARCHAR(255),
|
| 320 | + fa_size INTEGER NOT NULL,
|
| 321 | + fa_width INTEGER NOT NULL,
|
| 322 | + fa_height INTEGER NOT NULL,
|
| 323 | + fa_metadata CLOB(16M) NOT NULL DEFAULT '',
|
| 324 | + fa_bits SMALLINT,
|
| 325 | + fa_media_type VARCHAR(255),
|
| 326 | + fa_major_mime VARCHAR(255) DEFAULT 'unknown',
|
| 327 | + fa_minor_mime VARCHAR(255) DEFAULT 'unknown',
|
| 328 | + fa_description clob(1K) NOT NULL,
|
| 329 | + fa_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 330 | + fa_user_text VARCHAR(255) NOT NULL,
|
| 331 | + fa_timestamp TIMESTAMP,
|
| 332 | + fa_deleted SMALLINT NOT NULL DEFAULT 0
|
| 333 | +);
|
| 334 | +CREATE INDEX fa_name_time ON filearchive (fa_name, fa_timestamp);
|
| 335 | +CREATE INDEX fa_dupe ON filearchive (fa_storage_group, fa_storage_key);
|
| 336 | +CREATE INDEX fa_notime ON filearchive (fa_deleted_timestamp);
|
| 337 | +CREATE INDEX fa_nouser ON filearchive (fa_deleted_user);
|
| 338 | +
|
| 339 | +CREATE SEQUENCE rc_rc_id_seq;
|
| 340 | +CREATE TABLE recentchanges (
|
| 341 | + rc_id INTEGER NOT NULL PRIMARY KEY,
|
| 342 | + --PRIMARY KEY DEFAULT nextval('rc_rc_id_seq'),
|
| 343 | + rc_timestamp TIMESTAMP NOT NULL,
|
| 344 | + rc_cur_time TIMESTAMP NOT NULL,
|
| 345 | + rc_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 346 | + rc_user_text VARCHAR(255) NOT NULL,
|
| 347 | + rc_namespace SMALLINT NOT NULL,
|
| 348 | + rc_title VARCHAR(255) NOT NULL,
|
| 349 | + rc_comment VARCHAR(255),
|
| 350 | + rc_minor SMALLINT NOT NULL DEFAULT 0,
|
| 351 | + rc_bot SMALLINT NOT NULL DEFAULT 0,
|
| 352 | + rc_new SMALLINT NOT NULL DEFAULT 0,
|
| 353 | + rc_cur_id INTEGER REFERENCES page(page_id) ON DELETE SET NULL,
|
| 354 | + rc_this_oldid INTEGER NOT NULL,
|
| 355 | + rc_last_oldid INTEGER NOT NULL,
|
| 356 | + rc_type SMALLINT NOT NULL DEFAULT 0,
|
| 357 | + rc_moved_to_ns SMALLINT,
|
| 358 | + rc_moved_to_title VARCHAR(255),
|
| 359 | + rc_patrolled SMALLINT NOT NULL DEFAULT 0,
|
| 360 | + rc_ip VARCHAR(255), -- was CIDR type
|
| 361 | + rc_old_len INTEGER,
|
| 362 | + rc_new_len INTEGER,
|
| 363 | + rc_deleted SMALLINT NOT NULL DEFAULT 0,
|
| 364 | + rc_logid INTEGER NOT NULL DEFAULT 0,
|
| 365 | + rc_log_type VARCHAR(255),
|
| 366 | + rc_log_action VARCHAR(255),
|
| 367 | + rc_params CLOB(64K)
|
| 368 | +);
|
| 369 | +CREATE INDEX rc_timestamp ON recentchanges (rc_timestamp);
|
| 370 | +CREATE INDEX rc_namespace_title ON recentchanges (rc_namespace, rc_title);
|
| 371 | +CREATE INDEX rc_cur_id ON recentchanges (rc_cur_id);
|
| 372 | +CREATE INDEX new_name_timestamp ON recentchanges (rc_new, rc_namespace, rc_timestamp);
|
| 373 | +CREATE INDEX rc_ip ON recentchanges (rc_ip);
|
| 374 | +
|
| 375 | +
|
| 376 | +
|
| 377 | +CREATE TABLE watchlist (
|
| 378 | + wl_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
|
| 379 | + wl_namespace SMALLINT NOT NULL DEFAULT 0,
|
| 380 | + wl_title VARCHAR(255) NOT NULL,
|
| 381 | + wl_notificationtimestamp TIMESTAMP
|
| 382 | +);
|
| 383 | +CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
|
| 384 | +
|
| 385 | +
|
| 386 | +CREATE TABLE math (
|
| 387 | + math_inputhash VARGRAPHIC(255) NOT NULL UNIQUE,
|
| 388 | + math_outputhash VARGRAPHIC(255) NOT NULL,
|
| 389 | + math_html_conservativeness SMALLINT NOT NULL,
|
| 390 | + math_html VARCHAR(255),
|
| 391 | + math_mathml VARCHAR(255)
|
| 392 | +);
|
| 393 | +
|
| 394 | +
|
| 395 | +CREATE TABLE interwiki (
|
| 396 | + iw_prefix VARCHAR(255) NOT NULL UNIQUE,
|
| 397 | + iw_url CLOB(64K) NOT NULL,
|
| 398 | + iw_local SMALLINT NOT NULL,
|
| 399 | + iw_trans SMALLINT NOT NULL DEFAULT 0
|
| 400 | +);
|
| 401 | +
|
| 402 | +
|
| 403 | +CREATE TABLE querycache (
|
| 404 | + qc_type VARCHAR(255) NOT NULL,
|
| 405 | + qc_value INTEGER NOT NULL,
|
| 406 | + qc_namespace SMALLINT NOT NULL,
|
| 407 | + qc_title VARCHAR(255) NOT NULL
|
| 408 | +);
|
| 409 | +CREATE INDEX querycache_type_value ON querycache (qc_type, qc_value);
|
| 410 | +
|
| 411 | +
|
| 412 | +
|
| 413 | +CREATE TABLE querycache_info (
|
| 414 | + qci_type VARCHAR(255) UNIQUE NOT NULL,
|
| 415 | + qci_timestamp TIMESTAMP
|
| 416 | +);
|
| 417 | +
|
| 418 | +
|
| 419 | +CREATE TABLE querycachetwo (
|
| 420 | + qcc_type VARCHAR(255) NOT NULL,
|
| 421 | + qcc_value INTEGER NOT NULL DEFAULT 0,
|
| 422 | + qcc_namespace INTEGER NOT NULL DEFAULT 0,
|
| 423 | + qcc_title VARCHAR(255) NOT NULL DEFAULT '',
|
| 424 | + qcc_namespacetwo INTEGER NOT NULL DEFAULT 0,
|
| 425 | + qcc_titletwo VARCHAR(255) NOT NULL DEFAULT ''
|
| 426 | +);
|
| 427 | +CREATE INDEX querycachetwo_type_value ON querycachetwo (qcc_type, qcc_value);
|
| 428 | +CREATE INDEX querycachetwo_title ON querycachetwo (qcc_type,qcc_namespace,qcc_title);
|
| 429 | +CREATE INDEX querycachetwo_titletwo ON querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
|
| 430 | +
|
| 431 | +CREATE TABLE objectcache (
|
| 432 | + keyname VARCHAR(255) NOT NULL UNIQUE, -- was nullable
|
| 433 | + value CLOB(16M) NOT NULL DEFAULT '',
|
| 434 | + exptime TIMESTAMP NOT NULL
|
| 435 | +);
|
| 436 | +CREATE INDEX objectcacache_exptime ON objectcache (exptime);
|
| 437 | +
|
| 438 | +
|
| 439 | +
|
| 440 | +CREATE TABLE transcache (
|
| 441 | + tc_url VARCHAR(255) NOT NULL UNIQUE,
|
| 442 | + tc_contents VARCHAR(255) NOT NULL,
|
| 443 | + tc_time TIMESTAMP NOT NULL
|
| 444 | +);
|
| 445 | +
|
| 446 | +CREATE SEQUENCE log_log_id_seq;
|
| 447 | +CREATE TABLE logging (
|
| 448 | + log_id INTEGER NOT NULL PRIMARY KEY,
|
| 449 | + --PRIMARY KEY DEFAULT nextval('log_log_id_seq'),
|
| 450 | + log_type VARCHAR(255) NOT NULL,
|
| 451 | + log_action VARCHAR(255) NOT NULL,
|
| 452 | + log_timestamp TIMESTAMP NOT NULL,
|
| 453 | + log_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 454 | + log_namespace SMALLINT NOT NULL,
|
| 455 | + log_title VARCHAR(255) NOT NULL,
|
| 456 | + log_comment VARCHAR(255),
|
| 457 | + log_params CLOB(64K),
|
| 458 | + log_deleted SMALLINT NOT NULL DEFAULT 0
|
| 459 | +);
|
| 460 | +CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
|
| 461 | +CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
|
| 462 | +CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
|
| 463 | +
|
| 464 | +CREATE SEQUENCE trackbacks_tb_id_seq;
|
| 465 | +CREATE TABLE trackbacks (
|
| 466 | + tb_id INTEGER NOT NULL PRIMARY KEY,
|
| 467 | + --PRIMARY KEY DEFAULT nextval('trackbacks_tb_id_seq'),
|
| 468 | + tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE,
|
| 469 | + tb_title VARCHAR(255) NOT NULL,
|
| 470 | + tb_url CLOB(64K) NOT NULL,
|
| 471 | + tb_ex VARCHAR(255),
|
| 472 | + tb_name VARCHAR(255)
|
| 473 | +);
|
| 474 | +CREATE INDEX trackback_page ON trackbacks (tb_page);
|
| 475 | +
|
| 476 | +
|
| 477 | +CREATE SEQUENCE job_job_id_seq;
|
| 478 | +CREATE TABLE job (
|
| 479 | + job_id INTEGER NOT NULL PRIMARY KEY,
|
| 480 | + --PRIMARY KEY DEFAULT nextval('job_job_id_seq'),
|
| 481 | + job_cmd VARCHAR(255) NOT NULL,
|
| 482 | + job_namespace SMALLINT NOT NULL,
|
| 483 | + job_title VARCHAR(255) NOT NULL,
|
| 484 | + job_params CLOB(64K) NOT NULL
|
| 485 | +);
|
| 486 | +CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
|
| 487 | +
|
| 488 | +
|
| 489 | +
|
| 490 | +-- Postgres' Tsearch2 dropped
|
| 491 | +--ALTER TABLE page ADD titlevector tsvector;
|
| 492 | +--CREATE FUNCTION ts2_page_title() RETURNS TRIGGER LANGUAGE plpgsql AS
|
| 493 | +--$mw$
|
| 494 | +--BEGIN
|
| 495 | +--IF TG_OP = 'INSERT' THEN
|
| 496 | +-- NEW.titlevector = to_tsvector('default',REPLACE(NEW.page_title,'/',' '));
|
| 497 | +--ELSIF NEW.page_title != OLD.page_title THEN
|
| 498 | +-- NEW.titlevector := to_tsvector('default',REPLACE(NEW.page_title,'/',' '));
|
| 499 | +--END IF;
|
| 500 | +--RETURN NEW;
|
| 501 | +--END;
|
| 502 | +--$mw$;
|
| 503 | +
|
| 504 | +--CREATE TRIGGER ts2_page_title BEFORE INSERT OR UPDATE ON page
|
| 505 | +-- FOR EACH ROW EXECUTE PROCEDURE ts2_page_title();
|
| 506 | +
|
| 507 | +
|
| 508 | +--ALTER TABLE pagecontent ADD textvector tsvector;
|
| 509 | +--CREATE FUNCTION ts2_page_text() RETURNS TRIGGER LANGUAGE plpgsql AS
|
| 510 | +--$mw$
|
| 511 | +--BEGIN
|
| 512 | +--IF TG_OP = 'INSERT' THEN
|
| 513 | +-- NEW.textvector = to_tsvector('default',NEW.old_text);
|
| 514 | +--ELSIF NEW.old_text != OLD.old_text THEN
|
| 515 | +-- NEW.textvector := to_tsvector('default',NEW.old_text);
|
| 516 | +--END IF;
|
| 517 | +--RETURN NEW;
|
| 518 | +--END;
|
| 519 | +--$mw$;
|
| 520 | +
|
| 521 | +--CREATE TRIGGER ts2_page_text BEFORE INSERT OR UPDATE ON pagecontent
|
| 522 | +-- FOR EACH ROW EXECUTE PROCEDURE ts2_page_text();
|
| 523 | +
|
| 524 | +-- These are added by the setup script due to version compatibility issues
|
| 525 | +-- If using 8.1, we switch from "gin" to "gist"
|
| 526 | +
|
| 527 | +--CREATE INDEX ts2_page_title ON page USING gin(titlevector);
|
| 528 | +--CREATE INDEX ts2_page_text ON pagecontent USING gin(textvector);
|
| 529 | +
|
| 530 | +--TODO
|
| 531 | +--CREATE FUNCTION add_interwiki (TEXT,INT,SMALLINT) RETURNS INT LANGUAGE SQL AS
|
| 532 | +--$mw$
|
| 533 | +-- INSERT INTO interwiki (iw_prefix, iw_url, iw_local) VALUES ($1,$2,$3);
|
| 534 | +-- SELECT 1;
|
| 535 | +--$mw$;
|
| 536 | +
|
| 537 | +-- hack implementation
|
| 538 | +-- should be replaced with OmniFind, Contains(), etc
|
| 539 | +CREATE TABLE searchindex (
|
| 540 | + si_page int NOT NULL,
|
| 541 | + si_title varchar(255) NOT NULL default '',
|
| 542 | + si_text clob NOT NULL
|
| 543 | +);
|
| 544 | +
|
| 545 | +-- This table is not used unless profiling is turned on
|
| 546 | +CREATE TABLE profiling (
|
| 547 | + pf_count INTEGER NOT NULL DEFAULT 0,
|
| 548 | + pf_time NUMERIC(18,10) NOT NULL DEFAULT 0,
|
| 549 | + pf_memory NUMERIC(18,10) NOT NULL DEFAULT 0,
|
| 550 | + pf_name VARCHAR(255) NOT NULL,
|
| 551 | + pf_server VARCHAR(255)
|
| 552 | +);
|
| 553 | +CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server);
|
| 554 | +
|
| 555 | +CREATE TABLE protected_titles (
|
| 556 | + pt_namespace SMALLINT NOT NULL,
|
| 557 | + pt_title VARCHAR(255) NOT NULL,
|
| 558 | + pt_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
|
| 559 | + pt_reason clob(1K),
|
| 560 | + pt_timestamp TIMESTAMP NOT NULL,
|
| 561 | + pt_expiry TIMESTAMP ,
|
| 562 | + pt_create_perm VARCHAR(255) NOT NULL DEFAULT ''
|
| 563 | +);
|
| 564 | +CREATE UNIQUE INDEX protected_titles_unique ON protected_titles(pt_namespace, pt_title);
|
| 565 | +
|
| 566 | +
|
| 567 | +
|
| 568 | +CREATE TABLE updatelog (
|
| 569 | + ul_key VARCHAR(255) NOT NULL PRIMARY KEY
|
| 570 | +);
|
| 571 | +
|
| 572 | +CREATE SEQUENCE category_id_seq;
|
| 573 | +CREATE TABLE category (
|
| 574 | + cat_id INTEGER NOT NULL PRIMARY KEY,
|
| 575 | + --PRIMARY KEY DEFAULT nextval('category_id_seq'),
|
| 576 | + cat_title VARCHAR(255) NOT NULL,
|
| 577 | + cat_pages INTEGER NOT NULL DEFAULT 0,
|
| 578 | + cat_subcats INTEGER NOT NULL DEFAULT 0,
|
| 579 | + cat_files INTEGER NOT NULL DEFAULT 0,
|
| 580 | + cat_hidden SMALLINT NOT NULL DEFAULT 0
|
| 581 | +);
|
| 582 | +CREATE UNIQUE INDEX category_title ON category(cat_title);
|
| 583 | +CREATE INDEX category_pages ON category(cat_pages);
|
| 584 | +
|
| 585 | +CREATE TABLE mediawiki_version (
|
| 586 | + type VARCHAR(255) NOT NULL,
|
| 587 | + mw_version VARCHAR(255) NOT NULL,
|
| 588 | + notes VARCHAR(255) ,
|
| 589 | +
|
| 590 | + pg_version VARCHAR(255) ,
|
| 591 | + pg_dbname VARCHAR(255) ,
|
| 592 | + pg_user VARCHAR(255) ,
|
| 593 | + pg_port VARCHAR(255) ,
|
| 594 | + mw_schema VARCHAR(255) ,
|
| 595 | + ts2_schema VARCHAR(255) ,
|
| 596 | + ctype VARCHAR(255) ,
|
| 597 | +
|
| 598 | + sql_version VARCHAR(255) ,
|
| 599 | + sql_date VARCHAR(255) ,
|
| 600 | + cdate TIMESTAMP NOT NULL DEFAULT CURRENT TIMESTAMP
|
| 601 | +);
|
| 602 | +
|
| 603 | +INSERT INTO mediawiki_version (type,mw_version,sql_version,sql_date)
|
| 604 | + VALUES ('Creation','??','$LastChangedRevision: 34049 $','$LastChangedDate: 2008-04-30 10:20:36 -0400 (Wed, 30 Apr 2008) $');
|
| 605 | +
|
Index: trunk/phase3/maintenance/ibm_db2/README |
— | — | @@ -0,0 +1,41 @@ |
| 2 | +== Syntax differences between other databases and IBM DB2 ==
|
| 3 | +{| border cellspacing=0 cellpadding=4
|
| 4 | +!MySQL!!IBM DB2
|
| 5 | +|-
|
| 6 | +
|
| 7 | +|SELECT 1 FROM $table LIMIT 1
|
| 8 | +|SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
|
| 9 | +WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
|
| 10 | +|-
|
| 11 | +|MySQL code tries to read one row and interprets lack of error as proof of existence.
|
| 12 | +|DB2 code counts the number of TABLES of that name in the database. There ought to be 1 for it to exist.
|
| 13 | +|-
|
| 14 | +|BEGIN
|
| 15 | +|(implicit)
|
| 16 | +|-
|
| 17 | +|TEXT
|
| 18 | +|VARCHAR(255) or CLOB
|
| 19 | +|-
|
| 20 | +|TIMESTAMPTZ
|
| 21 | +|TIMESTAMP
|
| 22 | +|-
|
| 23 | +|BYTEA
|
| 24 | +|VARGRAPHIC(255)
|
| 25 | +|-
|
| 26 | +|DEFAULT nextval('some_kind_of_sequence'),
|
| 27 | +|GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1),
|
| 28 | +|-
|
| 29 | +|CIDR
|
| 30 | +|VARCHAR(255)
|
| 31 | +|-
|
| 32 | +|LIMIT 10
|
| 33 | +|FETCH FIRST 10 ROWS ONLY
|
| 34 | +|-
|
| 35 | +|ROLLBACK TO
|
| 36 | +|ROLLBACK TO SAVEPOINT
|
| 37 | +|-
|
| 38 | +|RELEASE
|
| 39 | +|RELEASE SAVEPOINT
|
| 40 | +|}
|
| 41 | +== See also ==
|
| 42 | +*[http://ca.php.net/manual/en/function.db2-connect.php PHP Manual for DB2 functions] |
\ No newline at end of file |
Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -1693,6 +1693,11 @@ |
1694 | 1694 | define('TS_POSTGRES', 7); |
1695 | 1695 | |
1696 | 1696 | /** |
| 1697 | + * DB2 format time |
| 1698 | + */ |
| 1699 | +define('TS_DB2', 8); |
| 1700 | + |
| 1701 | +/** |
1697 | 1702 | * @param mixed $outputtype A timestamp in one of the supported formats, the |
1698 | 1703 | * function will autodetect which format is supplied |
1699 | 1704 | * and act accordingly. |
— | — | @@ -1753,6 +1758,8 @@ |
1754 | 1759 | return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00'; |
1755 | 1760 | case TS_POSTGRES: |
1756 | 1761 | return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT'; |
| 1762 | + case TS_DB2: |
| 1763 | + return gmdate( 'Y-m-d H:i:s', $uts); |
1757 | 1764 | default: |
1758 | 1765 | throw new MWException( 'wfTimestamp() called with illegal output type.'); |
1759 | 1766 | } |
Index: trunk/phase3/includes/db/DatabaseIbm_db2.php |
— | — | @@ -0,0 +1,1796 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This script is the IBM DB2 database abstraction layer |
| 5 | + * |
| 6 | + * See maintenance/ibm_db2/README for development notes and other specific information |
| 7 | + * @ingroup Database |
| 8 | + * @file |
| 9 | + * @author leo.petr+mediawiki@gmail.com |
| 10 | + */ |
| 11 | + |
| 12 | +/** |
| 13 | + * Utility class for generating blank objects |
| 14 | + * Intended as an equivalent to {} in Javascript |
| 15 | + * @ingroup Database |
| 16 | + */ |
| 17 | +class BlankObject { |
| 18 | +} |
| 19 | + |
| 20 | +/** |
| 21 | + * This represents a column in a DB2 database |
| 22 | + * @ingroup Database |
| 23 | + */ |
| 24 | +class IBM_DB2Field { |
| 25 | + private $name, $tablename, $type, $nullable, $max_length; |
| 26 | + |
| 27 | + /** |
| 28 | + * Builder method for the class |
| 29 | + * @param Object $db Database interface |
| 30 | + * @param string $table table name |
| 31 | + * @param string $field column name |
| 32 | + * @return IBM_DB2Field |
| 33 | + */ |
| 34 | + static function fromText($db, $table, $field) { |
| 35 | + global $wgDBmwschema; |
| 36 | + |
| 37 | + $q = <<<END |
| 38 | +SELECT |
| 39 | +lcase(coltype) AS typname, |
| 40 | +nulls AS attnotnull, length AS attlen |
| 41 | +FROM sysibm.syscolumns |
| 42 | +WHERE tbcreator=%s AND tbname=%s AND name=%s; |
| 43 | +END; |
| 44 | + $res = $db->query(sprintf($q, |
| 45 | + $db->addQuotes($wgDBmwschema), |
| 46 | + $db->addQuotes($table), |
| 47 | + $db->addQuotes($field))); |
| 48 | + $row = $db->fetchObject($res); |
| 49 | + if (!$row) |
| 50 | + return null; |
| 51 | + $n = new IBM_DB2Field; |
| 52 | + $n->type = $row->typname; |
| 53 | + $n->nullable = ($row->attnotnull == 'N'); |
| 54 | + $n->name = $field; |
| 55 | + $n->tablename = $table; |
| 56 | + $n->max_length = $row->attlen; |
| 57 | + return $n; |
| 58 | + } |
| 59 | + /** |
| 60 | + * Get column name |
| 61 | + * @return string column name |
| 62 | + */ |
| 63 | + function name() { return $this->name; } |
| 64 | + /** |
| 65 | + * Get table name |
| 66 | + * @return string table name |
| 67 | + */ |
| 68 | + function tableName() { return $this->tablename; } |
| 69 | + /** |
| 70 | + * Get column type |
| 71 | + * @return string column type |
| 72 | + */ |
| 73 | + function type() { return $this->type; } |
| 74 | + /** |
| 75 | + * Can column be null? |
| 76 | + * @return bool true or false |
| 77 | + */ |
| 78 | + function nullable() { return $this->nullable; } |
| 79 | + /** |
| 80 | + * How much can you fit in the column per row? |
| 81 | + * @return int length |
| 82 | + */ |
| 83 | + function maxLength() { return $this->max_length; } |
| 84 | +} |
| 85 | + |
| 86 | +/** |
| 87 | + * Wrapper around binary large objects |
| 88 | + * @ingroup Database |
| 89 | + */ |
| 90 | +class IBM_DB2Blob { |
| 91 | + private $mData; |
| 92 | + |
| 93 | + function __construct($data) { |
| 94 | + $this->mData = $data; |
| 95 | + } |
| 96 | + |
| 97 | + function getData() { |
| 98 | + return $this->mData; |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +/** |
| 103 | + * Primary database interface |
| 104 | + * @ingroup Database |
| 105 | + */ |
| 106 | +class DatabaseIbm_db2 extends Database { |
| 107 | + /* |
| 108 | + * Inherited members |
| 109 | + protected $mLastQuery = ''; |
| 110 | + protected $mPHPError = false; |
| 111 | + |
| 112 | + protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; |
| 113 | + protected $mOut, $mOpened = false; |
| 114 | + |
| 115 | + protected $mFailFunction; |
| 116 | + protected $mTablePrefix; |
| 117 | + protected $mFlags; |
| 118 | + protected $mTrxLevel = 0; |
| 119 | + protected $mErrorCount = 0; |
| 120 | + protected $mLBInfo = array(); |
| 121 | + protected $mFakeSlaveLag = null, $mFakeMaster = false; |
| 122 | + * |
| 123 | + */ |
| 124 | + |
| 125 | + /// Server port for uncataloged connections |
| 126 | + protected $mPort = NULL; |
| 127 | + /// Whether connection is cataloged |
| 128 | + protected $mCataloged = NULL; |
| 129 | + /// Schema for tables, stored procedures, triggers |
| 130 | + protected $mSchema = NULL; |
| 131 | + /// Whether the schema has been applied in this session |
| 132 | + protected $mSchemaSet = false; |
| 133 | + /// Result of last query |
| 134 | + protected $mLastResult = NULL; |
| 135 | + /// Number of rows affected by last INSERT/UPDATE/DELETE |
| 136 | + protected $mAffectedRows = NULL; |
| 137 | + /// Number of rows returned by last SELECT |
| 138 | + protected $mNumRows = NULL; |
| 139 | + |
| 140 | + |
| 141 | + const CATALOGED = "cataloged"; |
| 142 | + const UNCATALOGED = "uncataloged"; |
| 143 | + const USE_GLOBAL = "get from global"; |
| 144 | + |
| 145 | + /// Last sequence value used for a primary key |
| 146 | + protected $mInsertId = NULL; |
| 147 | + |
| 148 | + /* |
| 149 | + * These can be safely inherited |
| 150 | + * |
| 151 | + * Getter/Setter: (18) |
| 152 | + * failFunction |
| 153 | + * setOutputPage |
| 154 | + * bufferResults |
| 155 | + * ignoreErrors |
| 156 | + * trxLevel |
| 157 | + * errorCount |
| 158 | + * getLBInfo |
| 159 | + * setLBInfo |
| 160 | + * lastQuery |
| 161 | + * isOpen |
| 162 | + * setFlag |
| 163 | + * clearFlag |
| 164 | + * getFlag |
| 165 | + * getProperty |
| 166 | + * getDBname |
| 167 | + * getServer |
| 168 | + * tableNameCallback |
| 169 | + * tablePrefix |
| 170 | + * |
| 171 | + * Administrative: (8) |
| 172 | + * debug |
| 173 | + * installErrorHandler |
| 174 | + * restoreErrorHandler |
| 175 | + * connectionErrorHandler |
| 176 | + * reportConnectionError |
| 177 | + * sourceFile |
| 178 | + * sourceStream |
| 179 | + * replaceVars |
| 180 | + * |
| 181 | + * Database: (5) |
| 182 | + * query |
| 183 | + * set |
| 184 | + * selectField |
| 185 | + * generalizeSQL |
| 186 | + * update |
| 187 | + * strreplace |
| 188 | + * deadlockLoop |
| 189 | + * |
| 190 | + * Prepared Statement: 6 |
| 191 | + * prepare |
| 192 | + * freePrepared |
| 193 | + * execute |
| 194 | + * safeQuery |
| 195 | + * fillPrepared |
| 196 | + * fillPreparedArg |
| 197 | + * |
| 198 | + * Slave/Master: (4) |
| 199 | + * masterPosWait |
| 200 | + * getSlavePos |
| 201 | + * getMasterPos |
| 202 | + * getLag |
| 203 | + * |
| 204 | + * Generation: (9) |
| 205 | + * tableNames |
| 206 | + * tableNamesN |
| 207 | + * tableNamesWithUseIndexOrJOIN |
| 208 | + * escapeLike |
| 209 | + * delete |
| 210 | + * insertSelect |
| 211 | + * timestampOrNull |
| 212 | + * resultObject |
| 213 | + * aggregateValue |
| 214 | + * selectSQLText |
| 215 | + * selectRow |
| 216 | + * makeUpdateOptions |
| 217 | + * |
| 218 | + * Reflection: (1) |
| 219 | + * indexExists |
| 220 | + */ |
| 221 | + |
| 222 | + /* |
| 223 | + * These need to be implemented TODO |
| 224 | + * |
| 225 | + * Administrative: 7 / 7 |
| 226 | + * constructor [Done] |
| 227 | + * open [Done] |
| 228 | + * openCataloged [Done] |
| 229 | + * close [Done] |
| 230 | + * newFromParams [Done] |
| 231 | + * openUncataloged [Done] |
| 232 | + * setup_database [Done] |
| 233 | + * |
| 234 | + * Getter/Setter: 13 / 13 |
| 235 | + * cascadingDeletes [Done] |
| 236 | + * cleanupTriggers [Done] |
| 237 | + * strictIPs [Done] |
| 238 | + * realTimestamps [Done] |
| 239 | + * impliciGroupby [Done] |
| 240 | + * implicitOrderby [Done] |
| 241 | + * searchableIPs [Done] |
| 242 | + * functionalIndexes [Done] |
| 243 | + * getWikiID [Done] |
| 244 | + * isOpen [Done] |
| 245 | + * getServerVersion [Done] |
| 246 | + * getSoftwareLink [Done] |
| 247 | + * getSearchEngine [Done] |
| 248 | + * |
| 249 | + * Database driver wrapper: 23 / 23 |
| 250 | + * lastError [Done] |
| 251 | + * lastErrno [Done] |
| 252 | + * doQuery [Done] |
| 253 | + * tableExists [Done] |
| 254 | + * fetchObject [Done] |
| 255 | + * fetchRow [Done] |
| 256 | + * freeResult [Done] |
| 257 | + * numRows [Done] |
| 258 | + * numFields [Done] |
| 259 | + * fieldName [Done] |
| 260 | + * insertId [Done] |
| 261 | + * dataSeek [Done] |
| 262 | + * affectedRows [Done] |
| 263 | + * selectDB [Done] |
| 264 | + * strencode [Done] |
| 265 | + * conditional [Done] |
| 266 | + * wasDeadlock [Done] |
| 267 | + * ping [Done] |
| 268 | + * getStatus [Done] |
| 269 | + * setTimeout [Done] |
| 270 | + * lock [Done] |
| 271 | + * unlock [Done] |
| 272 | + * insert [Done] |
| 273 | + * select [Done] |
| 274 | + * |
| 275 | + * Slave/master: 2 / 2 |
| 276 | + * setFakeSlaveLag [Done] |
| 277 | + * setFakeMaster [Done] |
| 278 | + * |
| 279 | + * Reflection: 6 / 6 |
| 280 | + * fieldExists [Done] |
| 281 | + * indexInfo [Done] |
| 282 | + * fieldInfo [Done] |
| 283 | + * fieldType [Done] |
| 284 | + * indexUnique [Done] |
| 285 | + * textFieldSize [Done] |
| 286 | + * |
| 287 | + * Generation: 16 / 16 |
| 288 | + * tableName [Done] |
| 289 | + * addQuotes [Done] |
| 290 | + * makeList [Done] |
| 291 | + * makeSelectOptions [Done] |
| 292 | + * estimateRowCount [Done] |
| 293 | + * nextSequenceValue [Done] |
| 294 | + * useIndexClause [Done] |
| 295 | + * replace [Done] |
| 296 | + * deleteJoin [Done] |
| 297 | + * lowPriorityOption [Done] |
| 298 | + * limitResult [Done] |
| 299 | + * limitResultForUpdate [Done] |
| 300 | + * timestamp [Done] |
| 301 | + * encodeBlob [Done] |
| 302 | + * decodeBlob [Done] |
| 303 | + * buildConcat [Done] |
| 304 | + */ |
| 305 | + |
| 306 | + ###################################### |
| 307 | + # Getters and Setters |
| 308 | + ###################################### |
| 309 | + |
| 310 | + /** |
| 311 | + * Returns true if this database supports (and uses) cascading deletes |
| 312 | + */ |
| 313 | + function cascadingDeletes() { |
| 314 | + return true; |
| 315 | + } |
| 316 | + |
| 317 | + /** |
| 318 | + * Returns true if this database supports (and uses) triggers (e.g. on the page table) |
| 319 | + */ |
| 320 | + function cleanupTriggers() { |
| 321 | + return true; |
| 322 | + } |
| 323 | + |
| 324 | + /** |
| 325 | + * Returns true if this database is strict about what can be put into an IP field. |
| 326 | + * Specifically, it uses a NULL value instead of an empty string. |
| 327 | + */ |
| 328 | + function strictIPs() { |
| 329 | + return true; |
| 330 | + } |
| 331 | + |
| 332 | + /** |
| 333 | + * Returns true if this database uses timestamps rather than integers |
| 334 | + */ |
| 335 | + function realTimestamps() { |
| 336 | + return true; |
| 337 | + } |
| 338 | + |
| 339 | + /** |
| 340 | + * Returns true if this database does an implicit sort when doing GROUP BY |
| 341 | + */ |
| 342 | + function implicitGroupby() { |
| 343 | + return false; |
| 344 | + } |
| 345 | + |
| 346 | + /** |
| 347 | + * Returns true if this database does an implicit order by when the column has an index |
| 348 | + * For example: SELECT page_title FROM page LIMIT 1 |
| 349 | + */ |
| 350 | + function implicitOrderby() { |
| 351 | + return false; |
| 352 | + } |
| 353 | + |
| 354 | + /** |
| 355 | + * Returns true if this database can do a native search on IP columns |
| 356 | + * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; |
| 357 | + */ |
| 358 | + function searchableIPs() { |
| 359 | + return true; |
| 360 | + } |
| 361 | + |
| 362 | + /** |
| 363 | + * Returns true if this database can use functional indexes |
| 364 | + */ |
| 365 | + function functionalIndexes() { |
| 366 | + return true; |
| 367 | + } |
| 368 | + |
| 369 | + /** |
| 370 | + * Returns a unique string representing the wiki on the server |
| 371 | + */ |
| 372 | + function getWikiID() { |
| 373 | + if( $this->mSchema ) { |
| 374 | + return "{$this->mDBname}-{$this->mSchema}"; |
| 375 | + } else { |
| 376 | + return $this->mDBname; |
| 377 | + } |
| 378 | + } |
| 379 | + |
| 380 | + |
| 381 | + ###################################### |
| 382 | + # Setup |
| 383 | + ###################################### |
| 384 | + |
| 385 | + |
| 386 | + /** |
| 387 | + * |
| 388 | + * @param string $server hostname of database server |
| 389 | + * @param string $user username |
| 390 | + * @param string $password |
| 391 | + * @param string $dbName database name on the server |
| 392 | + * @param function $failFunction (optional) |
| 393 | + * @param integer $flags database behaviour flags (optional, unused) |
| 394 | + */ |
| 395 | + public function DatabaseIbm_db2($server = false, $user = false, $password = false, |
| 396 | + $dbName = false, $failFunction = false, $flags = 0, |
| 397 | + $schema = self::USE_GLOBAL ) |
| 398 | + { |
| 399 | + |
| 400 | + global $wgOut, $wgDBmwschema; |
| 401 | + # Can't get a reference if it hasn't been set yet |
| 402 | + if ( !isset( $wgOut ) ) { |
| 403 | + $wgOut = NULL; |
| 404 | + } |
| 405 | + $this->mOut =& $wgOut; |
| 406 | + $this->mFailFunction = $failFunction; |
| 407 | + $this->mFlags = DBO_TRX | $flags; |
| 408 | + |
| 409 | + if ( $schema == self::USE_GLOBAL ) { |
| 410 | + $this->mSchema = $wgDBmwschema; |
| 411 | + } |
| 412 | + else { |
| 413 | + $this->mSchema = $schema; |
| 414 | + } |
| 415 | + |
| 416 | + $this->open( $server, $user, $password, $dbName); |
| 417 | + } |
| 418 | + |
| 419 | + /** |
| 420 | + * Opens a database connection and returns it |
| 421 | + * Closes any existing connection |
| 422 | + * @return a fresh connection |
| 423 | + * @param string $server hostname |
| 424 | + * @param string $user |
| 425 | + * @param string $password |
| 426 | + * @param string $dbName database name |
| 427 | + */ |
| 428 | + public function open( $server, $user, $password, $dbName ) |
| 429 | + { |
| 430 | + // Load the port number |
| 431 | + global $wgDBport_db2, $wgDBcataloged; |
| 432 | + wfProfileIn( __METHOD__ ); |
| 433 | + |
| 434 | + // Load IBM DB2 driver if missing |
| 435 | + if (!@extension_loaded('ibm_db2')) { |
| 436 | + @dl('ibm_db2.so'); |
| 437 | + } |
| 438 | + // Test for IBM DB2 support, to avoid suppressed fatal error |
| 439 | + if ( !function_exists( 'db2_connect' ) ) { |
| 440 | + $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n"; |
| 441 | + wfDebug($error); |
| 442 | + $this->reportConnectionError($error); |
| 443 | + } |
| 444 | + |
| 445 | + if (!strlen($user)) { // Copied from Postgres |
| 446 | + return null; |
| 447 | + } |
| 448 | + |
| 449 | + // Close existing connection |
| 450 | + $this->close(); |
| 451 | + // Cache conn info |
| 452 | + $this->mServer = $server; |
| 453 | + $this->mPort = $port = $wgDBport_db2; |
| 454 | + $this->mUser = $user; |
| 455 | + $this->mPassword = $password; |
| 456 | + $this->mDBname = $dbName; |
| 457 | + $this->mCataloged = $cataloged = $wgDBcataloged; |
| 458 | + |
| 459 | + if ( $cataloged == self::CATALOGED ) { |
| 460 | + $this->openCataloged($dbName, $user, $password); |
| 461 | + } |
| 462 | + elseif ( $cataloged == self::UNCATALOGED ) { |
| 463 | + $this->openUncataloged($dbName, $user, $password, $server, $port); |
| 464 | + } |
| 465 | + // Don't do this |
| 466 | + // Not all MediaWiki code is transactional |
| 467 | + // Rather, turn it off in the begin function and turn on after a commit |
| 468 | + // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF); |
| 469 | + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); |
| 470 | + |
| 471 | + if ( $this->mConn == false ) { |
| 472 | + wfDebug( "DB connection error\n" ); |
| 473 | + wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); |
| 474 | + wfDebug( $this->lastError()."\n" ); |
| 475 | + return null; |
| 476 | + } |
| 477 | + |
| 478 | + $this->mOpened = true; |
| 479 | + $this->applySchema(); |
| 480 | + |
| 481 | + wfProfileOut( __METHOD__ ); |
| 482 | + return $this->mConn; |
| 483 | + } |
| 484 | + |
| 485 | + /** |
| 486 | + * Opens a cataloged database connection, sets mConn |
| 487 | + */ |
| 488 | + protected function openCataloged( $dbName, $user, $password ) |
| 489 | + { |
| 490 | + @$this->mConn = db2_connect($dbName, $user, $password); |
| 491 | + } |
| 492 | + |
| 493 | + /** |
| 494 | + * Opens an uncataloged database connection, sets mConn |
| 495 | + */ |
| 496 | + protected function openUncataloged( $dbName, $user, $password, $server, $port ) |
| 497 | + { |
| 498 | + $str = "DRIVER={IBM DB2 ODBC DRIVER};"; |
| 499 | + $str .= "DATABASE=$dbName;"; |
| 500 | + $str .= "HOSTNAME=$server;"; |
| 501 | + if ($port) $str .= "PORT=$port;"; |
| 502 | + $str .= "PROTOCOL=TCPIP;"; |
| 503 | + $str .= "UID=$user;"; |
| 504 | + $str .= "PWD=$password;"; |
| 505 | + |
| 506 | + @$this->mConn = db2_connect($str, $user, $password); |
| 507 | + } |
| 508 | + |
| 509 | + /** |
| 510 | + * Closes a database connection, if it is open |
| 511 | + * Returns success, true if already closed |
| 512 | + */ |
| 513 | + public function close() { |
| 514 | + $this->mOpened = false; |
| 515 | + if ( $this->mConn ) { |
| 516 | + if ($this->trxLevel() > 0) { |
| 517 | + $this->commit(); |
| 518 | + } |
| 519 | + return db2_close( $this->mConn ); |
| 520 | + } |
| 521 | + else { |
| 522 | + return true; |
| 523 | + } |
| 524 | + } |
| 525 | + |
| 526 | + /** |
| 527 | + * Returns a fresh instance of this class |
| 528 | + * @static |
| 529 | + * @return |
| 530 | + * @param string $server hostname of database server |
| 531 | + * @param string $user username |
| 532 | + * @param string $password |
| 533 | + * @param string $dbName database name on the server |
| 534 | + * @param function $failFunction (optional) |
| 535 | + * @param integer $flags database behaviour flags (optional, unused) |
| 536 | + */ |
| 537 | + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0) |
| 538 | + { |
| 539 | + return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags ); |
| 540 | + } |
| 541 | + |
| 542 | + /** |
| 543 | + * Retrieves the most current database error |
| 544 | + * Forces a database rollback |
| 545 | + */ |
| 546 | + public function lastError() { |
| 547 | + if ($this->lastError2()) { |
| 548 | + $this->rollback(); |
| 549 | + return true; |
| 550 | + } |
| 551 | + return false; |
| 552 | + } |
| 553 | + |
| 554 | + private function lastError2() { |
| 555 | + $connerr = db2_conn_errormsg(); |
| 556 | + if ($connerr) return $connerr; |
| 557 | + $stmterr = db2_stmt_errormsg(); |
| 558 | + if ($stmterr) return $stmterr; |
| 559 | + if ($this->mConn) return "No open connection."; |
| 560 | + if ($this->mOpened) return "No open connection allegedly."; |
| 561 | + |
| 562 | + return false; |
| 563 | + } |
| 564 | + |
| 565 | + /** |
| 566 | + * Get the last error number |
| 567 | + * Return 0 if no error |
| 568 | + * @return integer |
| 569 | + */ |
| 570 | + public function lastErrno() { |
| 571 | + $connerr = db2_conn_error(); |
| 572 | + if ($connerr) return $connerr; |
| 573 | + $stmterr = db2_stmt_error(); |
| 574 | + if ($stmterr) return $stmterr; |
| 575 | + return 0; |
| 576 | + } |
| 577 | + |
| 578 | + /** |
| 579 | + * Is a database connection open? |
| 580 | + * @return |
| 581 | + */ |
| 582 | + public function isOpen() { return $this->mOpened; } |
| 583 | + |
| 584 | + /** |
| 585 | + * The DBMS-dependent part of query() |
| 586 | + * @param $sql String: SQL query. |
| 587 | + * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure |
| 588 | + * @access private |
| 589 | + */ |
| 590 | + /*private*/ |
| 591 | + public function doQuery( $sql ) { |
| 592 | + //print "<li><pre>$sql</pre></li>"; |
| 593 | + // Switch into the correct namespace |
| 594 | + $this->applySchema(); |
| 595 | + |
| 596 | + $ret = db2_exec( $this->mConn, $sql ); |
| 597 | + if( !$ret ) { |
| 598 | + print "<br><pre>"; |
| 599 | + print $sql; |
| 600 | + print "</pre><br>"; |
| 601 | + $error = db2_stmt_errormsg(); |
| 602 | + throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) ); |
| 603 | + } |
| 604 | + $this->mLastResult = $ret; |
| 605 | + $this->mAffectedRows = NULL; // Not calculated until asked for |
| 606 | + return $ret; |
| 607 | + } |
| 608 | + |
| 609 | + /** |
| 610 | + * @return string Version information from the database |
| 611 | + */ |
| 612 | + public function getServerVersion() { |
| 613 | + $info = db2_server_info( $this->mConn ); |
| 614 | + return $info->DBMS_VER; |
| 615 | + } |
| 616 | + |
| 617 | + /** |
| 618 | + * Queries whether a given table exists |
| 619 | + * @return boolean |
| 620 | + */ |
| 621 | + public function tableExists( $table ) { |
| 622 | + $schema = $this->mSchema; |
| 623 | + $sql = <<< EOF |
| 624 | +SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST |
| 625 | +WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema' |
| 626 | +EOF; |
| 627 | + $res = $this->query( $sql ); |
| 628 | + if (!$res) return false; |
| 629 | + |
| 630 | + // If the table exists, there should be one of it |
| 631 | + @$row = $this->fetchRow($res); |
| 632 | + $count = $row[0]; |
| 633 | + if ($count == '1' or $count == 1) { |
| 634 | + return true; |
| 635 | + } |
| 636 | + |
| 637 | + return false; |
| 638 | + } |
| 639 | + |
| 640 | + /** |
| 641 | + * Fetch the next row from the given result object, in object form. |
| 642 | + * Fields can be retrieved with $row->fieldname, with fields acting like |
| 643 | + * member variables. |
| 644 | + * |
| 645 | + * @param $res SQL result object as returned from Database::query(), etc. |
| 646 | + * @return DB2 row object |
| 647 | + * @throws DBUnexpectedError Thrown if the database returns an error |
| 648 | + */ |
| 649 | + public function fetchObject( $res ) { |
| 650 | + if ( $res instanceof ResultWrapper ) { |
| 651 | + $res = $res->result; |
| 652 | + } |
| 653 | + @$row = db2_fetch_object( $res ); |
| 654 | + if( $this->lastErrno() ) { |
| 655 | + throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); |
| 656 | + } |
| 657 | + // Make field names lowercase for compatibility with MySQL |
| 658 | + if ($row) |
| 659 | + { |
| 660 | + $row2 = new BlankObject(); |
| 661 | + foreach ($row as $key => $value) |
| 662 | + { |
| 663 | + $keyu = strtolower($key); |
| 664 | + $row2->$keyu = $value; |
| 665 | + } |
| 666 | + $row = $row2; |
| 667 | + } |
| 668 | + return $row; |
| 669 | + } |
| 670 | + |
| 671 | + /** |
| 672 | + * Fetch the next row from the given result object, in associative array |
| 673 | + * form. Fields are retrieved with $row['fieldname']. |
| 674 | + * |
| 675 | + * @param $res SQL result object as returned from Database::query(), etc. |
| 676 | + * @return DB2 row object |
| 677 | + * @throws DBUnexpectedError Thrown if the database returns an error |
| 678 | + */ |
| 679 | + public function fetchRow( $res ) { |
| 680 | + if ( $res instanceof ResultWrapper ) { |
| 681 | + $res = $res->result; |
| 682 | + } |
| 683 | + @$row = db2_fetch_array( $res ); |
| 684 | + if ( $this->lastErrno() ) { |
| 685 | + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) ); |
| 686 | + } |
| 687 | + return $row; |
| 688 | + }
|
| 689 | +
|
| 690 | + /**
|
| 691 | + * Override if introduced to base Database class
|
| 692 | + */
|
| 693 | + public function initial_setup() {
|
| 694 | + // do nothing
|
| 695 | + } |
| 696 | + |
| 697 | + /** |
| 698 | + * Create tables, stored procedures, and so on |
| 699 | + */ |
| 700 | + public function setup_database() { |
| 701 | + // Timeout was being changed earlier due to mysterious crashes |
| 702 | + // Changing it now may cause more problems than not changing it |
| 703 | + //set_time_limit(240); |
| 704 | + try { |
| 705 | + // TODO: switch to root login if available |
| 706 | + |
| 707 | + // Switch into the correct namespace |
| 708 | + $this->applySchema(); |
| 709 | + $this->begin(); |
| 710 | + |
| 711 | + $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this); |
| 712 | + $res = null; |
| 713 | + |
| 714 | + // TODO: update mediawiki_version table |
| 715 | + |
| 716 | + // TODO: populate interwiki links |
| 717 | + |
| 718 | + $this->commit(); |
| 719 | + } |
| 720 | + catch (MWException $mwe) |
| 721 | + { |
| 722 | + print "<br><pre>$mwe</pre><br>"; |
| 723 | + } |
| 724 | + } |
| 725 | +
|
| 726 | + /**
|
| 727 | + * Escapes strings |
| 728 | + * Doesn't escape numbers
|
| 729 | + * @param string s string to escape
|
| 730 | + * @return escaped string
|
| 731 | + */ |
| 732 | + public function addQuotes( $s ) { |
| 733 | + //wfDebug("DB2::addQuotes($s)\n"); |
| 734 | + if ( is_null( $s ) ) { |
| 735 | + return "NULL"; |
| 736 | + } else if ($s instanceof Blob) { |
| 737 | + return "'".$s->fetch($s)."'"; |
| 738 | + } |
| 739 | + $s = $this->strencode($s); |
| 740 | + if ( is_numeric($s) ) { |
| 741 | + return $s; |
| 742 | + } |
| 743 | + else { |
| 744 | + return "'$s'"; |
| 745 | + } |
| 746 | + } |
| 747 | + |
| 748 | + /** |
| 749 | + * Escapes strings |
| 750 | + * Only escapes numbers going into non-numeric fields |
| 751 | + * @param string s string to escape |
| 752 | + * @return escaped string |
| 753 | + */ |
| 754 | + public function addQuotesSmart( $table, $field, $s ) { |
| 755 | + if ( is_null( $s ) ) { |
| 756 | + return "NULL"; |
| 757 | + } else if ($s instanceof Blob) { |
| 758 | + return "'".$s->fetch($s)."'"; |
| 759 | + } |
| 760 | + $s = $this->strencode($s); |
| 761 | + if ( is_numeric($s) ) { |
| 762 | + // Check with the database if the column is actually numeric |
| 763 | + // This allows for numbers in titles, etc |
| 764 | + $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY"); |
| 765 | + $type = db2_field_type($res, strtoupper($field)); |
| 766 | + if ( $this->is_numeric_type( $type ) ) { |
| 767 | + //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n"); |
| 768 | + return $s; |
| 769 | + } |
| 770 | + else { |
| 771 | + wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n"); |
| 772 | + return "'$s'"; |
| 773 | + } |
| 774 | + } |
| 775 | + else { |
| 776 | + return "'$s'"; |
| 777 | + } |
| 778 | + } |
| 779 | + |
| 780 | + /** |
| 781 | + * Verifies that a DB2 column/field type is numeric |
| 782 | + * @return bool true if numeric |
| 783 | + * @param string $type DB2 column type |
| 784 | + */ |
| 785 | + public function is_numeric_type( $type ) { |
| 786 | + switch (strtoupper($type)) { |
| 787 | + case 'SMALLINT': |
| 788 | + case 'INTEGER': |
| 789 | + case 'INT': |
| 790 | + case 'BIGINT': |
| 791 | + case 'DECIMAL': |
| 792 | + case 'REAL': |
| 793 | + case 'DOUBLE': |
| 794 | + case 'DECFLOAT': |
| 795 | + return true; |
| 796 | + } |
| 797 | + return false; |
| 798 | + } |
| 799 | + |
| 800 | + /** |
| 801 | + * Alias for addQuotes() |
| 802 | + * @param string s string to escape |
| 803 | + * @return escaped string |
| 804 | + */ |
| 805 | + public function strencode( $s ) { |
| 806 | + // Bloody useless function |
| 807 | + // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a. |
| 808 | + // But also necessary |
| 809 | + $s = db2_escape_string($s); |
| 810 | + // Wide characters are evil -- some of them look like ' |
| 811 | + $s = utf8_encode($s); |
| 812 | + // Fix its stupidity |
| 813 | + $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r'); |
| 814 | + $to = array("\\", "''", "\n", "\t", '"', "\r"); |
| 815 | + $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping |
| 816 | + return $s; |
| 817 | + } |
| 818 | +
|
| 819 | + /**
|
| 820 | + * Switch into the database schema
|
| 821 | + */ |
| 822 | + protected function applySchema() { |
| 823 | + if ( !($this->mSchemaSet) ) {
|
| 824 | + $this->mSchemaSet = true; |
| 825 | + $this->begin(); |
| 826 | + $this->doQuery("SET SCHEMA = $this->mSchema");
|
| 827 | + $this->commit(); |
| 828 | + } |
| 829 | + }
|
| 830 | +
|
| 831 | + /**
|
| 832 | + * Start a transaction (mandatory)
|
| 833 | + */
|
| 834 | + public function begin() {
|
| 835 | + // turn off auto-commit |
| 836 | + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF); |
| 837 | + $this->mTrxLevel = 1;
|
| 838 | + }
|
| 839 | +
|
| 840 | + /**
|
| 841 | + * End a transaction |
| 842 | + * Must have a preceding begin()
|
| 843 | + */
|
| 844 | + public function commit() {
|
| 845 | + db2_commit($this->mConn); |
| 846 | + // turn auto-commit back on |
| 847 | + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); |
| 848 | + $this->mTrxLevel = 0;
|
| 849 | + }
|
| 850 | +
|
| 851 | + /**
|
| 852 | + * Cancel a transaction
|
| 853 | + */
|
| 854 | + public function rollback() {
|
| 855 | + db2_rollback($this->mConn); |
| 856 | + // turn auto-commit back on |
| 857 | + // not sure if this is appropriate |
| 858 | + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); |
| 859 | + $this->mTrxLevel = 0;
|
| 860 | + } |
| 861 | + |
| 862 | + /** |
| 863 | + * Makes an encoded list of strings from an array |
| 864 | + * $mode: |
| 865 | + * LIST_COMMA - comma separated, no field names |
| 866 | + * LIST_AND - ANDed WHERE clause (without the WHERE) |
| 867 | + * LIST_OR - ORed WHERE clause (without the WHERE) |
| 868 | + * LIST_SET - comma separated with field names, like a SET clause |
| 869 | + * LIST_NAMES - comma separated field names |
| 870 | + */ |
| 871 | + public function makeList( $a, $mode = LIST_COMMA ) { |
| 872 | + wfDebug("DB2::makeList()\n"); |
| 873 | + if ( !is_array( $a ) ) { |
| 874 | + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); |
| 875 | + } |
| 876 | + |
| 877 | + $first = true; |
| 878 | + $list = ''; |
| 879 | + foreach ( $a as $field => $value ) { |
| 880 | + if ( !$first ) { |
| 881 | + if ( $mode == LIST_AND ) { |
| 882 | + $list .= ' AND '; |
| 883 | + } elseif($mode == LIST_OR) { |
| 884 | + $list .= ' OR '; |
| 885 | + } else { |
| 886 | + $list .= ','; |
| 887 | + } |
| 888 | + } else { |
| 889 | + $first = false; |
| 890 | + } |
| 891 | + if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { |
| 892 | + $list .= "($value)"; |
| 893 | + } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { |
| 894 | + $list .= "$value"; |
| 895 | + } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { |
| 896 | + if( count( $value ) == 0 ) { |
| 897 | + throw new MWException( __METHOD__.': empty input' ); |
| 898 | + } elseif( count( $value ) == 1 ) { |
| 899 | + // Special-case single values, as IN isn't terribly efficient |
| 900 | + // Don't necessarily assume the single key is 0; we don't |
| 901 | + // enforce linear numeric ordering on other arrays here. |
| 902 | + $value = array_values( $value ); |
| 903 | + $list .= $field." = ".$this->addQuotes( $value[0] ); |
| 904 | + } else { |
| 905 | + $list .= $field." IN (".$this->makeList($value).") "; |
| 906 | + } |
| 907 | + } elseif( is_null($value) ) { |
| 908 | + if ( $mode == LIST_AND || $mode == LIST_OR ) { |
| 909 | + $list .= "$field IS "; |
| 910 | + } elseif ( $mode == LIST_SET ) { |
| 911 | + $list .= "$field = "; |
| 912 | + } |
| 913 | + $list .= 'NULL'; |
| 914 | + } else { |
| 915 | + if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { |
| 916 | + $list .= "$field = "; |
| 917 | + } |
| 918 | + if ( $mode == LIST_NAMES ) { |
| 919 | + $list .= $value; |
| 920 | + } |
| 921 | + // Leo: Can't insert quoted numbers into numeric columns |
| 922 | + // (?) Might cause other problems. May have to check column type before insertion. |
| 923 | + else if ( is_numeric($value) ) { |
| 924 | + $list .= $value; |
| 925 | + } |
| 926 | + else { |
| 927 | + $list .= $this->addQuotes( $value ); |
| 928 | + } |
| 929 | + } |
| 930 | + } |
| 931 | + return $list; |
| 932 | + } |
| 933 | + |
| 934 | + /** |
| 935 | + * Makes an encoded list of strings from an array |
| 936 | + * Quotes numeric values being inserted into non-numeric fields |
| 937 | + * @return string |
| 938 | + * @param string $table name of the table |
| 939 | + * @param array $a list of values |
| 940 | + * @param $mode: |
| 941 | + * LIST_COMMA - comma separated, no field names |
| 942 | + * LIST_AND - ANDed WHERE clause (without the WHERE) |
| 943 | + * LIST_OR - ORed WHERE clause (without the WHERE) |
| 944 | + * LIST_SET - comma separated with field names, like a SET clause |
| 945 | + * LIST_NAMES - comma separated field names |
| 946 | + */ |
| 947 | + public function makeListSmart( $table, $a, $mode = LIST_COMMA ) { |
| 948 | + if ( !is_array( $a ) ) { |
| 949 | + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); |
| 950 | + } |
| 951 | + |
| 952 | + $first = true; |
| 953 | + $list = ''; |
| 954 | + foreach ( $a as $field => $value ) { |
| 955 | + if ( !$first ) { |
| 956 | + if ( $mode == LIST_AND ) { |
| 957 | + $list .= ' AND '; |
| 958 | + } elseif($mode == LIST_OR) { |
| 959 | + $list .= ' OR '; |
| 960 | + } else { |
| 961 | + $list .= ','; |
| 962 | + } |
| 963 | + } else { |
| 964 | + $first = false; |
| 965 | + } |
| 966 | + if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { |
| 967 | + $list .= "($value)"; |
| 968 | + } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { |
| 969 | + $list .= "$value"; |
| 970 | + } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { |
| 971 | + if( count( $value ) == 0 ) { |
| 972 | + throw new MWException( __METHOD__.': empty input' ); |
| 973 | + } elseif( count( $value ) == 1 ) { |
| 974 | + // Special-case single values, as IN isn't terribly efficient |
| 975 | + // Don't necessarily assume the single key is 0; we don't |
| 976 | + // enforce linear numeric ordering on other arrays here. |
| 977 | + $value = array_values( $value ); |
| 978 | + $list .= $field." = ".$this->addQuotes( $value[0] ); |
| 979 | + } else { |
| 980 | + $list .= $field." IN (".$this->makeList($value).") "; |
| 981 | + } |
| 982 | + } elseif( is_null($value) ) { |
| 983 | + if ( $mode == LIST_AND || $mode == LIST_OR ) { |
| 984 | + $list .= "$field IS "; |
| 985 | + } elseif ( $mode == LIST_SET ) { |
| 986 | + $list .= "$field = "; |
| 987 | + } |
| 988 | + $list .= 'NULL'; |
| 989 | + } else { |
| 990 | + if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { |
| 991 | + $list .= "$field = "; |
| 992 | + } |
| 993 | + if ( $mode == LIST_NAMES ) { |
| 994 | + $list .= $value; |
| 995 | + } |
| 996 | + else { |
| 997 | + $list .= $this->addQuotesSmart( $table, $field, $value ); |
| 998 | + } |
| 999 | + } |
| 1000 | + } |
| 1001 | + return $list; |
| 1002 | + } |
| 1003 | + |
| 1004 | + /** |
| 1005 | + * Construct a LIMIT query with optional offset |
| 1006 | + * This is used for query pages |
| 1007 | + * $sql string SQL query we will append the limit too |
| 1008 | + * $limit integer the SQL limit |
| 1009 | + * $offset integer the SQL offset (default false) |
| 1010 | + */ |
| 1011 | + public function limitResult($sql, $limit, $offset=false) { |
| 1012 | + if( !is_numeric($limit) ) { |
| 1013 | + throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); |
| 1014 | + } |
| 1015 | + if( $offset ) { |
| 1016 | + wfDebug("Offset parameter not supported in limitResult()\n"); |
| 1017 | + } |
| 1018 | + // TODO implement proper offset handling |
| 1019 | + // idea: get all the rows between 0 and offset, advance cursor to offset |
| 1020 | + return "$sql FETCH FIRST $limit ROWS ONLY "; |
| 1021 | + } |
| 1022 | + |
| 1023 | + /** |
| 1024 | + * Handle reserved keyword replacement in table names |
| 1025 | + * @return |
| 1026 | + * @param $name Object |
| 1027 | + */ |
| 1028 | + public function tableName( $name ) { |
| 1029 | + # Replace reserved words with better ones |
| 1030 | + switch( $name ) { |
| 1031 | + case 'user': |
| 1032 | + return 'mwuser'; |
| 1033 | + case 'text': |
| 1034 | + return 'pagecontent'; |
| 1035 | + default: |
| 1036 | + return $name; |
| 1037 | + } |
| 1038 | + } |
| 1039 | + |
| 1040 | + /** |
| 1041 | + * Generates a timestamp in an insertable format |
| 1042 | + * @return string timestamp value |
| 1043 | + * @param timestamp $ts |
| 1044 | + */ |
| 1045 | + public function timestamp( $ts=0 ) { |
| 1046 | + // TS_MW cannot be easily distinguished from an integer |
| 1047 | + return wfTimestamp(TS_DB2,$ts); |
| 1048 | + } |
| 1049 | + |
| 1050 | + /** |
| 1051 | + * Return the next in a sequence, save the value for retrieval via insertId() |
| 1052 | + * @param string seqName Name of a defined sequence in the database |
| 1053 | + * @return next value in that sequence |
| 1054 | + */ |
| 1055 | + public function nextSequenceValue( $seqName ) { |
| 1056 | + $safeseq = preg_replace( "/'/", "''", $seqName ); |
| 1057 | + $res = $this->query( "VALUES NEXTVAL FOR $safeseq" ); |
| 1058 | + $row = $this->fetchRow( $res ); |
| 1059 | + $this->mInsertId = $row[0]; |
| 1060 | + $this->freeResult( $res ); |
| 1061 | + return $this->mInsertId; |
| 1062 | + } |
| 1063 | + |
| 1064 | + /** |
| 1065 | + * This must be called after nextSequenceVal |
| 1066 | + * @return Last sequence value used as a primary key |
| 1067 | + */ |
| 1068 | + public function insertId() { |
| 1069 | + return $this->mInsertId; |
| 1070 | + } |
| 1071 | + |
| 1072 | + /** |
| 1073 | + * INSERT wrapper, inserts an array into a table |
| 1074 | + * |
| 1075 | + * $args may be a single associative array, or an array of these with numeric keys, |
| 1076 | + * for multi-row insert |
| 1077 | + * |
| 1078 | + * @param array $table String: Name of the table to insert to. |
| 1079 | + * @param array $args Array: Items to insert into the table. |
| 1080 | + * @param array $fname String: Name of the function, for profiling |
| 1081 | + * @param mixed $options String or Array. Valid options: IGNORE |
| 1082 | + * |
| 1083 | + * @return bool Success of insert operation. IGNORE always returns true. |
| 1084 | + */ |
| 1085 | + public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) { |
| 1086 | + wfDebug("DB2::insert($table)\n"); |
| 1087 | + if ( !count( $args ) ) { |
| 1088 | + return true; |
| 1089 | + } |
| 1090 | + |
| 1091 | + $table = $this->tableName( $table ); |
| 1092 | + |
| 1093 | + if ( !is_array( $options ) ) |
| 1094 | + $options = array( $options ); |
| 1095 | + |
| 1096 | + if ( isset( $args[0] ) && is_array( $args[0] ) ) { |
| 1097 | + } |
| 1098 | + else { |
| 1099 | + $args = array($args); |
| 1100 | + } |
| 1101 | + $keys = array_keys( $args[0] ); |
| 1102 | + |
| 1103 | + // If IGNORE is set, we use savepoints to emulate mysql's behavior |
| 1104 | + $ignore = in_array( 'IGNORE', $options ) ? 'mw' : ''; |
| 1105 | + |
| 1106 | + // Cache autocommit value at the start |
| 1107 | + $oldautocommit = db2_autocommit($this->mConn); |
| 1108 | + |
| 1109 | + // If we are not in a transaction, we need to be for savepoint trickery |
| 1110 | + $didbegin = 0; |
| 1111 | + if (! $this->mTrxLevel) { |
| 1112 | + $this->begin(); |
| 1113 | + $didbegin = 1; |
| 1114 | + } |
| 1115 | + if ( $ignore ) { |
| 1116 | + $olde = error_reporting( 0 ); |
| 1117 | + // For future use, we may want to track the number of actual inserts |
| 1118 | + // Right now, insert (all writes) simply return true/false |
| 1119 | + $numrowsinserted = 0; |
| 1120 | + } |
| 1121 | + |
| 1122 | + $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; |
| 1123 | + |
| 1124 | + if ( !$ignore ) { |
| 1125 | + $first = true; |
| 1126 | + foreach ( $args as $row ) { |
| 1127 | + if ( $first ) { |
| 1128 | + $first = false; |
| 1129 | + } else { |
| 1130 | + $sql .= ','; |
| 1131 | + } |
| 1132 | + $sql .= '(' . $this->makeListSmart( $table, $row ) . ')'; |
| 1133 | + } |
| 1134 | + $res = (bool)$this->query( $sql, $fname, $ignore ); |
| 1135 | + } |
| 1136 | + else { |
| 1137 | + $res = true; |
| 1138 | + $origsql = $sql; |
| 1139 | + foreach ( $args as $row ) { |
| 1140 | + $tempsql = $origsql; |
| 1141 | + $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')'; |
| 1142 | + |
| 1143 | + if ( $ignore ) { |
| 1144 | + db2_exec($this->mConn, "SAVEPOINT $ignore"); |
| 1145 | + } |
| 1146 | + |
| 1147 | + $tempres = (bool)$this->query( $tempsql, $fname, $ignore ); |
| 1148 | + |
| 1149 | + if ( $ignore ) { |
| 1150 | + $bar = db2_stmt_error(); |
| 1151 | + if ($bar != false) { |
| 1152 | + db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" ); |
| 1153 | + } |
| 1154 | + else { |
| 1155 | + db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" ); |
| 1156 | + $numrowsinserted++; |
| 1157 | + } |
| 1158 | + } |
| 1159 | + |
| 1160 | + // If any of them fail, we fail overall for this function call |
| 1161 | + // Note that this will be ignored if IGNORE is set |
| 1162 | + if (! $tempres) |
| 1163 | + $res = false; |
| 1164 | + } |
| 1165 | + } |
| 1166 | + |
| 1167 | + if ($didbegin) { |
| 1168 | + $this->commit(); |
| 1169 | + } |
| 1170 | + // if autocommit used to be on, it's ok to commit everything |
| 1171 | + else if ($oldautocommit) |
| 1172 | + { |
| 1173 | + $this->commit(); |
| 1174 | + } |
| 1175 | + |
| 1176 | + if ( $ignore ) { |
| 1177 | + $olde = error_reporting( $olde ); |
| 1178 | + // Set the affected row count for the whole operation |
| 1179 | + $this->mAffectedRows = $numrowsinserted; |
| 1180 | + |
| 1181 | + // IGNORE always returns true |
| 1182 | + return true; |
| 1183 | + } |
| 1184 | + |
| 1185 | + return $res; |
| 1186 | + } |
| 1187 | + |
| 1188 | + /** |
| 1189 | + * UPDATE wrapper, takes a condition array and a SET array |
| 1190 | + * |
| 1191 | + * @param string $table The table to UPDATE |
| 1192 | + * @param array $values An array of values to SET |
| 1193 | + * @param array $conds An array of conditions (WHERE). Use '*' to update all rows. |
| 1194 | + * @param string $fname The Class::Function calling this function |
| 1195 | + * (for the log) |
| 1196 | + * @param array $options An array of UPDATE options, can be one or |
| 1197 | + * more of IGNORE, LOW_PRIORITY |
| 1198 | + * @return bool |
| 1199 | + */ |
| 1200 | + function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) { |
| 1201 | + $table = $this->tableName( $table ); |
| 1202 | + $opts = $this->makeUpdateOptions( $options ); |
| 1203 | + $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET ); |
| 1204 | + if ( $conds != '*' ) { |
| 1205 | + $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND ); |
| 1206 | + } |
| 1207 | + return $this->query( $sql, $fname ); |
| 1208 | + } |
| 1209 | + |
| 1210 | + /** |
| 1211 | + * DELETE query wrapper |
| 1212 | + * |
| 1213 | + * Use $conds == "*" to delete all rows |
| 1214 | + */ |
| 1215 | + function delete( $table, $conds, $fname = 'Database::delete' ) { |
| 1216 | + if ( !$conds ) { |
| 1217 | + throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' ); |
| 1218 | + } |
| 1219 | + $table = $this->tableName( $table ); |
| 1220 | + $sql = "DELETE FROM $table"; |
| 1221 | + if ( $conds != '*' ) { |
| 1222 | + $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND ); |
| 1223 | + } |
| 1224 | + return $this->query( $sql, $fname ); |
| 1225 | + } |
| 1226 | + |
| 1227 | + /** |
| 1228 | + * Returns the number of rows affected by the last query or 0 |
| 1229 | + * @return int the number of rows affected by the last query |
| 1230 | + */ |
| 1231 | + public function affectedRows() { |
| 1232 | + if ( !is_null( $this->mAffectedRows ) ) { |
| 1233 | + // Forced result for simulated queries |
| 1234 | + return $this->mAffectedRows; |
| 1235 | + } |
| 1236 | + if( empty( $this->mLastResult ) ) |
| 1237 | + return 0; |
| 1238 | + return db2_num_rows( $this->mLastResult ); |
| 1239 | + } |
| 1240 | + |
| 1241 | + /** |
| 1242 | + * USE INDEX clause |
| 1243 | + * DB2 doesn't have them and returns "" |
| 1244 | + * @param sting $index |
| 1245 | + */ |
| 1246 | + public function useIndexClause( $index ) { |
| 1247 | + return ""; |
| 1248 | + } |
| 1249 | + |
| 1250 | + /** |
| 1251 | + * Simulates REPLACE with a DELETE followed by INSERT |
| 1252 | + * @param $table Object |
| 1253 | + * @param array $uniqueIndexes array consisting of indexes and arrays of indexes |
| 1254 | + * @param array $rows Rows to insert |
| 1255 | + * @param string $fname Name of the function for profiling |
| 1256 | + * @return nothing |
| 1257 | + */ |
| 1258 | + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) { |
| 1259 | + $table = $this->tableName( $table ); |
| 1260 | + |
| 1261 | + if (count($rows)==0) { |
| 1262 | + return; |
| 1263 | + } |
| 1264 | + |
| 1265 | + # Single row case |
| 1266 | + if ( !is_array( reset( $rows ) ) ) { |
| 1267 | + $rows = array( $rows ); |
| 1268 | + } |
| 1269 | + |
| 1270 | + foreach( $rows as $row ) { |
| 1271 | + # Delete rows which collide |
| 1272 | + if ( $uniqueIndexes ) { |
| 1273 | + $sql = "DELETE FROM $table WHERE "; |
| 1274 | + $first = true; |
| 1275 | + foreach ( $uniqueIndexes as $index ) { |
| 1276 | + if ( $first ) { |
| 1277 | + $first = false; |
| 1278 | + $sql .= "("; |
| 1279 | + } else { |
| 1280 | + $sql .= ') OR ('; |
| 1281 | + } |
| 1282 | + if ( is_array( $index ) ) { |
| 1283 | + $first2 = true; |
| 1284 | + foreach ( $index as $col ) { |
| 1285 | + if ( $first2 ) { |
| 1286 | + $first2 = false; |
| 1287 | + } else { |
| 1288 | + $sql .= ' AND '; |
| 1289 | + } |
| 1290 | + $sql .= $col.'=' . $this->addQuotes( $row[$col] ); |
| 1291 | + } |
| 1292 | + } else { |
| 1293 | + $sql .= $index.'=' . $this->addQuotes( $row[$index] ); |
| 1294 | + } |
| 1295 | + } |
| 1296 | + $sql .= ')'; |
| 1297 | + $this->query( $sql, $fname ); |
| 1298 | + } |
| 1299 | + |
| 1300 | + # Now insert the row |
| 1301 | + $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . |
| 1302 | + $this->makeList( $row, LIST_COMMA ) . ')'; |
| 1303 | + $this->query( $sql, $fname ); |
| 1304 | + } |
| 1305 | + } |
| 1306 | + |
| 1307 | + /** |
| 1308 | + * Returns the number of rows in the result set |
| 1309 | + * Has to be called right after the corresponding select query |
| 1310 | + * @param Object $res result set |
| 1311 | + * @return int number of rows |
| 1312 | + */ |
| 1313 | + public function numRows( $res ) { |
| 1314 | + if ( $res instanceof ResultWrapper ) { |
| 1315 | + $res = $res->result; |
| 1316 | + } |
| 1317 | + if ( $this->mNumRows ) { |
| 1318 | + return $this->mNumRows; |
| 1319 | + } |
| 1320 | + else { |
| 1321 | + return 0; |
| 1322 | + } |
| 1323 | + } |
| 1324 | + |
| 1325 | + /** |
| 1326 | + * Moves the row pointer of the result set |
| 1327 | + * @param Object $res result set |
| 1328 | + * @param int $row row number |
| 1329 | + * @return success or failure |
| 1330 | + */ |
| 1331 | + public function dataSeek( $res, $row ) { |
| 1332 | + if ( $res instanceof ResultWrapper ) { |
| 1333 | + $res = $res->result; |
| 1334 | + } |
| 1335 | + return db2_fetch_row( $res, $row ); |
| 1336 | + } |
| 1337 | + |
| 1338 | + ### |
| 1339 | + # Fix notices in Block.php |
| 1340 | + ### |
| 1341 | + |
| 1342 | + /** |
| 1343 | + * Frees memory associated with a statement resource |
| 1344 | + * @param Object $res Statement resource to free |
| 1345 | + * @return bool success or failure |
| 1346 | + */ |
| 1347 | + public function freeResult( $res ) { |
| 1348 | + if ( $res instanceof ResultWrapper ) { |
| 1349 | + $res = $res->result; |
| 1350 | + } |
| 1351 | + if ( !@db2_free_result( $res ) ) { |
| 1352 | + throw new DBUnexpectedError($this, "Unable to free DB2 result\n" ); |
| 1353 | + } |
| 1354 | + } |
| 1355 | + |
| 1356 | + /** |
| 1357 | + * Returns the number of columns in a resource |
| 1358 | + * @param Object $res Statement resource |
| 1359 | + * @return Number of fields/columns in resource |
| 1360 | + */ |
| 1361 | + public function numFields( $res ) { |
| 1362 | + if ( $res instanceof ResultWrapper ) { |
| 1363 | + $res = $res->result; |
| 1364 | + } |
| 1365 | + return db2_num_fields( $res ); |
| 1366 | + } |
| 1367 | + |
| 1368 | + /** |
| 1369 | + * Returns the nth column name |
| 1370 | + * @param Object $res Statement resource |
| 1371 | + * @param int $n Index of field or column |
| 1372 | + * @return string name of nth column |
| 1373 | + */ |
| 1374 | + public function fieldName( $res, $n ) { |
| 1375 | + if ( $res instanceof ResultWrapper ) { |
| 1376 | + $res = $res->result; |
| 1377 | + } |
| 1378 | + return db2_field_name( $res, $n ); |
| 1379 | + } |
| 1380 | + |
| 1381 | + /** |
| 1382 | + * SELECT wrapper |
| 1383 | + * |
| 1384 | + * @param mixed $table Array or string, table name(s) (prefix auto-added) |
| 1385 | + * @param mixed $vars Array or string, field name(s) to be retrieved |
| 1386 | + * @param mixed $conds Array or string, condition(s) for WHERE |
| 1387 | + * @param string $fname Calling function name (use __METHOD__) for logs/profiling |
| 1388 | + * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), |
| 1389 | + * see Database::makeSelectOptions code for list of supported stuff |
| 1390 | + * @param array $join_conds Associative array of table join conditions (optional) |
| 1391 | + * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) |
| 1392 | + * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure |
| 1393 | + */ |
| 1394 | + public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() ) |
| 1395 | + { |
| 1396 | + $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds ); |
| 1397 | + |
| 1398 | + // We must adjust for offset |
| 1399 | + if ( isset( $options['LIMIT'] ) ) { |
| 1400 | + if ( isset ($options['OFFSET'] ) ) { |
| 1401 | + $limit = $options['LIMIT']; |
| 1402 | + $offset = $options['OFFSET']; |
| 1403 | + } |
| 1404 | + } |
| 1405 | + |
| 1406 | + |
| 1407 | + // DB2 does not have a proper num_rows() function yet, so we must emulate it |
| 1408 | + // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one |
| 1409 | + // Yay! |
| 1410 | + |
| 1411 | + // we want the count |
| 1412 | + $vars2 = array('count(*) as num_rows'); |
| 1413 | + // respecting just the limit option |
| 1414 | + $options2 = array(); |
| 1415 | + if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT']; |
| 1416 | + // but don't try to emulate for GROUP BY |
| 1417 | + if ( isset( $options['GROUP BY'] ) ) return $res; |
| 1418 | + |
| 1419 | + $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds ); |
| 1420 | + $obj = $this->fetchObject($res2); |
| 1421 | + $this->mNumRows = $obj->num_rows; |
| 1422 | + |
| 1423 | + wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n"); |
| 1424 | + |
| 1425 | + return $res; |
| 1426 | + } |
| 1427 | + |
| 1428 | + /** |
| 1429 | + * Handles ordering, grouping, and having options ('GROUP BY' => colname) |
| 1430 | + * Has limited support for per-column options (colnum => 'DISTINCT') |
| 1431 | + * |
| 1432 | + * @private |
| 1433 | + * |
| 1434 | + * @param array $options an associative array of options to be turned into |
| 1435 | + * an SQL query, valid keys are listed in the function. |
| 1436 | + * @return array |
| 1437 | + */ |
| 1438 | + function makeSelectOptions( $options ) { |
| 1439 | + $preLimitTail = $postLimitTail = ''; |
| 1440 | + $startOpts = ''; |
| 1441 | + |
| 1442 | + $noKeyOptions = array(); |
| 1443 | + foreach ( $options as $key => $option ) { |
| 1444 | + if ( is_numeric( $key ) ) { |
| 1445 | + $noKeyOptions[$option] = true; |
| 1446 | + } |
| 1447 | + } |
| 1448 | + |
| 1449 | + if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; |
| 1450 | + if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}"; |
| 1451 | + if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; |
| 1452 | + |
| 1453 | + if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; |
| 1454 | + |
| 1455 | + return array( $startOpts, '', $preLimitTail, $postLimitTail ); |
| 1456 | + } |
| 1457 | + |
| 1458 | + /** |
| 1459 | + * Returns link to IBM DB2 free download |
| 1460 | + * @return string wikitext of a link to the server software's web site |
| 1461 | + */ |
| 1462 | + public function getSoftwareLink() { |
| 1463 | + return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]"; |
| 1464 | + } |
| 1465 | + |
| 1466 | + /** |
| 1467 | + * Does nothing |
| 1468 | + * @param object $db |
| 1469 | + * @return bool true |
| 1470 | + */ |
| 1471 | + public function selectDB( $db ) { |
| 1472 | + return true; |
| 1473 | + } |
| 1474 | + |
| 1475 | + /** |
| 1476 | + * Returns an SQL expression for a simple conditional. |
| 1477 | + * Uses CASE on DB2 |
| 1478 | + * |
| 1479 | + * @param string $cond SQL expression which will result in a boolean value |
| 1480 | + * @param string $trueVal SQL expression to return if true |
| 1481 | + * @param string $falseVal SQL expression to return if false |
| 1482 | + * @return string SQL fragment |
| 1483 | + */ |
| 1484 | + public function conditional( $cond, $trueVal, $falseVal ) { |
| 1485 | + return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; |
| 1486 | + } |
| 1487 | + |
| 1488 | + ### |
| 1489 | + # Fix search crash |
| 1490 | + ### |
| 1491 | + /** |
| 1492 | + * Get search engine class. All subclasses of this |
| 1493 | + * need to implement this if they wish to use searching. |
| 1494 | + * |
| 1495 | + * @return string |
| 1496 | + */ |
| 1497 | + public function getSearchEngine() { |
| 1498 | + return "SearchIBM_DB2"; |
| 1499 | + } |
| 1500 | + |
| 1501 | + ### |
| 1502 | + # Tuesday the 14th of October, 2008 |
| 1503 | + ### |
| 1504 | + /** |
| 1505 | + * Did the last database access fail because of deadlock? |
| 1506 | + * @return bool |
| 1507 | + */ |
| 1508 | + public function wasDeadlock() { |
| 1509 | + // get SQLSTATE |
| 1510 | + $err = $this->lastErrno(); |
| 1511 | + switch($err) { |
| 1512 | + case '40001': // sql0911n, Deadlock or timeout, rollback |
| 1513 | + case '57011': // sql0904n, Resource unavailable, no rollback |
| 1514 | + case '57033': // sql0913n, Deadlock or timeout, no rollback |
| 1515 | + wfDebug("In a deadlock because of SQLSTATE $err"); |
| 1516 | + return true; |
| 1517 | + } |
| 1518 | + return false; |
| 1519 | + } |
| 1520 | + |
| 1521 | + /** |
| 1522 | + * Ping the server and try to reconnect if it there is no connection |
| 1523 | + * The connection may be closed and reopened while this happens |
| 1524 | + * @return bool whether the connection exists |
| 1525 | + */ |
| 1526 | + public function ping() { |
| 1527 | + // db2_ping() doesn't exist |
| 1528 | + // Emulate |
| 1529 | + $this->close(); |
| 1530 | + if ($this->mCataloged == NULL) { |
| 1531 | + return false; |
| 1532 | + } |
| 1533 | + else if ($this->mCataloged) { |
| 1534 | + $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword); |
| 1535 | + } |
| 1536 | + else if (!$this->mCataloged) { |
| 1537 | + $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort); |
| 1538 | + } |
| 1539 | + return false; |
| 1540 | + } |
| 1541 | + ###################################### |
| 1542 | + # Unimplemented and not applicable |
| 1543 | + ###################################### |
| 1544 | + /** |
| 1545 | + * Not implemented |
| 1546 | + * @return string '' |
| 1547 | + * @deprecated |
| 1548 | + */ |
| 1549 | + public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; } |
| 1550 | + /** |
| 1551 | + * Not implemented |
| 1552 | + * @deprecated |
| 1553 | + */ |
| 1554 | + public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); } |
| 1555 | + /** |
| 1556 | + * Not implemented |
| 1557 | + * TODO |
| 1558 | + * @return bool true |
| 1559 | + */ |
| 1560 | + public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; } |
| 1561 | + /** |
| 1562 | + * Not implemented |
| 1563 | + * TODO |
| 1564 | + * @return bool true |
| 1565 | + */ |
| 1566 | + public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; } |
| 1567 | + /** |
| 1568 | + * Not implemented |
| 1569 | + * @deprecated |
| 1570 | + */ |
| 1571 | + public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); } |
| 1572 | + /** |
| 1573 | + * Not implemented |
| 1574 | + * @deprecated |
| 1575 | + */ |
| 1576 | + public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); } |
| 1577 | + /** |
| 1578 | + * Not implemented |
| 1579 | + * @return string $sql |
| 1580 | + * @deprecated |
| 1581 | + */ |
| 1582 | + public function limitResultForUpdate($sql, $num) { return $sql; } |
| 1583 | + /** |
| 1584 | + * No such option |
| 1585 | + * @return string '' |
| 1586 | + * @deprecated |
| 1587 | + */ |
| 1588 | + public function lowPriorityOption() { return ''; } |
| 1589 | + |
| 1590 | + ###################################### |
| 1591 | + # Reflection |
| 1592 | + ###################################### |
| 1593 | + |
| 1594 | + /** |
| 1595 | + * Query whether a given column exists in the mediawiki schema |
| 1596 | + * @param string $table name of the table |
| 1597 | + * @param string $field name of the column |
| 1598 | + * @param string $fname function name for logging and profiling |
| 1599 | + */ |
| 1600 | + public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) { |
| 1601 | + $table = $this->tableName( $table ); |
| 1602 | + $schema = $this->mSchema; |
| 1603 | + $etable = preg_replace("/'/", "''", $table); |
| 1604 | + $eschema = preg_replace("/'/", "''", $schema); |
| 1605 | + $ecol = preg_replace("/'/", "''", $field); |
| 1606 | + $sql = <<<SQL |
| 1607 | +SELECT 1 as fieldexists |
| 1608 | +FROM sysibm.syscolumns sc |
| 1609 | +WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema' |
| 1610 | +SQL; |
| 1611 | + $res = $this->query( $sql, $fname ); |
| 1612 | + $count = $res ? $this->numRows($res) : 0; |
| 1613 | + if ($res) |
| 1614 | + $this->freeResult( $res ); |
| 1615 | + return $count; |
| 1616 | + } |
| 1617 | + |
| 1618 | + /** |
| 1619 | + * Returns information about an index |
| 1620 | + * If errors are explicitly ignored, returns NULL on failure |
| 1621 | + * @param string $table table name |
| 1622 | + * @param string $index index name |
| 1623 | + * @param string |
| 1624 | + * @return object query row in object form |
| 1625 | + */ |
| 1626 | + public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) { |
| 1627 | + $table = $this->tableName( $table ); |
| 1628 | + $sql = <<<SQL |
| 1629 | +SELECT name as indexname |
| 1630 | +FROM sysibm.sysindexes si |
| 1631 | +WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema' |
| 1632 | +SQL; |
| 1633 | + $res = $this->query( $sql, $fname ); |
| 1634 | + if ( !$res ) { |
| 1635 | + return NULL; |
| 1636 | + } |
| 1637 | + $row = $this->fetchObject( $res ); |
| 1638 | + if ($row != NULL) return $row; |
| 1639 | + else return false; |
| 1640 | + } |
| 1641 | + |
| 1642 | + /** |
| 1643 | + * Returns an information object on a table column |
| 1644 | + * @param string $table table name |
| 1645 | + * @param string $field column name |
| 1646 | + * @return IBM_DB2Field |
| 1647 | + */ |
| 1648 | + public function fieldInfo( $table, $field ) { |
| 1649 | + return IBM_DB2Field::fromText($this, $table, $field); |
| 1650 | + } |
| 1651 | + |
| 1652 | + /** |
| 1653 | + * db2_field_type() wrapper |
| 1654 | + * @param Object $res Result of executed statement |
| 1655 | + * @param mixed $index number or name of the column |
| 1656 | + * @return string column type |
| 1657 | + */ |
| 1658 | + public function fieldType( $res, $index ) { |
| 1659 | + if ( $res instanceof ResultWrapper ) { |
| 1660 | + $res = $res->result; |
| 1661 | + } |
| 1662 | + return db2_field_type( $res, $index ); |
| 1663 | + } |
| 1664 | + |
| 1665 | + /** |
| 1666 | + * Verifies that an index was created as unique |
| 1667 | + * @param string $table table name |
| 1668 | + * @param string $index index name |
| 1669 | + * @param string $fnam function name for profiling |
| 1670 | + * @return bool |
| 1671 | + */ |
| 1672 | + public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { |
| 1673 | + $table = $this->tableName( $table ); |
| 1674 | + $sql = <<<SQL |
| 1675 | +SELECT si.name as indexname |
| 1676 | +FROM sysibm.sysindexes si |
| 1677 | +WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema' |
| 1678 | +AND si.uniquerule IN ('U', 'P') |
| 1679 | +SQL; |
| 1680 | + $res = $this->query( $sql, $fname ); |
| 1681 | + if ( !$res ) { |
| 1682 | + return null; |
| 1683 | + } |
| 1684 | + if ($this->fetchObject( $res )) { |
| 1685 | + return true; |
| 1686 | + } |
| 1687 | + return false; |
| 1688 | + |
| 1689 | + } |
| 1690 | + |
| 1691 | + /** |
| 1692 | + * Returns the size of a text field, or -1 for "unlimited" |
| 1693 | + * @param string $table table name |
| 1694 | + * @param string $field column name |
| 1695 | + * @return int length or -1 for unlimited |
| 1696 | + */ |
| 1697 | + public function textFieldSize( $table, $field ) { |
| 1698 | + $table = $this->tableName( $table ); |
| 1699 | + $sql = <<<SQL |
| 1700 | +SELECT length as size |
| 1701 | +FROM sysibm.syscolumns sc |
| 1702 | +WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema' |
| 1703 | +SQL; |
| 1704 | + $res = $this->query($sql); |
| 1705 | + $row = $this->fetchObject($res); |
| 1706 | + $size = $row->size; |
| 1707 | + $this->freeResult( $res ); |
| 1708 | + return $size; |
| 1709 | + } |
| 1710 | + |
| 1711 | + /** |
| 1712 | + * DELETE where the condition is a join |
| 1713 | + * @param string $delTable deleting from this table |
| 1714 | + * @param string $joinTable using data from this table |
| 1715 | + * @param string $delVar variable in deleteable table |
| 1716 | + * @param string $joinVar variable in data table |
| 1717 | + * @param array $conds conditionals for join table |
| 1718 | + * @param string $fname function name for profiling |
| 1719 | + */ |
| 1720 | + public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) { |
| 1721 | + if ( !$conds ) { |
| 1722 | + throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); |
| 1723 | + } |
| 1724 | + |
| 1725 | + $delTable = $this->tableName( $delTable ); |
| 1726 | + $joinTable = $this->tableName( $joinTable ); |
| 1727 | + $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; |
| 1728 | + if ( $conds != '*' ) { |
| 1729 | + $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); |
| 1730 | + } |
| 1731 | + $sql .= ')'; |
| 1732 | + |
| 1733 | + $this->query( $sql, $fname ); |
| 1734 | + } |
| 1735 | + |
| 1736 | + /** |
| 1737 | + * Estimate rows in dataset |
| 1738 | + * Returns estimated count, based on COUNT(*) output |
| 1739 | + * Takes same arguments as Database::select() |
| 1740 | + * @param string $table table name |
| 1741 | + * @param array $vars unused |
| 1742 | + * @param array $conds filters on the table |
| 1743 | + * @param string $fname function name for profiling |
| 1744 | + * @param array $options options for select |
| 1745 | + * @return int row count |
| 1746 | + */ |
| 1747 | + public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { |
| 1748 | + $rows = 0; |
| 1749 | + $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options ); |
| 1750 | + if ($res) { |
| 1751 | + $row = $this->fetchRow($res); |
| 1752 | + $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0; |
| 1753 | + } |
| 1754 | + $this->freeResult($res); |
| 1755 | + return $rows; |
| 1756 | + } |
| 1757 | + |
| 1758 | + /** |
| 1759 | + * Description is left as an exercise for the reader |
| 1760 | + * @param mixed $b data to be encoded |
| 1761 | + * @return IBM_DB2Blob |
| 1762 | + */ |
| 1763 | + public function encodeBlob($b) { |
| 1764 | + return new IBM_DB2Blob($b); |
| 1765 | + } |
| 1766 | + |
| 1767 | + /** |
| 1768 | + * Description is left as an exercise for the reader |
| 1769 | + * @param IBM_DB2Blob $b data to be decoded |
| 1770 | + * @return mixed |
| 1771 | + */ |
| 1772 | + public function decodeBlob($b) { |
| 1773 | + return $b->getData(); |
| 1774 | + } |
| 1775 | + |
| 1776 | + /** |
| 1777 | + * Convert into a list of string being concatenated |
| 1778 | + * @param array $stringList strings that need to be joined together by the SQL engine |
| 1779 | + * @return string joined by the concatenation operator |
| 1780 | + */ |
| 1781 | + public function buildConcat( $stringList ) { |
| 1782 | + // || is equivalent to CONCAT |
| 1783 | + // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz' |
| 1784 | + return implode( ' || ', $stringList ); |
| 1785 | + } |
| 1786 | + |
| 1787 | + /** |
| 1788 | + * Generates the SQL required to convert a DB2 timestamp into a Unix epoch |
| 1789 | + * @param string $column name of timestamp column |
| 1790 | + * @return string SQL code |
| 1791 | + */ |
| 1792 | + public function extractUnixEpoch( $column ) { |
| 1793 | + // TODO |
| 1794 | + // see SpecialAncientpages |
| 1795 | + } |
| 1796 | +} |
| 1797 | +?> |
\ No newline at end of file |
Index: trunk/phase3/includes/Revision.php |
— | — | @@ -961,6 +961,10 @@ |
962 | 962 | */ |
963 | 963 | static function getTimestampFromId( $title, $id ) { |
964 | 964 | $dbr = wfGetDB( DB_SLAVE ); |
| 965 | + // Casting fix for DB2 |
| 966 | + if ($id == '') { |
| 967 | + $id = 0; |
| 968 | + } |
965 | 969 | $conds = array( 'rev_id' => $id ); |
966 | 970 | $conds['rev_page'] = $title->getArticleId(); |
967 | 971 | $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ ); |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -320,6 +320,11 @@ |
321 | 321 | 'PostgresField' => 'includes/db/DatabasePostgres.php', |
322 | 322 | 'ResultWrapper' => 'includes/db/Database.php', |
323 | 323 | 'SQLiteField' => 'includes/db/DatabaseSqlite.php', |
| 324 | + |
| 325 | + 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php', |
| 326 | + 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php', |
| 327 | + 'IBM_DB2SearchResultSet' => 'includes/SearchIBM_DB2.php', |
| 328 | + 'SearchIBM_DB2' => 'includes/SearchIBM_DB2.php', |
324 | 329 | |
325 | 330 | # includes/diff |
326 | 331 | 'AncestorComparator' => 'includes/diff/HTMLDiff.php', |
Index: trunk/phase3/includes/SearchIBM_DB2.php |
— | — | @@ -0,0 +1,247 @@ |
| 2 | +<?php
|
| 3 | +# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
|
| 4 | +# http://www.mediawiki.org/
|
| 5 | +#
|
| 6 | +# This program is free software; you can redistribute it and/or modify
|
| 7 | +# it under the terms of the GNU General Public License as published by
|
| 8 | +# the Free Software Foundation; either version 2 of the License, or
|
| 9 | +# (at your option) any later version.
|
| 10 | +#
|
| 11 | +# This program is distributed in the hope that it will be useful,
|
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 14 | +# GNU General Public License for more details.
|
| 15 | +#
|
| 16 | +# You should have received a copy of the GNU General Public License along
|
| 17 | +# with this program; if not, write to the Free Software Foundation, Inc.,
|
| 18 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| 19 | +# http://www.gnu.org/copyleft/gpl.html
|
| 20 | +
|
| 21 | +/**
|
| 22 | + * @file
|
| 23 | + * @ingroup Search
|
| 24 | + */
|
| 25 | +
|
| 26 | +/**
|
| 27 | + * Search engine hook base class for IBM DB2
|
| 28 | + * @ingroup Search
|
| 29 | + */
|
| 30 | +class SearchIBM_DB2 extends SearchEngine {
|
| 31 | + function __construct($db) {
|
| 32 | + $this->db = $db;
|
| 33 | + }
|
| 34 | +
|
| 35 | + /**
|
| 36 | + * Perform a full text search query and return a result set.
|
| 37 | + *
|
| 38 | + * @param string $term - Raw search term
|
| 39 | + * @return IBM_DB2SearchResultSet
|
| 40 | + * @access public
|
| 41 | + */
|
| 42 | + function searchText( $term ) {
|
| 43 | + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true)));
|
| 44 | + return new IBM_DB2SearchResultSet($resultSet, $this->searchTerms);
|
| 45 | + }
|
| 46 | +
|
| 47 | + /**
|
| 48 | + * Perform a title-only search query and return a result set.
|
| 49 | + *
|
| 50 | + * @param string $term - Raw search term
|
| 51 | + * @return IBM_DB2SearchResultSet
|
| 52 | + * @access public
|
| 53 | + */
|
| 54 | + function searchTitle($term) {
|
| 55 | + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false)));
|
| 56 | + return new MySQLSearchResultSet($resultSet, $this->searchTerms);
|
| 57 | + }
|
| 58 | +
|
| 59 | +
|
| 60 | + /**
|
| 61 | + * Return a partial WHERE clause to exclude redirects, if so set
|
| 62 | + * @return string
|
| 63 | + * @private
|
| 64 | + */
|
| 65 | + function queryRedirect() {
|
| 66 | + if ($this->showRedirects) {
|
| 67 | + return '';
|
| 68 | + } else {
|
| 69 | + return 'AND page_is_redirect=0';
|
| 70 | + }
|
| 71 | + }
|
| 72 | +
|
| 73 | + /**
|
| 74 | + * Return a partial WHERE clause to limit the search to the given namespaces
|
| 75 | + * @return string
|
| 76 | + * @private
|
| 77 | + */
|
| 78 | + function queryNamespaces() {
|
| 79 | + if( is_null($this->namespaces) )
|
| 80 | + return '';
|
| 81 | + $namespaces = implode(',', $this->namespaces);
|
| 82 | + if ($namespaces == '') {
|
| 83 | + $namespaces = '0';
|
| 84 | + }
|
| 85 | + return 'AND page_namespace IN (' . $namespaces . ')';
|
| 86 | + }
|
| 87 | +
|
| 88 | + /**
|
| 89 | + * Return a LIMIT clause to limit results on the query.
|
| 90 | + * @return string
|
| 91 | + * @private
|
| 92 | + */
|
| 93 | + function queryLimit($sql) {
|
| 94 | + return $this->db->limitResult($sql, $this->limit, $this->offset);
|
| 95 | + }
|
| 96 | +
|
| 97 | + /**
|
| 98 | + * Does not do anything for generic search engine
|
| 99 | + * subclasses may define this though
|
| 100 | + * @return string
|
| 101 | + * @private
|
| 102 | + */
|
| 103 | + function queryRanking($filteredTerm, $fulltext) {
|
| 104 | + // requires Net Search Extender or equivalent
|
| 105 | + // return ' ORDER BY score(1)';
|
| 106 | + return '';
|
| 107 | + }
|
| 108 | +
|
| 109 | + /**
|
| 110 | + * Construct the full SQL query to do the search.
|
| 111 | + * The guts shoulds be constructed in queryMain()
|
| 112 | + * @param string $filteredTerm
|
| 113 | + * @param bool $fulltext
|
| 114 | + * @private
|
| 115 | + */
|
| 116 | + function getQuery( $filteredTerm, $fulltext ) {
|
| 117 | + return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' .
|
| 118 | + $this->queryRedirect() . ' ' .
|
| 119 | + $this->queryNamespaces() . ' ' .
|
| 120 | + $this->queryRanking( $filteredTerm, $fulltext ) . ' ');
|
| 121 | + }
|
| 122 | +
|
| 123 | +
|
| 124 | + /**
|
| 125 | + * Picks which field to index on, depending on what type of query.
|
| 126 | + * @param bool $fulltext
|
| 127 | + * @return string
|
| 128 | + */
|
| 129 | + function getIndexField($fulltext) {
|
| 130 | + return $fulltext ? 'si_text' : 'si_title';
|
| 131 | + }
|
| 132 | +
|
| 133 | + /**
|
| 134 | + * Get the base part of the search query.
|
| 135 | + *
|
| 136 | + * @param string $filteredTerm
|
| 137 | + * @param bool $fulltext
|
| 138 | + * @return string
|
| 139 | + * @private
|
| 140 | + */
|
| 141 | + function queryMain( $filteredTerm, $fulltext ) {
|
| 142 | + $match = $this->parseQuery($filteredTerm, $fulltext);
|
| 143 | + $page = $this->db->tableName('page');
|
| 144 | + $searchindex = $this->db->tableName('searchindex');
|
| 145 | + return 'SELECT page_id, page_namespace, page_title ' .
|
| 146 | + "FROM $page,$searchindex " .
|
| 147 | + 'WHERE page_id=si_page AND ' . $match;
|
| 148 | + }
|
| 149 | +
|
| 150 | + /** @todo document */
|
| 151 | + function parseQuery($filteredText, $fulltext) {
|
| 152 | + global $wgContLang;
|
| 153 | + $lc = SearchEngine::legalSearchChars();
|
| 154 | + $this->searchTerms = array();
|
| 155 | +
|
| 156 | + # FIXME: This doesn't handle parenthetical expressions.
|
| 157 | + $m = array();
|
| 158 | + $q = array();
|
| 159 | +
|
| 160 | + if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
|
| 161 | + $filteredText, $m, PREG_SET_ORDER)) {
|
| 162 | + foreach($m as $terms) {
|
| 163 | + $q[] = $terms[1] . $wgContLang->stripForSearch($terms[2]);
|
| 164 | +
|
| 165 | + if (!empty($terms[3])) {
|
| 166 | + $regexp = preg_quote( $terms[3], '/' );
|
| 167 | + if ($terms[4])
|
| 168 | + $regexp .= "[0-9A-Za-z_]+";
|
| 169 | + } else {
|
| 170 | + $regexp = preg_quote(str_replace('"', '', $terms[2]), '/');
|
| 171 | + }
|
| 172 | + $this->searchTerms[] = $regexp;
|
| 173 | + }
|
| 174 | + }
|
| 175 | +
|
| 176 | + $searchon = $this->db->strencode(join(',', $q));
|
| 177 | + $field = $this->getIndexField($fulltext);
|
| 178 | +
|
| 179 | + // requires Net Search Extender or equivalent
|
| 180 | + //return " CONTAINS($field, '$searchon') > 0 ";
|
| 181 | +
|
| 182 | + return " lcase($field) LIKE lcase('%$searchon%')";
|
| 183 | + }
|
| 184 | +
|
| 185 | + /**
|
| 186 | + * Create or update the search index record for the given page.
|
| 187 | + * Title and text should be pre-processed.
|
| 188 | + *
|
| 189 | + * @param int $id
|
| 190 | + * @param string $title
|
| 191 | + * @param string $text
|
| 192 | + */
|
| 193 | + function update($id, $title, $text) {
|
| 194 | + $dbw = wfGetDB(DB_MASTER);
|
| 195 | + $dbw->replace('searchindex',
|
| 196 | + array('si_page'),
|
| 197 | + array(
|
| 198 | + 'si_page' => $id,
|
| 199 | + 'si_title' => $title,
|
| 200 | + 'si_text' => $text
|
| 201 | + ), 'SearchIBM_DB2::update' );
|
| 202 | + // ?
|
| 203 | + //$dbw->query("CALL ctx_ddl.sync_index('si_text_idx')");
|
| 204 | + //$dbw->query("CALL ctx_ddl.sync_index('si_title_idx')");
|
| 205 | + }
|
| 206 | +
|
| 207 | + /**
|
| 208 | + * Update a search index record's title only.
|
| 209 | + * Title should be pre-processed.
|
| 210 | + *
|
| 211 | + * @param int $id
|
| 212 | + * @param string $title
|
| 213 | + */
|
| 214 | + function updateTitle($id, $title) {
|
| 215 | + $dbw = wfGetDB(DB_MASTER);
|
| 216 | +
|
| 217 | + $dbw->update('searchindex',
|
| 218 | + array('si_title' => $title),
|
| 219 | + array('si_page' => $id),
|
| 220 | + 'SearchIBM_DB2::updateTitle',
|
| 221 | + array());
|
| 222 | + }
|
| 223 | +}
|
| 224 | +
|
| 225 | +/**
|
| 226 | + * @ingroup Search
|
| 227 | + */
|
| 228 | +class IBM_DB2SearchResultSet extends SearchResultSet {
|
| 229 | + function __construct($resultSet, $terms) {
|
| 230 | + $this->mResultSet = $resultSet;
|
| 231 | + $this->mTerms = $terms;
|
| 232 | + }
|
| 233 | +
|
| 234 | + function termMatches() {
|
| 235 | + return $this->mTerms;
|
| 236 | + }
|
| 237 | +
|
| 238 | + function numRows() {
|
| 239 | + return $this->mResultSet->numRows();
|
| 240 | + }
|
| 241 | +
|
| 242 | + function next() {
|
| 243 | + $row = $this->mResultSet->fetchObject();
|
| 244 | + if ($row === false)
|
| 245 | + return false;
|
| 246 | + return new SearchResult($row);
|
| 247 | + }
|
| 248 | +}
|
Index: trunk/phase3/config/index.php |
— | — | @@ -79,6 +79,12 @@ |
80 | 80 | $ourdb['mssql']['bgcolor'] = '#ffc0cb'; |
81 | 81 | $ourdb['mssql']['rootuser'] = 'administrator'; |
82 | 82 | |
| 83 | +$ourdb['ibm_db2']['fullname'] = 'DB2'; |
| 84 | +$ourdb['ibm_db2']['havedriver'] = 0; |
| 85 | +$ourdb['ibm_db2']['compile'] = 'ibm_db2'; |
| 86 | +$ourdb['ibm_db2']['bgcolor'] = '#ffeba1'; |
| 87 | +$ourdb['ibm_db2']['rootuser'] = 'db2admin'; |
| 88 | + |
83 | 89 | ?> |
84 | 90 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
85 | 91 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> |
— | — | @@ -615,6 +621,12 @@ |
616 | 622 | ## MSSQL specific |
617 | 623 | // We need a second field so it doesn't overwrite the MySQL one |
618 | 624 | $conf->DBprefix2 = importPost( "DBprefix2" ); |
| 625 | + |
| 626 | + ## DB2 specific: |
| 627 | + // New variable in order to have a different default port number |
| 628 | + $conf->DBport_db2 = importPost( "DBport_db2", "50000" ); |
| 629 | + $conf->DBmwschema = importPost( "DBmwschema", "mediawiki" ); |
| 630 | + $conf->DBcataloged = importPost( "DBcataloged", "cataloged" ); |
619 | 631 | |
620 | 632 | $conf->ShellLocale = getShellLocale( $conf->LanguageCode ); |
621 | 633 | |
— | — | @@ -786,6 +798,9 @@ |
787 | 799 | $wgDBprefix = $conf->DBprefix2; |
788 | 800 | } |
789 | 801 | |
| 802 | + ## DB2 specific: |
| 803 | + $wgDBcataloged = $conf->DBcataloged; |
| 804 | + |
790 | 805 | $wgCommandLineMode = true; |
791 | 806 | if (! defined ( 'STDERR' ) ) |
792 | 807 | define( 'STDERR', fopen("php://stderr", "wb")); |
— | — | @@ -861,12 +876,31 @@ |
862 | 877 | } #conn. att. |
863 | 878 | |
864 | 879 | if( !$ok ) { continue; } |
865 | | - |
| 880 | + } |
| 881 | + else if( $conf->DBtype == 'ibm_db2' ) { |
| 882 | + if( $useRoot ) { |
| 883 | + $db_user = $conf->RootUser; |
| 884 | + $db_pass = $conf->RootPW; |
| 885 | + } else { |
| 886 | + $db_user = $wgDBuser; |
| 887 | + $db_pass = $wgDBpassword; |
| 888 | + } |
| 889 | + |
| 890 | + echo( "<li>Attempting to connect to database \"$wgDBname\" as \"$db_user\"..." ); |
| 891 | + $wgDatabase = $dbc->newFromParams($wgDBserver, $db_user, $db_pass, $wgDBname, 1); |
| 892 | + if (!$wgDatabase->isOpen()) { |
| 893 | + print " error: " . $wgDatabase->lastError() . "</li>\n"; |
| 894 | + } else { |
| 895 | + $myver = $wgDatabase->getServerVersion(); |
| 896 | + } |
| 897 | + if (is_callable(array($wgDatabase, 'initial_setup'))) $wgDatabase->initial_setup('', $wgDBname); |
| 898 | + |
866 | 899 | } else { # not mysql |
867 | 900 | error_reporting( E_ALL ); |
868 | 901 | $wgSuperUser = ''; |
869 | 902 | ## Possible connect as a superuser |
870 | | - if( $useRoot && $conf->DBtype != 'sqlite' ) { |
| 903 | + // Changed !mysql to postgres check since it seems to only apply to postgres |
| 904 | + if( $useRoot && $conf->DBtype == 'postgres' ) { |
871 | 905 | $wgDBsuperuser = $conf->RootUser; |
872 | 906 | echo( "<li>Attempting to connect to database \"postgres\" as superuser \"$wgDBsuperuser\"..." ); |
873 | 907 | $wgDatabase = $dbc->newFromParams($wgDBserver, $wgDBsuperuser, $conf->RootPW, "postgres", 1); |
— | — | @@ -1113,6 +1147,8 @@ |
1114 | 1148 | $revid = $revision->insertOn( $wgDatabase ); |
1115 | 1149 | $article->updateRevisionOn( $wgDatabase, $revision ); |
1116 | 1150 | } |
| 1151 | + // Now that all database work is done, make sure everything is committed |
| 1152 | + $wgDatabase->commit(); |
1117 | 1153 | |
1118 | 1154 | /* Write out the config file now that all is well */ |
1119 | 1155 | print "<li style=\"list-style: none\">\n"; |
— | — | @@ -1459,6 +1495,25 @@ |
1460 | 1496 | <p>Avoid exotic characters; something like <tt>mw_</tt> is good.</p> |
1461 | 1497 | </div> |
1462 | 1498 | </fieldset> |
| 1499 | + |
| 1500 | + <?php database_switcher('ibm_db2'); ?> |
| 1501 | + <div class="config-input"><?php |
| 1502 | + aField( $conf, "DBport_db2", "Database port:" ); |
| 1503 | + ?></div> |
| 1504 | + <div class="config-input"><?php |
| 1505 | + aField( $conf, "DBmwschema", "Schema for mediawiki:" ); |
| 1506 | + ?></div> |
| 1507 | + <div>Select one:</div> |
| 1508 | + <ul class="plain"> |
| 1509 | + <li><?php aField( $conf, "DBcataloged", "Cataloged (DB2 installed locally)", "radio", "cataloged" ); ?></li> |
| 1510 | + <li><?php aField( $conf, "DBcataloged", "Uncataloged (remote DB2 through ODBC)", "radio", "uncataloged" ); ?></li> |
| 1511 | + </ul> |
| 1512 | + <div class="config-desc"> |
| 1513 | + <p>If you need to share one database between multiple wikis, or |
| 1514 | + between MediaWiki and another web application, you may specify |
| 1515 | + a different schema to avoid conflicts.</p> |
| 1516 | + </div> |
| 1517 | + </fieldset> |
1463 | 1518 | |
1464 | 1519 | <div class="config-input" style="padding:2em 0 3em"> |
1465 | 1520 | <label class='column'> </label> |
— | — | @@ -1629,6 +1684,12 @@ |
1630 | 1685 | $dbsettings = |
1631 | 1686 | "# MSSQL specific settings |
1632 | 1687 | \$wgDBprefix = \"{$slconf['DBprefix2']}\";"; |
| 1688 | + } elseif( $conf->DBtype == 'ibm_db2' ) { |
| 1689 | + $dbsettings = |
| 1690 | +"# DB2 specific settings |
| 1691 | +\$wgDBport_db2 = \"{$slconf['DBport_db2']}\"; |
| 1692 | +\$wgDBmwschema = \"{$slconf['DBmwschema']}\"; |
| 1693 | +\$wgDBcataloged = \"{$slconf['DBcataloged']}\";"; |
1633 | 1694 | } else { |
1634 | 1695 | // ummm... :D |
1635 | 1696 | $dbsettings = ''; |