com.bigdata.btree.AbstractTuple 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 May 30, 2008
*/
package com.bigdata.btree;
import java.nio.ByteBuffer;
import java.util.Arrays;
import com.bigdata.btree.data.ILeafData;
import com.bigdata.io.ByteArrayBuffer;
import com.bigdata.io.DataInputBuffer;
import com.bigdata.io.DataOutputBuffer;
import com.bigdata.rawstore.IBlock;
import com.bigdata.rawstore.IRawStore;
/**
* Abstract base class with much of the functionality of {@link ITuple}.
*
* @author Bryan Thompson
* @version $Id$
*/
public abstract class AbstractTuple implements ITuple {
final protected int flags;
final protected boolean needKeys;
final protected boolean needVals;
final public int flags() {
return flags;
}
final public boolean getKeysRequested() {
return needKeys;
}
final public boolean getValuesRequested() {
return needVals;
}
/**
* Reused for each key that is materialized and null
if keys
* are not being requested. Among other things, the {@link #kbuf} access may
* be used to completely avoid heap allocations when considering keys. The
* data for the current key are simply copied from the leaf into the
* {@link #kbuf} and the application can either examine the data in the
* {@link #kbuf} or copy it into its own buffers.
*/
final private DataOutputBuffer kbuf;
final public ByteArrayBuffer getKeyBuffer() {
if (!needKeys)
throw new UnsupportedOperationException();
return kbuf;
}
/**
* Return a stream from which the key may be read.
*
* @throws UnsupportedOperationException
* if the keys were not requested.
*/
final public DataInputBuffer getKeyStream() {
if (!needKeys)
throw new UnsupportedOperationException();
if (keyStream == null) {
// setup the input stream.
keyStream = new DataInputBuffer(kbuf.array(), 0/* offset */, kbuf
.limit()/* len */);
} else {
// reset the buffer.
keyStream.setBuffer(kbuf.array(), 0, kbuf.limit());
}
return keyStream;
}
private DataInputBuffer keyStream = null;
/**
* Return a stream from which the value may be read.
*
* @throws UnsupportedOperationException
* if the values were not requested.
*/
final public DataInputBuffer getValueStream() {
if (!needVals)
throw new UnsupportedOperationException();
if (isNull)
throw new UnsupportedOperationException();
if (versionDeleted)
throw new UnsupportedOperationException();
if (valStream == null) {
// setup the input stream.
valStream = new DataInputBuffer(vbuf.array(), 0/* offset */, vbuf
.limit()/* len */);
} else {
// reset the buffer.
valStream.setBuffer(vbuf.array(), 0, vbuf.limit());
}
return valStream;
}
private DataInputBuffer valStream = null;
/**
* Reused for each value that is materialized and null
if
* values are not being requested.
*/
final private DataOutputBuffer vbuf;
/**
* true
iff the iterator is visiting an index entry with a
* null
value.
*/
private boolean isNull;
private long versionTimestamp = 0L;
/**
* Note: The default of false
has the correct semantics if
* the btree does not support isolation since isolation requires that both
* deleted entries and version timestamps are enabled.
*/
private boolean versionDeleted = false;
/**
* The #of entries that have been visited. This will be ZERO (0) until the
* first entry has been visited, at which point it is incremented to ONE
* (1), etc.
*/
private int nvisited = 0;
/**
* The #of entries that have been visited so far and ZERO (0) until the
* first entry has been visited.
*/
public long getVisitCount() {
return nvisited;
}
/** Disallowed. */
private AbstractTuple() {
throw new UnsupportedOperationException();
}
public AbstractTuple(final int flags) {
this.flags = flags;
needKeys = (flags & IRangeQuery.KEYS) != 0;
needVals = (flags & IRangeQuery.VALS) != 0;
if(needKeys) {
/*
* Note: we are choosing a smallish initial capacity. We could
* specify ZERO (0) to never over estimate, but the buffer will be
* re-sized automatically. An initial non-zero value makes it more
* likely that we will not have to re-allocate. The smallish initial
* value means that we are not allocating or wasting that much space
* if we in fact require less (or more).
*/
kbuf = new DataOutputBuffer(128);
} else {
kbuf = null;
}
if(needVals) {
vbuf = new DataOutputBuffer(128);
} else {
vbuf = null;
}
}
final public byte[] getKey() {
if (!needKeys)
throw new UnsupportedOperationException();
return kbuf.toByteArray();
}
final public boolean isNull() {
return isNull;
}
final public byte[] getValue() {
if (!needVals)
throw new UnsupportedOperationException();
if (versionDeleted || isNull)
return null;
return vbuf.toByteArray();
}
final public ByteArrayBuffer getValueBuffer() {
if(versionDeleted)
throw new UnsupportedOperationException();
if (isNull)
throw new UnsupportedOperationException();
return vbuf;
}
@SuppressWarnings("unchecked")
final public E getObject() {
if (versionDeleted) {
// Visiting a deleted entry.
return null;
}
return (E) getTupleSerializer().deserialize(this);
}
final public long getVersionTimestamp() {
return versionTimestamp;
}
final public boolean isDeletedVersion() {
return versionDeleted;
}
/**
* @todo Implement. In order to work the tuple needs to know the source
* store from which the index entry was read. In the context of a
* fused view this is the element of that view that supplied the tuple
* binding (blocks may be stored on journals or index segments). In
* the context of a scale-out index, the state must include enough
* information to locate the data service for the index view and then
* the source index within that view.
*
* Consider the notion of leases for the read back from a data
* service. If you do not read on the block soon enough the lease is
* gone and you will get an error if you try to read on the block. The
* lease can be used to limit the time that an index partition is
* locked to a data service.
*/
public IBlock readBlock(long addr) {
throw new UnsupportedOperationException();
}
/**
* Copy data and metadata for the index entry from the {@link Leaf} into the
* {@link Tuple} and increment the counter of the #of visited entries.
*
* @param index
* The index entry.
* @param leaf
* The leaf. If a raw record must be materialized, it will be
* read from the backing store for the {@link Leaf}.
*
* @todo The various copy methods should also set the [sourceIndex] property
* and {@link ITuple#getSourceIndex()} should be implemented by this
* class (or maybe add a setSourceIndex() to be more flexible).
*/
public void copy(final int index, final ILeafData leaf) {
nvisited++;
/*
* true iff delete markers are enabled and this entry is marked deleted.
*/
versionDeleted = leaf.hasDeleteMarkers() && leaf.getDeleteMarker(index);
/*
* the version timestamp iff timestamps are enabled and otherwise 0L.
*/
versionTimestamp = leaf.hasVersionTimestamps() ? leaf
.getVersionTimestamp(index) : 0L;
if (needKeys) {
kbuf.reset();
leaf.getKeys().copy(index, kbuf);
}
if (needVals) {
vbuf.reset();
if (!versionDeleted) {
isNull = leaf.getValues().isNull(index);
if (!isNull) {
if (leaf.hasRawRecords()) {
final long addr = leaf.getRawRecord(index);
if (addr == IRawStore.NULL) {
// copy out of the leaf.
leaf.getValues().copy(index, vbuf);
} else {
// materialize from the backing store.
final ByteBuffer tmp = ((IRawRecordAccess) leaf)
.readRawRecord(addr);
// and copy into the value buffer.
vbuf.copy(tmp);
}
} else {
// copy out of the leaf.
leaf.getValues().copy(index, vbuf);
}
}
}
}
}
/**
* Assumes that the tuple is not deleted and that timestamp information is
* not available.
*
* @param key
* @param val
*/
public void copyTuple(final byte[] key, final byte[] val) {
copyTuple(key, val, false/* deleted */, 0L/* timestamp */);
}
/**
* Sets all fields on the tuple and increments the tuple visited counter.
*
* @param key
* @param val
* @param deleted
* @param timestamp
*/
public void copyTuple(final byte[] key, final byte[] val,
final boolean deleted, final long timestamp) {
this.nvisited++;
this.versionDeleted = deleted;
this.versionTimestamp = timestamp;
if (needKeys) {
kbuf.reset();
kbuf.put(key);
}
if (needVals) {
vbuf.reset();
if (!versionDeleted) {
isNull = val == null;
if (!isNull) {
vbuf.put(val);
}
}
}
}
/**
* Sets all fields and increments the tuple visited counter.
*
* @param t
* Some tuple.
*/
public void copyTuple(ITuple t) {
this.nvisited++;
this.versionDeleted = t.isDeletedVersion();
this.versionTimestamp = t.getVersionTimestamp();
if (needKeys) {
kbuf.reset().copyAll(t.getKeyBuffer());
// final ByteArrayBuffer tmp = t.getKeyBuffer();
// kbuf.put(tmp.array(), 0/* offset */, tmp.limit());
}
if (needVals) {
vbuf.reset();
if (!versionDeleted) {
isNull = t.isNull();
if (!isNull) {
vbuf.reset().copyAll(t.getValueBuffer());
// final ByteArrayBuffer tmp = t.getValueBuffer();
//
// vbuf.put(tmp.array(), 0/* offset */, tmp.limit());
}
}
}
}
/**
* Clears the buffered data copied into the {@link AbstractTuple} from the
* source btree.
*/
protected void clear() {
this.versionDeleted = false;
this.versionTimestamp = 0L;
if (kbuf != null)
this.kbuf.reset();
if (vbuf != null)
this.vbuf.reset();
this.isNull = true;
}
/**
* Change the tuple state to reflect the fact that the tuple has been
* deleted from the backing index.
*/
void markDeleted() {
this.versionDeleted = true;
this.versionTimestamp = 0L;
if (vbuf != null)
this.vbuf.reset();
this.isNull = true;
}
/**
* Note: A de-serialization problem thrown out of {@link #toString()} MAY
* indicate that the {@link ITupleSerializer} was not overriden and a raw
* byte[] was stored as the tuple's value. In this case,
* {@link #getObject()} will attempt to use Java standard de-serialization
* on the byte[] value, which will, of course, fail. The fix for this
* problem is to specify the {@link NOPTupleSerializer} for the index. This
* problem may still show up in some of the test suites when the trace level
* is turned up since many of the tests were written before the
* {@link ITupleSerializer} abstraction was addeded.
*/
public String toString() {
return toString(this);
}
/**
* Utility method returns a {@link String} representation of an
* {@link ITuple}.
*
* @param tuple
* The {@link ITuple}.
*
* @return The {@link String} representation of that {@link ITuple}.
*/
public static String toString(final ITuple tuple) {
Object obj;
try {
obj = tuple.getObject();
} catch(UnsupportedOperationException ex) {
// note: indicates Not Available rather than [null].
obj = "N/A";
} catch(Throwable t) {
/*
* Note: The most common error here is an attempt to de-serialize a
* byte[] that does not in fact contain a serialized object. This
* typically happens when the DefaultTupleSerializer was not
* overridden with a NOPTupleSerializer and the application (or often
* the test case) is storing byte[]s rather than Objects.
*/
obj = t.getMessage();
}
final StringBuilder sb = new StringBuilder();
sb.append(tuple.getClass().getName() + "@" + tuple.hashCode());
sb.append("{ nvisited=" + tuple.getVisitCount());
sb.append(", flags=" + flagString(tuple.flags()));
if (tuple.isDeletedVersion()) {
sb.append(", deleted");
}
if (tuple.getVersionTimestamp() != 0L) {
sb.append(", timestamp=" + tuple.getVersionTimestamp());
}
sb.append(", key="
+ (tuple.getKeysRequested() ? Arrays.toString(tuple.getKey())
: "N/A"));
sb.append(", val="
+ (tuple.getValuesRequested() ? (tuple.isNull() ? "null"
: Arrays.toString(tuple.getValue())) : "N/A"));
sb.append(", obj="
+ (obj instanceof byte[] ? Arrays
.toString((byte[]) obj) : obj));
sb.append(", sourceIndex=" + tuple.getSourceIndex());
sb.append("}");
return sb.toString();
}
/**
* Externalizes the flags as a list of symbolic constants.
*
* @param flags
* The {@link IRangeQuery} flags.
*
* @return The list of symbolic constants.
*/
public static String flagString(final int flags) {
final StringBuilder sb = new StringBuilder();
// #of flags that are turned on.
int onCount = 0;
sb.append("[");
if ((flags & IRangeQuery.KEYS) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("KEYS");
}
if ((flags & IRangeQuery.VALS) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("VALS");
}
if ((flags & IRangeQuery.DELETED) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("DELETED");
}
if ((flags & IRangeQuery.READONLY) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("READONLY");
}
if ((flags & IRangeQuery.REMOVEALL) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("REMOVEALL");
}
if ((flags & IRangeQuery.CURSOR) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("CURSOR");
}
if ((flags & IRangeQuery.REVERSE) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("REVERSE");
}
if ((flags & IRangeQuery.FIXED_LENGTH_SUCCESSOR) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("FIXED_LENGTH_SUCCESSOR");
}
if ((flags & IRangeQuery.PARALLEL) != 0) {
if (onCount++ > 0)
sb.append(",");
sb.append("PARALLEL");
}
sb.append("]");
return sb.toString();
}
}