Skip to content

Commit

Permalink
Unable to retrieve multiple stored field values (elastic#106575)
Browse files Browse the repository at this point in the history
The issue happens when we try to use multiple stored fields through
the FetchFieldsPhase, which we do when using `_fields` since
we have a single shared instance of SingleFieldsVisitor per field
and document and use a shared `currentValues` array.
  • Loading branch information
salvatore-campagna authored Mar 21, 2024
1 parent ffec5f6 commit 999dcb8
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 14 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/106575.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 106575
summary: Unable to retrieve multiple stored field values
area: "Search"
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,76 @@ setup:
- match: {hits.hits.0.fields.day_of_week_letters: [T, a, d, h, r, s, u, y] }
- match: {hits.hits.0.fields.prefixed_node: [node_c] }

---
"fetch multiple stored fields":
- skip:
version: " - 8.13.99"
reason: "bug fixed in 8.14"

- do:
indices.create:
index: sensor-test
body:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
runtime:
prefixed_node:
type: keyword
script:
source: |
for (String node : params._fields.node.values) {
emit(params.prefix + node);
}
params:
prefix: node_
prefixed_region:
type: keyword
script:
source: |
for (String region : params._fields.region.values) {
emit(params.prefix + region)
}
params:
prefix: us-
properties:
timestamp:
type: date
node:
type: keyword
store: true
region:
type: keyword
store: true

- do:
bulk:
index: sensor-test
refresh: true
body: |
{"index":{}}
{"timestamp": 1516729294000, "node": "a", "region": "west-1" }
{"index":{}}
{"timestamp": 1516642894000, "node": "b", "region": "west-2" }
{"index":{}}
{"timestamp": 1516556494000, "node": "a", "region": "west-1"}
{"index":{}}
{"timestamp": 1516470094000, "node": "b", "region": "west-2"}
{"index":{}}
{"timestamp": 1516383694000, "node": "c", "region": "west-2"}
{"index":{}}
{"timestamp": 1516297294000, "node": "c", "region": "west-2"}
- do:
search:
index: sensor-test
body:
sort: timestamp
fields: [prefixed_node, prefixed_region]
- match: {hits.total.value: 6}
- match: {hits.hits.0.fields.prefixed_node: [node_c] }
- match: {hits.hits.0.fields.prefixed_region: [us-west-2]}

---
"docvalue_fields":
- do:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,20 @@ static Function<LeafReaderContext, LeafFieldLookupProvider> fromStoredFields() {
return ctx -> new LeafFieldLookupProvider() {

StoredFields storedFields;
int currentDoc = -1;
final List<Object> currentValues = new ArrayList<>(2);

@Override
public void populateFieldLookup(FieldLookup fieldLookup, int doc) throws IOException {
if (storedFields == null) {
storedFields = ctx.reader().storedFields();
}
if (doc == currentDoc) {
fieldLookup.setValues(currentValues);
} else {
currentDoc = doc;
currentValues.clear();
// TODO can we remember which fields have been loaded here and get them eagerly next time?
// likelihood is if a script is loading several fields on one doc they will load the same
// set of fields next time round
SingleFieldsVisitor visitor = new SingleFieldsVisitor(fieldLookup.fieldType(), currentValues);
storedFields.document(doc, visitor);
fieldLookup.setValues(currentValues);
}
// TODO can we remember which fields have been loaded here and get them eagerly next time?
// likelihood is if a script is loading several fields on one doc they will load the same
// set of fields next time round
final List<Object> currentValues = new ArrayList<>(2);
storedFields.document(doc, new SingleFieldsVisitor(fieldLookup.fieldType(), currentValues));
fieldLookup.setValues(currentValues);
}

};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.search.fetch.subphase;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.StoredFields;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MapperServiceTestCase;
import org.elasticsearch.search.lookup.FieldLookup;
import org.elasticsearch.search.lookup.LeafFieldLookupProvider;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.hamcrest.Matchers;

import java.io.IOException;
import java.util.List;

public class PopulateFieldLookupTests extends MapperServiceTestCase {
public void testPopulateFieldLookup() throws IOException {
final XContentBuilder mapping = createMapping();
final MapperService mapperService = createMapperService(mapping);
withLuceneIndex(mapperService, iw -> {
final Document doc = new Document();
doc.add(new StoredField("integer", 101));
doc.add(new StoredField("keyword", new BytesRef("foobar")));
iw.addDocument(doc);
}, reader -> {
final StoredFields storedFields = reader.storedFields();
final Document document = storedFields.document(0);
final List<String> documentFields = document.getFields().stream().map(IndexableField::name).toList();
assertThat(documentFields, Matchers.containsInAnyOrder("integer", "keyword"));

final IndexSearcher searcher = newSearcher(reader);
final LeafReaderContext readerContext = searcher.getIndexReader().leaves().get(0);
final LeafFieldLookupProvider provider = LeafFieldLookupProvider.fromStoredFields().apply(readerContext);
final FieldLookup integerFieldLookup = new FieldLookup(mapperService.fieldType("integer"));
final FieldLookup keywordFieldLookup = new FieldLookup(mapperService.fieldType("keyword"));
provider.populateFieldLookup(integerFieldLookup, 0);
provider.populateFieldLookup(keywordFieldLookup, 0);
assertEquals(List.of(101), integerFieldLookup.getValues());
assertEquals(List.of("foobar"), keywordFieldLookup.getValues());
});
}

private static XContentBuilder createMapping() throws IOException {
final XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("_doc");
{
mapping.startObject("properties");
{
mapping.startObject("integer");
{
mapping.field("type", "integer").field("store", "true");
}
mapping.endObject();
mapping.startObject("keyword");
{
mapping.field("type", "keyword").field("store", "true");
}
mapping.endObject();
}
mapping.endObject();

}
return mapping.endObject().endObject();
}
}

0 comments on commit 999dcb8

Please sign in to comment.