[Synonym] Return Empty RuleSet (#131032)

* Adding filter agg to include empty synonym sets

* Adding featurespecification for synonyms

* linting

* Update docs/changelog/131032.yaml

* update changelog

* Adding synonym feature into module-info

* sorted the expected response

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Samiul Monir 2025-07-11 08:07:11 -04:00 committed by GitHub
parent 0b06d9f24d
commit a12338aa27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 6 deletions

View File

@ -0,0 +1,5 @@
pr: 131032
summary: "Fix: `GET _synonyms` returns synonyms with empty rules"
area: Relevance
type: bug
issues: []

View File

@ -157,3 +157,33 @@ 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: "empty-synonyms"
count: 0
- synonyms_set: "test-synonyms-1"
count: 3
- synonyms_set: "test-synonyms-2"
count: 1
- synonyms_set: "test-synonyms-3"
count: 2

View File

@ -429,6 +429,7 @@ module org.elasticsearch.server {
org.elasticsearch.index.mapper.MapperFeatures,
org.elasticsearch.index.IndexFeatures,
org.elasticsearch.search.SearchFeatures,
org.elasticsearch.synonyms.SynonymFeatures,
org.elasticsearch.script.ScriptFeatures,
org.elasticsearch.search.retriever.RetrieversFeatures,
org.elasticsearch.action.admin.cluster.stats.ClusterStatsFeatures,

View File

@ -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);
}
}

View File

@ -50,6 +50,9 @@ import org.elasticsearch.indices.IndexCreationException;
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;
@ -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;
@ -185,15 +190,33 @@ public class SynonymsManagementAPIService {
}
}
/**
* 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<>() {
@ -201,11 +224,11 @@ public class SynonymsManagementAPIService {
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));
}

View File

@ -14,6 +14,7 @@ org.elasticsearch.rest.action.admin.cluster.GetSnapshotsFeatures
org.elasticsearch.index.IndexFeatures
org.elasticsearch.index.mapper.MapperFeatures
org.elasticsearch.search.SearchFeatures
org.elasticsearch.synonyms.SynonymFeatures
org.elasticsearch.search.retriever.RetrieversFeatures
org.elasticsearch.script.ScriptFeatures
org.elasticsearch.cluster.routing.RoutingFeatures