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.Objects; 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: 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 062 private static final boolean UNSAFE_UNALIGNED = HBasePlatformDependent.unaligned(); 063 064 // the wildcard byte is 1 on the user side. but the filter converts it internally 065 // in preprocessMask. This was changed in HBASE-15676 due to a bug with using 0. 066 // in v1, the 1 byte gets converted to 0 067 // in v2, the 1 byte gets converted to 2. 068 // we support both here to ensure backwards compatibility between client and server 069 static final byte V1_PROCESSED_WILDCARD_MASK = 0; 070 static final byte V2_PROCESSED_WILDCARD_MASK = 2; 071 072 private final byte processedWildcardMask; 073 private List<Pair<byte[], byte[]>> fuzzyKeysData; 074 private boolean done = false; 075 076 /** 077 * The index of a last successfully found matching fuzzy string (in fuzzyKeysData). We will start 078 * matching next KV with this one. If they do not match then we will return back to the one-by-one 079 * iteration over fuzzyKeysData. 080 */ 081 private int lastFoundIndex = -1; 082 083 /** 084 * Row tracker (keeps all next rows after SEEK_NEXT_USING_HINT was returned) 085 */ 086 private RowTracker tracker; 087 088 // this client side constructor ensures that all client-constructed 089 // FuzzyRowFilters use the new v2 mask. 090 public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) { 091 this(fuzzyKeysData, V2_PROCESSED_WILDCARD_MASK); 092 } 093 094 // This constructor is only used internally here, when parsing from protos on the server side. 095 // It exists to enable seamless migration from v1 to v2. 096 // Additionally used in tests, but never used on client side. 097 FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData, byte processedWildcardMask) { 098 this.processedWildcardMask = processedWildcardMask; 099 100 List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new ArrayList<>(fuzzyKeysData.size()); 101 102 for (Pair<byte[], byte[]> aFuzzyKeysData : fuzzyKeysData) { 103 if (aFuzzyKeysData.getFirst().length != aFuzzyKeysData.getSecond().length) { 104 Pair<String, String> readable = new Pair<>(Bytes.toStringBinary(aFuzzyKeysData.getFirst()), 105 Bytes.toStringBinary(aFuzzyKeysData.getSecond())); 106 throw new IllegalArgumentException("Fuzzy pair lengths do not match: " + readable); 107 } 108 109 Pair<byte[], byte[]> p = new Pair<>(); 110 // create a copy of pair bytes so that they are not modified by the filter. 111 p.setFirst(Arrays.copyOf(aFuzzyKeysData.getFirst(), aFuzzyKeysData.getFirst().length)); 112 p.setSecond(Arrays.copyOf(aFuzzyKeysData.getSecond(), aFuzzyKeysData.getSecond().length)); 113 114 // update mask ( 0 -> -1 (0xff), 1 -> [0 or 2 depending on processedWildcardMask value]) 115 p.setSecond(preprocessMask(p.getSecond())); 116 preprocessSearchKey(p); 117 118 fuzzyKeyDataCopy.add(p); 119 } 120 this.fuzzyKeysData = fuzzyKeyDataCopy; 121 this.tracker = new RowTracker(); 122 } 123 124 private void preprocessSearchKey(Pair<byte[], byte[]> p) { 125 if (!UNSAFE_UNALIGNED) { 126 // do nothing 127 return; 128 } 129 byte[] key = p.getFirst(); 130 byte[] mask = p.getSecond(); 131 for (int i = 0; i < mask.length; i++) { 132 // set non-fixed part of a search key to 0. 133 if (mask[i] == processedWildcardMask) { 134 key[i] = 0; 135 } 136 } 137 } 138 139 /** 140 * We need to preprocess mask array, as since we treat 2's as unfixed positions and -1 (0xff) as 141 * fixed positions 142 * @return mask array 143 */ 144 private byte[] preprocessMask(byte[] mask) { 145 if (!UNSAFE_UNALIGNED) { 146 // do nothing 147 return mask; 148 } 149 if (isPreprocessedMask(mask)) return mask; 150 for (int i = 0; i < mask.length; i++) { 151 if (mask[i] == 0) { 152 mask[i] = -1; // 0 -> -1 153 } else if (mask[i] == 1) { 154 mask[i] = processedWildcardMask;// 1 -> 0 or 2 depending on mask version 155 } 156 } 157 return mask; 158 } 159 160 private boolean isPreprocessedMask(byte[] mask) { 161 for (int i = 0; i < mask.length; i++) { 162 if (mask[i] != -1 && mask[i] != processedWildcardMask) { 163 return false; 164 } 165 } 166 return true; 167 } 168 169 @Deprecated 170 @Override 171 public ReturnCode filterKeyValue(final Cell c) { 172 return filterCell(c); 173 } 174 175 @Override 176 public ReturnCode filterCell(final Cell c) { 177 final int startIndex = lastFoundIndex >= 0 ? lastFoundIndex : 0; 178 final int size = fuzzyKeysData.size(); 179 for (int i = startIndex; i < size + startIndex; i++) { 180 final int index = i % size; 181 Pair<byte[], byte[]> fuzzyData = fuzzyKeysData.get(index); 182 idempotentMaskShift(fuzzyData.getSecond()); 183 SatisfiesCode satisfiesCode = satisfies(isReversed(), c.getRowArray(), c.getRowOffset(), 184 c.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond()); 185 if (satisfiesCode == SatisfiesCode.YES) { 186 lastFoundIndex = index; 187 return ReturnCode.INCLUDE; 188 } 189 } 190 // NOT FOUND -> seek next using hint 191 lastFoundIndex = -1; 192 193 return ReturnCode.SEEK_NEXT_USING_HINT; 194 } 195 196 static void idempotentMaskShift(byte[] mask) { 197 // This shift is idempotent - always end up with 0 and -1 as mask values. 198 // This works regardless of mask version, because both 0 >> 2 and 2 >> 2 199 // result in 0. 200 for (int j = 0; j < mask.length; j++) { 201 mask[j] >>= 2; 202 } 203 } 204 205 @Override 206 public Cell getNextCellHint(Cell currentCell) { 207 boolean result = tracker.updateTracker(currentCell); 208 if (result == false) { 209 done = true; 210 return null; 211 } 212 byte[] nextRowKey = tracker.nextRow(); 213 return PrivateCellUtil.createFirstOnRow(nextRowKey, 0, (short) nextRowKey.length); 214 } 215 216 /** 217 * If we have multiple fuzzy keys, row tracker should improve overall performance. It calculates 218 * all next rows (one per every fuzzy key) and put them (the fuzzy key is bundled) into a priority 219 * queue so that the smallest row key always appears at queue head, which helps to decide the 220 * "Next Cell Hint". As scanning going on, the number of candidate rows in the RowTracker will 221 * remain the size of fuzzy keys until some of the fuzzy keys won't possibly have matches any 222 * more. 223 */ 224 private class RowTracker { 225 private final PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>> nextRows; 226 private boolean initialized = false; 227 228 RowTracker() { 229 nextRows = new PriorityQueue<>(fuzzyKeysData.size(), 230 new Comparator<Pair<byte[], Pair<byte[], byte[]>>>() { 231 @Override 232 public int compare(Pair<byte[], Pair<byte[], byte[]>> o1, 233 Pair<byte[], Pair<byte[], byte[]>> o2) { 234 return isReversed() 235 ? Bytes.compareTo(o2.getFirst(), o1.getFirst()) 236 : Bytes.compareTo(o1.getFirst(), o2.getFirst()); 237 } 238 }); 239 } 240 241 byte[] nextRow() { 242 if (nextRows.isEmpty()) { 243 throw new IllegalStateException("NextRows should not be empty, " 244 + "make sure to call nextRow() after updateTracker() return true"); 245 } else { 246 return nextRows.peek().getFirst(); 247 } 248 } 249 250 boolean updateTracker(Cell currentCell) { 251 if (!initialized) { 252 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) { 253 updateWith(currentCell, fuzzyData); 254 } 255 initialized = true; 256 } else { 257 while (!nextRows.isEmpty() && !lessThan(currentCell, nextRows.peek().getFirst())) { 258 Pair<byte[], Pair<byte[], byte[]>> head = nextRows.poll(); 259 Pair<byte[], byte[]> fuzzyData = head.getSecond(); 260 updateWith(currentCell, fuzzyData); 261 } 262 } 263 return !nextRows.isEmpty(); 264 } 265 266 boolean lessThan(Cell currentCell, byte[] nextRowKey) { 267 int compareResult = 268 CellComparator.getInstance().compareRows(currentCell, nextRowKey, 0, nextRowKey.length); 269 return (!isReversed() && compareResult < 0) || (isReversed() && compareResult > 0); 270 } 271 272 void updateWith(Cell currentCell, Pair<byte[], byte[]> fuzzyData) { 273 byte[] nextRowKeyCandidate = 274 getNextForFuzzyRule(isReversed(), currentCell.getRowArray(), currentCell.getRowOffset(), 275 currentCell.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond()); 276 if (nextRowKeyCandidate != null) { 277 nextRows.add(new Pair<>(nextRowKeyCandidate, fuzzyData)); 278 } 279 } 280 281 } 282 283 @Override 284 public boolean filterAllRemaining() { 285 return done; 286 } 287 288 /** Returns The filter serialized using pb */ 289 @Override 290 public byte[] toByteArray() { 291 FilterProtos.FuzzyRowFilter.Builder builder = FilterProtos.FuzzyRowFilter.newBuilder() 292 .setIsMaskV2(processedWildcardMask == V2_PROCESSED_WILDCARD_MASK); 293 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) { 294 BytesBytesPair.Builder bbpBuilder = BytesBytesPair.newBuilder(); 295 bbpBuilder.setFirst(UnsafeByteOperations.unsafeWrap(fuzzyData.getFirst())); 296 bbpBuilder.setSecond(UnsafeByteOperations.unsafeWrap(fuzzyData.getSecond())); 297 builder.addFuzzyKeysData(bbpBuilder); 298 } 299 return builder.build().toByteArray(); 300 } 301 302 /** 303 * @param pbBytes A pb serialized {@link FuzzyRowFilter} instance 304 * @return An instance of {@link FuzzyRowFilter} made from <code>bytes</code> 305 * @see #toByteArray 306 */ 307 public static FuzzyRowFilter parseFrom(final byte[] pbBytes) throws DeserializationException { 308 FilterProtos.FuzzyRowFilter proto; 309 try { 310 proto = FilterProtos.FuzzyRowFilter.parseFrom(pbBytes); 311 } catch (InvalidProtocolBufferException e) { 312 throw new DeserializationException(e); 313 } 314 int count = proto.getFuzzyKeysDataCount(); 315 ArrayList<Pair<byte[], byte[]>> fuzzyKeysData = new ArrayList<>(count); 316 for (int i = 0; i < count; ++i) { 317 BytesBytesPair current = proto.getFuzzyKeysData(i); 318 byte[] keyBytes = current.getFirst().toByteArray(); 319 byte[] keyMeta = current.getSecond().toByteArray(); 320 fuzzyKeysData.add(new Pair<>(keyBytes, keyMeta)); 321 } 322 byte processedWildcardMask = proto.hasIsMaskV2() && proto.getIsMaskV2() 323 ? V2_PROCESSED_WILDCARD_MASK 324 : V1_PROCESSED_WILDCARD_MASK; 325 return new FuzzyRowFilter(fuzzyKeysData, processedWildcardMask); 326 } 327 328 @Override 329 public String toString() { 330 final StringBuilder sb = new StringBuilder(); 331 sb.append("FuzzyRowFilter"); 332 sb.append("{fuzzyKeysData="); 333 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) { 334 sb.append('{').append(Bytes.toStringBinary(fuzzyData.getFirst())).append(":"); 335 sb.append(Bytes.toStringBinary(fuzzyData.getSecond())).append('}'); 336 } 337 sb.append("}, "); 338 return sb.toString(); 339 } 340 341 // Utility methods 342 343 static enum SatisfiesCode { 344 /** row satisfies fuzzy rule */ 345 YES, 346 /** row doesn't satisfy fuzzy rule, but there's possible greater row that does */ 347 NEXT_EXISTS, 348 /** row doesn't satisfy fuzzy rule and there's no greater row that does */ 349 NO_NEXT 350 } 351 352 @InterfaceAudience.Private 353 static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { 354 return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); 355 } 356 357 @InterfaceAudience.Private 358 static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes, 359 byte[] fuzzyKeyMeta) { 360 return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); 361 } 362 363 static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length, 364 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { 365 366 if (!UNSAFE_UNALIGNED) { 367 return satisfiesNoUnsafe(reverse, row, offset, length, fuzzyKeyBytes, fuzzyKeyMeta); 368 } 369 370 if (row == null) { 371 // do nothing, let scan to proceed 372 return SatisfiesCode.YES; 373 } 374 length = Math.min(length, fuzzyKeyBytes.length); 375 int numWords = length / Bytes.SIZEOF_LONG; 376 377 int j = numWords << 3; // numWords * SIZEOF_LONG; 378 379 for (int i = 0; i < j; i += Bytes.SIZEOF_LONG) { 380 long fuzzyBytes = Bytes.toLong(fuzzyKeyBytes, i); 381 long fuzzyMeta = Bytes.toLong(fuzzyKeyMeta, i); 382 long rowValue = Bytes.toLong(row, offset + i); 383 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) { 384 // We always return NEXT_EXISTS 385 return SatisfiesCode.NEXT_EXISTS; 386 } 387 } 388 389 int off = j; 390 391 if (length - off >= Bytes.SIZEOF_INT) { 392 int fuzzyBytes = Bytes.toInt(fuzzyKeyBytes, off); 393 int fuzzyMeta = Bytes.toInt(fuzzyKeyMeta, off); 394 int rowValue = Bytes.toInt(row, offset + off); 395 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) { 396 // We always return NEXT_EXISTS 397 return SatisfiesCode.NEXT_EXISTS; 398 } 399 off += Bytes.SIZEOF_INT; 400 } 401 402 if (length - off >= Bytes.SIZEOF_SHORT) { 403 short fuzzyBytes = Bytes.toShort(fuzzyKeyBytes, off); 404 short fuzzyMeta = Bytes.toShort(fuzzyKeyMeta, off); 405 short rowValue = Bytes.toShort(row, offset + off); 406 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) { 407 // We always return NEXT_EXISTS 408 // even if it does not (in this case getNextForFuzzyRule 409 // will return null) 410 return SatisfiesCode.NEXT_EXISTS; 411 } 412 off += Bytes.SIZEOF_SHORT; 413 } 414 415 if (length - off >= Bytes.SIZEOF_BYTE) { 416 int fuzzyBytes = fuzzyKeyBytes[off] & 0xff; 417 int fuzzyMeta = fuzzyKeyMeta[off] & 0xff; 418 int rowValue = row[offset + off] & 0xff; 419 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) { 420 // We always return NEXT_EXISTS 421 return SatisfiesCode.NEXT_EXISTS; 422 } 423 } 424 return SatisfiesCode.YES; 425 } 426 427 static SatisfiesCode satisfiesNoUnsafe(boolean reverse, byte[] row, int offset, int length, 428 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { 429 if (row == null) { 430 // do nothing, let scan to proceed 431 return SatisfiesCode.YES; 432 } 433 434 Order order = Order.orderFor(reverse); 435 boolean nextRowKeyCandidateExists = false; 436 437 for (int i = 0; i < fuzzyKeyMeta.length && i < length; i++) { 438 // First, checking if this position is fixed and not equals the given one 439 boolean byteAtPositionFixed = fuzzyKeyMeta[i] == 0; 440 boolean fixedByteIncorrect = byteAtPositionFixed && fuzzyKeyBytes[i] != row[i + offset]; 441 if (fixedByteIncorrect) { 442 // in this case there's another row that satisfies fuzzy rule and bigger than this row 443 if (nextRowKeyCandidateExists) { 444 return SatisfiesCode.NEXT_EXISTS; 445 } 446 447 // If this row byte is less than fixed then there's a byte array bigger than 448 // this row and which satisfies the fuzzy rule. Otherwise there's no such byte array: 449 // this row is simply bigger than any byte array that satisfies the fuzzy rule 450 boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF); 451 if (rowByteLessThanFixed && !reverse) { 452 return SatisfiesCode.NEXT_EXISTS; 453 } else if (!rowByteLessThanFixed && reverse) { 454 return SatisfiesCode.NEXT_EXISTS; 455 } else { 456 return SatisfiesCode.NO_NEXT; 457 } 458 } 459 460 // Second, checking if this position is not fixed and byte value is not the biggest. In this 461 // case there's a byte array bigger than this row and which satisfies the fuzzy rule. To get 462 // bigger byte array that satisfies the rule we need to just increase this byte 463 // (see the code of getNextForFuzzyRule below) by one. 464 // Note: if non-fixed byte is already at biggest value, this doesn't allow us to say there's 465 // bigger one that satisfies the rule as it can't be increased. 466 if (fuzzyKeyMeta[i] == 1 && !order.isMax(fuzzyKeyBytes[i])) { 467 nextRowKeyCandidateExists = true; 468 } 469 } 470 return SatisfiesCode.YES; 471 } 472 473 @InterfaceAudience.Private 474 static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { 475 return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); 476 } 477 478 @InterfaceAudience.Private 479 static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes, 480 byte[] fuzzyKeyMeta) { 481 return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta); 482 } 483 484 /** Abstracts directional comparisons based on scan direction. */ 485 private enum Order { 486 ASC { 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 == (byte) 0xff; 506 } 507 508 @Override 509 public byte min() { 510 return 0; 511 } 512 }, 513 DESC { 514 @Override 515 public boolean lt(int lhs, int rhs) { 516 return lhs > rhs; 517 } 518 519 @Override 520 public boolean gt(int lhs, int rhs) { 521 return lhs < rhs; 522 } 523 524 @Override 525 public byte inc(byte val) { 526 // TODO: what about over/underflow? 527 return (byte) (val - 1); 528 } 529 530 @Override 531 public boolean isMax(byte val) { 532 return val == 0; 533 } 534 535 @Override 536 public byte min() { 537 return (byte) 0xFF; 538 } 539 }; 540 541 public static Order orderFor(boolean reverse) { 542 return reverse ? DESC : ASC; 543 } 544 545 /** Returns true when {@code lhs < rhs}. */ 546 public abstract boolean lt(int lhs, int rhs); 547 548 /** Returns true when {@code lhs > rhs}. */ 549 public abstract boolean gt(int lhs, int rhs); 550 551 /** Returns {@code val} incremented by 1. */ 552 public abstract byte inc(byte val); 553 554 /** Return true when {@code val} is the maximum value */ 555 public abstract boolean isMax(byte val); 556 557 /** Return the minimum value according to this ordering scheme. */ 558 public abstract byte min(); 559 } 560 561 /** 562 * @return greater byte array than given (row) which satisfies the fuzzy rule if it exists, null 563 * otherwise 564 */ 565 @InterfaceAudience.Private 566 static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int length, 567 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) { 568 // To find out the next "smallest" byte array that satisfies fuzzy rule and "greater" than 569 // the given one we do the following: 570 // 1. setting values on all "fixed" positions to the values from fuzzyKeyBytes 571 // 2. if during the first step given row did not increase, then we increase the value at 572 // the first "non-fixed" position (where it is not maximum already) 573 574 // It is easier to perform this by using fuzzyKeyBytes copy and setting "non-fixed" position 575 // values than otherwise. 576 byte[] result = 577 Arrays.copyOf(fuzzyKeyBytes, length > fuzzyKeyBytes.length ? length : fuzzyKeyBytes.length); 578 if (reverse && length > fuzzyKeyBytes.length) { 579 // we need trailing 0xff's instead of trailing 0x00's 580 for (int i = fuzzyKeyBytes.length; i < result.length; i++) { 581 result[i] = (byte) 0xFF; 582 } 583 } 584 int toInc = -1; 585 final Order order = Order.orderFor(reverse); 586 587 boolean increased = false; 588 for (int i = 0; i < result.length; i++) { 589 if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) { 590 result[i] = row[offset + i]; 591 if (!order.isMax(row[offset + i])) { 592 // this is "non-fixed" position and is not at max value, hence we can increase it 593 toInc = i; 594 } 595 } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == -1 /* fixed */) { 596 if (order.lt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) { 597 // if setting value for any fixed position increased the original array, 598 // we are OK 599 increased = true; 600 break; 601 } 602 603 if (order.gt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) { 604 // if setting value for any fixed position makes array "smaller", then just stop: 605 // in case we found some non-fixed position to increase we will do it, otherwise 606 // there's no "next" row key that satisfies fuzzy rule and "greater" than given row 607 break; 608 } 609 } 610 } 611 612 if (!increased) { 613 if (toInc < 0) { 614 return null; 615 } 616 result[toInc] = order.inc(result[toInc]); 617 618 // Setting all "non-fixed" positions to zeroes to the right of the one we increased so 619 // that found "next" row key is the smallest possible 620 for (int i = toInc + 1; i < result.length; i++) { 621 if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0 /* non-fixed */) { 622 result[i] = order.min(); 623 } 624 } 625 } 626 627 return reverse ? result : trimTrailingZeroes(result, fuzzyKeyMeta, toInc); 628 } 629 630 /** 631 * For forward scanner, next cell hint should not contain any trailing zeroes unless they are part 632 * of fuzzyKeyMeta hint = '\x01\x01\x01\x00\x00' will skip valid row '\x01\x01\x01' 633 * @param toInc - position of incremented byte 634 * @return trimmed version of result 635 */ 636 637 private static byte[] trimTrailingZeroes(byte[] result, byte[] fuzzyKeyMeta, int toInc) { 638 int off = fuzzyKeyMeta.length >= result.length ? result.length - 1 : fuzzyKeyMeta.length - 1; 639 for (; off >= 0; off--) { 640 if (fuzzyKeyMeta[off] != 0) break; 641 } 642 if (off < toInc) off = toInc; 643 byte[] retValue = new byte[off + 1]; 644 System.arraycopy(result, 0, retValue, 0, retValue.length); 645 return retValue; 646 } 647 648 /** 649 * @return true if and only if the fields of the filter that are serialized are equal to the 650 * corresponding fields in other. Used for testing. 651 */ 652 @Override 653 boolean areSerializedFieldsEqual(Filter o) { 654 if (o == this) return true; 655 if (!(o instanceof FuzzyRowFilter)) return false; 656 657 FuzzyRowFilter other = (FuzzyRowFilter) o; 658 if (this.fuzzyKeysData.size() != other.fuzzyKeysData.size()) return false; 659 for (int i = 0; i < fuzzyKeysData.size(); ++i) { 660 Pair<byte[], byte[]> thisData = this.fuzzyKeysData.get(i); 661 Pair<byte[], byte[]> otherData = other.fuzzyKeysData.get(i); 662 if ( 663 !(Bytes.equals(thisData.getFirst(), otherData.getFirst()) 664 && Bytes.equals(thisData.getSecond(), otherData.getSecond())) 665 ) { 666 return false; 667 } 668 } 669 return true; 670 } 671 672 @Override 673 public boolean equals(Object obj) { 674 return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj); 675 } 676 677 @Override 678 public int hashCode() { 679 return Objects.hash(this.fuzzyKeysData); 680 } 681}