/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed 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 co.cask.cdap.data2.dataset2.lib.table.leveldb;
import co.cask.cdap.api.common.Bytes;
import java.io.DataOutput;
import java.io.IOException;
/**
* This is a copy of parts of HBase's KeyValue, to be used for the leveldb table implementation.
*/
public class KeyValue {
/**
* Timestamp to use when we want to refer to the latest cell.
* This is the timestamp sent by clients when no timestamp is specified on
* commit.
*/
public static final long LATEST_TIMESTAMP = Long.MAX_VALUE;
/**
* Timestamp to use when we want to refer to the oldest cell.
*/
public static final long OLDEST_TIMESTAMP = Long.MIN_VALUE;
/**
* Comparator for plain key; i.e. non-catalog table key. Works on Key portion
* of KeyValue only.
*/
public static final KeyComparator KEY_COMPARATOR = new KeyComparator();
/** Size of the key type field in bytes. */
public static final int TYPE_SIZE = Bytes.SIZEOF_BYTE;
/** Size of the row length field in bytes. */
public static final int ROW_LENGTH_SIZE = Bytes.SIZEOF_SHORT;
/** Size of the family length field in bytes. */
public static final int FAMILY_LENGTH_SIZE = Bytes.SIZEOF_BYTE;
/** Size of the timestamp field in bytes. */
public static final int TIMESTAMP_SIZE = Bytes.SIZEOF_LONG;
// Size of the timestamp and type byte on end of a key -- a long + a byte.
public static final int TIMESTAMP_TYPE_SIZE = TIMESTAMP_SIZE + TYPE_SIZE;
// Size of the length shorts and bytes in key.
public static final int KEY_INFRASTRUCTURE_SIZE = ROW_LENGTH_SIZE
+ FAMILY_LENGTH_SIZE + TIMESTAMP_TYPE_SIZE;
// How far into the key the row starts at. First thing to read is the short
// that says how long the row is.
public static final int ROW_OFFSET =
Bytes.SIZEOF_INT /*keylength*/ +
Bytes.SIZEOF_INT /*valuelength*/;
// Size of the length ints in a KeyValue datastructure.
public static final int KEYVALUE_INFRASTRUCTURE_SIZE = ROW_OFFSET;
/**
* Key type.
* Has space for other key types to be added later. Cannot rely on
* enum ordinals . They change if item is removed or moved. Do our own codes.
*/
public static enum Type {
Minimum((byte) 0),
Put((byte) 4),
Delete((byte) 8),
DeleteColumn((byte) 12),
UndeleteColumn((byte) 13),
DeleteFamily((byte) 14),
// Maximum is used when searching; you look from maximum on down.
Maximum((byte) 255);
private final byte code;
Type(final byte c) {
this.code = c;
}
public byte getCode() {
return this.code;
}
/**
* Cannot rely on enum ordinals . They change if item is removed or moved.
* Do our own codes.
* @return Type associated with passed code.
*/
public static Type codeToType(final byte b) {
for (Type t : Type.values()) {
if (t.getCode() == b) {
return t;
}
}
throw new RuntimeException("Unknown code " + b);
}
}
private byte [] bytes = null;
private int offset = 0;
private int length = 0;
// the row cached
private volatile byte [] rowCache = null;
/**
* Creates a KeyValue from the start of the specified byte array.
* Presumes <code>bytes</code> content is formatted as a KeyValue blob.
* @param bytes byte array
*/
public KeyValue(final byte [] bytes) {
this(bytes, 0);
}
/**
* Creates a KeyValue from the specified byte array and offset.
* Presumes <code>bytes</code> content starting at <code>offset</code> is
* formatted as a KeyValue blob.
* @param bytes byte array
* @param offset offset to start of KeyValue
*/
public KeyValue(final byte [] bytes, final int offset) {
this(bytes, offset, getLength(bytes, offset));
}
/**
* Creates a KeyValue from the specified byte array, starting at offset, and
* for length <code>length</code>.
* @param bytes byte array
* @param offset offset to start of the KeyValue
* @param length length of the KeyValue
*/
public KeyValue(final byte [] bytes, final int offset, final int length) {
this.bytes = bytes;
this.offset = offset;
this.length = length;
}
/** Constructors that build a new backing byte array from fields */
/**
* Constructs KeyValue structure filled with specified values.
* @param row row key
* @param family family name
* @param qualifier column qualifier
* @param timestamp version timestamp
* @param type key type
* @throws IllegalArgumentException
*/
public KeyValue(final byte[] row, final byte[] family,
final byte[] qualifier, final long timestamp, Type type) {
this(row, family, qualifier, timestamp, type, null);
}
/**
* Constructs KeyValue structure filled with specified values.
* @param row row key
* @param family family name
* @param qualifier column qualifier
* @param timestamp version timestamp
* @param value column value
* @throws IllegalArgumentException
*/
public KeyValue(final byte[] row, final byte[] family,
final byte[] qualifier, final long timestamp, final byte[] value) {
this(row, family, qualifier, timestamp, Type.Put, value);
}
/**
* Constructs KeyValue structure filled with specified values.
* @param row row key
* @param family family name
* @param qualifier column qualifier
* @param timestamp version timestamp
* @param type key type
* @param value column value
* @throws IllegalArgumentException
*/
public KeyValue(final byte[] row, final byte[] family,
final byte[] qualifier, final long timestamp, Type type,
final byte[] value) {
this(row, family, qualifier, 0, qualifier == null ? 0 : qualifier.length,
timestamp, type, value, 0, value == null ? 0 : value.length);
}
/**
* Constructs KeyValue structure filled with specified values.
* @param row row key
* @param family family name
* @param qualifier column qualifier
* @param qoffset qualifier offset
* @param qlength qualifier length
* @param timestamp version timestamp
* @param type key type
* @param value column value
* @param voffset value offset
* @param vlength value length
* @throws IllegalArgumentException
*/
public KeyValue(byte [] row, byte [] family,
byte [] qualifier, int qoffset, int qlength, long timestamp, Type type,
byte [] value, int voffset, int vlength) {
this(row, 0, row == null ? 0 : row.length,
family, 0, family == null ? 0 : family.length,
qualifier, qoffset, qlength, timestamp, type,
value, voffset, vlength);
}
/**
* Constructs KeyValue structure filled with specified values.
* <p>
* Column is split into two fields, family and qualifier.
* @param row row key
* @param roffset row offset
* @param rlength row length
* @param family family name
* @param foffset family offset
* @param flength family length
* @param qualifier column qualifier
* @param qoffset qualifier offset
* @param qlength qualifier length
* @param timestamp version timestamp
* @param type key type
* @param value column value
* @param voffset value offset
* @param vlength value length
* @throws IllegalArgumentException
*/
public KeyValue(final byte [] row, final int roffset, final int rlength,
final byte [] family, final int foffset, final int flength,
final byte [] qualifier, final int qoffset, final int qlength,
final long timestamp, final Type type,
final byte [] value, final int voffset, final int vlength) {
this.bytes = createByteArray(row, roffset, rlength,
family, foffset, flength, qualifier, qoffset, qlength,
timestamp, type, value, voffset, vlength);
this.length = bytes.length;
this.offset = 0;
}
/**
* Write KeyValue format into a byte array.
*
* @param row row key
* @param roffset row offset
* @param rlength row length
* @param family family name
* @param foffset family offset
* @param flength family length
* @param qualifier column qualifier
* @param qoffset qualifier offset
* @param qlength qualifier length
* @param timestamp version timestamp
* @param type key type
* @param value column value
* @param voffset value offset
* @param vlength value length
* @return The newly created byte array.
*/
static byte [] createByteArray(final byte [] row, final int roffset,
final int rlength, final byte [] family, final int foffset, int flength,
final byte [] qualifier, final int qoffset, int qlength,
final long timestamp, final Type type,
final byte [] value, final int voffset, int vlength) {
if (rlength > Short.MAX_VALUE) {
throw new IllegalArgumentException("Row > " + Short.MAX_VALUE);
}
if (row == null) {
throw new IllegalArgumentException("Row is null");
}
// Family length
flength = family == null ? 0 : flength;
if (flength > Byte.MAX_VALUE) {
throw new IllegalArgumentException("Family > " + Byte.MAX_VALUE);
}
// Qualifier length
qlength = qualifier == null ? 0 : qlength;
if (qlength > Integer.MAX_VALUE - rlength - flength) {
throw new IllegalArgumentException("Qualifier > " + Integer.MAX_VALUE);
}
// Key length
long longkeylength = KEY_INFRASTRUCTURE_SIZE + rlength + flength + qlength;
if (longkeylength > Integer.MAX_VALUE) {
throw new IllegalArgumentException("keylength " + longkeylength + " > " +
Integer.MAX_VALUE);
}
int keylength = (int) longkeylength;
// Value length
vlength = value == null ? 0 : vlength;
// Allocate right-sized byte array.
byte [] bytes = new byte[KEYVALUE_INFRASTRUCTURE_SIZE + keylength + vlength];
// Write key, value and key row length.
int pos = 0;
pos = Bytes.putInt(bytes, pos, keylength);
pos = Bytes.putInt(bytes, pos, vlength);
pos = Bytes.putShort(bytes, pos, (short) (rlength & 0x0000ffff));
pos = Bytes.putBytes(bytes, pos, row, roffset, rlength);
pos = Bytes.putByte(bytes, pos, (byte) (flength & 0x0000ff));
if (flength != 0) {
pos = Bytes.putBytes(bytes, pos, family, foffset, flength);
}
if (qlength != 0) {
pos = Bytes.putBytes(bytes, pos, qualifier, qoffset, qlength);
}
pos = Bytes.putLong(bytes, pos, timestamp);
pos = Bytes.putByte(bytes, pos, type.getCode());
if (value != null && value.length > 0) {
Bytes.putBytes(bytes, pos, value, voffset, vlength);
}
return bytes;
}
// Needed doing 'contains' on List. Only compares the key portion, not the
// value.
public boolean equals(Object other) {
if (!(other instanceof KeyValue)) {
return false;
}
KeyValue kv = (KeyValue) other;
// Comparing bytes should be fine doing equals test. Shouldn't have to
// worry about special .META. comparators doing straight equals.
return Bytes.equals(getBuffer(), getKeyOffset(), getKeyLength(),
kv.getBuffer(), kv.getKeyOffset(), kv.getKeyLength());
}
public int hashCode() {
byte[] b = getBuffer();
int start = getOffset(), end = getOffset() + getLength();
int h = b[start++];
for (int i = start; i < end; i++) {
h = (h * 13) ^ b[i];
}
return h;
}
//---------------------------------------------------------------------------
//
// String representation
//
//---------------------------------------------------------------------------
public String toString() {
if (this.bytes == null || this.bytes.length == 0) {
return "empty";
}
return keyToString(this.bytes, this.offset + ROW_OFFSET, getKeyLength()) +
"/vlen=" + getValueLength();
}
public static String keyToString(final byte [] b, final int o, final int l) {
if (b == null) {
return "";
}
int rowlength = Bytes.toShort(b, o);
String row = Bytes.toStringBinary(b, o + Bytes.SIZEOF_SHORT, rowlength);
int columnoffset = o + Bytes.SIZEOF_SHORT + 1 + rowlength;
int familylength = b[columnoffset - 1];
int columnlength = l - ((columnoffset - o) + TIMESTAMP_TYPE_SIZE);
String family = familylength == 0 ? "" :
Bytes.toStringBinary(b, columnoffset, familylength);
String qualifier = columnlength == 0 ? "" :
Bytes.toStringBinary(b, columnoffset + familylength,
columnlength - familylength);
long timestamp = Bytes.toLong(b, o + (l - TIMESTAMP_TYPE_SIZE));
String timestampStr = humanReadableTimestamp(timestamp);
byte type = b[o + l - 1];
return row + "/" + family +
(family != null && family.length() > 0 ? ":" : "") +
qualifier + "/" + timestampStr + "/" + Type.codeToType(type);
}
public static String humanReadableTimestamp(final long timestamp) {
if (timestamp == LATEST_TIMESTAMP) {
return "LATEST_TIMESTAMP";
}
if (timestamp == OLDEST_TIMESTAMP) {
return "OLDEST_TIMESTAMP";
}
return String.valueOf(timestamp);
}
//---------------------------------------------------------------------------
//
// Public Member Accessors
//
//---------------------------------------------------------------------------
/**
* @return The byte array backing this KeyValue.
*/
public byte [] getBuffer() {
return this.bytes;
}
/**
* @return Offset into {@link #getBuffer()} at which this KeyValue starts.
*/
public int getOffset() {
return this.offset;
}
/**
* @return Length of bytes this KeyValue occupies in {@link #getBuffer()}.
*/
public int getLength() {
return length;
}
//---------------------------------------------------------------------------
//
// Length and Offset Calculators
//
//---------------------------------------------------------------------------
/**
* Determines the total length of the KeyValue stored in the specified
* byte array and offset. Includes all headers.
* @param bytes byte array
* @param offset offset to start of the KeyValue
* @return length of entire KeyValue, in bytes
*/
private static int getLength(byte [] bytes, int offset) {
return ROW_OFFSET +
Bytes.toInt(bytes, offset) +
Bytes.toInt(bytes, offset + Bytes.SIZEOF_INT);
}
/**
* @return Key offset in backing buffer..
*/
public int getKeyOffset() {
return this.offset + ROW_OFFSET;
}
/**
* Length of key portion.
*/
private int keyLength = 0;
public int getKeyLength() {
if (keyLength == 0) {
keyLength = Bytes.toInt(this.bytes, this.offset);
}
return keyLength;
}
/**
* @return Value offset
*/
public int getValueOffset() {
return getKeyOffset() + getKeyLength();
}
/**
* @return Value length
*/
public int getValueLength() {
return Bytes.toInt(this.bytes, this.offset + Bytes.SIZEOF_INT);
}
/**
* @return Row offset
*/
public int getRowOffset() {
return getKeyOffset() + Bytes.SIZEOF_SHORT;
}
/**
* @return Row length
*/
public short getRowLength() {
return Bytes.toShort(this.bytes, getKeyOffset());
}
/**
* @return Family offset
*/
public int getFamilyOffset() {
return getFamilyOffset(getRowLength());
}
/**
* @return Family offset
*/
public int getFamilyOffset(int rlength) {
return this.offset + ROW_OFFSET + Bytes.SIZEOF_SHORT + rlength + Bytes.SIZEOF_BYTE;
}
/**
* @return Family length
*/
public byte getFamilyLength() {
return getFamilyLength(getFamilyOffset());
}
/**
* @return Family length
*/
public byte getFamilyLength(int foffset) {
return this.bytes[foffset - 1];
}
/**
* @return Qualifier offset
*/
public int getQualifierOffset() {
return getQualifierOffset(getFamilyOffset());
}
/**
* @return Qualifier offset
*/
public int getQualifierOffset(int foffset) {
return foffset + getFamilyLength(foffset);
}
/**
* @return Qualifier length
*/
public int getQualifierLength() {
return getQualifierLength(getRowLength(), getFamilyLength());
}
/**
* @return Qualifier length
*/
public int getQualifierLength(int rlength, int flength) {
return getKeyLength() -
(KEY_INFRASTRUCTURE_SIZE + rlength + flength);
}
/**
* @param keylength Pass if you have it to save on a int creation.
* @return Timestamp offset
*/
public int getTimestampOffset(final int keylength) {
return getKeyOffset() + keylength - TIMESTAMP_TYPE_SIZE;
}
//---------------------------------------------------------------------------
//
// Methods that return copies of fields
//
//---------------------------------------------------------------------------
/**
* Do not use unless you have to. Used internally for compacting and testing.
*
* Use {@link #getRow()}, {@link #getFamily()}, {@link #getQualifier()}, and
* {@link #getValue()} if accessing a KeyValue client-side.
* @return Copy of the key portion only.
*/
public byte [] getKey() {
int keylength = getKeyLength();
byte [] key = new byte[keylength];
System.arraycopy(getBuffer(), getKeyOffset(), key, 0, keylength);
return key;
}
/**
* Returns value in a new byte array.
* Primarily for use client-side. If server-side, use
* {@link #getBuffer()} with appropriate offsets and lengths instead to
* save on allocations.
* @return Value in a new byte array.
*/
public byte [] getValue() {
int o = getValueOffset();
int l = getValueLength();
byte [] result = new byte[l];
System.arraycopy(getBuffer(), o, result, 0, l);
return result;
}
/**
* Primarily for use client-side. Returns the row of this KeyValue in a new
* byte array.<p>
*
* If server-side, use {@link #getBuffer()} with appropriate offsets and
* lengths instead.
* @return Row in a new byte array.
*/
public byte [] getRow() {
if (rowCache == null) {
int o = getRowOffset();
short l = getRowLength();
// initialize and copy the data into a local variable
// in case multiple threads race here.
byte local[] = new byte[l];
System.arraycopy(getBuffer(), o, local, 0, l);
rowCache = local; // volatile assign
}
return rowCache;
}
private long timestampCache = -1;
/**
* @return Timestamp
*/
public long getTimestamp() {
if (timestampCache == -1) {
timestampCache = getTimestamp(getKeyLength());
}
return timestampCache;
}
/**
* @param keylength Pass if you have it to save on a int creation.
* @return Timestamp
*/
long getTimestamp(final int keylength) {
int tsOffset = getTimestampOffset(keylength);
return Bytes.toLong(this.bytes, tsOffset);
}
/**
* @return Type of this KeyValue.
*/
public byte getType() {
return getType(getKeyLength());
}
/**
* @param keylength Pass if you have it to save on a int creation.
* @return Type of this KeyValue.
*/
byte getType(final int keylength) {
return this.bytes[this.offset + keylength - 1 + ROW_OFFSET];
}
/**
* Primarily for use client-side. Returns the family of this KeyValue in a
* new byte array.<p>
*
* If server-side, use {@link #getBuffer()} with appropriate offsets and
* lengths instead.
* @return Returns family. Makes a copy.
*/
public byte [] getFamily() {
int o = getFamilyOffset();
int l = getFamilyLength(o);
byte [] result = new byte[l];
System.arraycopy(this.bytes, o, result, 0, l);
return result;
}
/**
* Primarily for use client-side. Returns the column qualifier of this
* KeyValue in a new byte array.<p>
*
* If server-side, use {@link #getBuffer()} with appropriate offsets and
* lengths instead.
* Use {@link #getBuffer()} with appropriate offsets and lengths instead.
* @return Returns qualifier. Makes a copy.
*/
public byte [] getQualifier() {
int o = getQualifierOffset();
int l = getQualifierLength();
byte [] result = new byte[l];
System.arraycopy(this.bytes, o, result, 0, l);
return result;
}
//---------------------------------------------------------------------------
//
// KeyValue splitter
//
//---------------------------------------------------------------------------
/**
* Utility class that splits a KeyValue buffer into separate byte arrays.
* <p>
* Should get rid of this if we can, but is very useful for debugging.
*/
public static class SplitKeyValue {
private byte [][] split;
SplitKeyValue() {
this.split = new byte[6][];
}
public void setRow(byte [] value) {
this.split[0] = value;
}
public void setFamily(byte [] value) {
this.split[1] = value;
}
public void setQualifier(byte [] value) {
this.split[2] = value;
}
public void setTimestamp(byte [] value) {
this.split[3] = value;
}
public void setType(byte [] value) {
this.split[4] = value;
}
public void setValue(byte [] value) {
this.split[5] = value;
}
public byte [] getRow() {
return this.split[0];
}
public byte [] getTimestamp() {
return this.split[3];
}
public byte [] getType() {
return this.split[4];
}
public byte [] getValue() {
return this.split[5];
}
}
public SplitKeyValue split() {
SplitKeyValue split = new SplitKeyValue();
int splitOffset = this.offset;
int keyLen = Bytes.toInt(bytes, splitOffset);
splitOffset += Bytes.SIZEOF_INT;
int valLen = Bytes.toInt(bytes, splitOffset);
splitOffset += Bytes.SIZEOF_INT;
short rowLen = Bytes.toShort(bytes, splitOffset);
splitOffset += Bytes.SIZEOF_SHORT;
byte [] row = new byte[rowLen];
System.arraycopy(bytes, splitOffset, row, 0, rowLen);
splitOffset += rowLen;
split.setRow(row);
byte famLen = bytes[splitOffset];
splitOffset += Bytes.SIZEOF_BYTE;
byte [] family = new byte[famLen];
System.arraycopy(bytes, splitOffset, family, 0, famLen);
splitOffset += famLen;
split.setFamily(family);
int colLen = keyLen -
(rowLen + famLen + Bytes.SIZEOF_SHORT + Bytes.SIZEOF_BYTE +
Bytes.SIZEOF_LONG + Bytes.SIZEOF_BYTE);
byte [] qualifier = new byte[colLen];
System.arraycopy(bytes, splitOffset, qualifier, 0, colLen);
splitOffset += colLen;
split.setQualifier(qualifier);
byte [] timestamp = new byte[Bytes.SIZEOF_LONG];
System.arraycopy(bytes, splitOffset, timestamp, 0, Bytes.SIZEOF_LONG);
splitOffset += Bytes.SIZEOF_LONG;
split.setTimestamp(timestamp);
byte [] type = new byte[1];
type[0] = bytes[splitOffset];
splitOffset += Bytes.SIZEOF_BYTE;
split.setType(type);
byte [] value = new byte[valLen];
System.arraycopy(bytes, splitOffset, value, 0, valLen);
split.setValue(value);
return split;
}
//---------------------------------------------------------------------------
//
// Compare specified fields against those contained in this KeyValue
//
//---------------------------------------------------------------------------
/**
* Compare key portion of a {@link KeyValue}.
*/
public static class KeyComparator {
volatile boolean ignoreTimestamp = false;
volatile boolean ignoreType = false;
public int compare(byte[] left, int loffset, int llength, byte[] right,
int roffset, int rlength) {
// Compare row
short lrowlength = Bytes.toShort(left, loffset);
short rrowlength = Bytes.toShort(right, roffset);
int compare = compareRows(left, loffset + Bytes.SIZEOF_SHORT,
lrowlength, right, roffset + Bytes.SIZEOF_SHORT, rrowlength);
if (compare != 0) {
return compare;
}
// Compare the rest of the two KVs without making any assumptions about
// the common prefix. This function will not compare rows anyway, so we
// don't need to tell it that the common prefix includes the row.
return compareWithoutRow(0, left, loffset, llength, right, roffset,
rlength, rrowlength);
}
/**
* Compare columnFamily, qualifier, timestamp, and key type (everything
* except the row). This method is used both in the normal comparator and
* the "same-prefix" comparator. Note that we are assuming that row portions
* of both KVs have already been parsed and found identical, and we don't
* validate that assumption here.
* @param commonPrefix
* the length of the common prefix of the two key-values being
* compared, including row length and row
*/
private int compareWithoutRow(int commonPrefix, byte[] left, int loffset,
int llength, byte[] right, int roffset, int rlength, short rowlength) {
/***
* KeyValue Format and commonLength:
* |_keyLen_|_valLen_|_rowLen_|_rowKey_|_famiLen_|_fami_|_Quali_|....
* ------------------|-------commonLength--------|--------------
*/
int commonLength = ROW_LENGTH_SIZE + FAMILY_LENGTH_SIZE + rowlength;
// commonLength + TIMESTAMP_TYPE_SIZE
int commonLengthWithTSAndType = TIMESTAMP_TYPE_SIZE + commonLength;
// ColumnFamily + Qualifier length.
int lcolumnlength = llength - commonLengthWithTSAndType;
int rcolumnlength = rlength - commonLengthWithTSAndType;
byte ltype = left[loffset + (llength - 1)];
byte rtype = right[roffset + (rlength - 1)];
// If the column is not specified, the "minimum" key type appears the
// latest in the sorted order, regardless of the timestamp. This is used
// for specifying the last key/value in a given row, because there is no
// "lexicographically last column" (it would be infinitely long). The
// "maximum" key type does not need this behavior.
if (lcolumnlength == 0 && ltype == Type.Minimum.getCode()) {
// left is "bigger", i.e. it appears later in the sorted order
return 1;
}
if (rcolumnlength == 0 && rtype == Type.Minimum.getCode()) {
return -1;
}
int lfamilyoffset = commonLength + loffset;
int rfamilyoffset = commonLength + roffset;
// Column family length.
int lfamilylength = left[lfamilyoffset - 1];
int rfamilylength = right[rfamilyoffset - 1];
// If left family size is not equal to right family size, we need not
// compare the qualifiers.
boolean sameFamilySize = (lfamilylength == rfamilylength);
int common = 0;
if (commonPrefix > 0) {
common = Math.max(0, commonPrefix - commonLength);
if (!sameFamilySize) {
// Common should not be larger than Math.min(lfamilylength,
// rfamilylength).
common = Math.min(common, Math.min(lfamilylength, rfamilylength));
} else {
common = Math.min(common, Math.min(lcolumnlength, rcolumnlength));
}
}
if (!sameFamilySize) {
// comparing column family is enough.
return Bytes.compareTo(left, lfamilyoffset + common, lfamilylength
- common, right, rfamilyoffset + common, rfamilylength - common);
}
// Compare family & qualifier together.
final int comparison = Bytes.compareTo(left, lfamilyoffset + common,
lcolumnlength - common, right, rfamilyoffset + common,
rcolumnlength - common);
if (comparison != 0) {
return comparison;
}
return compareTimestampAndType(left, loffset, llength, right, roffset,
rlength, ltype, rtype);
}
private int compareTimestampAndType(byte[] left, int loffset, int llength,
byte[] right, int roffset, int rlength, byte ltype, byte rtype) {
int compare;
if (!this.ignoreTimestamp) {
// Get timestamps.
long ltimestamp = Bytes.toLong(left,
loffset + (llength - TIMESTAMP_TYPE_SIZE));
long rtimestamp = Bytes.toLong(right,
roffset + (rlength - TIMESTAMP_TYPE_SIZE));
compare = compareTimestamps(ltimestamp, rtimestamp);
if (compare != 0) {
return compare;
}
}
if (!this.ignoreType) {
// Compare types. Let the delete types sort ahead of puts; i.e. types
// of higher numbers sort before those of lesser numbers. Maximum (255)
// appears ahead of everything, and minimum (0) appears after
// everything.
return (0xff & rtype) - (0xff & ltype);
}
return 0;
}
public int compare(byte[] left, byte[] right) {
return compare(left, 0, left.length, right, 0, right.length);
}
public int compareRows(byte [] left, int loffset, int llength,
byte [] right, int roffset, int rlength) {
return Bytes.compareTo(left, loffset, llength, right, roffset, rlength);
}
int compareTimestamps(final long ltimestamp, final long rtimestamp) {
// The below older timestamps sorting ahead of newer timestamps looks
// wrong but it is intentional. This way, newer timestamps are first
// found when we iterate over a memstore and newer versions are the
// first we trip over when reading from a store file.
if (ltimestamp < rtimestamp) {
return 1;
} else if (ltimestamp > rtimestamp) {
return -1;
}
return 0;
}
}
public void write(final DataOutput out) throws IOException {
out.writeInt(this.length);
out.write(this.bytes, this.offset, this.length);
}
public static KeyValue fromKey(byte[] key) {
int len = key.length + (2 * Bytes.SIZEOF_INT);
byte[] kvBytes = new byte[len];
int pos = 0;
pos = Bytes.putInt(kvBytes, pos, key.length);
pos = Bytes.putInt(kvBytes, pos, 0);
Bytes.putBytes(kvBytes, pos, key, 0, key.length);
return new KeyValue(kvBytes);
}
}