r45755 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r45754‎ | r45755 | r45756 >
Date:22:20, 14 January 2009
Author:leonsp
Status:deferred (Comments)
Tags:
Comment:
(bug 17028) Added support for IBM DB2 database. config/index.php has new interface elements that only show up if PHP has ibm_db2 module enabled. AutoLoader knows about the new DB2 classes. GlobalFunctions has a new constant for DB2 time format. Revision class fixed slightly. Also includes new PHP files containing the Database and Search API implementations for IBM DB2.
Modified paths:
  • /trunk/phase3/config/index.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/Revision.php (modified) (history)
  • /trunk/phase3/includes/SearchIBM_DB2.php (added) (history)
  • /trunk/phase3/includes/db/DatabaseIbm_db2.php (added) (history)
  • /trunk/phase3/maintenance/ibm_db2 (added) (history)
  • /trunk/phase3/maintenance/ibm_db2/README (added) (history)
  • /trunk/phase3/maintenance/ibm_db2/tables.sql (added) (history)

Diff [purge]

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 @@
16941694 define('TS_POSTGRES', 7);
16951695
16961696 /**
 1697+ * DB2 format time
 1698+ */
 1699+define('TS_DB2', 8);
 1700+
 1701+/**
16971702 * @param mixed $outputtype A timestamp in one of the supported formats, the
16981703 * function will autodetect which format is supplied
16991704 * and act accordingly.
@@ -1753,6 +1758,8 @@
17541759 return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
17551760 case TS_POSTGRES:
17561761 return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
 1762+ case TS_DB2:
 1763+ return gmdate( 'Y-m-d H:i:s', $uts);
17571764 default:
17581765 throw new MWException( 'wfTimestamp() called with illegal output type.');
17591766 }
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 @@
962962 */
963963 static function getTimestampFromId( $title, $id ) {
964964 $dbr = wfGetDB( DB_SLAVE );
 965+ // Casting fix for DB2
 966+ if ($id == '') {
 967+ $id = 0;
 968+ }
965969 $conds = array( 'rev_id' => $id );
966970 $conds['rev_page'] = $title->getArticleId();
967971 $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
Index: trunk/phase3/includes/AutoLoader.php
@@ -320,6 +320,11 @@
321321 'PostgresField' => 'includes/db/DatabasePostgres.php',
322322 'ResultWrapper' => 'includes/db/Database.php',
323323 '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',
324329
325330 # includes/diff
326331 '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 @@
8080 $ourdb['mssql']['bgcolor'] = '#ffc0cb';
8181 $ourdb['mssql']['rootuser'] = 'administrator';
8282
 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+
8389 ?>
8490 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
8591 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
@@ -615,6 +621,12 @@
616622 ## MSSQL specific
617623 // We need a second field so it doesn't overwrite the MySQL one
618624 $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" );
619631
620632 $conf->ShellLocale = getShellLocale( $conf->LanguageCode );
621633
@@ -786,6 +798,9 @@
787799 $wgDBprefix = $conf->DBprefix2;
788800 }
789801
 802+ ## DB2 specific:
 803+ $wgDBcataloged = $conf->DBcataloged;
 804+
790805 $wgCommandLineMode = true;
791806 if (! defined ( 'STDERR' ) )
792807 define( 'STDERR', fopen("php://stderr", "wb"));
@@ -861,12 +876,31 @@
862877 } #conn. att.
863878
864879 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+
866899 } else { # not mysql
867900 error_reporting( E_ALL );
868901 $wgSuperUser = '';
869902 ## 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' ) {
871905 $wgDBsuperuser = $conf->RootUser;
872906 echo( "<li>Attempting to connect to database \"postgres\" as superuser \"$wgDBsuperuser\"..." );
873907 $wgDatabase = $dbc->newFromParams($wgDBserver, $wgDBsuperuser, $conf->RootPW, "postgres", 1);
@@ -1113,6 +1147,8 @@
11141148 $revid = $revision->insertOn( $wgDatabase );
11151149 $article->updateRevisionOn( $wgDatabase, $revision );
11161150 }
 1151+ // Now that all database work is done, make sure everything is committed
 1152+ $wgDatabase->commit();
11171153
11181154 /* Write out the config file now that all is well */
11191155 print "<li style=\"list-style: none\">\n";
@@ -1459,6 +1495,25 @@
14601496 <p>Avoid exotic characters; something like <tt>mw_</tt> is good.</p>
14611497 </div>
14621498 </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>
14631518
14641519 <div class="config-input" style="padding:2em 0 3em">
14651520 <label class='column'>&nbsp;</label>
@@ -1629,6 +1684,12 @@
16301685 $dbsettings =
16311686 "# MSSQL specific settings
16321687 \$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']}\";";
16331694 } else {
16341695 // ummm... :D
16351696 $dbsettings = '';

Follow-up revisions

RevisionCommit summaryAuthorDate
r45765* svn:eol-style native for r45755...catrope10:18, 15 January 2009
r72166Completed removal of $wgDBport_db2 in favour of regular $wgDBport begun by pl...leonsp21:06, 1 September 2010

Comments

#Comment by Bryan (talk | contribs)   22:33, 14 January 2009

This looks like an svn:eol-style problem...

#Comment by Catrope (talk | contribs)   10:19, 15 January 2009

Fixed in r45765

#Comment by Platonides (talk | contribs)   09:45, 26 July 2010

$wgDBcataloged should be set to a sensible default in DefaultSettings.php

The same would apply to $wgDBport_db2, but I have replaced it with the normal $wgDBport in r69918

#Comment by 😂 (talk | contribs)   12:43, 10 August 2010

From looking at the code, the behavior of $wgDBcataloged is based around it's value of null/true/false. Not sure what the default should be (true?), but it seems to affect whether it uses $wgDBname or some weird connection string--see openCataloged() vs. openUncataloged().

Either way, the code isn't up to standards, and doesn't seem to be very loved. Back in April or so somebody claimed they were still looking at it, but I haven't heard or seen anything since. I wouldn't be opposed to outright removal at this point. In general, I think we should adopt that policy towards supporting various DBs. If nobody is going to maintain it, we shouldn't be shipping it.

#Comment by Leonsp (talk | contribs)   02:01, 11 August 2010

I realize that I haven't made new commits in some time, but I would like to bring this up to spec. There are considerable improvements to the DB2 support layer on my drive, but I got bogged down debugging a breaking schema issue and haven't committed. I will now take a close look again.

$wgDBcataloged is based on true/false logic. It should never have a null value.

1. "cataloged" and "uncataloged" are probably bad values to represent true and false. Would 1 and 0 be the best alternative?

2. If 1 and 0 are the best, I will set it to a default of 1 in DefaultSettings.php

3. Is there anything specific that needs to be improved in terms of standards adherence? I have read the document. A lot of the existing database API does not meet standards. For example, the parent class in Database.php does not explicitly declare public/private/protected everywhere or use consistent spacing in conditionals. I will try to improve on standards adherence in general, but it would be good to have a specific issue to address.

#Comment by MZMcBride (talk | contribs)   02:05, 11 August 2010

What's wrong with using "true" and "false"?

#Comment by Leonsp (talk | contribs)   02:06, 11 August 2010

"true" and "false" it is.

#Comment by Leonsp (talk | contribs)   21:14, 1 September 2010

After thinking about it, I have removed $wgDBcataloged in r72166. There is no performance difference and this seems to have been a confusing option. All connections are now uncataloged.

Cataloged connections use a local DB2 catalog on the web server to connect to a local or remote database, thus not requiring a hostname or port number. Uncataloged connections require a hostname and port number whether local or remote. This is pretty esoteric and doesn't need to be exposed to end users.

I've also completed the removal of $wgDBport.

Time for me to look at adding DB2 support to the new installer.

#Comment by Platonides (talk | contribs)   11:23, 31 August 2010

Doesn't Ibm db2 support BEGIN and COMMIT keywords specified in SQL standard, that you need to do them by messing with db2_autocommit ?

#Comment by MaxSem (talk | contribs)   05:19, 1 September 2010

Not only it supports these commands, but they are actually used in tables.sql for DB2.

#Comment by Leonsp (talk | contribs)   19:30, 2 September 2010

There's no actual BEGIN TRANSACTION statement on DB2. It's implicit when in transactional mode (i.e. when AutoCommit is off). I have to turn AutoCommit off explicitly because otherwise all changes are immediately committed.

I'd be happy to leave AutoCommit off permanently, but I got feedback some time back that not all MediaWiki code is transactional. Thus, AutoCommit has to be generally on except in a transaction.

Status & tagging log