Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.55% covered (warning)
50.55%
228 / 451
36.36% covered (danger)
36.36%
12 / 33
CRAP
0.00% covered (danger)
0.00%
0 / 1
Searcher
50.55% covered (warning)
50.55%
228 / 451
36.36% covered (danger)
36.36%
12 / 33
1713.75
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 search
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
3.01
 setResultsType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isReturnRaw
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 limitSearchToLocalWiki
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 nearMatchTitleSearch
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 countContentWords
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 prefixSearch
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 buildFullTextSearch
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
5.10
 searchTextInternal
59.18% covered (warning)
59.18%
29 / 49
0.00% covered (danger)
0.00%
0 / 1
27.33
 get
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
72
 findNamespace
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 buildSearch
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 searchOne
38.46% covered (danger)
38.46%
5 / 13
0.00% covered (danger)
0.00%
0 / 1
10.83
 searchMulti
38.89% covered (danger)
38.89%
42 / 108
0.00% covered (danger)
0.00%
0 / 1
132.46
 updateNamespacesFromQuery
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getSearchContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPoolCounterType
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
5.25
 isAutomatedRequest
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
4.94
 getOverriddenConnection
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 recordQueryCacheMetrics
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 newLog
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 processRawReturn
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 searchArchive
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
1
 areSearchesTheSame
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 buildInterleaveSearcher
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
6.00
 emptyResultSet
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 applyDebugOptionsToQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makeSearcher
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setOffsetLimit
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getOffsetLimit
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 buildFullTextBuilder
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
4.01
1<?php
2
3namespace CirrusSearch;
4
5use CirrusSearch\Fallbacks\FallbackRunner;
6use CirrusSearch\Fallbacks\SearcherFactory;
7use CirrusSearch\Maintenance\NullPrinter;
8use CirrusSearch\MetaStore\MetaStoreIndex;
9use CirrusSearch\Parser\BasicQueryClassifier;
10use CirrusSearch\Parser\FullTextKeywordRegistry;
11use CirrusSearch\Parser\NamespacePrefixParser;
12use CirrusSearch\Profile\SearchProfileService;
13use CirrusSearch\Query\CountContentWordsBuilder;
14use CirrusSearch\Query\FullTextQueryBuilder;
15use CirrusSearch\Query\KeywordFeature;
16use CirrusSearch\Query\NearMatchQueryBuilder;
17use CirrusSearch\Query\PrefixSearchQueryBuilder;
18use CirrusSearch\Search\BaseCirrusSearchResultSet;
19use CirrusSearch\Search\FullTextResultsType;
20use CirrusSearch\Search\MSearchRequests;
21use CirrusSearch\Search\MSearchResponses;
22use CirrusSearch\Search\ResultsType;
23use CirrusSearch\Search\SearchContext;
24use CirrusSearch\Search\SearchQuery;
25use CirrusSearch\Search\SearchRequestBuilder;
26use CirrusSearch\Search\TeamDraftInterleaver;
27use CirrusSearch\Search\TitleHelper;
28use CirrusSearch\Search\TitleResultsType;
29use Elastica\Exception\ResponseException;
30use Elastica\Exception\RuntimeException;
31use Elastica\Multi\Search as MultiSearch;
32use Elastica\Query;
33use Elastica\Query\BoolQuery;
34use Elastica\Query\MultiMatch;
35use Elastica\Search;
36use MediaWiki\Context\RequestContext;
37use MediaWiki\Logger\LoggerFactory;
38use MediaWiki\MediaWikiServices;
39use MediaWiki\Request\WebRequest;
40use MediaWiki\Status\Status;
41use MediaWiki\Title\Title;
42use MediaWiki\User\User;
43use MediaWiki\WikiMap\WikiMap;
44use Wikimedia\Assert\Assert;
45use Wikimedia\ObjectFactory\ObjectFactory;
46use Wikimedia\Stats\StatsFactory;
47
48/**
49 * Performs searches using Elasticsearch.  Note that each instance of this class
50 * is single use only.
51 *
52 * This program is free software; you can redistribute it and/or modify
53 * it under the terms of the GNU General Public License as published by
54 * the Free Software Foundation; either version 2 of the License, or
55 * (at your option) any later version.
56 *
57 * This program is distributed in the hope that it will be useful,
58 * but WITHOUT ANY WARRANTY; without even the implied warranty of
59 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60 * GNU General Public License for more details.
61 *
62 * You should have received a copy of the GNU General Public License along
63 * with this program; if not, write to the Free Software Foundation, Inc.,
64 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
65 * http://www.gnu.org/copyleft/gpl.html
66 */
67class Searcher extends ElasticsearchIntermediary implements SearcherFactory {
68    public const SUGGESTION_HIGHLIGHT_PRE = '<em>';
69    public const SUGGESTION_HIGHLIGHT_POST = '</em>';
70    public const HIGHLIGHT_PRE_MARKER = ''; // \uE000. Can't be a unicode literal until php7
71    public const HIGHLIGHT_PRE = '<span class="searchmatch">';
72    public const HIGHLIGHT_POST_MARKER = ''; // \uE001
73    public const HIGHLIGHT_POST = '</span>';
74
75    /**
76     * Maximum offset + limit depth allowed. As in the deepest possible result
77     * to return. Too deep will cause very slow queries. 10,000 feels plenty
78     * deep. This should be <= index.max_result_window in elasticsearch.
79     */
80    private const MAX_OFFSET_LIMIT = 10000;
81
82    /**
83     * Identifies the main search in MSearchRequests/MSearchResponses
84     */
85    public const MAINSEARCH_MSEARCH_KEY = '__main__';
86
87    /**
88     * Identifies the "tested" search request in MSearchRequests/MSearchResponses
89     */
90    private const INTERLEAVED_MSEARCH_KEY = '__interleaved__';
91
92    /**
93     * @var int search offset
94     */
95    protected $offset;
96
97    /**
98     * @var int maximum number of result
99     */
100    protected $limit;
101
102    /**
103     * @var string sort type
104     */
105    private $sort = 'relevance';
106
107    /**
108     * @var string index base name to use
109     */
110    protected $indexBaseName;
111
112    /**
113     * Search environment configuration
114     * @var SearchConfig
115     */
116    protected $config;
117
118    /**
119     * @var SearchContext
120     */
121    protected $searchContext;
122
123    /**
124     * Indexing type we'll be using.
125     * @var string|\Elastica\Index
126     */
127    private $index;
128
129    /**
130     * @var NamespacePrefixParser|null
131     */
132    private $namespacePrefixParser;
133    /**
134     * @var InterwikiResolver
135     */
136    protected $interwikiResolver;
137
138    /** @var TitleHelper */
139    protected $titleHelper;
140    /**
141     * @var CirrusSearchHookRunner
142     */
143    protected $cirrusSearchHookRunner;
144
145    /**
146     * @param Connection $conn
147     * @param int $offset Offset the results by this much
148     * @param int $limit Limit the results to this many
149     * @param SearchConfig $config Configuration settings
150     * @param int[]|null $namespaces Array of namespace numbers to search or null to search all namespaces.
151     * @param User|null $user user for which this search is being performed.  Attached to slow request logs.
152     * @param string|bool $index Base name for index to search from, defaults to $wgCirrusSearchIndexBaseName
153     * @param CirrusDebugOptions|null $options the debugging options to use or null to use defaults
154     * @param NamespacePrefixParser|null $namespacePrefixParser
155     * @param InterwikiResolver|null $interwikiResolver
156     * @param TitleHelper|null $titleHelper
157     * @param CirrusSearchHookRunner|null $cirrusSearchHookRunner
158     * @see CirrusDebugOptions::defaultOptions()
159     */
160    public function __construct(
161        Connection $conn, $offset,
162        $limit,
163        SearchConfig $config,
164        ?array $namespaces = null,
165        ?User $user = null,
166        $index = false,
167        ?CirrusDebugOptions $options = null,
168        ?NamespacePrefixParser $namespacePrefixParser = null,
169        ?InterwikiResolver $interwikiResolver = null,
170        ?TitleHelper $titleHelper = null,
171        ?CirrusSearchHookRunner $cirrusSearchHookRunner = null
172    ) {
173        parent::__construct(
174            $conn,
175            $user,
176            $config->get( 'CirrusSearchSlowSearch' ),
177            $config->get( 'CirrusSearchExtraBackendLatency' )
178        );
179        $this->config = $config;
180        $this->setOffsetLimit( $offset, $limit );
181        $this->indexBaseName = $index ?: $config->get( SearchConfig::INDEX_BASE_NAME );
182        // TODO: Make these params mandatory once WBCS stops extending this class
183        $this->namespacePrefixParser = $namespacePrefixParser;
184        $this->interwikiResolver = $interwikiResolver ?: MediaWikiServices::getInstance()->getService( InterwikiResolver::SERVICE );
185        $this->titleHelper = $titleHelper ?: new TitleHelper( WikiMap::getCurrentWikiId(), $this->interwikiResolver );
186        $this->cirrusSearchHookRunner = $cirrusSearchHookRunner ?: new CirrusSearchHookRunner(
187            MediaWikiServices::getInstance()->getHookContainer() );
188        $this->searchContext = new SearchContext( $this->config, $namespaces, $options, null, null, $this->cirrusSearchHookRunner );
189    }
190
191    /**
192     * Unified search public entry-point.
193     *
194     * NOTE: only fulltext search supported for now.
195     * @param SearchQuery $query
196     * @return Status
197     */
198    public function search( SearchQuery $query ) {
199        if ( $query->getDebugOptions()->isCirrusDumpQueryAST() ) {
200            return Status::newGood( [ 'ast' => $query->getParsedQuery()->toArray() ] );
201        }
202        // TODO: properly pass the profile context name and its params once we have a dispatch service.
203        $this->searchContext = SearchContext::fromSearchQuery( $query, FallbackRunner::create( $query, $this->interwikiResolver ),
204            $this->cirrusSearchHookRunner );
205        $this->setOffsetLimit( $query->getOffset(), $query->getLimit() );
206        $this->config = $query->getSearchConfig();
207        $this->sort = $query->getSort();
208
209        if ( $query->getSearchEngineEntryPoint() === SearchQuery::SEARCH_TEXT ) {
210            $this->searchContext->setResultsType(
211                new FullTextResultsType(
212                    $this->searchContext->getFetchPhaseBuilder(),