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 static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023import static org.junit.jupiter.api.Assertions.assertNull;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import org.apache.hadoop.hbase.Cell;
027import org.apache.hadoop.hbase.KeyValue;
028import org.apache.hadoop.hbase.KeyValueUtil;
029import org.apache.hadoop.hbase.testclassification.FilterTests;
030import org.apache.hadoop.hbase.testclassification.SmallTests;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.junit.jupiter.api.BeforeEach;
033import org.junit.jupiter.api.Tag;
034import org.junit.jupiter.api.Test;
035
036@Tag(FilterTests.TAG)
037@Tag(SmallTests.TAG)
038public class TestPrefixFilter {
039
040  Filter mainFilter;
041  static final char FIRST_CHAR = 'a';
042  static final char LAST_CHAR = 'e';
043  static final String HOST_PREFIX = "org.apache.site-";
044
045  @BeforeEach
046  public void setUp() throws Exception {
047    this.mainFilter = new PrefixFilter(Bytes.toBytes(HOST_PREFIX));
048  }
049
050  @Test
051  public void testPrefixOnRow() throws Exception {
052    prefixRowTests(mainFilter);
053  }
054
055  @Test
056  public void testPrefixOnRowInsideWhileMatchRow() throws Exception {
057    prefixRowTests(new WhileMatchFilter(this.mainFilter), true);
058  }
059
060  @Test
061  public void testSerialization() throws Exception {
062    // Decompose mainFilter to bytes.
063    byte[] buffer = mainFilter.toByteArray();
064
065    // Recompose filter.
066    Filter newFilter = PrefixFilter.parseFrom(buffer);
067
068    // Ensure the serialization preserved the filter by running all test.
069    prefixRowTests(newFilter);
070  }
071
072  private void prefixRowTests(Filter filter) throws Exception {
073    prefixRowTests(filter, false);
074  }
075
076  private void prefixRowTests(Filter filter, boolean lastFilterAllRemaining) throws Exception {
077    for (char c = FIRST_CHAR; c <= LAST_CHAR; c++) {
078      byte[] t = createRow(c);
079      assertFalse(filter.filterRowKey(KeyValueUtil.createFirstOnRow(t)),
080        "Failed with character " + c);
081      assertFalse(filter.filterAllRemaining());
082    }
083    String yahooSite = "com.yahoo.www";
084    byte[] yahooSiteBytes = Bytes.toBytes(yahooSite);
085    KeyValue yahooSiteCell = KeyValueUtil.createFirstOnRow(yahooSiteBytes);
086    assertFalse(filter.filterRowKey(yahooSiteCell), "Failed with character " + yahooSite);
087    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(yahooSiteCell));
088    assertEquals(lastFilterAllRemaining, filter.filterAllRemaining());
089  }
090
091  private byte[] createRow(final char c) {
092    return Bytes.toBytes(HOST_PREFIX + Character.toString(c));
093  }
094
095  @Test
096  public void shouldProvideHintWhenKeyBefore() {
097    byte[] prefix = Bytes.toBytes("gg");
098    PrefixFilter filter = new PrefixFilter(prefix);
099
100    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("aa"));
101
102    // Should include this row so that filterCell() will be invoked.
103    assertFalse(filter.filterRowKey(cell));
104    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(cell));
105    Cell actualCellHint = filter.getNextCellHint(cell);
106    assertNotNull(actualCellHint);
107    Cell expectedCellHint = KeyValueUtil.createFirstOnRow(prefix);
108    assertEquals(expectedCellHint, actualCellHint);
109    assertFalse(filter.filterAllRemaining());
110    assertTrue(filter.filterRow());
111  }
112
113  @Test
114  public void shouldProvideHintWhenKeyBeforeAndShorter() {
115    byte[] prefix = Bytes.toBytes("gggg");
116    PrefixFilter filter = new PrefixFilter(prefix);
117
118    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("aa"));
119
120    // Should include this row so that filterCell() will be invoked.
121    assertFalse(filter.filterRowKey(cell));
122    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(cell));
123    Cell actualCellHint = filter.getNextCellHint(cell);
124    assertNotNull(actualCellHint);
125    Cell expectedCellHint = KeyValueUtil.createFirstOnRow(prefix);
126    assertEquals(expectedCellHint, actualCellHint);
127    assertFalse(filter.filterAllRemaining());
128    assertTrue(filter.filterRow());
129  }
130
131  @Test
132  public void shouldIncludeWhenKeyMatches() {
133    PrefixFilter filter = new PrefixFilter(Bytes.toBytes("gg"));
134
135    KeyValue matchingCell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("gg"));
136
137    assertFalse(filter.filterRowKey(matchingCell));
138    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(matchingCell));
139    assertFalse(filter.filterAllRemaining());
140    assertFalse(filter.filterRow());
141  }
142
143  @Test
144  public void shouldReturnNextRowWhenKeyAfter() {
145    PrefixFilter filter = new PrefixFilter(Bytes.toBytes("gg"));
146
147    KeyValue afterCell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("pp"));
148
149    assertTrue(filter.filterRowKey(afterCell));
150    assertEquals(Filter.ReturnCode.NEXT_ROW, filter.filterCell(afterCell));
151    assertTrue(filter.filterAllRemaining());
152    assertTrue(filter.filterRow());
153  }
154
155  @Test
156  public void shouldProvideHintWhenKeyBeforeReversed() {
157    PrefixFilter filter = new PrefixFilter(Bytes.toBytes("aa"));
158    filter.setReversed(true);
159
160    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("x"));
161
162    // Should include this row so that filterCell() will be invoked.
163    assertFalse(filter.filterRowKey(cell));
164    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(cell));
165    Cell actualCellHint = filter.getNextCellHint(cell);
166    assertNotNull(actualCellHint);
167    Cell expectedCellHint = KeyValueUtil.createFirstOnRow(Bytes.toBytes("ab"));
168    assertEquals(expectedCellHint, actualCellHint);
169    assertFalse(filter.filterAllRemaining());
170    assertTrue(filter.filterRow());
171  }
172
173  @Test
174  public void hintShouldIncreaseLastNonMaxByteWhenReversed() {
175    PrefixFilter filter = new PrefixFilter(new byte[] { 'a', 'a', Byte.MAX_VALUE });
176    filter.setReversed(true);
177
178    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("x"));
179
180    // Should include this row so that filterCell() will be invoked.
181    assertFalse(filter.filterRowKey(cell));
182    assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(cell));
183    Cell actualCellHint = filter.getNextCellHint(cell);
184    assertNotNull(actualCellHint);
185    Cell expectedCellHint = KeyValueUtil.createFirstOnRow(new byte[] { 'a', 'b', Byte.MAX_VALUE });
186    assertEquals(expectedCellHint, actualCellHint);
187    assertFalse(filter.filterAllRemaining());
188    assertTrue(filter.filterRow());
189  }
190
191  @Test
192  public void shouldIncludeWhenKeyMatchesReversed() {
193    PrefixFilter filter = new PrefixFilter(Bytes.toBytes("aa"));
194    filter.setReversed(true);
195
196    KeyValue matchingCell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("aa"));
197
198    assertFalse(filter.filterRowKey(matchingCell));
199    assertEquals(Filter.ReturnCode.INCLUDE, filter.filterCell(matchingCell));
200    assertFalse(filter.filterAllRemaining());
201    assertFalse(filter.filterRow());
202  }
203
204  @Test
205  public void shouldReturnNextRowWhenKeyAfterReversed() {
206    PrefixFilter filter = new PrefixFilter(Bytes.toBytes("dd"));
207    filter.setReversed(true);
208
209    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("aa"));
210
211    assertTrue(filter.filterRowKey(cell));
212    assertEquals(Filter.ReturnCode.NEXT_ROW, filter.filterCell(cell));
213    assertTrue(filter.filterAllRemaining());
214    assertTrue(filter.filterRow());
215  }
216
217  @Test
218  public void hintShouldNotIncreaseMaxBytesWhenReversed() {
219    PrefixFilter filter =
220      new PrefixFilter(new byte[] { Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE });
221    filter.setReversed(true);
222
223    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("x"));
224
225    assertTrue(filter.filterRowKey(cell));
226    assertEquals(Filter.ReturnCode.NEXT_ROW, filter.filterCell(cell));
227    Cell actualCellHint = filter.getNextCellHint(cell);
228    assertNotNull(actualCellHint);
229    Cell expectedCellHint =
230      KeyValueUtil.createFirstOnRow(new byte[] { Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE });
231    assertEquals(expectedCellHint, actualCellHint);
232    assertTrue(filter.filterAllRemaining());
233    assertTrue(filter.filterRow());
234  }
235
236  @Test
237  public void shouldNotThrowWhenCreatedWithNullPrefix() {
238    PrefixFilter filter = new PrefixFilter(null);
239    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("doesNotMatter"));
240
241    assertNull(filter.getNextCellHint(cell));
242    filter.setReversed(true);
243    assertNull(filter.getNextCellHint(cell));
244  }
245
246  @Test
247  public void shouldNotThrowWhenCreatedWithEmptyByteArrayPrefix() {
248    byte[] emptyPrefix = {};
249    KeyValue emptyPrefixCell = KeyValueUtil.createFirstOnRow(emptyPrefix);
250    KeyValue cell = KeyValueUtil.createFirstOnRow(Bytes.toBytes("doesNotMatter"));
251
252    PrefixFilter filter = new PrefixFilter(emptyPrefix);
253
254    Cell forwardNextCellHint = filter.getNextCellHint(cell);
255    assertNotNull(forwardNextCellHint);
256    assertEquals(emptyPrefixCell, forwardNextCellHint);
257
258    filter.setReversed(true);
259    Cell reverseNextCellHint = filter.getNextCellHint(cell);
260    assertNotNull(reverseNextCellHint);
261    assertEquals(emptyPrefixCell, reverseNextCellHint);
262  }
263}