001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.filter;
019
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Comparator;
023import java.util.List;
024import java.util.PriorityQueue;
025
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.CellComparator;
028import org.apache.hadoop.hbase.PrivateCellUtil;
029import org.apache.yetus.audience.InterfaceAudience;
030import org.apache.hadoop.hbase.exceptions.DeserializationException;
031import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
032import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
033import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
034import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.BytesBytesPair;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.util.Pair;
037import org.apache.hadoop.hbase.util.UnsafeAvailChecker;
038
039import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
040
041/**
042 * This is optimized version of a standard FuzzyRowFilter Filters data based on fuzzy row key.
043 * Performs fast-forwards during scanning. It takes pairs (row key, fuzzy info) to match row keys.
044 * Where fuzzy info is a byte array with 0 or 1 as its values:
045 * <ul>
046 * <li>0 - means that this byte in provided row key is fixed, i.e. row key's byte at same position
047 * must match</li>
048 * <li>1 - means that this byte in provided row key is NOT fixed, i.e. row key's byte at this
049 * position can be different from the one in provided row key</li>
050 * </ul>
051 * Example: Let's assume row key format is userId_actionId_year_month. Length of userId is fixed and
052 * is 4, length of actionId is 2 and year and month are 4 and 2 bytes long respectively. Let's
053 * assume that we need to fetch all users that performed certain action (encoded as "99") in Jan of
054 * any year. Then the pair (row key, fuzzy info) would be the following: row key = "????_99_????_01"
055 * (one can use any value instead of "?") fuzzy info =
056 * "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00" I.e. fuzzy info tells the matching
057 * mask is "????_99_????_01", where at ? can be any value.
058 */
059@InterfaceAudience.Public
060public class FuzzyRowFilter extends FilterBase {
061  private static final boolean UNSAFE_UNALIGNED = UnsafeAvailChecker.unaligned();
062  private List<Pair<byte[], byte[]>> fuzzyKeysData;
063  private boolean done = false;
064
065  /**
066   * The index of a last successfully found matching fuzzy string (in fuzzyKeysData). We will start
067   * matching next KV with this one. If they do not match then we will return back to the one-by-one
068   * iteration over fuzzyKeysData.
069   */
070  private int lastFoundIndex = -1;
071
072  /**
073   * Row tracker (keeps all next rows after SEEK_NEXT_USING_HINT was returned)
074   */
075  private RowTracker tracker;
076
077  public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) {
078    List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new ArrayList<>(fuzzyKeysData.size());
079
080    for (Pair<byte[], byte[]> aFuzzyKeysData : fuzzyKeysData) {
081      if (aFuzzyKeysData.getFirst().length != aFuzzyKeysData.getSecond().length) {
082        Pair<String, String> readable =
083          new Pair<>(Bytes.toStringBinary(aFuzzyKeysData.getFirst()), Bytes.toStringBinary(aFuzzyKeysData.getSecond()));
084        throw new IllegalArgumentException("Fuzzy pair lengths do not match: " + readable);
085      }
086
087      Pair<byte[], byte[]> p = new Pair<>();
088      // create a copy of pair bytes so that they are not modified by the filter.
089      p.setFirst(Arrays.copyOf(aFuzzyKeysData.getFirst(), aFuzzyKeysData.getFirst().length));
090      p.setSecond(Arrays.copyOf(aFuzzyKeysData.getSecond(), aFuzzyKeysData.getSecond().length));
091
092      // update mask ( 0 -> -1 (0xff), 1 -> 2)
093      p.setSecond(preprocessMask(p.getSecond()));
094      preprocessSearchKey(p);
095
096      fuzzyKeyDataCopy.add(p);
097    }
098    this.fuzzyKeysData = fuzzyKeyDataCopy;
099    this.tracker = new RowTracker();
100  }
101
102
103  private void preprocessSearchKey(Pair<byte[], byte[]> p) {
104    if (!UNSAFE_UNALIGNED) {
105      // do nothing
106      return;
107    }
108    byte[] key = p.getFirst();
109    byte[] mask = p.getSecond();
110    for (int i = 0; i < mask.length; i++) {
111      // set non-fixed part of a search key to 0.
112      if (mask[i] == 2) {
113        key[i] = 0;
114      }
115    }
116  }
117
118  /**
119   * We need to preprocess mask array, as since we treat 2's as unfixed positions and -1 (0xff) as
120   * fixed positions
121   * @param mask
122   * @return mask array
123   */
124  private byte[] preprocessMask(byte[] mask) {
125    if (!UNSAFE_UNALIGNED) {
126      // do nothing
127      return mask;
128    }
129    if (isPreprocessedMask(mask)) return mask;
130    for (int i = 0; i < mask.length; i++) {
131      if (mask[i] == 0) {
132        mask[i] = -1; // 0 -> -1
133      } else if (mask[i] == 1) {
134        mask[i] = 2;// 1 -> 2
135      }
136    }
137    return mask;
138  }
139
140  private boolean isPreprocessedMask(byte[] mask) {
141    for (int i = 0; i < mask.length; i++) {
142      if (mask[i] != -1 && mask[i] != 2) {
143        return false;
144      }
145    }
146    return true;
147  }
148
149  @Deprecated
150  @Override
151  public ReturnCode filterKeyValue(final Cell c) {
152    return filterCell(c);
153  }
154
155  @Override
156  public ReturnCode filterCell(final Cell c) {
157    final int startIndex = lastFoundIndex >= 0 ? lastFoundIndex : 0;
158    final int size = fuzzyKeysData.size();
159    for (int i = startIndex; i < size + startIndex; i++) {
160      final int index = i % size;
161      Pair<byte[], byte[]> fuzzyData = fuzzyKeysData.get(index);
162      // This shift is idempotent - always end up with 0 and -1 as mask values.
163      for (int j = 0; j < fuzzyData.getSecond().length; j++) {
164        fuzzyData.getSecond()[j] >>= 2;
165      }
166      SatisfiesCode satisfiesCode =
167          satisfies(isReversed(), c.getRowArray(), c.getRowOffset(), c.getRowLength(),
168            fuzzyData.getFirst(), fuzzyData.getSecond());
169      if (satisfiesCode == SatisfiesCode.YES) {
170        lastFoundIndex = index;
171        return ReturnCode.INCLUDE;
172      }
173    }
174    // NOT FOUND -> seek next using hint
175    lastFoundIndex = -1;
176
177    return ReturnCode.SEEK_NEXT_USING_HINT;
178
179  }
180
181  @Override
182  public Cell getNextCellHint(Cell currentCell) {
183    boolean result = tracker.updateTracker(currentCell);
184    if (result == false) {
185      done = true;
186      return null;
187    }
188    byte[] nextRowKey = tracker.nextRow();
189    return PrivateCellUtil.createFirstOnRow(nextRowKey, 0, (short) nextRowKey.length);
190  }
191
192  /**
193   * If we have multiple fuzzy keys, row tracker should improve overall performance. It calculates
194   * all next rows (one per every fuzzy key) and put them (the fuzzy key is bundled) into a priority
195   * queue so that the smallest row key always appears at queue head, which helps to decide the
196   * "Next Cell Hint". As scanning going on, the number of candidate rows in the RowTracker will
197   * remain the size of fuzzy keys until some of the fuzzy keys won't possibly have matches any
198   * more.
199   */
200  private class RowTracker {
201    private final PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>> nextRows;
202    private boolean initialized = false;
203
204    RowTracker() {
205      nextRows = new PriorityQueue<>(fuzzyKeysData.size(),
206              new Comparator<Pair<byte[], Pair<byte[], byte[]>>>() {
207                @Override
208                public int compare(Pair<byte[], Pair<byte[], byte[]>> o1,
209                    Pair<byte[], Pair<byte[], byte[]>> o2) {
210                  return isReversed()? Bytes.compareTo(o2.getFirst(), o1.getFirst()):
211                    Bytes.compareTo(o1.getFirst(), o2.getFirst());
212                }
213              });
214    }
215
216    byte[] nextRow() {
217      if (nextRows.isEmpty()) {
218        throw new IllegalStateException(
219            "NextRows should not be empty, make sure to call nextRow() after updateTracker() return true");
220      } else {
221        return nextRows.peek().getFirst();
222      }
223    }
224
225    boolean updateTracker(Cell currentCell) {
226      if (!initialized) {
227        for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
228          updateWith(currentCell, fuzzyData);
229        }
230        initialized = true;
231      } else {
232        while (!nextRows.isEmpty() && !lessThan(currentCell, nextRows.peek().getFirst())) {
233          Pair<byte[], Pair<byte[], byte[]>> head = nextRows.poll();
234          Pair<byte[], byte[]> fuzzyData = head.getSecond();
235          updateWith(currentCell, fuzzyData);
236        }
237      }
238      return !nextRows.isEmpty();
239    }
240
241    boolean lessThan(Cell currentCell, byte[] nextRowKey) {
242      int compareResult = CellComparator.getInstance().compareRows(currentCell, nextRowKey, 0, nextRowKey.length);
243      return (!isReversed() && compareResult < 0) || (isReversed() && compareResult > 0);
244    }
245
246    void updateWith(Cell currentCell, Pair<byte[], byte[]> fuzzyData) {
247      byte[] nextRowKeyCandidate =
248          getNextForFuzzyRule(isReversed(), currentCell.getRowArray(), currentCell.getRowOffset(),
249            currentCell.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond());
250      if (nextRowKeyCandidate != null) {
251        nextRows.add(new Pair<>(nextRowKeyCandidate, fuzzyData));
252      }
253    }
254
255  }
256
257  @Override
258  public boolean filterAllRemaining() {
259    return done;
260  }
261
262  /**
263   * @return The filter serialized using pb
264   */
265  @Override
266  public byte[] toByteArray() {
267    FilterProtos.FuzzyRowFilter.Builder builder = FilterProtos.FuzzyRowFilter.newBuilder();
268    for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
269      BytesBytesPair.Builder bbpBuilder = BytesBytesPair.newBuilder();
270      bbpBuilder.setFirst(UnsafeByteOperations.unsafeWrap(fuzzyData.getFirst()));
271      bbpBuilder.setSecond(UnsafeByteOperations.unsafeWrap(fuzzyData.getSecond()));
272      builder.addFuzzyKeysData(bbpBuilder);
273    }
274    return builder.build().toByteArray();
275  }
276
277  /**
278   * @param pbBytes A pb serialized {@link FuzzyRowFilter} instance
279   * @return An instance of {@link FuzzyRowFilter} made from <code>bytes</code>
280   * @throws DeserializationException
281   * @see #toByteArray
282   */
283  public static FuzzyRowFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
284    FilterProtos.FuzzyRowFilter proto;
285    try {
286      proto = FilterProtos.FuzzyRowFilter.parseFrom(pbBytes);
287    } catch (InvalidProtocolBufferException e) {
288      throw new DeserializationException(e);
289    }
290    int count = proto.getFuzzyKeysDataCount();
291    ArrayList<Pair<byte[], byte[]>> fuzzyKeysData = new ArrayList<>(count);
292    for (int i = 0; i < count; ++i) {
293      BytesBytesPair current = proto.getFuzzyKeysData(i);
294      byte[] keyBytes = current.getFirst().toByteArray();
295      byte[] keyMeta = current.getSecond().toByteArray();
296      fuzzyKeysData.add(new Pair<>(keyBytes, keyMeta));
297    }
298    return new FuzzyRowFilter(fuzzyKeysData);
299  }
300
301  @Override
302  public String toString() {
303    final StringBuilder sb = new StringBuilder();
304    sb.append("FuzzyRowFilter");
305    sb.append("{fuzzyKeysData=");
306    for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
307      sb.append('{').append(Bytes.toStringBinary(fuzzyData.getFirst())).append(":");
308      sb.append(Bytes.toStringBinary(fuzzyData.getSecond())).append('}');
309    }
310    sb.append("}, ");
311    return sb.toString();
312  }
313
314  // Utility methods
315
316  static enum SatisfiesCode {
317    /** row satisfies fuzzy rule */
318    YES,
319    /** row doesn't satisfy fuzzy rule, but there's possible greater row that does */
320    NEXT_EXISTS,
321    /** row doesn't satisfy fuzzy rule and there's no greater row that does */
322    NO_NEXT
323  }
324
325  @VisibleForTesting
326  static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
327    return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
328  }
329
330  @VisibleForTesting
331  static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
332      byte[] fuzzyKeyMeta) {
333    return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
334  }
335
336  static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length,
337      byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
338
339    if (!UNSAFE_UNALIGNED) {
340      return satisfiesNoUnsafe(reverse, row, offset, length, fuzzyKeyBytes, fuzzyKeyMeta);
341    }
342
343    if (row == null) {
344      // do nothing, let scan to proceed
345      return SatisfiesCode.YES;
346    }
347    length = Math.min(length, fuzzyKeyBytes.length);
348    int numWords = length / Bytes.SIZEOF_LONG;
349
350    int j = numWords << 3; // numWords * SIZEOF_LONG;
351
352    for (int i = 0; i < j; i += Bytes.SIZEOF_LONG) {
353      long fuzzyBytes = Bytes.toLong(fuzzyKeyBytes, i);
354      long fuzzyMeta = Bytes.toLong(fuzzyKeyMeta, i);
355      long rowValue = Bytes.toLong(row, offset + i);
356      if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
357        // We always return NEXT_EXISTS
358        return SatisfiesCode.NEXT_EXISTS;
359      }
360    }
361
362    int off = j;
363
364    if (length - off >= Bytes.SIZEOF_INT) {
365      int fuzzyBytes = Bytes.toInt(fuzzyKeyBytes, off);
366      int fuzzyMeta = Bytes.toInt(fuzzyKeyMeta, off);
367      int rowValue = Bytes.toInt(row, offset + off);
368      if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
369        // We always return NEXT_EXISTS
370        return SatisfiesCode.NEXT_EXISTS;
371      }
372      off += Bytes.SIZEOF_INT;
373    }
374
375    if (length - off >= Bytes.SIZEOF_SHORT) {
376      short fuzzyBytes = Bytes.toShort(fuzzyKeyBytes, off);
377      short fuzzyMeta = Bytes.toShort(fuzzyKeyMeta, off);
378      short rowValue = Bytes.toShort(row, offset + off);
379      if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
380        // We always return NEXT_EXISTS
381        // even if it does not (in this case getNextForFuzzyRule
382        // will return null)
383        return SatisfiesCode.NEXT_EXISTS;
384      }
385      off += Bytes.SIZEOF_SHORT;
386    }
387
388    if (length - off >= Bytes.SIZEOF_BYTE) {
389      int fuzzyBytes = fuzzyKeyBytes[off] & 0xff;
390      int fuzzyMeta = fuzzyKeyMeta[off] & 0xff;
391      int rowValue = row[offset + off] & 0xff;
392      if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
393        // We always return NEXT_EXISTS
394        return SatisfiesCode.NEXT_EXISTS;
395      }
396    }
397    return SatisfiesCode.YES;
398  }
399
400  static SatisfiesCode satisfiesNoUnsafe(boolean reverse, byte[] row, int offset, int length,
401      byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
402    if (row == null) {
403      // do nothing, let scan to proceed
404      return SatisfiesCode.YES;
405    }
406
407    Order order = Order.orderFor(reverse);
408    boolean nextRowKeyCandidateExists = false;
409
410    for (int i = 0; i < fuzzyKeyMeta.length && i < length; i++) {
411      // First, checking if this position is fixed and not equals the given one
412      boolean byteAtPositionFixed = fuzzyKeyMeta[i] == 0;
413      boolean fixedByteIncorrect = byteAtPositionFixed && fuzzyKeyBytes[i] != row[i + offset];
414      if (fixedByteIncorrect) {
415        // in this case there's another row that satisfies fuzzy rule and bigger than this row
416        if (nextRowKeyCandidateExists) {
417          return SatisfiesCode.NEXT_EXISTS;
418        }
419
420        // If this row byte is less than fixed then there's a byte array bigger than
421        // this row and which satisfies the fuzzy rule. Otherwise there's no such byte array:
422        // this row is simply bigger than any byte array that satisfies the fuzzy rule
423        boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF);
424        if (rowByteLessThanFixed && !reverse) {
425          return SatisfiesCode.NEXT_EXISTS;
426        } else if (!rowByteLessThanFixed && reverse) {
427          return SatisfiesCode.NEXT_EXISTS;
428        } else {
429          return SatisfiesCode.NO_NEXT;
430        }
431      }
432
433      // Second, checking if this position is not fixed and byte value is not the biggest. In this
434      // case there's a byte array bigger than this row and which satisfies the fuzzy rule. To get
435      // bigger byte array that satisfies the rule we need to just increase this byte
436      // (see the code of getNextForFuzzyRule below) by one.
437      // Note: if non-fixed byte is already at biggest value, this doesn't allow us to say there's
438      // bigger one that satisfies the rule as it can't be increased.
439      if (fuzzyKeyMeta[i] == 1 && !order.isMax(fuzzyKeyBytes[i])) {
440        nextRowKeyCandidateExists = true;
441      }
442    }
443    return SatisfiesCode.YES;
444  }
445
446  @VisibleForTesting
447  static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
448    return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
449  }
450
451  @VisibleForTesting
452  static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
453      byte[] fuzzyKeyMeta) {
454    return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
455  }
456
457  /** Abstracts directional comparisons based on scan direction. */
458  private enum Order {
459    ASC {
460      @Override
461      public boolean lt(int lhs, int rhs) {
462        return lhs < rhs;
463      }
464
465      @Override
466      public boolean gt(int lhs, int rhs) {
467        return lhs > rhs;
468      }
469
470      @Override
471      public byte inc(byte val) {
472        // TODO: what about over/underflow?
473        return (byte) (val + 1);
474      }
475
476      @Override
477      public boolean isMax(byte val) {
478        return val == (byte) 0xff;
479      }
480
481      @Override
482      public byte min() {
483        return 0;
484      }
485    },
486    DESC {
487      @Override
488      public boolean lt(int lhs, int rhs) {
489        return lhs > rhs;
490      }
491
492      @Override
493      public boolean gt(int lhs, int rhs) {
494        return lhs < rhs;
495      }
496
497      @Override
498      public byte inc(byte val) {
499        // TODO: what about over/underflow?
500        return (byte) (val - 1);
501      }
502
503      @Override
504      public boolean isMax(byte val) {
505        return val == 0;
506      }
507
508      @Override
509      public byte min() {
510        return (byte) 0xFF;
511      }
512    };
513
514    public static Order orderFor(boolean reverse) {
515      return reverse ? DESC : ASC;
516    }
517
518    /** Returns true when {@code lhs < rhs}. */
519    public abstract boolean lt(int lhs, int rhs);
520
521    /** Returns true when {@code lhs > rhs}. */
522    public abstract boolean gt(int lhs, int rhs);
523
524    /** Returns {@code val} incremented by 1. */
525    public abstract byte inc(byte val);
526
527    /** Return true when {@code val} is the maximum value */
528    public abstract boolean isMax(byte val);
529
530    /** Return the minimum value according to this ordering scheme. */
531    public abstract byte min();
532  }
533
534  /**
535   * @return greater byte array than given (row) which satisfies the fuzzy rule if it exists, null
536   *         otherwise
537   */
538  @VisibleForTesting
539  static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int length,
540      byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
541    // To find out the next "smallest" byte array that satisfies fuzzy rule and "greater" than
542    // the given one we do the following:
543    // 1. setting values on all "fixed" positions to the values from fuzzyKeyBytes
544    // 2. if during the first step given row did not increase, then we increase the value at
545    // the first "non-fixed" position (where it is not maximum already)
546
547    // It is easier to perform this by using fuzzyKeyBytes copy and setting "non-fixed" position
548    // values than otherwise.
549    byte[] result =
550        Arrays.copyOf(fuzzyKeyBytes, length > fuzzyKeyBytes.length ? length : fuzzyKeyBytes.length);
551    if (reverse && length > fuzzyKeyBytes.length) {
552      // we need trailing 0xff's instead of trailing 0x00's
553      for (int i = fuzzyKeyBytes.length; i < result.length; i++) {
554        result[i] = (byte) 0xFF;
555      }
556    }
557    int toInc = -1;
558    final Order order = Order.orderFor(reverse);
559
560    boolean increased = false;
561    for (int i = 0; i < result.length; i++) {
562      if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
563        result[i] = row[offset + i];
564        if (!order.isMax(row[offset + i])) {
565          // this is "non-fixed" position and is not at max value, hence we can increase it
566          toInc = i;
567        }
568      } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == -1 /* fixed */) {
569        if (order.lt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
570          // if setting value for any fixed position increased the original array,
571          // we are OK
572          increased = true;
573          break;
574        }
575
576        if (order.gt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
577          // if setting value for any fixed position makes array "smaller", then just stop:
578          // in case we found some non-fixed position to increase we will do it, otherwise
579          // there's no "next" row key that satisfies fuzzy rule and "greater" than given row
580          break;
581        }
582      }
583    }
584
585    if (!increased) {
586      if (toInc < 0) {
587        return null;
588      }
589      result[toInc] = order.inc(result[toInc]);
590
591      // Setting all "non-fixed" positions to zeroes to the right of the one we increased so
592      // that found "next" row key is the smallest possible
593      for (int i = toInc + 1; i < result.length; i++) {
594        if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
595          result[i] = order.min();
596        }
597      }
598    }
599
600    return reverse? result: trimTrailingZeroes(result, fuzzyKeyMeta, toInc);
601  }
602
603  /**
604   * For forward scanner, next cell hint should  not contain any trailing zeroes
605   * unless they are part of fuzzyKeyMeta
606   * hint = '\x01\x01\x01\x00\x00'
607   * will skip valid row '\x01\x01\x01'
608   * 
609   * @param result
610   * @param fuzzyKeyMeta
611   * @param toInc - position of incremented byte
612   * @return trimmed version of result
613   */
614  
615  private static byte[] trimTrailingZeroes(byte[] result, byte[] fuzzyKeyMeta, int toInc) {
616    int off = fuzzyKeyMeta.length >= result.length? result.length -1:
617           fuzzyKeyMeta.length -1;  
618    for( ; off >= 0; off--){
619      if(fuzzyKeyMeta[off] != 0) break;
620    }
621    if (off < toInc)  off = toInc;
622    byte[] retValue = new byte[off+1];
623    System.arraycopy(result, 0, retValue, 0, retValue.length);
624    return retValue;
625  }
626
627  /**
628   * @return true if and only if the fields of the filter that are serialized are equal to the
629   *         corresponding fields in other. Used for testing.
630   */
631  @Override
632  boolean areSerializedFieldsEqual(Filter o) {
633    if (o == this) return true;
634    if (!(o instanceof FuzzyRowFilter)) return false;
635
636    FuzzyRowFilter other = (FuzzyRowFilter) o;
637    if (this.fuzzyKeysData.size() != other.fuzzyKeysData.size()) return false;
638    for (int i = 0; i < fuzzyKeysData.size(); ++i) {
639      Pair<byte[], byte[]> thisData = this.fuzzyKeysData.get(i);
640      Pair<byte[], byte[]> otherData = other.fuzzyKeysData.get(i);
641      if (!(Bytes.equals(thisData.getFirst(), otherData.getFirst()) && Bytes.equals(
642        thisData.getSecond(), otherData.getSecond()))) {
643        return false;
644      }
645    }
646    return true;
647  }
648}