|
8 | 8 | */
|
9 | 9 | package org.elasticsearch.action.search;
|
10 | 10 |
|
| 11 | +import org.apache.lucene.document.Document; |
| 12 | +import org.apache.lucene.index.IndexReader; |
| 13 | +import org.apache.lucene.index.LeafReaderContext; |
| 14 | +import org.apache.lucene.search.Query; |
| 15 | +import org.apache.lucene.search.QueryCachingPolicy; |
11 | 16 | import org.apache.lucene.search.ScoreDoc;
|
12 | 17 | import org.apache.lucene.search.TopDocs;
|
13 | 18 | import org.apache.lucene.search.TotalHits;
|
| 19 | +import org.apache.lucene.store.Directory; |
| 20 | +import org.apache.lucene.tests.index.RandomIndexWriter; |
14 | 21 | import org.apache.lucene.tests.store.MockDirectoryWrapper;
|
| 22 | +import org.apache.lucene.util.Accountable; |
15 | 23 | import org.elasticsearch.action.ActionListener;
|
| 24 | +import org.elasticsearch.action.OriginalIndices; |
| 25 | +import org.elasticsearch.cluster.metadata.IndexMetadata; |
16 | 26 | import org.elasticsearch.common.UUIDs;
|
17 | 27 | import org.elasticsearch.common.breaker.CircuitBreaker;
|
18 | 28 | import org.elasticsearch.common.breaker.NoopCircuitBreaker;
|
19 | 29 | import org.elasticsearch.common.lucene.search.TopDocsAndMaxScore;
|
| 30 | +import org.elasticsearch.common.settings.Settings; |
20 | 31 | import org.elasticsearch.common.util.concurrent.AtomicArray;
|
21 | 32 | import org.elasticsearch.common.util.concurrent.EsExecutors;
|
| 33 | +import org.elasticsearch.index.IndexSettings; |
| 34 | +import org.elasticsearch.index.IndexVersion; |
| 35 | +import org.elasticsearch.index.cache.bitset.BitsetFilterCache; |
| 36 | +import org.elasticsearch.index.mapper.IdLoader; |
| 37 | +import org.elasticsearch.index.mapper.MapperMetrics; |
| 38 | +import org.elasticsearch.index.mapper.MappingLookup; |
| 39 | +import org.elasticsearch.index.query.SearchExecutionContext; |
22 | 40 | import org.elasticsearch.index.shard.ShardId;
|
23 | 41 | import org.elasticsearch.search.DocValueFormat;
|
24 | 42 | import org.elasticsearch.search.SearchHit;
|
25 | 43 | import org.elasticsearch.search.SearchHits;
|
26 | 44 | import org.elasticsearch.search.SearchPhaseResult;
|
27 | 45 | import org.elasticsearch.search.SearchShardTarget;
|
| 46 | +import org.elasticsearch.search.fetch.FetchPhase; |
28 | 47 | import org.elasticsearch.search.fetch.FetchSearchResult;
|
| 48 | +import org.elasticsearch.search.fetch.FetchSubPhase; |
| 49 | +import org.elasticsearch.search.fetch.FetchSubPhaseProcessor; |
29 | 50 | import org.elasticsearch.search.fetch.QueryFetchSearchResult;
|
30 | 51 | import org.elasticsearch.search.fetch.ShardFetchSearchRequest;
|
| 52 | +import org.elasticsearch.search.fetch.StoredFieldsSpec; |
| 53 | +import org.elasticsearch.search.internal.AliasFilter; |
| 54 | +import org.elasticsearch.search.internal.ContextIndexSearcher; |
| 55 | +import org.elasticsearch.search.internal.SearchContext; |
31 | 56 | import org.elasticsearch.search.internal.ShardSearchContextId;
|
| 57 | +import org.elasticsearch.search.internal.ShardSearchRequest; |
32 | 58 | import org.elasticsearch.search.profile.ProfileResult;
|
33 | 59 | import org.elasticsearch.search.profile.SearchProfileQueryPhaseResult;
|
34 | 60 | import org.elasticsearch.search.profile.SearchProfileShardResult;
|
35 | 61 | import org.elasticsearch.search.query.QuerySearchResult;
|
| 62 | +import org.elasticsearch.search.query.SearchTimeoutException; |
36 | 63 | import org.elasticsearch.test.ESTestCase;
|
37 | 64 | import org.elasticsearch.test.InternalAggregationTestCase;
|
| 65 | +import org.elasticsearch.test.TestSearchContext; |
38 | 66 | import org.elasticsearch.transport.Transport;
|
39 | 67 |
|
| 68 | +import java.io.IOException; |
| 69 | +import java.util.Collections; |
40 | 70 | import java.util.List;
|
41 | 71 | import java.util.Map;
|
42 | 72 | import java.util.concurrent.CountDownLatch;
|
@@ -747,4 +777,159 @@ private static void addProfiling(boolean profiled, QuerySearchResult queryResult
|
747 | 777 | private static ProfileResult fetchProfile(boolean profiled) {
|
748 | 778 | return profiled ? new ProfileResult("fetch", "fetch", Map.of(), Map.of(), FETCH_PROFILE_TIME, List.of()) : null;
|
749 | 779 | }
|
| 780 | + |
| 781 | + public void testFetchTimeoutWithPartialResults() throws IOException { |
| 782 | + Directory dir = newDirectory(); |
| 783 | + RandomIndexWriter w = new RandomIndexWriter(random(), dir); |
| 784 | + w.addDocument(new Document()); |
| 785 | + w.addDocument(new Document()); |
| 786 | + w.addDocument(new Document()); |
| 787 | + IndexReader r = w.getReader(); |
| 788 | + w.close(); |
| 789 | + ContextIndexSearcher contextIndexSearcher = createSearcher(r); |
| 790 | + try (SearchContext searchContext = createSearchContext(contextIndexSearcher, true)) { |
| 791 | + FetchPhase fetchPhase = createFetchPhase(contextIndexSearcher); |
| 792 | + fetchPhase.execute(searchContext, new int[] { 0, 1, 2 }, null); |
| 793 | + assertTrue(searchContext.queryResult().searchTimedOut()); |
| 794 | + assertEquals(1, searchContext.fetchResult().hits().getHits().length); |
| 795 | + } finally { |
| 796 | + r.close(); |
| 797 | + dir.close(); |
| 798 | + } |
| 799 | + } |
| 800 | + |
| 801 | + public void testFetchTimeoutNoPartialResults() throws IOException { |
| 802 | + Directory dir = newDirectory(); |
| 803 | + RandomIndexWriter w = new RandomIndexWriter(random(), dir); |
| 804 | + w.addDocument(new Document()); |
| 805 | + w.addDocument(new Document()); |
| 806 | + w.addDocument(new Document()); |
| 807 | + IndexReader r = w.getReader(); |
| 808 | + w.close(); |
| 809 | + ContextIndexSearcher contextIndexSearcher = createSearcher(r); |
| 810 | + |
| 811 | + try (SearchContext searchContext = createSearchContext(contextIndexSearcher, false)) { |
| 812 | + FetchPhase fetchPhase = createFetchPhase(contextIndexSearcher); |
| 813 | + expectThrows(SearchTimeoutException.class, () -> fetchPhase.execute(searchContext, new int[] { 0, 1, 2 }, null)); |
| 814 | + assertNull(searchContext.fetchResult().hits()); |
| 815 | + } finally { |
| 816 | + r.close(); |
| 817 | + dir.close(); |
| 818 | + } |
| 819 | + } |
| 820 | + |
| 821 | + private static ContextIndexSearcher createSearcher(IndexReader reader) throws IOException { |
| 822 | + return new ContextIndexSearcher(reader, null, null, new QueryCachingPolicy() { |
| 823 | + @Override |
| 824 | + public void onUse(Query query) {} |
| 825 | + |
| 826 | + @Override |
| 827 | + public boolean shouldCache(Query query) { |
| 828 | + return false; |
| 829 | + } |
| 830 | + }, randomBoolean()); |
| 831 | + } |
| 832 | + |
| 833 | + private static FetchPhase createFetchPhase(ContextIndexSearcher contextIndexSearcher) { |
| 834 | + return new FetchPhase(Collections.singletonList(fetchContext -> new FetchSubPhaseProcessor() { |
| 835 | + boolean processCalledOnce = false; |
| 836 | + |
| 837 | + @Override |
| 838 | + public void setNextReader(LeafReaderContext readerContext) {} |
| 839 | + |
| 840 | + @Override |
| 841 | + public void process(FetchSubPhase.HitContext hitContext) { |
| 842 | + // we throw only once one doc has been fetched, so we can test partial results are returned |
| 843 | + if (processCalledOnce) { |
| 844 | + contextIndexSearcher.throwTimeExceededException(); |
| 845 | + } else { |
| 846 | + processCalledOnce = true; |
| 847 | + } |
| 848 | + } |
| 849 | + |
| 850 | + @Override |
| 851 | + public StoredFieldsSpec storedFieldsSpec() { |
| 852 | + return StoredFieldsSpec.NO_REQUIREMENTS; |
| 853 | + } |
| 854 | + })); |
| 855 | + } |
| 856 | + |
| 857 | + private static SearchContext createSearchContext(ContextIndexSearcher contextIndexSearcher, boolean allowPartialResults) { |
| 858 | + IndexSettings indexSettings = new IndexSettings( |
| 859 | + IndexMetadata.builder("index") |
| 860 | + .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())) |
| 861 | + .numberOfShards(1) |
| 862 | + .numberOfReplicas(0) |
| 863 | + .creationDate(System.currentTimeMillis()) |
| 864 | + .build(), |
| 865 | + Settings.EMPTY |
| 866 | + ); |
| 867 | + BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(indexSettings, new BitsetFilterCache.Listener() { |
| 868 | + @Override |
| 869 | + public void onCache(ShardId shardId, Accountable accountable) { |
| 870 | + |
| 871 | + } |
| 872 | + |
| 873 | + @Override |
| 874 | + public void onRemoval(ShardId shardId, Accountable accountable) { |
| 875 | + |
| 876 | + } |
| 877 | + }); |
| 878 | + |
| 879 | + SearchExecutionContext searchExecutionContext = new SearchExecutionContext( |
| 880 | + 0, |
| 881 | + 0, |
| 882 | + indexSettings, |
| 883 | + bitsetFilterCache, |
| 884 | + null, |
| 885 | + null, |
| 886 | + MappingLookup.EMPTY, |
| 887 | + null, |
| 888 | + null, |
| 889 | + null, |
| 890 | + null, |
| 891 | + null, |
| 892 | + null, |
| 893 | + null, |
| 894 | + null, |
| 895 | + null, |
| 896 | + null, |
| 897 | + null, |
| 898 | + Collections.emptyMap(), |
| 899 | + null, |
| 900 | + MapperMetrics.NOOP |
| 901 | + ); |
| 902 | + TestSearchContext searchContext = new TestSearchContext(searchExecutionContext, null, contextIndexSearcher) { |
| 903 | + private final FetchSearchResult fetchSearchResult = new FetchSearchResult(); |
| 904 | + private final ShardSearchRequest request = new ShardSearchRequest( |
| 905 | + OriginalIndices.NONE, |
| 906 | + new SearchRequest().allowPartialSearchResults(allowPartialResults), |
| 907 | + new ShardId("index", "indexUUID", 0), |
| 908 | + 0, |
| 909 | + 1, |
| 910 | + AliasFilter.EMPTY, |
| 911 | + 1f, |
| 912 | + 0L, |
| 913 | + null |
| 914 | + ); |
| 915 | + |
| 916 | + @Override |
| 917 | + public IdLoader newIdLoader() { |
| 918 | + return new IdLoader.StoredIdLoader(); |
| 919 | + } |
| 920 | + |
| 921 | + @Override |
| 922 | + public FetchSearchResult fetchResult() { |
| 923 | + return fetchSearchResult; |
| 924 | + } |
| 925 | + |
| 926 | + @Override |
| 927 | + public ShardSearchRequest request() { |
| 928 | + return request; |
| 929 | + } |
| 930 | + }; |
| 931 | + searchContext.addReleasable(searchContext.fetchResult()::decRef); |
| 932 | + searchContext.setTask(new SearchShardTask(-1, "type", "action", "description", null, Collections.emptyMap())); |
| 933 | + return searchContext; |
| 934 | + } |
750 | 935 | }
|
0 commit comments