Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 67 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
FormSearchEntity | |
0.00% |
0 / 67 |
|
0.00% |
0 / 3 |
56 | |
0.00% |
0 / 1 |
getElasticSearchQuery | |
0.00% |
0 / 58 |
|
0.00% |
0 / 1 |
30 | |||
makeResultType | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getRankedSearchResults | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | namespace Wikibase\Lexeme\Search\Elastic; |
3 | |
4 | use CirrusSearch\Search\ResultsType; |
5 | use CirrusSearch\Search\SearchContext; |
6 | use Elastica\Query\AbstractQuery; |
7 | use Elastica\Query\BoolQuery; |
8 | use Elastica\Query\DisMax; |
9 | use Elastica\Query\MatchNone; |
10 | use Elastica\Query\MatchQuery; |
11 | use Elastica\Query\Term; |
12 | use Wikibase\Lexeme\MediaWiki\Content\LexemeContent; |
13 | use Wikibase\Search\Elastic\EntitySearchElastic; |
14 | use Wikibase\Search\Elastic\EntitySearchUtils; |
15 | |
16 | /** |
17 | * Search for entity of type form. |
18 | * |
19 | * @license GPL-2.0-or-later |
20 | * @author Stas Malyshev |
21 | */ |
22 | class FormSearchEntity extends LexemeSearchEntity { |
23 | /** |
24 | * Search limit. |
25 | * @var int |
26 | */ |
27 | private $limit; |
28 | |
29 | /** |
30 | * Produce ES query that matches the arguments. |
31 | * This is search for forms - matches only form representations |
32 | * but not lexemes. |
33 | * |
34 | * @param string $text |
35 | * @param string $entityType |
36 | * @param SearchContext $context |
37 | * |
38 | * @return AbstractQuery |
39 | */ |
40 | protected function getElasticSearchQuery( |
41 | $text, |
42 | $entityType, |
43 | SearchContext $context |
44 | ) { |
45 | $context->setOriginalSearchTerm( $text ); |
46 | // TODO consider using Form::ENTITY_TYPE |
47 | if ( $entityType !== 'form' ) { |
48 | $context->setResultsPossible( false ); |
49 | $context->addWarning( 'wikibase-search-bad-entity-type', $entityType ); |
50 | return new MatchNone(); |
51 | } |
52 | // Drop only leading spaces for exact matches, and all spaces for the rest |
53 | $textExact = ltrim( $text ); |
54 | $text = trim( $text ); |
55 | |
56 | $labelsFilter = new MatchQuery( 'lexeme_forms.representation.prefix', $text ); |
57 | |
58 | $profile = $context->getConfig() |
59 | ->getProfileService() |
60 | ->loadProfile( EntitySearchElastic::WIKIBASE_PREFIX_QUERY_BUILDER, |
61 | self::CONTEXT_LEXEME_PREFIX ); |
62 | |
63 | $dismax = new DisMax(); |
64 | $dismax->setTieBreaker( $profile['tie-breaker'] ?? 0 ); |
65 | |
66 | $fields = [ |
67 | [ |
68 | "lexeme_forms.representation.near_match", |
69 | $profile['exact'] * $profile['form-discount'], |
70 | ], |
71 | [ |
72 | "lexeme_forms.representation.near_match_folded", |
73 | $profile['folded'] * $profile['form-discount'], |
74 | ], |
75 | ]; |
76 | // Fields to which query applies exactly as stated, without trailing space trimming |
77 | $fieldsExact = []; |
78 | if ( $textExact !== $text ) { |
79 | $fields[] = |
80 | [ |
81 | "lexeme_forms.representation.prefix", |
82 | $profile['prefix'] * $profile['space-discount'] * $profile['form-discount'], |
83 | ]; |
84 | $fieldsExact[] = |
85 | [ |
86 | "lexeme_forms.representation.prefix", |
87 | $profile['prefix'] * $profile['form-discount'], |
88 | ]; |
89 | } else { |
90 | $fields[] = |
91 | [ |
92 | "lexeme_forms.representation.prefix", |
93 | $profile['prefix'] * $profile['form-discount'], |
94 | ]; |
95 | } |
96 | |
97 | foreach ( $fields as $field ) { |
98 | $dismax->addQuery( EntitySearchUtils::makeConstScoreQuery( $field[0], $field[1], |
99 | $text ) ); |
100 | } |
101 | |
102 | foreach ( $fieldsExact as $field ) { |
103 | $dismax->addQuery( EntitySearchUtils::makeConstScoreQuery( $field[0], $field[1], |
104 | $textExact ) ); |
105 | } |
106 | |
107 | $labelsQuery = new BoolQuery(); |
108 | $labelsQuery->addFilter( $labelsFilter ); |
109 | $labelsQuery->addShould( $dismax ); |
110 | // lexeme_forms.id is a lowercase_keyword so use Match to apply the analyzer |
111 | $titleMatch = new MatchQuery( 'lexeme_forms.id', |
112 | EntitySearchUtils::normalizeId( $text, $this->idParser ) ); |
113 | |
114 | $query = new BoolQuery(); |
115 | // Match either labels or exact match to title |
116 | $query->addShould( $labelsQuery ); |
117 | $query->addShould( $titleMatch ); |
118 | $query->setMinimumShouldMatch( 1 ); |
119 | |
120 | // Filter to fetch only given entity type |
121 | $query->addFilter( new Term( [ 'content_model' => LexemeContent::CONTENT_MODEL_ID ] ) ); |
122 | |
123 | return $query; |
124 | } |
125 | |
126 | /** |
127 | * Get results type object for this search. |
128 | * @return ResultsType |
129 | */ |
130 | protected function makeResultType() { |
131 | return new FormTermResult( |
132 | $this->idParser, |
133 | $this->userLanguage, |
134 | $this->lookupFactory, |
135 | $this->limit |
136 | ); |
137 | } |
138 | |
139 | /** |
140 | * @inheritDoc |
141 | */ |
142 | public function getRankedSearchResults( |
143 | $text, |
144 | $languageCode, |
145 | $entityType, |
146 | $limit, |
147 | $strictLanguage, |
148 | ?string $profileContext = null |
149 | ) { |
150 | // We need to keep the limit since one document can produce several matches. |
151 | $this->limit = $limit; |
152 | return parent::getRankedSearchResults( $text, $languageCode, $entityType, $limit, |
153 | $strictLanguage, $profileContext ); |
154 | } |
155 | |
156 | } |