Skip to content

Commit 4d229ee

Browse files
authored
[8.12] Fix bug in rule_query where text_expansion errored because it was not rewritten (#105365) (#105565)
* Fix bug in rule_query where text_expansion errored because it was not rewritten (#105365) * Update 260_rule_query_search.yml Fix skip version for tests
1 parent 29481ae commit 4d229ee

File tree

5 files changed

+190
-37
lines changed

5 files changed

+190
-37
lines changed

docs/changelog/105365.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 105365
2+
summary: Fix bug in `rule_query` where `text_expansion` errored because it was not
3+
rewritten
4+
area: Application
5+
type: bug
6+
issues: []

x-pack/plugin/ent-search/qa/rest/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies {
77

88
restResources {
99
restApi {
10-
include '_common', 'bulk', 'cluster', 'connector', 'nodes', 'indices', 'index', 'query_ruleset', 'search_application', 'xpack', 'security', 'search'
10+
include '_common', 'bulk', 'cluster', 'connector', 'nodes', 'indices', 'index', 'query_ruleset', 'search_application', 'xpack', 'security', 'search', 'ml'
1111
}
1212
}
1313

x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/260_rule_query_search.yml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,154 @@ setup:
236236
- match: { hits.total.value: 1 }
237237
- match: { hits.hits.0._id: 'doc1' }
238238

239+
---
240+
"Perform a rule query with an organic query that must be rewritten to another query type":
241+
- skip:
242+
version: " - 8.12.2"
243+
reason: Bugfix that was broken in previous versions
244+
245+
- do:
246+
indices.create:
247+
index: test-index-with-sparse-vector
248+
body:
249+
mappings:
250+
properties:
251+
source_text:
252+
type: keyword
253+
ml.tokens:
254+
type: sparse_vector
255+
256+
- do:
257+
ml.put_trained_model:
258+
model_id: "text_expansion_model"
259+
body: >
260+
{
261+
"description": "simple model for testing",
262+
"model_type": "pytorch",
263+
"inference_config": {
264+
"text_expansion": {
265+
"tokenization": {
266+
"bert": {
267+
"with_special_tokens": false
268+
}
269+
}
270+
}
271+
}
272+
}
273+
- do:
274+
ml.put_trained_model_vocabulary:
275+
model_id: "text_expansion_model"
276+
body: >
277+
{ "vocabulary": ["[PAD]", "[UNK]", "these", "are", "my", "words", "the", "washing", "machine", "is", "leaking", "octopus", "comforter", "smells"] }
278+
- do:
279+
ml.put_trained_model_definition_part:
280+
model_id: "text_expansion_model"
281+
part: 0
282+
body: >
283+
{
284+
"total_definition_length":2078,
285+
"definition": "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",
286+
"total_parts": 1
287+
}
288+
- do:
289+
bulk:
290+
index: test-index-with-sparse-vector
291+
refresh: true
292+
body: |
293+
{"index": {}}
294+
{"source_text": "my words comforter", "ml.tokens":{"my":1.0, "words":1.0,"comforter":1.0}}
295+
{"index": {}}
296+
{"source_text": "the machine is leaking", "ml.tokens":{"the":1.0,"machine":1.0,"is":1.0,"leaking":1.0}}
297+
{"index": {}}
298+
{"source_text": "these are my words", "ml.tokens":{"these":1.0,"are":1.0,"my":1.0,"words":1.0}}
299+
{"index": {}}
300+
{"source_text": "the octopus comforter smells", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"smells":1.0}}
301+
{"index": {}}
302+
{"source_text": "the octopus comforter is leaking", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"is":1.0,"leaking":1.0}}
303+
{"index": {}}
304+
{"source_text": "washing machine smells", "ml.tokens":{"washing":1.0,"machine":1.0,"smells":1.0}}
305+
{"index": { "_id": "pinned_doc1" }}
306+
{"source_text": "unrelated pinned doc", "ml.tokens":{"unrelated":1.0,"pinned":1.0,"doc":1.0}}
307+
{"index": { "_id": "pinned_doc2" }}
308+
{"source_text": "another unrelated pinned doc", "ml.tokens":{"another":1.0, "unrelated":1.0,"pinned":1.0,"doc":1.0}}
309+
310+
- do:
311+
ml.start_trained_model_deployment:
312+
model_id: text_expansion_model
313+
wait_for: started
314+
315+
- do:
316+
query_ruleset.put:
317+
ruleset_id: combined-ruleset
318+
body:
319+
rules:
320+
- rule_id: rule1
321+
type: pinned
322+
criteria:
323+
- type: exact
324+
metadata: foo
325+
values: [ bar ]
326+
actions:
327+
ids:
328+
- 'pinned_doc1'
329+
- rule_id: rule2
330+
type: pinned
331+
criteria:
332+
- type: exact
333+
metadata: foo
334+
values: [ baz ]
335+
actions:
336+
docs:
337+
- '_index': 'test-index-with-sparse-vector'
338+
'_id': 'pinned_doc2'
339+
- do:
340+
search:
341+
body:
342+
query:
343+
rule_query:
344+
organic:
345+
text_expansion:
346+
ml.tokens:
347+
model_id: text_expansion_model
348+
model_text: "octopus comforter smells"
349+
match_criteria:
350+
foo: bar
351+
ruleset_id: combined-ruleset
352+
353+
- match: { hits.total.value: 5 }
354+
- match: { hits.hits.0._id: 'pinned_doc1' }
355+
356+
- do:
357+
search:
358+
body:
359+
query:
360+
rule_query:
361+
organic:
362+
text_expansion:
363+
ml.tokens:
364+
model_id: text_expansion_model
365+
model_text: "octopus comforter smells"
366+
match_criteria:
367+
foo: baz
368+
ruleset_id: combined-ruleset
369+
370+
- match: { hits.total.value: 5 }
371+
- match: { hits.hits.0._id: 'pinned_doc2' }
372+
373+
- do:
374+
search:
375+
body:
376+
query:
377+
rule_query:
378+
organic:
379+
text_expansion:
380+
ml.tokens:
381+
model_id: text_expansion_model
382+
model_text: "octopus comforter smells"
383+
match_criteria:
384+
foo: puggle
385+
ruleset_id: combined-ruleset
386+
387+
- match: { hits.total.value: 4 }
388+
239389

x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/rules/RuleQueryBuilder.java

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,6 @@ private RuleQueryBuilder(
111111
throw new IllegalArgumentException("rulesetId must not be null or empty");
112112
}
113113

114-
// PinnedQueryBuilder will return an error if we attempt to return more than the maximum number of
115-
// pinned hits. Here, we truncate matching rules rather than return an error.
116-
if (pinnedIds != null && pinnedIds.size() > MAX_NUM_PINNED_HITS) {
117-
HeaderWarning.addWarning("Truncating query rule pinned hits to " + MAX_NUM_PINNED_HITS + " documents");
118-
pinnedIds = pinnedIds.subList(0, MAX_NUM_PINNED_HITS);
119-
}
120-
121-
if (pinnedDocs != null && pinnedDocs.size() > MAX_NUM_PINNED_HITS) {
122-
HeaderWarning.addWarning("Truncating query rule pinned hits to " + MAX_NUM_PINNED_HITS + " documents");
123-
pinnedDocs = pinnedDocs.subList(0, MAX_NUM_PINNED_HITS);
124-
}
125-
126114
this.organicQuery = organicQuery;
127115
this.matchCriteria = matchCriteria;
128116
this.rulesetId = rulesetId;
@@ -174,6 +162,9 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
174162

175163
@Override
176164
protected Query doToQuery(SearchExecutionContext context) throws IOException {
165+
// NOTE: this is old query logic, as in 8.12.2+ and 8.13.0+ we will always rewrite this query
166+
// into a pinned query or the organic query. This logic remains here for backwards compatibility
167+
// with coordinator nodes running versions 8.10.0 - 8.12.1.
177168
if ((pinnedIds != null && pinnedIds.isEmpty() == false) && (pinnedDocs != null && pinnedDocs.isEmpty() == false)) {
178169
throw new IllegalArgumentException("applied rules contain both pinned ids and pinned docs, only one of ids or docs is allowed");
179170
}
@@ -187,21 +178,25 @@ protected Query doToQuery(SearchExecutionContext context) throws IOException {
187178
} else {
188179
return organicQuery.toQuery(context);
189180
}
190-
191181
}
192182

193-
@SuppressWarnings("unchecked")
194183
@Override
195-
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
196-
if (pinnedIds != null || pinnedDocs != null) {
197-
return this;
198-
} else if (pinnedIdsSupplier != null || pinnedDocsSupplier != null) {
199-
List<String> identifiedPinnedIds = pinnedIdsSupplier != null ? pinnedIdsSupplier.get() : null;
200-
List<Item> identifiedPinnedDocs = pinnedDocsSupplier != null ? pinnedDocsSupplier.get() : null;
201-
if (identifiedPinnedIds == null && identifiedPinnedDocs == null) {
202-
return this; // not executed yet
184+
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) {
185+
if (pinnedIdsSupplier != null && pinnedDocsSupplier != null) {
186+
List<String> identifiedPinnedIds = pinnedIdsSupplier.get();
187+
List<Item> identifiedPinnedDocs = pinnedDocsSupplier.get();
188+
if (identifiedPinnedIds == null || identifiedPinnedDocs == null) {
189+
return this; // Not executed yet
190+
} else if (identifiedPinnedIds.isEmpty() && identifiedPinnedDocs.isEmpty()) {
191+
return organicQuery; // Nothing to pin here
192+
} else if (identifiedPinnedIds.isEmpty() == false && identifiedPinnedDocs.isEmpty() == false) {
193+
throw new IllegalArgumentException(
194+
"applied rules contain both pinned ids and pinned docs, only one of ids or docs is allowed"
195+
);
196+
} else if (identifiedPinnedIds.isEmpty() == false) {
197+
return new PinnedQueryBuilder(organicQuery, truncateList(identifiedPinnedIds).toArray(new String[0]));
203198
} else {
204-
return new RuleQueryBuilder(organicQuery, matchCriteria, rulesetId, identifiedPinnedIds, identifiedPinnedDocs, null, null);
199+
return new PinnedQueryBuilder(organicQuery, truncateList(identifiedPinnedDocs).toArray(new Item[0]));
205200
}
206201
}
207202

@@ -244,18 +239,19 @@ public void onFailure(Exception e) {
244239
});
245240
});
246241

247-
QueryBuilder newOrganicQuery = organicQuery.rewrite(queryRewriteContext);
248-
RuleQueryBuilder rewritten = new RuleQueryBuilder(
249-
newOrganicQuery,
250-
matchCriteria,
251-
this.rulesetId,
252-
null,
253-
null,
254-
pinnedIdsSetOnce::get,
255-
pinnedDocsSetOnce::get
256-
);
257-
rewritten.boost(this.boost);
258-
return rewritten;
242+
return new RuleQueryBuilder(organicQuery, matchCriteria, this.rulesetId, null, null, pinnedIdsSetOnce::get, pinnedDocsSetOnce::get)
243+
.boost(this.boost)
244+
.queryName(this.queryName);
245+
}
246+
247+
private List<?> truncateList(List<?> input) {
248+
// PinnedQueryBuilder will return an error if we attempt to return more than the maximum number of
249+
// pinned hits. Here, we truncate matching rules rather than return an error.
250+
if (input.size() > MAX_NUM_PINNED_HITS) {
251+
HeaderWarning.addWarning("Truncating query rule pinned hits to " + MAX_NUM_PINNED_HITS + " documents");
252+
return input.subList(0, MAX_NUM_PINNED_HITS);
253+
}
254+
return input;
259255
}
260256

261257
@Override

x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/rules/RuleQueryBuilderTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.xcontent.XContentBuilder;
3232
import org.elasticsearch.xcontent.XContentFactory;
3333
import org.elasticsearch.xpack.application.LocalStateEnterpriseSearch;
34+
import org.elasticsearch.xpack.searchbusinessrules.SearchBusinessRules;
3435
import org.hamcrest.Matchers;
3536

3637
import java.io.IOException;
@@ -62,7 +63,7 @@ protected void doAssertLuceneQuery(RuleQueryBuilder queryBuilder, Query query, S
6263

6364
@Override
6465
protected Collection<Class<? extends Plugin>> getPlugins() {
65-
return Collections.singletonList(LocalStateEnterpriseSearch.class);
66+
return List.of(LocalStateEnterpriseSearch.class, SearchBusinessRules.class);
6667
}
6768

6869
public void testIllegalArguments() {

0 commit comments

Comments
 (0)