Index: src/test/java/org/apache/hadoop/hbase/TestKeyValue.java =================================================================== --- src/test/java/org/apache/hadoop/hbase/TestKeyValue.java (revision 1200717) +++ src/test/java/org/apache/hadoop/hbase/TestKeyValue.java (working copy) @@ -353,7 +353,7 @@ assertKVLess(c, firstOnRowA, lastOnRowA); } - public void testConvertToKeyOnly() throws Exception { + public void testCreateKeyOnly() throws Exception { long ts = 1; byte [] value = Bytes.toBytes("a real value"); byte [] evalue = new byte[0]; // empty value @@ -361,9 +361,7 @@ for (byte[] val : new byte[][]{value, evalue}) { for (boolean useLen : new boolean[]{false,true}) { KeyValue kv1 = new KeyValue(rowA, family, qualA, ts, val); - KeyValue kv1ko = kv1.clone(); - assertTrue(kv1.equals(kv1ko)); - kv1ko.convertToKeyOnly(useLen); + KeyValue kv1ko = kv1.createKeyOnly(useLen); // keys are still the same assertTrue(kv1.equals(kv1ko)); // but values are not Index: src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (working copy) @@ -364,6 +364,14 @@ return this.startKey; } + /** + * + * @return the Filter + */ + Filter getFilter() { + return this.filter; + } + public KeyValue getNextKeyHint(KeyValue kv) { if (filter == null) { return null; Index: src/main/java/org/apache/hadoop/hbase/regionserver/StoreScanner.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/regionserver/StoreScanner.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/regionserver/StoreScanner.java (working copy) @@ -26,6 +26,7 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.Filter; import java.io.IOException; import java.util.ArrayList; @@ -288,22 +289,21 @@ store != null ? store.getComparator() : null; LOOP: while((kv = this.heap.peek()) != null) { - // kv is no longer immutable due to KeyOnlyFilter! use copy for safety - KeyValue copyKv = kv.shallowCopy(); // Check that the heap gives us KVs in an increasing order. if (prevKV != null && comparator != null && comparator.compare(prevKV, kv) > 0) { throw new IOException("Key " + prevKV + " followed by a " + "smaller key " + kv + " in cf " + store); } - prevKV = copyKv; - ScanQueryMatcher.MatchCode qcode = matcher.match(copyKv); + prevKV = kv; + ScanQueryMatcher.MatchCode qcode = matcher.match(kv); switch(qcode) { case INCLUDE: case INCLUDE_AND_SEEK_NEXT_ROW: case INCLUDE_AND_SEEK_NEXT_COL: - results.add(copyKv); + Filter f = matcher.getFilter(); + results.add(f == null ? kv : f.transform(kv)); if (qcode == ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW) { if (!matcher.moreRowsMayExistAfter(kv)) { Index: src/main/java/org/apache/hadoop/hbase/filter/KeyOnlyFilter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/KeyOnlyFilter.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/KeyOnlyFilter.java (working copy) @@ -43,9 +43,8 @@ public KeyOnlyFilter(boolean lenAsVal) { this.lenAsVal = lenAsVal; } @Override - public ReturnCode filterKeyValue(KeyValue kv) { - kv.convertToKeyOnly(this.lenAsVal); - return ReturnCode.INCLUDE; + public KeyValue transform(KeyValue kv) { + return kv.createKeyOnly(this.lenAsVal); } public static Filter createFilterFromArguments(ArrayList filterArguments) { Index: src/main/java/org/apache/hadoop/hbase/filter/SkipFilter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/SkipFilter.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/SkipFilter.java (working copy) @@ -76,6 +76,11 @@ return c; } + @Override + public KeyValue transform(KeyValue v) { + return filter.transform(v); + } + public boolean filterRow() { return filterRow; } Index: src/main/java/org/apache/hadoop/hbase/filter/FilterBase.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/FilterBase.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/FilterBase.java (working copy) @@ -78,6 +78,16 @@ } /** + * By default no transformation takes place + * + * @inheritDoc + */ + @Override + public KeyValue transform(KeyValue v) { + return v; + } + + /** * Filters that never filter by modifying the returned List of KeyValues can * inherit this implementation that does nothing. * @@ -128,5 +138,5 @@ */ public static Filter createFilterFromArguments(ArrayList filterArguments) { throw new IllegalArgumentException("This method has not been implemented"); + } } -} Index: src/main/java/org/apache/hadoop/hbase/filter/FilterList.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/FilterList.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/FilterList.java (working copy) @@ -181,6 +181,15 @@ } @Override + public KeyValue transform(KeyValue v) { + KeyValue current = v; + for (Filter filter : filters) { + current = filter.transform(current); + } + return current; + } + + @Override public ReturnCode filterKeyValue(KeyValue v) { ReturnCode rc = operator == Operator.MUST_PASS_ONE? ReturnCode.SKIP: ReturnCode.INCLUDE; Index: src/main/java/org/apache/hadoop/hbase/filter/Filter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/Filter.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/Filter.java (working copy) @@ -87,9 +87,24 @@ * @return code as described below * @see Filter.ReturnCode */ - public ReturnCode filterKeyValue(KeyValue v); + public ReturnCode filterKeyValue(final KeyValue v); /** + * Give the filter a chance to transform the passed KeyValue. + * If the KeyValue is changed a new KeyValue object must be returned. + * @see org.apache.hadoop.hbase.KeyValue#shallowCopy() + * + * The transformed KeyValue is what is eventually returned to the + * client. Most filters will return the passed KeyValue unchanged. + * @see org.apache.hadoop.hbase.filter.KeyOnlyFilter#transform(KeyValue) + * for an example of a transformation. + * + * @param v the KeyValue in question + * @return the changed KeyValue + */ + public KeyValue transform(final KeyValue v); + + /** * Return codes for filterValue(). */ public enum ReturnCode { @@ -147,5 +162,5 @@ * @return KeyValue which must be next seeked. return null if the filter is * not sure which key to seek to next. */ - public KeyValue getNextKeyHint(KeyValue currentKV); + public KeyValue getNextKeyHint(final KeyValue currentKV); } Index: src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java (working copy) @@ -75,6 +75,11 @@ return c; } + @Override + public KeyValue transform(KeyValue v) { + return filter.transform(v); + } + public boolean filterRow() { boolean filterRow = this.filter.filterRow(); changeFAR(filterRow); Index: src/main/java/org/apache/hadoop/hbase/KeyValue.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/KeyValue.java (revision 1200717) +++ src/main/java/org/apache/hadoop/hbase/KeyValue.java (working copy) @@ -1313,15 +1313,11 @@ } /** - * 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. - *

- * This method is used by KeyOnlyFilter and is an advanced feature of - * KeyValue, proceed with caution. + * Creates a new KeyValue that only contains the key portion (the value is + * set to be null). * @param lenAsVal replace value with the actual value length (false=empty) */ - public void convertToKeyOnly(boolean lenAsVal) { + public KeyValue createKeyOnly(boolean lenAsVal) { // KV format: // Rebuild as: <0:4> int dataLen = lenAsVal? Bytes.SIZEOF_INT : 0; @@ -1332,9 +1328,7 @@ if (lenAsVal) { Bytes.putInt(newBuffer, newBuffer.length - dataLen, this.getValueLength()); } - this.bytes = newBuffer; - this.offset = 0; - this.length = newBuffer.length; + return new KeyValue(newBuffer); } /**