Unable to retrieve multiple stored field values (#106575)

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.
This commit is contained in:
Salvatore Campagna 2024-03-21 13:35:17 +01:00 committed by GitHub
parent ffec5f6fce
commit 999dcb8d08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 159 additions and 14 deletions

View File

@ -0,0 +1,5 @@
pr: 106575
summary: Unable to retrieve multiple stored field values
area: "Search"
type: bug
issues: []

View File

@ -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:

View File

@ -34,27 +34,20 @@ public interface LeafFieldLookupProvider {
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);
}
};
}

View File

@ -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();
}
}