/** * Copyright 2009 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Comparator; import com.google.common.primitives.Longs; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.io.HeapSize; import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ClassSize; import org.apache.hadoop.io.RawComparator; import org.apache.hadoop.io.Writable; /** * An HBase Key/Value. * * <p>If being used client-side, the primary methods to access individual fields * are {@link #getRow()}, {@link #getFamily()}, {@link #getQualifier()}, * {@link #getTimestamp()}, and {@link #getValue()}. These methods allocate new * byte arrays and return copies so they should be avoided server-side. * * <p>Instances of this class are immutable. They are not * comparable but Comparators are provided. Comparators change with context, * whether user table or a catalog table comparison context. Its * important that you use the appropriate comparator comparing rows in * particular. There are Comparators for KeyValue instances and then for * just the Key portion of a KeyValue used mostly in {@link HFile}. * * <p>KeyValue wraps a byte array and has offset and length for passed array * at where to start interpreting the content as a KeyValue blob. The KeyValue * blob format inside the byte array is: * <code><keylength> <valuelength> <key> <value></code> * Key is decomposed as: * <code><rowlength> <row> <columnfamilylength> <columnfamily> <columnqualifier> <timestamp> <keytype></code> * Rowlength maximum is Short.MAX_SIZE, column family length maximum is * Byte.MAX_SIZE, and column qualifier + key length must be < Integer.MAX_SIZE. * The column does not contain the family/qualifier delimiter. * * <p>TODO: Group Key-only comparators and operations into a Key class, just * for neatness sake, if can figure what to call it. */ public class KeyValue implements Writable, HeapSize { static final Log LOG = LogFactory.getLog(KeyValue.class); /** * Colon character in UTF-8 */ public static final char COLUMN_FAMILY_DELIMITER = ':'; public static final byte[] COLUMN_FAMILY_DELIM_ARRAY = new byte[]{COLUMN_FAMILY_DELIMITER}; /** * Comparator for plain key/values; i.e. non-catalog table key/values. */ public static KVComparator COMPARATOR = new KVComparator(); /** * Comparator for plain key; i.e. non-catalog table key. Works on Key portion * of KeyValue only. */ public static KeyComparator KEY_COMPARATOR = new KeyComparator(); /** * A {@link KVComparator} for <code>.META.</code> catalog table * {@link KeyValue}s. */ public static KVComparator META_COMPARATOR = new MetaComparator(); /** * A {@link KVComparator} for <code>.META.</code> catalog table * {@link KeyValue} keys. */ public static KeyComparator META_KEY_COMPARATOR = new MetaKeyComparator(); /** * A {@link KVComparator} for <code>-ROOT-</code> catalog table * {@link KeyValue}s. */ public static KVComparator ROOT_COMPARATOR = new RootComparator(); /** * A {@link KVComparator} for <code>-ROOT-</code> catalog table * {@link KeyValue} keys. */ public static KeyComparator ROOT_KEY_COMPARATOR = new RootKeyComparator(); /** * Get the appropriate row comparator for the specified table. * * Hopefully we can get rid of this, I added this here because it's replacing * something in HSK. We should move completely off of that. * * @param tableName The table name. * @return The comparator. */ public static KeyComparator getRowComparator(byte [] tableName) { if(Bytes.equals(HTableDescriptor.ROOT_TABLEDESC.getName(),tableName)) { return ROOT_COMPARATOR.getRawComparator(); } if(Bytes.equals(HTableDescriptor.META_TABLEDESC.getName(), tableName)) { return META_COMPARATOR.getRawComparator(); } return COMPARATOR.getRawComparator(); } // Size of the timestamp and type byte on end of a key -- a long + a byte. public static final int TIMESTAMP_TYPE_SIZE = Bytes.SIZEOF_LONG /* timestamp */ + Bytes.SIZEOF_BYTE /*keytype*/; // Size of the length shorts and bytes in key. public static final int KEY_INFRASTRUCTURE_SIZE = Bytes.SIZEOF_SHORT /*rowlength*/ + Bytes.SIZEOF_BYTE /*columnfamilylength*/ + 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), 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. * @param b * @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); } } /** * Lowest possible key. * Makes a Key with highest possible Timestamp, empty row and column. No * key can be equal or lower than this one in memstore or in store file. */ public static final KeyValue LOWESTKEY = new KeyValue(HConstants.EMPTY_BYTE_ARRAY, HConstants.LATEST_TIMESTAMP); private byte [] bytes = null; private int offset = 0; private int length = 0; // the row cached private byte [] rowCache = null; /** Here be dragons **/ // used to achieve atomic operations in the memstore. public long getMemstoreTS() { return memstoreTS; } public void setMemstoreTS(long memstoreTS) { this.memstoreTS = memstoreTS; } // default value is 0, aka DNC private long memstoreTS = 0; /** Dragon time over, return to normal business */ /** Writable Constructor -- DO NOT USE */ public KeyValue() {} /** * 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 null value. * Sets type to {@link KeyValue.Type#Maximum} * @param row - row key (arbitrary byte array) * @param timestamp */ public KeyValue(final byte [] row, final long timestamp) { this(row, timestamp, Type.Maximum); } /** * Constructs KeyValue structure filled with null value. * @param row - row key (arbitrary byte array) * @param timestamp */ public KeyValue(final byte [] row, final long timestamp, Type type) { this(row, null, null, timestamp, type, null); } /** * Constructs KeyValue structure filled with null value. * Sets type to {@link KeyValue.Type#Maximum} * @param row - row key (arbitrary byte array) * @param family family name * @param qualifier column qualifier */ public KeyValue(final byte [] row, final byte [] family, final byte [] qualifier) { this(row, family, qualifier, HConstants.LATEST_TIMESTAMP, Type.Maximum); } /** * Constructs KeyValue structure filled with null value. * @param row - row key (arbitrary byte array) * @param family family name * @param qualifier column qualifier */ public KeyValue(final byte [] row, final byte [] family, final byte [] qualifier, final byte [] value) { this(row, family, qualifier, HConstants.LATEST_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 * @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; if (vlength > HConstants.MAXIMUM_VALUE_LENGTH) { // FindBugs INT_VACUOUS_COMPARISON throw new IllegalArgumentException("Valuer > " + HConstants.MAXIMUM_VALUE_LENGTH); } // 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) { pos = Bytes.putBytes(bytes, pos, value, voffset, vlength); } return bytes; } /** * Write KeyValue format into a byte array. * <p> * Takes column in the form <code>family:qualifier</code> * @param row - row key (arbitrary byte array) * @param roffset * @param rlength * @param column * @param coffset * @param clength * @param timestamp * @param type * @param value * @param voffset * @param vlength * @return The newly created byte array. */ static byte [] createByteArray(final byte [] row, final int roffset, final int rlength, final byte [] column, final int coffset, int clength, final long timestamp, final Type type, final byte [] value, final int voffset, int vlength) { // If column is non-null, figure where the delimiter is at. int delimiteroffset = 0; if (column != null && column.length > 0) { delimiteroffset = getFamilyDelimiterIndex(column, coffset, clength); if (delimiteroffset > Byte.MAX_VALUE) { throw new IllegalArgumentException("Family > " + Byte.MAX_VALUE); } } else { return createByteArray(row,roffset,rlength,null,0,0,null,0,0,timestamp, type,value,voffset,vlength); } int flength = delimiteroffset-coffset; int qlength = clength - flength - 1; return createByteArray(row, roffset, rlength, column, coffset, flength, column, delimiteroffset+1, qlength, timestamp, type, value, voffset, vlength); } // 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. boolean result = Bytes.BYTES_RAWCOMPARATOR.compare(getBuffer(), getKeyOffset(), getKeyLength(), kv.getBuffer(), kv.getKeyOffset(), kv.getKeyLength()) == 0; return result; } 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; } //--------------------------------------------------------------------------- // // KeyValue cloning // //--------------------------------------------------------------------------- /** * Clones a KeyValue. This creates a copy, re-allocating the buffer. * @return Fully copied clone of this KeyValue */ public KeyValue clone() { byte [] b = new byte[this.length]; System.arraycopy(this.bytes, this.offset, b, 0, this.length); KeyValue ret = new KeyValue(b, 0, b.length); // Important to clone the memstoreTS as well - otherwise memstore's // update-in-place methods (eg increment) will end up creating // new entries ret.setMemstoreTS(memstoreTS); return ret; } //--------------------------------------------------------------------------- // // 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(); } /** * @param k Key portion of a KeyValue. * @return Key as a String. */ public static String keyToString(final byte [] k) { return keyToString(k, 0, k.length); } /** * Use for logging. * @param b Key portion of a KeyValue. * @param o Offset to start of key * @param l Length of key. * @return Key as a String. */ 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)); byte type = b[o + l - 1]; // return row + "/" + family + // (family != null && family.length() > 0? COLUMN_FAMILY_DELIMITER: "") + // qualifier + "/" + timestamp + "/" + Type.codeToType(type); return row + "/" + family + (family != null && family.length() > 0? ":" :"") + qualifier + "/" + timestamp + "/" + Type.codeToType(type); } //--------------------------------------------------------------------------- // // 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 (2 * Bytes.SIZEOF_INT) + 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; } public String getKeyString() { return Bytes.toStringBinary(getBuffer(), getKeyOffset(), getKeyLength()); } /** * @return 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); } /** * @return Column (family + qualifier) length */ public int getTotalColumnLength() { int rlength = getRowLength(); int foffset = getFamilyOffset(rlength); return getTotalColumnLength(rlength,foffset); } /** * @return Column (family + qualifier) length */ public int getTotalColumnLength(int rlength, int foffset) { int flength = getFamilyLength(foffset); int qlength = getQualifierLength(rlength,flength); return flength + qlength; } /** * @return Timestamp offset */ public int getTimestampOffset() { return getTimestampOffset(getKeyLength()); } /** * @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; } /** * @return True if this KeyValue has a LATEST_TIMESTAMP timestamp. */ public boolean isLatestTimestamp() { return Bytes.compareTo(getBuffer(), getTimestampOffset(), Bytes.SIZEOF_LONG, HConstants.LATEST_TIMESTAMP_BYTES, 0, Bytes.SIZEOF_LONG) == 0; } /** * @param now Time to set into <code>this</code> IFF timestamp == * {@link HConstants#LATEST_TIMESTAMP} (else, its a noop). * @return True is we modified this. */ public boolean updateLatestStamp(final byte [] now) { if (this.isLatestTimestamp()) { int tsOffset = getTimestampOffset(); System.arraycopy(now, 0, this.bytes, tsOffset, Bytes.SIZEOF_LONG); return true; } return false; } //--------------------------------------------------------------------------- // // 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(); rowCache = new byte[l]; System.arraycopy(getBuffer(), o, rowCache, 0, l); } return rowCache; } /** * * @return Timestamp */ private long timestampCache = -1; 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]; } /** * @return True if a delete type, a {@link KeyValue.Type#Delete} or * a {KeyValue.Type#DeleteFamily} or a {@link KeyValue.Type#DeleteColumn} * KeyValue type. */ public boolean isDelete() { int t = getType(); return Type.Delete.getCode() <= t && t <= Type.DeleteFamily.getCode(); } /** * @return True if this KV is a {@link KeyValue.Type#Delete} type. */ public boolean isDeleteType() { return getType() == Type.Delete.getCode(); } /** * @return True if this KV is a delete family type. */ public boolean isDeleteFamily() { return getType() == Type.DeleteFamily.getCode(); } /** * * @return True if this KV is a delete family or column type. */ public boolean isDeleteColumnOrFamily() { int t = getType(); return t == Type.DeleteColumn.getCode() || t == Type.DeleteFamily.getCode(); } /** * 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 [] getFamily() { return this.split[1]; } public byte [] getQualifier() { return this.split[2]; } 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 // //--------------------------------------------------------------------------- /** * @param family * @return True if matching families. */ public boolean matchingFamily(final byte [] family) { return matchingFamily(family, 0, family.length); } public boolean matchingFamily(final byte[] family, int offset, int length) { if (this.length == 0 || this.bytes.length == 0) { return false; } return Bytes.compareTo(family, offset, length, this.bytes, getFamilyOffset(), getFamilyLength()) == 0; } public boolean matchingFamily(final KeyValue other) { return matchingFamily(other.getBuffer(), other.getFamilyOffset(), other.getFamilyLength()); } /** * @param qualifier * @return True if matching qualifiers. */ public boolean matchingQualifier(final byte [] qualifier) { return matchingQualifier(qualifier, 0, qualifier.length); } public boolean matchingQualifier(final byte [] qualifier, int offset, int length) { return Bytes.compareTo(qualifier, offset, length, this.bytes, getQualifierOffset(), getQualifierLength()) == 0; } public boolean matchingQualifier(final KeyValue other) { return matchingQualifier(other.getBuffer(), other.getQualifierOffset(), other.getQualifierLength()); } public boolean matchingRow(final byte [] row) { return matchingRow(row, 0, row.length); } public boolean matchingRow(final byte[] row, int offset, int length) { return Bytes.compareTo(row, offset, length, this.bytes, getRowOffset(), getRowLength()) == 0; } public boolean matchingRow(KeyValue other) { return matchingRow(other.getBuffer(), other.getRowOffset(), other.getRowLength()); } /** * @param column Column minus its delimiter * @return True if column matches. */ public boolean matchingColumnNoDelimiter(final byte [] column) { int rl = getRowLength(); int o = getFamilyOffset(rl); int fl = getFamilyLength(o); int l = fl + getQualifierLength(rl,fl); return Bytes.compareTo(column, 0, column.length, this.bytes, o, l) == 0; } /** * * @param family column family * @param qualifier column qualifier * @return True if column matches */ public boolean matchingColumn(final byte[] family, final byte[] qualifier) { int rl = getRowLength(); int o = getFamilyOffset(rl); int fl = getFamilyLength(o); int ql = getQualifierLength(rl,fl); if (Bytes.compareTo(family, 0, family.length, this.bytes, o, family.length) != 0) { return false; } if (qualifier == null || qualifier.length == 0) { if (ql == 0) { return true; } return false; } return Bytes.compareTo(qualifier, 0, qualifier.length, this.bytes, o + fl, ql) == 0; } /** * @param left * @param loffset * @param llength * @param lfamilylength Offset of family delimiter in left column. * @param right * @param roffset * @param rlength * @param rfamilylength Offset of family delimiter in right column. * @return The result of the comparison. */ static int compareColumns(final byte [] left, final int loffset, final int llength, final int lfamilylength, final byte [] right, final int roffset, final int rlength, final int rfamilylength) { // Compare family portion first. int diff = Bytes.compareTo(left, loffset, lfamilylength, right, roffset, rfamilylength); if (diff != 0) { return diff; } // Compare qualifier portion return Bytes.compareTo(left, loffset + lfamilylength, llength - lfamilylength, right, roffset + rfamilylength, rlength - rfamilylength); } /** * @return True if non-null row and column. */ public boolean nonNullRowAndColumn() { return getRowLength() > 0 && !isEmptyColumn(); } /** * @return True if column is empty. */ public boolean isEmptyColumn() { return getQualifierLength() == 0; } /** * Converts this KeyValue to only contain the key portion (the value is * changed to be null). This method does a full copy of the backing byte * array and does not modify the original byte array of this KeyValue. * <p> * This method is used by <code>KeyOnlyFilter</code> and is an advanced feature of * KeyValue, proceed with caution. * @param lenAsVal replace value with the actual value length (false=empty) */ public void convertToKeyOnly(boolean lenAsVal) { // KV format: <keylen:4><valuelen:4><key:keylen><value:valuelen> // Rebuild as: <keylen:4><0:4><key:keylen> int dataLen = lenAsVal? Bytes.SIZEOF_INT : 0; byte [] newBuffer = new byte[getKeyLength() + (2 * Bytes.SIZEOF_INT) + dataLen]; System.arraycopy(this.bytes, this.offset, newBuffer, 0, Math.min(newBuffer.length,this.length)); Bytes.putInt(newBuffer, Bytes.SIZEOF_INT, dataLen); if (lenAsVal) { Bytes.putInt(newBuffer, newBuffer.length - dataLen, this.getValueLength()); } this.bytes = newBuffer; this.offset = 0; this.length = newBuffer.length; } /** * Splits a column in family:qualifier form into separate byte arrays. * <p> * Not recommend to be used as this is old-style API. * @param c The column. * @return The parsed column. */ public static byte [][] parseColumn(byte [] c) { final int index = getDelimiter(c, 0, c.length, COLUMN_FAMILY_DELIMITER); if (index == -1) { // If no delimiter, return array of size 1 return new byte [][] { c }; } else if(index == c.length - 1) { // Only a family, return array size 1 byte [] family = new byte[c.length-1]; System.arraycopy(c, 0, family, 0, family.length); return new byte [][] { family }; } // Family and column, return array size 2 final byte [][] result = new byte [2][]; result[0] = new byte [index]; System.arraycopy(c, 0, result[0], 0, index); final int len = c.length - (index + 1); result[1] = new byte[len]; System.arraycopy(c, index + 1 /*Skip delimiter*/, result[1], 0, len); return result; } /** * Makes a column in family:qualifier form from separate byte arrays. * <p> * Not recommended for usage as this is old-style API. * @param family * @param qualifier * @return family:qualifier */ public static byte [] makeColumn(byte [] family, byte [] qualifier) { return Bytes.add(family, COLUMN_FAMILY_DELIM_ARRAY, qualifier); } /** * @param b * @return Index of the family-qualifier colon delimiter character in passed * buffer. */ public static int getFamilyDelimiterIndex(final byte [] b, final int offset, final int length) { return getRequiredDelimiter(b, offset, length, COLUMN_FAMILY_DELIMITER); } private static int getRequiredDelimiter(final byte [] b, final int offset, final int length, final int delimiter) { int index = getDelimiter(b, offset, length, delimiter); if (index < 0) { throw new IllegalArgumentException("No " + (char)delimiter + " in <" + Bytes.toString(b) + ">" + ", length=" + length + ", offset=" + offset); } return index; } static int getRequiredDelimiterInReverse(final byte [] b, final int offset, final int length, final int delimiter) { int index = getDelimiterInReverse(b, offset, length, delimiter); if (index < 0) { throw new IllegalArgumentException("No " + delimiter + " in <" + Bytes.toString(b) + ">" + ", length=" + length + ", offset=" + offset); } return index; } /** * @param b * @param delimiter * @return Index of delimiter having started from start of <code>b</code> * moving rightward. */ public static int getDelimiter(final byte [] b, int offset, final int length, final int delimiter) { if (b == null) { throw new NullPointerException(); } int result = -1; for (int i = offset; i < length + offset; i++) { if (b[i] == delimiter) { result = i; break; } } return result; } /** * Find index of passed delimiter walking from end of buffer backwards. * @param b * @param delimiter * @return Index of delimiter */ public static int getDelimiterInReverse(final byte [] b, final int offset, final int length, final int delimiter) { if (b == null) { throw new NullPointerException(); } int result = -1; for (int i = (offset + length) - 1; i >= offset; i--) { if (b[i] == delimiter) { result = i; break; } } return result; } /** * A {@link KVComparator} for <code>-ROOT-</code> catalog table * {@link KeyValue}s. */ public static class RootComparator extends MetaComparator { private final KeyComparator rawcomparator = new RootKeyComparator(); public KeyComparator getRawComparator() { return this.rawcomparator; } @Override protected Object clone() throws CloneNotSupportedException { return new RootComparator(); } } /** * A {@link KVComparator} for <code>.META.</code> catalog table * {@link KeyValue}s. */ public static class MetaComparator extends KVComparator { private final KeyComparator rawcomparator = new MetaKeyComparator(); public KeyComparator getRawComparator() { return this.rawcomparator; } @Override protected Object clone() throws CloneNotSupportedException { return new MetaComparator(); } } /** * Compare KeyValues. When we compare KeyValues, we only compare the Key * portion. This means two KeyValues with same Key but different Values are * considered the same as far as this Comparator is concerned. * Hosts a {@link KeyComparator}. */ public static class KVComparator implements java.util.Comparator<KeyValue> { private final KeyComparator rawcomparator = new KeyComparator(); /** * @return RawComparator that can compare the Key portion of a KeyValue. * Used in hfile where indices are the Key portion of a KeyValue. */ public KeyComparator getRawComparator() { return this.rawcomparator; } public int compare(final KeyValue left, final KeyValue right) { int ret = getRawComparator().compare(left.getBuffer(), left.getOffset() + ROW_OFFSET, left.getKeyLength(), right.getBuffer(), right.getOffset() + ROW_OFFSET, right.getKeyLength()); if (ret != 0) return ret; // Negate this comparison so later edits show up first return -Longs.compare(left.getMemstoreTS(), right.getMemstoreTS()); } public int compareTimestamps(final KeyValue left, final KeyValue right) { return compareTimestamps(left, left.getKeyLength(), right, right.getKeyLength()); } int compareTimestamps(final KeyValue left, final int lkeylength, final KeyValue right, final int rkeylength) { // Compare timestamps long ltimestamp = left.getTimestamp(lkeylength); long rtimestamp = right.getTimestamp(rkeylength); return getRawComparator().compareTimestamps(ltimestamp, rtimestamp); } /** * @param left * @param right * @return Result comparing rows. */ public int compareRows(final KeyValue left, final KeyValue right) { return compareRows(left, left.getRowLength(), right, right.getRowLength()); } /** * @param left * @param lrowlength Length of left row. * @param right * @param rrowlength Length of right row. * @return Result comparing rows. */ public int compareRows(final KeyValue left, final short lrowlength, final KeyValue right, final short rrowlength) { return getRawComparator().compareRows(left.getBuffer(), left.getRowOffset(), lrowlength, right.getBuffer(), right.getRowOffset(), rrowlength); } /** * @param left * @param row - row key (arbitrary byte array) * @return RawComparator */ public int compareRows(final KeyValue left, final byte [] row) { return getRawComparator().compareRows(left.getBuffer(), left.getRowOffset(), left.getRowLength(), row, 0, row.length); } public int compareRows(byte [] left, int loffset, int llength, byte [] right, int roffset, int rlength) { return getRawComparator().compareRows(left, loffset, llength, right, roffset, rlength); } public int compareColumns(final KeyValue left, final byte [] right, final int roffset, final int rlength, final int rfamilyoffset) { int offset = left.getFamilyOffset(); int length = left.getFamilyLength() + left.getQualifierLength(); return getRawComparator().compareColumns(left.getBuffer(), offset, length, left.getFamilyLength(offset), right, roffset, rlength, rfamilyoffset); } int compareColumns(final KeyValue left, final short lrowlength, final KeyValue right, final short rrowlength) { int lfoffset = left.getFamilyOffset(lrowlength); int rfoffset = right.getFamilyOffset(rrowlength); int lclength = left.getTotalColumnLength(lrowlength,lfoffset); int rclength = right.getTotalColumnLength(rrowlength, rfoffset); int lfamilylength = left.getFamilyLength(lfoffset); int rfamilylength = right.getFamilyLength(rfoffset); return getRawComparator().compareColumns(left.getBuffer(), lfoffset, lclength, lfamilylength, right.getBuffer(), rfoffset, rclength, rfamilylength); } /** * Compares the row and column of two keyvalues for equality * @param left * @param right * @return True if same row and column. */ public boolean matchingRowColumn(final KeyValue left, final KeyValue right) { short lrowlength = left.getRowLength(); short rrowlength = right.getRowLength(); // TsOffset = end of column data. just comparing Row+CF length of each return left.getTimestampOffset() == right.getTimestampOffset() && matchingRows(left, lrowlength, right, rrowlength) && compareColumns(left, lrowlength, right, rrowlength) == 0; } /** * @param left * @param right * @return True if rows match. */ public boolean matchingRows(final KeyValue left, final byte [] right) { return compareRows(left, right) == 0; } /** * Compares the row of two keyvalues for equality * @param left * @param right * @return True if rows match. */ public boolean matchingRows(final KeyValue left, final KeyValue right) { short lrowlength = left.getRowLength(); short rrowlength = right.getRowLength(); return matchingRows(left, lrowlength, right, rrowlength); } /** * @param left * @param lrowlength * @param right * @param rrowlength * @return True if rows match. */ public boolean matchingRows(final KeyValue left, final short lrowlength, final KeyValue right, final short rrowlength) { return lrowlength == rrowlength && compareRows(left, lrowlength, right, rrowlength) == 0; } public boolean matchingRows(final byte [] left, final int loffset, final int llength, final byte [] right, final int roffset, final int rlength) { int compare = compareRows(left, loffset, llength, right, roffset, rlength); if (compare != 0) { return false; } return true; } /** * Compares the row and timestamp of two keys * Was called matchesWithoutColumn in HStoreKey. * @param right Key to compare against. * @return True if same row and timestamp is greater than the timestamp in * <code>right</code> */ public boolean matchingRowsGreaterTimestamp(final KeyValue left, final KeyValue right) { short lrowlength = left.getRowLength(); short rrowlength = right.getRowLength(); if (!matchingRows(left, lrowlength, right, rrowlength)) { return false; } return left.getTimestamp() >= right.getTimestamp(); } @Override protected Object clone() throws CloneNotSupportedException { return new KVComparator(); } /** * @return Comparator that ignores timestamps; useful counting versions. */ public KVComparator getComparatorIgnoringTimestamps() { KVComparator c = null; try { c = (KVComparator)this.clone(); c.getRawComparator().ignoreTimestamp = true; } catch (CloneNotSupportedException e) { LOG.error("Not supported", e); } return c; } /** * @return Comparator that ignores key type; useful checking deletes */ public KVComparator getComparatorIgnoringType() { KVComparator c = null; try { c = (KVComparator)this.clone(); c.getRawComparator().ignoreType = true; } catch (CloneNotSupportedException e) { LOG.error("Not supported", e); } return c; } } /** * Creates a KeyValue that is last on the specified row id. That is, * every other possible KeyValue for the given row would compareTo() * less than the result of this call. * @param row row key * @return Last possible KeyValue on passed <code>row</code> */ public static KeyValue createLastOnRow(final byte[] row) { return new KeyValue(row, null, null, HConstants.LATEST_TIMESTAMP, Type.Minimum); } /** * Create a KeyValue that is smaller than all other possible KeyValues * for the given row. That is any (valid) KeyValue on 'row' would sort * _after_ the result. * * @param row - row key (arbitrary byte array) * @return First possible KeyValue on passed <code>row</code> */ public static KeyValue createFirstOnRow(final byte [] row) { return createFirstOnRow(row, HConstants.LATEST_TIMESTAMP); } /** * Creates a KeyValue that is smaller than all other KeyValues that * are older than the passed timestamp. * @param row - row key (arbitrary byte array) * @param ts - timestamp * @return First possible key on passed <code>row</code> and timestamp. */ public static KeyValue createFirstOnRow(final byte [] row, final long ts) { return new KeyValue(row, null, null, ts, Type.Maximum); } /** * @param row - row key (arbitrary byte array) * @param c column - {@link #parseColumn(byte[])} is called to split * the column. * @param ts - timestamp * @return First possible key on passed <code>row</code>, column and timestamp * @deprecated */ public static KeyValue createFirstOnRow(final byte [] row, final byte [] c, final long ts) { byte [][] split = parseColumn(c); return new KeyValue(row, split[0], split[1], ts, Type.Maximum); } /** * Create a KeyValue for the specified row, family and qualifier that would be * smaller than all other possible KeyValues that have the same row,family,qualifier. * Used for seeking. * @param row - row key (arbitrary byte array) * @param family - family name * @param qualifier - column qualifier * @return First possible key on passed <code>row</code>, and column. */ public static KeyValue createFirstOnRow(final byte [] row, final byte [] family, final byte [] qualifier) { return new KeyValue(row, family, qualifier, HConstants.LATEST_TIMESTAMP, Type.Maximum); } /** * @param row - row key (arbitrary byte array) * @param f - family name * @param q - column qualifier * @param ts - timestamp * @return First possible key on passed <code>row</code>, column and timestamp */ public static KeyValue createFirstOnRow(final byte [] row, final byte [] f, final byte [] q, final long ts) { return new KeyValue(row, f, q, ts, Type.Maximum); } /** * Create a KeyValue for the specified row, family and qualifier that would be * smaller than all other possible KeyValues that have the same row, * family, qualifier. * Used for seeking. * @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 * @return First possible key on passed Row, Family, Qualifier. */ public static KeyValue createFirstOnRow(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) { return new KeyValue(row, roffset, rlength, family, foffset, flength, qualifier, qoffset, qlength, HConstants.LATEST_TIMESTAMP, Type.Maximum, null, 0, 0); } /** * Create a KeyValue for the specified row, family and qualifier that would be * larger than or equal to all other possible KeyValues that have the same * row, family, qualifier. * Used for reseeking. * @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 * @return Last possible key on passed row, family, qualifier. */ public static KeyValue createLastOnRow(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) { return new KeyValue(row, roffset, rlength, family, foffset, flength, qualifier, qoffset, qlength, HConstants.OLDEST_TIMESTAMP, Type.Minimum, null, 0, 0); } /** * @param b * @return A KeyValue made of a byte array that holds the key-only part. * Needed to convert hfile index members to KeyValues. */ public static KeyValue createKeyValueFromKey(final byte [] b) { return createKeyValueFromKey(b, 0, b.length); } /** * @param bb * @return A KeyValue made of a byte buffer that holds the key-only part. * Needed to convert hfile index members to KeyValues. */ public static KeyValue createKeyValueFromKey(final ByteBuffer bb) { return createKeyValueFromKey(bb.array(), bb.arrayOffset(), bb.limit()); } /** * @param b * @param o * @param l * @return A KeyValue made of a byte array that holds the key-only part. * Needed to convert hfile index members to KeyValues. */ public static KeyValue createKeyValueFromKey(final byte [] b, final int o, final int l) { byte [] newb = new byte[b.length + ROW_OFFSET]; System.arraycopy(b, o, newb, ROW_OFFSET, l); Bytes.putInt(newb, 0, b.length); Bytes.putInt(newb, Bytes.SIZEOF_INT, 0); return new KeyValue(newb); } /** * Compare key portion of a {@link KeyValue} for keys in <code>-ROOT-<code> * table. */ public static class RootKeyComparator extends MetaKeyComparator { public int compareRows(byte [] left, int loffset, int llength, byte [] right, int roffset, int rlength) { // Rows look like this: .META.,ROW_FROM_META,RID // LOG.info("ROOT " + Bytes.toString(left, loffset, llength) + // "---" + Bytes.toString(right, roffset, rlength)); final int metalength = 7; // '.META.' length int lmetaOffsetPlusDelimiter = loffset + metalength; int leftFarDelimiter = getDelimiterInReverse(left, lmetaOffsetPlusDelimiter, llength - metalength, HRegionInfo.DELIMITER); int rmetaOffsetPlusDelimiter = roffset + metalength; int rightFarDelimiter = getDelimiterInReverse(right, rmetaOffsetPlusDelimiter, rlength - metalength, HRegionInfo.DELIMITER); if (leftFarDelimiter < 0 && rightFarDelimiter >= 0) { // Nothing between .META. and regionid. Its first key. return -1; } else if (rightFarDelimiter < 0 && leftFarDelimiter >= 0) { return 1; } else if (leftFarDelimiter < 0 && rightFarDelimiter < 0) { return 0; } int result = super.compareRows(left, lmetaOffsetPlusDelimiter, leftFarDelimiter - lmetaOffsetPlusDelimiter, right, rmetaOffsetPlusDelimiter, rightFarDelimiter - rmetaOffsetPlusDelimiter); if (result != 0) { return result; } // Compare last part of row, the rowid. leftFarDelimiter++; rightFarDelimiter++; result = compareRowid(left, leftFarDelimiter, llength - (leftFarDelimiter - loffset), right, rightFarDelimiter, rlength - (rightFarDelimiter - roffset)); return result; } } /** * Comparator that compares row component only of a KeyValue. */ public static class RowComparator implements Comparator<KeyValue> { final KVComparator comparator; public RowComparator(final KVComparator c) { this.comparator = c; } public int compare(KeyValue left, KeyValue right) { return comparator.compareRows(left, right); } } /** * Compare key portion of a {@link KeyValue} for keys in <code>.META.</code> * table. */ public static class MetaKeyComparator extends KeyComparator { public int compareRows(byte [] left, int loffset, int llength, byte [] right, int roffset, int rlength) { // LOG.info("META " + Bytes.toString(left, loffset, llength) + // "---" + Bytes.toString(right, roffset, rlength)); int leftDelimiter = getDelimiter(left, loffset, llength, HRegionInfo.DELIMITER); int rightDelimiter = getDelimiter(right, roffset, rlength, HRegionInfo.DELIMITER); if (leftDelimiter < 0 && rightDelimiter >= 0) { // Nothing between .META. and regionid. Its first key. return -1; } else if (rightDelimiter < 0 && leftDelimiter >= 0) { return 1; } else if (leftDelimiter < 0 && rightDelimiter < 0) { return 0; } // Compare up to the delimiter int result = Bytes.compareTo(left, loffset, leftDelimiter - loffset, right, roffset, rightDelimiter - roffset); if (result != 0) { return result; } // Compare middle bit of the row. // Move past delimiter leftDelimiter++; rightDelimiter++; int leftFarDelimiter = getRequiredDelimiterInReverse(left, leftDelimiter, llength - (leftDelimiter - loffset), HRegionInfo.DELIMITER); int rightFarDelimiter = getRequiredDelimiterInReverse(right, rightDelimiter, rlength - (rightDelimiter - roffset), HRegionInfo.DELIMITER); // Now compare middlesection of row. result = super.compareRows(left, leftDelimiter, leftFarDelimiter - leftDelimiter, right, rightDelimiter, rightFarDelimiter - rightDelimiter); if (result != 0) { return result; } // Compare last part of row, the rowid. leftFarDelimiter++; rightFarDelimiter++; result = compareRowid(left, leftFarDelimiter, llength - (leftFarDelimiter - loffset), right, rightFarDelimiter, rlength - (rightFarDelimiter - roffset)); return result; } protected int compareRowid(byte[] left, int loffset, int llength, byte[] right, int roffset, int rlength) { return Bytes.compareTo(left, loffset, llength, right, roffset, rlength); } } /** * Compare key portion of a {@link KeyValue}. */ public static class KeyComparator implements RawComparator<byte []> { 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 column family. Start compare past row and family length. int lcolumnoffset = Bytes.SIZEOF_SHORT + lrowlength + 1 + loffset; int rcolumnoffset = Bytes.SIZEOF_SHORT + rrowlength + 1 + roffset; int lcolumnlength = llength - TIMESTAMP_TYPE_SIZE - (lcolumnoffset - loffset); int rcolumnlength = rlength - TIMESTAMP_TYPE_SIZE - (rcolumnoffset - roffset); // if row matches, and no column in the 'left' AND put type is 'minimum', // then return that left is larger than right. // This supports 'last key on a row' - the magic is if there is no column in the // left operand, and the left operand has a type of '0' - magical value, // then we say the left is bigger. This will let us seek to the last key in // a row. byte ltype = left[loffset + (llength - 1)]; byte rtype = right[roffset + (rlength - 1)]; if (lcolumnlength == 0 && ltype == Type.Minimum.getCode()) { return 1; // left is bigger. } if (rcolumnlength == 0 && rtype == Type.Minimum.getCode()) { return -1; } // TODO the family and qualifier should be compared separately compare = Bytes.compareTo(left, lcolumnoffset, lcolumnlength, right, rcolumnoffset, rcolumnlength); if (compare != 0) { return 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 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); } protected int compareColumns( byte [] left, int loffset, int llength, final int lfamilylength, byte [] right, int roffset, int rlength, final int rfamilylength) { return KeyValue.compareColumns(left, loffset, llength, lfamilylength, right, roffset, rlength, rfamilylength); } 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; } } // HeapSize public long heapSize() { return ClassSize.align(ClassSize.OBJECT + (2 * ClassSize.REFERENCE) + ClassSize.align(ClassSize.ARRAY) + ClassSize.align(length) + (3 * Bytes.SIZEOF_INT) + ClassSize.align(ClassSize.ARRAY) + (2 * Bytes.SIZEOF_LONG)); } // this overload assumes that the length bytes have already been read, // and it expects the length of the KeyValue to be explicitly passed // to it. public void readFields(int length, final DataInput in) throws IOException { this.length = length; this.offset = 0; this.bytes = new byte[this.length]; in.readFully(this.bytes, 0, this.length); } // Writable public void readFields(final DataInput in) throws IOException { int length = in.readInt(); readFields(length, in); } public void write(final DataOutput out) throws IOException { out.writeInt(this.length); out.write(this.bytes, this.offset, this.length); } }