Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
76.71% |
56 / 73 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
FunctionScoreChain | |
76.71% |
56 / 73 |
|
25.00% |
1 / 4 |
43.14 | |
0.00% |
0 / 1 |
__construct | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
3.01 | |||
applyOverrides | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
buildRescoreQuery | |
72.73% |
8 / 11 |
|
0.00% |
0 / 1 |
6.73 | |||
getImplementation | |
73.47% |
36 / 49 |
|
0.00% |
0 / 1 |
29.24 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search\Rescore; |
4 | |
5 | use CirrusSearch\CirrusSearchHookRunner; |
6 | use CirrusSearch\Profile\ArrayPathSetter; |
7 | use CirrusSearch\Profile\SearchProfileService; |
8 | use CirrusSearch\Search\SearchContext; |
9 | use Elastica\Query\FunctionScore; |
10 | |
11 | /** |
12 | * This program is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation; either version 2 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License along |
23 | * with this program; if not, write to the Free Software Foundation, Inc., |
24 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
25 | * http://www.gnu.org/copyleft/gpl.html |
26 | */ |
27 | |
28 | class FunctionScoreChain { |
29 | /** |
30 | * List of allowed function_score param |
31 | * we keep boost and boost_mode even if they do not make sense |
32 | * here since we do not allow to specify the query param. |
33 | * The query will be MatchAll with a score to 1. |
34 | * |
35 | * @var string[] |
36 | */ |
37 | private static $functionScoreParams = [ |
38 | 'boost', |
39 | 'boost_mode', |
40 | 'max_boost', |
41 | 'score_mode', |
42 | 'min_score' |
43 | ]; |
44 | |
45 | /** |
46 | * @var SearchContext |
47 | */ |
48 | private $context; |
49 | |
50 | /** |
51 | * @var FunctionScoreDecorator |
52 | */ |
53 | private $functionScore; |
54 | |
55 | /** |
56 | * @var array the function score chain |
57 | */ |
58 | private $chain; |
59 | |
60 | /** |
61 | * @var string the name of the chain |
62 | */ |
63 | private $chainName; |
64 | /** |
65 | * @var CirrusSearchHookRunner |
66 | */ |
67 | private $cirrusSearchHookRunner; |
68 | |
69 | /** |
70 | * Builds a new function score chain. |
71 | * |
72 | * @param SearchContext $context |
73 | * @param string $chainName the name of the chain (must be a valid |
74 | * chain in wgCirrusSearchRescoreFunctionScoreChains) |
75 | * @param array $overrides Parameter overrides |
76 | * @param CirrusSearchHookRunner $cirrusSearchHookRunner |
77 | */ |
78 | public function __construct( SearchContext $context, $chainName, $overrides, CirrusSearchHookRunner $cirrusSearchHookRunner ) { |
79 | $this->chainName = $chainName; |
80 | $this->context = $context; |
81 | $this->functionScore = new FunctionScoreDecorator(); |
82 | $chain = $context->getConfig() |
83 | ->getProfileService() |
84 | ->loadProfileByName( SearchProfileService::RESCORE_FUNCTION_CHAINS, $chainName ); |
85 | $this->chain = $overrides ? $this->applyOverrides( $chain, $overrides ) : $chain; |
86 | |
87 | $params = array_intersect_key( $this->chain, array_flip( self::$functionScoreParams ) ); |
88 | foreach ( $params as $param => $value ) { |
89 | $this->functionScore->setParam( $param, $value ); |
90 | } |
91 | $this->cirrusSearchHookRunner = $cirrusSearchHookRunner; |
92 | } |
93 | |
94 | private function applyOverrides( array $chain, array $overrides ) { |
95 | $transformer = new ArrayPathSetter( $overrides ); |
96 | return $transformer->transform( $chain ); |
97 | } |
98 | |
99 | /** |
100 | * @return FunctionScore|null the rescore query or null none of functions were |
101 | * needed. |
102 | * @throws InvalidRescoreProfileException |
103 | */ |
104 | public function buildRescoreQuery() { |
105 | if ( !isset( $this->chain['functions'] ) ) { |
106 | throw new InvalidRescoreProfileException( "No functions defined in chain {$this->chainName}." ); |
107 | } |
108 | foreach ( $this->chain['functions'] as $func ) { |
109 | $impl = $this->getImplementation( $func ); |
110 | $impl->append( $this->functionScore ); |
111 | } |
112 | // Add extensions |
113 | if ( !empty( $this->chain['add_extensions'] ) ) { |
114 | foreach ( $this->context->getExtraScoreBuilders() as $extBuilder ) { |
115 | $extBuilder->append( $this->functionScore ); |
116 | } |
117 | } |
118 | if ( !$this->functionScore->isEmptyFunction() ) { |
119 | return $this->functionScore; |
120 | } |
121 | return null; |
122 | } |
123 | |
124 | /** |
125 | * @param array $func |
126 | * @return BoostFunctionBuilder |
127 | * @throws InvalidRescoreProfileException |
128 | */ |
129 | private function getImplementation( $func ) { |
130 | $weight = $func['weight'] ?? 1; |
131 | $config = $this->context->getConfig(); |
132 | switch ( $func['type'] ) { |
133 | case 'boostlinks': |
134 | return new IncomingLinksFunctionScoreBuilder(); |
135 | case 'recency': |
136 | foreach ( $this->context->getExtraScoreBuilders() as $boostFunctionBuilder ) { |
137 | if ( $boostFunctionBuilder instanceof PreferRecentFunctionScoreBuilder ) { |
138 | // If prefer-recent was used as a keyword we don't send the one |
139 | // from the profile |
140 | return new BoostedQueriesFunction( [], [] ); |
141 | } |
142 | } |
143 | |
144 | $preferRecentDecayPortion = $config->get( 'CirrusSearchPreferRecentDefaultDecayPortion' ); |
145 | $preferRecentHalfLife = 0; |
146 | if ( $preferRecentDecayPortion > 0 ) { |
147 | $preferRecentHalfLife = $config->get( 'CirrusSearchPreferRecentDefaultHalfLife' ); |
148 | } |
149 | return new PreferRecentFunctionScoreBuilder( $config, $weight, |
150 | $preferRecentHalfLife, $preferRecentDecayPortion ); |
151 | case 'templates': |
152 | $withDefaultBoosts = true; |
153 | foreach ( $this->context->getExtraScoreBuilders() as $boostFunctionBuilder ) { |
154 | if ( $boostFunctionBuilder instanceof ByKeywordTemplateBoostFunction ) { |
155 | $withDefaultBoosts = false; |
156 | break; |
157 | } |
158 | } |
159 | |
160 | return new BoostTemplatesFunctionScoreBuilder( $config, $this->context->getNamespaces(), |
161 | $this->context->getLimitSearchToLocalWiki(), $withDefaultBoosts, $weight ); |
162 | case 'namespaces': |
163 | return new NamespacesFunctionScoreBuilder( $config, $this->context->getNamespaces(), $weight ); |
164 | case 'language': |
165 | return new LangWeightFunctionScoreBuilder( $config, $weight ); |
166 | case 'custom_field': |
167 | return new CustomFieldFunctionScoreBuilder( $config, $weight, $func['params'] ); |
168 | case 'script': |
169 | return new ScriptScoreFunctionScoreBuilder( $config, $weight, $func['script'] ); |
170 | case 'logscale_boost': |
171 | return new LogScaleBoostFunctionScoreBuilder( $config, $weight, $func['params'] ); |
172 | case 'satu': |
173 | return new SatuFunctionScoreBuilder( $config, $weight, $func['params'] ); |
174 | case 'log_multi': |
175 | return new LogMultFunctionScoreBuilder( $config, $weight, $func['params'] ); |
176 | case 'geomean': |
177 | return new GeoMeanFunctionScoreBuilder( $config, $weight, $func['params'] ); |
178 | case 'term_boost': |
179 | return new TermBoostScoreBuilder( $config, $weight, $func['params'] ); |
180 | default: |
181 | $builder = null; |
182 | $this->cirrusSearchHookRunner->onCirrusSearchScoreBuilder( $func, $this->context, $builder ); |
183 | if ( !$builder ) { |
184 | throw new InvalidRescoreProfileException( "Unknown function score type {$func['type']}." ); |
185 | } |
186 | if ( !( $builder instanceof BoostFunctionBuilder ) ) { |
187 | throw new InvalidRescoreProfileException( "Invalid function score type {$func['type']}: expected " . |
188 | BoostFunctionBuilder::class . " but was " . get_class( $builder ) ); |
189 | } |
190 | /** |
191 | * @var $builder BoostFunctionBuilder |
192 | */ |
193 | return $builder; |
194 | } |
195 | } |
196 | } |