| /* |
| * 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(); |
| } |
| } |