Search in sources :

Example 1 with HitContext

use of org.opensearch.search.fetch.FetchSubPhase.HitContext in project OpenSearch by opensearch-project.

the class FetchPhase method prepareNonNestedHitContext.

/**
 * Resets the provided {@link HitContext} with information on the current
 * document. This includes the following:
 *   - Adding an initial {@link SearchHit} instance.
 *   - Loading the document source and setting it on {@link SourceLookup}. This allows
 *     fetch subphases that use the hit context to access the preloaded source.
 */
private HitContext prepareNonNestedHitContext(SearchContext context, SearchLookup lookup, FieldsVisitor fieldsVisitor, int docId, Map<String, Set<String>> storedToRequestedFields, LeafReaderContext subReaderContext, CheckedBiConsumer<Integer, FieldsVisitor, IOException> fieldReader) throws IOException {
    int subDocId = docId - subReaderContext.docBase;
    DocumentMapper documentMapper = context.mapperService().documentMapper();
    Text typeText = documentMapper.typeText();
    if (fieldsVisitor == null) {
        SearchHit hit = new SearchHit(docId, null, null, null);
        return new HitContext(hit, subReaderContext, subDocId, lookup.source());
    } else {
        SearchHit hit;
        loadStoredFields(context.mapperService(), fieldReader, fieldsVisitor, subDocId);
        Uid uid = fieldsVisitor.uid();
        if (fieldsVisitor.fields().isEmpty() == false) {
            Map<String, DocumentField> docFields = new HashMap<>();
            Map<String, DocumentField> metaFields = new HashMap<>();
            fillDocAndMetaFields(context, fieldsVisitor, storedToRequestedFields, docFields, metaFields);
            hit = new SearchHit(docId, uid.id(), docFields, metaFields);
        } else {
            hit = new SearchHit(docId, uid.id(), emptyMap(), emptyMap());
        }
        HitContext hitContext = new HitContext(hit, subReaderContext, subDocId, lookup.source());
        if (fieldsVisitor.source() != null) {
            hitContext.sourceLookup().setSource(fieldsVisitor.source());
        }
        return hitContext;
    }
}
Also used : Uid(org.opensearch.index.mapper.Uid) SearchHit(org.opensearch.search.SearchHit) DocumentField(org.opensearch.common.document.DocumentField) HashMap(java.util.HashMap) DocumentMapper(org.opensearch.index.mapper.DocumentMapper) Text(org.opensearch.common.text.Text) HitContext(org.opensearch.search.fetch.FetchSubPhase.HitContext)

Example 2 with HitContext

use of org.opensearch.search.fetch.FetchSubPhase.HitContext in project OpenSearch by opensearch-project.

the class FetchPhase method prepareNestedHitContext.

/**
 * Resets the provided {@link HitContext} with information on the current
 * nested document. This includes the following:
 *   - Adding an initial {@link SearchHit} instance.
 *   - Loading the document source, filtering it based on the nested document ID, then
 *     setting it on {@link SourceLookup}. This allows fetch subphases that use the hit
 *     context to access the preloaded source.
 */
@SuppressWarnings("unchecked")
private HitContext prepareNestedHitContext(SearchContext context, int nestedTopDocId, int rootDocId, Map<String, Set<String>> storedToRequestedFields, LeafReaderContext subReaderContext, CheckedBiConsumer<Integer, FieldsVisitor, IOException> storedFieldReader) throws IOException {
    // Also if highlighting is requested on nested documents we need to fetch the _source from the root document,
    // otherwise highlighting will attempt to fetch the _source from the nested doc, which will fail,
    // because the entire _source is only stored with the root document.
    boolean needSource = sourceRequired(context) || context.highlight() != null;
    Uid rootId;
    Map<String, Object> rootSourceAsMap = null;
    XContentType rootSourceContentType = null;
    int nestedDocId = nestedTopDocId - subReaderContext.docBase;
    if (context instanceof InnerHitsContext.InnerHitSubContext) {
        InnerHitsContext.InnerHitSubContext innerHitsContext = (InnerHitsContext.InnerHitSubContext) context;
        rootId = innerHitsContext.getRootId();
        if (needSource) {
            SourceLookup rootLookup = innerHitsContext.getRootLookup();
            rootSourceAsMap = rootLookup.loadSourceIfNeeded();
            rootSourceContentType = rootLookup.sourceContentType();
        }
    } else {
        FieldsVisitor rootFieldsVisitor = new FieldsVisitor(needSource);
        loadStoredFields(context.mapperService(), storedFieldReader, rootFieldsVisitor, rootDocId);
        rootFieldsVisitor.postProcess(context.mapperService());
        rootId = rootFieldsVisitor.uid();
        if (needSource) {
            if (rootFieldsVisitor.source() != null) {
                Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(rootFieldsVisitor.source(), false);
                rootSourceAsMap = tuple.v2();
                rootSourceContentType = tuple.v1();
            } else {
                rootSourceAsMap = Collections.emptyMap();
            }
        }
    }
    Map<String, DocumentField> docFields = emptyMap();
    Map<String, DocumentField> metaFields = emptyMap();
    if (context.hasStoredFields() && !context.storedFieldsContext().fieldNames().isEmpty()) {
        FieldsVisitor nestedFieldsVisitor = new CustomFieldsVisitor(storedToRequestedFields.keySet(), false);
        loadStoredFields(context.mapperService(), storedFieldReader, nestedFieldsVisitor, nestedDocId);
        if (nestedFieldsVisitor.fields().isEmpty() == false) {
            docFields = new HashMap<>();
            metaFields = new HashMap<>();
            fillDocAndMetaFields(context, nestedFieldsVisitor, storedToRequestedFields, docFields, metaFields);
        }
    }
    DocumentMapper documentMapper = context.mapperService().documentMapper();
    ObjectMapper nestedObjectMapper = documentMapper.findNestedObjectMapper(nestedDocId, context, subReaderContext);
    assert nestedObjectMapper != null;
    SearchHit.NestedIdentity nestedIdentity = getInternalNestedIdentity(context, nestedDocId, subReaderContext, context.mapperService(), nestedObjectMapper);
    SearchHit hit = new SearchHit(nestedTopDocId, rootId.id(), nestedIdentity, docFields, metaFields);
    // Use a clean, fresh SourceLookup
    HitContext hitContext = new HitContext(hit, subReaderContext, nestedDocId, new SourceLookup());
    if (rootSourceAsMap != null && rootSourceAsMap.isEmpty() == false) {
        // Isolate the nested json array object that matches with nested hit and wrap it back into the same json
        // structure with the nested json array object being the actual content. The latter is important, so that
        // features like source filtering and highlighting work consistent regardless of whether the field points
        // to a json object array for consistency reasons on how we refer to fields
        Map<String, Object> nestedSourceAsMap = new HashMap<>();
        Map<String, Object> current = nestedSourceAsMap;
        for (SearchHit.NestedIdentity nested = nestedIdentity; nested != null; nested = nested.getChild()) {
            String nestedPath = nested.getField().string();
            current.put(nestedPath, new HashMap<>());
            Object extractedValue = XContentMapValues.extractValue(nestedPath, rootSourceAsMap);
            List<?> nestedParsedSource;
            if (extractedValue instanceof List) {
                // nested field has an array value in the _source
                nestedParsedSource = (List<?>) extractedValue;
            } else if (extractedValue instanceof Map) {
                // nested field has an object value in the _source. This just means the nested field has just one inner object,
                // which is valid, but uncommon.
                nestedParsedSource = Collections.singletonList(extractedValue);
            } else {
                throw new IllegalStateException("extracted source isn't an object or an array");
            }
            if ((nestedParsedSource.get(0) instanceof Map) == false && nestedObjectMapper.parentObjectMapperAreNested(context.mapperService()) == false) {
                // This is why only the first element of nestedParsedSource needs to be checked.
                throw new IllegalArgumentException("Cannot execute inner hits. One or more parent object fields of nested field [" + nestedObjectMapper.name() + "] are not nested. All parent fields need to be nested fields too");
            }
            rootSourceAsMap = (Map<String, Object>) nestedParsedSource.get(nested.getOffset());
            if (nested.getChild() == null) {
                current.put(nestedPath, rootSourceAsMap);
            } else {
                Map<String, Object> next = new HashMap<>();
                current.put(nestedPath, next);
                current = next;
            }
        }
        hitContext.sourceLookup().setSource(nestedSourceAsMap);
        hitContext.sourceLookup().setSourceContentType(rootSourceContentType);
    }
    return hitContext;
}
Also used : FieldsVisitor(org.opensearch.index.fieldvisitor.FieldsVisitor) CustomFieldsVisitor(org.opensearch.index.fieldvisitor.CustomFieldsVisitor) DocumentField(org.opensearch.common.document.DocumentField) SearchHit(org.opensearch.search.SearchHit) HashMap(java.util.HashMap) HitContext(org.opensearch.search.fetch.FetchSubPhase.HitContext) XContentType(org.opensearch.common.xcontent.XContentType) List(java.util.List) ArrayList(java.util.ArrayList) ObjectMapper(org.opensearch.index.mapper.ObjectMapper) SourceLookup(org.opensearch.search.lookup.SourceLookup) DocumentMapper(org.opensearch.index.mapper.DocumentMapper) Uid(org.opensearch.index.mapper.Uid) CustomFieldsVisitor(org.opensearch.index.fieldvisitor.CustomFieldsVisitor) InnerHitsContext(org.opensearch.search.fetch.subphase.InnerHitsContext) Map(java.util.Map) HashMap(java.util.HashMap) Collections.emptyMap(java.util.Collections.emptyMap)

Example 3 with HitContext

use of org.opensearch.search.fetch.FetchSubPhase.HitContext in project OpenSearch by opensearch-project.

the class FetchPhase method execute.

public void execute(SearchContext context) {
    if (LOGGER.isTraceEnabled()) {
        LOGGER.trace("{}", new SearchContextSourcePrinter(context));
    }
    if (context.isCancelled()) {
        throw new TaskCancelledException("cancelled task with reason: " + context.getTask().getReasonCancelled());
    }
    if (context.docIdsToLoadSize() == 0) {
        // no individual hits to process, so we shortcut
        context.fetchResult().hits(new SearchHits(new SearchHit[0], context.queryResult().getTotalHits(), context.queryResult().getMaxScore()));
        return;
    }
    DocIdToIndex[] docs = new DocIdToIndex[context.docIdsToLoadSize()];
    for (int index = 0; index < context.docIdsToLoadSize(); index++) {
        docs[index] = new DocIdToIndex(context.docIdsToLoad()[context.docIdsToLoadFrom() + index], index);
    }
    // make sure that we iterate in doc id order
    Arrays.sort(docs);
    Map<String, Set<String>> storedToRequestedFields = new HashMap<>();
    FieldsVisitor fieldsVisitor = createStoredFieldsVisitor(context, storedToRequestedFields);
    FetchContext fetchContext = new FetchContext(context);
    SearchHit[] hits = new SearchHit[context.docIdsToLoadSize()];
    List<FetchSubPhaseProcessor> processors = getProcessors(context.shardTarget(), fetchContext);
    int currentReaderIndex = -1;
    LeafReaderContext currentReaderContext = null;
    CheckedBiConsumer<Integer, FieldsVisitor, IOException> fieldReader = null;
    boolean hasSequentialDocs = hasSequentialDocs(docs);
    for (int index = 0; index < context.docIdsToLoadSize(); index++) {
        if (context.isCancelled()) {
            throw new TaskCancelledException("cancelled task with reason: " + context.getTask().getReasonCancelled());
        }
        int docId = docs[index].docId;
        try {
            int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
            if (currentReaderIndex != readerIndex) {
                currentReaderContext = context.searcher().getIndexReader().leaves().get(readerIndex);
                currentReaderIndex = readerIndex;
                if (currentReaderContext.reader() instanceof SequentialStoredFieldsLeafReader && hasSequentialDocs && docs.length >= 10) {
                    // All the docs to fetch are adjacent but Lucene stored fields are optimized
                    // for random access and don't optimize for sequential access - except for merging.
                    // So we do a little hack here and pretend we're going to do merges in order to
                    // get better sequential access.
                    SequentialStoredFieldsLeafReader lf = (SequentialStoredFieldsLeafReader) currentReaderContext.reader();
                    fieldReader = lf.getSequentialStoredFieldsReader()::visitDocument;
                } else {
                    fieldReader = currentReaderContext.reader()::document;
                }
                for (FetchSubPhaseProcessor processor : processors) {
                    processor.setNextReader(currentReaderContext);
                }
            }
            assert currentReaderContext != null;
            HitContext hit = prepareHitContext(context, fetchContext.searchLookup(), fieldsVisitor, docId, storedToRequestedFields, currentReaderContext, fieldReader);
            for (FetchSubPhaseProcessor processor : processors) {
                processor.process(hit);
            }
            hits[docs[index].index] = hit.hit();
        } catch (Exception e) {
            throw new FetchPhaseExecutionException(context.shardTarget(), "Error running fetch phase for doc [" + docId + "]", e);
        }
    }
    if (context.isCancelled()) {
        throw new TaskCancelledException("cancelled task with reason: " + context.getTask().getReasonCancelled());
    }
    TotalHits totalHits = context.queryResult().getTotalHits();
    context.fetchResult().hits(new SearchHits(hits, totalHits, context.queryResult().getMaxScore()));
}
Also used : TotalHits(org.apache.lucene.search.TotalHits) FieldsVisitor(org.opensearch.index.fieldvisitor.FieldsVisitor) CustomFieldsVisitor(org.opensearch.index.fieldvisitor.CustomFieldsVisitor) BitSet(org.apache.lucene.util.BitSet) Set(java.util.Set) HashSet(java.util.HashSet) SearchHit(org.opensearch.search.SearchHit) HashMap(java.util.HashMap) HitContext(org.opensearch.search.fetch.FetchSubPhase.HitContext) LeafReaderContext(org.apache.lucene.index.LeafReaderContext) SearchHits(org.opensearch.search.SearchHits) SequentialStoredFieldsLeafReader(org.opensearch.common.lucene.index.SequentialStoredFieldsLeafReader) IOException(java.io.IOException) SearchContextSourcePrinter(org.opensearch.search.SearchContextSourcePrinter) TaskCancelledException(org.opensearch.tasks.TaskCancelledException) IOException(java.io.IOException) TaskCancelledException(org.opensearch.tasks.TaskCancelledException)

Example 4 with HitContext

use of org.opensearch.search.fetch.FetchSubPhase.HitContext in project OpenSearch by opensearch-project.

the class FetchSourcePhaseTests method testNestedSourceWithSourceDisabled.

public void testNestedSourceWithSourceDisabled() throws IOException {
    HitContext hitContext = hitExecute(null, true, null, null, new SearchHit.NestedIdentity("nested1", 0, null));
    assertNull(hitContext.hit().getSourceAsMap());
    IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> hitExecute(null, true, "field1", null, new SearchHit.NestedIdentity("nested1", 0, null)));
    assertEquals("unable to fetch fields from _source field: _source is disabled in the mappings " + "for index [index]", e.getMessage());
}
Also used : SearchHit(org.opensearch.search.SearchHit) HitContext(org.opensearch.search.fetch.FetchSubPhase.HitContext)

Example 5 with HitContext

use of org.opensearch.search.fetch.FetchSubPhase.HitContext in project OpenSearch by opensearch-project.

the class FetchSourcePhaseTests method testBasicFiltering.

public void testBasicFiltering() throws IOException {
    XContentBuilder source = XContentFactory.jsonBuilder().startObject().field("field1", "value").field("field2", "value2").endObject();
    HitContext hitContext = hitExecute(source, false, null, null);
    assertNull(hitContext.hit().getSourceAsMap());
    hitContext = hitExecute(source, true, "field1", null);
    assertEquals(Collections.singletonMap("field1", "value"), hitContext.hit().getSourceAsMap());
    hitContext = hitExecute(source, true, "hello", null);
    assertEquals(Collections.emptyMap(), hitContext.hit().getSourceAsMap());
    hitContext = hitExecute(source, true, "*", "field2");
    assertEquals(Collections.singletonMap("field1", "value"), hitContext.hit().getSourceAsMap());
}
Also used : HitContext(org.opensearch.search.fetch.FetchSubPhase.HitContext) XContentBuilder(org.opensearch.common.xcontent.XContentBuilder)

Aggregations

HitContext (org.opensearch.search.fetch.FetchSubPhase.HitContext)12 SearchHit (org.opensearch.search.SearchHit)7 HashMap (java.util.HashMap)4 XContentBuilder (org.opensearch.common.xcontent.XContentBuilder)4 LeafReaderContext (org.apache.lucene.index.LeafReaderContext)3 SourceLookup (org.opensearch.search.lookup.SourceLookup)3 IOException (java.io.IOException)2 ArrayList (java.util.ArrayList)2 List (java.util.List)2 Map (java.util.Map)2 MemoryIndex (org.apache.lucene.index.memory.MemoryIndex)2 DocumentField (org.opensearch.common.document.DocumentField)2 CustomFieldsVisitor (org.opensearch.index.fieldvisitor.CustomFieldsVisitor)2 FieldsVisitor (org.opensearch.index.fieldvisitor.FieldsVisitor)2 DocumentMapper (org.opensearch.index.mapper.DocumentMapper)2 Uid (org.opensearch.index.mapper.Uid)2 FetchContext (org.opensearch.search.fetch.FetchContext)2 FetchSubPhaseProcessor (org.opensearch.search.fetch.FetchSubPhaseProcessor)2 BreakIterator (java.text.BreakIterator)1 Collections.emptyMap (java.util.Collections.emptyMap)1