/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.search; import org.elasticsearch.common.inject.ModuleTestCase; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; import org.elasticsearch.plugins.SearchPlugin; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.BaseAggregationBuilder; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext; import org.elasticsearch.search.aggregations.bucket.significant.heuristics.ChiSquare; import org.elasticsearch.search.aggregations.bucket.significant.heuristics.SignificanceHeuristic; import org.elasticsearch.search.aggregations.bucket.significant.heuristics.SignificanceHeuristicParser; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.pipeline.AbstractPipelineAggregationBuilder; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.derivative.DerivativePipelineAggregationBuilder; import org.elasticsearch.search.aggregations.pipeline.derivative.DerivativePipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.derivative.InternalDerivative; import org.elasticsearch.search.aggregations.pipeline.movavg.models.MovAvgModel; import org.elasticsearch.search.aggregations.pipeline.movavg.models.SimpleModel; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.fetch.FetchSubPhase; import org.elasticsearch.search.fetch.subphase.ExplainFetchSubPhase; import org.elasticsearch.search.fetch.subphase.highlight.CustomHighlighter; import org.elasticsearch.search.fetch.subphase.highlight.FastVectorHighlighter; import org.elasticsearch.search.fetch.subphase.highlight.Highlighter; import org.elasticsearch.search.fetch.subphase.highlight.PlainHighlighter; import org.elasticsearch.search.fetch.subphase.highlight.PostingsHighlighter; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.suggest.CustomSuggesterSearchIT.CustomSuggestionBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder; import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; public class SearchModuleTests extends ModuleTestCase { public void testDoubleRegister() { SearchPlugin registersDupeHighlighter = new SearchPlugin() { @Override public Map<String, Highlighter> getHighlighters() { return singletonMap("plain", new PlainHighlighter()); } }; expectThrows(IllegalArgumentException.class, () -> new SearchModule(Settings.EMPTY, false, singletonList(registersDupeHighlighter))); SearchPlugin registersDupeSuggester = new SearchPlugin() { public List<SearchPlugin.SuggesterSpec<?>> getSuggesters() { return singletonList(new SuggesterSpec<>("term", TermSuggestionBuilder::new, TermSuggestionBuilder::fromXContent)); } }; expectThrows(IllegalArgumentException.class, () -> new NamedXContentRegistry( new SearchModule(Settings.EMPTY, false, singletonList(registersDupeSuggester)).getNamedXContents())); SearchPlugin registersDupeScoreFunction = new SearchPlugin() { @Override public List<ScoreFunctionSpec<?>> getScoreFunctions() { return singletonList(new ScoreFunctionSpec<>(GaussDecayFunctionBuilder.NAME, GaussDecayFunctionBuilder::new, GaussDecayFunctionBuilder.PARSER)); } }; expectThrows(IllegalArgumentException.class, () -> new NamedXContentRegistry( new SearchModule(Settings.EMPTY, false, singletonList(registersDupeScoreFunction)).getNamedXContents())); SearchPlugin registersDupeSignificanceHeuristic = new SearchPlugin() { @Override public List<SearchExtensionSpec<SignificanceHeuristic, SignificanceHeuristicParser>> getSignificanceHeuristics() { return singletonList(new SearchExtensionSpec<>(ChiSquare.NAME, ChiSquare::new, ChiSquare.PARSER)); } }; expectThrows(IllegalArgumentException.class, () -> new SearchModule(Settings.EMPTY, false, singletonList(registersDupeSignificanceHeuristic))); SearchPlugin registersDupeMovAvgModel = new SearchPlugin() { @Override public List<SearchExtensionSpec<MovAvgModel, MovAvgModel.AbstractModelParser>> getMovingAverageModels() { return singletonList(new SearchExtensionSpec<>(SimpleModel.NAME, SimpleModel::new, SimpleModel.PARSER)); } }; expectThrows(IllegalArgumentException.class, () -> new SearchModule(Settings.EMPTY, false, singletonList(registersDupeMovAvgModel))); SearchPlugin registersDupeFetchSubPhase = new SearchPlugin() { @Override public List<FetchSubPhase> getFetchSubPhases(FetchPhaseConstructionContext context) { return singletonList(new ExplainFetchSubPhase()); } }; expectThrows(IllegalArgumentException.class, () -> new SearchModule(Settings.EMPTY, false, singletonList(registersDupeFetchSubPhase))); SearchPlugin registersDupeQuery = new SearchPlugin() { public List<SearchPlugin.QuerySpec<?>> getQueries() { return singletonList(new QuerySpec<>(TermQueryBuilder.NAME, TermQueryBuilder::new, TermQueryBuilder::fromXContent)); } }; expectThrows(IllegalArgumentException.class, () -> new NamedXContentRegistry( new SearchModule(Settings.EMPTY, false, singletonList(registersDupeQuery)).getNamedXContents())); SearchPlugin registersDupeAggregation = new SearchPlugin() { public List<AggregationSpec> getAggregations() { return singletonList(new AggregationSpec(TermsAggregationBuilder.NAME, TermsAggregationBuilder::new, TermsAggregationBuilder::parse)); } }; expectThrows(IllegalArgumentException.class, () -> new NamedXContentRegistry(new SearchModule(Settings.EMPTY, false, singletonList(registersDupeAggregation)).getNamedXContents())); SearchPlugin registersDupePipelineAggregation = new SearchPlugin() { public List<PipelineAggregationSpec> getPipelineAggregations() { return singletonList(new PipelineAggregationSpec( DerivativePipelineAggregationBuilder.NAME, DerivativePipelineAggregationBuilder::new, DerivativePipelineAggregator::new, DerivativePipelineAggregationBuilder::parse) .addResultReader(InternalDerivative::new)); } }; expectThrows(IllegalArgumentException.class, () -> new NamedXContentRegistry(new SearchModule(Settings.EMPTY, false, singletonList(registersDupePipelineAggregation)).getNamedXContents())); } public void testRegisterSuggester() { SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() { @Override public List<SuggesterSpec<?>> getSuggesters() { return singletonList(new SuggesterSpec<>("custom", CustomSuggestionBuilder::new, CustomSuggestionBuilder::fromXContent)); } })); assertEquals(1, module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.match("term")).count()); assertEquals(1, module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.match("phrase")).count()); assertEquals(1, module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.match("completion")).count()); assertEquals(1, module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.match("custom")).count()); assertEquals(1, module.getNamedWriteables().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("term")).count()); assertEquals(1, module.getNamedWriteables().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("phrase")).count()); assertEquals(1, module.getNamedWriteables().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("completion")).count()); assertEquals(1, module.getNamedWriteables().stream() .filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("custom")).count()); } public void testRegisterHighlighter() { CustomHighlighter customHighlighter = new CustomHighlighter(); SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() { @Override public Map<String, Highlighter> getHighlighters() { return singletonMap("custom", customHighlighter); } })); Map<String, Highlighter> highlighters = module.getHighlighters(); assertEquals(FastVectorHighlighter.class, highlighters.get("fvh").getClass()); assertEquals(PlainHighlighter.class, highlighters.get("plain").getClass()); assertEquals(PostingsHighlighter.class, highlighters.get("postings").getClass()); assertSame(highlighters.get("custom"), customHighlighter); } public void testRegisteredQueries() throws IOException { List<String> allSupportedQueries = new ArrayList<>(); Collections.addAll(allSupportedQueries, NON_DEPRECATED_QUERIES); Collections.addAll(allSupportedQueries, DEPRECATED_QUERIES); SearchModule module = new SearchModule(Settings.EMPTY, false, emptyList()); Set<String> registeredNonDeprecated = module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(QueryBuilder.class)) .map(e -> e.name.getPreferredName()) .collect(toSet()); Set<String> registeredAll = module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(QueryBuilder.class)) .flatMap(e -> Arrays.stream(e.name.getAllNamesIncludedDeprecated())) .collect(toSet()); assertThat(registeredNonDeprecated, containsInAnyOrder(NON_DEPRECATED_QUERIES)); assertThat(registeredAll, containsInAnyOrder(allSupportedQueries.toArray(new String[0]))); } public void testRegisterAggregation() { SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() { public List<AggregationSpec> getAggregations() { return singletonList(new AggregationSpec("test", TestAggregationBuilder::new, TestAggregationBuilder::fromXContent)); } })); assertThat( module.getNamedXContents().stream() .filter(entry -> entry.categoryClass.equals(BaseAggregationBuilder.class) && entry.name.match("test")) .collect(toList()), hasSize(1)); } public void testRegisterPipelineAggregation() { SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() { public List<PipelineAggregationSpec> getPipelineAggregations() { return singletonList(new PipelineAggregationSpec("test", TestPipelineAggregationBuilder::new, TestPipelineAggregator::new, TestPipelineAggregationBuilder::fromXContent)); } })); assertThat( module.getNamedXContents().stream() .filter(entry -> entry.categoryClass.equals(BaseAggregationBuilder.class) && entry.name.match("test")) .collect(toList()), hasSize(1)); } private static final String[] NON_DEPRECATED_QUERIES = new String[] { "bool", "boosting", "common", "constant_score", "dis_max", "exists", "field_masking_span", "function_score", "fuzzy", "geo_bounding_box", "geo_distance", "geo_polygon", "geo_shape", "ids", "match", "match_all", "match_none", "match_phrase", "match_phrase_prefix", "more_like_this", "multi_match", "nested", "parent_id", "prefix", "query_string", "range", "regexp", "script", "simple_query_string", "span_containing", "span_first", "span_multi", "span_near", "span_not", "span_or", "span_term", "span_within", "term", "terms", "type", "wildcard", "wrapper" }; //add here deprecated queries to make sure we log a deprecation warnings when they are used private static final String[] DEPRECATED_QUERIES = new String[] {}; /** * Dummy test {@link AggregationBuilder} used to test registering aggregation builders. */ private static class TestAggregationBuilder extends ValuesSourceAggregationBuilder<ValuesSource, TestAggregationBuilder> { /** * Read from a stream. */ protected TestAggregationBuilder(StreamInput in) throws IOException { super(in, null, null); } @Override public String getType() { return "test"; } @Override protected void innerWriteTo(StreamOutput out) throws IOException { } @Override protected ValuesSourceAggregatorFactory<ValuesSource, ?> innerBuild(SearchContext context, ValuesSourceConfig<ValuesSource> config, AggregatorFactory<?> parent, Builder subFactoriesBuilder) throws IOException { return null; } @Override protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { return null; } @Override protected int innerHashCode() { return 0; } @Override protected boolean innerEquals(Object obj) { return false; } private static TestAggregationBuilder fromXContent(String name, QueryParseContext c) { return null; } } /** * Dummy test {@link PipelineAggregator} used to test registering aggregation builders. */ private static class TestPipelineAggregationBuilder extends AbstractPipelineAggregationBuilder<TestPipelineAggregationBuilder> { /** * Read from a stream. */ TestPipelineAggregationBuilder(StreamInput in) throws IOException { super(in, "test"); } @Override public String getWriteableName() { return "test"; } @Override protected void doWriteTo(StreamOutput out) throws IOException { } @Override protected PipelineAggregator createInternal(Map<String, Object> metaData) throws IOException { return null; } @Override protected XContentBuilder internalXContent(XContentBuilder builder, Params params) throws IOException { return null; } @Override protected int doHashCode() { return 0; } @Override protected boolean doEquals(Object obj) { return false; } private static TestPipelineAggregationBuilder fromXContent(String name, QueryParseContext c) { return null; } } /** * Dummy test {@link PipelineAggregator} used to test registering aggregation builders. */ private static class TestPipelineAggregator extends PipelineAggregator { /** * Read from a stream. */ TestPipelineAggregator(StreamInput in) throws IOException { super(in); } @Override public String getWriteableName() { return "test"; } @Override protected void doWriteTo(StreamOutput out) throws IOException { } @Override public InternalAggregation reduce(InternalAggregation aggregation, ReduceContext reduceContext) { return null; } } }