Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
InterwikiSearcher
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 3
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getInterwikiResults
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
72
 recordQueryCacheMetrics
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace CirrusSearch;
4
5use CirrusSearch\Fallbacks\FallbackRunner;
6use CirrusSearch\Parser\BasicQueryClassifier;
7use CirrusSearch\Parser\NamespacePrefixParser;
8use CirrusSearch\Search\CrossProjectBlockScorerFactory;
9use CirrusSearch\Search\FullTextResultsType;
10use CirrusSearch\Search\MSearchRequests;
11use CirrusSearch\Search\SearchContext;
12use CirrusSearch\Search\SearchQuery;
13use CirrusSearch\Search\SearchQueryBuilder;
14use CirrusSearch\Search\TitleHelper;
15use MediaWiki\MediaWikiServices;
16use MediaWiki\Status\Status;
17use MediaWiki\User\User;
18use Wikimedia\Stats\StatsFactory;
19
20/**
21 * Performs searches using Elasticsearch -- on interwikis!
22 *
23 * @license GPL-2.0-or-later
24 */
25class InterwikiSearcher extends Searcher {
26
27    /**
28     * @param Connection $connection
29     * @param SearchConfig $config
30     * @param int[]|null $namespaces Namespace numbers to search, or null for all of them
31     * @param User|null $user
32     * @param CirrusDebugOptions|null $debugOptions
33     * @param NamespacePrefixParser|null $namespacePrefixParser
34     * @param InterwikiResolver|null $interwikiResolver
35     * @param TitleHelper|null $titleHelper
36     * @param CirrusSearchHookRunner|null $cirrusSearchHookRunner
37     */
38    public function __construct(
39        Connection $connection,
40        SearchConfig $config,
41        ?array $namespaces = null,
42        ?User $user = null,
43        ?CirrusDebugOptions $debugOptions = null,
44        ?NamespacePrefixParser $namespacePrefixParser = null,
45        ?InterwikiResolver $interwikiResolver = null,
46        ?TitleHelper $titleHelper = null,
47        ?CirrusSearchHookRunner $cirrusSearchHookRunner = null
48    ) {
49        $maxResults = $config->get( 'CirrusSearchNumCrossProjectSearchResults' );
50        parent::__construct( $connection, 0, $maxResults, $config, $namespaces, $user, false,
51            $debugOptions, $namespacePrefixParser, $interwikiResolver, $titleHelper, $cirrusSearchHookRunner );
52    }
53
54    /**
55     * Fetch search results, from caches, if there's any
56     * @param SearchQuery $query original search query
57     * @return Status
58     */
59    public function getInterwikiResults( SearchQuery $query ): Status {
60        $sources = MediaWikiServices::getInstance()
61            ->getService( InterwikiResolver::SERVICE )
62            ->getSisterProjectConfigs();
63        if ( !$sources ) {
64            // Nothing to search for
65            return Status::newGood( [] );
66        }
67
68        $iwQueries = [];
69        foreach ( $sources as $interwiki => $config ) {
70            $iwQueries[$interwiki] = SearchQueryBuilder::forCrossProjectSearch( $config, $query )
71                ->build();
72        }
73
74        $blockScorer = CrossProjectBlockScorerFactory::load( $this->config );
75        $msearches = new MSearchRequests();
76        foreach ( $iwQueries as $interwiki => $iwQuery ) {
77            $context = SearchContext::fromSearchQuery( $iwQuery,
78                FallbackRunner::create( $iwQuery, $this->interwikiResolver ), $this->cirrusSearchHookRunner );
79            $this->searchContext = $context;
80            $this->setResultsType( new FullTextResultsType(
81                $this->searchContext->getFetchPhaseBuilder(),
82                $query->getParsedQuery()->isQueryOfClass( BasicQueryClassifier::COMPLEX_QUERY ),
83                $this->titleHelper, [], $this->searchContext->getConfig()->getElement( 'CirrusSearchDeduplicateInMemory' ) === true ) );
84            $this->config = $context->getConfig();
85            $this->limit = $iwQuery->getLimit();
86            $this->offset = $iwQuery->getOffset();
87            $this->buildFullTextSearch( $query->getParsedQuery()->getQueryWithoutNsHeader() );
88            $this->indexBaseName = $context->getConfig()->get( 'CirrusSearchIndexBaseName' );
89            $search = $this->buildSearch();
90            if ( $this->searchContext->areResultsPossible() ) {
91                $msearches->addRequest( $interwiki, $search );
92            }
93        }
94
95        $searchDescription = "{$this->searchContext->getSearchType()} search for '{$this->searchContext->getOriginalSearchTerm()}'";
96        if ( $this->searchContext->getDebugOptions()->isCirrusDumpQuery() ) {
97            return $msearches->dumpQuery( $searchDescription );
98        }
99        $mresponses = $this->searchMulti( $msearches );
100        if ( $mresponses->hasFailure() ) {
101            return $mresponses->getFailure();
102        }
103
104        if ( $this->searchContext->getDebugOptions()->isReturnRaw() ) {
105            return $mresponses->dumpResults( $searchDescription );
106        }
107
108        return $mresponses->transformAndGetMulti( $this->searchContext->getResultsType(), array_keys( $iwQueries ),
109            static function ( array $v ) use ( $blockScorer ) {
110                return $blockScorer->reorder( $v );
111            } );
112    }
113
114    protected function recordQueryCacheMetrics( StatsFactory $requestStats, string $cacheStatus, ?string $type = null ): void {
115        parent::recordQueryCacheMetrics( $requestStats, $cacheStatus, "interwiki" );
116    }
117}