/* * 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.collapse; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Query; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.index.mapper.KeywordFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.NumberFieldMapper; import org.elasticsearch.index.query.InnerHitBuilder; import org.elasticsearch.index.query.InnerHitBuilderTests; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.SearchContextException; import org.elasticsearch.search.SearchModule; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; import java.io.IOException; import static java.util.Collections.emptyList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CollapseBuilderTests extends AbstractWireSerializingTestCase { private static NamedWriteableRegistry namedWriteableRegistry; private static NamedXContentRegistry xContentRegistry; @BeforeClass public static void init() { SearchModule searchModule = new SearchModule(Settings.EMPTY, false, emptyList()); namedWriteableRegistry = new NamedWriteableRegistry(searchModule.getNamedWriteables()); xContentRegistry = new NamedXContentRegistry(searchModule.getNamedXContents()); } @AfterClass public static void afterClass() throws Exception { namedWriteableRegistry = null; xContentRegistry = null; } public static CollapseBuilder randomCollapseBuilder() { CollapseBuilder builder = new CollapseBuilder(randomAlphaOfLength(10)); builder.setMaxConcurrentGroupRequests(randomIntBetween(1, 48)); if (randomBoolean()) { InnerHitBuilder innerHit = InnerHitBuilderTests.randomInnerHits(false, false); builder.setInnerHits(innerHit); } return builder; } @Override protected Writeable createTestInstance() { return randomCollapseBuilder(); } @Override protected Writeable.Reader<CollapseBuilder> instanceReader() { return CollapseBuilder::new; } @Override protected NamedWriteableRegistry getNamedWriteableRegistry() { return namedWriteableRegistry; } @Override protected NamedXContentRegistry xContentRegistry() { return xContentRegistry; } private SearchContext mockSearchContext() { SearchContext context = mock(SearchContext.class); QueryShardContext shardContext = mock(QueryShardContext.class); when(context.getQueryShardContext()).thenReturn(shardContext); when(context.scrollContext()).thenReturn(null); when(context.rescore()).thenReturn(null); when(context.searchAfter()).thenReturn(null); return context; } public void testBuild() throws IOException { Directory dir = new RAMDirectory(); try (IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())))) { writer.commit(); } SearchContext searchContext = mockSearchContext(); try (IndexReader reader = DirectoryReader.open(dir)) { when(searchContext.getQueryShardContext().getIndexReader()).thenReturn(reader); MappedFieldType numberFieldType = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG); MappedFieldType keywordFieldType = new KeywordFieldMapper.KeywordFieldType(); for (MappedFieldType fieldType : new MappedFieldType[] {numberFieldType, keywordFieldType}) { fieldType.setName("field"); fieldType.setHasDocValues(true); when(searchContext.getQueryShardContext().fieldMapper("field")).thenReturn(fieldType); CollapseBuilder builder = new CollapseBuilder("field"); CollapseContext collapseContext = builder.build(searchContext); assertEquals(collapseContext.getFieldType(), fieldType); fieldType.setIndexOptions(IndexOptions.NONE); collapseContext = builder.build(searchContext); assertEquals(collapseContext.getFieldType(), fieldType); fieldType.setHasDocValues(false); SearchContextException exc = expectThrows(SearchContextException.class, () -> builder.build(searchContext)); assertEquals(exc.getMessage(), "cannot collapse on field `field` without `doc_values`"); fieldType.setHasDocValues(true); builder.setInnerHits(new InnerHitBuilder()); exc = expectThrows(SearchContextException.class, () -> builder.build(searchContext)); assertEquals(exc.getMessage(), "cannot expand `inner_hits` for collapse field `field`, " + "only indexed field can retrieve `inner_hits`"); } } } public void testBuildWithSearchContextExceptions() throws IOException { SearchContext context = mockSearchContext(); { CollapseBuilder builder = new CollapseBuilder("unknown_field"); SearchContextException exc = expectThrows(SearchContextException.class, () -> builder.build(context)); assertEquals(exc.getMessage(), "no mapping found for `unknown_field` in order to collapse on"); } { MappedFieldType fieldType = new MappedFieldType() { @Override public MappedFieldType clone() { return null; } @Override public String typeName() { return null; } @Override public Query termQuery(Object value, QueryShardContext context) { return null; } }; fieldType.setName("field"); fieldType.setHasDocValues(true); when(context.getQueryShardContext().fieldMapper("field")).thenReturn(fieldType); CollapseBuilder builder = new CollapseBuilder("field"); SearchContextException exc = expectThrows(SearchContextException.class, () -> builder.build(context)); assertEquals(exc.getMessage(), "unknown type for collapse field `field`, only keywords and numbers are accepted"); } } }