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