com.bigdata.btree.data.DefaultLeafCoder Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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
*/
/*
* Created on Aug 28, 2009
*/
package com.bigdata.btree.data;
import it.unimi.dsi.bits.Fast;
import it.unimi.dsi.io.OutputBitStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import com.bigdata.btree.AbstractBTree;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.btree.raba.IRaba;
import com.bigdata.btree.raba.codec.ICodedRaba;
import com.bigdata.btree.raba.codec.IRabaCoder;
import com.bigdata.io.AbstractFixedByteArrayBuffer;
import com.bigdata.io.DataOutputBuffer;
import com.bigdata.rawstore.IRawStore;
import com.bigdata.util.BytesUtil;
/**
* Default implementation for immutable {@link ILeafData} records.
*
* @author Bryan Thompson
* @version $Id$
*/
public class DefaultLeafCoder implements IAbstractNodeDataCoder,
Externalizable {
/**
*
*/
private static final long serialVersionUID = -2225107318522852096L;
// private static final transient Logger log = Logger
// .getLogger(DefaultLeafCoder.class);
/**
* The initial version of the serialized representation of the
* {@link DefaultLeafCoder} class (versus the serializer representation of
* the node or leaf).
*/
private static final transient byte VERSION0 = 0x00;
private IRabaCoder keysCoder;
private IRabaCoder valsCoder;
@Override
public void readExternal(final ObjectInput in) throws IOException,
ClassNotFoundException {
final byte version = in.readByte();
switch(version) {
case VERSION0:
break;
default:
throw new IOException();
}
keysCoder = (IRabaCoder) in.readObject();
valsCoder = (IRabaCoder) in.readObject();
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.write(VERSION0);
out.writeObject(keysCoder);
out.writeObject(valsCoder);
}
/** Yes. */
@Override
final public boolean isLeafDataCoder() {
return true;
}
/** No. */
@Override
public boolean isNodeDataCoder() {
return false;
}
@Override
public String toString() {
return super.toString() + "{keysCoder=" + keysCoder + ", valsCoder="
+ valsCoder + "}";
}
/**
* De-serialization ctor.
*/
public DefaultLeafCoder() {
}
/**
*
* @param keysCoder
* The {@link IRabaCoder} for the leaf's keys.
* @param valsCoder
* The {@link IRabaCoder} for the leaf's values.
*/
public DefaultLeafCoder(final IRabaCoder keysCoder,
final IRabaCoder valsCoder) {
if (keysCoder == null)
throw new IllegalArgumentException();
if (valsCoder == null)
throw new IllegalArgumentException();
this.keysCoder = keysCoder;
this.valsCoder = valsCoder;
}
@Override
public ILeafData decode(final AbstractFixedByteArrayBuffer data) {
return new ReadOnlyLeafData(data, keysCoder, valsCoder);
}
@Override
public ILeafData encodeLive(final ILeafData leaf, final DataOutputBuffer buf) {
if (leaf == null)
throw new IllegalArgumentException();
if (buf == null)
throw new IllegalArgumentException();
// cache some fields.
final int nkeys = leaf.getKeyCount();
// The byte offset of the start of the coded record into the buffer.
final int O_origin = buf.pos();
// Flag record as leaf or linked-leaf (vs node).
final boolean doubleLinked = leaf.isDoubleLinked();
buf.putByte((byte) (doubleLinked ? AbstractReadOnlyNodeData.LINKED_LEAF
: AbstractReadOnlyNodeData.LEAF));
if(doubleLinked) {
/*
* Skip over priorAddr/nextAddr fields (java will have zeroed the
* entire buffer when we allocated it). These fields need to be
* filled in on the record after it has been serialized (and
* potentially after it has been compressed) so we know its space on
* disk requirements.
*/
buf.skip(AbstractReadOnlyNodeData.SIZEOF_ADDR * 2);
}
buf.putShort(AbstractReadOnlyNodeData.currentVersion);
short flags = 0;
final boolean hasDeleteMarkers = leaf.hasDeleteMarkers();
final boolean hasVersionTimestamps = leaf.hasVersionTimestamps();
final boolean hasRawRecords = leaf.hasRawRecords();
// final boolean hasHashKeys = leaf instanceof IBucketData; // @todo add hasHashKeys() method?
if (hasDeleteMarkers) {
flags |= AbstractReadOnlyNodeData.FLAG_DELETE_MARKERS;
}
if (hasVersionTimestamps) {
flags |= AbstractReadOnlyNodeData.FLAG_VERSION_TIMESTAMPS;
}
if (hasRawRecords) {
flags |= AbstractReadOnlyNodeData.FLAG_RAW_RECORDS;
}
// if(hasHashKeys) {
// flags |= AbstractReadOnlyNodeData.FLAG_HASH_KEYS;
// }
buf.putShort(flags);
buf.putInt(nkeys); // pack?
final int O_keysSize = buf.pos();
// skip past the keysSize and valuesSize fields.
buf.skip(AbstractReadOnlyNodeData.SIZEOF_KEYS_SIZE * 2);
// buf.putInt(encodedKeys.length); // keysSize
// buf.putInt(encodedValues.length); // valuesSize
// encode the keys into the buffer
final ICodedRaba encodedKeys = keysCoder
.encodeLive(leaf.getKeys(), buf);
// encode the values into the buffer.
final ICodedRaba encodedValues = valsCoder.encodeLive(leaf.getValues(),
buf);
/*
* Patch the buffer to indicate the byte length of the encoded keys and
* the encoded values.
*/
buf.putInt(O_keysSize, encodedKeys.data().len());
buf.putInt(O_keysSize + AbstractReadOnlyNodeData.SIZEOF_KEYS_SIZE,
encodedValues.data().len());
// delete markers (bit coded).
// final int O_deleteMarkers;
if (hasDeleteMarkers) {
// O_deleteMarkers = buf.pos();
for (int i = 0; i < nkeys;) {
byte bits = 0;
for (int j = 0; j < 8 && i < nkeys; j++, i++) {
if(leaf.getDeleteMarker(i)) {
// Note: bit order is per BitInputStream & BytesUtil!
bits |= 1 << (7 - j);
}
}
buf.putByte(bits);
}
// } else {
//
// O_deleteMarkers = -1;
}
// The byte offset to minVersionTimestamp.
// final int O_versionTimestamps;
if (hasVersionTimestamps) {
/*
* The (min,max) are written out as full length long values. The per
* tuple revision timestamps are written out using the minimum #of
* bits required to code the data.
*
* Note: If min==max then ZERO bits are used per timestamp!
*/
final long min = leaf.getMinimumVersionTimestamp();
final long max = leaf.getMaximumVersionTimestamp();
// final long delta = max - min;
// assert delta >= 0;
// will be in [1:64]
final byte versionTimestampBits = (byte) (Fast
.mostSignificantBit(max - min) + 1);
// one byte.
buf.putByte((byte) versionTimestampBits);
// offset of minVersionTimestamp.
// O_versionTimestamps = buf.pos();
// int64
buf.putLong(min);
// int64
buf.putLong(max);
if (versionTimestampBits > 0) {
/*
* Note: We only write the deltas if there is more than one
* distinct timestamp value (min!=max). When min==max, the
* deltas are coded in zero bits, so this would be a NOP anyway.
*/
final int byteLength = BytesUtil.bitFlagByteLength(nkeys
* versionTimestampBits/* nbits */);
final byte[] a = new byte[byteLength];
final OutputBitStream obs = new OutputBitStream(a);
try {
// array of [versionTimestampBits] fields.
for (int i = 0; i < nkeys; i++) {
final long deltat = leaf.getVersionTimestamp(i) - min;
assert deltat >= 0;
obs.writeLong(deltat, versionTimestampBits);
}
obs.flush();
// copy onto the buffer.
buf.put(a);
} catch (IOException e) {
throw new RuntimeException(e);
// Note: close is not necessary if flushed and backed by
// byte[].
// } finally {
// try {
// obs.close();
// } catch (IOException e) {
// log.error(e);
// }
}
}
// } else {
//
// O_versionTimestamps = -1;
}
// raw records (bit coded).
// final int O_rawRecords
if (hasRawRecords) {
// O_rawRecords = buf.pos();
for (int i = 0; i < nkeys;) {
byte bits = 0;
for (int j = 0; j < 8 && i < nkeys; j++, i++) {
if (leaf.getRawRecord(i) != IRawStore.NULL) {
// Note: bit order is per BitInputStream & BytesUtil!
bits |= 1 << (7 - j);
}
}
buf.putByte(bits);
}
// } else {
//
// O_deleteMarkers = -1;
}
// hash codes of the keys (MSB prefix plus LSB coded).
// final int O_hashKeys;
// if (hasHashKeys) {
//
// // The bit length of the hash values.
// final int hashBitLength = 32;//((IBucketData)leaf).getHashBitLength();
//
// // The bit length of the shared MSB prefix.
// final int lengthMSB = ((IBucketData)leaf).getLengthMSB();
//
// // The bit length of the LSB which differ for each hash value.
// final int lengthLSB = hashBitLength - lengthMSB;
//
//// buf.putShort((short) hashBitLength);
//
// buf.putShort((short) lengthMSB);
//
//// O_hashKeys = buf.pos();
//
// if (nkeys > 0) {
//
// final int byteLength = BytesUtil
// .bitFlagByteLength((lengthMSB + (nkeys * lengthLSB))/* nbits */);
//
// final byte[] a = new byte[byteLength];
//
// final OutputBitStream obs = new OutputBitStream(a);
//
// try {
//
// // The hash of the first key.
// int h = ((IBucketData) leaf).getHash(0/* index */);
//
// // Drop off the LSB bits, leaving the MSB bits in the LSB position.
// h = h >>> lengthLSB;
//
//// // Reverse bits to since obs writes the LSB of the int.
//// h = Integer.reverse(h);
//
// // The MSB prefix.
// obs.writeInt(h, lengthMSB/* MSB bits */);
//
// // The LSB of the hash of each key.
// for (int i = 0; i < nkeys; i++) {
//
// // The hash of this key.
// h = ((IBucketData)leaf).getHash(i);
//
// // Drop off the MSB bits.
// h = h >>> lengthMSB;
//
//// // Reverse bits since obs writes the LSB of the int.
//// h = Integer.reverse(h);
//
// // The LSB.
// obs.writeInt(h, lengthLSB);
//
// }
//
// // copy onto the buffer.
// buf.put(a);
//
// } catch (IOException e) {
// throw new RuntimeException(e);
// // Note: close is not necessary if flushed and backed by
// // byte[].
// // } finally {
// // try {
// // obs.close();
// // } catch (IOException e) {
// // log.error(e);
// // }
// }
//
// }
//
//// } else {
////
//// O_hashKeys = -1;
//
// }
// Slice containing the coded leaf.
final AbstractFixedByteArrayBuffer slice = buf.slice(//
O_origin, buf.pos() - O_origin);
// A read-only view of the coded leaf data record.
return new ReadOnlyLeafData(slice, encodedKeys, encodedValues);
}
@Override
public AbstractFixedByteArrayBuffer encode(final ILeafData leaf,
final DataOutputBuffer buf) {
return encodeLive(leaf, buf).data();
}
/**
* A read-only view of the data for a B+Tree leaf based on a compact record
* format. While some fields are cached, for the most part the various data
* fields, including the keys and values, are accessed in place in the data
* record in order to minimize the memory footprint of the leaf. The keys
* and values are coded using a caller specified {@link IRabaCoder}. The
* specific coding scheme is specified by the {@link IndexMetadata} for the
* B+Tree instance and is not stored within the leaf data record. The use of
* prefix coding for keys is a good general choices, but should not be used
* in combination with a hash tree unless an order preserving hashing
* function is being used.
*
* Note: The leading byte of the record format codes for a leaf, a
* double-linked leaf or a node in a manner which is compatible with
* {@link ReadOnlyNodeData}.
*
* @author Bryan
* Thompson
* @version $Id: DefaultLeafCoder.java 3991 2010-12-03 18:48:02Z thompsonbry
* $
*/
// *
// * The {@link DefaultLeafCoder} automatically maintains hash values for keys
// * for an {@link IBucketData} record. The hash values of the keys in the
// * bucket will have a shared prefix (the MSB hash prefix) which corresponds
// * to the globalDepth of the path through the hash tree leading to this
// * bucket less the localDepth of this bucket. It is therefore possible to
// * store only the LSB bits of the hash values in the page and reconstruct
// * the hash values using the MSB bits from the path through the hash tree.
// * In order to be able to reconstruct the full hash code key based solely on
// * local information, the MSB bits can be written out once and the LSB bits
// * can be written out once per tuple. Testing the hash value of a key may
// * then be done considering only the LSB bits of the hash value. This
// * storage scheme also has the advantage that the hash value is not
// * restricted to an int32 and is therefore compatible with the use of
// * cryptographic hash functions. (If hash values are stored in a B+Tree leaf
// * they will not shared this prefix property and can not be compressed in
// * this manner).
static private class ReadOnlyLeafData extends AbstractReadOnlyNodeData
implements ILeafData {//, IBucketData {
/** The backing buffer. */
private final AbstractFixedByteArrayBuffer b;
// fields which are cached by the ctor.
// private final boolean doubleLinked;
private final int nkeys;
private final short flags;
private final IRaba keys;
private final IRaba vals;
/**
* Offset of the bit flags in the buffer encoding the presence of deleted
* tuples -or- -1
if the leaf does not report those data.
*/
private final int O_deleteMarkers;
/**
* The byte offset of the minimum version timestamp in the buffer -or-
* -1
if the leaf does not report those data. The minimum
* timestamp is coded as a full length long. The next field in the
* buffer is the maximum version timestamp. The following fields are an
* array of {@link #nkeys} coded timestamp values (one per tuple). Those
* timestamps are coded in {@link #versionTimestampBits} each.
*/
private final int O_versionTimestamps;
/**
* The #of bits used to code the version timestamps -or- ZERO (0) if
* they are not present.
*/
private final byte versionTimestampBits;
/**
* The minimum across the versionTimestamp[] (iff version timestamps are
* in use and otherwise -1L
).
*/
private final long minVersionTimestamp;
/**
* Offset of the bit flags in the buffer encoding the presence of tuples
* with raw records -or- -1
if the leaf does not report
* those data.
*/
private final int O_rawRecords;
@Override
public final AbstractFixedByteArrayBuffer data() {
return b;
}
/**
* Constructor used when the caller is encoding the {@link ILeafData}.
*
* @param buf
* A buffer containing the leaf data.
*/
protected ReadOnlyLeafData(final AbstractFixedByteArrayBuffer buf,
final ICodedRaba keys, final ICodedRaba values) {
if (buf == null)
throw new IllegalArgumentException();
if (keys == null)
throw new IllegalArgumentException();
if (values == null)
throw new IllegalArgumentException();
int pos = O_TYPE;
final byte type = buf.getByte(pos);
pos += SIZEOF_TYPE;
final boolean doubleLinked;
switch (type) {
case NODE:
throw new AssertionError();
case LEAF:
doubleLinked = false;
break;
case LINKED_LEAF:
doubleLinked = true;
break;
default:
throw new AssertionError("type=" + type);
}
if (doubleLinked) {
// skip over the prior/next addr.
pos += SIZEOF_ADDR * 2;
}
final int version = buf.getShort(pos);
pos += SIZEOF_VERSION;
switch (version) {
case VERSION0:
case VERSION1:
break;
default:
throw new AssertionError("version=" + version);
}
flags = buf.getShort(pos);
pos += SIZEOF_FLAGS;
final boolean hasVersionTimestamps = ((flags & FLAG_VERSION_TIMESTAMPS) != 0);
final boolean hasDeleteMarkers = ((flags & FLAG_DELETE_MARKERS) != 0);
final boolean hasRawRecords = ((flags & FLAG_RAW_RECORDS) != 0);
// final boolean hasHashKeys = ((flags & FLAG_HASH_KEYS) != 0);
this.nkeys = buf.getInt(pos);
pos += SIZEOF_NKEYS;
final int keysSize = buf.getInt(pos);
pos += SIZEOF_KEYS_SIZE;
final int valuesSize = buf.getInt(pos);
pos += SIZEOF_KEYS_SIZE;
// keys
this.keys = keys;//keysCoder.decode(buf.slice(pos, keysSize));
pos += keysSize;// skip over the keys.
// values
this.vals = values;//valuesCoder.decode(buf.slice(pos,valuesSize));
pos += valuesSize;// skip over the values.
// delete markers
if (hasDeleteMarkers) {
O_deleteMarkers = pos;
// advance past the bit flags.
pos += BytesUtil.bitFlagByteLength(nkeys);// bit coded.
} else {
O_deleteMarkers = -1;
}
// version timestamps
if (hasVersionTimestamps) {
versionTimestampBits = buf.getByte(pos);
pos++;
O_versionTimestamps = pos;
minVersionTimestamp = buf.getLong(pos);// cache.
// advance past the timestamps.
pos += (2 * SIZEOF_TIMESTAMP)
+ BytesUtil.bitFlagByteLength(nkeys
* versionTimestampBits/* nbits */);
// // advance past the timestamps.
// pos += nkeys * SIZEOF_TIMESTAMP;
} else {
O_versionTimestamps = -1;
versionTimestampBits = 0;
minVersionTimestamp = -1L;
}
// raw record flags
if (hasRawRecords) {
O_rawRecords = pos;
// advance past the bit flags.
pos += BytesUtil.bitFlagByteLength(nkeys);// bit coded.
} else {
O_rawRecords = -1;
}
// if(hasHashKeys) {
//
// final int lengthMSB = buf.getShort(pos);
// pos += 2;
//
// lengthLSB = 32 /* hashBitLength */- lengthMSB;
//
// /*
// * The byte offset to the start of the bit coded hash keys. The
// * first bit coded value is the MSB prefix. You need to skip
// * over that when indexing into the LSB array.
// */
// O_hashKeys = pos;
//
// final int byteLength = BytesUtil
// .bitFlagByteLength((lengthMSB + (nkeys * lengthLSB))/* nbits */);
//
// if (nkeys > 0) {
//
// final InputBitStream ibs = buf.slice(pos, byteLength)
// .getInputBitStream();
//
// try {
// hashMSB = ibs.readInt(lengthMSB);
// } catch (IOException ex) {
// // Note: should not be thrown.
// throw new RuntimeException(ex);
// }
//
// } else {
//
// hashMSB = 0;
//
// }
//
// } else {
//
// O_hashKeys = -1;
// lengthLSB = 0;
// hashMSB = 0;
//
// }
// save reference to buffer
this.b = buf;
}
/**
* Decode in place (wraps a record containing the encoded data for a leaf).
*
* @param buf
* A buffer containing the leaf data.
*/
protected ReadOnlyLeafData(final AbstractFixedByteArrayBuffer buf,
final IRabaCoder keysCoder, final IRabaCoder valuesCoder) {
if (buf == null)
throw new IllegalArgumentException();
if (keysCoder == null)
throw new IllegalArgumentException();
if (valuesCoder == null)
throw new IllegalArgumentException();
int pos = O_TYPE;
final byte type = buf.getByte(pos);
pos += SIZEOF_TYPE;
final boolean doubleLinked;
switch (type) {
case NODE:
throw new AssertionError();
case LEAF:
doubleLinked = false;
break;
case LINKED_LEAF:
doubleLinked = true;
break;
default:
throw new AssertionError("type=" + type);
}
if (doubleLinked) {
// skip over the prior/next addr.
pos += SIZEOF_ADDR * 2;
}
final int version = buf.getShort(pos);
pos += SIZEOF_VERSION;
switch (version) {
case VERSION0:
case VERSION1:
break;
default:
throw new AssertionError("version=" + version);
}
flags = buf.getShort(pos);
pos += SIZEOF_FLAGS;
final boolean hasVersionTimestamps = ((flags & FLAG_VERSION_TIMESTAMPS) != 0);
final boolean hasDeleteMarkers = ((flags & FLAG_DELETE_MARKERS) != 0);
final boolean hasRawRecords = ((flags & FLAG_RAW_RECORDS) != 0);
// final boolean hasHashKeys = ((flags & FLAG_HASH_KEYS) != 0);
this.nkeys = buf.getInt(pos);
pos += SIZEOF_NKEYS;
final int keysSize = buf.getInt(pos);
pos += SIZEOF_KEYS_SIZE;
final int valuesSize = buf.getInt(pos);
pos += SIZEOF_KEYS_SIZE;
// keys
this.keys = keysCoder.decode(buf.slice(pos, keysSize));
pos += keysSize;// skip over the keys.
if (nkeys != keys.size()) // sanity check nkeys
throw new RuntimeException("nkeys=" + nkeys + ", keys.size="
+ keys.size());
// values
this.vals = valuesCoder.decode(buf.slice(pos,valuesSize));
pos += valuesSize;// skip over the values.
if (nkeys != vals.size()) // sanity check nkeys
throw new RuntimeException("nkeys=" + nkeys + ", vals.size="
+ vals.size());
// delete markers
if (hasDeleteMarkers) {
O_deleteMarkers = pos;
// advance past the bit flags.
pos += BytesUtil.bitFlagByteLength(nkeys);// bit coded.
} else {
O_deleteMarkers = -1;
}
// version timestamps
if (hasVersionTimestamps) {
versionTimestampBits = buf.getByte(pos);
pos++;
O_versionTimestamps = pos;
minVersionTimestamp = buf.getLong(pos);// cache.
// advance past the timestamps.
pos += (2 * SIZEOF_TIMESTAMP)
+ BytesUtil.bitFlagByteLength(nkeys
* versionTimestampBits/* nbits */);
// // advance past the timestamps.
// pos += nkeys * SIZEOF_TIMESTAMP;
} else {
O_versionTimestamps = -1;
versionTimestampBits = 0;
minVersionTimestamp = -1L;
}
// raw record markers
if (hasRawRecords) {
O_rawRecords = pos;
// advance past the bit flags.
pos += BytesUtil.bitFlagByteLength(nkeys);// bit coded.
} else {
O_rawRecords = -1;
}
// if(hasHashKeys) {
//
// final int lengthMSB = buf.getShort(pos);
// pos += 2;
//
// lengthLSB = 32 /* hashBitLength */- lengthMSB;
//
// /*
// * The byte offset to the start of the bit coded hash keys. The
// * first bit coded value is the MSB prefix. You need to skip
// * over that when indexing into the LSB array.
// */
// O_hashKeys = pos;
//
// final int byteLength = BytesUtil
// .bitFlagByteLength((lengthMSB + (nkeys * lengthLSB))/* nbits */);
//
// if (nkeys > 0) {
//
// final InputBitStream ibs = buf.slice(pos, byteLength)
// .getInputBitStream();
//
// try {
// hashMSB = ibs.readInt(lengthMSB);
// } catch (IOException ex) {
// // Note: should not be thrown.
// throw new RuntimeException(ex);
// }
//
// } else {
//
// hashMSB = 0;
//
// }
//
// } else {
//
// O_hashKeys = -1;
// lengthLSB = 0;
// hashMSB = 0;
//
// }
// save reference to buffer
this.b = buf;
}
/**
* Always returns true
.
*/
@Override
final public boolean isLeaf() {
return true;
}
/**
* Yes.
*/
@Override
final public boolean isReadOnly() {
return true;
}
/**
* Yes.
*/
@Override
final public boolean isCoded() {
return true;
}
/**
* {@inheritDoc}. This field is cached.
*/
@Override
final public int getKeyCount() {
return nkeys;
}
// /**
// * For a leaf the #of tuples is always the #of keys.
// */
// final public long getSpannedTupleCount() {
//
// return nkeys;
//
// }
/**
* For a leaf, the #of values is always the #of keys.
*/
@Override
final public int getValueCount() {
return nkeys;
}
@Override
final public boolean hasVersionTimestamps() {
return (flags & FLAG_VERSION_TIMESTAMPS) != 0;
}
@Override
final public boolean hasDeleteMarkers() {
return (flags & FLAG_DELETE_MARKERS) != 0;
}
@Override
final public boolean hasRawRecords() {
return (flags & FLAG_RAW_RECORDS) != 0;
}
// final public boolean hasHashKeys() {
//
// return (flags & FLAG_HASH_KEYS) != 0;
//
// }
@Override
public long getMinimumVersionTimestamp() {
if (!hasVersionTimestamps())
throw new UnsupportedOperationException();
// return b.getLong(O_versionTimestamps);
return minVersionTimestamp;
}
@Override
public long getMaximumVersionTimestamp() {
if (!hasVersionTimestamps())
throw new UnsupportedOperationException();
return b.getLong(O_versionTimestamps + SIZEOF_TIMESTAMP);
}
@Override
final public long getVersionTimestamp(final int index) {
if (!hasVersionTimestamps())
throw new UnsupportedOperationException();
// return b.getLong(O_versionTimestamps + index * SIZEOF_TIMESTAMP);
final long bitpos = ((O_versionTimestamps + (2L * SIZEOF_TIMESTAMP)) << 3)
+ ((long) index * versionTimestampBits);
final long bitIndex = (b.off() << 3) + bitpos;
final long deltat = BytesUtil.getBits64(b.array(), (int) bitIndex,
versionTimestampBits);
return minVersionTimestamp + deltat;
// final InputBitStream ibs = b.getInputBitStream();
// try {
//
// final long bitpos = ((O_versionTimestamps + (2L * SIZEOF_TIMESTAMP)) << 3)
// + ((long)index * versionTimestampBits);
//
// ibs.position(bitpos);
//
// final long deltat = ibs
// .readLong(versionTimestampBits/* nbits */);
//
// return getMinimumVersionTimestamp() + deltat;
//
// } catch(IOException ex) {
//
// throw new RuntimeException(ex);
//
//// close not required for IBS backed by byte[] and has high overhead.
//// } finally {
//// try {
//// ibs.close();
//// } catch (IOException ex) {
//// log.error(ex);
//// }
// }
}
@Override
final public boolean getDeleteMarker(final int index) {
if (!hasDeleteMarkers())
throw new UnsupportedOperationException();
return b.getBit((O_deleteMarkers << 3) + index);
}
@Override
final public long getRawRecord(final int index) {
if (!hasRawRecords())
throw new UnsupportedOperationException();
final boolean flag = b.getBit((O_rawRecords << 3) + index);
if (!flag)
return IRawStore.NULL;
/*
* @todo This could be optimized to decode without materializing the
* byte[8] array which represents the long addr.
*/
return AbstractBTree.decodeRecordAddr(vals.get(index));
}
// final public int getLengthMSB() {
//
// if (!hasHashKeys())
// throw new UnsupportedOperationException();
//
// final int lengthMSB = 32/* hashBitLength */- lengthLSB;
//
// return lengthMSB;
//
// }
// final public int getHash(final int index) {
//
// if (index < 0 || index >= nkeys)
// throw new IllegalArgumentException();
//
// if (!hasHashKeys())
// throw new UnsupportedOperationException();
//
// final int lengthMSB = 32/* hashBitLength */- lengthLSB;
//
// final int byteLength = BytesUtil.bitFlagByteLength(lengthMSB
// + (nkeys * lengthLSB)/* nbits */);
//
// final InputBitStream ibs = b.slice(O_hashKeys, byteLength)
// .getInputBitStream();
//
// try {
//
// final long position = lengthMSB + index * lengthLSB;
//
// ibs.position(position);
//
// int h = ibs.readInt(lengthLSB);
//
// h |= hashMSB;
//
// return h;
//
// } catch(IOException ex) {
//
// throw new RuntimeException(ex);
//
// }
//
// }
//
// public Iterator hashIterator(final int h) {
//
// return new HashMatchIterator(h);
//
// }
// /**
// * Visits the index of each bucket entry having a matching hash code.
// *
// * @todo a trie over the hash entries would provide much faster search.
// */
// private class HashMatchIterator implements Iterator {
//
// private final int h;
// private final int lengthMSB;
// private final InputBitStream ibs;
// private int currentIndex = 0;
// private Integer nextResult = null;
//
// private HashMatchIterator(final int h) {
//
// this.h = h;
//
// lengthMSB = 32/* hashBitLength */- lengthLSB;
//
// final int byteLength = BytesUtil.bitFlagByteLength(lengthMSB
// + (nkeys * lengthLSB)/* nbits */);
//
// ibs = b.slice(O_hashKeys, byteLength)
// .getInputBitStream();
//
// }
//
// public boolean hasNext() {
//
// final int n = getKeyCount();
//
// while (nextResult == null && currentIndex < n) {
//
// final int index = currentIndex++;
//
// int h1;
// try {
//
// // We do not need to re-position the ibs.
//// final long position = lengthMSB + currentIndex
//// * lengthLSB;
//// ibs.position(position);
//
// h1 = ibs.readInt(lengthLSB);
//
// h1 |= hashMSB;
//
// } catch (IOException ex) {
//
// throw new RuntimeException(ex);
//
// }
//
// if (h1 == h) {
//
// nextResult = Integer.valueOf(index);
//
// break;
//
// }
//
// }
//
// return nextResult != null;
//
// }
//
// public Integer next() {
//
// if (!hasNext())
// throw new NoSuchElementException();
//
// final Integer tmp = nextResult;
//
// nextResult = null;
//
// return tmp;
//
// }
//
// public void remove() {
//
// throw new UnsupportedOperationException();
//
// }
//
// }
@Override
final public IRaba getKeys() {
return keys;
}
@Override
final public IRaba getValues() {
return vals;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getName() + "{");
DefaultLeafCoder.toString(this, sb);
sb.append("}");
return sb.toString();
}
/*
* Double-linked leaf support.
*/
/**
* Return true
if the leaf encodes the address or the prior and
* next leaves.
*/
@Override
public final boolean isDoubleLinked() {
return b.getByte(0) == LINKED_LEAF;
}
// /**
// * Update the data record to set the prior and next leaf address.
// *
// * Note: In order to use this method to write linked leaves on the store
// * you have to either write behind at a pre-determined address on the
// * store or settle for writing only the prior or the next leaf address,
// * but not both. It is up to the caller to perform these tricks. All
// * this method does is to touch up the serialized record.
// *
// * Note: This method has NO side-effects on the position or
// * limit of the internal {@link ByteBuffer}.
// *
// * @param priorAddr
// * The address of the previous leaf in key order,
// * 0L
if it is known that there is no previous
// * leaf, and -1L
if either: (a) it is not known
// * whether there is a previous leaf; or (b) it is known but
// * the address of that leaf is not known to the caller.
// * @param nextAddr
// * The address of the next leaf in key order, 0L
// * if it is known that there is no next leaf, and
// * -1L
if either: (a) it is not known whether
// * there is a next leaf; or (b) it is known but the address
// * of that leaf is not known to the caller.
// *
// * @see IndexSegmentBuilder
// */
// public void updateLeaf(final long priorAddr, final long nextAddr) {
//
// if (!isDoubleLinked()) {
//
// // Not double-linked.
// throw new UnsupportedOperationException();
//
// }
//
// /*
// * Note: these fields are written immediately after the byte
// * indicating whether this is a leaf, linked-leaf, or node.
// */
//
// b.putLong(O_PRIOR, priorAddr);
//
// b.putLong(O_NEXT + SIZEOF_ADDR, nextAddr);
//
// }
@Override
public final long getPriorAddr() {
if(!isDoubleLinked())
throw new UnsupportedOperationException();
return b.getLong(O_PRIOR);
}
@Override
public final long getNextAddr() {
if(!isDoubleLinked())
throw new UnsupportedOperationException();
return b.getLong(O_NEXT);
}
}
/**
* Utility method formats the {@link ILeafData}.
*
* @param leaf
* A leaf data record.
* @param sb
* The representation will be written onto this object.
*
* @return The sb parameter.
*/
static public StringBuilder toString(final ILeafData leaf,
final StringBuilder sb) {
final int nkeys = leaf.getKeyCount();
if(leaf.isDoubleLinked()) {
sb.append(", priorAddr=" + leaf.getPriorAddr());
sb.append(", nextAddr=" + leaf.getNextAddr());
}
sb.append(",\nkeys=" + leaf.getKeys());
sb.append(",\nvals=" + leaf.getValues());
if (leaf.hasDeleteMarkers()) {
sb.append(",\ndeleteMarkers=[");
for (int i = 0; i < nkeys; i++) {
if (i > 0)
sb.append(", ");
sb.append(leaf.getDeleteMarker(i));
}
sb.append("]");
}
if (leaf.hasVersionTimestamps()) {
sb.append(",\nversionTimestamps={min="
+ leaf.getMinimumVersionTimestamp() + ",max="
+ leaf.getMaximumVersionTimestamp() + ",tuples=[");
for (int i = 0; i < nkeys; i++) {
if (i > 0)
sb.append(", ");
// sb.append(new Date(leaf.getVersionTimestamp(i)).toString());
sb.append(leaf.getVersionTimestamp(i));
}
sb.append("]");
}
if (leaf.hasRawRecords()) {
sb.append(",\nrawRecords=[");
for (int i = 0; i < nkeys; i++) {
if (i > 0)
sb.append(", ");
sb.append(leaf.getRawRecord(i));
}
sb.append("]");
}
// if (leaf instanceof IBucketData) {
//
// final IBucketData d = (IBucketData)leaf;
//
// sb.append(",\nhashCodes={lengthMSB=" + d.getLengthMSB()
// + ",tuples=[");
//
// for (int i = 0; i < nkeys; i++) {
//
// if (i > 0)
// sb.append(", ");
//
// sb.append(d.getHash(i));
//
// }
//
// sb.append("]");
//
// }
return sb;
}
}