/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.btree;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.UUID;
import org.apache.log4j.Logger;
import com.bigdata.io.ChecksumUtility;
import com.bigdata.io.FileChannelUtility;
import com.bigdata.io.NOPReopener;
import com.bigdata.journal.Journal;
import com.bigdata.journal.RootBlockException;
import com.bigdata.rawstore.IAddressManager;
import com.bigdata.util.Bytes;
/**
* The checkpoint record for an {@link IndexSegment}.
* <p>
* The checkpoint record for the index segment file is written at the head of
* the file. It should have identical timestamps at the start and end of the
* checkpoint record (e.g., it doubles as a root block). Since the file format
* is immutable it is ok to have what is essentially only a single root block.
* If the timestamps do not agree then the build was not successfully completed.
* <p>
* Similar to the {@link BTree}'s {@link Checkpoint} record, this record
* contains only data that pertains specifically to the {@link IndexSegment}
* checkpoint or data otherwise required to bootstrap the load of the
* {@link IndexSegment} from the file. General purpose metadata is stored in the
* {@link IndexMetadata} record.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class IndexSegmentCheckpoint implements ICheckpoint {
/**
* Logger.
*/
private static final Logger log = Logger
.getLogger(IndexSegmentCheckpoint.class);
/**
* The file is empty.
*/
private static final String ERR_EMPTY = "Empty file";
/**
* The file is non-empty, but is too small to contain a valid root block.
*/
private static final String ERR_TOO_SMALL = "Too small for valid root block";
private static final int SIZEOF_MAGIC = Bytes.SIZEOF_INT;
private static final int SIZEOF_VERSION = Bytes.SIZEOF_INT;
private static final int SIZEOF_OFFSET_BITS = Bytes.SIZEOF_INT;
private static final int SIZEOF_BRANCHING_FACTOR = Bytes.SIZEOF_INT;
private static final int SIZEOF_COUNTS = Bytes.SIZEOF_INT;
private static final int SIZEOF_NBYTES = Bytes.SIZEOF_INT;
private static final int SIZEOF_ADDR = Bytes.SIZEOF_LONG;
private static final int SIZEOF_ERROR_RATE = Bytes.SIZEOF_DOUBLE;
private static final int SIZEOF_TIMESTAMP = Bytes.SIZEOF_LONG;
private static final int SIZEOF_CHECKSUM = Bytes.SIZEOF_INT;
/**
* The #of unused bytes in the checkpoint record format for various versions
* of the record format. Note that the unused space occurs <em>before</em>
* the final timestamp in the record. As the unused bytes are allocated in
* new versions the value in this field MUST be adjusted down from its
* original value of 256.
*/
static final int SIZEOF_UNUSED_VERSION0 = 256;
static final int SIZEOF_UNUSED_VERSION1 = SIZEOF_UNUSED_VERSION0 -
( Bytes.SIZEOF_BYTE // useChecksums
);
static final int SIZEOF_UNUSED_VERSION2 = SIZEOF_UNUSED_VERSION1-
( Bytes.SIZEOF_LONG // int64 value for [entries]
);
/**
* The #of bytes required by the current {@link IndexSegmentCheckpoint}
* record format.
*/
static final int SIZE = //
SIZEOF_MAGIC + //
SIZEOF_VERSION + //
Bytes.SIZEOF_LONG + // timestamp0
Bytes.SIZEOF_UUID + // segment UUID.
SIZEOF_OFFSET_BITS + // #of bits used to represent a byte offset.
SIZEOF_COUNTS * 4 + // height, #leaves, #nodes, (nentries:int32 now unused)
SIZEOF_NBYTES + // max record length
Bytes.SIZEOF_LONG * 6 + // {offset,extent} tuples for the {leaves, nodes, blobs} regions.
SIZEOF_ADDR * 5 + // address of the {root node/leaf, indexMetadata, bloomFilter, {first,last}Leaf}.
Bytes.SIZEOF_LONG + // file size in bytes.
Bytes.SIZEOF_BYTE + // compactingMerge flag (0 | 1).
Bytes.SIZEOF_BYTE + // useChecksums flag (0 | 1) [VERSION1]
Bytes.SIZEOF_LONG + // nentries:int64 as of VERSION2
SIZEOF_UNUSED_VERSION2 + // available bytes for future versions.
SIZEOF_CHECKSUM+ // the checksum for the proceeding bytes in the checkpoint record.
Bytes.SIZEOF_LONG // timestamp1
;
/**
* Magic value written at the start of the {@link IndexSegmentCheckpoint}
* record.
*/
static transient final public int MAGIC = 0x87ab34f5;
/**
* Version 0 of the serialization format.
*/
static transient final public int VERSION0 = 0x0;
/**
* Version 1 of the serialization format introduces an option for record
* level checksums. New fields in this version include:
* <dl>
* <dt>{@link #useChecksums}</dt>
* <dd><code>true</code> iff record level checksums are enabled. The default
* for earlier versions is <code>false</code>, which provides backward
* compatibility for existing {@link IndexSegment} files.</dd>
* </dl>
*/
static transient final public int VERSION1 = 0x1;
/**
* Version 2 of the serialization format replaced the int32 value for
* nentries with an int64 value.
*/
static transient final public int VERSION2 = 0x2;
/**
* The current serialization version.
*/
static transient final public int currentVersion = VERSION2;
/**
* UUID for this {@link IndexSegment} (it is a unique identifier for the
* index segment resource and is reported as the {@link UUID} of the
* {@link IndexSegmentStore}).
*/
final public UUID segmentUUID;
/**
* The #of bits in an 64-bit long integer address that are used to represent
* the byte offset into the {@link IndexSegmentStore}.
*/
final public int offsetBits;
/**
* The {@link IAddressManager} used to interpret addresses in the
* {@link IndexSegmentStore}.
*/
final IndexSegmentAddressManager am;
/**
* Height of the index segment (origin zero, so height := 0 means that
* there is only a root leaf in the tree).
*/
final public int height;
/**
* The #of leaves serialized in the file.
* <p>
* Note: {@link IndexSegmentBuilder} is restricted to MAX_INT leaves in
* its build plan.
*/
final public int nleaves;
/**
* The #of nodes serialized in the file. If zero, then {@link #nleaves} MUST
* be ONE (1) and the index consists solely of a root leaf.
* <p>
* Note: {@link IndexSegmentBuilder} is restricted to MAX_INT leaves in
* its build plan and there are always more leaves than nodes in a BTree
* so this is also an int32 value.
*/
final public int nnodes;
/**
* The #of index entries serialized in the file (non-negative and MAY be
* zero).
*/
final public long nentries;
/**
* The maximum #of bytes in any node or leaf stored on the
* {@link IndexSegment}.
* <p>
* Note: while this appears to be unused now, it is still of interest and
* will be retained.
*/
final public int maxNodeOrLeafLength;
/*
* begin {offset,size} region extent tuples for the various multi-record
* regions defined in the store file. These use a long value for the
* byteCount size each region can span many, many records.
*/
/**
* The offset of the contiguous region containing the serialized leaves in
* the file.
* <p>
* Note: The offset must be equal to {@link #SIZE} since the leaves are
* written immediately after the {@link IndexSegmentCheckpoint} record.
*/
final public long offsetLeaves;
/**
* The #of bytes in the contiguous region containing the serialized leaves in
* the file.
*/
final public long extentLeaves;
/**
* The offset of the contiguous region containing the serialized nodes in
* the file or <code>0L</code> iff there are no nodes in the file.
*/
final public long offsetNodes;
/**
* The #of bytes in the contiguous region containing the serialized nodes in
* the file or <code>0L</code> iff there are no nodes in the file.
*/
final public long extentNodes;
/**
* The offset of the optional contiguous region containing the raw records
* to be resolved by blob references or <code>0L</code> iff there are no
* raw records in this region.
*/
final public long offsetBlobs;
/**
* The #of bytes in the optional contiguous region containing the raw
* records to be resolved by blob references or <code>0L</code> iff there
* are no raw records in this region.
*/
final public long extentBlobs;
/*
* begin: addresses for individual records.
*/
/**
* Address of the root node or leaf in the file.
*/
final public long addrRoot;
/**
* The address of the {@link IndexMetadata} record.
*/
final public long addrMetadata;
/**
* Address of the optional bloom filter and 0L iff no bloom filter
* was constructed.
*/
final public long addrBloom;
/**
* Address of the first leaf in the file.
*/
final public long addrFirstLeaf;
/**
* Address of the last leaf in the file.
*/
final public long addrLastLeaf;
/*
* end: addresses for individual records.
*/
/**
* Length of the file in bytes.
*/
final public long length;
/**
* <code>true</code> iff the caller asserted that the {@link IndexSegment}
* was a fused view of the source index (partition) as of the specified
* {@link #commitTime}. <code>false</code> implies that the
* {@link IndexSegment} is the result of an incremental build. This flag is
* important when attempting a bottom up reconstruction of a scale-out index
* from its components on various journals and {@link IndexSegmentStore}s.
*/
final public boolean compactingMerge;
/**
* <code>true</code> iff record level checksums are in use for the
* {@link IndexSegment}.
*
* @see #VERSION1
*/
final public boolean useChecksums;
/**
* The commit time associated with the view from which the
* {@link IndexSegment} was generated. The {@link IndexSegment} state is
* equivalent to the state of the view as of that timestamp. However, the
* {@link IndexSegment} provides a view of only a single commit point in
* contrast to the many commit points that are typically available on a
* {@link Journal}.
* <p>
* Note: This field is written at the head and tail of the
* {@link IndexSegmentCheckpoint} record. If the timestamps on that record
* do not agree then the build operation probably failed while writing the
* checkpoint record.
*/
final public long commitTime;
/**
* The checksum for the serialized representation of the
* {@link IndexSegmentCheckpoint} record. This is computed when the record
* is serialized and verified when it is de-serialized.
*/
private int checksum;
/**
* A read-only view of the serialized {@link IndexSegmentCheckpoint} record.
*/
final private ByteBuffer buf;
/**
* Reads the {@link IndexSegmentCheckpoint} record for the
* {@link IndexSegment}. The operation seeks to the start of the file and
* uses relative reads with the file pointer.
*
* @param raf
* The file.
*
* @throws IOException
* If there is a IO problem.
*
* @throws RootBlockException
* if the {@link IndexSegmentCheckpoint} record is invalid (it
* doubles as a root block), including if the total file length
* is not large enough to contain an valid
* {@link IndexSegmentCheckpoint} record.
*/
public IndexSegmentCheckpoint(final RandomAccessFile raf) throws IOException {
if (raf == null)
throw new IllegalArgumentException();
final long len = raf.length();
if (len == 0L) {
throw new RootBlockException(ERR_EMPTY);
}
if (raf.length() < SIZE) {
// File is non-empty, but too small to contain a valid root block.
throw new RootBlockException(ERR_TOO_SMALL);
}
// allocate buffer for the checkpoint record.
ByteBuffer buf = ByteBuffer.allocate(SIZE);
// read in the serialized checkpoint record.
FileChannelUtility.readAll(new NOPReopener(raf), buf, 0L);
// prepare for reading.
buf.rewind();
// extract the various fields.
final int magic = buf.getInt();
if (magic != MAGIC) {
throw new RootBlockException("MAGIC: expected=" + MAGIC
+ ", actual=" + magic);
}
final int version = buf.getInt();
if (version < 0 || version > currentVersion) {
throw new RootBlockException("unknown version=" + version);
}
final long timestamp0 = buf.getLong();
segmentUUID = new UUID(buf.getLong()/*MSB*/, buf.getLong()/*LSB*/);
offsetBits = buf.getInt();
height = buf.getInt();
nleaves = buf.getInt();
nnodes = buf.getInt();
long nentries = -1;
if (version < VERSION2) {
nentries = buf.getInt();
} else {
buf.getInt();
}
maxNodeOrLeafLength = buf.getInt();
// regions.
offsetLeaves = buf.getLong();
extentLeaves = buf.getLong();
offsetNodes = buf.getLong();
extentNodes = buf.getLong();
offsetBlobs = buf.getLong();
extentBlobs = buf.getLong();
// simple addresses.
addrRoot = buf.getLong();
addrMetadata = buf.getLong();
addrBloom = buf.getLong();
addrFirstLeaf = buf.getLong();
addrLastLeaf = buf.getLong();
// other data.
length = buf.getLong();
compactingMerge = buf.get() != 0;
if (version >= VERSION1) {
useChecksums = buf.get() != 0;
} else {
// // skip unused byte for prior versions.
// buf.get();
// record checksums were not used for prior versions.
useChecksums = false;
}
if (version >= VERSION2) {
nentries = buf.getLong();
}
this.nentries = nentries;
// advance to beyond the end of the unused section.
switch (version) {
case VERSION0:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION0);
break;
case VERSION1:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION1);
break;
case VERSION2:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION2);
break;
default:
throw new AssertionError();
}
// Note: this sets the instance field to the checksum read from the record!
checksum = buf.getInt();
final long timestamp1 = buf.getLong();
this.commitTime = timestamp0;
/*
* Now do the validation steps.
*
* Start with the checksum.
*
* Then the timestamps.
*
* Then the file length, etc.
*/
// compute the checksum of the record.
final int checksum = new ChecksumUtility().checksum(buf, 0, SIZE
- (SIZEOF_CHECKSUM + SIZEOF_TIMESTAMP));
if (checksum != this.checksum) {
throw new RootBlockException("Bad checksum: expected="
+ this.checksum + ", but actual=" + checksum);
}
if (timestamp0 != timestamp1) {
throw new RootBlockException("Timestamps differ: " + timestamp0
+ " vs " + timestamp1);
}
if (length != raf.length()) {
throw new RootBlockException("Length differs: actual="
+ raf.length() + ", expected=" + length);
}
am = new IndexSegmentAddressManager(this);
// more validation.
validate();
// save read-only view of the checkpoint record.
buf.rewind();
this.buf = buf.asReadOnlyBuffer();
if (log.isInfoEnabled())
log.info(this.toString());
}
/**
* Create a new checkpoint record in preparation for writing it on a file
* containing a newly constructed {@link IndexSegment}.
*
* @todo javadoc.
*/
public IndexSegmentCheckpoint(//
//
final int offsetBits,//
// basic checkpoint record.
final int height, //
final int nleaves,//
final int nnodes,//
final long nentries,//
//
final int maxNodeOrLeafLength,//
// region extents
final long offsetLeaves, final long extentLeaves,//
final long offsetNodes, final long extentNodes,//
final long offsetBlobs, final long extentBlobs,//
// simple addresses
final long addrRoot, //
final long addrMetadata, //
final long addrBloom,//
final long addrFirstLeaf,//
final long addrLastLeaf,//
// misc.
final long length,//
final boolean compactingMerge,//
final boolean useChecksums,
final UUID segmentUUID,//
final long commitTime//
) {
/*
* Copy the various fields to initialize the checkpoint record.
*/
this.segmentUUID = segmentUUID;
this.offsetBits = offsetBits;
this.height = height;
this.nleaves = nleaves;
this.nnodes = nnodes;
this.nentries = nentries;
this.maxNodeOrLeafLength = maxNodeOrLeafLength;
// region extents.
this.offsetLeaves = offsetLeaves;
this.extentLeaves = extentLeaves;
this.offsetNodes = offsetNodes;
this.extentNodes = extentNodes;
this.offsetBlobs = offsetBlobs;
this.extentBlobs = extentBlobs;
// simple addresses
this.addrRoot = addrRoot;
this.addrMetadata = addrMetadata;
this.addrBloom = addrBloom;
this.addrFirstLeaf = addrFirstLeaf;
this.addrLastLeaf = addrLastLeaf;
// other data
this.length = length;
this.compactingMerge = compactingMerge;
this.useChecksums = useChecksums;
this.commitTime = commitTime;
/*
* Create the address manager using this checkpoint record (requires
* that certain fields are initialized on the checkpoint record).
*/
am = new IndexSegmentAddressManager(this);
validate();
buf = createView();
if (log.isInfoEnabled())
log.info(this.toString());
}
/**
* Test validity of the {@link IndexSegmentCheckpoint} record.
*/
public void validate() {
// assert branchingFactor >= BTree.MIN_BRANCHING_FACTOR;
// height is non-negative.
// assert height >= 0;
if (height < 0)
throw new RootBlockException("height=" + height);
// must be non-negative.
if (nentries < 0)
throw new RootBlockException("nentries=" + nentries);
// if (nentries == 0) {
//
// /*
// * Empty index segment.
// */
//
// if (nleaves != 0)
// throw new RootBlockException("empty index but nleaves="
// + nleaves);
//
// if (nnodes != 0)
// throw new RootBlockException("empty index but nnodes="
// + nnodes);
//
// if (maxNodeOrLeafLength != 0)
// throw new RootBlockException(
// "empty index but maxNodeOrLeafLength="
// + maxNodeOrLeafLength);
//
// if (extentLeaves != 0L)
// throw new RootBlockException("empty index but extentLeaves="
// + extentLeaves);
//
// if (offsetLeaves != 0L)
// throw new RootBlockException("empty index but offsetLeaves="
// + offsetLeaves);
//
// if (extentNodes != 0L)
// throw new RootBlockException("empty index but extentNodes="
// + extentNodes);
//
// if (offsetNodes != 0L)
// throw new RootBlockException("empty index but offsetNodes="
// + offsetNodes);
//
// if (addrFirstLeaf != 0L)
// throw new RootBlockException("empty index but addrFirstLeaf="
// + addrFirstLeaf);
//
// if (addrLastLeaf != 0L)
// throw new RootBlockException("empty index but addrLastLeaf="
// + addrLastLeaf);
//
// } else {
{
if (nleaves <= 0)
throw new RootBlockException("nleaves=" + nleaves);
// zero nodes is Ok - the B+Tree may be just a root leaf.
if (nnodes < 0)
throw new RootBlockException("nnodes=" + nnodes);
// // #entries must fit within the tree height.
// assert nentries <= Math.pow(branchingFactor,height+1);
// assert maxNodeOrLeafLength > 0;
if (maxNodeOrLeafLength <= 0)
throw new RootBlockException("maxNodeOrLeafLength="
+ maxNodeOrLeafLength);
// validate addrLeaves
{
// assert addrLeaves != 0L;
if (extentLeaves == 0L) {
throw new RootBlockException("extentLeaves=" + extentLeaves);
}
// leaves start immediately after the checkpoint record.
if (offsetLeaves != SIZE) {
throw new RootBlockException("offsetLeaves=" + offsetLeaves
+ ", but expecting " + SIZE);
}
if (offsetLeaves + extentLeaves > length) {
throw new RootBlockException(
"The leaves region extends beyond the end of the file: leaves={extent="
+ extentLeaves + ", offset=" + offsetLeaves
+ "}, but length=" + length);
}
if (addrFirstLeaf == 0L)
throw new RootBlockException("No address for the first leaf?");
if (addrLastLeaf == 0L)
throw new RootBlockException("No address for the first leaf?");
}
if(nnodes == 0) {
/*
* The root is a leaf. In this case there is only a single root leaf
* and there are no nodes.
*/
if (offsetNodes != 0L || extentNodes != 0L) {
/*
* Since there is are no nodes, nodes offset and extent MUST be
* ZERO.
*/
throw new RootBlockException("nodes={extent=" + extentNodes
+ ", offset=" + offsetNodes + "}, but expecting zero.");
}
if (am.getByteCount(addrRoot) != extentLeaves) {
/*
* Since there is only a single root leaf, size of the root leaf
* record MUST equal extent of the leaves region.
*/
throw new RootBlockException("addrRoot("
+ am.toString(addrRoot)
+ ") : size is not equal to extentLeaves("
+ extentLeaves + ")");
}
// assert am.getOffset(addrRoot) >= am.getOffset(addrLeaves);
// assert am.getOffset(addrRoot) < length;
} else {
/*
* The root is a node.
*/
if (offsetNodes == 0L || extentNodes == 0L) {
// the nodes region MUST exist.
throw new RootBlockException("nodes={extent=" + extentNodes
+ ", offset=" + offsetNodes + "}");
}
if (offsetNodes + extentNodes > length) {
throw new RootBlockException(
"The nodes region extends beyond the end of the file: nodes={extent="
+ extentNodes + ",offset=" + offsetNodes
+ "}, but length=" + length);
}
if (am.getOffset(addrRoot) + am.getByteCount(addrRoot) > length) {
throw new RootBlockException(
"The root node record extends beyond the end of the file: addrRoot="
+ am.toString(addrRoot) + ", but length="
+ length);
}
// assert am.getOffset(addrNodes) > am.getOffset(addrLeaves);
// assert am.getOffset(addrNodes) < length;
// assert am.getOffset(addrRoot) >= am.getOffset(addrNodes);
// assert am.getOffset(addrRoot) < length;
}
}
/*
* @todo validate the blob, bloom, and metadata addresses and the
* first/last leaf addresses as well and the total length of the file.
*/
// if( addrBloom == 0L ) assert errorRate == 0.;
//
// if( errorRate != 0.) assert addrBloom != 0L;
// assert commitTime != 0L;
if (commitTime <= 0L) {
throw new RootBlockException("commitTime=" + commitTime);
}
// assert segmentUUID != null;
if (segmentUUID == null) {
throw new RootBlockException("No segment UUID");
}
}
/**
* Returns a new view of the read-only {@link ByteBuffer} containing the
* serialized representation of the {@link IndexSegmentCheckpoint} record.
*/
public ByteBuffer asReadOnlyBuffer() {
return buf.asReadOnlyBuffer(); // Note: a _new_ view.
}
/**
* Serialize the {@link IndexSegmentCheckpoint} record onto a read-only
* {@link ByteBuffer}.
*
* @return The read-only {@link ByteBuffer}.
*/
private ByteBuffer createView() {
final ByteBuffer buf = ByteBuffer.allocate(SIZE);
buf.putInt(MAGIC);
buf.putInt(currentVersion);
buf.putLong(commitTime);
buf.putLong(segmentUUID.getMostSignificantBits());
buf.putLong(segmentUUID.getLeastSignificantBits());
buf.putInt(offsetBits);
buf.putInt(height);
buf.putInt(nleaves);
buf.putInt(nnodes);
if (currentVersion < VERSION2) {
if (nentries > Integer.MAX_VALUE)
throw new RuntimeException();
buf.putInt((int) nentries);
} else {
buf.putInt(0/* unused */);
}
buf.putInt(maxNodeOrLeafLength);
// region extents.
buf.putLong(offsetLeaves);
buf.putLong(extentLeaves);
buf.putLong(offsetNodes);
buf.putLong(extentNodes);
buf.putLong(offsetBlobs);
buf.putLong(extentBlobs);
// simple addresses
buf.putLong(addrRoot);
buf.putLong(addrMetadata);
buf.putLong(addrBloom);
buf.putLong(addrFirstLeaf);
buf.putLong(addrLastLeaf);
// other data.
buf.putLong(length);
buf.put((byte) (compactingMerge ? 1 : 0));
if (currentVersion >= VERSION1) {
buf.put((byte) (useChecksums ? 1 : 0));
}
if (currentVersion >= VERSION2) {
buf.putLong(nentries);
}
// skip over the unused bytes.
switch (currentVersion) {
case VERSION0:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION0);
break;
case VERSION1:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION1);
break;
case VERSION2:
buf.position(buf.position() + SIZEOF_UNUSED_VERSION2);
break;
default:
throw new AssertionError();
}
// Note: this sets the instance field!
checksum = new ChecksumUtility().checksum(buf, 0, SIZE
- (SIZEOF_CHECKSUM + SIZEOF_TIMESTAMP));
buf.putInt(checksum); // checksum of the proceeding bytes.
buf.putLong(commitTime);
assert buf.position() == SIZE : "position=" + buf.position()
+ " but checkpoint record should be " + SIZE + " bytes";
assert buf.limit() == SIZE;
buf.rewind();
// read-only view.
return buf.asReadOnlyBuffer();
}
/**
* Write the checkpoint record at the start of the file.
*
* @param raf
* The file.
*
* @throws IOException
*/
public void write(final RandomAccessFile raf) throws IOException {
FileChannelUtility.writeAll(raf.getChannel(), asReadOnlyBuffer(), 0L);
if (log.isInfoEnabled()) {
log.info("wrote checkpoint record: " + this);
}
}
/**
* A human readable representation of the {@link IndexSegmentCheckpoint}
* record.
*/
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("magic="+Integer.toHexString(MAGIC));
sb.append(", segmentUUID="+segmentUUID);
sb.append(", offsetBits="+offsetBits);
sb.append(", height=" + height);
sb.append(", nleaves=" + nleaves);
sb.append(", nnodes=" + nnodes);
sb.append(", nentries=" + nentries);
sb.append(", maxNodeOrLeafLength=" + maxNodeOrLeafLength);
sb.append(", leavesRegion={extent=" + extentLeaves+", offset="+offsetLeaves+"}, avgLeafSize="+(nleaves==0?0:(extentLeaves/nleaves)));
sb.append(", nodesRegion={extent=" + extentNodes+", offset="+offsetNodes+"}, avgNodeSize="+(nnodes==0?0:(extentNodes/nnodes)));
sb.append(", blobsRegion={extent=" + extentBlobs+", offset="+offsetBlobs+"}");
sb.append(", addrRoot=" + am.toString(addrRoot));
sb.append(", addrFirstLeaf=" + am.toString(addrFirstLeaf));
sb.append(", addrLastLeaf=" + am.toString(addrLastLeaf));
sb.append(", addrMetadata=" + am.toString(addrMetadata));
sb.append(", addrBloom=" + am.toString(addrBloom));
sb.append(", length=" + length);
sb.append(", compactingMerge=" + compactingMerge);
sb.append(", useChecksums=" + useChecksums);
sb.append(", checksum=" + checksum);
sb.append(", commitTime=" + new Date(commitTime));
return sb.toString();
}
/**
* {@inheritDoc}
* <p>
* Note: The checkpoint is assembled from the root block by the constructor.
* There is no address from which it can be re-read.
*
* @return <code>0L</code>
*/
@Override
public long getCheckpointAddr() {
return 0L;
}
/**
* {@inheritDoc}
* <p>
* Note: The checkpoint is assembled from the root block by the constructor.
* There is no address from which it can be re-read.
*
* @return <code>false</code>
*/
@Override
public boolean hasCheckpointAddr() {
return false;
}
@Override
public long getMetadataAddr() {
return addrMetadata;
}
@Override
public long getRootAddr() {
return addrRoot;
}
@Override
public long getBloomFilterAddr() {
return addrBloom;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getGlobalDepth() {
return 0; // ZERO since not HTree.
}
@Override
public long getNodeCount() {
return nnodes;
}
@Override
public long getLeafCount() {
return nleaves;
}
@Override
public long getEntryCount() {
return nentries;
}
/**
* {@inheritDoc}
* <p>
* Note: There is no counter associated with an {@link IndexSegment}. The
* counter is only available for the {@link BTree}.
*
* @return <code>0L</code>
*/
@Override
public long getCounter() {
return 0;
}
@Override
public long getRecordVersion() {
// TODO Auto-generated method stub
return 0;
}
@Override
public IndexTypeEnum getIndexType() {
return IndexTypeEnum.BTree;
}
}