1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.filter;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.PriorityQueue;
25
26 import org.apache.hadoop.hbase.Cell;
27 import org.apache.hadoop.hbase.KeyValueUtil;
28 import org.apache.hadoop.hbase.classification.InterfaceAudience;
29 import org.apache.hadoop.hbase.classification.InterfaceStability;
30 import org.apache.hadoop.hbase.exceptions.DeserializationException;
31 import org.apache.hadoop.hbase.protobuf.generated.FilterProtos;
32 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.BytesBytesPair;
33 import org.apache.hadoop.hbase.util.ByteStringer;
34 import org.apache.hadoop.hbase.util.Bytes;
35 import org.apache.hadoop.hbase.util.Pair;
36 import org.apache.hadoop.hbase.util.UnsafeAccess;
37 import org.apache.hadoop.hbase.util.UnsafeAvailChecker;
38
39 import com.google.common.annotations.VisibleForTesting;
40 import com.google.protobuf.InvalidProtocolBufferException;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 @InterfaceAudience.Public
61 @InterfaceStability.Evolving
62 public class FuzzyRowFilter extends FilterBase {
63 private static final boolean UNSAFE_UNALIGNED = UnsafeAvailChecker.unaligned();
64 private List<Pair<byte[], byte[]>> fuzzyKeysData;
65 private boolean done = false;
66
67
68
69
70
71
72 private int lastFoundIndex = -1;
73
74
75
76
77 private RowTracker tracker;
78
79 public FuzzyRowFilter(List<Pair<byte[], byte[]>> fuzzyKeysData) {
80 List<Pair<byte[], byte[]>> fuzzyKeyDataCopy = new ArrayList<>(fuzzyKeysData.size());
81
82 for (Pair<byte[], byte[]> aFuzzyKeysData : fuzzyKeysData) {
83 if (aFuzzyKeysData.getFirst().length != aFuzzyKeysData.getSecond().length) {
84 Pair<String, String> readable =
85 new Pair<>(Bytes.toStringBinary(aFuzzyKeysData.getFirst()), Bytes.toStringBinary(aFuzzyKeysData.getSecond()));
86 throw new IllegalArgumentException("Fuzzy pair lengths do not match: " + readable);
87 }
88
89 Pair<byte[], byte[]> p = new Pair<>();
90
91 p.setFirst(Arrays.copyOf(aFuzzyKeysData.getFirst(), aFuzzyKeysData.getFirst().length));
92 p.setSecond(Arrays.copyOf(aFuzzyKeysData.getSecond(), aFuzzyKeysData.getSecond().length));
93
94
95 p.setSecond(preprocessMask(p.getSecond()));
96 preprocessSearchKey(p);
97
98 fuzzyKeyDataCopy.add(p);
99 }
100 this.fuzzyKeysData = fuzzyKeyDataCopy;
101 this.tracker = new RowTracker();
102 }
103
104 private void preprocessSearchKey(Pair<byte[], byte[]> p) {
105 if (!UNSAFE_UNALIGNED) {
106 return;
107 }
108 byte[] key = p.getFirst();
109 byte[] mask = p.getSecond();
110 for (int i = 0; i < mask.length; i++) {
111
112 if (mask[i] == 2) {
113 key[i] = 0;
114 }
115 }
116 }
117
118
119
120
121
122
123
124 private byte[] preprocessMask(byte[] mask) {
125 if (!UNSAFE_UNALIGNED) {
126 return mask;
127 }
128 if (isPreprocessedMask(mask)) return mask;
129 for (int i = 0; i < mask.length; i++) {
130 if (mask[i] == 0) {
131 mask[i] = -1;
132 } else if (mask[i] == 1) {
133 mask[i] = 2;
134 }
135 }
136 return mask;
137 }
138
139 private boolean isPreprocessedMask(byte[] mask) {
140 for (int i = 0; i < mask.length; i++) {
141 if (mask[i] != -1 && mask[i] != 2) {
142 return false;
143 }
144 }
145 return true;
146 }
147
148 @Override
149 public ReturnCode filterKeyValue(Cell c) {
150 final int startIndex = lastFoundIndex >= 0 ? lastFoundIndex : 0;
151 final int size = fuzzyKeysData.size();
152 for (int i = startIndex; i < size + startIndex; i++) {
153 final int index = i % size;
154 Pair<byte[], byte[]> fuzzyData = fuzzyKeysData.get(index);
155
156 for (int j = 0; j < fuzzyData.getSecond().length; j++) {
157 fuzzyData.getSecond()[j] >>= 2;
158 }
159 SatisfiesCode satisfiesCode =
160 satisfies(isReversed(), c.getRowArray(), c.getRowOffset(), c.getRowLength(),
161 fuzzyData.getFirst(), fuzzyData.getSecond());
162 if (satisfiesCode == SatisfiesCode.YES) {
163 lastFoundIndex = index;
164 return ReturnCode.INCLUDE;
165 }
166 }
167
168 lastFoundIndex = -1;
169
170 return ReturnCode.SEEK_NEXT_USING_HINT;
171
172 }
173
174 @Override
175 public Cell getNextCellHint(Cell currentCell) {
176 boolean result = tracker.updateTracker(currentCell);
177 if (result == false) {
178 done = true;
179 return null;
180 }
181 byte[] nextRowKey = tracker.nextRow();
182 return KeyValueUtil.createFirstOnRow(nextRowKey);
183 }
184
185
186
187
188
189
190
191
192
193 private class RowTracker {
194 private final PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>> nextRows;
195 private boolean initialized = false;
196
197 RowTracker() {
198 nextRows =
199 new PriorityQueue<Pair<byte[], Pair<byte[], byte[]>>>(fuzzyKeysData.size(),
200 new Comparator<Pair<byte[], Pair<byte[], byte[]>>>() {
201 @Override
202 public int compare(Pair<byte[], Pair<byte[], byte[]>> o1,
203 Pair<byte[], Pair<byte[], byte[]>> o2) {
204 return isReversed()? Bytes.compareTo(o2.getFirst(), o1.getFirst()):
205 Bytes.compareTo(o1.getFirst(), o2.getFirst());
206 }
207 });
208 }
209
210 byte[] nextRow() {
211 if (nextRows.isEmpty()) {
212 throw new IllegalStateException(
213 "NextRows should not be empty, make sure to call nextRow() after updateTracker() return true");
214 } else {
215 return nextRows.peek().getFirst();
216 }
217 }
218
219 boolean updateTracker(Cell currentCell) {
220 if (!initialized) {
221 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
222 updateWith(currentCell, fuzzyData);
223 }
224 initialized = true;
225 } else {
226 while (!nextRows.isEmpty() && !lessThan(currentCell, nextRows.peek().getFirst())) {
227 Pair<byte[], Pair<byte[], byte[]>> head = nextRows.poll();
228 Pair<byte[], byte[]> fuzzyData = head.getSecond();
229 updateWith(currentCell, fuzzyData);
230 }
231 }
232 return !nextRows.isEmpty();
233 }
234
235 boolean lessThan(Cell currentCell, byte[] nextRowKey) {
236 int compareResult =
237 Bytes.compareTo(currentCell.getRowArray(), currentCell.getRowOffset(),
238 currentCell.getRowLength(), nextRowKey, 0, nextRowKey.length);
239 return (!isReversed() && compareResult < 0) || (isReversed() && compareResult > 0);
240 }
241
242 void updateWith(Cell currentCell, Pair<byte[], byte[]> fuzzyData) {
243 byte[] nextRowKeyCandidate =
244 getNextForFuzzyRule(isReversed(), currentCell.getRowArray(), currentCell.getRowOffset(),
245 currentCell.getRowLength(), fuzzyData.getFirst(), fuzzyData.getSecond());
246 if (nextRowKeyCandidate != null) {
247 nextRows.add(new Pair<byte[], Pair<byte[], byte[]>>(nextRowKeyCandidate, fuzzyData));
248 }
249 }
250
251 }
252
253 @Override
254 public boolean filterAllRemaining() {
255 return done;
256 }
257
258
259
260
261 @Override
262 public byte[] toByteArray() {
263 FilterProtos.FuzzyRowFilter.Builder builder = FilterProtos.FuzzyRowFilter.newBuilder();
264 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
265 BytesBytesPair.Builder bbpBuilder = BytesBytesPair.newBuilder();
266 bbpBuilder.setFirst(ByteStringer.wrap(fuzzyData.getFirst()));
267 bbpBuilder.setSecond(ByteStringer.wrap(fuzzyData.getSecond()));
268 builder.addFuzzyKeysData(bbpBuilder);
269 }
270 return builder.build().toByteArray();
271 }
272
273
274
275
276
277
278
279 public static FuzzyRowFilter parseFrom(final byte[] pbBytes) throws DeserializationException {
280 FilterProtos.FuzzyRowFilter proto;
281 try {
282 proto = FilterProtos.FuzzyRowFilter.parseFrom(pbBytes);
283 } catch (InvalidProtocolBufferException e) {
284 throw new DeserializationException(e);
285 }
286 int count = proto.getFuzzyKeysDataCount();
287 ArrayList<Pair<byte[], byte[]>> fuzzyKeysData = new ArrayList<Pair<byte[], byte[]>>(count);
288 for (int i = 0; i < count; ++i) {
289 BytesBytesPair current = proto.getFuzzyKeysData(i);
290 byte[] keyBytes = current.getFirst().toByteArray();
291 byte[] keyMeta = current.getSecond().toByteArray();
292 fuzzyKeysData.add(new Pair<byte[], byte[]>(keyBytes, keyMeta));
293 }
294 return new FuzzyRowFilter(fuzzyKeysData);
295 }
296
297 @Override
298 public String toString() {
299 final StringBuilder sb = new StringBuilder();
300 sb.append("FuzzyRowFilter");
301 sb.append("{fuzzyKeysData=");
302 for (Pair<byte[], byte[]> fuzzyData : fuzzyKeysData) {
303 sb.append('{').append(Bytes.toStringBinary(fuzzyData.getFirst())).append(":");
304 sb.append(Bytes.toStringBinary(fuzzyData.getSecond())).append('}');
305 }
306 sb.append("}, ");
307 return sb.toString();
308 }
309
310
311
312 static enum SatisfiesCode {
313
314 YES,
315
316 NEXT_EXISTS,
317
318 NO_NEXT
319 }
320
321 @VisibleForTesting
322 static SatisfiesCode satisfies(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
323 return satisfies(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
324 }
325
326 @VisibleForTesting
327 static SatisfiesCode satisfies(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
328 byte[] fuzzyKeyMeta) {
329 return satisfies(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
330 }
331
332 static SatisfiesCode satisfies(boolean reverse, byte[] row, int offset, int length,
333 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
334
335 if (!UNSAFE_UNALIGNED) {
336 return satisfiesNoUnsafe(reverse, row, offset, length, fuzzyKeyBytes, fuzzyKeyMeta);
337 }
338
339 if (row == null) {
340
341 return SatisfiesCode.YES;
342 }
343 length = Math.min(length, fuzzyKeyBytes.length);
344 int numWords = length / Bytes.SIZEOF_LONG;
345 int offsetAdj = offset + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
346
347 int j = numWords << 3;
348
349 for (int i = 0; i < j; i += Bytes.SIZEOF_LONG) {
350
351 long fuzzyBytes =
352 UnsafeAccess.theUnsafe.getLong(fuzzyKeyBytes, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
353 + (long) i);
354 long fuzzyMeta =
355 UnsafeAccess.theUnsafe.getLong(fuzzyKeyMeta, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
356 + (long) i);
357 long rowValue = UnsafeAccess.theUnsafe.getLong(row, offsetAdj + (long) i);
358 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
359
360 return SatisfiesCode.NEXT_EXISTS;
361 }
362 }
363
364 int off = j;
365
366 if (length - off >= Bytes.SIZEOF_INT) {
367 int fuzzyBytes =
368 UnsafeAccess.theUnsafe.getInt(fuzzyKeyBytes, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
369 + (long) off);
370 int fuzzyMeta =
371 UnsafeAccess.theUnsafe.getInt(fuzzyKeyMeta, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
372 + (long) off);
373 int rowValue = UnsafeAccess.theUnsafe.getInt(row, offsetAdj + (long) off);
374 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
375
376 return SatisfiesCode.NEXT_EXISTS;
377 }
378 off += Bytes.SIZEOF_INT;
379 }
380
381 if (length - off >= Bytes.SIZEOF_SHORT) {
382 short fuzzyBytes =
383 UnsafeAccess.theUnsafe.getShort(fuzzyKeyBytes, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
384 + (long) off);
385 short fuzzyMeta =
386 UnsafeAccess.theUnsafe.getShort(fuzzyKeyMeta, UnsafeAccess.BYTE_ARRAY_BASE_OFFSET
387 + (long) off);
388 short rowValue = UnsafeAccess.theUnsafe.getShort(row, offsetAdj + (long) off);
389 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
390
391
392
393 return SatisfiesCode.NEXT_EXISTS;
394 }
395 off += Bytes.SIZEOF_SHORT;
396 }
397
398 if (length - off >= Bytes.SIZEOF_BYTE) {
399 int fuzzyBytes = fuzzyKeyBytes[off] & 0xff;
400 int fuzzyMeta = fuzzyKeyMeta[off] & 0xff;
401 int rowValue = row[offset + off] & 0xff;
402 if ((rowValue & fuzzyMeta) != (fuzzyBytes)) {
403
404 return SatisfiesCode.NEXT_EXISTS;
405 }
406 }
407 return SatisfiesCode.YES;
408 }
409
410 static SatisfiesCode satisfiesNoUnsafe(boolean reverse, byte[] row, int offset, int length,
411 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
412 if (row == null) {
413
414 return SatisfiesCode.YES;
415 }
416
417 Order order = Order.orderFor(reverse);
418 boolean nextRowKeyCandidateExists = false;
419
420 for (int i = 0; i < fuzzyKeyMeta.length && i < length; i++) {
421
422 boolean byteAtPositionFixed = fuzzyKeyMeta[i] == 0;
423 boolean fixedByteIncorrect = byteAtPositionFixed && fuzzyKeyBytes[i] != row[i + offset];
424 if (fixedByteIncorrect) {
425
426 if (nextRowKeyCandidateExists) {
427 return SatisfiesCode.NEXT_EXISTS;
428 }
429
430
431
432
433 boolean rowByteLessThanFixed = (row[i + offset] & 0xFF) < (fuzzyKeyBytes[i] & 0xFF);
434 if (rowByteLessThanFixed && !reverse) {
435 return SatisfiesCode.NEXT_EXISTS;
436 } else if (!rowByteLessThanFixed && reverse) {
437 return SatisfiesCode.NEXT_EXISTS;
438 } else {
439 return SatisfiesCode.NO_NEXT;
440 }
441 }
442
443
444
445
446
447
448
449 if (fuzzyKeyMeta[i] == 1 && !order.isMax(fuzzyKeyBytes[i])) {
450 nextRowKeyCandidateExists = true;
451 }
452 }
453 return SatisfiesCode.YES;
454 }
455
456 @VisibleForTesting
457 static byte[] getNextForFuzzyRule(byte[] row, byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
458 return getNextForFuzzyRule(false, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
459 }
460
461 @VisibleForTesting
462 static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, byte[] fuzzyKeyBytes,
463 byte[] fuzzyKeyMeta) {
464 return getNextForFuzzyRule(reverse, row, 0, row.length, fuzzyKeyBytes, fuzzyKeyMeta);
465 }
466
467
468 private enum Order {
469 ASC {
470 @Override
471 public boolean lt(int lhs, int rhs) {
472 return lhs < rhs;
473 }
474
475 @Override
476 public boolean gt(int lhs, int rhs) {
477 return lhs > rhs;
478 }
479
480 @Override
481 public byte inc(byte val) {
482
483 return (byte) (val + 1);
484 }
485
486 @Override
487 public boolean isMax(byte val) {
488 return val == (byte) 0xff;
489 }
490
491 @Override
492 public byte min() {
493 return 0;
494 }
495 },
496 DESC {
497 @Override
498 public boolean lt(int lhs, int rhs) {
499 return lhs > rhs;
500 }
501
502 @Override
503 public boolean gt(int lhs, int rhs) {
504 return lhs < rhs;
505 }
506
507 @Override
508 public byte inc(byte val) {
509
510 return (byte) (val - 1);
511 }
512
513 @Override
514 public boolean isMax(byte val) {
515 return val == 0;
516 }
517
518 @Override
519 public byte min() {
520 return (byte) 0xFF;
521 }
522 };
523
524 public static Order orderFor(boolean reverse) {
525 return reverse ? DESC : ASC;
526 }
527
528
529 public abstract boolean lt(int lhs, int rhs);
530
531
532 public abstract boolean gt(int lhs, int rhs);
533
534
535 public abstract byte inc(byte val);
536
537
538 public abstract boolean isMax(byte val);
539
540
541 public abstract byte min();
542 }
543
544
545
546
547
548 @VisibleForTesting
549 static byte[] getNextForFuzzyRule(boolean reverse, byte[] row, int offset, int length,
550 byte[] fuzzyKeyBytes, byte[] fuzzyKeyMeta) {
551
552
553
554
555
556
557
558
559 byte[] result =
560 Arrays.copyOf(fuzzyKeyBytes, length > fuzzyKeyBytes.length ? length : fuzzyKeyBytes.length);
561 if (reverse && length > fuzzyKeyBytes.length) {
562
563 for (int i = fuzzyKeyBytes.length; i < result.length; i++) {
564 result[i] = (byte) 0xFF;
565 }
566 }
567 int toInc = -1;
568 final Order order = Order.orderFor(reverse);
569
570 boolean increased = false;
571 for (int i = 0; i < result.length; i++) {
572 if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0
573 result[i] = row[offset + i];
574 if (!order.isMax(row[offset + i])) {
575
576 toInc = i;
577 }
578 } else if (i < fuzzyKeyMeta.length && fuzzyKeyMeta[i] == -1
579 if (order.lt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
580
581
582 increased = true;
583 break;
584 }
585
586 if (order.gt((row[i + offset] & 0xFF), (fuzzyKeyBytes[i] & 0xFF))) {
587
588
589
590 break;
591 }
592 }
593 }
594
595 if (!increased) {
596 if (toInc < 0) {
597 return null;
598 }
599 result[toInc] = order.inc(result[toInc]);
600
601
602
603 for (int i = toInc + 1; i < result.length; i++) {
604 if (i >= fuzzyKeyMeta.length || fuzzyKeyMeta[i] == 0
605 result[i] = order.min();
606 }
607 }
608 }
609
610 return reverse? result: trimTrailingZeroes(result, fuzzyKeyMeta, toInc);
611 }
612
613
614
615
616
617
618
619
620
621
622
623
624
625 private static byte[] trimTrailingZeroes(byte[] result, byte[] fuzzyKeyMeta, int toInc) {
626 int off = fuzzyKeyMeta.length >= result.length? result.length -1:
627 fuzzyKeyMeta.length -1;
628 for( ; off >= 0; off--){
629 if(fuzzyKeyMeta[off] != 0) break;
630 }
631 if (off < toInc) off = toInc;
632 byte[] retValue = new byte[off+1];
633 System.arraycopy(result, 0, retValue, 0, retValue.length);
634 return retValue;
635 }
636
637
638
639
640
641 @Override
642 boolean areSerializedFieldsEqual(Filter o) {
643 if (o == this) return true;
644 if (!(o instanceof FuzzyRowFilter)) return false;
645
646 FuzzyRowFilter other = (FuzzyRowFilter) o;
647 if (this.fuzzyKeysData.size() != other.fuzzyKeysData.size()) return false;
648 for (int i = 0; i < fuzzyKeysData.size(); ++i) {
649 Pair<byte[], byte[]> thisData = this.fuzzyKeysData.get(i);
650 Pair<byte[], byte[]> otherData = other.fuzzyKeysData.get(i);
651 if (!(Bytes.equals(thisData.getFirst(), otherData.getFirst()) && Bytes.equals(
652 thisData.getSecond(), otherData.getSecond()))) {
653 return false;
654 }
655 }
656 return true;
657 }
658 }