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.client; 019 020import static org.junit.Assert.*; 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import org.apache.hadoop.hbase.*; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.filter.Filter; 029import org.apache.hadoop.hbase.filter.TimestampsFilter; 030import org.apache.hadoop.hbase.testclassification.ClientTests; 031import org.apache.hadoop.hbase.testclassification.MediumTests; 032import org.apache.hadoop.hbase.util.Bytes; 033import org.junit.After; 034import org.junit.AfterClass; 035import org.junit.Before; 036import org.junit.BeforeClass; 037import org.junit.ClassRule; 038import org.junit.Rule; 039import org.junit.Test; 040import org.junit.experimental.categories.Category; 041import org.junit.rules.TestName; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * Run tests related to {@link TimestampsFilter} using HBase client APIs. 047 * Sets up the HBase mini cluster once at start. Each creates a table 048 * named for the method and does its stuff against that. 049 */ 050@Category({MediumTests.class, ClientTests.class}) 051public class TestTimestampsFilter { 052 053 @ClassRule 054 public static final HBaseClassTestRule CLASS_RULE = 055 HBaseClassTestRule.forClass(TestTimestampsFilter.class); 056 057 private static final Logger LOG = LoggerFactory.getLogger(TestTimestampsFilter.class); 058 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 059 060 @Rule 061 public TestName name = new TestName(); 062 063 /** 064 * @throws java.lang.Exception 065 */ 066 @BeforeClass 067 public static void setUpBeforeClass() throws Exception { 068 TEST_UTIL.startMiniCluster(); 069 } 070 071 /** 072 * @throws java.lang.Exception 073 */ 074 @AfterClass 075 public static void tearDownAfterClass() throws Exception { 076 TEST_UTIL.shutdownMiniCluster(); 077 } 078 079 /** 080 * @throws java.lang.Exception 081 */ 082 @Before 083 public void setUp() throws Exception { 084 // Nothing to do. 085 } 086 087 /** 088 * @throws java.lang.Exception 089 */ 090 @After 091 public void tearDown() throws Exception { 092 // Nothing to do. 093 } 094 095 /** 096 * Test from client side for TimestampsFilter. 097 * 098 * The TimestampsFilter provides the ability to request cells (KeyValues) 099 * whose timestamp/version is in the specified list of timestamps/version. 100 * 101 * @throws Exception 102 */ 103 @Test 104 public void testTimestampsFilter() throws Exception { 105 final byte [] TABLE = Bytes.toBytes(name.getMethodName()); 106 byte [] FAMILY = Bytes.toBytes("event_log"); 107 byte [][] FAMILIES = new byte[][] { FAMILY }; 108 Cell kvs[]; 109 110 // create table; set versions to max... 111 Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE); 112 113 for (int rowIdx = 0; rowIdx < 5; rowIdx++) { 114 for (int colIdx = 0; colIdx < 5; colIdx++) { 115 // insert versions 201..300 116 putNVersions(ht, FAMILY, rowIdx, colIdx, 201, 300); 117 // insert versions 1..100 118 putNVersions(ht, FAMILY, rowIdx, colIdx, 1, 100); 119 } 120 } 121 122 // do some verification before flush 123 verifyInsertedValues(ht, FAMILY); 124 125 TEST_UTIL.flush(); 126 127 // do some verification after flush 128 verifyInsertedValues(ht, FAMILY); 129 130 // Insert some more versions after flush. These should be in memstore. 131 // After this we should have data in both memstore & HFiles. 132 for (int rowIdx = 0; rowIdx < 5; rowIdx++) { 133 for (int colIdx = 0; colIdx < 5; colIdx++) { 134 putNVersions(ht, FAMILY, rowIdx, colIdx, 301, 400); 135 putNVersions(ht, FAMILY, rowIdx, colIdx, 101, 200); 136 } 137 } 138 139 for (int rowIdx = 0; rowIdx < 5; rowIdx++) { 140 for (int colIdx = 0; colIdx < 5; colIdx++) { 141 kvs = getNVersions(ht, FAMILY, rowIdx, colIdx, 142 Arrays.asList(505L, 5L, 105L, 305L, 205L)); 143 assertEquals(4, kvs.length); 144 checkOneCell(kvs[0], FAMILY, rowIdx, colIdx, 305); 145 checkOneCell(kvs[1], FAMILY, rowIdx, colIdx, 205); 146 checkOneCell(kvs[2], FAMILY, rowIdx, colIdx, 105); 147 checkOneCell(kvs[3], FAMILY, rowIdx, colIdx, 5); 148 } 149 } 150 151 // Request an empty list of versions using the Timestamps filter; 152 // Should return none. 153 kvs = getNVersions(ht, FAMILY, 2, 2, new ArrayList<>()); 154 assertEquals(0, kvs == null? 0: kvs.length); 155 156 // 157 // Test the filter using a Scan operation 158 // Scan rows 0..4. For each row, get all its columns, but only 159 // those versions of the columns with the specified timestamps. 160 Result[] results = scanNVersions(ht, FAMILY, 0, 4, 161 Arrays.asList(6L, 106L, 306L)); 162 assertEquals("# of rows returned from scan", 5, results.length); 163 for (int rowIdx = 0; rowIdx < 5; rowIdx++) { 164 kvs = results[rowIdx].rawCells(); 165 // each row should have 5 columns. 166 // And we have requested 3 versions for each. 167 assertEquals("Number of KeyValues in result for row:" + rowIdx, 168 3*5, kvs.length); 169 for (int colIdx = 0; colIdx < 5; colIdx++) { 170 int offset = colIdx * 3; 171 checkOneCell(kvs[offset + 0], FAMILY, rowIdx, colIdx, 306); 172 checkOneCell(kvs[offset + 1], FAMILY, rowIdx, colIdx, 106); 173 checkOneCell(kvs[offset + 2], FAMILY, rowIdx, colIdx, 6); 174 } 175 } 176 ht.close(); 177 } 178 179 @Test 180 public void testMultiColumns() throws Exception { 181 final byte [] TABLE = Bytes.toBytes(name.getMethodName()); 182 byte [] FAMILY = Bytes.toBytes("event_log"); 183 byte [][] FAMILIES = new byte[][] { FAMILY }; 184 185 // create table; set versions to max... 186 Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE); 187 188 Put p = new Put(Bytes.toBytes("row")); 189 p.addColumn(FAMILY, Bytes.toBytes("column0"), 3L, Bytes.toBytes("value0-3")); 190 p.addColumn(FAMILY, Bytes.toBytes("column1"), 3L, Bytes.toBytes("value1-3")); 191 p.addColumn(FAMILY, Bytes.toBytes("column2"), 1L, Bytes.toBytes("value2-1")); 192 p.addColumn(FAMILY, Bytes.toBytes("column2"), 2L, Bytes.toBytes("value2-2")); 193 p.addColumn(FAMILY, Bytes.toBytes("column2"), 3L, Bytes.toBytes("value2-3")); 194 p.addColumn(FAMILY, Bytes.toBytes("column3"), 2L, Bytes.toBytes("value3-2")); 195 p.addColumn(FAMILY, Bytes.toBytes("column4"), 1L, Bytes.toBytes("value4-1")); 196 p.addColumn(FAMILY, Bytes.toBytes("column4"), 2L, Bytes.toBytes("value4-2")); 197 p.addColumn(FAMILY, Bytes.toBytes("column4"), 3L, Bytes.toBytes("value4-3")); 198 ht.put(p); 199 200 ArrayList<Long> timestamps = new ArrayList<>(); 201 timestamps.add(new Long(3)); 202 TimestampsFilter filter = new TimestampsFilter(timestamps); 203 204 Get g = new Get(Bytes.toBytes("row")); 205 g.setFilter(filter); 206 g.setMaxVersions(); 207 g.addColumn(FAMILY, Bytes.toBytes("column2")); 208 g.addColumn(FAMILY, Bytes.toBytes("column4")); 209 210 Result result = ht.get(g); 211 for (Cell kv : result.listCells()) { 212 System.out.println("found row " + Bytes.toString(CellUtil.cloneRow(kv)) + 213 ", column " + Bytes.toString(CellUtil.cloneQualifier(kv)) + ", value " 214 + Bytes.toString(CellUtil.cloneValue(kv))); 215 } 216 217 assertEquals(2, result.listCells().size()); 218 assertTrue(CellUtil.matchingValue(result.listCells().get(0), Bytes.toBytes("value2-3"))); 219 assertTrue(CellUtil.matchingValue(result.listCells().get(1), Bytes.toBytes("value4-3"))); 220 221 ht.close(); 222 } 223 224 /** 225 * Test TimestampsFilter in the presence of version deletes. 226 * 227 * @throws Exception 228 */ 229 @Test 230 public void testWithVersionDeletes() throws Exception { 231 232 // first test from memstore (without flushing). 233 testWithVersionDeletes(false); 234 235 // run same test against HFiles (by forcing a flush). 236 testWithVersionDeletes(true); 237 } 238 239 private void testWithVersionDeletes(boolean flushTables) throws IOException { 240 final byte [] TABLE = Bytes.toBytes(name.getMethodName() + "_" + 241 (flushTables ? "flush" : "noflush")); 242 byte [] FAMILY = Bytes.toBytes("event_log"); 243 byte [][] FAMILIES = new byte[][] { FAMILY }; 244 245 // create table; set versions to max... 246 Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLE), FAMILIES, Integer.MAX_VALUE); 247 248 // For row:0, col:0: insert versions 1 through 5. 249 putNVersions(ht, FAMILY, 0, 0, 1, 5); 250 251 // delete version 4. 252 deleteOneVersion(ht, FAMILY, 0, 0, 4); 253 254 if (flushTables) { 255 TEST_UTIL.flush(); 256 } 257 258 // request a bunch of versions including the deleted version. We should 259 // only get back entries for the versions that exist. 260 Cell kvs[] = getNVersions(ht, FAMILY, 0, 0, Arrays.asList(2L, 3L, 4L, 5L)); 261 assertEquals(3, kvs.length); 262 checkOneCell(kvs[0], FAMILY, 0, 0, 5); 263 checkOneCell(kvs[1], FAMILY, 0, 0, 3); 264 checkOneCell(kvs[2], FAMILY, 0, 0, 2); 265 266 ht.close(); 267 } 268 269 private void verifyInsertedValues(Table ht, byte[] cf) throws IOException { 270 for (int rowIdx = 0; rowIdx < 5; rowIdx++) { 271 for (int colIdx = 0; colIdx < 5; colIdx++) { 272 // ask for versions that exist. 273 Cell[] kvs = getNVersions(ht, cf, rowIdx, colIdx, 274 Arrays.asList(5L, 300L, 6L, 80L)); 275 assertEquals(4, kvs.length); 276 checkOneCell(kvs[0], cf, rowIdx, colIdx, 300); 277 checkOneCell(kvs[1], cf, rowIdx, colIdx, 80); 278 checkOneCell(kvs[2], cf, rowIdx, colIdx, 6); 279 checkOneCell(kvs[3], cf, rowIdx, colIdx, 5); 280 281 // ask for versions that do not exist. 282 kvs = getNVersions(ht, cf, rowIdx, colIdx, 283 Arrays.asList(101L, 102L)); 284 assertEquals(0, kvs == null? 0: kvs.length); 285 286 // ask for some versions that exist and some that do not. 287 kvs = getNVersions(ht, cf, rowIdx, colIdx, 288 Arrays.asList(1L, 300L, 105L, 70L, 115L)); 289 assertEquals(3, kvs.length); 290 checkOneCell(kvs[0], cf, rowIdx, colIdx, 300); 291 checkOneCell(kvs[1], cf, rowIdx, colIdx, 70); 292 checkOneCell(kvs[2], cf, rowIdx, colIdx, 1); 293 } 294 } 295 } 296 297 /** 298 * Assert that the passed in KeyValue has expected contents for the 299 * specified row, column & timestamp. 300 */ 301 private void checkOneCell(Cell kv, byte[] cf, 302 int rowIdx, int colIdx, long ts) { 303 304 String ctx = "rowIdx=" + rowIdx + "; colIdx=" + colIdx + "; ts=" + ts; 305 306 assertEquals("Row mismatch which checking: " + ctx, 307 "row:"+ rowIdx, Bytes.toString(CellUtil.cloneRow(kv))); 308 309 assertEquals("ColumnFamily mismatch while checking: " + ctx, 310 Bytes.toString(cf), Bytes.toString(CellUtil.cloneFamily(kv))); 311 312 assertEquals("Column qualifier mismatch while checking: " + ctx, 313 "column:" + colIdx, 314 Bytes.toString(CellUtil.cloneQualifier(kv))); 315 316 assertEquals("Timestamp mismatch while checking: " + ctx, 317 ts, kv.getTimestamp()); 318 319 assertEquals("Value mismatch while checking: " + ctx, 320 "value-version-" + ts, Bytes.toString(CellUtil.cloneValue(kv))); 321 } 322 323 /** 324 * Uses the TimestampFilter on a Get to request a specified list of 325 * versions for the row/column specified by rowIdx & colIdx. 326 * 327 */ 328 private Cell[] getNVersions(Table ht, byte[] cf, int rowIdx, 329 int colIdx, List<Long> versions) 330 throws IOException { 331 byte row[] = Bytes.toBytes("row:" + rowIdx); 332 byte column[] = Bytes.toBytes("column:" + colIdx); 333 Filter filter = new TimestampsFilter(versions); 334 Get get = new Get(row); 335 get.addColumn(cf, column); 336 get.setFilter(filter); 337 get.setMaxVersions(); 338 Result result = ht.get(get); 339 340 return result.rawCells(); 341 } 342 343 /** 344 * Uses the TimestampFilter on a Scan to request a specified list of 345 * versions for the rows from startRowIdx to endRowIdx (both inclusive). 346 */ 347 private Result[] scanNVersions(Table ht, byte[] cf, int startRowIdx, 348 int endRowIdx, List<Long> versions) 349 throws IOException { 350 byte startRow[] = Bytes.toBytes("row:" + startRowIdx); 351 byte endRow[] = Bytes.toBytes("row:" + endRowIdx + 1); // exclusive 352 Filter filter = new TimestampsFilter(versions); 353 Scan scan = new Scan(startRow, endRow); 354 scan.setFilter(filter); 355 scan.setMaxVersions(); 356 ResultScanner scanner = ht.getScanner(scan); 357 return scanner.next(endRowIdx - startRowIdx + 1); 358 } 359 360 /** 361 * Insert in specific row/column versions with timestamps 362 * versionStart..versionEnd. 363 */ 364 private void putNVersions(Table ht, byte[] cf, int rowIdx, int colIdx, 365 long versionStart, long versionEnd) 366 throws IOException { 367 byte row[] = Bytes.toBytes("row:" + rowIdx); 368 byte column[] = Bytes.toBytes("column:" + colIdx); 369 Put put = new Put(row); 370 put.setDurability(Durability.SKIP_WAL); 371 372 for (long idx = versionStart; idx <= versionEnd; idx++) { 373 put.addColumn(cf, column, idx, Bytes.toBytes("value-version-" + idx)); 374 } 375 376 ht.put(put); 377 } 378 379 /** 380 * For row/column specified by rowIdx/colIdx, delete the cell 381 * corresponding to the specified version. 382 */ 383 private void deleteOneVersion(Table ht, byte[] cf, int rowIdx, 384 int colIdx, long version) 385 throws IOException { 386 byte row[] = Bytes.toBytes("row:" + rowIdx); 387 byte column[] = Bytes.toBytes("column:" + colIdx); 388 Delete del = new Delete(row); 389 del.addColumn(cf, column, version); 390 ht.delete(del); 391 } 392 393} 394 395