/* * Licensed to ElasticSearch and Shay Banon 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.fetch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.IndexReader; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.lucene.document.ResetFieldSelector; import org.elasticsearch.index.Index; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMappers; import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.internal.SourceFieldMapper; import org.elasticsearch.index.mapper.internal.UidFieldMapper; import org.elasticsearch.index.mapper.selector.AllButSourceFieldSelector; import org.elasticsearch.index.mapper.selector.FieldMappersFieldSelector; import org.elasticsearch.index.mapper.selector.UidAndSourceFieldSelector; import org.elasticsearch.index.mapper.selector.UidFieldSelector; import org.elasticsearch.indices.TypeMissingException; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchParseElement; import org.elasticsearch.search.SearchPhase; import org.elasticsearch.search.fetch.explain.ExplainFetchSubPhase; import org.elasticsearch.search.fetch.matchedfilters.MatchedFiltersFetchSubPhase; import org.elasticsearch.search.fetch.partial.PartialFieldsFetchSubPhase; import org.elasticsearch.search.fetch.script.ScriptFieldsFetchSubPhase; import org.elasticsearch.search.fetch.version.VersionFetchSubPhase; import org.elasticsearch.search.highlight.HighlightPhase; import org.elasticsearch.search.internal.InternalSearchHit; import org.elasticsearch.search.internal.InternalSearchHitField; import org.elasticsearch.search.internal.InternalSearchHits; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * */ public class FetchPhase implements SearchPhase { private final FetchSubPhase[] fetchSubPhases; @Inject public FetchPhase(HighlightPhase highlightPhase, ScriptFieldsFetchSubPhase scriptFieldsPhase, PartialFieldsFetchSubPhase partialFieldsPhase, MatchedFiltersFetchSubPhase matchFiltersPhase, ExplainFetchSubPhase explainPhase, VersionFetchSubPhase versionPhase) { this.fetchSubPhases = new FetchSubPhase[]{scriptFieldsPhase, partialFieldsPhase, matchFiltersPhase, explainPhase, highlightPhase, versionPhase}; } @Override public Map<String, ? extends SearchParseElement> parseElements() { ImmutableMap.Builder<String, SearchParseElement> parseElements = ImmutableMap.builder(); parseElements.put("fields", new FieldsParseElement()); for (FetchSubPhase fetchSubPhase : fetchSubPhases) { parseElements.putAll(fetchSubPhase.parseElements()); } return parseElements.build(); } @Override public void preProcess(SearchContext context) { } public void execute(SearchContext context) { ResetFieldSelector fieldSelector; List<String> extractFieldNames = null; boolean sourceRequested = false; if (!context.hasFieldNames()) { if (context.hasPartialFields()) { // partial fields need the source, so fetch it, but don't return it fieldSelector = new UidAndSourceFieldSelector(); sourceRequested = false; } else if (context.hasScriptFields()) { // we ask for script fields, and no field names, don't load the source fieldSelector = UidFieldSelector.INSTANCE; sourceRequested = false; } else { fieldSelector = new UidAndSourceFieldSelector(); sourceRequested = true; } } else if (context.fieldNames().isEmpty()) { fieldSelector = UidFieldSelector.INSTANCE; sourceRequested = false; } else { boolean loadAllStored = false; FieldMappersFieldSelector fieldSelectorMapper = null; for (String fieldName : context.fieldNames()) { if (fieldName.equals("*")) { loadAllStored = true; continue; } if (fieldName.equals(SourceFieldMapper.NAME)) { sourceRequested = true; continue; } FieldMappers x = context.smartNameFieldMappers(fieldName); if (x != null && x.mapper().stored()) { if (fieldSelectorMapper == null) { fieldSelectorMapper = new FieldMappersFieldSelector(); } fieldSelectorMapper.add(x); } else { if (extractFieldNames == null) { extractFieldNames = Lists.newArrayList(); } extractFieldNames.add(fieldName); } } if (loadAllStored) { if (sourceRequested || extractFieldNames != null) { fieldSelector = null; // load everything, including _source } else { fieldSelector = AllButSourceFieldSelector.INSTANCE; } } else if (fieldSelectorMapper != null) { // we are asking specific stored fields, just add the UID and be done fieldSelectorMapper.add(UidFieldMapper.NAME); if (extractFieldNames != null || sourceRequested) { fieldSelectorMapper.add(SourceFieldMapper.NAME); } fieldSelector = fieldSelectorMapper; } else if (extractFieldNames != null || sourceRequested) { fieldSelector = new UidAndSourceFieldSelector(); } else { fieldSelector = UidFieldSelector.INSTANCE; } } InternalSearchHit[] hits = new InternalSearchHit[context.docIdsToLoadSize()]; for (int index = 0; index < context.docIdsToLoadSize(); index++) { int docId = context.docIdsToLoad()[context.docIdsToLoadFrom() + index]; Document doc = loadDocument(context, fieldSelector, docId); Uid uid = extractUid(context, doc, fieldSelector); DocumentMapper documentMapper = context.mapperService().documentMapper(uid.type()); if (documentMapper == null) { throw new TypeMissingException(new Index(context.shardTarget().index()), uid.type(), "failed to find type loaded for doc [" + uid.id() + "]"); } byte[] source = extractSource(doc, documentMapper); // get the version InternalSearchHit searchHit = new InternalSearchHit(docId, uid.id(), uid.type(), sourceRequested ? source : null, null); hits[index] = searchHit; for (Object oField : doc.getFields()) { Fieldable field = (Fieldable) oField; String name = field.name(); // ignore UID, we handled it above if (name.equals(UidFieldMapper.NAME)) { continue; } // ignore source, we handled it above if (name.equals(SourceFieldMapper.NAME)) { continue; } Object value = null; FieldMappers fieldMappers = documentMapper.mappers().indexName(field.name()); if (fieldMappers != null) { FieldMapper mapper = fieldMappers.mapper(); if (mapper != null) { name = mapper.names().fullName(); value = mapper.valueForSearch(field); } } if (value == null) { if (field.isBinary()) { value = new BytesArray(field.getBinaryValue(), field.getBinaryOffset(), field.getBinaryLength()); } else { value = field.stringValue(); } } if (searchHit.fieldsOrNull() == null) { searchHit.fields(new HashMap<String, SearchHitField>(2)); } SearchHitField hitField = searchHit.fields().get(name); if (hitField == null) { hitField = new InternalSearchHitField(name, new ArrayList<Object>(2)); searchHit.fields().put(name, hitField); } hitField.values().add(value); } int readerIndex = context.searcher().readerIndex(docId); IndexReader subReader = context.searcher().subReaders()[readerIndex]; int subDoc = docId - context.searcher().docStarts()[readerIndex]; // go over and extract fields that are not mapped / stored context.lookup().setNextReader(subReader); context.lookup().setNextDocId(subDoc); if (source != null) { context.lookup().source().setNextSource(new BytesArray(source)); } if (extractFieldNames != null) { for (String extractFieldName : extractFieldNames) { Object value = context.lookup().source().extractValue(extractFieldName); if (value != null) { if (searchHit.fieldsOrNull() == null) { searchHit.fields(new HashMap<String, SearchHitField>(2)); } SearchHitField hitField = searchHit.fields().get(extractFieldName); if (hitField == null) { hitField = new InternalSearchHitField(extractFieldName, new ArrayList<Object>(2)); searchHit.fields().put(extractFieldName, hitField); } hitField.values().add(value); } } } for (FetchSubPhase fetchSubPhase : fetchSubPhases) { FetchSubPhase.HitContext hitContext = new FetchSubPhase.HitContext(); if (fetchSubPhase.hitExecutionNeeded(context)) { hitContext.reset(searchHit, subReader, subDoc, context.searcher().getIndexReader(), docId, doc); fetchSubPhase.hitExecute(context, hitContext); } } } for (FetchSubPhase fetchSubPhase : fetchSubPhases) { if (fetchSubPhase.hitsExecutionNeeded(context)) { fetchSubPhase.hitsExecute(context, hits); } } context.fetchResult().hits(new InternalSearchHits(hits, context.queryResult().topDocs().totalHits, context.queryResult().topDocs().getMaxScore())); } private byte[] extractSource(Document doc, DocumentMapper documentMapper) { Fieldable sourceField = doc.getFieldable(SourceFieldMapper.NAME); if (sourceField != null) { return documentMapper.sourceMapper().nativeValue(sourceField); } return null; } private Uid extractUid(SearchContext context, Document doc, @Nullable ResetFieldSelector fieldSelector) { String sUid = doc.get(UidFieldMapper.NAME); if (sUid != null) { return Uid.createUid(sUid); } // no type, nothing to do (should not really happen) List<String> fieldNames = new ArrayList<String>(); for (Fieldable field : doc.getFields()) { fieldNames.add(field.name()); } throw new FetchPhaseExecutionException(context, "Failed to load uid from the index, missing internal _uid field, current fields in the doc [" + fieldNames + "], selector [" + fieldSelector + "]"); } private Document loadDocument(SearchContext context, @Nullable ResetFieldSelector fieldSelector, int docId) { try { if (fieldSelector != null) fieldSelector.reset(); return context.searcher().doc(docId, fieldSelector); } catch (IOException e) { throw new FetchPhaseExecutionException(context, "Failed to fetch doc id [" + docId + "]", e); } } }