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.io.IOException;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Comparator;
024import java.util.List;
025import java.util.PriorityQueue;
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.CellComparator;
028import org.apache.hadoop.hbase.PrivateCellUtil;
029import org.apache.hadoop.hbase.exceptions.DeserializationException;
030import org.apache.hadoop.hbase.unsafe.HBasePlatformDependent;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.apache.hadoop.hbase.util.Pair;
033import org.apache.yetus.audience.InterfaceAudience;
034
035import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
036import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
037
038import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos;
039import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.BytesBytesPair;
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:
052 * <p/>
053 * Let's assume row key format is userId_actionId_year_month. Length of userId is fixed and is 4,
054 * length of actionId is 2 and year and month are 4 and 2 bytes long respectively.
055 * <p/>
056 * Let's assume that we need to fetch all users that performed certain action (encoded as "99") in
057 * Jan of any year. Then the pair (row key, fuzzy info) would be the following:
058 *
059 * <pre>
060 * row key = "????_99_????_01" (one can use any value instead of "?")
061 * fuzzy info = "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00"
062 * </pre>
063 *
064 * I.e. fuzzy info tells the matching mask is "????_99_????_01", where at ? can be any value.
065 */
066@InterfaceAudience.Public
067public class FuzzyRowFilter extends FilterBase implements HintingFilter {
068  private static final boolean UNSAFE_UNALIGNED = HBasePlatformDependent.unaligned();
069  private final List<Pair<byte[], byte[]>> fuzzyKeysData;
070  // Used to record whether we want to skip the current row.
071  // Usually we should use filterRowKey here but in the current scan implementation, if filterRowKey
072  // returns true, we will just skip to next row, instead of calling getNextCellHint to determine
073  // the actual next row, so we need to implement filterCell and return SEEK_NEXT_USING_HINT to let
074  // upper layer call getNextCellHint.
075  // And if we do not implement filterRow, sometimes we will get incorrect result when using
076  // FuzzyRowFilter together with other filters, please see the description for HBASE-26967 for more
077  // details.
078  private boolean filterRow;
079  private boolean done = false;
080
081  /**
082   * The index of a last successfully found matching fuzzy string (in fuzzyKeysData). We will start
083   * matching next KV with this one. If they do not match then we will return back to the one-by-one
084   * iteration over fuzzyKeysData.
085   */
086  private int lastFoundIndex = -1;
087
088  /**
089   * Row tracker for next row hints and reverse same-row hint detection.
090   */
091  private final RowTracker tracker;
092
093  public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) {
094    List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new ArrayList<>(fuzzyKeysData.size());
095
096    for (Pair<byte[], byte[]> aFuzzyKeysData : fuzzyKeysData) {
097      if (aFuzzyKeysData.getFirst().length != aFuzzyKeysData.getSecond().length) {
098        Pair<String, String> readable = new Pair<>(Bytes.toStringBinary(aFuzzyKeysData.getFirst()),
099          Bytes.toStringBinary(aFuzzyKeysData.getSecond()));
100        throw new IllegalArgumentException("Fuzzy pair lengths do not match: " + readable);
101      }
102
103      Pair<byte[], byte[]> p = new Pair<>();
104      // create a copy of pair bytes so that they are not modified by the filter.
105      p.setFirst(Arrays.copyOf(aFuzzyKeysData.getFirst(), aFuzzyKeysData.getFirst().length));
106      p.setSecond(Arrays.copyOf(aFuzzyKeysData.getSecond(), aFuzzyKeysData.getSecond().length));
107
108      // Normalize the mask, zero the non-fixed key bytes, then fix the unsafe mask to its final
109      // {-1, 0} form once here so it is never mutated during scanning.
110      p.setSecond(preprocessMask(p.getSecond()));
111      preprocessSearchKey(p, UNSAFE_UNALIGNED);
112      preprocessMaskForSatisfies(p.getSecond(), UNSAFE_UNALIGNED);
113
114      fuzzyKeyDataCopy.add(p);
115    }
116    this.fuzzyKeysData = fuzzyKeyDataCopy;
117    this.tracker = new RowTracker();
118  }
119
120  /**
121   * Zeroes the non-fixed ("don't care") positions of the search key (on both paths) so the
122   * next-cell hint from {@link #getNextForFuzzyRule} is the smallest matching row. The byte at a
123   * non-fixed position is never compared, so this affects neither matching nor deserialization.
124   */
125  static void preprocessSearchKey(Pair<byte[], byte[]> p, boolean unsafeUnaligned) {
126    byte[] key = p.getFirst();
127    byte[] mask = p.getSecond();
128    for (int i = 0; i < mask.length; i++) {
129      // non-fixed is encoded as 2 on the unsafe path ({-1, 2}) and as 1 on no-unsafe ({0, 1})
130      if ((unsafeUnaligned && mask[i] == 2) || (!unsafeUnaligned && mask[i] == 1)) {
131        key[i] = 0;
132      }
133    }
134  }
135
136  /**
137   * Normalizes the incoming mask to the active path's encoding. Input is the public {0, 1} form, or
138   * the already-preprocessed unsafe {-1, 2} form when {@link #parseFrom} deserializes a filter from
139   * an unsafe peer; accepting both lets a filter serialized on one platform work on the other.
140   * Unsafe keeps/produces {-1, 2}; no-unsafe keeps/produces {0, 1}.
141   * @return mask array
142   */
143  private byte[] preprocessMask(byte[] mask) {
144    if (!UNSAFE_UNALIGNED) {
145      if (isPreprocessedMask(mask)) {
146        // deserialized {-1, 2} from an unsafe peer -> restore {0, 1}
147        for (int i = 0; i < mask.length; i++) {
148          if (mask[i] == -1) {
149            mask[i] = 0; // -1 -> 0
150          } else if (mask[i] == 2) {
151            mask[i] = 1; // 2 -> 1
152          }
153        }
154      }
155      return mask;
156    }
157    if (isPreprocessedMask(mask)) return mask;
158    for (int i = 0; i < mask.length; i++) {
159      if (mask[i] == 0) {
160        mask[i] = -1; // 0 -> -1
161      } else if (mask[i] == 1) {
162        mask[i] = 2;// 1 -> 2
163      }
164    }
165    return mask;
166  }
167
168  private boolean isPreprocessedMask(byte[] mask) {
169    for (int i = 0; i < mask.length; i++) {
170      if (mask[i] != -1 && mask[i] != 2) {
171        return false;
172      }
173    }
174    return true;
175  }
176
177  /**
178   * Converts a stored mask back to the public {0 (fixed), 1 (non-fixed)} form as a new array, so
179   * serialization, {@link #getFuzzyKeys}, equals and hashCode never expose the internal encoding.
180   * No-unsafe already stores {0, 1}; unsafe stores {-1, 0}, where -1 is fixed and anything else is
181   * non-fixed.
182   * @return a new array in {0, 1} form
183   */
184  private static byte[] toConstructorMask(byte[] mask, boolean unsafeUnaligned) {
185    byte[] out = Arrays.copyOf(mask, mask.length);
186    if (unsafeUnaligned) {
187      for (int i = 0; i < out.length; i++) {
188        out[i] = (byte) (out[i] == -1 ? 0 : 1);
189      }
190    }
191    return out;
192  }
193
194  /**
195   * Returns the Fuzzy keys in the format expected by the constructor.
196   * @return the Fuzzy keys in the format expected by the constructor
197   */
198  public List<Pair<byte[], byte[]>> getFuzzyKeys() {
199    List<Pair<byte[], byte[]>> returnList = new ArrayList<>(fuzzyKeysData.size());
200    for (Pair<byte[], byte[]> fuzzyKey : fuzzyKeysData) {
201      Pair<byte[], byte[]> returnKey = new Pair<>();
202      // This won't revert the original key's don't care values, but we don't care.
203      returnKey.setFirst(Arrays.copyOf(fuzzyKey.getFirst(), fuzzyKey.getFirst().length));
204      returnKey.setSecond(toConstructorMask(fuzzyKey.getSecond(), UNSAFE_UNALIGNED));
205      returnList.add(returnKey);
206    }
207    return returnList;
208  }
209
210  @Override
211  public void reset() throws IOException {
212    filterRow = false;
213  }
214
215  @Override
216  public boolean filterRow() throws IOException {
217    return filterRow;
218  }
219
220  @Override
221  public ReturnCode filterCell(final Cell c) {
222    final int startIndex = Math.max(lastFoundIndex, 0);
223    final int size = fuzzyKeysData.size();
224    for (int i = startIndex; i < size + startIndex; i++) {
225      final int index = i % size;
226      Pair<byte[], byte[]> fuzzyData = fuzzyKeysData.get(index);
227      SatisfiesCode satisfiesCode = satisfies(isReversed(), c.getRowArray(), c.getRowOffset(),
228        c.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond());
229      if (satisfiesCode == SatisfiesCode.YES) {
230        lastFoundIndex = index;
231        return ReturnCode.INCLUDE;
232      }
233    }
234    // NOT FOUND -> seek next using hint or skip the current row.
235    lastFoundIndex = -1;
236    filterRow = true;
237    // For reverse scans, a non-matching row can recreate itself as the next hint. Since fuzzy
238    // matching is row-key based, skip the whole non-matching row instead of seeking to it again.
239    if (isReversed() && tracker.updateTracker(c) && tracker.isNextRowSameAs(c)) {
240      return ReturnCode.NEXT_ROW;
241    }
242    return ReturnCode.SEEK_NEXT_USING_HINT;
243
244  }
245
246  @Override
247  public Cell getNextCellHint(Cell currentCell) {
248    boolean result = tracker.updateTracker(currentCell);
249    if (!result) {
250      done = true;
251      return null;
252    }
253    byte[] nextRowKey = tracker.nextRow();
254    if (isReversed() && !tracker.lessThan(currentCell, nextRowKey)) {
255      // filterCell normally handles same-row reverse hints with NEXT_ROW. If a non-progressing
256      // hint still reaches here, keep the current-row boundary to avoid skipping matching rows
257      // under it, but return a non-seeking hint so StoreScanner advances normally.
258      return PrivateCellUtil.createLastOnRow(currentCell);
259    }
260    return PrivateCellUtil.createFirstOnRow(nextRowKey, 0, (short) nextRowKey.length);
261  }
262
263  /**
264   * If we have multiple fuzzy keys, row tracker should improve overall performance. It calculates
265   * all next rows (one per every fuzzy key) and put them (the fuzzy key is bundled) into a priority
266   * queue so that the smallest row key always appears at queue head, which helps to decide the
267   * "Next Cell Hint". As scanning going on, the number of candidate rows in the RowTracker will
268   * remain the size of fuzzy keys until some of the fuzzy keys won't possibly have matches any
269   * more.
270   */
271  private class RowTracker {
272    private final PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>> nextRows;
273    private boolean initialized = false;
274
275    RowTracker() {
276      nextRows = new PriorityQueue<>(fuzzyKeysData.size(),
277        new Comparator<Pair<byte[], Pair<byte[], byte[]>>>() {
278          @Override
279          public int compare(Pair<byte[], Pair<byte[], byte[]>> o1,
280            Pair<byte[], Pair<byte[], byte[]>> o2) {
281            return isReversed()
282              ? Bytes.compareTo(o2.getFirst(), o1.getFirst())
283              : Bytes.compareTo(o1.getFirst(), o2.getFirst());
284          }
285        });
286    }
287
288    byte[] nextRow() {
289      if (nextRows.isEmpty()) {
290        throw new IllegalStateException("NextRows should not be empty, "
291          + "make sure to call nextRow() after updateTracker() return true");
292      } else {
293        return nextRows.peek().getFirst();
294      }
295    }
296
297    boolean updateTracker(Cell currentCell) {
298      if (!initialized) {
299        for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
300          updateWith(currentCell, fuzzyData);
301        }
302        initialized = true;
303      } else {
304        while (!nextRows.isEmpty() && !lessThan(currentCell, nextRows.peek().getFirst())) {
305          Pair<byte[], Pair<byte[], byte[]>> head = nextRows.poll();
306          Pair<byte[], byte[]> fuzzyData = head.getSecond();
307          byte[] nextRowKeyCandidate = updateWith(currentCell, fuzzyData);
308          if (nextRowKeyCandidate != null && !lessThan(currentCell, nextRowKeyCandidate)) {
309            // The candidate still does not make progress for this row. Keep it in the queue so
310            // filterCell can skip the row or getNextCellHint can return a non-seeking hint.
311            break;
312          }
313        }
314      }
315      return !nextRows.isEmpty();
316    }
317
318    boolean lessThan(Cell currentCell, byte[] nextRowKey) {
319      int compareResult =
320        CellComparator.getInstance().compareRows(currentCell, nextRowKey, 0, nextRowKey.length);
321      return (!isReversed() && compareResult < 0) || (isReversed() && compareResult > 0);
322    }
323
324    boolean isNextRowSameAs(Cell currentCell) {
325      if (nextRows.isEmpty()) {
326        return false;
327      }
328      byte[] candidateRowKey = nextRows.peek().getFirst();
329      return CellComparator.getInstance().compareRows(currentCell, candidateRowKey, 0,
330        candidateRowKey.length) == 0;
331    }
332
333    byte[] updateWith(Cell currentCell, Pair<byte[], byte[]> fuzzyData) {
334      // getNextForFuzzyRule needs {-1, 0}: a converted copy on no-unsafe, the stored mask on
335      // unsafe.
336      byte[] fuzzyKeyMeta = preprocessMaskForHinting(fuzzyData.getSecond(), UNSAFE_UNALIGNED);
337      byte[] nextRowKeyCandidate = getNextForFuzzyRule(isReversed(), currentCell.getRowArray(),
338        currentCell.getRowOffset(), currentCell.getRowLength(), fuzzyData.getFirst(), fuzzyKeyMeta);
339      if (nextRowKeyCandidate != null) {
340        nextRows.add(new Pair<>(nextRowKeyCandidate, fuzzyData));
341      }
342      return nextRowKeyCandidate;
343    }
344
345  }
346
347  @Override
348  public boolean filterAllRemaining() {
349    return done;
350  }
351
352  /** Returns The filter serialized using pb */
353  @Override
354  public byte[] toByteArray() {
355    FilterProtos.FuzzyRowFilter.Builder builder = FilterProtos.FuzzyRowFilter.newBuilder();
356    for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
357      BytesBytesPair.Builder bbpBuilder = BytesBytesPair.newBuilder();
358      bbpBuilder.setFirst(UnsafeByteOperations.unsafeWrap(fuzzyData.getFirst()));
359      // Emit the public {0, 1} mask, not the internal form, so the wire is platform-independent.
360      bbpBuilder.setSecond(UnsafeByteOperations
361        .unsafeWrap(toConstructorMask(fuzzyData.getSecond(), UNSAFE_UNALIGNED)));
362      builder.addFuzzyKeysData(bbpBuilder);
363    }
364    return builder.build().toByteArray();
365  }
366
367  /**
368   * Parse a serialized representation of {@link FuzzyRowFilter}
369   * @param pbBytes A pb serialized {@link FuzzyRowFilter} instance
370   * @return An instance of {@link FuzzyRowFilter} made from <code>bytes</code>
371   * @throws DeserializationException if an error occurred
372   * @see #toByteArray
373   */
374  public static FuzzyRowFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
375    FilterProtos.FuzzyRowFilter proto;
376    try {
377      proto = FilterProtos.FuzzyRowFilter.parseFrom(pbBytes);
378    } catch (InvalidProtocolBufferException e) {
379      throw new DeserializationException(e);
380    }
381    int count = proto.getFuzzyKeysDataCount();
382    ArrayList<Pair<byte[], byte[]>> fuzzyKeysData = new ArrayList<>(count);
383    for (int i = 0; i < count; ++i) {
384      BytesBytesPair current = proto.getFuzzyKeysData(i);
385      byte[] keyBytes = current.getFirst().toByteArray();
386      byte[] keyMeta = current.getSecond().toByteArray();
387      fuzzyKeysData.add(new Pair<>(keyBytes, keyMeta));
388    }
389    return new FuzzyRowFilter(fuzzyKeysData);
390  }
391
392  @Override
393  public String toString() {
394    final StringBuilder sb = new StringBuilder();
395    sb.append("FuzzyRowFilter");
396    sb.append("{fuzzyKeysData=");
397    for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
398      sb.append('{').append(Bytes.toStringBinary(fuzzyData.getFirst())).append(":");
399      sb.append(Bytes.toStringBinary(fuzzyData.getSecond())).append('}');
400    }
401    sb.append("}, ");
402    return sb.toString();
403  }
404
405  // Utility methods
406
407  static enum SatisfiesCode {
408    /** row satisfies fuzzy rule */
409    YES,
410    /** row doesn't satisfy fuzzy rule, but there's possible greater row that does */
411    NEXT_EXISTS,
412    /** row doesn't satisfy fuzzy rule and there's no greater row that does */
413    NO_NEXT
414  }
415
416  static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
417    return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
418  }
419
420  static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
421    byte[] fuzzyKeyMeta) {
422    return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
423  }
424
425  static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length,
426    byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
427
428    if (!UNSAFE_UNALIGNED) {
429      return satisfiesNoUnsafe(reverse, row, offset, length, fuzzyKeyBytes, fuzzyKeyMeta);
430    }
431
432    if (row == null) {
433      // do nothing, let scan to proceed
434      return SatisfiesCode.YES;
435    }
436    length = Math.min(length, fuzzyKeyBytes.length);
437    int numWords = length / Bytes.SIZEOF_LONG;
438
439    int j = numWords << 3; // numWords * SIZEOF_LONG;
440
441    for (int i = 0; i < j; i += Bytes.SIZEOF_LONG) {
442      long fuzzyBytes = Bytes.toLong(fuzzyKeyBytes, i);
443      long fuzzyMeta = Bytes.toLong(fuzzyKeyMeta, i);
444      long rowValue = Bytes.toLong(row, offset + i);
445      if ((rowValue & fuzzyMeta) != fuzzyBytes) {
446        // We always return NEXT_EXISTS
447        return SatisfiesCode.NEXT_EXISTS;
448      }
449    }
450
451    int off = j;
452
453    if (length - off >= Bytes.SIZEOF_INT) {
454      int fuzzyBytes = Bytes.toInt(fuzzyKeyBytes, off);
455      int fuzzyMeta = Bytes.toInt(fuzzyKeyMeta, off);
456      int rowValue = Bytes.toInt(row, offset + off);
457      if ((rowValue & fuzzyMeta) != fuzzyBytes) {
458        // We always return NEXT_EXISTS
459        return SatisfiesCode.NEXT_EXISTS;
460      }
461      off += Bytes.SIZEOF_INT;
462    }
463
464    if (length - off >= Bytes.SIZEOF_SHORT) {
465      short fuzzyBytes = Bytes.toShort(fuzzyKeyBytes, off);
466      short fuzzyMeta = Bytes.toShort(fuzzyKeyMeta, off);
467      short rowValue = Bytes.toShort(row, offset + off);
468      if ((rowValue & fuzzyMeta) != fuzzyBytes) {
469        // We always return NEXT_EXISTS
470        // even if it does not (in this case getNextForFuzzyRule
471        // will return null)
472        return SatisfiesCode.NEXT_EXISTS;
473      }
474      off += Bytes.SIZEOF_SHORT;
475    }
476
477    if (length - off >= Bytes.SIZEOF_BYTE) {
478      int fuzzyBytes = fuzzyKeyBytes[off] & 0xff;
479      int fuzzyMeta = fuzzyKeyMeta[off] & 0xff;
480      int rowValue = row[offset + off] & 0xff;
481      if ((rowValue & fuzzyMeta) != fuzzyBytes) {
482        // We always return NEXT_EXISTS
483        return SatisfiesCode.NEXT_EXISTS;
484      }
485    }
486    return SatisfiesCode.YES;
487  }
488
489  /**
490   * Mutates {@code mask} in place into the form {@link #satisfies} expects. Called once from the
491   * constructor so the stored mask is fixed up front and never mutated during scanning. Unsafe:
492   * shift {-1, 2} -&gt; {-1, 0} (the word-based satisfies wants non-fixed = 0). No-unsafe: no-op,
493   * {@link #satisfiesNoUnsafe} already wants {0, 1}.
494   */
495  static void preprocessMaskForSatisfies(byte[] mask, boolean unsafeUnaligned) {
496    if (!unsafeUnaligned) {
497      return;
498    }
499    for (int i = 0; i < mask.length; i++) {
500      mask[i] >>= 2;
501    }
502  }
503
504  /**
505   * Returns the mask in the {-1 (fixed), 0 (non-fixed)} form {@link #getNextForFuzzyRule} expects.
506   * No-unsafe converts {0, 1} into a NEW array (the stored {0, 1} is still needed by
507   * {@link #satisfiesNoUnsafe}); unsafe is already {-1, 0}, returned as is.
508   */
509  static byte[] preprocessMaskForHinting(byte[] mask, boolean unsafeUnaligned) {
510    if (unsafeUnaligned) {
511      return mask;
512    }
513    byte[] converted = Arrays.copyOf(mask, mask.length);
514    for (int i = 0; i < converted.length; i++) {
515      if (converted[i] == 0) {
516        converted[i] = -1;
517      } else if (converted[i] == 1) {
518        converted[i] = 0;
519      }
520    }
521    return converted;
522  }
523
524  static SatisfiesCode satisfiesNoUnsafe(boolean reverse, byte[] row, int offset, int length,
525    byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
526    if (row == null) {
527      // do nothing, let scan to proceed
528      return SatisfiesCode.YES;
529    }
530
531    Order order = Order.orderFor(reverse);
532    boolean nextRowKeyCandidateExists = false;
533
534    for (int i = 0; i < fuzzyKeyMeta.length && i < length; i++) {
535      // First, checking if this position is fixed and not equals the given one
536      boolean byteAtPositionFixed = fuzzyKeyMeta[i] == 0;
537      boolean fixedByteIncorrect = byteAtPositionFixed && fuzzyKeyBytes[i] != row[i + offset];
538      if (fixedByteIncorrect) {
539        // in this case there's another row that satisfies fuzzy rule and bigger than this row
540        if (nextRowKeyCandidateExists) {
541          return SatisfiesCode.NEXT_EXISTS;
542        }
543
544        // If this row byte is less than fixed then there's a byte array bigger than
545        // this row and which satisfies the fuzzy rule. Otherwise there's no such byte array:
546        // this row is simply bigger than any byte array that satisfies the fuzzy rule
547        boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF);
548        if (rowByteLessThanFixed && !reverse) {
549          return SatisfiesCode.NEXT_EXISTS;
550        } else if (!rowByteLessThanFixed && reverse) {
551          return SatisfiesCode.NEXT_EXISTS;
552        } else {
553          return SatisfiesCode.NO_NEXT;
554        }
555      }
556
557      // Second, checking if this position is not fixed and byte value is not the biggest. In this
558      // case there's a byte array bigger than this row and which satisfies the fuzzy rule. To get
559      // bigger byte array that satisfies the rule we need to just increase this byte
560      // (see the code of getNextForFuzzyRule below) by one.
561      // Note: if non-fixed byte is already at biggest value, this doesn't allow us to say there's
562      // bigger one that satisfies the rule as it can't be increased.
563      if (fuzzyKeyMeta[i] == 1 && !order.isMax(fuzzyKeyBytes[i])) {
564        nextRowKeyCandidateExists = true;
565      }
566    }
567    return SatisfiesCode.YES;
568  }
569
570  static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
571    return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
572  }
573
574  static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
575    byte[] fuzzyKeyMeta) {
576    return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
577  }
578
579  /** Abstracts directional comparisons based on scan direction. */
580  private enum Order {
581    ASC {
582      @Override
583      public boolean lt(int lhs, int rhs) {
584        return lhs < rhs;
585      }
586
587      @Override
588      public boolean gt(int lhs, int rhs) {
589        return lhs > rhs;
590      }
591
592      @Override
593      public byte inc(byte val) {
594        // TODO: what about over/underflow?
595        return (byte) (val + 1);
596      }
597
598      @Override
599      public boolean isMax(byte val) {
600        return val == (byte) 0xff;
601      }
602
603      @Override
604      public byte min() {
605        return 0;
606      }
607    },
608    DESC {
609      @Override
610      public boolean lt(int lhs, int rhs) {
611        return lhs > rhs;
612      }
613
614      @Override
615      public boolean gt(int lhs, int rhs) {
616        return lhs < rhs;
617      }
618
619      @Override
620      public byte inc(byte val) {
621        // TODO: what about over/underflow?
622        return (byte) (val - 1);
623      }
624
625      @Override
626      public boolean isMax(byte val) {
627        return val == 0;
628      }
629
630      @Override
631      public byte min() {
632        return (byte) 0xFF;
633      }
634    };
635
636    public static Order orderFor(boolean reverse) {
637      return reverse ? DESC : ASC;
638    }
639
640    /** Returns true when {@code lhs < rhs}. */
641    public abstract boolean lt(int lhs, int rhs);
642
643    /** Returns true when {@code lhs > rhs}. */
644    public abstract boolean gt(int lhs, int rhs);
645
646    /** Returns {@code val} incremented by 1. */
647    public abstract byte inc(byte val);
648
649    /** Return true when {@code val} is the maximum value */
650    public abstract boolean isMax(byte val);
651
652    /** Return the minimum value according to this ordering scheme. */
653    public abstract byte min();
654  }
655
656  /**
657   * Find out the closes next byte array that satisfies fuzzy rule and is after the given one. In
658   * the reverse case it returns increased byte array to make sure that the proper row is selected
659   * next.
660   * @return byte array which is after the given row and which satisfies the fuzzy rule if it
661   *         exists, null otherwise
662   */
663  static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int length,
664    byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
665    // To find out the closest next byte array that satisfies fuzzy rule and is after the given one
666    // we do the following:
667    // 1. setting values on all "fixed" positions to the values from fuzzyKeyBytes
668    // 2. if during the first step given row did not increase, then we increase the value at
669    // the first "non-fixed" position (where it is not maximum already)
670
671    // It is easier to perform this by using fuzzyKeyBytes copy and setting "non-fixed" position
672    // values than otherwise.
673    byte[] result = Arrays.copyOf(fuzzyKeyBytes, Math.max(length, fuzzyKeyBytes.length));
674    if (reverse) {
675      // we need 0xff's instead of 0x00's
676      for (int i = 0; i < result.length; i++) {
677        if (result[i] == 0) {
678          result[i] = (byte) 0xFF;
679        }
680      }
681    }
682    int toInc = -1;
683    final Order order = Order.orderFor(reverse);
684
685    boolean increased = false;
686    for (int i = 0; i < result.length; i++) {
687      if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
688        result[i] = row[offset + i];
689        if (!order.isMax(row[offset + i])) {
690          // this is "non-fixed" position and is not at max value, hence we can increase it
691          toInc = i;
692        }
693      } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == -1 /* fixed */) {
694        if (order.lt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
695          // if setting value for any fixed position increased the original array,
696          // we are OK
697          increased = true;
698          break;
699        }
700
701        if (order.gt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
702          // if setting value for any fixed position makes array "smaller", then just stop:
703          // in case we found some non-fixed position to increase we will do it, otherwise
704          // there's no "next" row key that satisfies fuzzy rule and "greater" than given row
705          break;
706        }
707      }
708    }
709
710    if (!increased) {
711      if (toInc < 0) {
712        return null;
713      }
714      result[toInc] = order.inc(result[toInc]);
715
716      // Setting all "non-fixed" positions to zeroes to the right of the one we increased so
717      // that found "next" row key is the smallest possible
718      for (int i = toInc + 1; i < result.length; i++) {
719        if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) {
720          result[i] = order.min();
721        }
722      }
723    }
724
725    byte[] trailingZerosTrimmed = trimTrailingZeroes(result, fuzzyKeyMeta, toInc);
726    if (reverse) {
727      // In the reverse case we increase last non-max byte to make sure that the proper row is
728      // selected next.
729      return PrivateCellUtil.increaseLastNonMaxByte(trailingZerosTrimmed);
730    } else {
731      return trailingZerosTrimmed;
732    }
733  }
734
735  /**
736   * For forward scanner, next cell hint should not contain any trailing zeroes unless they are part
737   * of fuzzyKeyMeta hint = '\x01\x01\x01\x00\x00' will skip valid row '\x01\x01\x01'
738   * @param toInc - position of incremented byte
739   * @return trimmed version of result
740   */
741
742  private static byte[] trimTrailingZeroes(byte[] result, byte[] fuzzyKeyMeta, int toInc) {
743    int off = fuzzyKeyMeta.length >= result.length ? result.length - 1 : fuzzyKeyMeta.length - 1;
744    for (; off >= 0; off--) {
745      if (fuzzyKeyMeta[off] != 0) break;
746    }
747    if (off < toInc) off = toInc;
748    byte[] retValue = new byte[off + 1];
749    System.arraycopy(result, 0, retValue, 0, retValue.length);
750    return retValue;
751  }
752
753  /**
754   * Returns true if and only if the fields of the filter that are serialized are equal to the
755   * corresponding fields in other. Used for testing.
756   */
757  @Override
758  boolean areSerializedFieldsEqual(Filter o) {
759    if (o == this) {
760      return true;
761    }
762    if (!(o instanceof FuzzyRowFilter)) {
763      return false;
764    }
765    FuzzyRowFilter other = (FuzzyRowFilter) o;
766    if (this.fuzzyKeysData.size() != other.fuzzyKeysData.size()) return false;
767    for (int i = 0; i < fuzzyKeysData.size(); ++i) {
768      Pair<byte[], byte[]> thisData = this.fuzzyKeysData.get(i);
769      Pair<byte[], byte[]> otherData = other.fuzzyKeysData.get(i);
770      // Compare masks in the normalized {0, 1} form, so equality matches the serialized bytes.
771      if (
772        !(Bytes.equals(thisData.getFirst(), otherData.getFirst())
773          && Bytes.equals(toConstructorMask(thisData.getSecond(), UNSAFE_UNALIGNED),
774            toConstructorMask(otherData.getSecond(), UNSAFE_UNALIGNED)))
775      ) {
776        return false;
777      }
778    }
779    return true;
780  }
781
782  @Override
783  public boolean equals(Object obj) {
784    return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj);
785  }
786
787  @Override
788  public int hashCode() {
789    int result = 1;
790    for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
791      result = 31 * result + Bytes.hashCode(fuzzyData.getFirst());
792      result =
793        31 * result + Bytes.hashCode(toConstructorMask(fuzzyData.getSecond(), UNSAFE_UNALIGNED));
794    }
795    return result;
796  }
797}