diff --git src/main/java/org/apache/hadoop/hbase/KeyValue.java src/main/java/org/apache/hadoop/hbase/KeyValue.java index 243d76f..4e38e32 100644 --- src/main/java/org/apache/hadoop/hbase/KeyValue.java +++ src/main/java/org/apache/hadoop/hbase/KeyValue.java @@ -505,6 +505,121 @@ public class KeyValue implements Writable, HeapSize { } /** + * Constructs KeyValue structure filled with specified values. Uses the provided buffer as the data buffer. + *

+ * Column is split into two fields, family and qualifier. + * + * @param buffer the bytes buffer to use + * @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(byte [] buffer, + 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 = buffer; + this.length = writeByteArray(buffer, row, roffset, rlength, + family, foffset, flength, qualifier, qoffset, qlength, + timestamp, type, value, voffset, vlength); + this.offset = 0; + } + + /** + * Write KeyValue format into the provided byte array. + * + * @param buffer the bytes buffer to use + * @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 number of useful bytes in the buffer. + */ + static int writeByteArray(byte [] buffer, + 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); + } + + // Write key, value and key row length. + int pos = 0; + pos = Bytes.putInt(buffer, pos, keylength); + pos = Bytes.putInt(buffer, pos, vlength); + pos = Bytes.putShort(buffer, pos, (short)(rlength & 0x0000ffff)); + pos = Bytes.putBytes(buffer, pos, row, roffset, rlength); + pos = Bytes.putByte(buffer, pos, (byte)(flength & 0x0000ff)); + if(flength != 0) { + pos = Bytes.putBytes(buffer, pos, family, foffset, flength); + } + if(qlength != 0) { + pos = Bytes.putBytes(buffer, pos, qualifier, qoffset, qlength); + } + pos = Bytes.putLong(buffer, pos, timestamp); + pos = Bytes.putByte(buffer, pos, type.getCode()); + if (value != null && value.length > 0) { + pos = Bytes.putBytes(buffer, pos, value, voffset, vlength); + } + + return KEYVALUE_INFRASTRUCTURE_SIZE + keylength + vlength; + } + + /** * Write KeyValue format into a byte array. * * @param row row key @@ -1295,6 +1410,34 @@ public class KeyValue implements Writable, HeapSize { } /** + * Checks if column matches. + * + * @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 True if column matches + */ + public boolean matchingColumn(final byte [] family, final int foffset, final int flength, + final byte [] qualifier, final int qoffset, final int qlength) { + + int rl = getRowLength(); + int o = getFamilyOffset(rl); + int fl = getFamilyLength(o); + int ql = getQualifierLength(rl,fl); + + if (!Bytes.equals(family, foffset, flength, this.bytes, o, fl)) + return false; + + if (qualifier == null || qlength == 0) + return (ql == 0); + + return Bytes.equals(qualifier, qoffset, qlength, this.bytes, o + fl, ql); + } + + /** * @param left * @param loffset * @param llength diff --git src/main/java/org/apache/hadoop/hbase/client/Result.java src/main/java/org/apache/hadoop/hbase/client/Result.java index df0b3ef..c4e69de 100644 --- src/main/java/org/apache/hadoop/hbase/client/Result.java +++ src/main/java/org/apache/hadoop/hbase/client/Result.java @@ -23,6 +23,7 @@ package org.apache.hadoop.hbase.client; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -33,6 +34,7 @@ import java.util.TreeMap; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.KeyValue.SplitKeyValue; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; @@ -80,6 +82,9 @@ public class Result implements Writable, WritableWithSize { private transient byte [] row = null; private ImmutableBytesWritable bytes = null; + // never use directly + private static byte [] buffer = new byte[1024]; + /** * Constructor used for Writable. */ @@ -228,6 +233,74 @@ public class Result implements Writable, WritableWithSize { } /** + * Searches for the latest value for the specified column. + * + * @param kvs the array to search + * @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 the index where the value was found, or -1 otherwise + */ + protected int binarySearch(final KeyValue [] kvs, + final byte [] family, final int foffset, final int flength, + final byte [] qualifier, final int qoffset, final int qlength) { + + KeyValue searchTerm = getSearchTerm(kvs[0].getRow(), family, foffset, flength, qualifier, qoffset, qlength); + + // pos === ( -(insertion point) - 1) + int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR); + // never will exact match + if (pos < 0) { + pos = (pos+1) * -1; + // pos is now insertion point + } + if (pos == kvs.length) { + return -1; // doesn't exist + } + return pos; + } + + /** + * Generates a dummy KeyValue object used for searching. + * + * @param row the value key + * @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 a dummy KeyValue object + */ + protected static KeyValue getSearchTerm(final byte [] row, + final byte [] family, final int foffset, final int flength, + final byte [] qualifier, final int qoffset, final int qlength) { + + long lLength = KeyValue.KEY_INFRASTRUCTURE_SIZE + + row.length + flength + qlength + + KeyValue.KEYVALUE_INFRASTRUCTURE_SIZE; + + if (lLength > Integer.MAX_VALUE) + throw new IllegalArgumentException("KeyValue length " + lLength + " > " + Integer.MAX_VALUE); + + int iLength = (int) lLength; + if (buffer.length < iLength) + buffer = new byte[iLength]; + + return new KeyValue(buffer, + row, 0, row.length, + family, foffset, flength, + qualifier, qoffset, qlength, + HConstants.LATEST_TIMESTAMP, KeyValue.Type.Maximum, + null, 0, 0); + } + + /** * The KeyValue for the most recent for a given column. If the column does * not exist in the result set - if it wasn't selected in the query (Get/Scan) * or just does not exist in the row the return value is null. @@ -253,6 +326,38 @@ public class Result implements Writable, WritableWithSize { } /** + * The KeyValue for the most recent for a given column. If the column does + * not exist in the result set - if it wasn't selected in the query (Get/Scan) + * or just does not exist in the row the return value is null. + * + * @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 KeyValue for the column or null + */ + public KeyValue getColumnLatest(byte [] family, int foffset, int flength, + byte [] qualifier, int qoffset, int qlength) { + + KeyValue [] kvs = raw(); // side effect possibly. + if (kvs == null || kvs.length == 0) + return null; + + int pos = binarySearch(kvs, family, foffset, flength, qualifier, qoffset, qlength); + if (pos == -1) + return null; + + KeyValue kv = kvs[pos]; + if (kv.matchingColumn(family, foffset, flength, qualifier, qoffset, qlength)) + return kv; + + return null; + } + + /** * Get the latest version of the specified column. * @param family family name * @param qualifier column qualifier @@ -267,6 +372,100 @@ public class Result implements Writable, WritableWithSize { } /** + * Loads the latest version of the specified column into the provided ByteBuffer. + *

+ * Does not clear or flip the buffer. + * + * @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 dst the buffer where to write the value + * + * @return true if a value was found, false otherwise + */ + public boolean loadValue(byte [] family, int foffset, int flength, + byte [] qualifier, int qoffset, int qlength, + ByteBuffer dst) { + + KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength); + + if (kv == null) + return false; + + dst.put(kv.getBuffer(), kv.getValueOffset(), kv.getValueLength()); + return true; + } + + /** + * Checks if the specified column contains a non-empty value. + * + * @param family family name + * @param qualifier column qualifier + * + * @return whether or not a latest value exists and is not empty + */ + public boolean containsNonEmptyColumn(byte [] family, byte [] qualifier) { + + return containsNonEmptyColumn(family, 0, family.length, qualifier, 0, qualifier.length); + } + + /** + * Checks if the specified column contains a non-empty value. + * + * @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 whether or not a latest value exists and is not empty + */ + public boolean containsNonEmptyColumn(byte [] family, int foffset, int flength, + byte [] qualifier, int qoffset, int qlength) { + + KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength); + + return (kv != null) && (kv.getValueLength() > 0); + } + + /** + * Checks if the specified column contains an empty value. + * + * @param family family name + * @param qualifier column qualifier + * + * @return whether or not a latest value exists and is empty + */ + public boolean containsEmptyColumn(byte [] family, byte [] qualifier) { + + return containsEmptyColumn(family, 0, family.length, qualifier, 0, qualifier.length); + } + + /** + * Checks if the specified column contains an empty value. + * + * @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 whether or not a latest value exists and is empty + */ + public boolean containsEmptyColumn(byte [] family, int foffset, int flength, + byte [] qualifier, int qoffset, int qlength) { + + KeyValue kv = getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength); + + return (kv != null) && (kv.getValueLength() == 0); + } + + /** * Checks for existence of the specified column. * @param family family name * @param qualifier column qualifier @@ -278,6 +477,24 @@ public class Result implements Writable, WritableWithSize { } /** + * Checks for existence of the specified column. + * + * @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 true if at least one value exists in the result, false if not + */ + public boolean containsColumn(byte [] family, int foffset, int flength, + byte [] qualifier, int qoffset, int qlength) { + + return getColumnLatest(family, foffset, flength, qualifier, qoffset, qlength) != null; + } + + /** * Map of families to all versions of its qualifiers and values. *

* Returns a three level Map of the form: