Index: src/test/java/org/apache/hadoop/hbase/TestKeyValue.java =================================================================== --- src/test/java/org/apache/hadoop/hbase/TestKeyValue.java (revision 1200661) +++ 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 1200661) +++ src/main/java/org/apache/hadoop/hbase/regionserver/ScanQueryMatcher.java (working copy) @@ -364,6 +364,14 @@ return this.startKey; } + /** + * + * @return the Filter + */ + public 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 1200661) +++ 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 1200661) +++ 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 1200661) +++ 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 1200661) +++ src/main/java/org/apache/hadoop/hbase/filter/FilterBase.java (working copy) @@ -78,6 +78,15 @@ } /** + * By default no transformation takes place + * + * @inheritDoc + */ + 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. * Index: src/main/java/org/apache/hadoop/hbase/filter/FilterList.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/FilterList.java (revision 1200661) +++ 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 1200661) +++ src/main/java/org/apache/hadoop/hbase/filter/Filter.java (working copy) @@ -90,6 +90,16 @@ public ReturnCode filterKeyValue(KeyValue v); /** + * Give the filter a chance to transform the passed KeyValue. + * If the KeyValue is changed it must be a new KeyValue object. + * @see org.apache.hadoop.hbase.KeyValue#shallowCopy() + * + * @param v the KeyValue in question + * @return the changed KeyValue + */ + public KeyValue transform(KeyValue v); + + /** * Return codes for filterValue(). */ public enum ReturnCode { Index: src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java (revision 1200661) +++ 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 1200661) +++ 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); } /**