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}