blob: 954894bb79fe1c52a105921c1d4bfe1c0bb9c682 [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.lucene.index;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.lucene.util.StringHelper;
/** Embeds a [read-only] SegmentInfo and adds per-commit
* fields.
*
* @lucene.experimental */
public class SegmentCommitInfo {
/** The {@link SegmentInfo} that we wrap. */
public final SegmentInfo info;
/** Id that uniquely identifies this segment commit. */
private byte[] id;
// How many deleted docs in the segment:
private int delCount;
// How many soft-deleted docs in the segment that are not also hard-deleted:
private int softDelCount;
// Generation number of the live docs file (-1 if there
// are no deletes yet):
private long delGen;
// Normally 1+delGen, unless an exception was hit on last
// attempt to write:
private long nextWriteDelGen;
// Generation number of the FieldInfos (-1 if there are no updates)
private long fieldInfosGen;
// Normally 1+fieldInfosGen, unless an exception was hit on last attempt to
// write
private long nextWriteFieldInfosGen;
// Generation number of the DocValues (-1 if there are no updates)
private long docValuesGen;
// Normally 1+dvGen, unless an exception was hit on last attempt to
// write
private long nextWriteDocValuesGen;
// Track the per-field DocValues update files
private final Map<Integer,Set<String>> dvUpdatesFiles = new HashMap<>();
// TODO should we add .files() to FieldInfosFormat, like we have on
// LiveDocsFormat?
// track the fieldInfos update files
private final Set<String> fieldInfosFiles = new HashSet<>();
private volatile long sizeInBytes = -1;
// NOTE: only used in-RAM by IW to track buffered deletes;
// this is never written to/read from the Directory
private long bufferedDeletesGen = -1;
/**
* Sole constructor.
* @param info
* {@link SegmentInfo} that we wrap
* @param delCount
* number of deleted documents in this segment
* @param delGen
* deletion generation number (used to name deletion files)
* @param fieldInfosGen
* FieldInfos generation number (used to name field-infos files)
* @param docValuesGen
* DocValues generation number (used to name doc-values updates files)
* @param id Id that uniquely identifies this segment commit. This id must be 16 bytes long. See {@link StringHelper#randomId()}
*/
public SegmentCommitInfo(SegmentInfo info, int delCount, int softDelCount, long delGen, long fieldInfosGen, long docValuesGen, byte[] id) {
this.info = info;
this.delCount = delCount;
this.softDelCount = softDelCount;
this.delGen = delGen;
this.nextWriteDelGen = delGen == -1 ? 1 : delGen + 1;
this.fieldInfosGen = fieldInfosGen;
this.nextWriteFieldInfosGen = fieldInfosGen == -1 ? 1 : fieldInfosGen + 1;
this.docValuesGen = docValuesGen;
this.nextWriteDocValuesGen = docValuesGen == -1 ? 1 : docValuesGen + 1;
this.id = id;
if (id != null && id.length != StringHelper.ID_LENGTH) {
throw new IllegalArgumentException("invalid id: " + Arrays.toString(id));
}
}
/** Returns the per-field DocValues updates files. */
public Map<Integer,Set<String>> getDocValuesUpdatesFiles() {
return Collections.unmodifiableMap(dvUpdatesFiles);
}
/** Sets the DocValues updates file names, per field number. Does not deep clone the map. */
public void setDocValuesUpdatesFiles(Map<Integer,Set<String>> dvUpdatesFiles) {
this.dvUpdatesFiles.clear();
for (Map.Entry<Integer,Set<String>> kv : dvUpdatesFiles.entrySet()) {
// rename the set
Set<String> set = new HashSet<>();
for (String file : kv.getValue()) {
set.add(info.namedForThisSegment(file));
}
this.dvUpdatesFiles.put(kv.getKey(), set);
}
}
/** Returns the FieldInfos file names. */
public Set<String> getFieldInfosFiles() {
return Collections.unmodifiableSet(fieldInfosFiles);
}
/** Sets the FieldInfos file names. */
public void setFieldInfosFiles(Set<String> fieldInfosFiles) {
this.fieldInfosFiles.clear();
for (String file : fieldInfosFiles) {
this.fieldInfosFiles.add(info.namedForThisSegment(file));
}
}
/** Called when we succeed in writing deletes */
void advanceDelGen() {
delGen = nextWriteDelGen;
nextWriteDelGen = delGen+1;
generationAdvanced();
}
/** Called if there was an exception while writing
* deletes, so that we don't try to write to the same
* file more than once. */
void advanceNextWriteDelGen() {
nextWriteDelGen++;
}
/** Gets the nextWriteDelGen. */
long getNextWriteDelGen() {
return nextWriteDelGen;
}
/** Sets the nextWriteDelGen. */
void setNextWriteDelGen(long v) {
nextWriteDelGen = v;
}
/** Called when we succeed in writing a new FieldInfos generation. */
void advanceFieldInfosGen() {
fieldInfosGen = nextWriteFieldInfosGen;
nextWriteFieldInfosGen = fieldInfosGen + 1;
generationAdvanced();
}
/**
* Called if there was an exception while writing a new generation of
* FieldInfos, so that we don't try to write to the same file more than once.
*/
void advanceNextWriteFieldInfosGen() {
nextWriteFieldInfosGen++;
}
/** Gets the nextWriteFieldInfosGen. */
long getNextWriteFieldInfosGen() {
return nextWriteFieldInfosGen;
}
/** Sets the nextWriteFieldInfosGen. */
void setNextWriteFieldInfosGen(long v) {
nextWriteFieldInfosGen = v;
}
/** Called when we succeed in writing a new DocValues generation. */
void advanceDocValuesGen() {
docValuesGen = nextWriteDocValuesGen;
nextWriteDocValuesGen = docValuesGen + 1;
generationAdvanced();
}
/**
* Called if there was an exception while writing a new generation of
* DocValues, so that we don't try to write to the same file more than once.
*/
void advanceNextWriteDocValuesGen() {
nextWriteDocValuesGen++;
}
/** Gets the nextWriteDocValuesGen. */
long getNextWriteDocValuesGen() {
return nextWriteDocValuesGen;
}
/** Sets the nextWriteDocValuesGen. */
void setNextWriteDocValuesGen(long v) {
nextWriteDocValuesGen = v;
}
/** Returns total size in bytes of all files for this
* segment. */
public long sizeInBytes() throws IOException {
if (sizeInBytes == -1) {
long sum = 0;
for (final String fileName : files()) {
sum += info.dir.fileLength(fileName);
}
sizeInBytes = sum;
}
return sizeInBytes;
}
/** Returns all files in use by this segment. */
public Collection<String> files() throws IOException {
// Start from the wrapped info's files:
Collection<String> files = new HashSet<>(info.files());
// TODO we could rely on TrackingDir.getCreatedFiles() (like we do for
// updates) and then maybe even be able to remove LiveDocsFormat.files().
// Must separately add any live docs files:
info.getCodec().liveDocsFormat().files(this, files);
// must separately add any field updates files
for (Set<String> updatefiles : dvUpdatesFiles.values()) {
files.addAll(updatefiles);
}
// must separately add fieldInfos files
files.addAll(fieldInfosFiles);
return files;
}
long getBufferedDeletesGen() {
return bufferedDeletesGen;
}
void setBufferedDeletesGen(long v) {
if (bufferedDeletesGen == -1) {
bufferedDeletesGen = v;
generationAdvanced();
} else {
throw new IllegalStateException("buffered deletes gen should only be set once");
}
}
/** Returns true if there are any deletions for the
* segment at this commit. */
public boolean hasDeletions() {
return delGen != -1;
}
/** Returns true if there are any field updates for the segment in this commit. */
public boolean hasFieldUpdates() {
return fieldInfosGen != -1;
}
/** Returns the next available generation number of the FieldInfos files. */
public long getNextFieldInfosGen() {
return nextWriteFieldInfosGen;
}
/**
* Returns the generation number of the field infos file or -1 if there are no
* field updates yet.
*/
public long getFieldInfosGen() {
return fieldInfosGen;
}
/** Returns the next available generation number of the DocValues files. */
public long getNextDocValuesGen() {
return nextWriteDocValuesGen;
}
/**
* Returns the generation number of the DocValues file or -1 if there are no
* doc-values updates yet.
*/
public long getDocValuesGen() {
return docValuesGen;
}
/**
* Returns the next available generation number
* of the live docs file.
*/
public long getNextDelGen() {
return nextWriteDelGen;
}
/**
* Returns generation number of the live docs file
* or -1 if there are no deletes yet.
*/
public long getDelGen() {
return delGen;
}
/**
* Returns the number of deleted docs in the segment.
*/
public int getDelCount() {
return delCount;
}
/**
* Returns the number of only soft-deleted docs.
*/
public int getSoftDelCount() {
return softDelCount;
}
void setDelCount(int delCount) {
if (delCount < 0 || delCount > info.maxDoc()) {
throw new IllegalArgumentException("invalid delCount=" + delCount + " (maxDoc=" + info.maxDoc() + ")");
}
assert softDelCount + delCount <= info.maxDoc() : "maxDoc=" + info.maxDoc() + ",delCount=" + delCount + ",softDelCount=" + softDelCount;
this.delCount = delCount;
}
void setSoftDelCount(int softDelCount) {
if (softDelCount < 0 || softDelCount > info.maxDoc()) {
throw new IllegalArgumentException("invalid softDelCount=" + softDelCount + " (maxDoc=" + info.maxDoc() + ")");
}
assert softDelCount + delCount <= info.maxDoc() : "maxDoc=" + info.maxDoc() + ",delCount=" + delCount + ",softDelCount=" + softDelCount;
this.softDelCount = softDelCount;
}
/** Returns a description of this segment. */
public String toString(int pendingDelCount) {
String s = info.toString(delCount + pendingDelCount);
if (delGen != -1) {
s += ":delGen=" + delGen;
}
if (fieldInfosGen != -1) {
s += ":fieldInfosGen=" + fieldInfosGen;
}
if (docValuesGen != -1) {
s += ":dvGen=" + docValuesGen;
}
if (softDelCount > 0) {
s += " :softDel=" + softDelCount;
}
if (this.id != null) {
s += " :id=" + StringHelper.idToString(id);
}
return s;
}
@Override
public String toString() {
return toString(0);
}
@Override
public SegmentCommitInfo clone() {
SegmentCommitInfo other = new SegmentCommitInfo(info, delCount, softDelCount, delGen, fieldInfosGen, docValuesGen, getId());
// Not clear that we need to carry over nextWriteDelGen
// (i.e. do we ever clone after a failed write and
// before the next successful write?), but just do it to
// be safe:
other.nextWriteDelGen = nextWriteDelGen;
other.nextWriteFieldInfosGen = nextWriteFieldInfosGen;
other.nextWriteDocValuesGen = nextWriteDocValuesGen;
// deep clone
for (Entry<Integer,Set<String>> e : dvUpdatesFiles.entrySet()) {
other.dvUpdatesFiles.put(e.getKey(), new HashSet<>(e.getValue()));
}
other.fieldInfosFiles.addAll(fieldInfosFiles);
return other;
}
final int getDelCount(boolean includeSoftDeletes) {
return includeSoftDeletes ? getDelCount() + getSoftDelCount() : getDelCount();
}
private void generationAdvanced() {
sizeInBytes = -1;
id = StringHelper.randomId();
}
/**
* Returns and Id that uniquely identifies this segment commit or <code>null</code> if there is no ID assigned.
* This ID changes each time the the segment changes due to a delete, doc-value or field update.
*/
public byte[] getId() {
return id == null ? null : id.clone();
}
}