r106480 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r106479‎ | r106480 | r106481 >
Date:21:17, 16 December 2011
Author:leonsp
Status:deferred (Comments)
Tags:
Comment:
Fixes to DB2 support:
* Database schema update to reflect 1.18 and 1.19 changes.
* Better support for numRows() and dataSeek() functions on DB2 result sets.
* Updates to DB2 installer and updater classes.
* Developed by Andre, Diego, and other students at Minho University and reviewed by me.
* See r85885, r85896.
Modified paths:
  • /trunk/phase3/includes/db/DatabaseIbm_db2.php (modified) (history)
  • /trunk/phase3/includes/installer/Ibm_db2Installer.php (modified) (history)
  • /trunk/phase3/includes/installer/Ibm_db2Updater.php (modified) (history)
  • /trunk/phase3/maintenance/ibm_db2/tables.sql (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/ibm_db2/tables.sql
@@ -21,7 +21,7 @@
2222 user_email_token_expires TIMESTAMP(3),
2323 user_email_authenticated TIMESTAMP(3),
2424 -- obsolete, replace by user_properties table
25 - user_options CLOB(64K) INLINE LENGTH 4096,
 25+ -- user_options CLOB(64K) INLINE LENGTH 4096,
2626 user_touched TIMESTAMP(3),
2727 user_registration TIMESTAMP(3),
2828 user_editcount INTEGER
@@ -33,14 +33,16 @@
3434 INCLUDE (user_name, user_real_name, user_password, user_newpassword, user_newpass_time, user_token,
3535 user_email, user_email_token, user_email_token_expires, user_email_authenticated,
3636 user_touched, user_registration, user_editcount);
 37+
 38+CREATE UNIQUE INDEX user_email ON user (user_email);
3739
3840 -- Create a dummy user to satisfy fk contraints especially with revisions
3941 INSERT INTO user(
4042 user_name, user_real_name, user_password, user_newpassword, user_newpass_time,
41 -user_email, user_email_authenticated, user_options, user_token, user_registration, user_editcount)
 43+user_email, user_email_authenticated, user_token, user_registration, user_editcount)
4244 VALUES (
4345 'Anonymous','', NULL, NULL, CURRENT_TIMESTAMP,
44 -NULL, NULL, NULL, NULL, CURRENT_timestamp, 0);
 46+NULL, NULL, NULL, CURRENT_timestamp, 0);
4547
4648
4749 CREATE TABLE user_groups (
@@ -105,7 +107,8 @@
106108 rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
107109 rev_deleted SMALLINT NOT NULL DEFAULT 0,
108110 rev_len BIGINT,
109 - rev_parent_id BIGINT DEFAULT NULL
 111+ rev_parent_id BIGINT DEFAULT NULL,
 112+ rev_sha1 VARCHAR(255) NOT NULL DEFAULT ''
110113 );
111114 CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
112115 CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
@@ -171,7 +174,8 @@
172175 ar_deleted SMALLINT NOT NULL DEFAULT 0,
173176 ar_len INTEGER,
174177 ar_page_id INTEGER,
175 - ar_parent_id INTEGER
 178+ ar_parent_id INTEGER,
 179+ ar_sha1 VARCHAR(255) NOT NULL DEFAULT ''
176180 );
177181 CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
178182 CREATE INDEX archive_user_text ON archive (ar_user_text);
@@ -522,7 +526,31 @@
523527 CREATE INDEX log_user_type_time ON logging (log_user, log_type, log_timestamp);
524528 CREATE INDEX log_page_id_time ON logging (log_page,log_timestamp);
525529
 530+CREATE UNIQUE INDEX "TYPE_ACTION" ON "LOGGING"
 531+(
 532+"LOG_TYPE",
 533+"LOG_ACTION",
 534+"LOG_TIMESTAMP"
 535+)
 536+;
526537
 538+
 539+
 540+CREATE TABLE trackbacks (
 541+ tb_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
 542+ --PRIMARY KEY DEFAULT nextval('trackbacks_tb_id_seq'),
 543+ -- foreign key also in MySQL
 544+ tb_page INTEGER,
 545+ -- REFERENCES page(page_id) ON DELETE CASCADE,
 546+ tb_title VARCHAR(255) NOT NULL,
 547+ tb_url CLOB(64K) INLINE LENGTH 4096 NOT NULL,
 548+ tb_ex CLOB(64K) INLINE LENGTH 4096,
 549+ tb_name VARCHAR(255)
 550+);
 551+CREATE INDEX trackback_page ON trackbacks (tb_page);
 552+
 553+
 554+
527555 CREATE TABLE job (
528556 job_id BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
529557 --PRIMARY KEY DEFAULT nextval('job_job_id_seq'),
@@ -637,7 +665,7 @@
638666 up_user BIGINT NOT NULL,
639667
640668 -- Name of the option being saved. This is indexed for bulk lookup.
641 - up_property VARCHAR(32) FOR BIT DATA NOT NULL,
 669+ up_property VARCHAR(255) FOR BIT DATA NOT NULL,
642670
643671 -- Property value as a string.
644672 up_value CLOB(64K) INLINE LENGTH 4096
@@ -722,3 +750,75 @@
723751 "IWL_TITLE" VARCHAR(255) FOR BIT DATA NOT NULL
724752 )
725753 ;
 754+
 755+
 756+--
 757+-- Store information about newly uploaded files before they're
 758+-- moved into the actual filestore
 759+--
 760+CREATE TABLE /*_*/uploadstash (
 761+ us_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
 762+
 763+ -- the user who uploaded the file.
 764+ us_user BIGINT NOT NULL,
 765+
 766+ -- file key. this is how applications actually search for the file.
 767+ -- this might go away, or become the primary key.
 768+ us_key varchar(255) NOT NULL,
 769+
 770+ -- the original path
 771+ us_orig_path varchar(255) NOT NULL,
 772+
 773+ -- the temporary path at which the file is actually stored
 774+ us_path varchar(255) NOT NULL,
 775+
 776+ -- which type of upload the file came from (sometimes)
 777+ us_source_type varchar(50),
 778+
 779+ -- the date/time on which the file was added
 780+ us_timestamp TIMESTAMP(3) not null,
 781+
 782+ us_status varchar(50) not null,
 783+
 784+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
 785+ --
 786+ us_size BIGINT NOT NULL,
 787+ -- this hash comes from File::sha1Base36(), and is 31 characters
 788+ us_sha1 varchar(31) NOT NULL,
 789+ us_mime varchar(255),
 790+ -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
 791+ us_media_type VARCHAR(30) CONSTRAINT my_constraint CHECK (us_media_type in ('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE')) default NULL,
 792+ -- image-specific properties
 793+ us_image_width BIGINT,
 794+ us_image_height BIGINT,
 795+ us_image_bits integer
 796+
 797+) /*$wgDBTableOptions*/;
 798+
 799+-- sometimes there's a delete for all of a user's stuff.
 800+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
 801+-- pick out files by key, enforce key uniqueness
 802+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
 803+-- the abandoned upload cleanup script needs this
 804+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
 805+
 806+
 807+
 808+-- Stores the groups the user has once belonged to.
 809+-- The user may still belong these groups. Check user_groups.
 810+
 811+CREATE TABLE user_former_groups (
 812+ ufg_user BIGINT NOT NULL DEFAULT 0,
 813+ ufg_group VARCHAR(16) FOR BIT DATA NOT NULL
 814+);
 815+CREATE UNIQUE INDEX ufg_user_group ON user_former_groups (ufg_user, ufg_group);
 816+
 817+
 818+-- Table for holding configuration changes
 819+CREATE TABLE "CONFIG"
 820+(
 821+"CF_NAME" VARCHAR(255) NOT NULL PRIMARY KEY,
 822+"CF_VALUE" BLOB NOT NULL
 823+)
 824+;
 825+
Index: trunk/phase3/includes/db/DatabaseIbm_db2.php
@@ -104,6 +104,147 @@
105105 }
106106
107107 /**
 108+ * Wrapper to address lack of certain operations in the DB2 driver
 109+ * ( seek, num_rows )
 110+ * @ingroup Database
 111+ * @since 1.19
 112+ */
 113+class IBM_DB2Result{
 114+ private $db;
 115+ private $result;
 116+ private $num_rows;
 117+ private $current_pos;
 118+ private $columns = array();
 119+ private $sql;
 120+
 121+ private $resultSet = array();
 122+ private $loadedLines = 0;
 123+
 124+ /**
 125+ * Construct and initialize a wrapper for DB2 query results
 126+ * @param $db Database
 127+ * @param $result Object
 128+ * @param $num_rows Integer
 129+ * @param $sql String
 130+ * @param $columns Array
 131+ */
 132+ public function __construct( $db, $result, $num_rows, $sql, $columns ){
 133+ $this->db = $db;
 134+
 135+ if( $result instanceof ResultWrapper ){
 136+ $this->result = $result->result;
 137+ }
 138+ else{
 139+ $this->result = $result;
 140+ }
 141+
 142+ $this->num_rows = $num_rows;
 143+ $this->current_pos = 0;
 144+ if ( $this->num_rows > 0 ) {
 145+ // Make a lower-case list of the column names
 146+ // By default, DB2 column names are capitalized
 147+ // while MySQL column names are lowercase
 148+
 149+ // Is there a reasonable maximum value for $i?
 150+ // Setting to 2048 to prevent an infinite loop
 151+ for( $i = 0; $i < 2048; $i++ ) {
 152+ $name = db2_field_name( $this->result, $i );
 153+ if ( $name != false ) {
 154+ continue;
 155+ }
 156+ else {
 157+ return false;
 158+ }
 159+
 160+ $this->columns[$i] = strtolower( $name );
 161+ }
 162+ }
 163+
 164+ $this->sql = $sql;
 165+ }
 166+
 167+ /**
 168+ * Unwrap the DB2 query results
 169+ * @return mixed Object on success, false on failure
 170+ */
 171+ public function getResult() {
 172+ if ( $this->result ) {
 173+ return $this->result;
 174+ }
 175+ else return false;
 176+ }
 177+
 178+ /**
 179+ * Get the number of rows in the result set
 180+ * @return integer
 181+ */
 182+ public function getNum_rows() {
 183+ return $this->num_rows;
 184+ }
 185+
 186+ /**
 187+ * Return a row from the result set in object format
 188+ * @return mixed Object on success, false on failure.
 189+ */
 190+ public function fetchObject() {
 191+ if ( $this->result
 192+ && $this->num_rows > 0
 193+ && $this->current_pos >= 0
 194+ && $this->current_pos < $this->num_rows )
 195+ {
 196+ $row = $this->fetchRow();
 197+ $ret = new stdClass();
 198+
 199+ foreach ( $row as $k => $v ) {
 200+ $lc = $this->columns[$k];
 201+ $ret->$lc = $v;
 202+ }
 203+ return $ret;
 204+ }
 205+ return false;
 206+ }
 207+
 208+ /**
 209+ * Return a row form the result set in array format
 210+ * @return mixed Array on success, false on failure
 211+ * @throws DBUnexpectedError
 212+ */
 213+ public function fetchRow(){
 214+ if ( $this->result
 215+ && $this->num_rows > 0
 216+ && $this->current_pos >= 0
 217+ && $this->current_pos < $this->num_rows )
 218+ {
 219+ if ( $this->loadedLines <= $this->current_pos ) {
 220+ $row = db2_fetch_array( $this->result );
 221+ $this->resultSet[$this->loadedLines++] = $row;
 222+ if ( $this->db->lastErrno() ) {
 223+ throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
 224+ . htmlspecialchars( $this->db->lastError() ) );
 225+ }
 226+ }
 227+
 228+ if ( $this->loadedLines > $this->current_pos ){
 229+ return $this->resultSet[$this->current_pos++];
 230+ }
 231+
 232+ }
 233+ return false;
 234+ }
 235+
 236+ /**
 237+ * Free a DB2 result object
 238+ * @throws DBUnexpectedError
 239+ */
 240+ public function freeResult(){
 241+ unset( $this->resultSet );
 242+ if ( !@db2_free_result( $this->result ) ) {
 243+ throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
 244+ }
 245+ }
 246+}
 247+
 248+/**
108249 * Primary database interface
109250 * @ingroup Database
110251 */
@@ -137,6 +278,8 @@
138279 protected $mAffectedRows = null;
139280 /** Number of rows returned by last SELECT */
140281 protected $mNumRows = null;
 282+ /** Current row number on the cursor of the last SELECT */
 283+ protected $currentRow = 0;
141284
142285 /** Connection config options - see constructor */
143286 public $mConnOptions = array();
@@ -233,7 +376,7 @@
234377 /**
235378 * Returns a unique string representing the wiki on the server
236379 */
237 - function getWikiID() {
 380+ public function getWikiID() {
238381 if( $this->mSchema ) {
239382 return "{$this->mDBname}-{$this->mSchema}";
240383 } else {
@@ -241,10 +384,22 @@
242385 }
243386 }
244387
245 - function getType() {
 388+ /**
 389+ * Returns the database software identifieir
 390+ * @return string
 391+ */
 392+ public function getType() {
246393 return 'ibm_db2';
247394 }
248395
 396+ /**
 397+ * Returns the database connection object
 398+ * @return Object
 399+ */
 400+ public function getDb(){
 401+ return $this->mConn;
 402+ }
 403+
249404 /**
250405 *
251406 * @param $server String: hostname of database server
@@ -268,17 +423,12 @@
269424 }
270425
271426 // configure the connection and statement objects
272 - /*
273 - $this->setDB2Option( 'cursor', 'DB2_SCROLLABLE',
274 - self::CONN_OPTION | self::STMT_OPTION );
275 - */
276427 $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
277428 self::CONN_OPTION | self::STMT_OPTION );
278429 $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
279430 self::STMT_OPTION );
280431 $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
281432 self::STMT_OPTION );
282 -
283433 parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
284434 }
285435
@@ -361,8 +511,6 @@
362512 throw new DBConnectionError( $this, $this->lastError() );
363513 }
364514
365 - // Apply connection config
366 - db2_set_option( $this->mConn, $this->mConnOptions, 1 );
367515 // Some MediaWiki code is still transaction-less (?).
368516 // The strategy is to keep AutoCommit on for that code
369517 // but switch it off whenever a transaction is begun.
@@ -391,7 +539,7 @@
392540 {
393541 $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
394542 wfSuppressWarnings();
395 - $this->mConn = db2_pconnect($dsn, "", "", array());
 543+ $this->mConn = db2_pconnect( $dsn, "", "", array() );
396544 wfRestoreWarnings();
397545 }
398546
@@ -464,7 +612,7 @@
465613 // Needed to handle any UTF-8 encoding issues in the raw sql
466614 // Note that we fully support prepared statements for DB2
467615 // prepare() and execute() should be used instead of doQuery() whenever possible
468 - $sql = utf8_decode($sql);
 616+ $sql = utf8_decode( $sql );
469617
470618 $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
471619 if( $ret == false ) {
@@ -1062,9 +1210,13 @@
10631211 */
10641212 public function dataSeek( $res, $row ) {
10651213 if ( $res instanceof ResultWrapper ) {
1066 - $res = $res->result;
 1214+ return $res = $res->result;
10671215 }
1068 - return db2_fetch_row( $res, $row );
 1216+ if ( $res instanceof IBM_DB2Result ) {
 1217+ return $res->dataSeek( $row );
 1218+ }
 1219+ wfDebug( "dataSeek operation in DB2 database\n" );
 1220+ return false;
10691221 }
10701222
10711223 ###
@@ -1097,6 +1249,9 @@
10981250 if ( $res instanceof ResultWrapper ) {
10991251 $res = $res->result;
11001252 }
 1253+ if ( $res instanceof IBM_DB2Result ) {
 1254+ $res = $res->getResult();
 1255+ }
11011256 return db2_num_fields( $res );
11021257 }
11031258
@@ -1110,6 +1265,9 @@
11111266 if ( $res instanceof ResultWrapper ) {
11121267 $res = $res->result;
11131268 }
 1269+ if ( $res instanceof IBM_DB2Result ) {
 1270+ $res = $res->getResult();
 1271+ }
11141272 return db2_field_name( $res, $n );
11151273 }
11161274
@@ -1122,7 +1280,7 @@
11231281 * @param $fname String: calling function name (use __METHOD__)
11241282 * for logs/profiling
11251283 * @param $options Associative array of options
1126 - * (e.g. array('GROUP BY' => 'page_title')),
 1284+ * (e.g. array( 'GROUP BY' => 'page_title' )),
11271285 * see Database::makeSelectOptions code for list of
11281286 * supported stuff
11291287 * @param $join_conds Associative array of table join conditions (optional)
@@ -1135,6 +1293,7 @@
11361294 {
11371295 $res = parent::select( $table, $vars, $conds, $fname, $options,
11381296 $join_conds );
 1297+ $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
11391298
11401299 // We must adjust for offset
11411300 if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
@@ -1161,10 +1320,11 @@
11621321
11631322 $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
11641323 $join_conds );
 1324+
11651325 $obj = $this->fetchObject( $res2 );
11661326 $this->mNumRows = $obj->num_rows;
1167 -
1168 - return $res;
 1327+
 1328+ return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
11691329 }
11701330
11711331 /**
@@ -1332,6 +1492,9 @@
13331493 if ( $res instanceof ResultWrapper ) {
13341494 $res = $res->result;
13351495 }
 1496+ if ( $res instanceof IBM_DB2Result ) {
 1497+ $res = $res->getResult();
 1498+ }
13361499 return db2_field_type( $res, $index );
13371500 }
13381501
Index: trunk/phase3/includes/installer/Ibm_db2Installer.php
@@ -216,7 +216,7 @@
217217 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
218218
219219 try {
220 - $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES' );
 220+ $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES FOR READ ONLY' );
221221 if( $result == false ) {
222222 $status->fatal( 'config-connection-error', '' );
223223 } else {
@@ -249,7 +249,7 @@
250250 \$wgDBport = \"{$port}\";";
251251 }
252252
253 - public function __construct($parent) {
254 - parent::__construct($parent);
 253+ public function __construct( $parent ) {
 254+ parent::__construct( $parent );
255255 }
256256 }
Index: trunk/phase3/includes/installer/Ibm_db2Updater.php
@@ -45,25 +45,31 @@
4646 array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
4747 array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
4848 array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
49 - array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
50 -
51 - // Tables
52 - array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
5349 array( 'addTable', 'msg_resource_links', 'patch-msg_resource_links.sql' ),
54 - array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
55 - array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
56 -
57 - // Indexes
5850 array( 'addIndex', 'msg_resource_links', 'uq61_msg_resource_links', 'patch-uq_61_msg_resource_links.sql' ),
5951 array( 'addIndex', 'msg_resource', 'uq81_msg_resource', 'patch-uq_81_msg_resource.sql' ),
 52+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
6053 array( 'addIndex', 'module_deps', 'uq96_module_deps', 'patch-uq_96_module_deps.sql' ),
61 -
62 - // Fields
 54+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api-field.sql' ),
 55+ array( 'addField', 'interwiki', 'iw_wikiid', 'patch-iw_wikiid-field.sql' )
6356 array( 'addField', 'categorylinks', 'cl_sortkey_prefix', 'patch-cl_sortkey_prefix-field.sql' ),
6457 array( 'addField', 'categorylinks', 'cl_collation', 'patch-cl_collation-field.sql' ),
6558 array( 'addField', 'categorylinks', 'cl_type', 'patch-cl_type-field.sql' ),
66 - array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api-field.sql' ),
67 - array( 'addField', 'interwiki', 'iw_wikiid', 'patch-iw_wikiid-field.sql' )
 59+
 60+ //1.18
 61+ array( 'doUserNewTalkTimestampNotNull' ),
 62+ array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
 63+ array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
 64+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
 65+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
 66+ array( 'doRebuildLocalisationCache' ),
 67+
 68+ // 1.19
 69+ array( 'addTable', 'config', 'patch-config.sql' ),
 70+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
 71+ array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
 72+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
 73+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' )
6874 );
6975 }
7076 }
\ No newline at end of file

Follow-up revisions

RevisionCommit summaryAuthorDate
r106483Fix parser error in r106480....platonides22:33, 16 December 2011
r106507Fixes to address MaxSem's comment on r106480 regarding DB2 tables.sql syntax,...leonsp15:59, 17 December 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r85885Fixes to DB2 support. DB2 support integration with 1.17 Installer and Updater...leonsp16:35, 12 April 2011
r85896Addressing comments by Krinkle on r85885. Removing commented-out code. Verifi...leonsp17:18, 12 April 2011

Comments

#Comment by MaxSem (talk | contribs)   09:54, 17 December 2011

tables.sql has inconsistent syntax, indentation and capitalization.

#Comment by Leonsp (talk | contribs)   16:02, 17 December 2011

Checked in consistency fixes in r106507.

Status & tagging log