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