Like langlinks, this stores the interwiki prefix (as iwl_prefix) and full page title (as iwl_title), attached to the page doing the liking (as iwl_from -> page_id).
Unlike langlinks, there can be multiple entries stored per interwiki prefix.
Updater to add the table confirmed on MySQL, untested on SQLite but should work.
Someone may still need to add and test a PostgreSQL updater.
Refactored makeWhereFrom2d() out of LinkBatch to Database so it could be re-used for the similar mapping for the interwiki links, which need a string prefix rather than an int namespace key.
Also cleaned it up internally to reuse existing code for building where clauses from arrays. (Tim & Domas -- if the previous more verbose code was there to reduce function call and array processing overhead on very large link lists, feel free to unroll it again if the difference is measurable. Just swap the var names around from the old LinkBatch code and escape the base key value if it's not an integer, it'll be functionally equivalent.)
*
* @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc)
* @param $db DatabaseBase object to use
- * @return String
+ * @return mixed string with SQL where clause fragment, or false if no items.
*/
- public function constructSet( $prefix, &$db ) {
- $first = true;
- $firstTitle = true;
- $sql = '';
- foreach ( $this->data as $ns => $dbkeys ) {
- if ( !count( $dbkeys ) ) {
- continue;
- }
-
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ' OR ';
- }
-
- if (count($dbkeys)==1) { // avoid multiple-reference syntax if simple equality can be used
- $singleKey = array_keys($dbkeys);
- $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title=".
- $db->addQuotes($singleKey[0]).
- ")";
- } else {
- $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN (";
-
- $firstTitle = true;
- foreach( $dbkeys as $dbkey => $unused ) {
- if ( $firstTitle ) {
- $firstTitle = false;
- } else {
- $sql .= ',';
- }
- $sql .= $db->addQuotes( $dbkey );
- }
- $sql .= '))';
- }
- }
- if ( $first && $firstTitle ) {
- # No titles added
- return false;
- } else {
- return $sql;
- }
+ public function constructSet( $prefix, $db ) {
+ return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" );
}
}
$this->mExternals = $parserOutput->getExternalLinks();
$this->mCategories = $parserOutput->getCategories();
$this->mProperties = $parserOutput->getProperties();
+ $this->mInterwikis = $parserOutput->getInterwikiLinks();
# Convert the format of the interlanguage links
# I didn't want to change it in the ParserOutput, because that array is passed all
$this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
$this->getInterlangInsertions( $existing ) );
+ # Inline interwiki links
+ $existing = $this->getExistingInterwikis();
+ $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ),
+ $this->getInterwikiInsertions( $existing ) );
+
# Template links
$existing = $this->getExistingTemplates();
$this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
$this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' );
$this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' );
$this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(),'ll_from' );
+ $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(),'iwl_from' );
$this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' );
# Update the cache of all the category pages and image description
}
}
- /**
- * Make a WHERE clause from a 2-d NS/dbkey array
- *
- * @param array $arr 2-d array indexed by namespace and DB key
- * @param string $prefix Field name prefix, without the underscore
- */
- function makeWhereFrom2d( &$arr, $prefix ) {
- $lb = new LinkBatch;
- $lb->setArray( $arr );
- return $lb->constructSet( $prefix, $this->mDb );
- }
-
/**
* Update a table by doing a delete query then an insert query
* @private
$fromField = "{$prefix}_from";
}
$where = array( $fromField => $this->mId );
- if ( $table == 'pagelinks' || $table == 'templatelinks' ) {
- $clause = $this->makeWhereFrom2d( $deletions, $prefix );
+ if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
+ if ( $table == 'iwlinks' ) {
+ $baseKey = 'iwl_prefix';
+ } else {
+ $baseKey = "{$prefix}_namespace";
+ }
+ $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
if ( $clause ) {
$where[] = $clause;
} else {
return $arr;
}
+ /**
+ * Get an array of interwiki insertions for passing to the DB
+ * Skips the titles specified by the 2-D array $existing
+ * @private
+ */
+ function getInterwikiInsertions( $existing = array() ) {
+ $arr = array();
+ foreach( $this->mInterwikis as $prefix => $dbkeys ) {
+ # array_diff_key() was introduced in PHP 5.1, there is a compatibility function
+ # in GlobalFunctions.php
+ $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
+ foreach ( $diffs as $dbk => $id ) {
+ $arr[] = array(
+ 'iwl_from' => $this->mId,
+ 'iwl_prefix' => $prefix,
+ 'iwl_title' => $dbk
+ );
+ }
+ }
+ return $arr;
+ }
+
+
/**
* Given an array of existing links, returns those links which are not in $this
return array_diff_assoc( $existing, $this->mProperties );
}
+ /**
+ * Given an array of existing interwiki links, returns those links which are not in $this
+ * and thus should be deleted.
+ * @private
+ */
+ function getInterwikiDeletions( $existing ) {
+ $del = array();
+ foreach ( $existing as $prefix => $dbkeys ) {
+ if ( isset( $this->mInterwikis[$prefix] ) ) {
+ $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
+ } else {
+ $del[$prefix] = $existing[$prefix];
+ }
+ }
+ return $del;
+ }
+
/**
* Get an array of existing links, as a 2-D array
* @private
return $arr;
}
+ /**
+ * Get an array of existing inline interwiki links, as a 2-D array
+ * @return array (prefix => array(dbkey => 1))
+ */
+ protected function getExistingInterwikis() {
+ $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ),
+ array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions );
+ $arr = array();
+ while ( $row = $this->mDb->fetchObject( $res ) ) {
+ if ( !isset( $arr[$row->iwl_prefix] ) ) {
+ $arr[$row->iwl_prefix] = array();
+ }
+ $arr[$row->iwl_prefix][$row->iwl_title] = 1;
+ }
+ $this->mDb->freeResult( $res );
+ return $arr;
+ }
+
/**
* Get an array of existing categories, with the name in the key and sort key in the value.
* @private
return $list;
}
+ /**
+ * Build a partial where clause from a 2-d array such as used for LinkBatch.
+ * The keys on each level may be either integers or strings.
+ *
+ * @param array $data organized as 2-d array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...)
+ * @param string $baseKey field name to match the base-level keys to (eg 'pl_namespace')
+ * @param string $subKey field name to match the sub-level keys to (eg 'pl_title')
+ * @return mixed string SQL fragment, or false if no items in array.
+ */
+ function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ $conds = array();
+ foreach ( $data as $base => $sub ) {
+ if ( count( $sub ) ) {
+ $conds[] = $this->makeList(
+ array( $baseKey => $base, $subKey => array_keys( $sub ) ),
+ LIST_AND);
+ }
+ }
+
+ if ( $conds ) {
+ return $this->makeList( $conds, LIST_OR );
+ } else {
+ // Nothing to search for...
+ return false;
+ }
+ }
+
/**
* Bitwise operations
*/
# Check if it's a static known link, e.g. interwiki
if ( $title->isAlwaysKnown() ) {
$colours[$pdbk] = '';
+ if( $title->getInterwiki() != '' ) {
+ $output->addInterwikiLink( $title );
+ }
} elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
$colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
$output->addLink( $title, $id );
#
# FIXME: isAlwaysKnown() can be expensive for file links; we should really do
# batch file existence checks for NS_FILE and NS_MEDIA
- if ( $iw == '' && $nt->isAlwaysKnown() ) {
+ if ( $iw = '' && $nt->isAlwaysKnown() ) {
$this->mOutput->addLink( $nt );
$s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
} else {
$mTemplateIds = array(), # 2-D map of NS/DBK to rev ID for the template references. ID=zero for broken.
$mImages = array(), # DB keys of the images used, in the array key only
$mExternalLinks = array(), # External link URLs, in the key only
+ $mInterwikiLinks = array(), # 2-D map of prefix/DBK (in keys only) for the inline interwiki links in the document.
$mNewSection = false, # Show a new section link?
$mHideNewSection = false, # Hide the new section link?
$mNoGallery = false, # No gallery on category page? (__NOGALLERY__)
function getText() { return $this->mText; }
function &getLanguageLinks() { return $this->mLanguageLinks; }
+ function getInterwikiLinks() { return $this->mInterwikiLinks; }
function getCategoryLinks() { return array_keys( $this->mCategories ); }
function &getCategories() { return $this->mCategories; }
function getCacheTime() { return $this->mCacheTime; }
$this->mExternalLinks[$url] = 1;
}
+ /**
+ * Record a local or interwiki inline link for saving in future link tables.
+ *
+ * @param Title $title
+ * @param mixed $id optional known page_id so we can skip the lookup
+ */
function addLink( $title, $id = null ) {
+ wfDebug(__METHOD__ . " got: " . $title->getPrefixedText() . "\n");
if ( $title->isExternal() ) {
// Don't record interwikis in pagelinks
+ $this->addInterwikiLink( $title );
return;
}
$ns = $title->getNamespace();
}
$this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
}
+
+ /**
+ * @param Title $title object, must be an interwiki link
+ * @throws MWException if given invalid input
+ */
+ function addInterwikiLink( $title ) {
+ $prefix = $title->getInterwiki();
+ if( $prefix == '' ) {
+ throw new MWException( 'Non-interwiki link passed, internal parser error.' );
+ }
+ if (!isset($this->mInterwikiLinks[$prefix])) {
+ $this->mInterwikiLinks[$prefix] = array();
+ }
+ $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
+ }
/**
* Return true if this cached output object predates the global or
--- /dev/null
+--
+-- Track inline interwiki links
+--
+CREATE TABLE /*_*/iwlinks (
+ -- page_id of the referring page
+ iwl_from int unsigned NOT NULL default 0,
+
+ -- Interwiki prefix code of the target
+ iwl_prefix varbinary(20) NOT NULL default '',
+
+ -- Title of the target, including namespace
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE INDEX /*i*/iwl_prefix ON /*_*/iwlinks (iwl_prefix, iwl_title);
global $wgDBtype;
$tables = array('user', 'page', 'page_restrictions',
'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
- 'categorylinks', 'templatelinks', 'externallinks', 'langlinks',
+ 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage',
'recentchanges', 'watchlist', 'math', 'interwiki',
'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+--
+-- Track inline interwiki links
+--
+CREATE TABLE /*_*/iwlinks (
+ -- page_id of the referring page
+ iwl_from int unsigned NOT NULL default 0,
+
+ -- Interwiki prefix code of the target
+ iwl_prefix varbinary(20) NOT NULL default '',
+
+ -- Title of the target, including namespace
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE INDEX /*i*/iwl_prefix ON /*_*/iwlinks (iwl_prefix, iwl_title);
+
+
--
-- Contains a single row with some aggregate info
-- on the state of the site.
array( 'do_update_mime_minor_field' ),
// Should've done this back in 1.10, but better late than never:
array( 'do_populate_rev_len' ),
+
+ // 1.17
+ array( 'add_table', 'iwlinks', 'patch-iwlinks.sql' ),
),
'sqlite' => array(
array( 'do_update_transcache_field' ),
// version-independent searchindex setup, added in 1.16
array( 'sqlite_setup_searchindex' ),
+
+ // 1.17
+ array( 'add_table', 'iwlinks', 'patch-iwlinks.sql' ), // @fixme so far untested on sqlite 2010-04-16
),
);
array('user_properties', 'patch-user_properties.sql'),
array('log_search', 'patch-log_search.sql'),
array('l10n_cache', 'patch-l10n_cache.sql'),
+ // @fixme add iwlinks table
);
$newcols = array(