blob: ecc950c994ef067184638e67d675bc5a4386e704 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.search;
import java.io.IOException;
import java.io.Reader;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DocumentStoredFieldVisitor;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.LazyDocument;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.IndexableFieldType;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.index.StoredFieldVisitor;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentBase;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.ByteArrayUtf8CharSequence;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.response.DocsStreamer;
import org.apache.solr.response.ResultContext;
import org.apache.solr.schema.AbstractEnumField;
import org.apache.solr.schema.BoolField;
import org.apache.solr.schema.LatLonPointSpatialField;
import org.apache.solr.schema.NumberType;
import org.apache.solr.schema.SchemaField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A helper class of {@link org.apache.solr.search.SolrIndexSearcher} for stored Document related matters
* including DocValue substitutions.
*/
public class SolrDocumentFetcher {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final SolrIndexSearcher searcher;
private final boolean enableLazyFieldLoading;
private final SolrCache<Integer,Document> documentCache;
private final Set<String> allStored;
private final Set<String> dvsCanSubstituteStored;
/** Contains the names/patterns of all docValues=true,stored=false fields in the schema. */
private final Set<String> allNonStoredDVs;
/** Contains the names/patterns of all docValues=true,stored=false,useDocValuesAsStored=true fields in the schema. */
private final Set<String> nonStoredDVsUsedAsStored;
/** Contains the names/patterns of all docValues=true,stored=false fields, excluding those that are copyField targets in the schema. */
private final Set<String> nonStoredDVsWithoutCopyTargets;
private static int largeValueLengthCacheThreshold = Integer.getInteger("solr.largeField.cacheThreshold", 512 * 1024); // internal setting
private final Set<String> largeFields;
private Collection<String> storedHighlightFieldNames; // lazy populated; use getter
@SuppressWarnings({"unchecked"})
SolrDocumentFetcher(SolrIndexSearcher searcher, SolrConfig solrConfig, boolean cachingEnabled) {
this.searcher = searcher;
this.enableLazyFieldLoading = solrConfig.enableLazyFieldLoading;
if (cachingEnabled) {
documentCache = solrConfig.documentCacheConfig == null ? null : solrConfig.documentCacheConfig.newInstance();
} else {
documentCache = null;
}
final Set<String> nonStoredDVsUsedAsStored = new HashSet<>();
final Set<String> allNonStoredDVs = new HashSet<>();
final Set<String> nonStoredDVsWithoutCopyTargets = new HashSet<>();
final Set<String> storedLargeFields = new HashSet<>();
final Set<String> dvsCanSubstituteStored = new HashSet<>();
final Set<String> allStoreds = new HashSet<>();
for (FieldInfo fieldInfo : searcher.getFieldInfos()) { // can find materialized dynamic fields, unlike using the Solr IndexSchema.
final SchemaField schemaField = searcher.getSchema().getFieldOrNull(fieldInfo.name);
if (schemaField == null) {
continue;
}
if (canSubstituteDvForStored(fieldInfo, schemaField)) {
dvsCanSubstituteStored.add(fieldInfo.name);
}
if (schemaField.stored()) {
allStoreds.add(fieldInfo.name);
}
if (!schemaField.stored() && schemaField.hasDocValues()) {
if (schemaField.useDocValuesAsStored()) {
nonStoredDVsUsedAsStored.add(fieldInfo.name);
}
allNonStoredDVs.add(fieldInfo.name);
if (!searcher.getSchema().isCopyFieldTarget(schemaField)) {
nonStoredDVsWithoutCopyTargets.add(fieldInfo.name);
}
}
if (schemaField.stored() && schemaField.isLarge()) {
storedLargeFields.add(schemaField.getName());
}
}
this.nonStoredDVsUsedAsStored = Collections.unmodifiableSet(nonStoredDVsUsedAsStored);
this.allNonStoredDVs = Collections.unmodifiableSet(allNonStoredDVs);
this.nonStoredDVsWithoutCopyTargets = Collections.unmodifiableSet(nonStoredDVsWithoutCopyTargets);
this.largeFields = Collections.unmodifiableSet(storedLargeFields);
this.dvsCanSubstituteStored = Collections.unmodifiableSet(dvsCanSubstituteStored);
this.allStored = Collections.unmodifiableSet(allStoreds);
}
// Does this field have both stored=true and docValues=true and is otherwise
// eligible for getting the field's value from DV?
private boolean canSubstituteDvForStored(FieldInfo fieldInfo, SchemaField schemaField) {
if (!schemaField.hasDocValues() || !schemaField.stored()) return false;
if (schemaField.multiValued()) return false;
DocValuesType docValuesType = fieldInfo.getDocValuesType();
NumberType numberType = schemaField.getType().getNumberType();
// can not decode a numeric without knowing its numberType
if (numberType == null && (docValuesType == DocValuesType.SORTED_NUMERIC || docValuesType == DocValuesType.NUMERIC)) {
return false;
}
return true;
}
public boolean isLazyFieldLoadingEnabled() {
return enableLazyFieldLoading;
}
public SolrCache<Integer, Document> getDocumentCache() {
return documentCache;
}
/**
* Returns a collection of the names of all stored fields which can be highlighted the index reader knows about.
*/
public Collection<String> getStoredHighlightFieldNames() {
synchronized (this) {
if (storedHighlightFieldNames == null) {
storedHighlightFieldNames = new LinkedList<>();
for (FieldInfo fieldInfo : searcher.getFieldInfos()) {
final String fieldName = fieldInfo.name;
try {
SchemaField field = searcher.getSchema().getField(fieldName);
if (field.stored() && ((field.getType() instanceof org.apache.solr.schema.TextField)
|| (field.getType() instanceof org.apache.solr.schema.StrField))) {
storedHighlightFieldNames.add(fieldName);
}
} catch (RuntimeException e) { // getField() throws a SolrException, but it arrives as a RuntimeException
log.warn("Field [{}] found in index, but not defined in schema.", fieldName);
}
}
}
return storedHighlightFieldNames;
}
}
/** @see SolrIndexSearcher#doc(int) */
public Document doc(int docId) throws IOException {
return doc(docId, (Set<String>) null);
}
/**
* Retrieve the {@link Document} instance corresponding to the document id.
* <p>
* <b>NOTE</b>: the document will have all fields accessible, but if a field filter is provided, only the provided
* fields will be loaded (the remainder will be available lazily).
*
* @see SolrIndexSearcher#doc(int, Set)
*/
public Document doc(int i, Set<String> fields) throws IOException {
Document d;
if (documentCache != null) {
final Set<String> getFields = enableLazyFieldLoading ? fields : null;
d = documentCache.computeIfAbsent(i, docId -> docNC(docId, getFields));
if (d == null) {
// failed to retrieve due to an earlier exception, try again?
return docNC(i, fields);
} else {
return d;
}
} else {
return docNC(i, fields);
}
}
private Document docNC(int i, Set<String> fields) throws IOException {
final DirectoryReader reader = searcher.getIndexReader();
final SolrDocumentStoredFieldVisitor visitor = new SolrDocumentStoredFieldVisitor(fields, reader, i);
reader.document(i, visitor);
return visitor.getDocument();
}
/**
* This is an optimized version for populating a SolrDocument that:
*
* 1. fetches all fields from docValues if possible. If no decompression of the stored
* data is necessary, we can avoid a disk seek and decompression cycle.
* This step is only used if all requested fields are
* {code docValues=true stored=false multiValued=false}.
* This last restriction because multiValued docValues fields do not faithfully reflect
* the input order in all cases. the values are returned and no decompression is necessary.
*
* 2. if 1 is impossible, try to fetch all requested fields from the stored values. If
* the stored data has to be decompressed anyway, it's more efficient to
* just get all field values from the stored values. If we got all the requested fields, return.
*
* 3. add fields where docValues=true stored=false thus could not be fetched in step 2
*
* @param luceneDocId The Lucene doc ID
* @param solrReturnFields the structure holding the fields to be returned.
* The first time this method is called for a particular
* document list, it will be modified by adding a
* RetrieveFieldsOptimizer for use in future calls.
*
* @return The SolrDocument with values requested.
* <p>
* This method is designed to be as simple as possible to use, just call it. e.g.
* {code SolrDocument sdoc = docFetcher.solrDoc(id, solrReturnFields);}
* then process the resulting SolrDocument as usual. Subsequent calls with the same
* solrReturnFields will re-use the optimizer created the first time.
*
* NOTE: DO NOT re-use the same SolrReturnFields object if the fields requested change.
*/
public SolrDocument solrDoc(int luceneDocId, SolrReturnFields solrReturnFields) {
Supplier<RetrieveFieldsOptimizer> rfoSupplier = () -> new RetrieveFieldsOptimizer(solrReturnFields);
return solrReturnFields.getFetchOptimizer(rfoSupplier).getSolrDoc(luceneDocId);
}
/** {@link StoredFieldVisitor} which loads the specified fields eagerly (or all if null).
* If {@link #enableLazyFieldLoading} then the rest get special lazy field entries. Designated "large"
* fields will always get a special field entry. */
private class SolrDocumentStoredFieldVisitor extends DocumentStoredFieldVisitor {
private final Document doc;
private final LazyDocument lazyFieldProducer; // arguably a better name than LazyDocument; at least how we use it here
private final int docId;
private final boolean addLargeFieldsLazily;
SolrDocumentStoredFieldVisitor(Set<String> toLoad, IndexReader reader, int docId) {
super(toLoad);
this.docId = docId;
this.doc = getDocument();
this.lazyFieldProducer = toLoad != null && enableLazyFieldLoading ? new LazyDocument(reader, docId) : null;
this.addLargeFieldsLazily = (documentCache != null && !largeFields.isEmpty());
//TODO can we return Status.STOP after a val is loaded and we know there are no other fields of interest?
// When: toLoad is one single-valued field, no lazyFieldProducer
}
@Override
public void stringField(FieldInfo fieldInfo, byte[] value) throws IOException {
Predicate<String> readAsBytes = ResultContext.READASBYTES.get();
if (readAsBytes != null && readAsBytes.test(fieldInfo.name)) {
final FieldType ft = new FieldType(TextField.TYPE_STORED);
ft.setStoreTermVectors(fieldInfo.hasVectors());
ft.setOmitNorms(fieldInfo.omitsNorms());
ft.setIndexOptions(fieldInfo.getIndexOptions());
doc.add(new StoredField(fieldInfo.name, new ByteArrayUtf8CharSequence(value, 0, value.length), ft));
} else {
super.stringField(fieldInfo, value);
}
}
@Override
public Status needsField(FieldInfo fieldInfo) throws IOException {
Status status = super.needsField(fieldInfo);
assert status != Status.STOP : "Status.STOP not supported or expected";
if (addLargeFieldsLazily && largeFields.contains(fieldInfo.name)) { // load "large" fields using this lazy mechanism
if (lazyFieldProducer != null || status == Status.YES) {
doc.add(new LargeLazyField(fieldInfo.name, docId));
}
return Status.NO;
}
if (status == Status.NO && lazyFieldProducer != null) { // lazy
doc.add(lazyFieldProducer.getField(fieldInfo));
}
return status;
}
}
/** @see SolrIndexSearcher#doc(int, StoredFieldVisitor) */
public void doc(int docId, StoredFieldVisitor visitor) throws IOException {
if (documentCache != null) {
// get cached document or retrieve it including all fields (and cache it)
Document cached = doc(docId);
visitFromCached(cached, visitor);
} else {
searcher.getIndexReader().document(docId, visitor);
}
}
/** Executes a stored field visitor against a hit from the document cache */
private void visitFromCached(Document document, StoredFieldVisitor visitor) throws IOException {
for (IndexableField f : document) {
final FieldInfo info = searcher.getFieldInfos().fieldInfo(f.name());
final StoredFieldVisitor.Status needsField = visitor.needsField(info);
if (needsField == StoredFieldVisitor.Status.STOP) return;
if (needsField == StoredFieldVisitor.Status.NO) continue;
BytesRef binaryValue = f.binaryValue();
if (binaryValue != null) {
visitor.binaryField(info, toByteArrayUnwrapIfPossible(binaryValue));
continue;
}
Number numericValue = f.numericValue();
if (numericValue != null) {
if (numericValue instanceof Double) {
visitor.doubleField(info, numericValue.doubleValue());
} else if (numericValue instanceof Integer) {
visitor.intField(info, numericValue.intValue());
} else if (numericValue instanceof Float) {
visitor.floatField(info, numericValue.floatValue());
} else if (numericValue instanceof Long) {
visitor.longField(info, numericValue.longValue());
} else {
throw new AssertionError();
}
continue;
}
// must be String
if (f instanceof LargeLazyField) { // optimization to avoid premature string conversion
visitor.stringField(info, toByteArrayUnwrapIfPossible(((LargeLazyField) f).readBytes()));
} else {
visitor.stringField(info, f.stringValue().getBytes(StandardCharsets.UTF_8));
}
}
}
private byte[] toByteArrayUnwrapIfPossible(BytesRef bytesRef) {
if (bytesRef.offset == 0 && bytesRef.bytes.length == bytesRef.length) {
return bytesRef.bytes;
} else {
return Arrays.copyOfRange(bytesRef.bytes, bytesRef.offset, bytesRef.offset + bytesRef.length);
}
}
/** Unlike LazyDocument.LazyField, we (a) don't cache large values, and (b) provide access to the byte[]. */
class LargeLazyField implements IndexableField {
final String name;
final int docId;
// synchronize on 'this' to access:
BytesRef cachedBytes; // we only conditionally populate this if it's big enough
private LargeLazyField(String name, int docId) {
this.name = name;
this.docId = docId;
}
@Override
public String toString() {
return fieldType().toString() + "<" + name() + ">"; // mimic Field.java
}
@Override
public String name() {
return name;
}
@Override
public IndexableFieldType fieldType() {
return searcher.getSchema().getField(name());
}
@Override
public TokenStream tokenStream(Analyzer analyzer, TokenStream reuse) {
return analyzer.tokenStream(name(), stringValue()); // or we could throw unsupported exception?
}
/** (for tests) */
synchronized boolean hasBeenLoaded() {
return cachedBytes != null;
}
@Override
public synchronized String stringValue() {
try {
return readBytes().utf8ToString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
synchronized BytesRef readBytes() throws IOException {
if (cachedBytes != null) {
return cachedBytes;
} else {
BytesRef bytesRef = new BytesRef();
searcher.getIndexReader().document(docId, new StoredFieldVisitor() {
boolean done = false;
@Override
public Status needsField(FieldInfo fieldInfo) throws IOException {
if (done) {
return Status.STOP;
}
return fieldInfo.name.equals(name()) ? Status.YES : Status.NO;
}
@Override
public void stringField(FieldInfo fieldInfo, byte[] value) throws IOException {
bytesRef.bytes = value;
bytesRef.length = value.length;
done = true;
}
@Override
public void binaryField(FieldInfo fieldInfo, byte[] value) throws IOException {
throw new UnsupportedOperationException("'large' binary fields are not (yet) supported");
}
});
if (bytesRef.length < largeValueLengthCacheThreshold) {
return cachedBytes = bytesRef;
} else {
return bytesRef;
}
}
}
@Override
public BytesRef binaryValue() {
return null;
}
@Override
public Reader readerValue() {
return null;
}
@Override
public Number numericValue() {
return null;
}
}
/**
* This will fetch and add the docValues fields to a given SolrDocument/SolrInputDocument
*
* @param doc
* A SolrDocument or SolrInputDocument instance where docValues will be added
* @param docid
* The lucene docid of the document to be populated
* @param fields
* The fields with docValues to populate the document with.
* DocValues fields which do not exist or not decodable will be ignored.
*/
public void decorateDocValueFields(@SuppressWarnings("rawtypes") SolrDocumentBase doc, int docid, Set<String> fields)
throws IOException {
final List<LeafReaderContext> leafContexts = searcher.getLeafContexts();
final int subIndex = ReaderUtil.subIndex(docid, leafContexts);
final int localId = docid - leafContexts.get(subIndex).docBase;
final LeafReader leafReader = leafContexts.get(subIndex).reader();
for (String fieldName : fields) {
Object fieldValue = decodeDVField(localId, leafReader, fieldName);
if (fieldValue != null) {
doc.setField(fieldName, fieldValue);
}
}
}
/**
* Decode value from DV field for a document
* @return null if DV field is not exist or can not decodable
*/
private Object decodeDVField(int localId, LeafReader leafReader, String fieldName) throws IOException {
final SchemaField schemaField = searcher.getSchema().getFieldOrNull(fieldName);
FieldInfo fi = searcher.getFieldInfos().fieldInfo(fieldName);
if (schemaField == null || !schemaField.hasDocValues() || fi == null) {
return null; // Searcher doesn't have info about this field, hence ignore it.
}
final DocValuesType dvType = fi.getDocValuesType();
switch (dvType) {
case NUMERIC:
final NumericDocValues ndv = leafReader.getNumericDocValues(fieldName);
if (ndv == null) {
return null;
}
if (!ndv.advanceExact(localId)) {
return null;
}
Long val = ndv.longValue();
return decodeNumberFromDV(schemaField, val, false);
case BINARY:
BinaryDocValues bdv = leafReader.getBinaryDocValues(fieldName);
if (bdv != null && bdv.advanceExact(localId)) {
return BytesRef.deepCopyOf(bdv.binaryValue());
}
return null;
case SORTED:
SortedDocValues sdv = leafReader.getSortedDocValues(fieldName);
if (sdv != null && sdv.advanceExact(localId)) {
final BytesRef bRef = sdv.binaryValue();
// Special handling for Boolean fields since they're stored as 'T' and 'F'.
if (schemaField.getType() instanceof BoolField) {
return schemaField.getType().toObject(schemaField, bRef);
} else {
return bRef.utf8ToString();
}
}
return null;
case SORTED_NUMERIC:
final SortedNumericDocValues numericDv = leafReader.getSortedNumericDocValues(fieldName);
if (numericDv != null && numericDv.advance(localId) == localId) {
final int docValueCount = numericDv.docValueCount();
final List<Object> outValues = new ArrayList<>(docValueCount);
for (int i = 0; i < docValueCount; i++) {
long number = numericDv.nextValue();
Object value = decodeNumberFromDV(schemaField, number, true);
// return immediately if the number is not decodable, hence won't return an empty list.
if (value == null) {
return null;
}
// normally never true but LatLonPointSpatialField uses SORTED_NUMERIC even when single valued
else if (schemaField.multiValued() == false) {
return value;
} else {
outValues.add(value);
}
}
assert outValues.size() > 0;
return outValues;
}
return null;
case SORTED_SET:
final SortedSetDocValues values = leafReader.getSortedSetDocValues(fieldName);
if (values != null && values.getValueCount() > 0 && values.advance(localId) == localId) {
final List<Object> outValues = new LinkedList<>();
for (long ord = values.nextOrd(); ord != SortedSetDocValues.NO_MORE_ORDS; ord = values.nextOrd()) {
BytesRef value = values.lookupOrd(ord);
outValues.add(schemaField.getType().toObject(schemaField, value));
}
assert outValues.size() > 0;
return outValues;
}
return null;
default:
return null;
}
}
private Object decodeNumberFromDV(SchemaField schemaField, long value, boolean sortableNumeric) {
// note: This special-case is unfortunate; if we have to add any more than perhaps the fieldType should
// have this method so that specific field types can customize it.
if (schemaField.getType() instanceof LatLonPointSpatialField) {
return LatLonPointSpatialField.decodeDocValueToString(value);
}
if (schemaField.getType().getNumberType() == null) {
log.warn("Couldn't decode docValues for field: [{}], schemaField: [{}], numberType is unknown",
schemaField.getName(), schemaField);
return null;
}
switch (schemaField.getType().getNumberType()) {
case INTEGER:
final int raw = (int)value;
if (schemaField.getType() instanceof AbstractEnumField) {
return ((AbstractEnumField)schemaField.getType()).getEnumMapping().intValueToStringValue(raw);
} else {
return raw;
}
case LONG:
return value;
case FLOAT:
if (sortableNumeric) {
return NumericUtils.sortableIntToFloat((int)value);
} else {
return Float.intBitsToFloat((int)value);
}
case DOUBLE:
if (sortableNumeric) {
return NumericUtils.sortableLongToDouble(value);
} else {
return Double.longBitsToDouble(value);
}
case DATE:
return new Date(value);
default:
// catched all possible values, this line will never be reached
throw new AssertionError();
}
}
public Set<String> getDvsCanSubstituteStored() {
return dvsCanSubstituteStored;
}
public Set<String> getAllStored() {
return allStored;
}
/**
* Returns an unmodifiable set of non-stored docValues field names.
*
* @param onlyUseDocValuesAsStored
* If false, returns all non-stored docValues. If true, returns only those non-stored docValues which have
* the {@link SchemaField#useDocValuesAsStored()} flag true.
*/
public Set<String> getNonStoredDVs(boolean onlyUseDocValuesAsStored) {
return onlyUseDocValuesAsStored ? nonStoredDVsUsedAsStored : allNonStoredDVs;
}
/**
* Returns an unmodifiable set of names of non-stored docValues fields, except those that are targets of a copy field.
*/
public Set<String> getNonStoredDVsWithoutCopyTargets() {
return nonStoredDVsWithoutCopyTargets;
}
/**
* Moved as a private class here, we consider it an impelmentation detail. It should not
* be exposed outside of this class.
* <p>
* This class is in charge of insuring that SolrDocuments can have their fields populated
* during a request in the most efficient way possible. See the comments at
* {@link #solrDoc(int docId, SolrReturnFields solrReturnFields)}
*/
class RetrieveFieldsOptimizer {
// null means get all available stored fields
private final Set<String> storedFields;
// always non null
private final Set<String> dvFields;
private final SolrReturnFields solrReturnFields;
RetrieveFieldsOptimizer(SolrReturnFields solrReturnFields) {
this.storedFields = calcStoredFieldsForReturn(solrReturnFields);
this.dvFields = calcDocValueFieldsForReturn(solrReturnFields);
this.solrReturnFields = solrReturnFields;
if (storedFields != null && dvsCanSubstituteStored.containsAll(storedFields)) {
dvFields.addAll(storedFields);
storedFields.clear();
}
}
/**
* Sometimes we could fetch a field value from either the stored document or docValues.
* Such fields have both and are single-valued.
* If choosing docValues allows us to avoid accessing the stored document altogether
* for all fields to be returned then we do it,
* otherwise we prefer the stored value when we have a choice.
*/
private boolean returnStoredFields() {
return !(storedFields != null && storedFields.isEmpty());
}
private boolean returnDVFields() {
return CollectionUtils.isNotEmpty(dvFields);
}
private Set<String> getStoredFields() {
return storedFields;
}
private Set<String> getDvFields() {
return dvFields;
}
//who uses all of these?
private ReturnFields getReturnFields() {
return solrReturnFields;
}
private Set<String> calcStoredFieldsForReturn(ReturnFields returnFields) {
final Set<String> storedFields = new HashSet<>();
Set<String> fnames = returnFields.getLuceneFieldNames();
if (returnFields.wantsAllFields()) {
return null;
} else if (returnFields.hasPatternMatching()) {
for (String s : getAllStored()) {
if (returnFields.wantsField(s)) {
storedFields.add(s);
}
}
} else if (fnames != null) {
storedFields.addAll(fnames);
storedFields.removeIf((String name) -> {
SchemaField schemaField = searcher.getSchema().getFieldOrNull(name);
if (schemaField == null) return false; // Get it from the stored fields if, for some reasonm, we can't get the schema.
if (schemaField.stored() && schemaField.multiValued()) return false; // must return multivalued fields from stored data if possible.
if (schemaField.stored() == false) return true; // if it's not stored, no choice but to return from DV.
return false;
});
}
storedFields.remove(SolrReturnFields.SCORE);
return storedFields;
}
private Set<String> calcDocValueFieldsForReturn(ReturnFields returnFields) {
// always return not null
final Set<String> result = new HashSet<>();
if (returnFields.wantsAllFields()) {
result.addAll(getNonStoredDVs(true));
// check whether there are no additional fields
Set<String> fieldNames = returnFields.getLuceneFieldNames(true);
if (fieldNames != null) {
// add all requested fields that may be useDocValuesAsStored=false
for (String fl : fieldNames) {
if (getNonStoredDVs(false).contains(fl)) {
result.add(fl);
}
}
}
} else if (returnFields.hasPatternMatching()) {
for (String s : getNonStoredDVs(true)) {
if (returnFields.wantsField(s)) {
result.add(s);
}
}
} else {
Set<String> fnames = returnFields.getLuceneFieldNames();
if (fnames != null) {
result.addAll(fnames);
// here we get all non-stored dv fields because even if a user has set
// useDocValuesAsStored=false in schema, he may have requested a field
// explicitly using the fl parameter
result.retainAll(getNonStoredDVs(false));
}
}
return result;
}
private SolrDocument getSolrDoc(int luceneDocId) {
SolrDocument sdoc = null;
try {
if (returnStoredFields()) {
Document doc = doc(luceneDocId, getStoredFields());
// make sure to use the schema from the searcher and not the request (cross-core)
sdoc = DocsStreamer.convertLuceneDocToSolrDoc(doc, searcher.getSchema(), getReturnFields());
if (returnDVFields() == false) {
solrReturnFields.setFieldSources(SolrReturnFields.FIELD_SOURCES.ALL_FROM_STORED);
return sdoc;
} else {
solrReturnFields.setFieldSources(SolrReturnFields.FIELD_SOURCES.MIXED_SOURCES);
}
} else {
// no need to get stored fields of the document, see SOLR-5968
sdoc = new SolrDocument();
solrReturnFields.setFieldSources(SolrReturnFields.FIELD_SOURCES.ALL_FROM_DV);
}
// decorate the document with non-stored docValues fields
if (returnDVFields()) {
decorateDocValueFields(sdoc, luceneDocId, getDvFields());
}
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error reading document with docId " + luceneDocId, e);
}
return sdoc;
}
}
}