فهرست منبع

Create .synonyms system index (#95548)

Create .synonyms system index that is exposed
under es.synonyms_api_feature_flag.

This is the first task for creating Synonyms API management,
where synonyms will be stored in the .synonyms system index.

Relates to #38523
Mayya Sharipova 2 سال پیش
والد
کامیت
07adeb0901

+ 5 - 0
docs/changelog/95548.yaml

@@ -0,0 +1,5 @@
+pr: 95548
+summary: Create `.synonyms` system index
+area: Analysis
+type: enhancement
+issues: []

+ 6 - 0
docs/reference/migration/apis/feature-migration.asciidoc

@@ -114,6 +114,12 @@ Example response:
       "migration_status" : "NO_MIGRATION_NEEDED",
       "indices" : [ ]
     },
+    {
+      "feature_name" : "synonyms",
+      "minimum_index_version" : "8.8.0",
+      "migration_status" : "NO_MIGRATION_NEEDED",
+      "indices" : [ ]
+    },
     {
       "feature_name" : "tasks",
       "minimum_index_version" : "8.0.0",

+ 23 - 2
modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/CommonAnalysisPlugin.java

@@ -100,6 +100,7 @@ import org.apache.lucene.analysis.tr.TurkishAnalyzer;
 import org.apache.lucene.analysis.util.ElisionFilter;
 import org.apache.lucene.util.SetOnce;
 import org.elasticsearch.Version;
+import org.elasticsearch.analysis.common.synonyms.SynonymsManagementAPIService;
 import org.elasticsearch.client.internal.Client;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.routing.allocation.AllocationService;
@@ -120,12 +121,14 @@ import org.elasticsearch.index.analysis.PreConfiguredTokenFilter;
 import org.elasticsearch.index.analysis.PreConfiguredTokenizer;
 import org.elasticsearch.index.analysis.TokenFilterFactory;
 import org.elasticsearch.index.analysis.TokenizerFactory;
+import org.elasticsearch.indices.SystemIndexDescriptor;
 import org.elasticsearch.indices.analysis.AnalysisModule.AnalysisProvider;
 import org.elasticsearch.indices.analysis.PreBuiltCacheFactory.CachingStrategy;
 import org.elasticsearch.lucene.analysis.miscellaneous.DisableGraphAttribute;
 import org.elasticsearch.plugins.AnalysisPlugin;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.plugins.ScriptPlugin;
+import org.elasticsearch.plugins.SystemIndexPlugin;
 import org.elasticsearch.repositories.RepositoriesService;
 import org.elasticsearch.script.ScriptContext;
 import org.elasticsearch.script.ScriptService;
@@ -146,8 +149,7 @@ import java.util.function.Supplier;
 
 import static org.elasticsearch.plugins.AnalysisPlugin.requiresAnalysisSettings;
 
-public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin, ScriptPlugin {
-
+public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin, ScriptPlugin, SystemIndexPlugin {
     private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(CommonAnalysisPlugin.class);
 
     private final SetOnce<ScriptService> scriptServiceHolder = new SetOnce<>();
@@ -652,4 +654,23 @@ public class CommonAnalysisPlugin extends Plugin implements AnalysisPlugin, Scri
 
         return tokenizers;
     }
+
+    @Override
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
+        if (SynonymsManagementAPIService.isEnabled()) {
+            return Collections.singletonList(SynonymsManagementAPIService.getSystemIndexDescriptor());
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public String getFeatureName() {
+        return "synonyms";
+    }
+
+    @Override
+    public String getFeatureDescription() {
+        return "Index for storing synonyms managed through APIs";
+    }
 }

+ 85 - 0
modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/synonyms/SynonymsManagementAPIService.java

@@ -0,0 +1,85 @@
+/*
+ * 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 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 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.analysis.common.synonyms;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.util.FeatureFlag;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
+import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
+
+public class SynonymsManagementAPIService {
+    private static final FeatureFlag SYNONYMS_API_FEATURE_FLAG = new FeatureFlag("synonyms_api");
+    public static final String SYNONYMS_INDEX = ".synonyms";
+    public static final String SYNONYMS_ORIGIN = "synonyms";
+
+    public static SystemIndexDescriptor getSystemIndexDescriptor() {
+        return SystemIndexDescriptor.builder()
+            .setIndexPattern(SYNONYMS_INDEX + "*")
+            .setDescription("Synonyms index for synonyms managed through APIs")
+            .setPrimaryIndex(SYNONYMS_INDEX)
+            .setMappings(mappings())
+            .setSettings(settings())
+            .setVersionMetaKey("version")
+            .setOrigin(SYNONYMS_ORIGIN)
+            .build();
+    }
+
+    private static XContentBuilder mappings() {
+        try {
+            XContentBuilder builder = jsonBuilder();
+            builder.startObject();
+            {
+                builder.startObject(SINGLE_MAPPING_NAME);
+                {
+                    builder.startObject("_meta");
+                    {
+                        builder.field("version", Version.CURRENT.toString());
+                    }
+                    builder.endObject();
+                    builder.field("dynamic", "strict");
+                    builder.startObject("properties");
+                    {
+                        builder.startObject("synonyms");
+                        {
+                            builder.field("type", "object");
+                            builder.field("enabled", "false");
+                        }
+                        builder.endObject();
+                    }
+                    builder.endObject();
+                }
+                builder.endObject();
+            }
+            builder.endObject();
+            return builder;
+        } catch (IOException e) {
+            throw new UncheckedIOException("Failed to build mappings for " + SYNONYMS_INDEX, e);
+        }
+    }
+
+    static Settings settings() {
+        return Settings.builder()
+            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+            .put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-all")
+            .build();
+    }
+
+    public static boolean isEnabled() {
+        return SYNONYMS_API_FEATURE_FLAG.isEnabled();
+    }
+}

+ 14 - 0
modules/analysis-common/src/test/java/org/elasticsearch/analysis/common/CommonAnalysisPluginTests.java

@@ -14,13 +14,17 @@ import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.index.analysis.TokenizerFactory;
+import org.elasticsearch.indices.SystemIndexDescriptor;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.IndexSettingsModule;
 import org.elasticsearch.test.VersionUtils;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
+import static org.elasticsearch.analysis.common.synonyms.SynonymsManagementAPIService.SYNONYMS_INDEX;
+
 public class CommonAnalysisPluginTests extends ESTestCase {
 
     /**
@@ -220,6 +224,16 @@ public class CommonAnalysisPluginTests extends ESTestCase {
         );
     }
 
+    public void testSystemSynonymsIndexName() {
+        assertEquals(
+            List.of(SYNONYMS_INDEX),
+            new CommonAnalysisPlugin().getSystemIndexDescriptors(Settings.EMPTY)
+                .stream()
+                .map(SystemIndexDescriptor::getPrimaryIndex)
+                .toList()
+        );
+    }
+
     public void doTestPrebuiltTokenizerDeprecation(String deprecatedName, String replacement, Version version, boolean expectWarning)
         throws IOException {
         final Settings settings = Settings.builder()