wikimedia/mediawiki-extensions-CirrusSearch

View on GitHub
includes/InterwikiSearcher.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace CirrusSearch;

use CirrusSearch\Fallbacks\FallbackRunner;
use CirrusSearch\Parser\BasicQueryClassifier;
use CirrusSearch\Parser\NamespacePrefixParser;
use CirrusSearch\Search\CrossProjectBlockScorerFactory;
use CirrusSearch\Search\FullTextResultsType;
use CirrusSearch\Search\MSearchRequests;
use CirrusSearch\Search\SearchContext;
use CirrusSearch\Search\SearchQuery;
use CirrusSearch\Search\SearchQueryBuilder;
use CirrusSearch\Search\TitleHelper;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
use MediaWiki\User\User;
use Wikimedia\Stats\StatsFactory;

/**
 * Performs searches using Elasticsearch -- on interwikis!
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */
class InterwikiSearcher extends Searcher {

    /**
     * @param Connection $connection
     * @param SearchConfig $config
     * @param int[]|null $namespaces Namespace numbers to search, or null for all of them
     * @param User|null $user
     * @param CirrusDebugOptions|null $debugOptions
     * @param NamespacePrefixParser|null $namespacePrefixParser
     * @param InterwikiResolver|null $interwikiResolver
     * @param TitleHelper|null $titleHelper
     * @param CirrusSearchHookRunner|null $cirrusSearchHookRunner
     */
    public function __construct(
        Connection $connection,
        SearchConfig $config,
        array $namespaces = null,
        User $user = null,
        CirrusDebugOptions $debugOptions = null,
        NamespacePrefixParser $namespacePrefixParser = null,
        InterwikiResolver $interwikiResolver = null,
        TitleHelper $titleHelper = null,
        CirrusSearchHookRunner $cirrusSearchHookRunner = null
    ) {
        $maxResults = $config->get( 'CirrusSearchNumCrossProjectSearchResults' );
        parent::__construct( $connection, 0, $maxResults, $config, $namespaces, $user, false,
            $debugOptions, $namespacePrefixParser, $interwikiResolver, $titleHelper, $cirrusSearchHookRunner );
    }

    /**
     * Fetch search results, from caches, if there's any
     * @param SearchQuery $query original search query
     * @return Status
     */
    public function getInterwikiResults( SearchQuery $query ): Status {
        $sources = MediaWikiServices::getInstance()
            ->getService( InterwikiResolver::SERVICE )
            ->getSisterProjectConfigs();
        if ( !$sources ) {
            // Nothing to search for
            return Status::newGood( [] );
        }

        $iwQueries = [];
        foreach ( $sources as $interwiki => $config ) {
            $iwQueries[$interwiki] = SearchQueryBuilder::forCrossProjectSearch( $config, $query )
                ->build();
        }

        $blockScorer = CrossProjectBlockScorerFactory::load( $this->config );
        $msearches = new MSearchRequests();
        foreach ( $iwQueries as $interwiki => $iwQuery ) {
            $context = SearchContext::fromSearchQuery( $iwQuery,
                FallbackRunner::create( $iwQuery, $this->interwikiResolver ), $this->cirrusSearchHookRunner );
            $this->searchContext = $context;
            $this->setResultsType( new FullTextResultsType(
                $this->searchContext->getFetchPhaseBuilder(),
                $query->getParsedQuery()->isQueryOfClass( BasicQueryClassifier::COMPLEX_QUERY ),
                $this->titleHelper, [], $this->searchContext->getConfig()->getElement( 'CirrusSearchDeduplicateInMemory' ) === true ) );
            $this->config = $context->getConfig();
            $this->limit = $iwQuery->getLimit();
            $this->offset = $iwQuery->getOffset();
            $this->buildFullTextSearch( $query->getParsedQuery()->getQueryWithoutNsHeader() );
            $this->indexBaseName = $context->getConfig()->get( 'CirrusSearchIndexBaseName' );
            $search = $this->buildSearch();
            if ( $this->searchContext->areResultsPossible() ) {
                $msearches->addRequest( $interwiki, $search );
            }
        }

        $searchDescription = "{$this->searchContext->getSearchType()} search for '{$this->searchContext->getOriginalSearchTerm()}'";
        if ( $this->searchContext->getDebugOptions()->isCirrusDumpQuery() ) {
            return $msearches->dumpQuery( $searchDescription );
        }
        $mresponses = $this->searchMulti( $msearches );
        if ( $mresponses->hasFailure() ) {
            return $mresponses->getFailure();
        }

        if ( $this->searchContext->getDebugOptions()->isReturnRaw() ) {
            return $mresponses->dumpResults( $searchDescription );
        }

        return $mresponses->transformAndGetMulti( $this->searchContext->getResultsType(), array_keys( $iwQueries ),
            static function ( array $v ) use ( $blockScorer ) {
                return $blockScorer->reorder( $v );
            } );
    }

    protected function recordQueryCacheMetrics( StatsFactory $requestStats, string $cacheStatus, ?string $type = null ): void {
        parent::recordQueryCacheMetrics( $requestStats, $cacheStatus, "interwiki" );
    }
}