View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.filter;
19  
20  import com.google.protobuf.InvalidProtocolBufferException;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Comparator;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.PriorityQueue;
27  import org.apache.hadoop.hbase.Cell;
28  import org.apache.hadoop.hbase.KeyValueUtil;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.exceptions.DeserializationException;
32  import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
33  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.BytesBytesPair;
34  import org.apache.hadoop.hbase.util.ByteStringer;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.apache.hadoop.hbase.util.Pair;
37  import org.apache.hadoop.hbase.util.UnsafeAvailChecker;
38  
39  /**
40   * This is optimized version of a standard FuzzyRowFilter Filters data based on fuzzy row key.
41   * Performs fast-forwards during scanning. It takes pairs (row key, fuzzy info) to match row keys.
42   * Where fuzzy info is a byte array with 0 or 1 as its values:
43   * <ul>
44   * <li>0 - means that this byte in provided row key is fixed, i.e. row key's byte at same position
45   * must match</li>
46   * <li>1 - means that this byte in provided row key is NOT fixed, i.e. row key's byte at this
47   * position can be different from the one in provided row key</li>
48   * </ul>
49   * Example: Let's assume row key format is userId_actionId_year_month. Length of userId is fixed and
50   * is 4, length of actionId is 2 and year and month are 4 and 2 bytes long respectively. Let's
51   * assume that we need to fetch all users that performed certain action (encoded as "99") in Jan of
52   * any year. Then the pair (row key, fuzzy info) would be the following: row key = "????_99_????_01"
53   * (one can use any value instead of "?") fuzzy info =
54   * "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00" I.e. fuzzy info tells the matching
55   * mask is "????_99_????_01", where at ? can be any value.
56   */
57  @InterfaceAudience.Public
58  @InterfaceStability.Evolving
59  public class FuzzyRowFilter extends FilterBase {
60    private static final boolean UNSAFE_UNALIGNED = UnsafeAvailChecker.unaligned();
61  
62    // the wildcard byte is 1 on the user side. but the filter converts it internally
63    // in preprocessMask. This was changed in HBASE-15676 due to a bug with using 0.
64    // in v1, the 1 byte gets converted to 0
65    // in v2, the 1 byte gets converted to 2.
66    // we support both here to ensure backwards compatibility between client and server
67    static final byte V1_PROCESSED_WILDCARD_MASK = 0;
68    static final byte V2_PROCESSED_WILDCARD_MASK = 2;
69  
70    private final byte processedWildcardMask;
71    private List<Pair<byte[], byte[]>> fuzzyKeysData;
72    private boolean done = false;
73  
74    /**
75     * The index of a last successfully found matching fuzzy string (in fuzzyKeysData). We will start
76     * matching next KV with this one. If they do not match then we will return back to the one-by-one
77     * iteration over fuzzyKeysData.
78     */
79    private int lastFoundIndex = -1;
80  
81    /**
82     * Row tracker (keeps all next rows after SEEK_NEXT_USING_HINT was returned)
83     */
84    private RowTracker tracker;
85  
86    // this client side constructor ensures that all client-constructed
87    // FuzzyRowFilters use the new v2 mask.
88    public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) {
89      this(fuzzyKeysData, V2_PROCESSED_WILDCARD_MASK);
90    }
91  
92    // This constructor is only used internally here, when parsing from protos on the server side.
93    // It exists to enable seamless migration from v1 to v2.
94    // Additionally used in tests, but never used on client side.
95    FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData, byte processedWildcardMask) {
96      this.processedWildcardMask = processedWildcardMask;
97  
98      List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new ArrayList<>(fuzzyKeysData.size());
99  
100     for (Pair<byte[], byte[]> aFuzzyKeysData : fuzzyKeysData) {
101       if (aFuzzyKeysData.getFirst().length != aFuzzyKeysData.getSecond().length) {
102         Pair<String, String> readable =
103           new Pair<>(Bytes.toStringBinary(aFuzzyKeysData.getFirst()), Bytes.toStringBinary(aFuzzyKeysData.getSecond()));
104         throw new IllegalArgumentException("Fuzzy pair lengths do not match: " + readable);
105       }
106 
107       Pair<byte[], byte[]> p = new Pair<>();
108       // create a copy of pair bytes so that they are not modified by the filter.
109       p.setFirst(Arrays.copyOf(aFuzzyKeysData.getFirst(), aFuzzyKeysData.getFirst().length));
110       p.setSecond(Arrays.copyOf(aFuzzyKeysData.getSecond(), aFuzzyKeysData.getSecond().length));
111 
112       // update mask ( 0 -> -1 (0xff), 1 -> [0 or 2 depending on processedWildcardMask value])
113       p.setSecond(preprocessMask(p.getSecond()));
114       preprocessSearchKey(p);
115 
116       fuzzyKeyDataCopy.add(p);
117     }
118     this.fuzzyKeysData = fuzzyKeyDataCopy;
119     this.tracker = new RowTracker();
120   }
121 
122   private void preprocessSearchKey(Pair<byte[], byte[]> p) {
123     if (!UNSAFE_UNALIGNED) {
124       return;
125     }
126     byte[] key = p.getFirst();
127     byte[] mask = p.getSecond();
128     for (int i = 0; i < mask.length; i++) {
129       // set non-fixed part of a search key to 0.
130       if (mask[i] == processedWildcardMask) {
131         key[i] = 0;
132       }
133     }
134   }
135 
136   /**
137    * We need to preprocess mask array, as since we treat 2's as unfixed positions and -1 (0xff) as
138    * fixed positions
139    * @param mask
140    * @return mask array
141    */
142   private byte[] preprocessMask(byte[] mask) {
143     if (!UNSAFE_UNALIGNED) {
144       return mask;
145     }
146     if (isPreprocessedMask(mask)) return mask;
147     for (int i = 0; i < mask.length; i++) {
148       if (mask[i] == 0) {
149         mask[i] = -1; // 0 -> -1
150       } else if (mask[i] == 1) {
151         mask[i] = processedWildcardMask;// 1 -> 0 or 2 depending on mask version
152       }
153     }
154     return mask;
155   }
156 
157   private boolean isPreprocessedMask(byte[] mask) {
158     for (int i = 0; i < mask.length; i++) {
159       if (mask[i] != -1 && mask[i] != processedWildcardMask) {
160         return false;
161       }
162     }
163     return true;
164   }
165 
166   @Override
167   public ReturnCode filterKeyValue(Cell c) {
168     final int startIndex = lastFoundIndex >= 0 ? lastFoundIndex : 0;
169     final int size = fuzzyKeysData.size();
170     for (int i = startIndex; i < size + startIndex; i++) {
171       final int index = i % size;
172       Pair<byte[], byte[]> fuzzyData = fuzzyKeysData.get(index);
173       idempotentMaskShift(fuzzyData.getSecond());
174       SatisfiesCode satisfiesCode =
175           satisfies(isReversed(), c.getRowArray(), c.getRowOffset(), c.getRowLength(),
176             fuzzyData.getFirst(), fuzzyData.getSecond());
177       if (satisfiesCode == SatisfiesCode.YES) {
178         lastFoundIndex = index;
179         return ReturnCode.INCLUDE;
180       }
181     }
182     // NOT FOUND -> seek next using hint
183     lastFoundIndex = -1;
184 
185     return ReturnCode.SEEK_NEXT_USING_HINT;
186   }
187 
188   static void idempotentMaskShift(byte[] mask) {
189     // This shift is idempotent - always end up with 0 and -1 as mask values.
190     // This works regardless of mask version, because both 0 >> 2 and 2 >> 2
191     // result in 0.
192     for (int j = 0; j < mask.length; j++) {
193       mask[j] >>= 2;
194     }
195   }
196 
197   @Override
198   public Cell getNextCellHint(Cell currentCell) {
199     boolean result = tracker.updateTracker(currentCell);
200     if (result == false) {
201       done = true;
202       return null;
203     }
204     byte[] nextRowKey = tracker.nextRow();
205     return KeyValueUtil.createFirstOnRow(nextRowKey);
206   }
207 
208   /**
209    * If we have multiple fuzzy keys, row tracker should improve overall performance. It calculates
210    * all next rows (one per every fuzzy key) and put them (the fuzzy key is bundled) into a priority
211    * queue so that the smallest row key always appears at queue head, which helps to decide the
212    * "Next Cell Hint". As scanning going on, the number of candidate rows in the RowTracker will
213    * remain the size of fuzzy keys until some of the fuzzy keys won't possibly have matches any
214    * more.
215    */
216   private class RowTracker {
217     private final PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>> nextRows;
218     private boolean initialized = false;
219 
220     RowTracker() {
221       nextRows =
222           new PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>>(fuzzyKeysData.size(),
223               new Comparator<Pair<byte[], Pair<byte[], byte[]>>>() {
224                 @Override
225                 public int compare(Pair<byte[], Pair<byte[], byte[]>> o1,
226                     Pair<byte[], Pair<byte[], byte[]>> o2) {
227                   return isReversed()? Bytes.compareTo(o2.getFirst(), o1.getFirst()):
228                     Bytes.compareTo(o1.getFirst(), o2.getFirst());
229                 }
230               });
231     }
232 
233     byte[] nextRow() {
234       if (nextRows.isEmpty()) {
235         throw new IllegalStateException(
236             "NextRows should not be empty, make sure to call nextRow() after updateTracker() return true");
237       } else {
238         return nextRows.peek().getFirst();
239       }
240     }
241 
242     boolean updateTracker(Cell currentCell) {
243       if (!initialized) {
244         for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
245           updateWith(currentCell, fuzzyData);
246         }
247         initialized = true;
248       } else {
249         while (!nextRows.isEmpty() && !lessThan(currentCell, nextRows.peek().getFirst())) {
250           Pair<byte[], Pair<byte[], byte[]>> head = nextRows.poll();
251           Pair<byte[], byte[]> fuzzyData = head.getSecond();
252           updateWith(currentCell, fuzzyData);
253         }
254       }
255       return !nextRows.isEmpty();
256     }
257 
258     boolean lessThan(Cell currentCell, byte[] nextRowKey) {
259       int compareResult =
260           Bytes.compareTo(currentCell.getRowArray(), currentCell.getRowOffset(),
261             currentCell.getRowLength(), nextRowKey, 0, nextRowKey.length);
262       return (!isReversed() && compareResult < 0) || (isReversed() && compareResult > 0);
263     }
264 
265     void updateWith(Cell currentCell, Pair<byte[], byte[]> fuzzyData) {
266       byte[] nextRowKeyCandidate =
267           getNextForFuzzyRule(isReversed(), currentCell.getRowArray(), currentCell.getRowOffset(),
268             currentCell.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond());
269       if (nextRowKeyCandidate != null) {
270         nextRows.add(new Pair<byte[], Pair<byte[], byte[]>>(nextRowKeyCandidate, fuzzyData));
271       }
272     }
273 
274   }
275 
276   @Override
277   public boolean filterAllRemaining() {
278     return done;
279   }
280 
281   /**
282    * @return The filter serialized using pb
283    */
284   @Override
285   public byte[] toByteArray() {
286     FilterProtos.FuzzyRowFilter.Builder builder = FilterProtos.FuzzyRowFilter
287         .newBuilder()
288         .setIsMaskV2(processedWildcardMask == V2_PROCESSED_WILDCARD_MASK);
289     for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
290       BytesBytesPair.Builder bbpBuilder = BytesBytesPair.newBuilder();
291       bbpBuilder.setFirst(ByteStringer.wrap(fuzzyData.getFirst()));
292       bbpBuilder.setSecond(ByteStringer.wrap(fuzzyData.getSecond()));
293       builder.addFuzzyKeysData(bbpBuilder);
294     }
295     return builder.build().toByteArray();
296   }
297 
298   /**
299    * @param pbBytes A pb serialized {@link FuzzyRowFilter} instance
300    * @return An instance of {@link FuzzyRowFilter} made from <code>bytes</code>
301    * @throws DeserializationException
302    * @see #toByteArray
303    */
304   public static FuzzyRowFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
305     FilterProtos.FuzzyRowFilter proto;
306     try {
307       proto = FilterProtos.FuzzyRowFilter.parseFrom(pbBytes);
308     } catch (InvalidProtocolBufferException e) {
309       throw new DeserializationException(e);
310     }
311     int count = proto.getFuzzyKeysDataCount();
312     ArrayList<Pair<byte[], byte[]>> fuzzyKeysData = new ArrayList<Pair<byte[], byte[]>>(count);
313     for (int i = 0; i < count; ++i) {
314       BytesBytesPair current = proto.getFuzzyKeysData(i);
315       byte[] keyBytes = current.getFirst().toByteArray();
316       byte[] keyMeta = current.getSecond().toByteArray();
317       fuzzyKeysData.add(new Pair<byte[], byte[]>(keyBytes, keyMeta));
318     }
319     byte processedWildcardMask = proto.hasIsMaskV2() && proto.getIsMaskV2()
320         ? V2_PROCESSED_WILDCARD_MASK
321         : V1_PROCESSED_WILDCARD_MASK;
322     return new FuzzyRowFilter(fuzzyKeysData, processedWildcardMask);
323   }
324 
325   @Override
326   public String toString() {
327     final StringBuilder sb = new StringBuilder();
328     sb.append("FuzzyRowFilter");
329     sb.append("{fuzzyKeysData=");
330     for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
331       sb.append('{').append(Bytes.toStringBinary(fuzzyData.getFirst())).append(":");
332       sb.append(Bytes.toStringBinary(fuzzyData.getSecond())).append('}');
333     }
334     sb.append("}, ");
335     return sb.toString();
336   }
337 
338   // Utility methods
339 
340   static enum SatisfiesCode {
341     /** row satisfies fuzzy rule */
342     YES,
343     /** row doesn't satisfy fuzzy rule, but there's possible greater row that does */
344     NEXT_EXISTS,
345     /** row doesn't satisfy fuzzy rule and there's no greater row that does */
346     NO_NEXT
347   }
348 
349   @InterfaceAudience.Private
350   static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
351     return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
352   }
353 
354   @InterfaceAudience.Private
355   static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
356       byte[] fuzzyKeyMeta) {
357     return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
358   }
359 
360   static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length,
361       byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
362 
363     if (!UNSAFE_UNALIGNED) {
364       return satisfiesNoUnsafe(reverse, row, offset, length, fuzzyKeyBytes, fuzzyKeyMeta);
365     }
366 
367     if (row == null) {
368       // do nothing, let scan to proceed
369       return SatisfiesCode.YES;
370     }
371     length = Math.min(length, fuzzyKeyBytes.length);
372     int numWords = length / Bytes.SIZEOF_LONG;
373 
374     int j = numWords << 3; // numWords * SIZEOF_LONG;
375 
376     for (int i = 0; i < j; i += Bytes.SIZEOF_LONG) {
377       long fuzzyBytes = Bytes.toLong(fuzzyKeyBytes, i);
378       long fuzzyMeta = Bytes.toLong(fuzzyKeyMeta, i);
379       long rowValue = Bytes.toLong(row, offset + i);
380       if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
381         // We always return NEXT_EXISTS
382         return SatisfiesCode.NEXT_EXISTS;
383       }
384     }
385 
386     int off = j;
387 
388     if (length - off >= Bytes.SIZEOF_INT) {
389       int fuzzyBytes = Bytes.toInt(fuzzyKeyBytes, off);
390       int fuzzyMeta = Bytes.toInt(fuzzyKeyMeta, off);
391       int rowValue = Bytes.toInt(row, offset + off);
392       if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
393         // We always return NEXT_EXISTS
394         return SatisfiesCode.NEXT_EXISTS;
395       }
396       off += Bytes.SIZEOF_INT;
397     }
398 
399     if (length - off >= Bytes.SIZEOF_SHORT) {
400       short fuzzyBytes = Bytes.toShort(fuzzyKeyBytes, off);
401       short fuzzyMeta = Bytes.toShort(fuzzyKeyMeta, off);
402       short rowValue = Bytes.toShort(row, offset + off);
403       if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
404         // We always return NEXT_EXISTS
405         // even if it does not (in this case getNextForFuzzyRule
406         // will return null)
407         return SatisfiesCode.NEXT_EXISTS;
408       }
409       off += Bytes.SIZEOF_SHORT;
410     }
411 
412     if (length - off >= Bytes.SIZEOF_BYTE) {
413       int fuzzyBytes = fuzzyKeyBytes[off] & 0xff;
414       int fuzzyMeta = fuzzyKeyMeta[off] & 0xff;
415       int rowValue = row[offset + off] & 0xff;
416       if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
417         // We always return NEXT_EXISTS
418         return SatisfiesCode.NEXT_EXISTS;
419       }
420     }
421     return SatisfiesCode.YES;
422   }
423 
424   static SatisfiesCode satisfiesNoUnsafe(boolean reverse, byte[] row, int offset, int length,
425       byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
426     if (row == null) {
427       // do nothing, let scan to proceed
428       return SatisfiesCode.YES;
429     }
430 
431     Order order = Order.orderFor(reverse);
432     boolean nextRowKeyCandidateExists = false;
433 
434     for (int i = 0; i < fuzzyKeyMeta.length && i < length; i++) {
435       // First, checking if this position is fixed and not equals the given one
436       boolean byteAtPositionFixed = fuzzyKeyMeta[i] == 0;
437       boolean fixedByteIncorrect = byteAtPositionFixed && fuzzyKeyBytes[i] != row[i + offset];
438       if (fixedByteIncorrect) {
439         // in this case there's another row that satisfies fuzzy rule and bigger than this row
440         if (nextRowKeyCandidateExists) {
441           return SatisfiesCode.NEXT_EXISTS;
442         }
443 
444         // If this row byte is less than fixed then there's a byte array bigger than
445         // this row and which satisfies the fuzzy rule. Otherwise there's no such byte array:
446         // this row is simply bigger than any byte array that satisfies the fuzzy rule
447         boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF);
448         if (rowByteLessThanFixed && !reverse) {
449           return SatisfiesCode.NEXT_EXISTS;
450         } else if (!rowByteLessThanFixed && reverse) {
451           return SatisfiesCode.NEXT_EXISTS;
452         } else {
453           return SatisfiesCode.NO_NEXT;
454         }
455       }
456 
457       // Second, checking if this position is not fixed and byte value is not the biggest. In this
458       // case there's a byte array bigger than this row and which satisfies the fuzzy rule. To get
459       // bigger byte array that satisfies the rule we need to just increase this byte
460       // (see the code of getNextForFuzzyRule below) by one.
461       // Note: if non-fixed byte is already at biggest value, this doesn't allow us to say there's
462       // bigger one that satisfies the rule as it can't be increased.
463       if (fuzzyKeyMeta[i] == 1 && !order.isMax(fuzzyKeyBytes[i])) {
464         nextRowKeyCandidateExists = true;
465       }
466     }
467     return SatisfiesCode.YES;
468   }
469 
470   @InterfaceAudience.Private
471   static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
472     return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
473   }
474 
475   @InterfaceAudience.Private
476   static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
477       byte[] fuzzyKeyMeta) {
478     return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
479   }
480 
481   /** Abstracts directional comparisons based on scan direction. */
482   private enum Order {
483     ASC {
484       @Override
485       public boolean lt(int lhs, int rhs) {
486         return lhs < rhs;
487       }
488 
489       @Override
490       public boolean gt(int lhs, int rhs) {
491         return lhs > rhs;
492       }
493 
494       @Override
495       public byte inc(byte val) {
496         // TODO: what about over/underflow?
497         return (byte) (val + 1);
498       }
499 
500       @Override
501       public boolean isMax(byte val) {
502         return val == (byte) 0xff;
503       }
504 
505       @Override
506       public byte min() {
507         return 0;
508       }
509     },
510     DESC {
511       @Override
512       public boolean lt(int lhs, int rhs) {
513         return lhs > rhs;
514       }
515 
516       @Override
517       public boolean gt(int lhs, int rhs) {
518         return lhs < rhs;
519       }
520 
521       @Override
522       public byte inc(byte val) {
523         // TODO: what about over/underflow?
524         return (byte) (val - 1);
525       }
526 
527       @Override
528       public boolean isMax(byte val) {
529         return val == 0;
530       }
531 
532       @Override
533       public byte min() {
534         return (byte) 0xFF;
535       }
536     };
537 
538     public static Order orderFor(boolean reverse) {
539       return reverse ? DESC : ASC;
540     }
541 
542     /** Returns true when {@code lhs < rhs}. */
543     public abstract boolean lt(int lhs, int rhs);
544 
545     /** Returns true when {@code lhs > rhs}. */
546     public abstract boolean gt(int lhs, int rhs);
547 
548     /** Returns {@code val} incremented by 1. */
549     public abstract byte inc(byte val);
550 
551     /** Return true when {@code val} is the maximum value */
552     public abstract boolean isMax(byte val);
553 
554     /** Return the minimum value according to this ordering scheme. */
555     public abstract byte min();
556   }
557 
558   /**
559    * @return greater byte array than given (row) which satisfies the fuzzy rule if it exists, null
560    *         otherwise
561    */
562   @InterfaceAudience.Private
563   static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int length,
564       byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
565     // To find out the next "smallest" byte array that satisfies fuzzy rule and "greater" than
566     // the given one we do the following:
567     // 1. setting values on all "fixed" positions to the values from fuzzyKeyBytes
568     // 2. if during the first step given row did not increase, then we increase the value at
569     // the first "non-fixed" position (where it is not maximum already)
570 
571     // It is easier to perform this by using fuzzyKeyBytes copy and setting "non-fixed" position
572     // values than otherwise.
573     byte[] result =
574         Arrays.copyOf(fuzzyKeyBytes, length > fuzzyKeyBytes.length ? length : fuzzyKeyBytes.length);
575     if (reverse && length > fuzzyKeyBytes.length) {
576       // we need trailing 0xff's instead of trailing 0x00's
577       for (int i = fuzzyKeyBytes.length; i < result.length; i++) {
578         result[i] = (byte) 0xFF;
579       }
580     }
581     int toInc = -1;
582     final Order order = Order.orderFor(reverse);
583 
584     boolean increased = false;
585     for (int i = 0; i < result.length; i++) {
586       if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
587         result[i] = row[offset + i];
588         if (!order.isMax(row[offset + i])) {
589           // this is "non-fixed" position and is not at max value, hence we can increase it
590           toInc = i;
591         }
592       } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == -1 /* fixed */) {
593         if (order.lt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
594           // if setting value for any fixed position increased the original array,
595           // we are OK
596           increased = true;
597           break;
598         }
599 
600         if (order.gt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
601           // if setting value for any fixed position makes array "smaller", then just stop:
602           // in case we found some non-fixed position to increase we will do it, otherwise
603           // there's no "next" row key that satisfies fuzzy rule and "greater" than given row
604           break;
605         }
606       }
607     }
608 
609     if (!increased) {
610       if (toInc < 0) {
611         return null;
612       }
613       result[toInc] = order.inc(result[toInc]);
614 
615       // Setting all "non-fixed" positions to zeroes to the right of the one we increased so
616       // that found "next" row key is the smallest possible
617       for (int i = toInc + 1; i < result.length; i++) {
618         if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
619           result[i] = order.min();
620         }
621       }
622     }
623 
624     return reverse? result: trimTrailingZeroes(result, fuzzyKeyMeta, toInc);
625   }
626 
627   /**
628    * For forward scanner, next cell hint should  not contain any trailing zeroes
629    * unless they are part of fuzzyKeyMeta
630    * hint = '\x01\x01\x01\x00\x00'
631    * will skip valid row '\x01\x01\x01'
632    * 
633    * @param result
634    * @param fuzzyKeyMeta
635    * @param toInc - position of incremented byte
636    * @return trimmed version of result
637    */
638   
639   private static byte[] trimTrailingZeroes(byte[] result, byte[] fuzzyKeyMeta, int toInc) {
640     int off = fuzzyKeyMeta.length >= result.length? result.length -1:
641            fuzzyKeyMeta.length -1;  
642     for( ; off >= 0; off--){
643       if(fuzzyKeyMeta[off] != 0) break;
644     }
645     if (off < toInc)  off = toInc;
646     byte[] retValue = new byte[off+1];
647     System.arraycopy(result, 0, retValue, 0, retValue.length);
648     return retValue;
649   }
650 
651   /**
652    * @return true if and only if the fields of the filter that are serialized are equal to the
653    *         corresponding fields in other. Used for testing.
654    */
655   @Override
656   boolean areSerializedFieldsEqual(Filter o) {
657     if (o == this) return true;
658     if (!(o instanceof FuzzyRowFilter)) return false;
659 
660     FuzzyRowFilter other = (FuzzyRowFilter) o;
661     if (this.fuzzyKeysData.size() != other.fuzzyKeysData.size()) return false;
662     for (int i = 0; i < fuzzyKeysData.size(); ++i) {
663       Pair<byte[], byte[]> thisData = this.fuzzyKeysData.get(i);
664       Pair<byte[], byte[]> otherData = other.fuzzyKeysData.get(i);
665       if (!(Bytes.equals(thisData.getFirst(), otherData.getFirst()) && Bytes.equals(
666         thisData.getSecond(), otherData.getSecond()))) {
667         return false;
668       }
669     }
670     return true;
671   }
672 
673   @Override
674   public boolean equals(Object obj) {
675     return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
676   }
677 
678   @Override
679   public int hashCode() {
680     return Objects.hash(this.fuzzyKeysData);
681   }
682 }