|
@@ -10,14 +10,25 @@ package org.elasticsearch.search.fetch.subphase;
|
|
|
|
|
|
import org.apache.lucene.index.LeafReaderContext;
|
|
|
import org.elasticsearch.common.document.DocumentField;
|
|
|
-import org.elasticsearch.search.SearchHit;
|
|
|
+import org.elasticsearch.index.mapper.IdFieldMapper;
|
|
|
+import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
|
|
+import org.elasticsearch.index.mapper.LegacyTypeFieldMapper;
|
|
|
+import org.elasticsearch.index.mapper.MappedFieldType;
|
|
|
+import org.elasticsearch.index.mapper.RoutingFieldMapper;
|
|
|
+import org.elasticsearch.index.mapper.SourceFieldMapper;
|
|
|
+import org.elasticsearch.index.query.SearchExecutionContext;
|
|
|
import org.elasticsearch.search.fetch.FetchContext;
|
|
|
import org.elasticsearch.search.fetch.FetchSubPhase;
|
|
|
import org.elasticsearch.search.fetch.FetchSubPhaseProcessor;
|
|
|
+import org.elasticsearch.search.fetch.StoredFieldsContext;
|
|
|
import org.elasticsearch.search.fetch.StoredFieldsSpec;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
/**
|
|
|
* A fetch sub-phase for high-level field retrieval. Given a list of fields, it
|
|
@@ -25,33 +36,77 @@ import java.util.Map;
|
|
|
* and returns them as document fields.
|
|
|
*/
|
|
|
public final class FetchFieldsPhase implements FetchSubPhase {
|
|
|
+
|
|
|
+ private static final List<FieldAndFormat> DEFAULT_METADATA_FIELDS = List.of(
|
|
|
+ new FieldAndFormat(IgnoredFieldMapper.NAME, null),
|
|
|
+ new FieldAndFormat(RoutingFieldMapper.NAME, null),
|
|
|
+ new FieldAndFormat(LegacyTypeFieldMapper.NAME, null)
|
|
|
+ );
|
|
|
+
|
|
|
@Override
|
|
|
public FetchSubPhaseProcessor getProcessor(FetchContext fetchContext) {
|
|
|
- FetchFieldsContext fetchFieldsContext = fetchContext.fetchFieldsContext();
|
|
|
- if (fetchFieldsContext == null) {
|
|
|
+ final FetchFieldsContext fetchFieldsContext = fetchContext.fetchFieldsContext();
|
|
|
+ final StoredFieldsContext storedFieldsContext = fetchContext.storedFieldsContext();
|
|
|
+
|
|
|
+ boolean fetchStoredFields = storedFieldsContext != null && storedFieldsContext.fetchFields();
|
|
|
+ if (fetchFieldsContext == null && fetchStoredFields == false) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- FieldFetcher fieldFetcher = FieldFetcher.create(fetchContext.getSearchExecutionContext(), fetchFieldsContext.fields());
|
|
|
+ final SearchExecutionContext searchExecutionContext = fetchContext.getSearchExecutionContext();
|
|
|
+ final FieldFetcher fieldFetcher = fetchFieldsContext == null ? null
|
|
|
+ : fetchFieldsContext.fields() == null ? null
|
|
|
+ : fetchFieldsContext.fields().isEmpty() ? null
|
|
|
+ : FieldFetcher.create(searchExecutionContext, fetchFieldsContext.fields());
|
|
|
|
|
|
+ final FieldFetcher metadataFieldFetcher;
|
|
|
+ if (storedFieldsContext != null
|
|
|
+ && storedFieldsContext.fieldNames() != null
|
|
|
+ && storedFieldsContext.fieldNames().isEmpty() == false) {
|
|
|
+ final Set<FieldAndFormat> metadataFields = new HashSet<>(DEFAULT_METADATA_FIELDS);
|
|
|
+ for (final String storedField : storedFieldsContext.fieldNames()) {
|
|
|
+ final Set<String> matchingFieldNames = searchExecutionContext.getMatchingFieldNames(storedField);
|
|
|
+ for (final String matchingFieldName : matchingFieldNames) {
|
|
|
+ if (SourceFieldMapper.NAME.equals(matchingFieldName) || IdFieldMapper.NAME.equals(matchingFieldName)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ final MappedFieldType fieldType = searchExecutionContext.getFieldType(matchingFieldName);
|
|
|
+ // NOTE: checking if the field is stored is required for backward compatibility reasons and to make
|
|
|
+ // sure we also handle here stored fields requested via `stored_fields`, which was previously a
|
|
|
+ // responsibility of StoredFieldsPhase.
|
|
|
+ if (searchExecutionContext.isMetadataField(matchingFieldName) && fieldType.isStored()) {
|
|
|
+ metadataFields.add(new FieldAndFormat(matchingFieldName, null));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ metadataFieldFetcher = FieldFetcher.create(searchExecutionContext, metadataFields);
|
|
|
+ } else {
|
|
|
+ metadataFieldFetcher = FieldFetcher.create(searchExecutionContext, DEFAULT_METADATA_FIELDS);
|
|
|
+ }
|
|
|
return new FetchSubPhaseProcessor() {
|
|
|
@Override
|
|
|
public void setNextReader(LeafReaderContext readerContext) {
|
|
|
- fieldFetcher.setNextReader(readerContext);
|
|
|
+ if (fieldFetcher != null) {
|
|
|
+ fieldFetcher.setNextReader(readerContext);
|
|
|
+ }
|
|
|
+ metadataFieldFetcher.setNextReader(readerContext);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public StoredFieldsSpec storedFieldsSpec() {
|
|
|
- return fieldFetcher.storedFieldsSpec();
|
|
|
+ if (fieldFetcher != null) {
|
|
|
+ return fieldFetcher.storedFieldsSpec();
|
|
|
+ }
|
|
|
+ return StoredFieldsSpec.NO_REQUIREMENTS;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void process(HitContext hitContext) throws IOException {
|
|
|
- Map<String, DocumentField> documentFields = fieldFetcher.fetch(hitContext.source(), hitContext.docId());
|
|
|
- SearchHit hit = hitContext.hit();
|
|
|
- for (Map.Entry<String, DocumentField> entry : documentFields.entrySet()) {
|
|
|
- hit.setDocumentField(entry.getKey(), entry.getValue());
|
|
|
- }
|
|
|
+ final Map<String, DocumentField> fields = fieldFetcher != null
|
|
|
+ ? fieldFetcher.fetch(hitContext.source(), hitContext.docId())
|
|
|
+ : Collections.emptyMap();
|
|
|
+ final Map<String, DocumentField> metadataFields = metadataFieldFetcher.fetch(hitContext.source(), hitContext.docId());
|
|
|
+ hitContext.hit().addDocumentFields(fields, metadataFields);
|
|
|
}
|
|
|
};
|
|
|
}
|