Skip to content

Commit 0f8fbd2

Browse files
authored
HBASE-30150 Propagate filter hints through composite filters (#8217) (#8486)
(cherry picked from commit e45818e) Signed-off-by: Viraj Jasani <vjasani@apache.org>
1 parent 8371eae commit 0f8fbd2

11 files changed

Lines changed: 1635 additions & 6 deletions

File tree

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnPrefixFilter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ public Cell getNextCellHint(Cell cell) {
154154
return PrivateCellUtil.createFirstOnRowCol(cell, prefix, 0, prefix.length);
155155
}
156156

157+
@Override
158+
public Cell getSkipHint(Cell skippedCell) throws IOException {
159+
if (this.prefix == null) {
160+
return null;
161+
}
162+
return getNextCellHint(skippedCell);
163+
}
164+
157165
@Override
158166
public String toString() {
159167
return this.getClass().getSimpleName() + " " + Bytes.toStringBinary(this.prefix);

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/ColumnRangeFilter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ public Cell getNextCellHint(Cell cell) {
206206
return PrivateCellUtil.createFirstOnRowCol(cell, this.minColumn, 0, len(this.minColumn));
207207
}
208208

209+
@Override
210+
public Cell getSkipHint(Cell skippedCell) throws IOException {
211+
if (this.minColumn == null) {
212+
return null;
213+
}
214+
return getNextCellHint(skippedCell);
215+
}
216+
209217
@Override
210218
public String toString() {
211219
return this.getClass().getSimpleName() + " " + (this.minColumnInclusive ? "[" : "(")

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/Filter.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,13 @@ public enum ReturnCode {
247247
* must point to a <em>smaller</em> row key (earlier in reverse-scan direction). The scanner
248248
* validates hint direction and falls back to {@code nextRow()} if the hint does not advance in
249249
* the scan direction.</li>
250-
* <li><strong>Composite filter limitation:</strong> {@code FilterList}, {@code SkipFilter}, and
251-
* {@code WhileMatchFilter} do not currently delegate this method to wrapped sub-filters. Hints
252-
* from filters used inside these wrappers will be silently ignored.</li>
250+
* <li><strong>Composite filter support:</strong> {@code FilterList} (both {@code MUST_PASS_ALL}
251+
* and {@code MUST_PASS_ONE}), {@code SkipFilter}, and {@code WhileMatchFilter} delegate this
252+
* method to their sub-filters and merge the results. For AND ({@code MUST_PASS_ALL}), only
253+
* sub-filters whose {@code filterRowKey} individually returned {@code true} are consulted, and
254+
* the farthest (maximal-step) hint among them is returned. For OR ({@code MUST_PASS_ONE}), the
255+
* nearest hint is returned only when every non-terminated sub-filter provides one — any null
256+
* collapses the OR result to null.</li>
253257
* </ul>
254258
* @param firstRowCell the first cell encountered in the rejected row; contains the row key that
255259
* was passed to {@code filterRowKey}
@@ -286,9 +290,11 @@ public Cell getHintForRejectedRow(final Cell firstRowCell) throws IOException {
286290
* <li>For reversed scans, the returned cell must have a <em>smaller</em> row key (i.e., earlier
287291
* in reverse-scan direction) than the {@code skippedCell}. Hints that do not advance in the scan
288292
* direction are silently ignored.</li>
289-
* <li><strong>Composite filter limitation:</strong> {@code FilterList}, {@code SkipFilter}, and
290-
* {@code WhileMatchFilter} do not currently delegate this method to wrapped sub-filters. Hints
291-
* from filters used inside these wrappers will be silently ignored.</li>
293+
* <li><strong>Composite filter support:</strong> {@code FilterList} (both {@code MUST_PASS_ALL}
294+
* and {@code MUST_PASS_ONE}), {@code SkipFilter}, and {@code WhileMatchFilter} delegate this
295+
* method to their sub-filters and merge the results (maximal step for AND; for OR, the nearest
296+
* hint is returned only when every non-terminated sub-filter provides one — any null collapses
297+
* the OR result to null).</li>
292298
* </ul>
293299
* @param skippedCell the cell that was rejected by the time-range, column, or version gate before
294300
* {@code filterCell} could be consulted

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FilterList.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,16 @@ public Cell getNextCellHint(Cell currentCell) throws IOException {
253253
return this.filterListBase.getNextCellHint(currentCell);
254254
}
255255

256+
@Override
257+
public Cell getHintForRejectedRow(Cell firstRowCell) throws IOException {
258+
return this.filterListBase.getHintForRejectedRow(firstRowCell);
259+
}
260+
261+
@Override
262+
public Cell getSkipHint(Cell skippedCell) throws IOException {
263+
return this.filterListBase.getSkipHint(skippedCell);
264+
}
265+
256266
@Override
257267
public boolean isFamilyEssential(byte[] name) throws IOException {
258268
return this.filterListBase.isFamilyEssential(name);

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FilterListWithAND.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Objects;
@@ -34,13 +35,20 @@ public class FilterListWithAND extends FilterListBase {
3435

3536
private List<Filter> seekHintFilters = new ArrayList<>();
3637
private boolean[] hintingFilters;
38+
/**
39+
* Tracks which sub-filters returned {@code true} from {@link Filter#filterRowKey(Cell)}. Set in
40+
* {@code filterRowKey()}, consumed by {@code getHintForRejectedRow()}, cleared only by
41+
* {@code reset()} — callers must invoke {@code reset()} between rows to avoid stale state.
42+
*/
43+
private boolean[] rejectedByFilterRowKey;
3744

3845
public FilterListWithAND(List<Filter> filters) {
3946
super(filters);
4047
// For FilterList with AND, when call FL's transformCell(), we should transform cell for all
4148
// sub-filters (because all sub-filters return INCLUDE*). So here, fill this array with true. we
4249
// keep this in FilterListWithAND for abstracting the transformCell() in FilterListBase.
4350
subFiltersIncludedCell = new ArrayList<>(Collections.nCopies(filters.size(), true));
51+
rejectedByFilterRowKey = new boolean[filters.size()];
4452
cacheHintingFilters();
4553
}
4654

@@ -51,6 +59,7 @@ public void addFilterLists(List<Filter> filters) {
5159
}
5260
this.filters.addAll(filters);
5361
this.subFiltersIncludedCell.addAll(Collections.nCopies(filters.size(), true));
62+
this.rejectedByFilterRowKey = Arrays.copyOf(this.rejectedByFilterRowKey, this.filters.size());
5463
this.cacheHintingFilters();
5564
}
5665

@@ -237,6 +246,7 @@ public void reset() throws IOException {
237246
filters.get(i).reset();
238247
}
239248
seekHintFilters.clear();
249+
Arrays.fill(rejectedByFilterRowKey, false);
240250
}
241251

242252
@Override
@@ -259,6 +269,7 @@ public boolean filterRowKey(Cell firstRowCell) throws IOException {
259269
if (isEmpty()) {
260270
return super.filterRowKey(firstRowCell);
261271
}
272+
Arrays.fill(rejectedByFilterRowKey, false);
262273
boolean anyRowKeyFiltered = false;
263274
boolean anyHintingPassed = false;
264275
for (int i = 0, n = filters.size(); i < n; i++) {
@@ -273,6 +284,7 @@ public boolean filterRowKey(Cell firstRowCell) throws IOException {
273284
// will catch the row changed event by filterRowKey(). If we return early here, those
274285
// filters will have no chance to update their row state.
275286
anyRowKeyFiltered = true;
287+
rejectedByFilterRowKey[i] = true;
276288
} else if (hintingFilters[i]) {
277289
// If filterRowKey returns false and this is a hinting filter, then we must not filter this
278290
// rowkey.
@@ -333,6 +345,60 @@ public Cell getNextCellHint(Cell currentCell) throws IOException {
333345
return maxHint;
334346
}
335347

348+
/**
349+
* Maximal step: return the farthest hint among sub-filters that actually rejected the row. Only
350+
* sub-filters whose {@link Filter#filterRowKey(Cell)} returned {@code true} are consulted,
351+
* honouring the per-filter contract. Null hints are ignored; if no rejecting sub-filter provides
352+
* a hint, return null.
353+
*/
354+
@Override
355+
public Cell getHintForRejectedRow(Cell firstRowCell) throws IOException {
356+
if (isEmpty()) {
357+
return super.getHintForRejectedRow(firstRowCell);
358+
}
359+
Cell maxHint = null;
360+
for (int i = 0, n = filters.size(); i < n; i++) {
361+
if (!rejectedByFilterRowKey[i]) {
362+
continue;
363+
}
364+
Filter filter = filters.get(i);
365+
if (filter.filterAllRemaining()) {
366+
continue;
367+
}
368+
Cell hint = filter.getHintForRejectedRow(firstRowCell);
369+
if (hint == null) {
370+
continue;
371+
}
372+
if (maxHint == null || this.compareCell(maxHint, hint) < 0) {
373+
maxHint = hint;
374+
}
375+
}
376+
return maxHint;
377+
}
378+
379+
/** Maximal step: return the farthest skip hint among sub-filters. */
380+
@Override
381+
public Cell getSkipHint(Cell skippedCell) throws IOException {
382+
if (isEmpty()) {
383+
return super.getSkipHint(skippedCell);
384+
}
385+
Cell maxHint = null;
386+
for (int i = 0, n = filters.size(); i < n; i++) {
387+
Filter filter = filters.get(i);
388+
if (filter.filterAllRemaining()) {
389+
continue;
390+
}
391+
Cell hint = filter.getSkipHint(skippedCell);
392+
if (hint == null) {
393+
continue;
394+
}
395+
if (maxHint == null || this.compareCell(maxHint, hint) < 0) {
396+
maxHint = hint;
397+
}
398+
}
399+
return maxHint;
400+
}
401+
336402
@Override
337403
public boolean equals(Object obj) {
338404
if (this == obj) {

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/FilterListWithOR.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,58 @@ public Cell getNextCellHint(Cell currentCell) throws IOException {
407407
return minKeyHint;
408408
}
409409

410+
/**
411+
* Minimal step: return the nearest hint. If any non-terminated sub-filter returns null, the
412+
* composite cannot safely skip, so return null.
413+
*/
414+
@Override
415+
public Cell getHintForRejectedRow(Cell firstRowCell) throws IOException {
416+
if (isEmpty()) {
417+
return super.getHintForRejectedRow(firstRowCell);
418+
}
419+
Cell minHint = null;
420+
for (int i = 0, n = filters.size(); i < n; i++) {
421+
Filter filter = filters.get(i);
422+
if (filter.filterAllRemaining()) {
423+
continue;
424+
}
425+
Cell hint = filter.getHintForRejectedRow(firstRowCell);
426+
if (hint == null) {
427+
return null;
428+
}
429+
if (minHint == null || this.compareCell(minHint, hint) > 0) {
430+
minHint = hint;
431+
}
432+
}
433+
return minHint;
434+
}
435+
436+
/**
437+
* Minimal step: return the nearest skip hint. Null from any sub-filter collapses the entire
438+
* result to null.
439+
*/
440+
@Override
441+
public Cell getSkipHint(Cell skippedCell) throws IOException {
442+
if (isEmpty()) {
443+
return super.getSkipHint(skippedCell);
444+
}
445+
Cell minHint = null;
446+
for (int i = 0, n = filters.size(); i < n; i++) {
447+
Filter filter = filters.get(i);
448+
if (filter.filterAllRemaining()) {
449+
continue;
450+
}
451+
Cell hint = filter.getSkipHint(skippedCell);
452+
if (hint == null) {
453+
return null;
454+
}
455+
if (minHint == null || this.compareCell(minHint, hint) > 0) {
456+
minHint = hint;
457+
}
458+
}
459+
return minHint;
460+
}
461+
410462
@Override
411463
public boolean equals(Object obj) {
412464
if (this == obj) {

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/MultipleColumnPrefixFilter.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,29 @@ public Cell getNextCellHint(Cell cell) {
179179
return PrivateCellUtil.createFirstOnRowCol(cell, hint, 0, hint.length);
180180
}
181181

182+
@Override
183+
public Cell getSkipHint(Cell skippedCell) throws IOException {
184+
if (sortedPrefixes.isEmpty()) {
185+
return null;
186+
}
187+
byte[] qualifier = CellUtil.cloneQualifier(skippedCell);
188+
TreeSet<byte[]> lesserOrEqual = (TreeSet<byte[]>) sortedPrefixes.headSet(qualifier, true);
189+
byte[] target;
190+
if (lesserOrEqual.isEmpty()) {
191+
target = sortedPrefixes.first();
192+
} else {
193+
byte[] largest = lesserOrEqual.last();
194+
if (Bytes.startsWith(qualifier, largest)) {
195+
return null;
196+
}
197+
target = sortedPrefixes.higher(largest);
198+
if (target == null) {
199+
return null;
200+
}
201+
}
202+
return PrivateCellUtil.createFirstOnRowCol(skippedCell, target, 0, target.length);
203+
}
204+
182205
public TreeSet<byte[]> createTreeSet() {
183206
return new TreeSet<>(new Comparator<Object>() {
184207
@Override

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/SkipFilter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ boolean areSerializedFieldsEqual(Filter o) {
147147
return getFilter().areSerializedFieldsEqual(other.getFilter());
148148
}
149149

150+
@Override
151+
public Cell getHintForRejectedRow(Cell firstRowCell) throws IOException {
152+
return filter.getHintForRejectedRow(firstRowCell);
153+
}
154+
155+
@Override
156+
public Cell getSkipHint(Cell skippedCell) throws IOException {
157+
return filter.getSkipHint(skippedCell);
158+
}
159+
150160
@Override
151161
public boolean isFamilyEssential(byte[] name) throws IOException {
152162
return filter.isFamilyEssential(name);

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/WhileMatchFilter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ boolean areSerializedFieldsEqual(Filter o) {
152152
return getFilter().areSerializedFieldsEqual(other.getFilter());
153153
}
154154

155+
@Override
156+
public Cell getHintForRejectedRow(Cell firstRowCell) throws IOException {
157+
return filter.getHintForRejectedRow(firstRowCell);
158+
}
159+
160+
@Override
161+
public Cell getSkipHint(Cell skippedCell) throws IOException {
162+
return filter.getSkipHint(skippedCell);
163+
}
164+
155165
@Override
156166
public boolean isFamilyEssential(byte[] name) throws IOException {
157167
return filter.isFamilyEssential(name);

0 commit comments

Comments
 (0)