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.shallowCopy().convertToKeyOnly(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) @@ -1321,7 +1321,7 @@ * KeyValue, proceed with caution. * @param lenAsVal replace value with the actual value length (false=empty) */ - public void convertToKeyOnly(boolean lenAsVal) { + public KeyValue convertToKeyOnly(boolean lenAsVal) { // KV format: // Rebuild as: <0:4> int dataLen = lenAsVal? Bytes.SIZEOF_INT : 0; @@ -1335,6 +1335,7 @@ this.bytes = newBuffer; this.offset = 0; this.length = newBuffer.length; + return this; } /**