/**
* 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.net.URL;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.document.FieldSelectorResult;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.TimeLimitingCollector;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopDocsCollector;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.OpenBitSet;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.request.UnInvertedField;
import org.apache.solr.schema.EmbeddedIndexedIntField;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SolrIndexSearcher adds schema awareness and caching functionality over the
* lucene IndexSearcher.
*
* @version $Id: SolrIndexSearcher.java 794328 2009-07-15 17:21:04Z shalin $
* @since solr 0.9
*/
public class SolrIndexSearcher extends IndexSearcher implements SolrInfoMBean {
private static Logger log = LoggerFactory
.getLogger(SolrIndexSearcher.class);
private final SolrCore core;
private final IndexSchema schema;
private String indexDir;
private final String name;
private long openTime = System.currentTimeMillis();
private long registerTime = 0;
private long warmupTime = 0;
private final SolrIndexReader reader;
private final boolean closeReader;
private final int queryResultWindowSize;
private final int queryResultMaxDocsCached;
private final boolean useFilterForSortedQuery;
public final boolean enableLazyFieldLoading;
private final boolean cachingEnabled;
private final SolrCache filterCache;
private final SolrCache queryResultCache;
private final SolrCache documentCache;
private final SolrCache fieldValueCache;
private final LuceneQueryOptimizer optimizer;
// map of generic caches - not synchronized since it's read-only after the
// constructor.
private final HashMap<String, SolrCache> cacheMap;
private static final HashMap<String, SolrCache> noGenericCaches = new HashMap<String, SolrCache>(
0);
// list of all caches associated with this searcher.
private final SolrCache[] cacheList;
private static final SolrCache[] noCaches = new SolrCache[0];
private final Collection<String> fieldNames;
private Collection<String> storedHighlightFieldNames;
/**
* Creates a searcher searching the index in the named directory.
*
* @deprecated use alternate constructor
*/
public SolrIndexSearcher(SolrCore core, IndexSchema schema, String name,
String path, boolean enableCache) throws IOException {
this(core, schema, name, core.getIndexReaderFactory().newReader(
core.getDirectoryFactory().open(path), false), true,
enableCache);
}
/*
* Creates a searcher searching the index in the provided directory. Note:
* uses the main IndexReaderFactory for the specified SolrCore.
*
* @see SolrCore#getMainIndexReaderFactory
*/
public SolrIndexSearcher(SolrCore core, IndexSchema schema, String name,
Directory directory, boolean enableCache) throws IOException {
this(core, schema, name, core.getIndexReaderFactory().newReader(
directory, false), true, enableCache);
}
/** Creates a searcher searching the index in the provided directory. */
public SolrIndexSearcher(SolrCore core, IndexSchema schema, String name,
Directory directory, boolean readOnly, boolean enableCache)
throws IOException {
this(core, schema, name, core.getIndexReaderFactory().newReader(
directory, readOnly), true, enableCache);
}
/** Creates a searcher searching the provided index. */
public SolrIndexSearcher(SolrCore core, IndexSchema schema, String name,
IndexReader r, boolean enableCache) {
this(core, schema, name, r, false, enableCache);
}
private static SolrIndexReader wrap(IndexReader r) {
SolrIndexReader sir;
// wrap the reader
if (!(r instanceof SolrIndexReader)) {
sir = new SolrIndexReader(r, null, 0);
sir.associateInfo(null);
} else {
sir = (SolrIndexReader) r;
}
return sir;
}
public SolrIndexSearcher(SolrCore core, IndexSchema schema, String name,
IndexReader r, boolean closeReader, boolean enableCache) {
super(wrap(r));
this.reader = (SolrIndexReader) super.getIndexReader();
this.core = core;
this.schema = schema;
this.name = "Searcher@" + Integer.toHexString(hashCode())
+ (name != null ? " " + name : "");
log.info("Opening " + this.name);
SolrIndexReader.setSearcher(reader, this);
if (r.directory() instanceof FSDirectory) {
FSDirectory fsDirectory = (FSDirectory) r.directory();
indexDir = fsDirectory.getFile().getAbsolutePath();
}
this.closeReader = closeReader;
setSimilarity(schema.getSimilarity());
SolrConfig solrConfig = core.getSolrConfig();
queryResultWindowSize = solrConfig.queryResultWindowSize;
queryResultMaxDocsCached = solrConfig.queryResultMaxDocsCached;
useFilterForSortedQuery = solrConfig.useFilterForSortedQuery;
enableLazyFieldLoading = solrConfig.enableLazyFieldLoading;
cachingEnabled = enableCache;
if (cachingEnabled) {
ArrayList<SolrCache> clist = new ArrayList<SolrCache>();
fieldValueCache = solrConfig.fieldValueCacheConfig == null ? null
: solrConfig.fieldValueCacheConfig.newInstance();
if (fieldValueCache != null)
clist.add(fieldValueCache);
filterCache = solrConfig.filterCacheConfig == null ? null
: solrConfig.filterCacheConfig.newInstance();
if (filterCache != null)
clist.add(filterCache);
queryResultCache = solrConfig.queryResultCacheConfig == null ? null
: solrConfig.queryResultCacheConfig.newInstance();
if (queryResultCache != null) {
clist.add(queryResultCache);
}
documentCache = solrConfig.documentCacheConfig == null ? null
: solrConfig.documentCacheConfig.newInstance();
if (documentCache != null)
clist.add(documentCache);
if (solrConfig.userCacheConfigs == null) {
cacheMap = noGenericCaches;
} else {
cacheMap = new HashMap<String, SolrCache>(
solrConfig.userCacheConfigs.length);
for (CacheConfig userCacheConfig : solrConfig.userCacheConfigs) {
SolrCache cache = null;
if (userCacheConfig != null)
cache = userCacheConfig.newInstance();
if (cache != null) {
cacheMap.put(cache.name(), cache);
clist.add(cache);
}
}
}
cacheList = clist.toArray(new SolrCache[clist.size()]);
} else {
filterCache = null;
queryResultCache = null;
documentCache = null;
fieldValueCache = null;
cacheMap = noGenericCaches;
cacheList = noCaches;
}
optimizer = solrConfig.filtOptEnabled ? new LuceneQueryOptimizer(
solrConfig.filtOptCacheSize, solrConfig.filtOptThreshold)
: null;
fieldNames = r.getFieldNames(IndexReader.FieldOption.ALL);
}
public String toString() {
return name;
}
/**
* Register sub-objects such as caches
*/
public void register() {
// register self
core.getInfoRegistry().put("searcher", this);
core.getInfoRegistry().put(name, this);
for (SolrCache cache : cacheList) {
cache.setState(SolrCache.State.LIVE);
core.getInfoRegistry().put(cache.name(), cache);
}
registerTime = System.currentTimeMillis();
}
/**
* Free's resources associated with this searcher.
*
* In particular, the underlying reader and any cache's in use are closed.
*/
public void close() throws IOException {
if (cachingEnabled) {
StringBuilder sb = new StringBuilder();
sb.append("Closing ").append(name);
for (SolrCache cache : cacheList) {
sb.append("\n\t");
sb.append(cache);
}
log.info(sb.toString());
} else {
log.debug("Closing " + name);
}
core.getInfoRegistry().remove(name);
// super.close();
// can't use super.close() since it just calls reader.close() and that
// may only be called once
// per reader (even if incRef() was previously called).
if (closeReader)
reader.decRef();
for (SolrCache cache : cacheList) {
cache.close();
}
}
/** Direct access to the IndexReader used by this searcher */
public SolrIndexReader getReader() {
return reader;
}
/** Direct access to the IndexSchema for use with this searcher */
public IndexSchema getSchema() {
return schema;
}
/**
* Returns a collection of all field names the index reader knows about.
*/
public Collection<String> getFieldNames() {
return fieldNames;
}
/**
* Returns a collection of the names of all stored fields which can be
* highlighted the index reader knows about.
*/
public Collection<String> getStoredHighlightFieldNames() {
if (storedHighlightFieldNames == null) {
storedHighlightFieldNames = new LinkedList<String>();
for (String fieldName : fieldNames) {
try {
SchemaField field = schema.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 \"" + fieldName
+ "\" found in index, but not defined in schema.");
}
}
}
return storedHighlightFieldNames;
}
//
// Set default regenerators on filter and query caches if they don't have
// any
//
public static void initRegenerators(SolrConfig solrConfig) {
if (solrConfig.fieldValueCacheConfig != null
&& solrConfig.fieldValueCacheConfig.getRegenerator() == null) {
solrConfig.fieldValueCacheConfig
.setRegenerator(new CacheRegenerator() {
public boolean regenerateItem(
SolrIndexSearcher newSearcher,
SolrCache newCache, SolrCache oldCache,
Object oldKey, Object oldVal)
throws IOException {
if (oldVal instanceof UnInvertedField) {
UnInvertedField.getUnInvertedField(
(String) oldKey, newSearcher);
}
return true;
}
});
}
if (solrConfig.filterCacheConfig != null
&& solrConfig.filterCacheConfig.getRegenerator() == null) {
solrConfig.filterCacheConfig.setRegenerator(new CacheRegenerator() {
public boolean regenerateItem(SolrIndexSearcher newSearcher,
SolrCache newCache, SolrCache oldCache, Object oldKey,
Object oldVal) throws IOException {
newSearcher.cacheDocSet((Query) oldKey, null, false);
return true;
}
});
}
if (solrConfig.queryResultCacheConfig != null
&& solrConfig.queryResultCacheConfig.getRegenerator() == null) {
final int queryResultWindowSize = solrConfig.queryResultWindowSize;
solrConfig.queryResultCacheConfig
.setRegenerator(new CacheRegenerator() {
public boolean regenerateItem(
SolrIndexSearcher newSearcher,
SolrCache newCache, SolrCache oldCache,
Object oldKey, Object oldVal)
throws IOException {
QueryResultKey key = (QueryResultKey) oldKey;
int nDocs = 1;
// request 1 doc and let caching round up to the
// next window size...
// unless the window size is <=1, in which case we
// will pick
// the minimum of the number of documents requested
// last time and
// a reasonable number such as 40.
// TODO: make more configurable later...
if (queryResultWindowSize <= 1) {
DocList oldList = (DocList) oldVal;
int oldnDocs = oldList.offset()
+ oldList.size();
// 40 has factors of 2,4,5,10,20
nDocs = Math.min(oldnDocs, 40);
}
int flags = NO_CHECK_QCACHE | key.nc_flags;
QueryCommand qc = new QueryCommand();
qc.setQuery(key.query).setFilterList(key.filters)
.setSort(key.sort).setLen(nDocs)
.setSupersetMaxDoc(nDocs).setFlags(flags);
QueryResult qr = new QueryResult();
newSearcher.getDocListC(qr, qc);
return true;
}
});
}
}
public QueryResult search(QueryResult qr, QueryCommand cmd)
throws IOException {
getDocListC(qr, cmd);
return qr;
}
public Hits search(Query query, Filter filter, Sort sort)
throws IOException {
// todo - when Solr starts accepting filters, need to
// change this conditional check (filter!=null) and create a new filter
// that ANDs them together if it already exists.
if (optimizer == null || filter != null
|| !(query instanceof BooleanQuery)) {
return super.search(query, filter, sort);
} else {
Query[] newQuery = new Query[1];
Filter[] newFilter = new Filter[1];
optimizer.optimize((BooleanQuery) query, this, 0, newQuery,
newFilter);
return super.search(newQuery[0], newFilter[0], sort);
}
}
/**
* @return the indexDir on which this searcher is opened
* @see org.apache.solr.search.SolrIndexSearcher#SolrIndexSearcher(org.apache.solr.core.SolrCore,
* org.apache.solr.schema.IndexSchema, String, String, boolean)
*/
public String getIndexDir() {
return indexDir;
}
/* ********************** Document retrieval ************************ */
/*
* Future optimizations (yonik)
*
* If no cache is present: - use NO_LOAD instead of LAZY_LOAD - use
* LOAD_AND_BREAK if a single field is begin retrieved
*/
/**
* FieldSelector which loads the specified fields, and load all other field
* lazily.
*/
static class SetNonLazyFieldSelector implements FieldSelector {
private Set<String> fieldsToLoad;
SetNonLazyFieldSelector(Set<String> toLoad) {
fieldsToLoad = toLoad;
}
public FieldSelectorResult accept(String fieldName) {
if (fieldsToLoad.contains(fieldName))
return FieldSelectorResult.LOAD;
else
return FieldSelectorResult.LAZY_LOAD;
}
}
/**
* Retrieve the {@link Document} instance corresponding to the document id.
*/
public Document doc(int i) throws IOException {
return doc(i, (Set<String>) null);
}
/**
* Retrieve a {@link Document} using a
* {@link org.apache.lucene.document.FieldSelector} This method does not
* currently use the Solr document cache.
*
* @see IndexReader#document(int, FieldSelector)
*/
public Document doc(int n, FieldSelector fieldSelector) throws IOException {
return getIndexReader().document(n, fieldSelector);
}
/**
* Retrieve the {@link Document} instance corresponding to the document id.
*
* Note: The document will have all fields accessable, but if a field filter
* is provided, only the provided fields will be loaded (the remainder will
* be available lazily).
*/
public Document doc(int i, Set<String> fields) throws IOException {
Document d;
if (documentCache != null) {
d = (Document) documentCache.get(i);
if (d != null)
return d;
}
if (!enableLazyFieldLoading || fields == null) {
d = getIndexReader().document(i);
} else {
d = getIndexReader().document(i,
new SetNonLazyFieldSelector(fields));
}
if (documentCache != null) {
documentCache.put(i, d);
}
return d;
}
/**
* Takes a list of docs (the doc ids actually), and reads them into an array
* of Documents.
*/
public void readDocs(Document[] docs, DocList ids) throws IOException {
readDocs(docs, ids, null);
}
/**
* Takes a list of docs (the doc ids actually) and a set of fields to load,
* and reads them into an array of Documents.
*/
public void readDocs(Document[] docs, DocList ids, Set<String> fields)
throws IOException {
DocIterator iter = ids.iterator();
for (int i = 0; i < docs.length; i++) {
docs[i] = doc(iter.nextDoc(), fields);
}
}
/* ********************** end document retrieval ************************ */
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
/** expert: internal API, subject to change */
public SolrCache getFieldValueCache() {
return fieldValueCache;
}
/**
* Returns the first document number containing the term <code>t</code>
* Returns -1 if no document was found. This method is primarily intended
* for clients that want to fetch documents using a unique identifier."
*
* @param t
* @return the first document number containing the term
*/
public int getFirstMatch(Term t) throws IOException {
TermDocs tdocs = null;
try {
tdocs = reader.termDocs(t);
if (!tdocs.next())
return -1;
return tdocs.doc();
} finally {
if (tdocs != null)
tdocs.close();
}
}
/**
* Compute and cache the DocSet that matches a query. The normal usage is
* expected to be cacheDocSet(myQuery, null,false) meaning that Solr will
* determine if the Query warrants caching, and if so, will compute the
* DocSet that matches the Query and cache it. If the answer to the query is
* already cached, nothing further will be done.
* <p>
* If the optionalAnswer DocSet is provided, it should *not* be modified
* after this call.
*
* @param query
* the lucene query that will act as the key
* @param optionalAnswer
* the DocSet to be cached - if null, it will be computed.
* @param mustCache
* if true, a best effort will be made to cache this entry. if
* false, heuristics may be used to determine if it should be
* cached.
*/
public void cacheDocSet(Query query, DocSet optionalAnswer,
boolean mustCache) throws IOException {
// Even if the cache is null, still compute the DocSet as it may serve
// to warm the Lucene
// or OS disk cache.
if (optionalAnswer != null) {
if (filterCache != null) {
filterCache.put(query, optionalAnswer);
}
return;
}
// Throw away the result, relying on the fact that getDocSet
// will currently always cache what it found. If getDocSet() starts
// using heuristics about what to cache, and mustCache==true, (or if we
// want this method to start using heuristics too) then
// this needs to change.
getDocSet(query);
}
/**
* Returns the set of document ids matching a query. This method is
* cache-aware and attempts to retrieve the answer from the cache if
* possible. If the answer was not cached, it may have been inserted into
* the cache as a result of this call. This method can handle negative
* queries.
* <p>
* The DocSet returned should <b>not</b> be modified.
*/
public DocSet getDocSet(Query query) throws IOException {
// Get the absolute value (positive version) of this query. If we
// get back the same reference, we know it's positive.
Query absQ = QueryUtils.getAbs(query);
boolean positive = query == absQ;
if (filterCache != null) {
DocSet absAnswer = (DocSet) filterCache.get(absQ);
if (absAnswer != null) {
if (positive)
return absAnswer;
else
return getPositiveDocSet(matchAllDocsQuery).andNot(
absAnswer);
}
}
DocSet absAnswer = getDocSetNC(absQ, null);
DocSet answer = positive ? absAnswer : getPositiveDocSet(
matchAllDocsQuery).andNot(absAnswer);
if (filterCache != null) {
// cache negative queries as positive
filterCache.put(absQ, absAnswer);
}
return answer;
}
// only handle positive (non negative) queries
DocSet getPositiveDocSet(Query q) throws IOException {
DocSet answer;
if (filterCache != null) {
answer = (DocSet) filterCache.get(q);
if (answer != null)
return answer;
}
answer = getDocSetNC(q, null);
if (filterCache != null)
filterCache.put(q, answer);
return answer;
}
private static Query matchAllDocsQuery = new MatchAllDocsQuery();
/**
* Returns the set of document ids matching all queries. This method is
* cache-aware and attempts to retrieve the answer from the cache if
* possible. If the answer was not cached, it may have been inserted into
* the cache as a result of this call. This method can handle negative
* queries.
* <p>
* The DocSet returned should <b>not</b> be modified.
*/
public DocSet getDocSet(List<Query> queries) throws IOException {
if (queries == null)
return null;
if (queries.size() == 1)
return getDocSet(queries.get(0));
DocSet answer = null;
boolean[] neg = new boolean[queries.size()];
DocSet[] sets = new DocSet[queries.size()];
int smallestIndex = -1;
int smallestCount = Integer.MAX_VALUE;
for (int i = 0; i < sets.length; i++) {
Query q = queries.get(i);
Query posQuery = QueryUtils.getAbs(q);
sets[i] = getPositiveDocSet(posQuery);
// Negative query if absolute value different from original
if (q == posQuery) {
neg[i] = false;
// keep track of the smallest positive set.
// This optimization is only worth it if size() is cached, which
// it would
// be if we don't do any set operations.
int sz = sets[i].size();
if (sz < smallestCount) {
smallestCount = sz;
smallestIndex = i;
answer = sets[i];
}
} else {
neg[i] = true;
}
}
// if no positive queries, start off with all docs
if (answer == null)
answer = getPositiveDocSet(matchAllDocsQuery);
// do negative queries first to shrink set size
for (int i = 0; i < sets.length; i++) {
if (neg[i])
answer = answer.andNot(sets[i]);
}
for (int i = 0; i < sets.length; i++) {
if (!neg[i] && i != smallestIndex)
answer = answer.intersection(sets[i]);
}
return answer;
}
// query must be positive
protected DocSet getDocSetNC(Query query, DocSet filter) throws IOException {
DocSetCollector collector = new DocSetCollector(maxDoc() >> 6, maxDoc());
if (filter == null) {
if (query instanceof TermQuery) {
Term t = ((TermQuery) query).getTerm();
SolrIndexReader[] readers = reader.getLeafReaders();
int[] offsets = reader.getLeafOffsets();
int[] arr = new int[256];
int[] freq = new int[256];
for (int i = 0; i < readers.length; i++) {
SolrIndexReader sir = readers[i];
int offset = offsets[i];
collector.setNextReader(sir, offset);
TermDocs tdocs = sir.termDocs(t);
for (;;) {
int num = tdocs.read(arr, freq);
if (num == 0)
break;
for (int j = 0; j < num; j++) {
collector.collect(arr[j]);
}
}
tdocs.close();
}
} else {
super.search(query, null, collector);
}
return collector.getDocSet();
} else {
Filter luceneFilter = filter.getTopFilter();
super.search(query, luceneFilter, collector);
return collector.getDocSet();
}
}
/**
* Returns the set of document ids matching both the query and the filter.
* This method is cache-aware and attempts to retrieve the answer from the
* cache if possible. If the answer was not cached, it may have been
* inserted into the cache as a result of this call.
* <p>
*
* @param query
* @param filter
* may be null
* @return DocSet meeting the specified criteria, should <b>not</b> be
* modified by the caller.
*/
public DocSet getDocSet(Query query, DocSet filter) throws IOException {
if (filter == null)
return getDocSet(query);
// Negative query if absolute value different from original
Query absQ = QueryUtils.getAbs(query);
boolean positive = absQ == query;
DocSet first;
if (filterCache != null) {
first = (DocSet) filterCache.get(absQ);
if (first == null) {
first = getDocSetNC(absQ, null);
filterCache.put(absQ, first);
}
return positive ? first.intersection(filter) : filter.andNot(first);
}
// If there isn't a cache, then do a single filtered query if positive.
return positive ? getDocSetNC(absQ, filter) : filter
.andNot(getPositiveDocSet(absQ));
}
/**
* Converts a filter into a DocSet. This method is not cache-aware and no
* caches are checked.
*/
public DocSet convertFilter(Filter lfilter) throws IOException {
BitSet bs = lfilter.bits(this.reader);
OpenBitSet obs = new OpenBitSet(bs.size());
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
obs.fastSet(i);
}
return new BitDocSet(obs);
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>.
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocList meeting the specified criteria, should <b>not</b> be
* modified by the caller.
* @throws IOException
*/
public DocList getDocList(Query query, Query filter, Sort lsort,
int offset, int len) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filter).setSort(lsort)
.setOffset(offset).setLen(len);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocList();
}
/**
* Returns documents matching both <code>query</code> and the intersection
* of the <code>filterList</code>, sorted by <code>sort</code>.
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
*
* @param query
* @param filterList
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocList meeting the specified criteria, should <b>not</b> be
* modified by the caller.
* @throws IOException
*/
public DocList getDocList(Query query, List<Query> filterList, Sort lsort,
int offset, int len, int flags) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filterList).setSort(lsort)
.setOffset(offset).setLen(len).setFlags(flags);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocList();
}
private static final int NO_CHECK_QCACHE = 0x80000000;
private static final int GET_DOCSET = 0x40000000;
private static final int NO_CHECK_FILTERCACHE = 0x20000000;
public static final int GET_SCORES = 0x01;
/**
* getDocList version that uses+populates query and filter caches. In the
* event of a timeout, the cache is not populated.
*/
private void getDocListC(QueryResult qr, QueryCommand cmd)
throws IOException {
// old parameters: DocListAndSet out, Query query, List<Query>
// filterList, DocSet filter, Sort lsort, int offset, int len, int
// flags, long timeAllowed, NamedList<Object> responseHeader
DocListAndSet out = new DocListAndSet();
qr.setDocListAndSet(out);
QueryResultKey key = null;
int maxDocRequested = cmd.getOffset() + cmd.getLen();
// check for overflow, and check for # docs in index
if (maxDocRequested < 0 || maxDocRequested > maxDoc())
maxDocRequested = maxDoc();
int supersetMaxDoc = maxDocRequested;
DocList superset = null;
Set terms = new HashSet();
try {
cmd.getQuery().extractTerms(terms);
} catch (Exception ex) {
terms = null;
}
if (terms.size() == 0) {
terms = null;
}
try {
// we can try and look up the complete query in the cache.
// we can't do that if filter!=null though (we don't want to
// do hashCode() and equals() for a big DocSet).
if (queryResultCache != null && cmd.getFilter() == null) {
// all of the current flags can be reused during warming,
// so set all of them on the cache key.
key = new QueryResultKey(cmd.getQuery(), cmd.getFilterList(), cmd.getSort(), cmd.getFlags());
if ((cmd.getFlags() & NO_CHECK_QCACHE) == 0) {
if (queryResultCache instanceof SolrCacheWithReader) {
if (terms != null) {
((SolrCacheWithReader) queryResultCache).setReader(reader.getWrappedReader());
superset = (DocList) ((SolrCacheWithReader) queryResultCache).get(key, terms);
}
} else {
superset = (DocList) queryResultCache.get(key);
}
// current query doesn't exist in our query result cache. so
// do full lucene lookup
if (superset != null) {
// check that the cache entry has scores recorded if we
// need
// them
if ((cmd.getFlags() & GET_SCORES) == 0 || superset.hasScores()) {
// NOTE: subset() returns null if the DocList has
// fewer
// docs than
// requested
out.docList = superset.subset(cmd.getOffset(), cmd.getLen());
}
}
if (out.docList != null) {
// found the docList in the cache... now check if we
// need
// the docset too.
// OPT: possible future optimization - if the doclist
// contains all the matches,
// use it to make the docset instead of rerunning the
// query.
if (out.docSet == null && ((cmd.getFlags() & GET_DOCSET) != 0)) {
if (cmd.getFilterList() == null) {
out.docSet = getDocSet(cmd.getQuery());
} else {
List<Query> newList = new ArrayList<Query>(cmd.getFilterList().size() + 1);
newList.add(cmd.getQuery());
newList.addAll(cmd.getFilterList());
out.docSet = getDocSet(newList);
}
}
return;
}
}
// If we are going to generate the result, bump up to the
// next resultWindowSize for better caching.
// handle 0 special case as well as avoid idiv in the common
// case.
if (maxDocRequested < queryResultWindowSize) {
supersetMaxDoc = queryResultWindowSize;
} else {
supersetMaxDoc = ((maxDocRequested - 1) / queryResultWindowSize + 1) * queryResultWindowSize;
if (supersetMaxDoc < 0)
supersetMaxDoc = maxDocRequested;
}
}
// OK, so now we need to generate an answer.
// One way to do that would be to check if we have an unordered list
// of results for the base query. If so, we can apply the filters
// and
// then
// sort by the resulting set. This can only be used if:
// - the sort doesn't contain score
// - we don't want score returned.
// check if we should try and use the filter cache
boolean useFilterCache = false;
if ((cmd.getFlags() & (GET_SCORES | NO_CHECK_FILTERCACHE)) == 0 && useFilterForSortedQuery && cmd.getSort() != null && filterCache != null) {
useFilterCache = true;
SortField[] sfields = cmd.getSort().getSort();
for (SortField sf : sfields) {
if (sf.getType() == SortField.SCORE) {
useFilterCache = false;
break;
}
}
}
// disable useFilterCache optimization temporarily
if (useFilterCache) {
// now actually use the filter cache.
// for large filters that match few documents, this may be
// slower than simply re-executing the query.
if (out.docSet == null) {
out.docSet = getDocSet(cmd.getQuery(), cmd.getFilter());
DocSet bigFilt = getDocSet(cmd.getFilterList());
if (bigFilt != null)
out.docSet = out.docSet.intersection(bigFilt);
}
// todo: there could be a sortDocSet that could take a list of
// the filters instead of anding them first...
// perhaps there should be a multi-docset-iterator
superset = sortDocSet(out.docSet, cmd.getSort(), supersetMaxDoc);
out.docList = superset.subset(cmd.getOffset(), cmd.getLen());
// lastly, put the superset in the cache if the size is less
// than or
// equal
// to queryResultMaxDocsCached
if (key != null && superset.size() <= queryResultMaxDocsCached && !qr.isPartialResults()) {
if (queryResultCache instanceof SolrCacheWithReader) {
if (terms != null) {
((SolrCacheWithReader) queryResultCache).put(key, superset, terms);
}
} else {
queryResultCache.put(key, superset);
}
}
} else {
// do it the normal way...
// TODO: need to implement term locking here
try {
if (queryResultCache instanceof SolrCacheWithReader) {
((SolrCacheWithReader) queryResultCache).acquireLock(terms);
}
cmd.setSupersetMaxDoc(supersetMaxDoc);
if ((cmd.getFlags() & GET_DOCSET) != 0) {
// this currently conflates returning the docset for the
// base
// query vs
// the base query and all filters.
DocSet qDocSet = getDocListAndSetNC(qr, cmd);
// cache the docSet matching the query w/o filtering
if (qDocSet != null && filterCache != null && !qr.isPartialResults())
filterCache.put(cmd.getQuery(), qDocSet);
} else {
getDocListNC(qr, cmd);
// Parameters:
// cmd.getQuery(),theFilt,cmd.getSort(),0,supersetMaxDoc,cmd.getFlags(),cmd.getTimeAllowed(),responseHeader);
}
superset = out.docList;
out.docList = superset.subset(cmd.getOffset(), cmd.getLen());
// lastly, put the superset in the cache if the size is less
// than or
// equal
// to queryResultMaxDocsCached
if (key != null && superset.size() <= queryResultMaxDocsCached && !qr.isPartialResults()) {
if (queryResultCache instanceof SolrCacheWithReader) {
if (terms != null) {
((SolrCacheWithReader) queryResultCache).put(key, superset, terms);
}
} else {
queryResultCache.put(key, superset);
}
}
} finally {
// TODO: hacky, it requires to always have
// SolrCacheWithReader configured on instance of Solbase
if (queryResultCache instanceof SolrCacheWithReader) {
((SolrCacheWithReader) queryResultCache).releaseLock(terms);
}
}
}
} finally {
if (queryResultCache != null && queryResultCache instanceof SolrCacheWithReader) {
((SolrCacheWithReader) queryResultCache).flushThreadLocalCache();
}
}
}
private void getDocListNC(QueryResult qr, QueryCommand cmd)
throws IOException {
// Parameters:
// cmd.getQuery(),theFilt,cmd.getSort(),0,supersetMaxDoc,cmd.getFlags(),cmd.getTimeAllowed(),responseHeader);
// Query query, DocSet filter, Sort lsort, int offset, int len, int
// flags, long timeAllowed, NamedList<Object> responseHeader
// in case of filter is by solbaseIndexedInt field, we don't want to create DocSet, we can skip all of this
//DocSet filter = cmd.getFilter() != null ? cmd.getFilter()
// : getDocSet(cmd.getFilterList());
// hmm, i'm not seeing getFilter returning obj at all, maybe it's deprecated?
// so then I need to create embedded doc set that's going to support multiple embeddedfieldfilter
DocSet filter = null;
if(cmd.getFilter() != null){
filter = cmd.getFilter();
} else if(cmd.getFilterList() != null){
List<Query> filterList = cmd.getFilterList();
filter = new EmbeddedDocSet();
for (Query q : filterList) {
// if range query get upper and lower bound
if (q instanceof TermRangeQuery) {
TermRangeQuery trq = (TermRangeQuery) q;
SchemaField sf = schema.getField(trq.getField());
if (sf.getType() instanceof EmbeddedIndexedIntField) {
EmbeddedIndexedIntField siif = (EmbeddedIndexedIntField) sf.getType();
int lowerBound, upperBound;
if (trq.getUpperTerm() == null) {
upperBound = Integer.MAX_VALUE;
} else {
upperBound = new Integer(trq.getUpperTerm());
}
if (trq.getLowerTerm() == null) {
lowerBound = 0;
} else {
lowerBound = new Integer(trq.getLowerTerm());
}
((EmbeddedDocSet)filter).addFilter(lowerBound, upperBound, siif.getFieldNumber());
} else {
// throw unsupported exception
throw new UnsupportedOperationException();
}
} else if (q instanceof TermQuery){
TermQuery tq = (TermQuery) q;
SchemaField sf = schema.getField(tq.getField());
if (sf.getType() instanceof EmbeddedIndexedIntField) {
EmbeddedIndexedIntField siif = (EmbeddedIndexedIntField) sf.getType();
// need to convert string to interger mapping here
String term = tq.getTerm().toString();
((EmbeddedDocSet)filter).addFilter(siif.getStringToInt(term), siif.getFieldNumber());
} else {
throw new UnsupportedOperationException();
}
}
}
// nullify EmbeddedDocSet in case it didn't have any of embedded filter created
// this will only happen if unsupported operation exception is thrown though.
if(((EmbeddedDocSet) filter).isEmptyFilter()){
filter = null;
}
}
final long timeAllowed = cmd.getTimeAllowed();
int len = cmd.getSupersetMaxDoc();
int last = len;
if (last < 0 || last > maxDoc())
last = maxDoc();
final int lastDocRequested = last;
int nDocsReturned;
int totalHits;
float maxScore;
int[] ids;
int[][] sorts;
float[] scores;
boolean needScores = (cmd.getFlags() & GET_SCORES) != 0;
Query query = QueryUtils.makeQueryable(cmd.getQuery());
final Filter luceneFilter = filter == null ? null : filter
.getTopFilter();
// handle zero case...
if (lastDocRequested <= 0) {
final float[] topscore = new float[] { Float.NEGATIVE_INFINITY };
final int[] numHits = new int[1];
Collector collector;
if (!needScores) {
collector = new Collector() {
public void setScorer(Scorer scorer) throws IOException {
}
public void collect(int doc, int[] sorts)
throws IOException {
numHits[0]++;
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
}
public boolean acceptsDocsOutOfOrder() {
return true;
}
};
} else {
collector = new Collector() {
Scorer scorer;
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;
}
public void collect(int doc, int[] sorts)
throws IOException {
numHits[0]++;
float score = scorer.score();
if (score > topscore[0])
topscore[0] = score;
}
public void setNextReader(IndexReader reader, int docBase)
throws IOException {
}
public boolean acceptsDocsOutOfOrder() {
return true;
}
};
}
if (timeAllowed > 0) {
collector = new TimeLimitingCollector(collector, timeAllowed);
}
try {
super.search(query, luceneFilter, collector);
} catch (TimeLimitingCollector.TimeExceededException x) {
log.warn("Query: " + query + "; " + x.getMessage());
qr.setPartialResults(true);
}
nDocsReturned = 0;
ids = new int[nDocsReturned];
sorts = new int[nDocsReturned][Scorer.numSort];
scores = new float[nDocsReturned];
totalHits = numHits[0];
maxScore = totalHits > 0 ? topscore[0] : 0.0f;
} else {
TopDocsCollector topCollector;
if (cmd.getSort() == null) {
topCollector = TopScoreDocCollector.create(len, true);
} else {
topCollector = TopFieldCollector.create(cmd.getSort(), len,
false, needScores, needScores, true);
}
Collector collector = topCollector;
if (timeAllowed > 0) {
collector = new TimeLimitingCollector(collector, timeAllowed);
}
try {
super.search(query, luceneFilter, collector);
} catch (TimeLimitingCollector.TimeExceededException x) {
log.warn("Query: " + query + "; " + x.getMessage());
qr.setPartialResults(true);
}
totalHits = topCollector.getTotalHits();
TopDocs topDocs = topCollector.topDocs(0, len);
maxScore = totalHits > 0 ? topDocs.getMaxScore() : 0.0f;
nDocsReturned = topDocs.scoreDocs.length;
ids = new int[nDocsReturned];
sorts = new int[nDocsReturned][Scorer.numSort];
scores = (cmd.getFlags() & GET_SCORES) != 0 ? new float[nDocsReturned]
: null;
for (int i = 0; i < nDocsReturned; i++) {
ScoreDoc scoreDoc = topDocs.scoreDocs[i];
ids[i] = scoreDoc.doc;
sorts[i] = scoreDoc.sorts;
if (scores != null)
scores[i] = scoreDoc.score;
}
}
int sliceLen = Math.min(lastDocRequested, nDocsReturned);
if (sliceLen < 0)
sliceLen = 0;
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits,
maxScore, sorts));
}
// any DocSet returned is for the query only, without any filtering... that
// way it may
// be cached if desired.
private DocSet getDocListAndSetNC(QueryResult qr, QueryCommand cmd)
throws IOException {
int len = cmd.getSupersetMaxDoc();
DocSet filter = cmd.getFilter() != null ? cmd.getFilter()
: getDocSet(cmd.getFilterList());
int last = len;
if (last < 0 || last > maxDoc())
last = maxDoc();
final int lastDocRequested = last;
int nDocsReturned;
int totalHits;
float maxScore;
int[] ids;
int[][] sorts;
float[] scores;
DocSet set;
boolean needScores = (cmd.getFlags() & GET_SCORES) != 0;
int maxDoc = maxDoc();
int smallSetSize = maxDoc >> 6;
Query query = QueryUtils.makeQueryable(cmd.getQuery());
final long timeAllowed = cmd.getTimeAllowed();
final Filter luceneFilter = filter == null ? null : filter
.getTopFilter();
// handle zero case...
if (lastDocRequested <= 0) {
final float[] topscore = new float[] { Float.NEGATIVE_INFINITY };
Collector collector;
DocSetCollector setCollector;
if (!needScores) {
collector = setCollector = new DocSetCollector(smallSetSize,
maxDoc);
} else {
collector = setCollector = new DocSetDelegateCollector(
smallSetSize, maxDoc, new Collector() {
Scorer scorer;
public void setScorer(Scorer scorer)
throws IOException {
this.scorer = scorer;
}
public void collect(int doc, int[] sorts)
throws IOException {
float score = scorer.score();
if (score > topscore[0])
topscore[0] = score;
}
public void setNextReader(IndexReader reader,
int docBase) throws IOException {
}
public boolean acceptsDocsOutOfOrder() {
return false;
}
});
}
if (timeAllowed > 0) {
collector = new TimeLimitingCollector(collector, timeAllowed);
}
try {
super.search(query, luceneFilter, collector);
} catch (TimeLimitingCollector.TimeExceededException x) {
log.warn("Query: " + query + "; " + x.getMessage());
qr.setPartialResults(true);
}
set = setCollector.getDocSet();
nDocsReturned = 0;
ids = new int[nDocsReturned];
sorts = new int[nDocsReturned][Scorer.numSort];
scores = new float[nDocsReturned];
totalHits = set.size();
maxScore = totalHits > 0 ? topscore[0] : 0.0f;
} else {
TopDocsCollector topCollector;
if (cmd.getSort() == null) {
topCollector = TopScoreDocCollector.create(len, true);
} else {
topCollector = TopFieldCollector.create(cmd.getSort(), len,
false, needScores, needScores, true);
}
DocSetCollector setCollector = new DocSetDelegateCollector(
maxDoc >> 6, maxDoc, topCollector);
Collector collector = setCollector;
if (timeAllowed > 0) {
collector = new TimeLimitingCollector(collector, timeAllowed);
}
try {
super.search(query, luceneFilter, collector);
} catch (TimeLimitingCollector.TimeExceededException x) {
log.warn("Query: " + query + "; " + x.getMessage());
qr.setPartialResults(true);
}
set = setCollector.getDocSet();
totalHits = topCollector.getTotalHits();
assert (totalHits == set.size());
TopDocs topDocs = topCollector.topDocs(0, len);
maxScore = totalHits > 0 ? topDocs.getMaxScore() : 0.0f;
nDocsReturned = topDocs.scoreDocs.length;
ids = new int[nDocsReturned];
sorts = new int[nDocsReturned][Scorer.numSort];
scores = (cmd.getFlags() & GET_SCORES) != 0 ? new float[nDocsReturned]
: null;
for (int i = 0; i < nDocsReturned; i++) {
ScoreDoc scoreDoc = topDocs.scoreDocs[i];
ids[i] = scoreDoc.doc;
sorts[i] = scoreDoc.sorts;
if (scores != null)
scores[i] = scoreDoc.score;
}
}
int sliceLen = Math.min(lastDocRequested, nDocsReturned);
if (sliceLen < 0)
sliceLen = 0;
qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits,
maxScore, sorts));
// TODO: if we collect results before the filter, we just need to
// intersect with
// that filter to generate the DocSet for qr.setDocSet()
qr.setDocSet(set);
// TODO: currently we don't generate the DocSet for the base query,
// but the QueryDocSet == CompleteDocSet if filter==null.
return filter == null ? qr.getDocSet() : null;
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>. FUTURE: The returned
* DocList may be retrieved from a cache.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocList meeting the specified criteria, should <b>not</b> be
* modified by the caller.
* @throws IOException
*/
public DocList getDocList(Query query, DocSet filter, Sort lsort,
int offset, int len) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilter(filter).setSort(lsort).setOffset(offset)
.setLen(len);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocList();
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>. Also returns the
* complete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
* <p>
* The DocList and DocSet returned should <b>not</b> be modified.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, Query filter,
Sort lsort, int offset, int len) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filter).setSort(lsort)
.setOffset(offset).setLen(len).setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>. Also returns the
* compete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
* <p>
* The DocList and DocSet returned should <b>not</b> be modified.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @param flags
* user supplied flags for the result set
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, Query filter,
Sort lsort, int offset, int len, int flags) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filter).setSort(lsort)
.setOffset(offset).setLen(len).setFlags(flags)
.setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
/**
* Returns documents matching both <code>query</code> and the intersection
* of <code>filterList</code>, sorted by <code>sort</code>. Also returns the
* compete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
* <p>
* The DocList and DocSet returned should <b>not</b> be modified.
*
* @param query
* @param filterList
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, List<Query> filterList,
Sort lsort, int offset, int len) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filterList).setSort(lsort)
.setOffset(offset).setLen(len).setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
/**
* Returns documents matching both <code>query</code> and the intersection
* of <code>filterList</code>, sorted by <code>sort</code>. Also returns the
* compete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* This method is cache aware and may retrieve <code>filter</code> from the
* cache or make an insertion into the cache as a result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
* <p>
* The DocList and DocSet returned should <b>not</b> be modified.
*
* @param query
* @param filterList
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @param flags
* user supplied flags for the result set
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, List<Query> filterList,
Sort lsort, int offset, int len, int flags) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilterList(filterList).setSort(lsort)
.setOffset(offset).setLen(len).setFlags(flags)
.setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>. Also returns the
* compete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, DocSet filter,
Sort lsort, int offset, int len) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilter(filter).setSort(lsort).setOffset(offset)
.setLen(len).setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
/**
* Returns documents matching both <code>query</code> and
* <code>filter</code> and sorted by <code>sort</code>. Also returns the
* compete set of documents matching <code>query</code> and
* <code>filter</code> (regardless of <code>offset</code> and
* <code>len</code>).
* <p>
* This method is cache aware and may make an insertion into the cache as a
* result of this call.
* <p>
* FUTURE: The returned DocList may be retrieved from a cache.
* <p>
* The DocList and DocSet returned should <b>not</b> be modified.
*
* @param query
* @param filter
* may be null
* @param lsort
* criteria by which to sort (if null, query relevance is used)
* @param offset
* offset into the list of documents to return
* @param len
* maximum number of documents to return
* @param flags
* user supplied flags for the result set
* @return DocListAndSet meeting the specified criteria, should <b>not</b>
* be modified by the caller.
* @throws IOException
*/
public DocListAndSet getDocListAndSet(Query query, DocSet filter,
Sort lsort, int offset, int len, int flags) throws IOException {
QueryCommand qc = new QueryCommand();
qc.setQuery(query).setFilter(filter).setSort(lsort).setOffset(offset)
.setLen(len).setFlags(flags).setNeedDocSet(true);
QueryResult qr = new QueryResult();
search(qr, qc);
return qr.getDocListAndSet();
}
protected DocList sortDocSet(DocSet set, Sort sort, int nDocs)
throws IOException {
// bit of a hack to tell if a set is sorted - do it better in the
// futute.
boolean inOrder = set instanceof BitDocSet
|| set instanceof SortedIntDocSet;
TopDocsCollector topCollector = TopFieldCollector.create(sort, nDocs,
false, false, false, inOrder);
DocIterator iter = set.iterator();
int base = 0;
int end = 0;
int readerIndex = -1;
SolrIndexReader r = null;
while (iter.hasNext()) {
int doc = iter.nextDoc();
while (doc >= end) {
r = reader.getLeafReaders()[++readerIndex];
base = reader.getLeafOffsets()[readerIndex];
end = base + r.maxDoc();
topCollector.setNextReader(r, base);
// we should never need to set the scorer given the settings for
// the collector
}
topCollector.collect(doc - base, null);
}
TopDocs topDocs = topCollector.topDocs(0, nDocs);
int nDocsReturned = topDocs.scoreDocs.length;
int[] ids = new int[nDocsReturned];
int[][] sorts = new int[nDocsReturned][Scorer.numSort];
for (int i = 0; i < nDocsReturned; i++) {
ScoreDoc scoreDoc = topDocs.scoreDocs[i];
ids[i] = scoreDoc.doc;
sorts[i] = scoreDoc.sorts;
}
return new DocSlice(0, nDocsReturned, ids, null, topDocs.totalHits,
0.0f, sorts);
}
/**
* Returns the number of documents that match both <code>a</code> and
* <code>b</code>.
* <p>
* This method is cache-aware and may check as well as modify the cache.
*
* @param a
* @param b
* @return the numer of documents in the intersection between <code>a</code>
* and <code>b</code>.
* @throws IOException
*/
public int numDocs(Query a, DocSet b) throws IOException {
// Negative query if absolute value different from original
Query absQ = QueryUtils.getAbs(a);
DocSet positiveA = getPositiveDocSet(absQ);
return a == absQ ? b.intersectionSize(positiveA) : b
.andNotSize(positiveA);
}
/**
* Returns the number of documents that match both <code>a</code> and
* <code>b</code>.
* <p>
* This method is cache-aware and may check as well as modify the cache.
*
* @param a
* @param b
* @return the numer of documents in the intersection between <code>a</code>
* and <code>b</code>.
* @throws IOException
*/
public int numDocs(Query a, Query b) throws IOException {
Query absA = QueryUtils.getAbs(a);
Query absB = QueryUtils.getAbs(b);
DocSet positiveA = getPositiveDocSet(absA);
DocSet positiveB = getPositiveDocSet(absB);
// Negative query if absolute value different from original
if (a == absA) {
if (b == absB)
return positiveA.intersectionSize(positiveB);
return positiveA.andNotSize(positiveB);
}
if (b == absB)
return positiveB.andNotSize(positiveA);
// if both negative, we need to create a temp DocSet since we
// don't have a counting method that takes three.
DocSet all = getPositiveDocSet(matchAllDocsQuery);
// -a -b == *:*.andNot(a).andNotSize(b) == *.*.andNotSize(a.union(b))
// we use the last form since the intermediate DocSet should normally be
// smaller.
return all.andNotSize(positiveA.union(positiveB));
}
/**
* Takes a list of docs (the doc ids actually), and returns an array of
* Documents containing all of the stored fields.
*/
public Document[] readDocs(DocList ids) throws IOException {
Document[] docs = new Document[ids.size()];
readDocs(docs, ids);
return docs;
}
/**
* Warm this searcher based on an old one (primarily for auto-cache
* warming).
*/
public void warm(SolrIndexSearcher old) throws IOException {
// Make sure this is first! filters can help queryResults execute!
boolean logme = log.isInfoEnabled();
long warmingStartTime = System.currentTimeMillis();
// warm the caches in order...
for (int i = 0; i < cacheList.length; i++) {
if (logme)
log.info("autowarming " + this + " from " + old + "\n\t"
+ old.cacheList[i]);
this.cacheList[i].warm(this, old.cacheList[i]);
if (logme)
log.info("autowarming result for " + this + "\n\t"
+ this.cacheList[i]);
}
warmupTime = System.currentTimeMillis() - warmingStartTime;
}
/**
* return the named generic cache
*/
public SolrCache getCache(String cacheName) {
return cacheMap.get(cacheName);
}
/**
* lookup an entry in a generic cache
*/
public Object cacheLookup(String cacheName, Object key) {
SolrCache cache = cacheMap.get(cacheName);
return cache == null ? null : cache.get(key);
}
/**
* insert an entry in a generic cache
*/
public Object cacheInsert(String cacheName, Object key, Object val) {
SolrCache cache = cacheMap.get(cacheName);
return cache == null ? null : cache.put(key, val);
}
public long getOpenTime() {
return openTime;
}
// ///////////////////////////////////////////////////////////////////
// SolrInfoMBean stuff: Statistics and Module Info
// ///////////////////////////////////////////////////////////////////
public String getName() {
return SolrIndexSearcher.class.getName();
}
public String getVersion() {
return SolrCore.version;
}
public String getDescription() {
return "index searcher";
}
public Category getCategory() {
return Category.CORE;
}
public String getSourceId() {
return "$Id: SolrIndexSearcher.java 794328 2009-07-15 17:21:04Z shalin $";
}
public String getSource() {
return "$URL: https://svn.apache.org/repos/asf/lucene/solr/branches/branch-1.4/src/java/org/apache/solr/search/SolrIndexSearcher.java $";
}
public URL[] getDocs() {
return null;
}
public NamedList getStatistics() {
NamedList lst = new SimpleOrderedMap();
lst.add("searcherName", name);
lst.add("caching", cachingEnabled);
lst.add("numDocs", reader.numDocs());
lst.add("maxDoc", reader.maxDoc());
lst.add("reader", reader.toString());
lst.add("readerDir", reader.directory());
lst.add("indexVersion", reader.getVersion());
lst.add("openedAt", new Date(openTime));
if (registerTime != 0)
lst.add("registeredAt", new Date(registerTime));
lst.add("warmupTime", warmupTime);
return lst;
}
/**
* A query request command to avoid having to change the method signatures
* if we want to pass additional information to the searcher.
*/
public static class QueryCommand {
private Query query;
private List<Query> filterList;
private DocSet filter;
private Sort sort;
private int offset;
private int len;
private int supersetMaxDoc;
private int flags;
private long timeAllowed = -1;
public Query getQuery() {
return query;
}
public QueryCommand setQuery(Query query) {
this.query = query;
return this;
}
public List<Query> getFilterList() {
return filterList;
}
/**
* @throws IllegalArgumentException
* if filter is not null.
*/
public QueryCommand setFilterList(List<Query> filterList) {
if (filter != null) {
throw new IllegalArgumentException(
"Either filter or filterList may be set in the QueryCommand, but not both.");
}
this.filterList = filterList;
return this;
}
/**
* A simple setter to build a filterList from a query
*
* @throws IllegalArgumentException
* if filter is not null.
*/
public QueryCommand setFilterList(Query f) {
if (filter != null) {
throw new IllegalArgumentException(
"Either filter or filterList may be set in the QueryCommand, but not both.");
}
filterList = null;
if (f != null) {
filterList = new ArrayList<Query>(2);
filterList.add(f);
}
return this;
}
public DocSet getFilter() {
return filter;
}
/**
* @throws IllegalArgumentException
* if filterList is not null.
*/
public QueryCommand setFilter(DocSet filter) {
if (filterList != null) {
throw new IllegalArgumentException(
"Either filter or filterList may be set in the QueryCommand, but not both.");
}
this.filter = filter;
return this;
}
public Sort getSort() {
return sort;
}
public QueryCommand setSort(Sort sort) {
this.sort = sort;
return this;
}
public int getOffset() {
return offset;
}
public QueryCommand setOffset(int offset) {
this.offset = offset;
return this;
}
public int getLen() {
return len;
}
public QueryCommand setLen(int len) {
this.len = len;
return this;
}
public int getSupersetMaxDoc() {
return supersetMaxDoc;
}
public QueryCommand setSupersetMaxDoc(int supersetMaxDoc) {
this.supersetMaxDoc = supersetMaxDoc;
return this;
}
public int getFlags() {
return flags;
}
public QueryCommand replaceFlags(int flags) {
this.flags = flags;
return this;
}
public QueryCommand setFlags(int flags) {
this.flags |= flags;
return this;
}
public QueryCommand clearFlags(int flags) {
this.flags &= ~flags;
return this;
}
public long getTimeAllowed() {
return timeAllowed;
}
public QueryCommand setTimeAllowed(long timeAllowed) {
this.timeAllowed = timeAllowed;
return this;
}
public boolean isNeedDocSet() {
return (flags & GET_DOCSET) != 0;
}
public QueryCommand setNeedDocSet(boolean needDocSet) {
return needDocSet ? setFlags(GET_DOCSET) : clearFlags(GET_DOCSET);
}
}
/**
* The result of a search.
*/
public static class QueryResult {
private boolean partialResults;
private DocListAndSet docListAndSet;
public DocList getDocList() {
return docListAndSet.docList;
}
public void setDocList(DocList list) {
if (docListAndSet == null) {
docListAndSet = new DocListAndSet();
}
docListAndSet.docList = list;
}
public DocSet getDocSet() {
return docListAndSet.docSet;
}
public void setDocSet(DocSet set) {
if (docListAndSet == null) {
docListAndSet = new DocListAndSet();
}
docListAndSet.docSet = set;
}
public boolean isPartialResults() {
return partialResults;
}
public void setPartialResults(boolean partialResults) {
this.partialResults = partialResults;
}
public void setDocListAndSet(DocListAndSet listSet) {
docListAndSet = listSet;
}
public DocListAndSet getDocListAndSet() {
return docListAndSet;
}
}
private int mapStringFieldToInt(String term, String fieldName){
return -1;
}
}