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.Set;
029import org.apache.hadoop.hbase.*;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.client.Durability;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.Result;
034import org.apache.hadoop.hbase.client.ResultScanner;
035import org.apache.hadoop.hbase.client.Scan;
036import org.apache.hadoop.hbase.client.Table;
037import org.apache.hadoop.hbase.filter.FilterList.Operator;
038import org.apache.hadoop.hbase.testclassification.FilterTests;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.junit.After;
042import org.junit.AfterClass;
043import org.junit.Before;
044import org.junit.BeforeClass;
045import org.junit.ClassRule;
046import org.junit.Rule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.junit.rules.TestName;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053class StringRange {
054  private String start = null;
055  private String end = null;
056  private boolean startInclusive = true;
057  private boolean endInclusive = false;
058
059  public StringRange(String start, boolean startInclusive, String end,
060      boolean endInclusive) {
061    this.start = start;
062    this.startInclusive = startInclusive;
063    this.end = end;
064    this.endInclusive = endInclusive;
065  }
066
067  public String getStart() {
068    return this.start;
069  }
070
071  public String getEnd() {
072    return this.end;
073  }
074
075  public boolean isStartInclusive() {
076    return this.startInclusive;
077  }
078
079  public boolean isEndInclusive() {
080    return this.endInclusive;
081  }
082
083  @Override
084  public int hashCode() {
085    int hashCode = 0;
086    if (this.start != null) {
087      hashCode ^= this.start.hashCode();
088    }
089
090    if (this.end != null) {
091      hashCode ^= this.end.hashCode();
092    }
093    return hashCode;
094  }
095
096  @Override
097  public String toString() {
098    String result = (this.startInclusive ? "[" : "(")
099          + (this.start == null ? null : this.start) + ", "
100          + (this.end == null ? null : this.end)
101          + (this.endInclusive ? "]" : ")");
102    return result;
103  }
104
105   public boolean inRange(String value) {
106    boolean afterStart = true;
107    if (this.start != null) {
108      int startCmp = value.compareTo(this.start);
109      afterStart = this.startInclusive ? startCmp >= 0 : startCmp > 0;
110    }
111
112    boolean beforeEnd = true;
113    if (this.end != null) {
114      int endCmp = value.compareTo(this.end);
115      beforeEnd = this.endInclusive ? endCmp <= 0 : endCmp < 0;
116    }
117
118    return afterStart && beforeEnd;
119  }
120
121}
122
123
124@Category({FilterTests.class, MediumTests.class})
125public class TestColumnRangeFilter {
126
127  @ClassRule
128  public static final HBaseClassTestRule CLASS_RULE =
129      HBaseClassTestRule.forClass(TestColumnRangeFilter.class);
130
131  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
132
133  private static final Logger LOG = LoggerFactory.getLogger(TestColumnRangeFilter.class);
134
135  @Rule
136  public TestName name = new TestName();
137
138  /**
139   * @throws java.lang.Exception
140   */
141  @BeforeClass
142  public static void setUpBeforeClass() throws Exception {
143    TEST_UTIL.startMiniCluster();
144  }
145
146  /**
147   * @throws java.lang.Exception
148   */
149  @AfterClass
150  public static void tearDownAfterClass() throws Exception {
151    TEST_UTIL.shutdownMiniCluster();
152  }
153
154  /**
155   * @throws java.lang.Exception
156   */
157  @Before
158  public void setUp() throws Exception {
159    // Nothing to do.
160  }
161
162  /**
163   * @throws java.lang.Exception
164   */
165  @After
166  public void tearDown() throws Exception {
167    // Nothing to do.
168  }
169
170  @Test
171  public void TestColumnRangeFilterClient() throws Exception {
172    String family = "Family";
173    Table ht = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()),
174        Bytes.toBytes(family), Integer.MAX_VALUE);
175
176    List<String> rows = generateRandomWords(10, 8);
177    long maxTimestamp = 2;
178    List<String> columns = generateRandomWords(20000, 8);
179
180    List<KeyValue> kvList = new ArrayList<>();
181
182    Map<StringRange, List<KeyValue>> rangeMap = new HashMap<>();
183
184    rangeMap.put(new StringRange(null, true, "b", false),
185        new ArrayList<>());
186    rangeMap.put(new StringRange("p", true, "q", false),
187        new ArrayList<>());
188    rangeMap.put(new StringRange("r", false, "s", true),
189        new ArrayList<>());
190    rangeMap.put(new StringRange("z", false, null, false),
191        new ArrayList<>());
192    String valueString = "ValueString";
193
194    for (String row : rows) {
195      Put p = new Put(Bytes.toBytes(row));
196      p.setDurability(Durability.SKIP_WAL);
197      for (String column : columns) {
198        for (long timestamp = 1; timestamp <= maxTimestamp; timestamp++) {
199          KeyValue kv = KeyValueTestUtil.create(row, family, column, timestamp,
200              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.setMaxVersions();
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 = new ColumnRangeFilter(Bytes.toBytes(stringRange.getStart()),
255          stringRange.isStartInclusive(), Bytes.toBytes(stringRange.getEnd()),
256          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}
299