Index: trunk/phase3/maintenance/archives/patch-iwlinks.sql |
— | — | @@ -0,0 +1,16 @@ |
| 2 | +-- |
| 3 | +-- Track inline interwiki links |
| 4 | +-- |
| 5 | +CREATE TABLE /*_*/iwlinks ( |
| 6 | + -- page_id of the referring page |
| 7 | + iwl_from int unsigned NOT NULL default 0, |
| 8 | + |
| 9 | + -- Interwiki prefix code of the target |
| 10 | + iwl_prefix varbinary(20) NOT NULL default '', |
| 11 | + |
| 12 | + -- Title of the target, including namespace |
| 13 | + iwl_title varchar(255) binary NOT NULL default '' |
| 14 | +) /*$wgDBTableOptions*/; |
| 15 | + |
| 16 | +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); |
| 17 | +CREATE INDEX /*i*/iwl_prefix ON /*_*/iwlinks (iwl_prefix, iwl_title); |
Property changes on: trunk/phase3/maintenance/archives/patch-iwlinks.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 18 | + native |
Index: trunk/phase3/maintenance/parserTests.inc |
— | — | @@ -602,7 +602,7 @@ |
603 | 603 | global $wgDBtype; |
604 | 604 | $tables = array('user', 'page', 'page_restrictions', |
605 | 605 | 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', |
606 | | - 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', |
| 606 | + 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks', |
607 | 607 | 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', |
608 | 608 | 'recentchanges', 'watchlist', 'math', 'interwiki', |
609 | 609 | 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', |
Index: trunk/phase3/maintenance/updaters.inc |
— | — | @@ -172,6 +172,9 @@ |
173 | 173 | array( 'do_update_mime_minor_field' ), |
174 | 174 | // Should've done this back in 1.10, but better late than never: |
175 | 175 | array( 'do_populate_rev_len' ), |
| 176 | + |
| 177 | + // 1.17 |
| 178 | + array( 'add_table', 'iwlinks', 'patch-iwlinks.sql' ), |
176 | 179 | ), |
177 | 180 | |
178 | 181 | 'sqlite' => array( |
— | — | @@ -199,6 +202,9 @@ |
200 | 203 | array( 'do_update_transcache_field' ), |
201 | 204 | // version-independent searchindex setup, added in 1.16 |
202 | 205 | array( 'sqlite_setup_searchindex' ), |
| 206 | + |
| 207 | + // 1.17 |
| 208 | + array( 'add_table', 'iwlinks', 'patch-iwlinks.sql' ), // @fixme so far untested on sqlite 2010-04-16 |
203 | 209 | ), |
204 | 210 | ); |
205 | 211 | |
— | — | @@ -1590,6 +1596,7 @@ |
1591 | 1597 | array('user_properties', 'patch-user_properties.sql'), |
1592 | 1598 | array('log_search', 'patch-log_search.sql'), |
1593 | 1599 | array('l10n_cache', 'patch-l10n_cache.sql'), |
| 1600 | + // @fixme add iwlinks table |
1594 | 1601 | ); |
1595 | 1602 | |
1596 | 1603 | $newcols = array( |
Index: trunk/phase3/maintenance/tables.sql |
— | — | @@ -607,7 +607,25 @@ |
608 | 608 | CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title); |
609 | 609 | |
610 | 610 | |
| 611 | +-- |
| 612 | +-- Track inline interwiki links |
611 | 613 | -- |
| 614 | +CREATE TABLE /*_*/iwlinks ( |
| 615 | + -- page_id of the referring page |
| 616 | + iwl_from int unsigned NOT NULL default 0, |
| 617 | + |
| 618 | + -- Interwiki prefix code of the target |
| 619 | + iwl_prefix varbinary(20) NOT NULL default '', |
| 620 | + |
| 621 | + -- Title of the target, including namespace |
| 622 | + iwl_title varchar(255) binary NOT NULL default '' |
| 623 | +) /*$wgDBTableOptions*/; |
| 624 | + |
| 625 | +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); |
| 626 | +CREATE INDEX /*i*/iwl_prefix ON /*_*/iwlinks (iwl_prefix, iwl_title); |
| 627 | + |
| 628 | + |
| 629 | +-- |
612 | 630 | -- Contains a single row with some aggregate info |
613 | 631 | -- on the state of the site. |
614 | 632 | -- |
Index: trunk/phase3/includes/LinkBatch.php |
— | — | @@ -144,48 +144,9 @@ |
145 | 145 | * |
146 | 146 | * @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc) |
147 | 147 | * @param $db DatabaseBase object to use |
148 | | - * @return String |
| 148 | + * @return mixed string with SQL where clause fragment, or false if no items. |
149 | 149 | */ |
150 | | - public function constructSet( $prefix, &$db ) { |
151 | | - $first = true; |
152 | | - $firstTitle = true; |
153 | | - $sql = ''; |
154 | | - foreach ( $this->data as $ns => $dbkeys ) { |
155 | | - if ( !count( $dbkeys ) ) { |
156 | | - continue; |
157 | | - } |
158 | | - |
159 | | - if ( $first ) { |
160 | | - $first = false; |
161 | | - } else { |
162 | | - $sql .= ' OR '; |
163 | | - } |
164 | | - |
165 | | - if (count($dbkeys)==1) { // avoid multiple-reference syntax if simple equality can be used |
166 | | - $singleKey = array_keys($dbkeys); |
167 | | - $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title=". |
168 | | - $db->addQuotes($singleKey[0]). |
169 | | - ")"; |
170 | | - } else { |
171 | | - $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN ("; |
172 | | - |
173 | | - $firstTitle = true; |
174 | | - foreach( $dbkeys as $dbkey => $unused ) { |
175 | | - if ( $firstTitle ) { |
176 | | - $firstTitle = false; |
177 | | - } else { |
178 | | - $sql .= ','; |
179 | | - } |
180 | | - $sql .= $db->addQuotes( $dbkey ); |
181 | | - } |
182 | | - $sql .= '))'; |
183 | | - } |
184 | | - } |
185 | | - if ( $first && $firstTitle ) { |
186 | | - # No titles added |
187 | | - return false; |
188 | | - } else { |
189 | | - return $sql; |
190 | | - } |
| 150 | + public function constructSet( $prefix, $db ) { |
| 151 | + return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" ); |
191 | 152 | } |
192 | 153 | } |
Index: trunk/phase3/includes/parser/LinkHolderArray.php |
— | — | @@ -162,6 +162,9 @@ |
163 | 163 | # Check if it's a static known link, e.g. interwiki |
164 | 164 | if ( $title->isAlwaysKnown() ) { |
165 | 165 | $colours[$pdbk] = ''; |
| 166 | + if( $title->getInterwiki() != '' ) { |
| 167 | + $output->addInterwikiLink( $title ); |
| 168 | + } |
166 | 169 | } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { |
167 | 170 | $colours[$pdbk] = $sk->getLinkColour( $title, $threshold ); |
168 | 171 | $output->addLink( $title, $id ); |
Index: trunk/phase3/includes/parser/Parser.php |
— | — | @@ -1830,7 +1830,7 @@ |
1831 | 1831 | # |
1832 | 1832 | # FIXME: isAlwaysKnown() can be expensive for file links; we should really do |
1833 | 1833 | # batch file existence checks for NS_FILE and NS_MEDIA |
1834 | | - if ( $iw == '' && $nt->isAlwaysKnown() ) { |
| 1834 | + if ( $iw = '' && $nt->isAlwaysKnown() ) { |
1835 | 1835 | $this->mOutput->addLink( $nt ); |
1836 | 1836 | $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); |
1837 | 1837 | } else { |
Index: trunk/phase3/includes/parser/ParserOutput.php |
— | — | @@ -17,6 +17,7 @@ |
18 | 18 | $mTemplateIds = array(), # 2-D map of NS/DBK to rev ID for the template references. ID=zero for broken. |
19 | 19 | $mImages = array(), # DB keys of the images used, in the array key only |
20 | 20 | $mExternalLinks = array(), # External link URLs, in the key only |
| 21 | + $mInterwikiLinks = array(), # 2-D map of prefix/DBK (in keys only) for the inline interwiki links in the document. |
21 | 22 | $mNewSection = false, # Show a new section link? |
22 | 23 | $mHideNewSection = false, # Hide the new section link? |
23 | 24 | $mNoGallery = false, # No gallery on category page? (__NOGALLERY__) |
— | — | @@ -40,6 +41,7 @@ |
41 | 42 | |
42 | 43 | function getText() { return $this->mText; } |
43 | 44 | function &getLanguageLinks() { return $this->mLanguageLinks; } |
| 45 | + function getInterwikiLinks() { return $this->mInterwikiLinks; } |
44 | 46 | function getCategoryLinks() { return array_keys( $this->mCategories ); } |
45 | 47 | function &getCategories() { return $this->mCategories; } |
46 | 48 | function getCacheTime() { return $this->mCacheTime; } |
— | — | @@ -96,9 +98,17 @@ |
97 | 99 | $this->mExternalLinks[$url] = 1; |
98 | 100 | } |
99 | 101 | |
| 102 | + /** |
| 103 | + * Record a local or interwiki inline link for saving in future link tables. |
| 104 | + * |
| 105 | + * @param Title $title |
| 106 | + * @param mixed $id optional known page_id so we can skip the lookup |
| 107 | + */ |
100 | 108 | function addLink( $title, $id = null ) { |
| 109 | + wfDebug(__METHOD__ . " got: " . $title->getPrefixedText() . "\n"); |
101 | 110 | if ( $title->isExternal() ) { |
102 | 111 | // Don't record interwikis in pagelinks |
| 112 | + $this->addInterwikiLink( $title ); |
103 | 113 | return; |
104 | 114 | } |
105 | 115 | $ns = $title->getNamespace(); |
— | — | @@ -139,6 +149,21 @@ |
140 | 150 | } |
141 | 151 | $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning |
142 | 152 | } |
| 153 | + |
| 154 | + /** |
| 155 | + * @param Title $title object, must be an interwiki link |
| 156 | + * @throws MWException if given invalid input |
| 157 | + */ |
| 158 | + function addInterwikiLink( $title ) { |
| 159 | + $prefix = $title->getInterwiki(); |
| 160 | + if( $prefix == '' ) { |
| 161 | + throw new MWException( 'Non-interwiki link passed, internal parser error.' ); |
| 162 | + } |
| 163 | + if (!isset($this->mInterwikiLinks[$prefix])) { |
| 164 | + $this->mInterwikiLinks[$prefix] = array(); |
| 165 | + } |
| 166 | + $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1; |
| 167 | + } |
143 | 168 | |
144 | 169 | /** |
145 | 170 | * Return true if this cached output object predates the global or |
Index: trunk/phase3/includes/db/Database.php |
— | — | @@ -1274,6 +1274,33 @@ |
1275 | 1275 | } |
1276 | 1276 | |
1277 | 1277 | /** |
| 1278 | + * Build a partial where clause from a 2-d array such as used for LinkBatch. |
| 1279 | + * The keys on each level may be either integers or strings. |
| 1280 | + * |
| 1281 | + * @param array $data organized as 2-d array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...) |
| 1282 | + * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace') |
| 1283 | + * @param string $subKey field name to match the sub-level keys to (eg 'pl_title') |
| 1284 | + * @return mixed string SQL fragment, or false if no items in array. |
| 1285 | + */ |
| 1286 | + function makeWhereFrom2d( $data, $baseKey, $subKey ) { |
| 1287 | + $conds = array(); |
| 1288 | + foreach ( $data as $base => $sub ) { |
| 1289 | + if ( count( $sub ) ) { |
| 1290 | + $conds[] = $this->makeList( |
| 1291 | + array( $baseKey => $base, $subKey => array_keys( $sub ) ), |
| 1292 | + LIST_AND); |
| 1293 | + } |
| 1294 | + } |
| 1295 | + |
| 1296 | + if ( $conds ) { |
| 1297 | + return $this->makeList( $conds, LIST_OR ); |
| 1298 | + } else { |
| 1299 | + // Nothing to search for... |
| 1300 | + return false; |
| 1301 | + } |
| 1302 | + } |
| 1303 | + |
| 1304 | + /** |
1278 | 1305 | * Bitwise operations |
1279 | 1306 | */ |
1280 | 1307 | |
Index: trunk/phase3/includes/LinksUpdate.php |
— | — | @@ -54,6 +54,7 @@ |
55 | 55 | $this->mExternals = $parserOutput->getExternalLinks(); |
56 | 56 | $this->mCategories = $parserOutput->getCategories(); |
57 | 57 | $this->mProperties = $parserOutput->getProperties(); |
| 58 | + $this->mInterwikis = $parserOutput->getInterwikiLinks(); |
58 | 59 | |
59 | 60 | # Convert the format of the interlanguage links |
60 | 61 | # I didn't want to change it in the ParserOutput, because that array is passed all |
— | — | @@ -115,6 +116,11 @@ |
116 | 117 | $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ), |
117 | 118 | $this->getInterlangInsertions( $existing ) ); |
118 | 119 | |
| 120 | + # Inline interwiki links |
| 121 | + $existing = $this->getExistingInterwikis(); |
| 122 | + $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ), |
| 123 | + $this->getInterwikiInsertions( $existing ) ); |
| 124 | + |
119 | 125 | # Template links |
120 | 126 | $existing = $this->getExistingTemplates(); |
121 | 127 | $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ), |
— | — | @@ -175,6 +181,7 @@ |
176 | 182 | $this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' ); |
177 | 183 | $this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' ); |
178 | 184 | $this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(),'ll_from' ); |
| 185 | + $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(),'iwl_from' ); |
179 | 186 | $this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' ); |
180 | 187 | |
181 | 188 | # Update the cache of all the category pages and image description |
— | — | @@ -292,18 +299,6 @@ |
293 | 300 | } |
294 | 301 | |
295 | 302 | /** |
296 | | - * Make a WHERE clause from a 2-d NS/dbkey array |
297 | | - * |
298 | | - * @param array $arr 2-d array indexed by namespace and DB key |
299 | | - * @param string $prefix Field name prefix, without the underscore |
300 | | - */ |
301 | | - function makeWhereFrom2d( &$arr, $prefix ) { |
302 | | - $lb = new LinkBatch; |
303 | | - $lb->setArray( $arr ); |
304 | | - return $lb->constructSet( $prefix, $this->mDb ); |
305 | | - } |
306 | | - |
307 | | - /** |
308 | 303 | * Update a table by doing a delete query then an insert query |
309 | 304 | * @private |
310 | 305 | */ |
— | — | @@ -314,8 +309,13 @@ |
315 | 310 | $fromField = "{$prefix}_from"; |
316 | 311 | } |
317 | 312 | $where = array( $fromField => $this->mId ); |
318 | | - if ( $table == 'pagelinks' || $table == 'templatelinks' ) { |
319 | | - $clause = $this->makeWhereFrom2d( $deletions, $prefix ); |
| 313 | + if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) { |
| 314 | + if ( $table == 'iwlinks' ) { |
| 315 | + $baseKey = 'iwl_prefix'; |
| 316 | + } else { |
| 317 | + $baseKey = "{$prefix}_namespace"; |
| 318 | + } |
| 319 | + $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" ); |
320 | 320 | if ( $clause ) { |
321 | 321 | $where[] = $clause; |
322 | 322 | } else { |
— | — | @@ -476,7 +476,30 @@ |
477 | 477 | return $arr; |
478 | 478 | } |
479 | 479 | |
| 480 | + /** |
| 481 | + * Get an array of interwiki insertions for passing to the DB |
| 482 | + * Skips the titles specified by the 2-D array $existing |
| 483 | + * @private |
| 484 | + */ |
| 485 | + function getInterwikiInsertions( $existing = array() ) { |
| 486 | + $arr = array(); |
| 487 | + foreach( $this->mInterwikis as $prefix => $dbkeys ) { |
| 488 | + # array_diff_key() was introduced in PHP 5.1, there is a compatibility function |
| 489 | + # in GlobalFunctions.php |
| 490 | + $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys; |
| 491 | + foreach ( $diffs as $dbk => $id ) { |
| 492 | + $arr[] = array( |
| 493 | + 'iwl_from' => $this->mId, |
| 494 | + 'iwl_prefix' => $prefix, |
| 495 | + 'iwl_title' => $dbk |
| 496 | + ); |
| 497 | + } |
| 498 | + } |
| 499 | + return $arr; |
| 500 | + } |
480 | 501 | |
| 502 | + |
| 503 | + |
481 | 504 | /** |
482 | 505 | * Given an array of existing links, returns those links which are not in $this |
483 | 506 | * and thus should be deleted. |
— | — | @@ -556,6 +579,23 @@ |
557 | 580 | } |
558 | 581 | |
559 | 582 | /** |
| 583 | + * Given an array of existing interwiki links, returns those links which are not in $this |
| 584 | + * and thus should be deleted. |
| 585 | + * @private |
| 586 | + */ |
| 587 | + function getInterwikiDeletions( $existing ) { |
| 588 | + $del = array(); |
| 589 | + foreach ( $existing as $prefix => $dbkeys ) { |
| 590 | + if ( isset( $this->mInterwikis[$prefix] ) ) { |
| 591 | + $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] ); |
| 592 | + } else { |
| 593 | + $del[$prefix] = $existing[$prefix]; |
| 594 | + } |
| 595 | + } |
| 596 | + return $del; |
| 597 | + } |
| 598 | + |
| 599 | + /** |
560 | 600 | * Get an array of existing links, as a 2-D array |
561 | 601 | * @private |
562 | 602 | */ |
— | — | @@ -652,6 +692,24 @@ |
653 | 693 | } |
654 | 694 | |
655 | 695 | /** |
| 696 | + * Get an array of existing inline interwiki links, as a 2-D array |
| 697 | + * @return array (prefix => array(dbkey => 1)) |
| 698 | + */ |
| 699 | + protected function getExistingInterwikis() { |
| 700 | + $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ), |
| 701 | + array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions ); |
| 702 | + $arr = array(); |
| 703 | + while ( $row = $this->mDb->fetchObject( $res ) ) { |
| 704 | + if ( !isset( $arr[$row->iwl_prefix] ) ) { |
| 705 | + $arr[$row->iwl_prefix] = array(); |
| 706 | + } |
| 707 | + $arr[$row->iwl_prefix][$row->iwl_title] = 1; |
| 708 | + } |
| 709 | + $this->mDb->freeResult( $res ); |
| 710 | + return $arr; |
| 711 | + } |
| 712 | + |
| 713 | + /** |
656 | 714 | * Get an array of existing categories, with the name in the key and sort key in the value. |
657 | 715 | * @private |
658 | 716 | */ |