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.*;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Objects;
029import java.util.Set;
030import org.apache.hadoop.hbase.Cell;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtil;
033import org.apache.hadoop.hbase.KeyValue;
034import org.apache.hadoop.hbase.KeyValueTestUtil;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.client.Durability;
037import org.apache.hadoop.hbase.client.Put;
038import org.apache.hadoop.hbase.client.Result;
039import org.apache.hadoop.hbase.client.ResultScanner;
040import org.apache.hadoop.hbase.client.Scan;
041import org.apache.hadoop.hbase.client.Table;
042import org.apache.hadoop.hbase.filter.FilterList.Operator;
043import org.apache.hadoop.hbase.testclassification.FilterTests;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.Before;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Rule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054import org.junit.rules.TestName;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058class StringRange {
059  private String start = null;
060  private String end = null;
061  private boolean startInclusive = true;
062  private boolean endInclusive = false;
063
064  public StringRange(String start, boolean startInclusive, String end, boolean endInclusive) {
065    this.start = start;
066    this.startInclusive = startInclusive;
067    this.end = end;
068    this.endInclusive = endInclusive;
069  }
070
071  public String getStart() {
072    return this.start;
073  }
074
075  public String getEnd() {
076    return this.end;
077  }
078
079  public boolean isStartInclusive() {
080    return this.startInclusive;
081  }
082
083  public boolean isEndInclusive() {
084    return this.endInclusive;
085  }
086
087  @Override
088  public int hashCode() {
089    int hashCode = 0;
090    if (this.start != null) {
091      hashCode ^= this.start.hashCode();
092    }
093
094    if (this.end != null) {
095      hashCode ^= this.end.hashCode();
096    }
097    return hashCode;
098  }
099
100  @Override
101  public boolean equals(Object obj) {
102    if (this == obj) {
103      return true;
104    }
105    if (obj == null) {
106      return false;
107    }
108    if (!(obj instanceof StringRange)) {
109      return false;
110    }
111    StringRange oth = (StringRange) obj;
112    return this.startInclusive == oth.startInclusive && this.endInclusive == oth.endInclusive
113      && Objects.equals(this.start, oth.start) && Objects.equals(this.end, oth.end);
114  }
115
116  @Override
117  public String toString() {
118    String result = (this.startInclusive ? "[" : "(") + (this.start == null ? null : this.start)
119      + ", " + (this.end == null ? null : this.end) + (this.endInclusive ? "]" : ")");
120    return result;
121  }
122
123  public boolean inRange(String value) {
124    boolean afterStart = true;
125    if (this.start != null) {
126      int startCmp = value.compareTo(this.start);
127      afterStart = this.startInclusive ? startCmp >= 0 : startCmp > 0;
128    }
129
130    boolean beforeEnd = true;
131    if (this.end != null) {
132      int endCmp = value.compareTo(this.end);
133      beforeEnd = this.endInclusive ? endCmp <= 0 : endCmp < 0;
134    }
135
136    return afterStart && beforeEnd;
137  }
138
139}
140
141@Category({ FilterTests.class, MediumTests.class })
142public class TestColumnRangeFilter {
143
144  @ClassRule
145  public static final HBaseClassTestRule CLASS_RULE =
146    HBaseClassTestRule.forClass(TestColumnRangeFilter.class);
147
148  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
149
150  private static final Logger LOG = LoggerFactory.getLogger(TestColumnRangeFilter.class);
151
152  @Rule
153  public TestName name = new TestName();
154
155  @BeforeClass
156  public static void setUpBeforeClass() throws Exception {
157    TEST_UTIL.startMiniCluster();
158  }
159
160  @AfterClass
161  public static void tearDownAfterClass() throws Exception {
162    TEST_UTIL.shutdownMiniCluster();
163  }
164
165  @Before
166  public void setUp() throws Exception {
167    // Nothing to do.
168  }
169
170  @After
171  public void tearDown() throws Exception {
172    // Nothing to do.
173  }
174
175  @Test
176  public void TestColumnRangeFilterClient() throws Exception {
177    String family = "Family";
178    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()), Bytes.toBytes(family),
179      Integer.MAX_VALUE);
180
181    List<String> rows = generateRandomWords(10, 8);
182    long maxTimestamp = 2;
183    List<String> columns = generateRandomWords(20000, 8);
184
185    List<KeyValue> kvList = new ArrayList<>();
186
187    Map<StringRange, List<KeyValue>> rangeMap = new HashMap<>();
188
189    rangeMap.put(new StringRange(null, true, "b", false), new ArrayList<>());
190    rangeMap.put(new StringRange("p", true, "q", false), new ArrayList<>());
191    rangeMap.put(new StringRange("r", false, "s", true), new ArrayList<>());
192    rangeMap.put(new StringRange("z", false, null, false), new ArrayList<>());
193    String valueString = "ValueString";
194
195    for (String row : rows) {
196      Put p = new Put(Bytes.toBytes(row));
197      p.setDurability(Durability.SKIP_WAL);
198      for (String column : columns) {
199        for (long timestamp = 1; timestamp <= maxTimestamp; timestamp++) {
200          KeyValue kv = KeyValueTestUtil.create(row, family, column, timestamp, valueString);
201          p.add(kv);
202          kvList.add(kv);
203          for (StringRange s : rangeMap.keySet()) {
204            if (s.inRange(column)) {
205              rangeMap.get(s).add(kv);
206            }
207          }
208        }
209      }
210      ht.put(p);
211    }
212
213    TEST_UTIL.flush();
214
215    ColumnRangeFilter filter;
216    Scan scan = new Scan();
217    scan.readAllVersions();
218    for (StringRange s : rangeMap.keySet()) {
219      filter = new ColumnRangeFilter(s.getStart() == null ? null : Bytes.toBytes(s.getStart()),
220        s.isStartInclusive(), s.getEnd() == null ? null : Bytes.toBytes(s.getEnd()),
221        s.isEndInclusive());
222      assertEquals(rangeMap.get(s).size(), cellsCount(ht, filter));
223    }
224    ht.close();
225  }
226
227  @Test
228  public void TestColumnRangeFilterWithColumnPaginationFilter() throws Exception {
229    String family = "Family";
230    String table = "TestColumnRangeFilterWithColumnPaginationFilter";
231    try (Table ht =
232      TEST_UTIL.createTable(TableName.valueOf(table), Bytes.toBytes(family), Integer.MAX_VALUE)) {
233      // one row.
234      String row = "row";
235      // One version
236      long timestamp = 100;
237      // 10 columns
238      int[] columns = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
239      String valueString = "ValueString";
240
241      Put p = new Put(Bytes.toBytes(row));
242      p.setDurability(Durability.SKIP_WAL);
243      for (int column : columns) {
244        KeyValue kv =
245          KeyValueTestUtil.create(row, family, Integer.toString(column), timestamp, valueString);
246        p.add(kv);
247      }
248      ht.put(p);
249
250      TEST_UTIL.flush();
251
252      // Column range from 1 to 9.
253      StringRange stringRange = new StringRange("1", true, "9", false);
254      ColumnRangeFilter filter1 =
255        new ColumnRangeFilter(Bytes.toBytes(stringRange.getStart()), stringRange.isStartInclusive(),
256          Bytes.toBytes(stringRange.getEnd()), stringRange.isEndInclusive());
257
258      ColumnPaginationFilter filter2 = new ColumnPaginationFilter(5, 0);
259      ColumnPaginationFilter filter3 = new ColumnPaginationFilter(5, 1);
260      ColumnPaginationFilter filter4 = new ColumnPaginationFilter(5, 2);
261      ColumnPaginationFilter filter5 = new ColumnPaginationFilter(5, 6);
262      ColumnPaginationFilter filter6 = new ColumnPaginationFilter(5, 9);
263      assertEquals(5, cellsCount(ht, new FilterList(Operator.MUST_PASS_ALL, filter1, filter2)));
264      assertEquals(5, cellsCount(ht, new FilterList(Operator.MUST_PASS_ALL, filter1, filter3)));
265      assertEquals(5, cellsCount(ht, new FilterList(Operator.MUST_PASS_ALL, filter1, filter4)));
266      assertEquals(2, cellsCount(ht, new FilterList(Operator.MUST_PASS_ALL, filter1, filter5)));
267      assertEquals(0, cellsCount(ht, new FilterList(Operator.MUST_PASS_ALL, filter1, filter6)));
268    }
269  }
270
271  private int cellsCount(Table table, Filter filter) throws IOException {
272    Scan scan = new Scan().setFilter(filter).readAllVersions();
273    try (ResultScanner scanner = table.getScanner(scan)) {
274      List<Cell> results = new ArrayList<>();
275      Result result;
276      while ((result = scanner.next()) != null) {
277        result.listCells().forEach(results::add);
278      }
279      return results.size();
280    }
281  }
282
283  List<String> generateRandomWords(int numberOfWords, int maxLengthOfWords) {
284    Set<String> wordSet = new HashSet<>();
285    for (int i = 0; i < numberOfWords; i++) {
286      int lengthOfWords = (int) (Math.random() * maxLengthOfWords) + 1;
287      char[] wordChar = new char[lengthOfWords];
288      for (int j = 0; j < wordChar.length; j++) {
289        wordChar[j] = (char) (Math.random() * 26 + 97);
290      }
291      String word = new String(wordChar);
292      wordSet.add(word);
293    }
294    List<String> wordList = new ArrayList<>(wordSet);
295    return wordList;
296  }
297
298}