diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java index aa4daf3179adb..8f53d96cd6ab7 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java @@ -64,7 +64,7 @@ public void setUp() throws Exception { when(fieldData.load(anyObject())).thenReturn(atomicFieldData); service = new ExpressionScriptEngine(); - lookup = new SearchLookup(mapperService, ignored -> fieldData); + lookup = new SearchLookup(mapperService, (ignored, l) -> fieldData); } private FieldScript.LeafFactory compile(String expression) { diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionNumberSortScriptTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionNumberSortScriptTests.java index 007f4d608c82d..0177854c78f78 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionNumberSortScriptTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionNumberSortScriptTests.java @@ -64,7 +64,7 @@ public void setUp() throws Exception { when(fieldData.load(anyObject())).thenReturn(atomicFieldData); service = new ExpressionScriptEngine(); - lookup = new SearchLookup(mapperService, ignored -> fieldData); + lookup = new SearchLookup(mapperService, (ignored, l) -> fieldData); } private NumberSortScript.LeafFactory compile(String expression) { diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTermsSetQueryTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTermsSetQueryTests.java index fd279a9fa14e9..e4bc2db619411 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTermsSetQueryTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTermsSetQueryTests.java @@ -64,7 +64,7 @@ public void setUp() throws Exception { when(fieldData.load(anyObject())).thenReturn(atomicFieldData); service = new ExpressionScriptEngine(); - lookup = new SearchLookup(mapperService, ignored -> fieldData); + lookup = new SearchLookup(mapperService, (ignored, l) -> fieldData); } private TermsSetQueryScript.LeafFactory compile(String expression) { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java index 92816426eaf47..06c90a3c66e6c 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java @@ -25,7 +25,6 @@ import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.NumberSortScript; import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.test.ESSingleNodeTestCase; import java.util.Collections; @@ -47,22 +46,21 @@ public void testNeedsScores() { PainlessScriptEngine service = new PainlessScriptEngine(Settings.EMPTY, contexts); QueryShardContext shardContext = index.newQueryShardContext(0, null, () -> 0, null); - SearchLookup lookup = new SearchLookup(index.mapperService(), shardContext::getForField); NumberSortScript.Factory factory = service.compile(null, "1.2", NumberSortScript.CONTEXT, Collections.emptyMap()); - NumberSortScript.LeafFactory ss = factory.newFactory(Collections.emptyMap(), lookup); + NumberSortScript.LeafFactory ss = factory.newFactory(Collections.emptyMap(), shardContext.lookup()); assertFalse(ss.needs_score()); factory = service.compile(null, "doc['d'].value", NumberSortScript.CONTEXT, Collections.emptyMap()); - ss = factory.newFactory(Collections.emptyMap(), lookup); + ss = factory.newFactory(Collections.emptyMap(), shardContext.lookup()); assertFalse(ss.needs_score()); factory = service.compile(null, "1/_score", NumberSortScript.CONTEXT, Collections.emptyMap()); - ss = factory.newFactory(Collections.emptyMap(), lookup); + ss = factory.newFactory(Collections.emptyMap(), shardContext.lookup()); assertTrue(ss.needs_score()); factory = service.compile(null, "doc['d'].value * _score", NumberSortScript.CONTEXT, Collections.emptyMap()); - ss = factory.newFactory(Collections.emptyMap(), lookup); + ss = factory.newFactory(Collections.emptyMap(), shardContext.lookup()); assertTrue(ss.needs_score()); } diff --git a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java index 68ac85ee7f576..c907e85ed9bcf 100644 --- a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java +++ b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java @@ -209,7 +209,7 @@ public boolean allowExpensiveQueries() { @SuppressWarnings("unchecked") public > IFD getForField(MappedFieldType fieldType) { - return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName(), this::lookup); + return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName(), () -> lookup().forField(fieldType.name())); } public void addNamedQuery(String name, Query query) { @@ -291,7 +291,10 @@ MappedFieldType failIfFieldMappingNotFound(String name, MappedFieldType fieldMap public SearchLookup lookup() { if (lookup == null) { - lookup = new SearchLookup(getMapperService(), this::getForField); + lookup = new SearchLookup( + getMapperService(), + (ft, lookup) -> indexFieldDataService.apply(ft, fullyQualifiedIndex.getName(), lookup) + ); } return lookup; } diff --git a/server/src/main/java/org/elasticsearch/search/lookup/SearchLookup.java b/server/src/main/java/org/elasticsearch/search/lookup/SearchLookup.java index 04aef7d2e8f63..5705b1c51de13 100644 --- a/server/src/main/java/org/elasticsearch/search/lookup/SearchLookup.java +++ b/server/src/main/java/org/elasticsearch/search/lookup/SearchLookup.java @@ -24,22 +24,88 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperService; -import java.util.function.Function; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; public class SearchLookup { + /** + * The maximum "depth" of field dependencies. When a field's doc values + * depends on another field's doc values and depends on another field's + * doc values that depends on another field's doc values, etc, etc, + * it can make a very deep stack. At some point we're concerned about + * {@link StackOverflowError}. This limits that "depth". + */ + static final int MAX_FIELD_CHAIN = 30; - final DocLookup docMap; + /** + * The chain of fields for which this lookup was created used for detecting + * loops in doc values calculations. It is empty for the "top level" lookup + * created for the entire search. When a lookup is created for a field we + * make sure its name isn't in the list then add it to the end. So the + * lookup for the a field named {@code foo} will be {@code ["foo"]}. If + * that field looks up the values of a field named {@code bar} then + * {@code bar}'s lookup will contain {@code ["foo", "bar"]}. + */ + private final List fieldChain; + private final MapperService mapperService; + private final BiFunction, IndexFieldData> indexFieldDataService; + private final DocLookup docMap; + private final SourceLookup sourceLookup; + private final FieldsLookup fieldsLookup; - final SourceLookup sourceLookup; + /** + * Create the "top level" field lookup for a search request. + */ + public SearchLookup( + MapperService mapperService, + BiFunction, IndexFieldData> indexFieldDataService + ) { + this(emptyList(), mapperService, indexFieldDataService, new SourceLookup(), new FieldsLookup(mapperService)); + } - final FieldsLookup fieldsLookup; + private SearchLookup( + List fieldChain, + MapperService mapperService, + BiFunction, IndexFieldData> indexFieldDataService, + SourceLookup sourceLookup, + FieldsLookup fieldsLookup + ) { + this.fieldChain = fieldChain; + this.mapperService = mapperService;; + this.indexFieldDataService = indexFieldDataService; + this.docMap = new DocLookup(mapperService, ft -> this.indexFieldDataService.apply(ft, () -> forField(ft.name()))); + this.sourceLookup = sourceLookup; + this.fieldsLookup = fieldsLookup; + } - public SearchLookup(MapperService mapperService, Function> fieldDataLookup) { - docMap = new DocLookup(mapperService, fieldDataLookup); - sourceLookup = new SourceLookup(); - fieldsLookup = new FieldsLookup(mapperService); + /** + * Build a new lookup for a field that needs the lookup to create doc values. + * + * @throws IllegalArgumentException if it detects a loop in the fields required to build doc values + */ + public SearchLookup forField(String name) { + List newFieldChain = new ArrayList<>(fieldChain.size() + 1); + newFieldChain.addAll(fieldChain); + newFieldChain.add(name); + if (fieldChain.contains(name)) { + throw new IllegalArgumentException("loop in field definitions: " + newFieldChain); + } + if (newFieldChain.size() > MAX_FIELD_CHAIN) { + throw new IllegalArgumentException("field definition too deep: " + newFieldChain); + } + return new SearchLookup(unmodifiableList(newFieldChain), mapperService, indexFieldDataService, sourceLookup, fieldsLookup); } + /** + * Builds a new {@linkplain LeafSearchLookup} for the caller to handle + * a {@linkplain LeafReaderContext}. This lookup shares the {@code _source} + * lookup but doesn't share doc values or stored fields. + */ public LeafSearchLookup getLeafSearchLookup(LeafReaderContext context) { return new LeafSearchLookup(context, docMap.getLeafDocLookup(context), diff --git a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java index 3cdb177386c48..7ebe8dbdce9fb 100644 --- a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java +++ b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java @@ -184,7 +184,9 @@ public FetchSourceContext fetchSourceContext() { @Override public SearchLookup lookup() { - SearchLookup lookup = new SearchLookup(this.mapperService(), this::getForField); + SearchLookup lookup = new SearchLookup(this.mapperService(), (ft, l) -> { + throw new UnsupportedOperationException(); + }); lookup.source().setSource(source); return lookup; } diff --git a/server/src/test/java/org/elasticsearch/search/lookup/SearchLookupTests.java b/server/src/test/java/org/elasticsearch/search/lookup/SearchLookupTests.java new file mode 100644 index 0000000000000..84885161f7d86 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/lookup/SearchLookupTests.java @@ -0,0 +1,257 @@ +/* + * 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.lookup; + +import org.apache.lucene.document.SortedNumericDocValuesField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.Collector; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.LeafCollector; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Scorable; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.store.Directory; +import org.elasticsearch.common.CheckedFunction; +import org.elasticsearch.index.fielddata.IndexFieldData; +import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType; +import org.elasticsearch.index.fielddata.LeafFieldData; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SearchLookupTests extends ESTestCase { + public void testDocValuesLoop() { + SearchLookup lookup = new SearchLookup( + mockMapperService(), + (ft, l) -> mockIFDNeverReturns(lrc -> l.get().getLeafSearchLookup(lrc).doc().get(ft.name())) + ); + Exception e = expectThrows(IllegalArgumentException.class, () -> collect(lookup, l -> l.doc().get("a"))); + assertThat(e.getMessage(), equalTo("loop in field definitions: [a, a]")); + } + + public void testDocValuesLooseLoop() { + SearchLookup lookup = new SearchLookup(mockMapperService(), (ft, l) -> { + if (ft.name().length() > 3) { + return mockIFDNeverReturns(lrc -> l.get().forField("a")); + } + return mockIFDNeverReturns(lrc -> l.get().getLeafSearchLookup(lrc).doc().get(ft.name() + "a")); + }); + Exception e = expectThrows(IllegalArgumentException.class, () -> collect(lookup, l -> l.doc().get("a"))); + assertThat(e.getMessage(), equalTo("loop in field definitions: [a, aa, aaa, aaaa, a]")); + } + + public void testDocValuesTerminatesInLoop() { + SearchLookup lookup = new SearchLookup(mockMapperService(), (ft, l) -> { + if (ft.name().length() > 3) { + return mockIFDNeverReturns(lrc -> l.get().forField(ft.name())); + } + return mockIFDNeverReturns(lrc -> l.get().getLeafSearchLookup(lrc).doc().get(ft.name() + "a")); + }); + Exception e = expectThrows(IllegalArgumentException.class, () -> collect(lookup, l -> l.doc().get("a"))); + assertThat(e.getMessage(), equalTo("loop in field definitions: [a, aa, aaa, aaaa, aaaa]")); + } + + /** + * Test a loop that only happens on some documents. + */ + public void testDocValuesSometimesLoop() { + SearchLookup lookup = new SearchLookup(mockMapperService(), (ft, l) -> { + if (ft.name().equals("test")) { + return new SortedNumericIndexFieldData("test", NumericType.LONG); + } + if (ft.name().length() > 3) { + return mockIFDNeverReturns(lrc -> l.get().forField("a")); + } + return mockIFD(lrc -> { + LeafFieldData lfd = mock(LeafFieldData.class); + when(lfd.getScriptValues()).thenAnswer(inv -> { + return new ScriptDocValues() { + @Override + public void setNextDocId(int docId) throws IOException { + LeafSearchLookup lookup = l.get().getLeafSearchLookup(lrc); + lookup.setDocument(docId); + ScriptDocValues testDv = lookup.doc().get("test"); + if (testDv.get(0).equals(2L)) { + lookup.doc().get(ft.name() + "a"); + } + } + + @Override + public String get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + }; + }); + return lfd; + }); + }); + Exception e = expectThrows(IllegalArgumentException.class, () -> collect(lookup, l -> l.doc().get("a"))); + assertThat(e.getMessage(), equalTo("loop in field definitions: [a, aa, aaa, aaaa, a]")); + } + + public void testDeepDocValues() throws IOException { + SearchLookup lookup = new SearchLookup(mockMapperService(), (ft, l) -> { + if (ft.name().length() > 3) { + return mockIFD(lrc -> { + LeafFieldData lfd = mock(LeafFieldData.class); + when(lfd.getScriptValues()).thenAnswer(inv -> { + return new ScriptDocValues() { + @Override + public void setNextDocId(int docId) throws IOException {} + + @Override + public String get(int index) { + return "test"; + } + + @Override + public int size() { + return 1; + } + }; + }); + return lfd; + }); + } + return mockIFD(lrc -> { + LeafFieldData lfd = mock(LeafFieldData.class); + when(lfd.getScriptValues()).thenAnswer(inv -> { + return new ScriptDocValues() { + String value; + + @Override + public void setNextDocId(int docId) throws IOException { + LeafSearchLookup lookup = l.get().getLeafSearchLookup(lrc); + lookup.setDocument(docId); + ScriptDocValues next = lookup.doc().get(ft.name() + "a"); + value = next.get(0) + "a"; + } + + @Override + public String get(int index) { + return value; + } + + @Override + public int size() { + return 1; + } + }; + }); + return lfd; + }); + }); + assertThat(collect(lookup, l -> l.doc().get("a")), equalTo(List.of("testaaa", "testaaa"))); + } + + public void testDocValuesTooDeep() { + SearchLookup lookup = new SearchLookup( + mockMapperService(), + (ft, l) -> mockIFDNeverReturns(lrc -> l.get().getLeafSearchLookup(lrc).doc().get(ft.name() + "a")) + ); + Exception e = expectThrows(IllegalArgumentException.class, () -> collect(lookup, l -> l.doc().get("a"))); + assertThat(e.getMessage(), startsWith("field definition too deep: [a, aa, aaa, aaaa,")); + assertThat(e.getMessage(), endsWith(", aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]")); + } + + private IndexFieldData mockIFDNeverReturns(Consumer load) { + return mockIFD(lrc -> { + load.accept(lrc); + return null; + }); + } + + private IndexFieldData mockIFD(Function load) { + IndexFieldData ifd = mock(IndexFieldData.class); + when(ifd.load(any())).thenAnswer(inv -> load.apply((LeafReaderContext) inv.getArguments()[0])); + return ifd; + } + + private MapperService mockMapperService() { + MapperService mapperService = mock(MapperService.class); + when(mapperService.fieldType(any())).thenAnswer(inv -> { + String name = (String) inv.getArguments()[0]; + MappedFieldType ft = mock(MappedFieldType.class); + when(ft.name()).thenReturn(name); + return ft; + }); + return mapperService; + } + + private List collect(SearchLookup lookup, CheckedFunction, IOException> getDocValues) + throws IOException { + List result = new ArrayList<>(); + try (Directory directory = newDirectory(); RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory)) { + indexWriter.addDocument(List.of(new SortedNumericDocValuesField("test", 1))); + indexWriter.addDocument(List.of(new SortedNumericDocValuesField("test", 2))); + try (DirectoryReader reader = indexWriter.getReader()) { + IndexSearcher searcher = newSearcher(reader); + searcher.search(new MatchAllDocsQuery(), new Collector() { + @Override + public ScoreMode scoreMode() { + return ScoreMode.COMPLETE_NO_SCORES; + } + + @Override + public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException { + LeafSearchLookup leafLookup = lookup.getLeafSearchLookup(context); + return new LeafCollector() { + @Override + public void setScorer(Scorable scorer) throws IOException {} + + @Override + public void collect(int doc) throws IOException { + leafLookup.setDocument(doc); + ScriptDocValues sdv = getDocValues.apply(leafLookup); + sdv.setNextDocId(doc); + for (Object v : sdv) { + result.add(v.toString()); + } + } + }; + } + }); + } + return result; + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java index 58f69c2658906..f06420f3d4a02 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java @@ -316,7 +316,7 @@ public boolean shouldCache(Query query) { when(searchContext.getForField(Mockito.any(MappedFieldType.class))) .thenAnswer(invocationOnMock -> ifds.getForField((MappedFieldType) invocationOnMock.getArguments()[0])); - SearchLookup searchLookup = new SearchLookup(mapperService, ifds::getForField); + SearchLookup searchLookup = new SearchLookup(mapperService, (ft, l) -> ifds.getForField(ft, "test", l)); when(searchContext.lookup()).thenReturn(searchLookup); QueryShardContext queryShardContext = diff --git a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java index 64b55b246f2de..9e42bd3409d6c 100644 --- a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java +++ b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/index/mapper/FlatObjectFieldLookupTests.java @@ -8,8 +8,8 @@ import org.elasticsearch.Version; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.fielddata.LeafFieldData; import org.elasticsearch.index.fielddata.IndexFieldData; +import org.elasticsearch.index.fielddata.LeafFieldData; import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.search.lookup.LeafDocLookup; import org.elasticsearch.search.lookup.SearchLookup; @@ -23,7 +23,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Function; +import java.util.function.BiFunction; +import java.util.function.Supplier; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -161,7 +162,7 @@ public void testScriptDocValuesLookup() { = new KeyedFlatObjectFieldType( "field", true, true, "key2", false, Collections.emptyMap()); when(mapperService.fieldType("json.key2")).thenReturn(fieldType2); - Function> fieldDataSupplier = fieldType -> { + BiFunction, IndexFieldData> fieldDataSupplier = (fieldType, l) -> { KeyedFlatObjectFieldType keyedFieldType = (KeyedFlatObjectFieldType) fieldType; return keyedFieldType.key().equals("key1") ? fieldData1 : fieldData2; }; diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java index 266395bedd0a6..5d020c204e47c 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java @@ -77,7 +77,7 @@ protected static QueryShardContext mockContext(boolean allowExpensiveQueries, Ab when(context.allowExpensiveQueries()).thenReturn(allowExpensiveQueries); SearchLookup lookup = new SearchLookup( mapperService, - mft -> mft.fielddataBuilder("test", context::lookup).build(null, null, mapperService) + (mft, l) -> mft.fielddataBuilder("test", context::lookup).build(null, null, mapperService) ); when(context.lookup()).thenReturn(lookup); return context; diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loop.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loop.yml new file mode 100644 index 0000000000000..35d908318d782 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loop.yml @@ -0,0 +1,91 @@ +--- +setup: + - do: + indices.create: + index: sensor + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + timestamp: + type: date + temperature: + type: long + voltage: + type: double + node: + type: keyword + tight_loop: + type: runtime_script + runtime_type: keyword + script: value(doc['tight_loop'].value) + loose_loop: + type: runtime_script + runtime_type: keyword + script: value(doc['lla'].value) + lla: + type: runtime_script + runtime_type: keyword + script: value(doc['llb'].value) + llb: + type: runtime_script + runtime_type: keyword + script: value(doc['llc'].value) + llc: + type: runtime_script + runtime_type: keyword + script: value(doc['lld'].value) + lld: + type: runtime_script + runtime_type: keyword + script: value(doc['lle'].value) + lle: + type: runtime_script + runtime_type: keyword + script: value(doc['llf'].value) + llf: + type: runtime_script + runtime_type: keyword + script: value(doc['llg'].value) + llg: + type: runtime_script + runtime_type: keyword + script: value(doc['loose_loop'].value) + + - do: + bulk: + index: sensor + refresh: true + body: | + {"index":{}} + {"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"} + +--- +"tight loop": + - do: + catch: '/loop in field definitions: \[tight_loop, tight_loop\]/' + search: + index: sensor + body: + sort: timestamp + docvalue_fields: [tight_loop] + +--- +"loose loop": + - do: + catch: '/loop in field definitions: \[loose_loop, lla, llb, llc, lld, lle, llf, llg, loose_loop\]/' + search: + index: sensor + body: + sort: timestamp + docvalue_fields: [loose_loop] + + - do: + catch: '/loop in field definitions: \[lld, lle, llf, llg, loose_loop, lla, llb, llc, lld\]/' + search: + index: sensor + body: + sort: timestamp + docvalue_fields: [lld]