Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/131032.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 131032
summary: "Fix: `GET _synonyms` returns synonyms with empty rules"
area: Relevance
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,32 @@ setup:

- match:
count: 12

---
"Return empty rule set":
- requires:
cluster_features: [ synonyms_set.get.return_empty_synonym_sets ]
reason: "synonyms_set get api return empty synonym sets"

- do:
synonyms.put_synonym:
id: empty-synonyms
body:
synonyms_set: []

- do:
synonyms.get_synonyms_sets: {}

- match:
count: 4

- match:
results:
- synonyms_set: "test-synonyms-1"
count: 3
- synonyms_set: "test-synonyms-2"
count: 1
- synonyms_set: "test-synonyms-3"
count: 2
- synonyms_set: "empty-synonyms"
count: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.synonyms;

import org.elasticsearch.features.FeatureSpecification;
import org.elasticsearch.features.NodeFeature;

import java.util.Set;

public class SynonymFeatures implements FeatureSpecification {
private static final NodeFeature RETURN_EMPTY_SYNONYM_SETS = new NodeFeature("synonyms_set.get.return_empty_synonym_sets");

@Override
public Set<NodeFeature> getTestFeatures() {
return Set.of(RETURN_EMPTY_SYNONYM_SETS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.filter.Filters;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
Expand Down Expand Up @@ -94,6 +97,8 @@ public class SynonymsManagementAPIService {
private static final int MAX_SYNONYMS_SETS = 10_000;
private static final String SYNONYM_RULE_ID_FIELD = SynonymRule.ID_FIELD.getPreferredName();
private static final String SYNONYM_SETS_AGG_NAME = "synonym_sets_aggr";
private static final String RULE_COUNT_AGG_NAME = "rule_count";
private static final String RULE_COUNT_FILTER_KEY = "synonym_rules";
private static final int SYNONYMS_INDEX_MAPPINGS_VERSION = 1;
public static final int INDEX_SEARCHABLE_TIMEOUT_SECONDS = 30;
private final int maxSynonymsSets;
Expand Down Expand Up @@ -185,27 +190,45 @@ private static XContentBuilder mappings() {
}
}

/**
* Returns all synonym sets with their rule counts, including empty synonym sets.
* @param from The index of the first synonym set to return
* @param size The number of synonym sets to return
* @param listener The listener to return the synonym sets to
*/
public void getSynonymsSets(int from, int size, ActionListener<PagedResult<SynonymSetSummary>> listener) {
BoolQueryBuilder synonymSetQuery = QueryBuilders.boolQuery()
.should(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_SET_OBJECT_TYPE))
.should(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
.minimumShouldMatch(1);

// Aggregation query to count only synonym rules (excluding synonym set objects)
FiltersAggregationBuilder ruleCountAggregation = new FiltersAggregationBuilder(
RULE_COUNT_AGG_NAME,
new FiltersAggregator.KeyedFilter(RULE_COUNT_FILTER_KEY, QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
);

client.prepareSearch(SYNONYMS_ALIAS_NAME)
.setSize(0)
// Retrieves aggregated synonym rules for each synonym set, excluding the synonym set object type
.setQuery(QueryBuilders.termQuery(OBJECT_TYPE_FIELD, SYNONYM_RULE_OBJECT_TYPE))
.setQuery(synonymSetQuery)
.addAggregation(
new TermsAggregationBuilder(SYNONYM_SETS_AGG_NAME).field(SYNONYMS_SET_FIELD)
.order(BucketOrder.key(true))
.size(maxSynonymsSets)
.subAggregation(ruleCountAggregation)
)
.setPreference(Preference.LOCAL.type())
.execute(new ActionListener<>() {
@Override
public void onResponse(SearchResponse searchResponse) {
Terms termsAggregation = searchResponse.getAggregations().get(SYNONYM_SETS_AGG_NAME);
List<? extends Terms.Bucket> buckets = termsAggregation.getBuckets();
SynonymSetSummary[] synonymSetSummaries = buckets.stream()
.skip(from)
.limit(size)
.map(bucket -> new SynonymSetSummary(bucket.getDocCount(), bucket.getKeyAsString()))
.toArray(SynonymSetSummary[]::new);
SynonymSetSummary[] synonymSetSummaries = buckets.stream().skip(from).limit(size).map(bucket -> {
Filters ruleCountFilters = bucket.getAggregations().get(RULE_COUNT_AGG_NAME);
Filters.Bucket ruleCountBucket = ruleCountFilters.getBucketByKey(RULE_COUNT_FILTER_KEY);
return new SynonymSetSummary(ruleCountBucket.getDocCount(), bucket.getKeyAsString());
}).toArray(SynonymSetSummary[]::new);

listener.onResponse(new PagedResult<>(buckets.size(), synonymSetSummaries));
}
Expand Down
Loading