Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.44% |
51 / 54 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
LangDetectFallbackMethod | |
94.44% |
51 / 54 |
|
80.00% |
4 / 5 |
18.06 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
build | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
successApproximation | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
8 | |||
rewrite | |
85.00% |
17 / 20 |
|
0.00% |
0 / 1 |
6.12 | |||
getMetrics | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Fallbacks; |
4 | |
5 | use CirrusSearch\InterwikiResolver; |
6 | use CirrusSearch\LanguageDetector\Detector; |
7 | use CirrusSearch\LanguageDetector\LanguageDetectorFactory; |
8 | use CirrusSearch\Parser\BasicQueryClassifier; |
9 | use CirrusSearch\Search\CirrusSearchResultSet; |
10 | use CirrusSearch\Search\SearchMetricsProvider; |
11 | use CirrusSearch\Search\SearchQuery; |
12 | use CirrusSearch\Search\SearchQueryBuilder; |
13 | use CirrusSearch\SearchConfig; |
14 | use CirrusSearch\Searcher; |
15 | use Wikimedia\Assert\Assert; |
16 | |
17 | class LangDetectFallbackMethod implements FallbackMethod, SearchMetricsProvider { |
18 | use FallbackMethodTrait; |
19 | |
20 | /** |
21 | * @var SearchQuery |
22 | */ |
23 | private $query; |
24 | |
25 | /** |
26 | * @var SearcherFactory |
27 | */ |
28 | private $searcherFactory; |
29 | |
30 | /** |
31 | * @var array|null |
32 | */ |
33 | private $searchMetrics = []; |
34 | |
35 | /** |
36 | * @var Detector[] |
37 | */ |
38 | private $detectors; |
39 | |
40 | /** |
41 | * @var InterwikiResolver |
42 | */ |
43 | private $interwikiResolver; |
44 | |
45 | /** |
46 | * @var SearchConfig|null |
47 | */ |
48 | private $detectedLangWikiConfig; |
49 | |
50 | /** |
51 | * @var int |
52 | */ |
53 | private $threshold; |
54 | |
55 | /** |
56 | * Do not use this constructor outside of tests! |
57 | * @param SearchQuery $query |
58 | * @param Detector[] $detectors |
59 | * @param InterwikiResolver $interwikiResolver |
60 | */ |
61 | public function __construct( |
62 | SearchQuery $query, |
63 | array $detectors, |
64 | InterwikiResolver $interwikiResolver |
65 | ) { |
66 | Assert::precondition( $query->getCrossSearchStrategy()->isCrossLanguageSearchSupported(), |
67 | "Cross language search must be supported for this query" ); |
68 | $this->query = $query; |
69 | $this->detectors = $detectors; |
70 | $this->interwikiResolver = $interwikiResolver; |
71 | $this->threshold = $query->getSearchConfig()->get( 'CirrusSearchInterwikiThreshold' ); |
72 | } |
73 | |
74 | /** |
75 | * @param SearchQuery $query |
76 | * @param array $params |
77 | * @param InterwikiResolver $interwikiResolver |
78 | * @return FallbackMethod|null |
79 | */ |
80 | public static function build( SearchQuery $query, array $params, InterwikiResolver $interwikiResolver ) { |
81 | if ( !$query->getCrossSearchStrategy()->isCrossLanguageSearchSupported() ) { |
82 | return null; |
83 | } |
84 | $langDetectFactory = new LanguageDetectorFactory( $query->getSearchConfig() ); |
85 | return new self( $query, $langDetectFactory->getDetectors(), $interwikiResolver ); |
86 | } |
87 | |
88 | /** |
89 | * @param FallbackRunnerContext $context |
90 | * @return float |
91 | */ |
92 | public function successApproximation( FallbackRunnerContext $context ) { |
93 | $firstPassResults = $context->getInitialResultSet(); |
94 | if ( !$this->query->isAllowRewrite() ) { |
95 | return 0.0; |
96 | } |
97 | if ( $this->resultsThreshold( $firstPassResults, $this->threshold ) ) { |
98 | return 0.0; |
99 | } |
100 | if ( !$this->query->getParsedQuery()->isQueryOfClass( BasicQueryClassifier::SIMPLE_BAG_OF_WORDS ) ) { |
101 | return 0.0; |
102 | } |
103 | foreach ( $this->detectors as $name => $detector ) { |
104 | $lang = $detector->detect( $this->query->getParsedQuery()->getRawQuery() ); |
105 | if ( $lang === null ) { |
106 | continue; |
107 | } |
108 | if ( $lang === $this->query->getSearchConfig()->get( 'LanguageCode' ) ) { |
109 | // The query is in the wiki language so we |
110 | // don't need to actually try another wiki. |
111 | // Note that this may not be very accurate for |
112 | // wikis that use deprecated language codes |
113 | // but the interwiki resolver should not return |
114 | // ourselves. |
115 | continue; |
116 | } |
117 | $iwPrefixAndConfig = $this->interwikiResolver->getSameProjectConfigByLang( $lang ); |
118 | if ( $iwPrefixAndConfig ) { |
119 | // it might be more accurate to attach these to the 'next' |
120 | // log context? It would be inconsistent with the |
121 | // langdetect => false condition which does not have a next |
122 | // request though. |
123 | Searcher::appendLastLogPayload( 'langdetect', $name ); |
124 | $prefix = key( $iwPrefixAndConfig ); |
125 | $config = $iwPrefixAndConfig[$prefix]; |
126 | $metric = [ $config->getWikiId(), $prefix ]; |
127 | $this->detectedLangWikiConfig = $config; |
128 | return 0.5; |
129 | } |
130 | } |
131 | Searcher::appendLastLogPayload( 'langdetect', 'failed' ); |
132 | return 0.0; |
133 | } |
134 | |
135 | /** |
136 | * @param FallbackRunnerContext $context |
137 | * @return FallbackStatus |
138 | */ |
139 | public function rewrite( FallbackRunnerContext $context ): FallbackStatus { |
140 | $previousSet = $context->getPreviousResultSet(); |
141 | Assert::precondition( $this->detectedLangWikiConfig !== null, |
142 | 'nothing has been detected, this should not even be tried.' ); |
143 | |
144 | if ( $this->resultsThreshold( $previousSet, $this->threshold ) ) { |
145 | return FallbackStatus::noSuggestion(); |
146 | } |
147 | |
148 | if ( !$context->costlyCallAllowed() ) { |
149 | return FallbackStatus::noSuggestion(); |
150 | } |
151 | |
152 | $crossLangQuery = SearchQueryBuilder::forCrossLanguageSearch( $this->detectedLangWikiConfig, |
153 | $this->query )->build(); |
154 | $searcher = $context->makeSearcher( $crossLangQuery ); |
155 | $status = $searcher->search( $crossLangQuery ); |
156 | if ( !$status->isOK() ) { |
157 | return FallbackStatus::noSuggestion(); |
158 | } |
159 | $crossLangResults = $status->getValue(); |
160 | if ( !$crossLangResults instanceof CirrusSearchResultSet ) { |
161 | // NOTE: Can/should this happen? |
162 | return FallbackStatus::noSuggestion(); |
163 | } |
164 | if ( $crossLangResults->numRows() > 0 ) { |
165 | return FallbackStatus::addInterwikiResults( $crossLangResults, |
166 | $this->detectedLangWikiConfig->getWikiId() ); |
167 | } |
168 | return FallbackStatus::noSuggestion(); |
169 | } |
170 | |
171 | public function getMetrics() { |
172 | return $this->searchMetrics; |
173 | } |
174 | } |