diff --git a/CHANGELOG.md b/CHANGELOG.md index 890d3f2b470d4..f4c5f6e390fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fix systemd integTest on deb regarding path ownership check ([#17641](https://github.com/opensearch-project/OpenSearch/pull/17641)) - Add dfs transformation function in XContentMapValues ([#17612](https://github.com/opensearch-project/OpenSearch/pull/17612)) - Added Kinesis support as a plugin for the pull-based ingestion ([#17615](https://github.com/opensearch-project/OpenSearch/pull/17615)) +- Add FilterFieldType for developers who want to wrap MappedFieldType ([#17627](https://github.com/opensearch-project/OpenSearch/pull/17627)) - [Security Manager Replacement] Create initial Java Agent to intercept Socket::connect calls ([#17724](https://github.com/opensearch-project/OpenSearch/pull/17724)) ### Changed diff --git a/server/src/internalClusterTest/java/org/opensearch/index/mapper/FilterFieldTypeTest.java b/server/src/internalClusterTest/java/org/opensearch/index/mapper/FilterFieldTypeTest.java new file mode 100644 index 0000000000000..ad3293620d67c --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/index/mapper/FilterFieldTypeTest.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.index.mapper; + +import org.opensearch.test.OpenSearchTestCase; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class FilterFieldTypeTest extends OpenSearchTestCase { + + private static final class MethodSignature { + private final String name; + private final Class returnType; + private final Class[] parameterTypes; + + public MethodSignature(String name, Class returnType, Class[] parameterTypes) { + this.name = name; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + MethodSignature that = (MethodSignature) o; + return Objects.equals(name, that.name) + && Objects.equals(returnType, that.returnType) + && Objects.deepEquals(parameterTypes, that.parameterTypes); + } + + @Override + public int hashCode() { + return Objects.hash(name, returnType, Arrays.hashCode(parameterTypes)); + } + } + + private static final Set EXCLUDED_SIGNATURES = Set.of(new MethodSignature("typeName", String.class, new Class[0])); + + public void testAllMethodsDelegated() { + Method[] mappedFieldTypeMethods = MappedFieldType.class.getMethods(); + Method[] filterFieldTypeMethods = FilterFieldType.class.getMethods(); + + Set mappedFieldTypeMethodSignatures = new HashSet<>(); + for (Method method : mappedFieldTypeMethods) { + if (method.getDeclaringClass() == MappedFieldType.class + && Modifier.isFinal(method.getModifiers()) == false + && Modifier.isStatic(method.getModifiers()) == false) { + mappedFieldTypeMethodSignatures.add( + new MethodSignature(method.getName(), method.getReturnType(), method.getParameterTypes()) + ); + } + } + + Set filterFieldTypeMethodSignatures = new HashSet<>(); + for (Method method : filterFieldTypeMethods) { + if (method.getDeclaringClass() == FilterFieldType.class) { + filterFieldTypeMethodSignatures.add( + new MethodSignature(method.getName(), method.getReturnType(), method.getParameterTypes()) + ); + } + } + for (MethodSignature methodSignature : mappedFieldTypeMethodSignatures) { + if (filterFieldTypeMethodSignatures.contains(methodSignature)) { + assertFalse( + "Method " + methodSignature.name + " should NOT be implemented in " + FilterFieldType.class.getSimpleName(), + EXCLUDED_SIGNATURES.contains(methodSignature) + ); + } else { + assertTrue( + "Method " + methodSignature.name + " should be implemented in " + FilterFieldType.class.getSimpleName(), + EXCLUDED_SIGNATURES.contains(methodSignature) + ); + } + } + } + +} diff --git a/server/src/main/java/org/opensearch/index/mapper/FilterFieldType.java b/server/src/main/java/org/opensearch/index/mapper/FilterFieldType.java new file mode 100644 index 0000000000000..5029dd471813e --- /dev/null +++ b/server/src/main/java/org/opensearch/index/mapper/FilterFieldType.java @@ -0,0 +1,293 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.index.mapper; + +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.queries.intervals.IntervalsSource; +import org.apache.lucene.queries.spans.SpanMultiTermQueryWrapper; +import org.apache.lucene.queries.spans.SpanQuery; +import org.apache.lucene.search.MultiTermQuery; +import org.apache.lucene.search.Query; +import org.opensearch.common.annotation.PublicApi; +import org.opensearch.common.geo.ShapeRelation; +import org.opensearch.common.time.DateMathParser; +import org.opensearch.common.unit.Fuzziness; +import org.opensearch.index.analysis.NamedAnalyzer; +import org.opensearch.index.fielddata.IndexFieldData; +import org.opensearch.index.query.IntervalMode; +import org.opensearch.index.query.QueryRewriteContext; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.search.DocValueFormat; +import org.opensearch.search.lookup.SearchLookup; + +import java.io.IOException; +import java.time.ZoneId; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Wraps a {@link MappedFieldType}, delegating all methods (except typeName). + *

+ * Subclasses can extend this class to wrap an existing {@link MappedFieldType} to reuse most functionality, while + * customizing/modifying some specific behavior by overriding the relevant methods. + */ +@PublicApi(since = "3.0.0") +public abstract class FilterFieldType extends MappedFieldType { + protected final MappedFieldType delegate; + + public FilterFieldType(MappedFieldType delegate) { + super( + delegate.name(), + delegate.isSearchable(), + delegate.isStored(), + delegate.hasDocValues(), + delegate.getTextSearchInfo(), + delegate.meta() + ); + this.delegate = delegate; + } + + @Override + public ValueFetcher valueFetcher(QueryShardContext context, SearchLookup searchLookup, String format) { + return delegate.valueFetcher(context, searchLookup, format); + } + + @Override + public Query termQuery(Object value, QueryShardContext context) { + return delegate.termQuery(value, context); + } + + @Override + public String familyTypeName() { + return delegate.familyTypeName(); + } + + @Override + public String name() { + return delegate.name(); + } + + @Override + public float boost() { + return delegate.boost(); + } + + @Override + public void setBoost(float boost) { + delegate.setBoost(boost); + } + + @Override + public boolean hasDocValues() { + return delegate.hasDocValues(); + } + + @Override + public NamedAnalyzer indexAnalyzer() { + return delegate.indexAnalyzer(); + } + + @Override + public void setIndexAnalyzer(NamedAnalyzer analyzer) { + delegate.setIndexAnalyzer(analyzer); + } + + @Override + public Object valueForDisplay(Object value) { + return delegate.valueForDisplay(value); + } + + @Override + public boolean isSearchable() { + return delegate.isSearchable(); + } + + @Override + public boolean isStored() { + return delegate.isStored(); + } + + @Override + public Function pointReaderIfPossible() { + return delegate.pointReaderIfPossible(); + } + + @Override + public boolean isAggregatable() { + return delegate.isAggregatable(); + } + + @Override + public Query termQueryCaseInsensitive(Object value, QueryShardContext context) { + return delegate.termQueryCaseInsensitive(value, context); + } + + @Override + public Query termsQuery(List values, QueryShardContext context) { + return delegate.termsQuery(values, context); + } + + @Override + public Query rangeQuery( + Object lowerTerm, + Object upperTerm, + boolean includeLower, + boolean includeUpper, + ShapeRelation relation, + ZoneId timeZone, + DateMathParser parser, + QueryShardContext context + ) { + return delegate.rangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, relation, timeZone, parser, context); + } + + @Override + public Query fuzzyQuery( + Object value, + Fuzziness fuzziness, + int prefixLength, + int maxExpansions, + boolean transpositions, + MultiTermQuery.RewriteMethod method, + QueryShardContext context + ) { + return delegate.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, method, context); + } + + @Override + public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitve, QueryShardContext context) { + return delegate.prefixQuery(value, method, caseInsensitve, context); + } + + @Override + public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitve, QueryShardContext context) { + return delegate.wildcardQuery(value, method, caseInsensitve, context); + } + + @Override + public Query normalizedWildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) { + return delegate.normalizedWildcardQuery(value, method, context); + } + + @Override + public Query regexpQuery( + String value, + int syntaxFlags, + int matchFlags, + int maxDeterminizedStates, + MultiTermQuery.RewriteMethod method, + QueryShardContext context + ) { + return delegate.regexpQuery(value, syntaxFlags, matchFlags, maxDeterminizedStates, method, context); + } + + @Override + public Query existsQuery(QueryShardContext context) { + return delegate.existsQuery(context); + } + + @Override + public Query phraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements) throws IOException { + return delegate.phraseQuery(stream, slop, enablePositionIncrements); + } + + @Override + public Query phraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements, QueryShardContext context) throws IOException { + return delegate.phraseQuery(stream, slop, enablePositionIncrements, context); + } + + @Override + public Query multiPhraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements) throws IOException { + return delegate.multiPhraseQuery(stream, slop, enablePositionIncrements); + } + + @Override + public Query multiPhraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements, QueryShardContext context) + throws IOException { + return delegate.multiPhraseQuery(stream, slop, enablePositionIncrements, context); + } + + @Override + public Query phrasePrefixQuery(TokenStream stream, int slop, int maxExpansions) throws IOException { + return delegate.phrasePrefixQuery(stream, slop, maxExpansions); + } + + @Override + public Query phrasePrefixQuery(TokenStream stream, int slop, int maxExpansions, QueryShardContext context) throws IOException { + return delegate.phrasePrefixQuery(stream, slop, maxExpansions, context); + } + + @Override + public SpanQuery spanPrefixQuery(String value, SpanMultiTermQueryWrapper.SpanRewriteMethod method, QueryShardContext context) { + return delegate.spanPrefixQuery(value, method, context); + } + + @Override + public Query distanceFeatureQuery(Object origin, String pivot, float boost, QueryShardContext context) { + return delegate.distanceFeatureQuery(origin, pivot, boost, context); + } + + @Override + public IntervalsSource intervals(String query, int max_gaps, IntervalMode mode, NamedAnalyzer analyzer, boolean prefix) + throws IOException { + return delegate.intervals(query, max_gaps, mode, analyzer, prefix); + } + + @Override + public Relation isFieldWithinQuery( + IndexReader reader, + Object from, + Object to, + boolean includeLower, + boolean includeUpper, + ZoneId timeZone, + DateMathParser dateMathParser, + QueryRewriteContext context + ) throws IOException { + return delegate.isFieldWithinQuery(reader, from, to, includeLower, includeUpper, timeZone, dateMathParser, context); + } + + @Override + public boolean eagerGlobalOrdinals() { + return delegate.eagerGlobalOrdinals(); + } + + @Override + public void setEagerGlobalOrdinals(boolean eagerGlobalOrdinals) { + delegate.setEagerGlobalOrdinals(eagerGlobalOrdinals); + } + + @Override + public DocValueFormat docValueFormat(String format, ZoneId timeZone) { + return delegate.docValueFormat(format, timeZone); + } + + @Override + public Map meta() { + return delegate.meta(); + } + + @Override + public TextSearchInfo getTextSearchInfo() { + return delegate.getTextSearchInfo(); + } + + @Override + public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier searchLookup) { + return delegate.fielddataBuilder(fullyQualifiedIndexName, searchLookup); + } + + @Override + public MappedFieldType unwrap() { + return delegate.unwrap(); + } +} diff --git a/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java index 4a0740dbf1b3c..81065a88c3001 100644 --- a/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java @@ -521,4 +521,12 @@ public Map meta() { public TextSearchInfo getTextSearchInfo() { return textSearchInfo; } + + /** + * @return a concrete (unfiltered) field type, which should be the current instance + * if this is not a field type wrapper. See {@link FilterFieldType}. + */ + public MappedFieldType unwrap() { + return this; + } }