Skip to content

Commit 5cceb0d

Browse files
Merge remote-tracking branch 'elastic/8.19' into backport/8.19/pr-130849
2 parents 2b32326 + df80e2e commit 5cceb0d

File tree

12 files changed

+141
-8
lines changed

12 files changed

+141
-8
lines changed

docs/changelog/131032.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 131032
2+
summary: "Fix: `GET _synonyms` returns synonyms with empty rules"
3+
area: Relevance
4+
type: bug
5+
issues: []

docs/changelog/131081.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 131081
2+
summary: Fix knn search error when dimensions are not set
3+
area: Vector Search
4+
type: bug
5+
issues:
6+
- 129550

muted-tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,6 @@ tests:
441441
- class: org.elasticsearch.search.CCSDuelIT
442442
method: testTerminateAfter
443443
issue: https://github.com/elastic/elasticsearch/issues/126085
444+
- class: org.elasticsearch.smoketest.DocsClientYamlTestSuiteIT
445+
method: test {yaml=reference/search/retriever/line_906}
446+
issue: https://github.com/elastic/elasticsearch/issues/131041

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,36 @@ setup:
621621
properties:
622622
embedding:
623623
type: dense_vector
624+
625+
626+
---
627+
"Searching with no data dimensions specified":
628+
- requires:
629+
cluster_features: "search.vectors.no_dimensions_bugfix"
630+
reason: "Search with no dimensions bugfix"
631+
632+
- do:
633+
indices.create:
634+
index: empty-test
635+
body:
636+
mappings:
637+
properties:
638+
vector:
639+
type: dense_vector
640+
index: true
641+
642+
- do:
643+
search:
644+
index: empty-test
645+
body:
646+
fields: [ "name" ]
647+
knn:
648+
field: vector
649+
query_vector: [ -0.5, 90.0, -10, 14.8, -156.0 ]
650+
k: 3
651+
num_candidates: 3
652+
rescore_vector:
653+
oversample: 1.5
654+
similarity: 0.1
655+
656+
- match: { hits.total.value: 0 }

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,33 @@ teardown:
178178

179179
- match:
180180
count: 12
181+
182+
---
183+
"Return empty rule set":
184+
- requires:
185+
cluster_features: [ synonyms_set.get.return_empty_synonym_sets ]
186+
reason: "synonyms_set get api return empty synonym sets"
187+
188+
- do:
189+
synonyms.put_synonym:
190+
id: empty-synonyms
191+
body:
192+
synonyms_set: []
193+
194+
- do:
195+
synonyms.get_synonyms_sets: {}
196+
197+
- match:
198+
count: 4
199+
200+
- match:
201+
results:
202+
- synonyms_set: "empty-synonyms"
203+
count: 0
204+
- synonyms_set: "test-synonyms-1"
205+
count: 3
206+
- synonyms_set: "test-synonyms-2"
207+
count: 1
208+
- synonyms_set: "test-synonyms-3"
209+
count: 2
210+

server/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@
432432
org.elasticsearch.index.IndexFeatures,
433433
org.elasticsearch.ingest.IngestGeoIpFeatures,
434434
org.elasticsearch.search.SearchFeatures,
435+
org.elasticsearch.synonyms.SynonymFeatures,
435436
org.elasticsearch.script.ScriptFeatures,
436437
org.elasticsearch.search.retriever.RetrieversFeatures,
437438
org.elasticsearch.reservedstate.service.FileSettingsFeatures,

server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.lucene.index.VectorEncoding;
3131
import org.apache.lucene.index.VectorSimilarityFunction;
3232
import org.apache.lucene.search.FieldExistsQuery;
33+
import org.apache.lucene.search.MatchNoDocsQuery;
3334
import org.apache.lucene.search.Query;
3435
import org.apache.lucene.search.join.BitSetProducer;
3536
import org.apache.lucene.util.BitUtil;
@@ -2216,6 +2217,9 @@ public Query createKnnQuery(
22162217
"to perform knn search on field [" + name() + "], its mapping must have [index] set to [true]"
22172218
);
22182219
}
2220+
if (dims == null) {
2221+
return new MatchNoDocsQuery("No data has been indexed for field [" + name() + "]");
2222+
}
22192223
return switch (getElementType()) {
22202224
case BYTE -> createKnnByteQuery(queryVector.asByteVector(), k, numCands, filter, similarityThreshold, parentFilter);
22212225
case FLOAT -> createKnnFloatQuery(

server/src/main/java/org/elasticsearch/search/SearchFeatures.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public Set<NodeFeature> getFeatures() {
2525
public static final NodeFeature COMPLETION_FIELD_SUPPORTS_DUPLICATE_SUGGESTIONS = new NodeFeature(
2626
"search.completion_field.duplicate.support"
2727
);
28+
public static final NodeFeature RESCORER_MISSING_FIELD_BAD_REQUEST = new NodeFeature("search.rescorer.missing.field.bad.request");
2829
public static final NodeFeature INT_SORT_FOR_INT_SHORT_BYTE_FIELDS = new NodeFeature("search.sort.int_sort_for_int_short_byte_fields");
2930
static final NodeFeature MULTI_MATCH_CHECKS_POSITIONS = new NodeFeature("search.multi.match.checks.positions");
3031
private static final NodeFeature KNN_QUERY_BUGFIX_130254 = new NodeFeature("search.knn.query.bugfix.130254", true);
32+
public static final NodeFeature SEARCH_WITH_NO_DIMENSIONS_BUGFIX = new NodeFeature("search.vectors.no_dimensions_bugfix");
3133

3234
@Override
3335
public Set<NodeFeature> getTestFeatures() {
@@ -36,7 +38,8 @@ public Set<NodeFeature> getTestFeatures() {
3638
COMPLETION_FIELD_SUPPORTS_DUPLICATE_SUGGESTIONS,
3739
INT_SORT_FOR_INT_SHORT_BYTE_FIELDS,
3840
MULTI_MATCH_CHECKS_POSITIONS,
39-
KNN_QUERY_BUGFIX_130254
41+
KNN_QUERY_BUGFIX_130254,
42+
SEARCH_WITH_NO_DIMENSIONS_BUGFIX
4043
);
4144
}
4245
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.synonyms;
11+
12+
import org.elasticsearch.features.FeatureSpecification;
13+
import org.elasticsearch.features.NodeFeature;
14+
15+
import java.util.Set;
16+
17+
public class SynonymFeatures implements FeatureSpecification {
18+
private static final NodeFeature RETURN_EMPTY_SYNONYM_SETS = new NodeFeature("synonyms_set.get.return_empty_synonym_sets");
19+
20+
@Override
21+
public Set<NodeFeature> getTestFeatures() {
22+
return Set.of(RETURN_EMPTY_SYNONYM_SETS);
23+
}
24+
}

server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
import org.elasticsearch.indices.SystemIndexDescriptor;
4646
import org.elasticsearch.rest.RestStatus;
4747
import org.elasticsearch.search.aggregations.BucketOrder;
48+
import org.elasticsearch.search.aggregations.bucket.filter.Filters;
49+
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder;
50+
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
4851
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
4952
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
5053
import org.elasticsearch.search.builder.SearchSourceBuilder;
@@ -89,6 +92,8 @@ public class SynonymsManagementAPIService {
8992
private static final int MAX_SYNONYMS_SETS = 10_000;
9093
private static final String SYNONYM_RULE_ID_FIELD = SynonymRule.ID_FIELD.getPreferredName();
9194
private static final String SYNONYM_SETS_AGG_NAME = "synonym_sets_aggr";
95+
private static final String RULE_COUNT_AGG_NAME = "rule_count";
96+
private static final String RULE_COUNT_FILTER_KEY = "synonym_rules";
9297
private static final int SYNONYMS_INDEX_MAPPINGS_VERSION = 1;
9398
private final int maxSynonymsSets;
9499

@@ -180,27 +185,45 @@ private static XContentBuilder mappings() {
180185
}
181186
}
182187

188+
/**
189+
* Returns all synonym sets with their rule counts, including empty synonym sets.
190+
* @param from The index of the first synonym set to return
191+
* @param size The number of synonym sets to return
192+
* @param listener The listener to return the synonym sets to
193+
*/
183194
public void getSynonymsSets(int from, int size, ActionListener<PagedResult<SynonymSetSummary>> listener) {
195+
BoolQueryBuilder synonymSetQuery = QueryBuilders.boolQuery()
196+
.should(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_SET_OBJECT_TYPE))
197+
.should(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
198+
.minimumShouldMatch(1);
199+
200+
// Aggregation query to count only synonym rules (excluding synonym set objects)
201+
FiltersAggregationBuilder ruleCountAggregation = new FiltersAggregationBuilder(
202+
RULE_COUNT_AGG_NAME,
203+
new FiltersAggregator.KeyedFilter(RULE_COUNT_FILTER_KEY, QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
204+
);
205+
184206
client.prepareSearch(SYNONYMS_ALIAS_NAME)
185207
.setSize(0)
186208
// Retrieves aggregated synonym rules for each synonym set, excluding the synonym set object type
187-
.setQuery(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
209+
.setQuery(synonymSetQuery)
188210
.addAggregation(
189211
new TermsAggregationBuilder(SYNONYM_SETS_AGG_NAME).field(SYNONYMS_SET_FIELD)
190212
.order(BucketOrder.key(true))
191213
.size(maxSynonymsSets)
214+
.subAggregation(ruleCountAggregation)
192215
)
193216
.setPreference(Preference.LOCAL.type())
194217
.execute(new ActionListener<>() {
195218
@Override
196219
public void onResponse(SearchResponse searchResponse) {
197220
Terms termsAggregation = searchResponse.getAggregations().get(SYNONYM_SETS_AGG_NAME);
198221
List<? extends Terms.Bucket> buckets = termsAggregation.getBuckets();
199-
SynonymSetSummary[] synonymSetSummaries = buckets.stream()
200-
.skip(from)
201-
.limit(size)
202-
.map(bucket -> new SynonymSetSummary(bucket.getDocCount(), bucket.getKeyAsString()))
203-
.toArray(SynonymSetSummary[]::new);
222+
SynonymSetSummary[] synonymSetSummaries = buckets.stream().skip(from).limit(size).map(bucket -> {
223+
Filters ruleCountFilters = bucket.getAggregations().get(RULE_COUNT_AGG_NAME);
224+
Filters.Bucket ruleCountBucket = ruleCountFilters.getBucketByKey(RULE_COUNT_FILTER_KEY);
225+
return new SynonymSetSummary(ruleCountBucket.getDocCount(), bucket.getKeyAsString());
226+
}).toArray(SynonymSetSummary[]::new);
204227

205228
listener.onResponse(new PagedResult<>(buckets.size(), synonymSetSummaries));
206229
}

0 commit comments

Comments
 (0)