blob: 8a3b8e31ae618b663c77673308b8b664de6b62c3 [file] [log] [blame]
package org.apache.lucene.index;
/*
* 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.
*/
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.codecs.StoredFieldsReader;
import org.apache.lucene.codecs.TermVectorsReader;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.util.Bits;
// javadocs
/**
* IndexReader implementation over a single segment.
* <p>
* Instances pointing to the same segment (but with different deletes, etc)
* may share the same core data.
* @lucene.experimental
*/
public final class SegmentReader extends AtomicReader {
private final SegmentInfoPerCommit si;
private final Bits liveDocs;
// Normally set to si.docCount - si.delDocCount, unless we
// were created as an NRT reader from IW, in which case IW
// tells us the docCount:
private final int numDocs;
final SegmentCoreReaders core;
final SegmentCoreReaders[] updates;
private final IOContext context;
private Fields fields;
private FieldInfos fieldInfos;
private StoredFieldsReader fieldsReader;
private TermVectorsReader termVectorsReader;
private Map<String,FieldGenerationReplacements> replacementsMap;
/**
* Constructs a new SegmentReader with a new core.
* @throws CorruptIndexException if the index is corrupt
* @throws IOException if there is a low-level IO error
*/
// TODO: why is this public?
public SegmentReader(SegmentInfoPerCommit si, int termInfosIndexDivisor, IOContext context) throws IOException {
this.si = si;
this.context = context;
core = new SegmentCoreReaders(this, si.info, -1, context, termInfosIndexDivisor);
updates = initUpdates(si, termInfosIndexDivisor, context);
boolean success = false;
try {
if (si.hasDeletions()) {
// NOTE: the bitvector is stored using the regular directory, not cfs
liveDocs = si.info.getCodec().liveDocsFormat().readLiveDocs(directory(), si, new IOContext(IOContext.READ, true));
} else {
assert si.getDelCount() == 0;
liveDocs = null;
}
numDocs = si.info.getDocCount() - si.getDelCount();
success = true;
} finally {
// With lock-less commits, it's entirely possible (and
// fine) to hit a FileNotFound exception above. In
// this case, we want to explicitly close any subset
// of things that were opened so that we don't have to
// wait for a GC to do so.
if (!success) {
core.decRef();
if (updates != null) {
for (int i = 0; i < updates.length; i++) {
updates[i].decRef();
}
}
}
}
}
/**
* Create new SegmentReader sharing core from a previous SegmentReader and
* loading new live docs from a new deletes file. Used by openIfChanged.
*/
SegmentReader(SegmentInfoPerCommit si, SegmentCoreReaders core,
SegmentCoreReaders[] updates, IOContext context) throws IOException {
this(si, context, core, updates, si.info.getCodec().liveDocsFormat()
.readLiveDocs(si.info.dir, si, context), si.info.getDocCount()
- si.getDelCount());
}
/**
* Create new SegmentReader sharing core from a previous SegmentReader and
* using the provided in-memory liveDocs. Used by IndexWriter to provide a new
* NRT reader
*/
SegmentReader(SegmentInfoPerCommit si, IOContext context,
SegmentCoreReaders core, SegmentCoreReaders[] updates, Bits liveDocs, int numDocs) {
this.si = si;
this.context = context;
this.core = core;
core.incRef();
this.updates = updates;
// TODO : handle NRT updates, add field liveUpdates
assert liveDocs != null;
this.liveDocs = liveDocs;
this.numDocs = numDocs;
}
private SegmentCoreReaders[] initUpdates(SegmentInfoPerCommit si, int termInfosIndexDivisor,
IOContext context) throws IOException {
if (si.hasUpdates()) {
SegmentCoreReaders[] newUpdates = new SegmentCoreReaders[(int) si
.getUpdateGen()];
for (int i = 0; i < newUpdates.length; i++) {
newUpdates[i] = new SegmentCoreReaders(this, si.info, i + 1, context,
termInfosIndexDivisor);
}
return newUpdates;
}
return null;
}
@Override
public Bits getLiveDocs() {
ensureOpen();
return liveDocs;
}
@Override
protected void doClose() throws IOException {
//System.out.println("SR.close seg=" + si);
core.decRef();
if (updates != null) {
for (int i = 0; i < updates.length; i++) {
updates[i].decRef();
}
}
}
@Override
public FieldInfos getFieldInfos() {
ensureOpen();
if (updates == null) {
return core.fieldInfos;
}
// need to create FieldInfos combining core and updates infos
final FieldInfos.Builder builder = new FieldInfos.Builder();
builder.add(core.fieldInfos);
for (final SegmentCoreReaders update : updates) {
builder.add(update.fieldInfos);
}
fieldInfos = builder.finish();
return fieldInfos;
}
/**
* Expert: retrieve thread-private {@link StoredFieldsReader}
*
* @lucene.internal
*/
public StoredFieldsReader getFieldsReader() throws IOException {
ensureOpen();
if (updates == null) {
return core.fieldsReaderLocal.get();
}
synchronized (updates) {
if (fieldsReader == null) {
// generate readers array
StoredFieldsReader[] allReaders = new StoredFieldsReader[updates.length + 1];
allReaders[0] = core.fieldsReaderLocal.get();
for (int i = 0; i < updates.length; i++) {
allReaders[i + 1] = updates[i].fieldsReaderLocal.get();
}
// generate replacements map
if (replacementsMap == null) {
generateReplacementsMap();
}
fieldsReader = new StackedStoredFieldsReader(allReaders,
replacementsMap);
}
}
return fieldsReader;
}
private synchronized void generateReplacementsMap() throws IOException {
if (replacementsMap == null) {
Set<String> visitedFields = new HashSet<String>();
replacementsMap = new HashMap<String,FieldGenerationReplacements>();
for (String field : core.fields) {
addReplacements(field);
visitedFields.add(field);
}
for (int i = 0; i < updates.length; i++) {
for (String field : updates[i].fields) {
if (!visitedFields.contains(field)) {
addReplacements(field);
visitedFields.add(field);
}
}
}
}
}
private void addReplacements(String field) throws IOException {
final FieldGenerationReplacements replacements = si.info.getCodec()
.generationReplacementsFormat()
.readGenerationReplacements(field, si, context);
if (replacements != null) {
replacementsMap.put(field, replacements);
}
}
@Override
public void document(int docID, StoredFieldVisitor visitor) throws IOException {
checkBounds(docID);
getFieldsReader().visitDocument(docID, visitor, null);
}
@Override
public Fields fields() throws IOException {
ensureOpen();
if (fields == null) {
if (updates == null || updates.length == 0) {
return core.fields;
}
// generate fields array
Fields[] fieldsArray = new Fields[updates.length + 1];
fieldsArray[0] = core.fields;
for (int i = 0; i < updates.length; i++) {
fieldsArray[i + 1] = updates[i].fields;
}
// generate replacements map
if (replacementsMap == null) {
generateReplacementsMap();
}
fields = new StackedFields(fieldsArray, replacementsMap, -1);
}
return fields;
}
@Override
public int numDocs() {
// Don't call ensureOpen() here (it could affect performance)
return numDocs;
}
@Override
public int maxDoc() {
// Don't call ensureOpen() here (it could affect performance)
return si.info.getDocCount();
}
/**
* Expert: retrieve thread-private {@link TermVectorsReader}
*
* @lucene.internal
*/
public TermVectorsReader getTermVectorsReader() throws IOException {
ensureOpen();
if (updates == null) {
return core.termVectorsLocal.get();
}
if (termVectorsReader == null) {
setStackedTermVectorsReader();
}
return termVectorsReader;
}
private synchronized void setStackedTermVectorsReader() throws IOException {
if (termVectorsReader == null) {
// generate readers array
TermVectorsReader[] tvReaders = new TermVectorsReader[updates.length + 1];
tvReaders[0] = core.termVectorsLocal.get();
for (int i = 0; i < updates.length; i++) {
tvReaders[i + 1] = updates[i].termVectorsLocal.get();
}
// generate replacements map
if (replacementsMap == null) {
generateReplacementsMap();
}
termVectorsReader = new StackedTermVectorsReader(tvReaders,
replacementsMap, -1);
}
}
private void checkBounds(int docID) {
if (docID < 0 || docID >= maxDoc()) {
throw new IndexOutOfBoundsException("docID must be >= 0 and < maxDoc="
+ maxDoc() + " (got docID=" + docID + ")");
}
}
@Override
public Fields getTermVectors(int docID) throws IOException {
ensureOpen();
if (updates == null) {
// no updates, delegate to core
checkBounds(docID);
final TermVectorsReader coreReader = core.termVectorsLocal.get();
if (coreReader == null) {
return null;
}
return coreReader.get(docID);
}
// generate fields array, only fields of the given docID
Fields[] fields = new Fields[updates.length + 1];
final TermVectorsReader coreReader = core.termVectorsLocal.get();
if (coreReader != null) {
checkBounds(docID);
fields[0] = coreReader.get(docID);
}
for (int i = 0; i < updates.length; i++) {
final TermVectorsReader updateReader = updates[i].termVectorsLocal.get();
if (updateReader != null) {
checkBounds(docID);
fields[i + 1] = updateReader.get(docID);
}
}
// generate replacements map
if (replacementsMap == null) {
generateReplacementsMap();
}
return new StackedFields(fields, replacementsMap, docID);
}
@Override
public String toString() {
// SegmentInfo.toString takes dir and number of
// *pending* deletions; so we reverse compute that here:
return si.toString(si.info.dir, si.info.getDocCount() - numDocs - si.getDelCount());
}
/**
* Return the name of the segment this reader is reading.
*/
public String getSegmentName() {
return si.info.name;
}
/**
* Return the SegmentInfoPerCommit of the segment this reader is reading.
*/
public SegmentInfoPerCommit getSegmentInfo() {
return si;
}
/** Returns the directory this index resides in. */
public Directory directory() {
// Don't ensureOpen here -- in certain cases, when a
// cloned/reopened reader needs to commit, it may call
// this method on the closed original reader
return si.info.dir;
}
// This is necessary so that cloned SegmentReaders (which
// share the underlying postings data) will map to the
// same entry in the FieldCache. See LUCENE-1579.
@Override
public Object getCoreCacheKey() {
return core;
}
@Override
public Object getCombinedCoreAndDeletesKey() {
return this;
}
/** Returns term infos index divisor originally passed to
* {@link #SegmentReader(SegmentInfoPerCommit, int, IOContext)}. */
public int getTermInfosIndexDivisor() {
return core.termsIndexDivisor;
}
@Override
public NumericDocValues getNumericDocValues(String field) throws IOException {
ensureOpen();
return core.getNumericDocValues(field);
}
@Override
public BinaryDocValues getBinaryDocValues(String field) throws IOException {
ensureOpen();
return core.getBinaryDocValues(field);
}
@Override
public SortedDocValues getSortedDocValues(String field) throws IOException {
ensureOpen();
return core.getSortedDocValues(field);
}
@Override
public SortedSetDocValues getSortedSetDocValues(String field) throws IOException {
ensureOpen();
return core.getSortedSetDocValues(field);
}
@Override
public NumericDocValues getNormValues(String field) throws IOException {
ensureOpen();
NumericDocValues normValues = core.getNormValues(field);
if (updates != null) {
for (final SegmentCoreReaders updateReader : updates) {
NumericDocValues updateNormValues = updateReader.getNormValues(field);
if (updateNormValues != null) {
normValues = updateNormValues;
}
}
}
return normValues;
}
/**
* Called when the shared core for this SegmentReader
* is closed.
* <p>
* This listener is called only once all SegmentReaders
* sharing the same core are closed. At this point it
* is safe for apps to evict this reader from any caches
* keyed on {@link #getCoreCacheKey}. This is the same
* interface that {@link FieldCache} uses, internally,
* to evict entries.</p>
*
* @lucene.experimental
*/
public static interface CoreClosedListener {
/** Invoked when the shared core of the provided {@link
* SegmentReader} has closed. */
public void onClose(SegmentReader owner);
}
/** Expert: adds a CoreClosedListener to this reader's shared core */
public void addCoreClosedListener(CoreClosedListener listener) {
ensureOpen();
core.addCoreClosedListener(listener);
}
/** Expert: removes a CoreClosedListener from this reader's shared core */
public void removeCoreClosedListener(CoreClosedListener listener) {
ensureOpen();
core.removeCoreClosedListener(listener);
}
}