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} -> {-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}