From 5a3e3c085f4983fecd0ac48845e61c5db4a1345e Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Fri, 11 Jul 2025 08:07:11 -0400 Subject: [PATCH] [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 --- docs/changelog/131032.yaml | 5 +++ .../test/synonyms/40_synonyms_sets_get.yml | 30 ++++++++++++++++ server/src/main/java/module-info.java | 1 + .../synonyms/SynonymFeatures.java | 24 +++++++++++++ .../SynonymsManagementAPIService.java | 35 +++++++++++++++---- ...lasticsearch.features.FeatureSpecification | 1 + 6 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 docs/changelog/131032.yaml create mode 100644 server/src/main/java/org/elasticsearch/synonyms/SynonymFeatures.java diff --git a/docs/changelog/131032.yaml b/docs/changelog/131032.yaml new file mode 100644 index 0000000000000..c7cbc3af0f9c7 --- /dev/null +++ b/docs/changelog/131032.yaml @@ -0,0 +1,5 @@ +pr: 131032 +summary: "Fix: `GET _synonyms` returns synonyms with empty rules" +area: Relevance +type: bug +issues: [] diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml index 24f776ba4b210..912c126fd67a6 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml @@ -178,3 +178,33 @@ teardown: - 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 + diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index f6cfe9bafe1a1..6dd5f2ff8a8d6 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -432,6 +432,7 @@ org.elasticsearch.index.IndexFeatures, org.elasticsearch.ingest.IngestGeoIpFeatures, org.elasticsearch.search.SearchFeatures, + org.elasticsearch.synonyms.SynonymFeatures, org.elasticsearch.script.ScriptFeatures, org.elasticsearch.search.retriever.RetrieversFeatures, org.elasticsearch.reservedstate.service.FileSettingsFeatures, diff --git a/server/src/main/java/org/elasticsearch/synonyms/SynonymFeatures.java b/server/src/main/java/org/elasticsearch/synonyms/SynonymFeatures.java new file mode 100644 index 0000000000000..b42143ed899a3 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/synonyms/SynonymFeatures.java @@ -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 getTestFeatures() { + return Set.of(RETURN_EMPTY_SYNONYM_SETS); + } +} diff --git a/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java b/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java index b6cb475e848e1..9120cb3da869a 100644 --- a/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java +++ b/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java @@ -45,6 +45,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; @@ -89,6 +92,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; private final int maxSynonymsSets; @@ -180,15 +185,33 @@ 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> 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<>() { @@ -196,11 +219,11 @@ public void getSynonymsSets(int from, int size, ActionListener 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)); } diff --git a/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification b/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification index 3f87d555b990e..7caa05ef29312 100644 --- a/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification +++ b/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification @@ -20,6 +20,7 @@ org.elasticsearch.index.IndexFeatures org.elasticsearch.index.mapper.MapperFeatures org.elasticsearch.ingest.IngestGeoIpFeatures org.elasticsearch.search.SearchFeatures +org.elasticsearch.synonyms.SynonymFeatures org.elasticsearch.search.retriever.RetrieversFeatures org.elasticsearch.script.ScriptFeatures org.elasticsearch.reservedstate.service.FileSettingsFeatures