Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.98% covered (success)
92.98%
53 / 57
89.47% covered (warning)
89.47%
17 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
CirrusDebugOptions
92.98% covered (success)
92.98%
53 / 57
89.47% covered (warning)
89.47%
17 / 19
39.53
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromRequest
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 defaultOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 forDumpingQueriesInUnitTests
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 forRelevanceTesting
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 forSemanticSearchUnitTests
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 debugOption
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 isCirrusDumpQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCirrusDumpQueryAST
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCirrusDumpResult
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCirrusSemanticSearch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCirrusSemanticSearchHighlights
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCirrusExplainPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCirrusExplainFormat
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 getCirrusMLRModel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDumpAndDie
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isReturnRaw
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
4
 applyDebugOptions
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
7.03
 mustNeverBeCached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace CirrusSearch;
4
5use CirrusSearch\Query\SemanticSearchQueryBuilder;
6use Elastica\Query;
7use MediaWiki\Request\WebRequest;
8
9/**
10 * Cirrus debug options generally set via *unofficial* URI param (&cirrusXYZ=ZYX)
11 */
12class CirrusDebugOptions {
13
14    /**
15     * @var bool
16     */
17    private $cirrusDumpQuery = false;
18
19    /**
20     * @var bool
21     */
22    private $cirrusDumpQueryAST = false;
23
24    /**
25     * @var bool
26     */
27    private $cirrusDumpResult = false;
28
29    private bool $cirrusSemanticSearch = false;
30
31    private bool $cirrusSemanticSearchHighlights = false;
32
33    /**
34     * @var string|null
35     */
36    private $cirrusExplain;
37
38    private ?string $cirrusExplainPage = null;
39
40    /**
41     * @var string|null
42     */
43    private $cirrusMLRModel;
44
45    /**
46     * @var bool used by unit tests (to not die and return the query as json back to the caller)
47     */
48    private $dumpAndDie = false;
49
50    private function __construct() {
51    }
52
53    /**
54     * @param WebRequest $request
55     * @return self
56     */
57    public static function fromRequest( WebRequest $request ): self {
58        $options = new self();
59        $options->cirrusDumpQuery = $request->getCheck( 'cirrusDumpQuery' );
60        $options->cirrusDumpQueryAST = $request->getCheck( 'cirrusDumpQueryAST' );
61        $options->cirrusDumpResult = $request->getCheck( 'cirrusDumpResult' );
62        $options->cirrusSemanticSearch = $request->getCheck( 'cirrusSemanticSearch' );
63        $options->cirrusSemanticSearchHighlights = $request->getVal( 'cirrusSemanticSearch' ) === 'hl';
64        $options->cirrusExplain = self::debugOption( $request, 'cirrusExplain', [ 'verbose', 'pretty', 'hot', 'raw' ] );
65        $options->cirrusExplainPage = $request->getVal( 'cirrusExplainPage' ) ?: null;
66        $options->cirrusMLRModel = $request->getVal( 'cirrusMLRModel' );
67        $options->dumpAndDie = $options->cirrusDumpQuery || $options->cirrusDumpQueryAST
68            || $options->cirrusDumpResult || $options->cirrusExplainPage !== null;
69        return $options;
70    }
71
72    /**
73     * Default options (no debug options set)
74     */
75    public static function defaultOptions(): self {
76        return new self();
77    }
78
79    /**
80     * Dump the query but not die.
81     * Only useful in Unit tests.
82     */
83    public static function forDumpingQueriesInUnitTests(): self {
84        $options = new self();
85        $options->cirrusDumpQuery = true;
86        $options->dumpAndDie = false;
87        return $options;
88    }
89
90    /**
91     * @param string|null $withExplain
92     * @return self
93     */
94    public static function forRelevanceTesting( $withExplain = null ): self {
95        $options = new self();
96        $options->cirrusExplain = $withExplain;
97        return $options;
98    }
99
100    public static function forSemanticSearchUnitTests(): self {
101        $options = new self();
102        $options->cirrusSemanticSearch = true;
103        return $options;
104    }
105
106    /**
107     * Inspect the param names $param and return its value only
108     * if it belongs to the set of allowed values declared in $allowedValues
109     * @param WebRequest $request
110     * @param string $param
111     * @param string[] $allowedValues
112     * @return string|null the debug option or null
113     */
114    private static function debugOption( WebRequest $request, $param, array $allowedValues ) {
115        $val = $request->getVal( $param );
116        if ( $val === null ) {
117            return null;
118        }
119        if ( in_array( $val, $allowedValues ) ) {
120            return $val;
121        }
122        return null;
123    }
124
125    /**
126     * @return bool
127     */
128    public function isCirrusDumpQuery() {
129        return $this->cirrusDumpQuery;
130    }
131
132    /**
133     * @return bool
134     */
135    public function isCirrusDumpQueryAST() {
136        return $this->cirrusDumpQueryAST;
137    }
138
139    /**
140     * @return bool
141     */
142    public function isCirrusDumpResult() {
143        return $this->cirrusDumpResult;
144    }
145
146    public function isCirrusSemanticSearch(): bool {
147        return $this->cirrusSemanticSearch;
148    }
149
150    public function isCirrusSemanticSearchHighlights(): bool {
151        return $this->cirrusSemanticSearchHighlights;
152    }
153
154    /**
155     * @return string|null The local mediawiki page id to explain, or null when
156     *  this is not an explain-page request.
157     */
158    public function getCirrusExplainPage() {
159        return $this->cirrusExplainPage;
160    }
161
162    /**
163     * @return string|null The formatting to apply, or null to return raw explains
164     */
165    public function getCirrusExplainFormat() {
166        if ( $this->cirrusExplain === 'raw' || $this->cirrusDumpQuery || $this->cirrusDumpQueryAST
167            || $this->cirrusExplainPage !== null
168        ) {
169            return null;
170        }
171        return $this->cirrusExplain;
172    }
173
174    /**
175     * @return string|null
176     */
177    public function getCirrusMLRModel() {
178        return $this->cirrusMLRModel;
179    }
180
181    /**
182     * @return bool
183     */
184    public function isDumpAndDie() {
185        return $this->dumpAndDie;
186    }
187
188    /**
189     * @return bool true if raw data (query or results) needs to be returned
190     */
191    public function isReturnRaw() {
192        return $this->cirrusDumpQuery || $this->cirrusDumpQueryAST || $this->cirrusDumpResult
193            || $this->cirrusExplainPage !== null;
194    }
195
196    /**
197     * @param Query $query
198     * @return Query
199     */
200    public function applyDebugOptions( Query $query ) {
201        if ( $this->cirrusExplain !== null ) {
202            $query->setExplain( true );
203        }
204        $stats = $query->hasParam( 'stats' ) ? $query->getParam( 'stats' ) : [];
205        if ( $this->isCirrusSemanticSearchHighlights() && in_array( SemanticSearchQueryBuilder::SYNTAX_NAME, $stats ) ) {
206            // only add the batch inference param if the semantic query is used (stats) & requested via cirrusSemanticSearch debug param.
207            if ( $query->hasParam( 'ext' ) ) {
208                $ext = $query->getParam( 'ext' );
209                if ( !is_array( $ext ) ) {
210                    throw new \RuntimeException( 'Search ext parameter must be an array but got "' . gettype( $ext ) . '".' );
211                }
212                $ext['semantic_highlighting_batch'] = true;
213            } else {
214                $ext = [ 'semantic_highlighting_batch' => true ];
215            }
216            $query->setParam( 'ext', $ext );
217        }
218        return $query;
219    }
220
221    /**
222     * @return bool True when queries built with this set of debug options must
223     *  not have their results cached and returned to other users.
224     */
225    public function mustNeverBeCached() {
226        return $this->isReturnRaw() || $this->cirrusExplain !== null;
227    }
228}