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.Assert.assertEquals;
021import static org.junit.Assert.assertNull;
022
023import java.io.IOException;
024import java.nio.ByteBuffer;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.LinkedList;
028import java.util.List;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.Cell;
031import org.apache.hadoop.hbase.CellUtil;
032import org.apache.hadoop.hbase.CompareOperator;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.HBaseTestingUtil;
035import org.apache.hadoop.hbase.HConstants;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.Durability;
038import org.apache.hadoop.hbase.client.Put;
039import org.apache.hadoop.hbase.client.Result;
040import org.apache.hadoop.hbase.client.ResultScanner;
041import org.apache.hadoop.hbase.client.Scan;
042import org.apache.hadoop.hbase.client.Table;
043import org.apache.hadoop.hbase.filter.FilterList.Operator;
044import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
045import org.apache.hadoop.hbase.testclassification.FilterTests;
046import org.apache.hadoop.hbase.testclassification.MediumTests;
047import org.apache.hadoop.hbase.util.Bytes;
048import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
049import org.apache.hadoop.hbase.util.Pair;
050import org.junit.AfterClass;
051import org.junit.BeforeClass;
052import org.junit.ClassRule;
053import org.junit.Rule;
054import org.junit.Test;
055import org.junit.experimental.categories.Category;
056import org.junit.rules.TestName;
057import org.slf4j.Logger;
058import org.slf4j.LoggerFactory;
059
060import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
061
062@Category({ FilterTests.class, MediumTests.class })
063public class TestFuzzyRowFilterEndToEnd {
064
065  @ClassRule
066  public static final HBaseClassTestRule CLASS_RULE =
067    HBaseClassTestRule.forClass(TestFuzzyRowFilterEndToEnd.class);
068
069  private static final Logger LOG = LoggerFactory.getLogger(TestFuzzyRowFilterEndToEnd.class);
070
071  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
072
073  private static final byte fuzzyValue = (byte) 63;
074
075  @Rule
076  public TestName name = new TestName();
077
078  @BeforeClass
079  public static void setUpBeforeClass() throws Exception {
080    Configuration conf = TEST_UTIL.getConfiguration();
081    conf.setInt("hbase.client.scanner.caching", 1000);
082    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
083      ConstantSizeRegionSplitPolicy.class.getName());
084    // set no splits
085    conf.setLong(HConstants.HREGION_MAX_FILESIZE, (1024L) * 1024 * 1024 * 10);
086
087    TEST_UTIL.startMiniCluster();
088  }
089
090  @AfterClass
091  public static void tearDownAfterClass() throws Exception {
092    TEST_UTIL.shutdownMiniCluster();
093  }
094
095  // HBASE-15676 Test that fuzzy info of all fixed bits (0s) finds matching row.
096  @Test
097  public void testAllFixedBits() throws IOException {
098    String cf = "f";
099    String cq = "q";
100
101    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), Bytes.toBytes(cf),
102      Integer.MAX_VALUE);
103    // Load data
104    String[] rows = new String[] { "\\x9C\\x00\\x044\\x00\\x00\\x00\\x00",
105      "\\x9C\\x00\\x044\\x01\\x00\\x00\\x00", "\\x9C\\x00\\x044\\x00\\x01\\x00\\x00",
106      "\\x9B\\x00\\x044e\\x9B\\x02\\xBB", "\\x9C\\x00\\x044\\x00\\x00\\x01\\x00",
107      "\\x9C\\x00\\x044\\x00\\x01\\x00\\x01", "\\x9B\\x00\\x044e\\xBB\\xB2\\xBB", };
108
109    for (int i = 0; i < rows.length; i++) {
110      Put p = new Put(Bytes.toBytesBinary(rows[i]));
111      p.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), Bytes.toBytes("value"));
112      ht.put(p);
113    }
114
115    TEST_UTIL.flush();
116
117    List<Pair<byte[], byte[]>> data = new ArrayList<>();
118    byte[] fuzzyKey = Bytes.toBytesBinary("\\x9B\\x00\\x044e");
119    byte[] mask = new byte[] { 0, 0, 0, 0, 0 };
120
121    // copy the fuzzy key and mask to test HBASE-18617
122    byte[] copyFuzzyKey = Arrays.copyOf(fuzzyKey, fuzzyKey.length);
123    byte[] copyMask = Arrays.copyOf(mask, mask.length);
124
125    data.add(new Pair<>(fuzzyKey, mask));
126    FuzzyRowFilter filter = new FuzzyRowFilter(data);
127
128    Scan scan = new Scan();
129    scan.setFilter(filter);
130
131    ResultScanner scanner = ht.getScanner(scan);
132    int total = 0;
133    while (scanner.next() != null) {
134      total++;
135    }
136    assertEquals(2, total);
137
138    assertEquals(true, Arrays.equals(copyFuzzyKey, fuzzyKey));
139    assertEquals(true, Arrays.equals(copyMask, mask));
140
141    TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
142  }
143
144  @Test
145  public void testHBASE14782() throws IOException {
146    String cf = "f";
147    String cq = "q";
148
149    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), Bytes.toBytes(cf),
150      Integer.MAX_VALUE);
151    // Load data
152    String[] rows =
153      new String[] { "\\x9C\\x00\\x044\\x00\\x00\\x00\\x00", "\\x9C\\x00\\x044\\x01\\x00\\x00\\x00",
154        "\\x9C\\x00\\x044\\x00\\x01\\x00\\x00", "\\x9C\\x00\\x044\\x00\\x00\\x01\\x00",
155        "\\x9C\\x00\\x044\\x00\\x01\\x00\\x01", "\\x9B\\x00\\x044e\\xBB\\xB2\\xBB", };
156
157    String badRow = "\\x9C\\x00\\x03\\xE9e\\xBB{X\\x1Fwts\\x1F\\x15vRX";
158
159    for (int i = 0; i < rows.length; i++) {
160      Put p = new Put(Bytes.toBytesBinary(rows[i]));
161      p.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), Bytes.toBytes("value"));
162      ht.put(p);
163    }
164
165    Put p = new Put(Bytes.toBytesBinary(badRow));
166    p.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), Bytes.toBytes("value"));
167    ht.put(p);
168
169    TEST_UTIL.flush();
170
171    List<Pair<byte[], byte[]>> data = new ArrayList<>();
172    byte[] fuzzyKey = Bytes.toBytesBinary("\\x00\\x00\\x044");
173    byte[] mask = new byte[] { 1, 0, 0, 0 };
174    data.add(new Pair<>(fuzzyKey, mask));
175    FuzzyRowFilter filter = new FuzzyRowFilter(data);
176
177    Scan scan = new Scan();
178    scan.setFilter(filter);
179
180    ResultScanner scanner = ht.getScanner(scan);
181    int total = 0;
182    while (scanner.next() != null) {
183      total++;
184    }
185    assertEquals(rows.length, total);
186    TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
187  }
188
189  @Test
190  public void testFilterList() throws Exception {
191    String cf = "f";
192    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), Bytes.toBytes(cf),
193      Integer.MAX_VALUE);
194
195    // 10 byte row key - (2 bytes 4 bytes 4 bytes)
196    // 4 byte qualifier
197    // 4 byte value
198
199    for (int i1 = 0; i1 < 5; i1++) {
200      for (int i2 = 0; i2 < 5; i2++) {
201        byte[] rk = new byte[10];
202
203        ByteBuffer buf = ByteBuffer.wrap(rk);
204        buf.clear();
205        buf.putShort((short) 2);
206        buf.putInt(i1);
207        buf.putInt(i2);
208
209        // Each row contains 5 columns
210        for (int c = 0; c < 5; c++) {
211          byte[] cq = new byte[4];
212          Bytes.putBytes(cq, 0, Bytes.toBytes(c), 0, 4);
213
214          Put p = new Put(rk);
215          p.setDurability(Durability.SKIP_WAL);
216          p.addColumn(Bytes.toBytes(cf), cq, Bytes.toBytes(c));
217          ht.put(p);
218          LOG.info(
219            "Inserting: rk: " + Bytes.toStringBinary(rk) + " cq: " + Bytes.toStringBinary(cq));
220        }
221      }
222    }
223
224    TEST_UTIL.flush();
225
226    // test passes if we get back 5 KV's (1 row)
227    runTest(ht, 5);
228
229  }
230
231  private void runTest(Table hTable, int expectedSize) throws IOException {
232    // [0, 2, ?, ?, ?, ?, 0, 0, 0, 1]
233    byte[] fuzzyKey1 = new byte[10];
234    ByteBuffer buf = ByteBuffer.wrap(fuzzyKey1);
235    buf.clear();
236    buf.putShort((short) 2);
237    for (int i = 0; i < 4; i++)
238      buf.put(fuzzyValue);
239    buf.putInt((short) 1);
240    byte[] mask1 = new byte[] { 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 };
241
242    byte[] fuzzyKey2 = new byte[10];
243    buf = ByteBuffer.wrap(fuzzyKey2);
244    buf.clear();
245    buf.putShort((short) 2);
246    buf.putInt((short) 2);
247    for (int i = 0; i < 4; i++)
248      buf.put(fuzzyValue);
249
250    byte[] mask2 = new byte[] { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 };
251
252    Pair<byte[], byte[]> pair1 = new Pair<>(fuzzyKey1, mask1);
253    Pair<byte[], byte[]> pair2 = new Pair<>(fuzzyKey2, mask2);
254
255    FuzzyRowFilter fuzzyRowFilter1 = new FuzzyRowFilter(Lists.newArrayList(pair1));
256    FuzzyRowFilter fuzzyRowFilter2 = new FuzzyRowFilter(Lists.newArrayList(pair2));
257    // regular test - we expect 1 row back (5 KVs)
258    runScanner(hTable, expectedSize, fuzzyRowFilter1, fuzzyRowFilter2);
259  }
260
261  private void runScanner(Table hTable, int expectedSize, Filter filter1, Filter filter2)
262    throws IOException {
263    String cf = "f";
264    Scan scan = new Scan();
265    scan.addFamily(Bytes.toBytes(cf));
266    FilterList filterList = new FilterList(Operator.MUST_PASS_ALL, filter1, filter2);
267    scan.setFilter(filterList);
268
269    ResultScanner scanner = hTable.getScanner(scan);
270    List<Cell> results = new ArrayList<>();
271    Result result;
272    long timeBeforeScan = EnvironmentEdgeManager.currentTime();
273    while ((result = scanner.next()) != null) {
274      for (Cell kv : result.listCells()) {
275        LOG.info("Got rk: " + Bytes.toStringBinary(CellUtil.cloneRow(kv)) + " cq: "
276          + Bytes.toStringBinary(CellUtil.cloneQualifier(kv)));
277        results.add(kv);
278      }
279    }
280    long scanTime = EnvironmentEdgeManager.currentTime() - timeBeforeScan;
281    scanner.close();
282
283    LOG.info("scan time = " + scanTime + "ms");
284    LOG.info("found " + results.size() + " results");
285
286    assertEquals(expectedSize, results.size());
287  }
288
289  @Test
290  public void testHBASE26967() throws IOException {
291    byte[] row1 = Bytes.toBytes("1");
292    byte[] row2 = Bytes.toBytes("2");
293    String cf1 = "f1";
294    String cf2 = "f2";
295    String cq1 = "col1";
296    String cq2 = "col2";
297
298    Table ht =
299      TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), new String[] { cf1, cf2 });
300
301    // Put data
302    List<Put> puts = Lists.newArrayList();
303    puts.add(new Put(row1).addColumn(Bytes.toBytes(cf1), Bytes.toBytes(cq1), Bytes.toBytes("a1")));
304    puts.add(new Put(row1).addColumn(Bytes.toBytes(cf2), Bytes.toBytes(cq2), Bytes.toBytes("a2")));
305    puts.add(new Put(row2).addColumn(Bytes.toBytes(cf1), Bytes.toBytes(cq1), Bytes.toBytes("b1")));
306    puts.add(new Put(row2).addColumn(Bytes.toBytes(cf2), Bytes.toBytes(cq2), Bytes.toBytes("b2")));
307    ht.put(puts);
308
309    TEST_UTIL.flush();
310
311    // FuzzyRowFilter
312    List<Pair<byte[], byte[]>> data = Lists.newArrayList();
313    byte[] fuzzyKey = Bytes.toBytes("1");
314    byte[] mask = new byte[] { 0 };
315    data.add(new Pair<>(fuzzyKey, mask));
316    FuzzyRowFilter fuzzyRowFilter = new FuzzyRowFilter(data);
317
318    // SingleColumnValueFilter
319    Filter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes(cf2),
320      Bytes.toBytes(cq2), CompareOperator.EQUAL, Bytes.toBytes("x"));
321
322    // FilterList
323    FilterList filterList = new FilterList(Operator.MUST_PASS_ONE);
324    filterList.addFilter(Lists.newArrayList(fuzzyRowFilter, singleColumnValueFilter));
325
326    Scan scan = new Scan();
327    scan.setFilter(filterList);
328
329    ResultScanner scanner = ht.getScanner(scan);
330    Result rs = scanner.next();
331    assertEquals(0, Bytes.compareTo(row1, rs.getRow()));
332
333    // The two cells (1,f1,col1,a1) (1,f2,col2,a2)
334    assertEquals(2, rs.listCells().size());
335
336    // Only one row who's rowKey=1
337    assertNull(scanner.next());
338
339    TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
340  }
341
342  @Test
343  public void testHBASE28634() throws IOException {
344    final String CF = "f";
345    final String CQ = "name";
346
347    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), Bytes.toBytes(CF));
348
349    // Put data
350    List<Put> puts = Lists.newArrayList();
351    puts.add(new Put(Bytes.toBytes("111311")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
352      Bytes.toBytes("a1")));
353    puts.add(new Put(Bytes.toBytes("111444")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
354      Bytes.toBytes("a2")));
355    puts.add(new Put(Bytes.toBytes("111511")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
356      Bytes.toBytes("a3")));
357    puts.add(new Put(Bytes.toBytes("111611")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
358      Bytes.toBytes("a4")));
359    puts.add(new Put(Bytes.toBytes("111446")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
360      Bytes.toBytes("a5")));
361    puts.add(new Put(Bytes.toBytes("111777")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
362      Bytes.toBytes("a6")));
363    puts.add(new Put(Bytes.toBytes("111777")).addColumn(Bytes.toBytes(CF), Bytes.toBytes(CQ),
364      Bytes.toBytes("a")));
365    ht.put(puts);
366
367    TEST_UTIL.flush();
368
369    // Forward scan
370    LinkedList<Pair<byte[], byte[]>> fuzzyList = new LinkedList<>();
371    byte[] fuzzyRowKey = Bytes.toBytes("111433");
372    byte[] mask = Bytes.toBytesBinary("\\xFF\\xFF\\xFF\\xFF\\x02\\x02");
373    fuzzyList.add(new Pair<>(fuzzyRowKey, mask));
374    FuzzyRowFilter fuzzyRowFilter = new FuzzyRowFilter(fuzzyList);
375
376    Scan scan = new Scan();
377    scan.setFilter(fuzzyRowFilter);
378
379    ResultScanner scanner = ht.getScanner(scan);
380    List<byte[]> actualRowsList = new ArrayList<>();
381    for (Result result : scanner) {
382      byte[] row = result.getRow();
383      actualRowsList.add(row);
384    }
385
386    assertEquals(2, actualRowsList.size());
387
388    // Reverse scan
389    scan = new Scan();
390    scan.setFilter(fuzzyRowFilter);
391    scan.setReversed(true);
392
393    scanner = ht.getScanner(scan);
394    actualRowsList = new ArrayList<>();
395    for (Result result : scanner) {
396      byte[] row = result.getRow();
397      actualRowsList.add(row);
398    }
399
400    assertEquals(2, actualRowsList.size());
401
402    TEST_UTIL.deleteTable(TableName.valueOf(name.getMethodName()));
403  }
404}