View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import static org.apache.hadoop.hbase.regionserver.ScanQueryMatcher.MatchCode.INCLUDE;
23  import static org.apache.hadoop.hbase.regionserver.ScanQueryMatcher.MatchCode.SKIP;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertFalse;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.NavigableSet;
31  
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.KeepDeletedCells;
37  import org.apache.hadoop.hbase.KeyValue;
38  import org.apache.hadoop.hbase.KeyValue.KVComparator;
39  import org.apache.hadoop.hbase.KeyValue.Type;
40  import org.apache.hadoop.hbase.KeyValueUtil;
41  import org.apache.hadoop.hbase.client.Get;
42  import org.apache.hadoop.hbase.client.Scan;
43  import org.apache.hadoop.hbase.regionserver.ScanQueryMatcher.MatchCode;
44  import org.apache.hadoop.hbase.testclassification.SmallTests;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
47  import org.junit.Before;
48  import org.junit.Test;
49  import org.junit.experimental.categories.Category;
50  
51  @Category(SmallTests.class)
52  public class TestQueryMatcher {
53    private static final boolean PRINT = false;
54    private Configuration conf;
55  
56    private byte[] row1;
57    private byte[] row2;
58    private byte[] row3;
59    private byte[] fam1;
60    private byte[] fam2;
61    private byte[] col1;
62    private byte[] col2;
63    private byte[] col3;
64    private byte[] col4;
65    private byte[] col5;
66  
67    private byte[] data;
68  
69    private Get get;
70  
71    long ttl = Long.MAX_VALUE;
72    KVComparator rowComparator;
73    private Scan scan;
74  
75    @Before
76    public void setUp() throws Exception {
77      this.conf = HBaseConfiguration.create();
78      row1 = Bytes.toBytes("row1");
79      row2 = Bytes.toBytes("row2");
80      row3 = Bytes.toBytes("row3");
81      fam1 = Bytes.toBytes("fam1");
82      fam2 = Bytes.toBytes("fam2");
83      col1 = Bytes.toBytes("col1");
84      col2 = Bytes.toBytes("col2");
85      col3 = Bytes.toBytes("col3");
86      col4 = Bytes.toBytes("col4");
87      col5 = Bytes.toBytes("col5");
88  
89      data = Bytes.toBytes("data");
90  
91      //Create Get
92      get = new Get(row1);
93      get.addFamily(fam1);
94      get.addColumn(fam2, col2);
95      get.addColumn(fam2, col4);
96      get.addColumn(fam2, col5);
97      this.scan = new Scan(get);
98  
99      rowComparator = KeyValue.COMPARATOR;
100   }
101 
102   /**
103    * This is a cryptic test. It is checking that we don't include a fake cell, one that has a
104    * timestamp of {@link HConstants#OLDEST_TIMESTAMP}. See HBASE-16074 for background.
105    * @throws IOException
106    */
107   @Test
108   public void testNeverIncludeFakeCell() throws IOException {
109     long now = EnvironmentEdgeManager.currentTime();
110     // Do with fam2 which has a col2 qualifier.
111     ScanQueryMatcher qm = new ScanQueryMatcher(scan,
112         new ScanInfo(this.conf, fam2, 10, 1, ttl, KeepDeletedCells.FALSE, 0, rowComparator),
113         get.getFamilyMap().get(fam2), now - ttl, now);
114     Cell kv = new KeyValue(row1, fam2, col2, 1, data);
115     Cell cell = KeyValueUtil.createLastOnRowCol(kv);
116     qm.setRow(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength());
117     MatchCode code = qm.match(cell);
118     assertFalse(code.compareTo(MatchCode.SEEK_NEXT_COL) != 0);
119   }
120 
121   private void _testMatch_ExplicitColumns(Scan scan, List<MatchCode> expected) throws IOException {
122     long now = EnvironmentEdgeManager.currentTime();
123     // 2,4,5
124     ScanQueryMatcher qm = new ScanQueryMatcher(scan, new ScanInfo(this.conf, fam2,
125         0, 1, ttl, KeepDeletedCells.FALSE, 0, rowComparator), get.getFamilyMap().get(fam2),
126         now - ttl, now);
127 
128     List<KeyValue> memstore = new ArrayList<KeyValue>();
129     memstore.add(new KeyValue(row1, fam2, col1, 1, data));
130     memstore.add(new KeyValue(row1, fam2, col2, 1, data));
131     memstore.add(new KeyValue(row1, fam2, col3, 1, data));
132     memstore.add(new KeyValue(row1, fam2, col4, 1, data));
133     memstore.add(new KeyValue(row1, fam2, col5, 1, data));
134 
135     memstore.add(new KeyValue(row2, fam1, col1, data));
136 
137     List<ScanQueryMatcher.MatchCode> actual = new ArrayList<ScanQueryMatcher.MatchCode>();
138     KeyValue k = memstore.get(0);
139     qm.setRow(k.getRowArray(), k.getRowOffset(), k.getRowLength());
140 
141     for (KeyValue kv : memstore){
142       actual.add(qm.match(kv));
143     }
144 
145     assertEquals(expected.size(), actual.size());
146     for(int i=0; i< expected.size(); i++){
147       assertEquals(expected.get(i), actual.get(i));
148       if(PRINT){
149         System.out.println("expected "+expected.get(i)+
150             ", actual " +actual.get(i));
151       }
152     }
153   }
154 
155   @Test
156   public void testMatch_ExplicitColumns()
157   throws IOException {
158     //Moving up from the Tracker by using Gets and List<KeyValue> instead
159     //of just byte []
160 
161     //Expected result
162     List<MatchCode> expected = new ArrayList<ScanQueryMatcher.MatchCode>();
163     expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL);
164     expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL);
165     expected.add(ScanQueryMatcher.MatchCode.SEEK_NEXT_COL);
166     expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL);
167     expected.add(ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_ROW);
168     expected.add(ScanQueryMatcher.MatchCode.DONE);
169 
170     _testMatch_ExplicitColumns(scan, expected);
171   }
172 
173   @Test
174   public void testMatch_Wildcard()
175   throws IOException {
176     //Moving up from the Tracker by using Gets and List<KeyValue> instead
177     //of just byte []
178 
179     //Expected result
180     List<MatchCode> expected = new ArrayList<ScanQueryMatcher.MatchCode>();
181     expected.add(ScanQueryMatcher.MatchCode.INCLUDE);
182     expected.add(ScanQueryMatcher.MatchCode.INCLUDE);
183     expected.add(ScanQueryMatcher.MatchCode.INCLUDE);
184     expected.add(ScanQueryMatcher.MatchCode.INCLUDE);
185     expected.add(ScanQueryMatcher.MatchCode.INCLUDE);
186     expected.add(ScanQueryMatcher.MatchCode.DONE);
187 
188     long now = EnvironmentEdgeManager.currentTime();
189     ScanQueryMatcher qm = new ScanQueryMatcher(scan, new ScanInfo(this.conf, fam2,
190         0, 1, ttl, KeepDeletedCells.FALSE, 0, rowComparator), null,
191         now - ttl, now);
192 
193     List<KeyValue> memstore = new ArrayList<KeyValue>();
194     memstore.add(new KeyValue(row1, fam2, col1, 1, data));
195     memstore.add(new KeyValue(row1, fam2, col2, 1, data));
196     memstore.add(new KeyValue(row1, fam2, col3, 1, data));
197     memstore.add(new KeyValue(row1, fam2, col4, 1, data));
198     memstore.add(new KeyValue(row1, fam2, col5, 1, data));
199     memstore.add(new KeyValue(row2, fam1, col1, 1, data));
200 
201     List<ScanQueryMatcher.MatchCode> actual = new ArrayList<ScanQueryMatcher.MatchCode>();
202 
203     KeyValue k = memstore.get(0);
204     qm.setRow(k.getRowArray(), k.getRowOffset(), k.getRowLength());
205 
206     for(KeyValue kv : memstore) {
207       actual.add(qm.match(kv));
208     }
209 
210     assertEquals(expected.size(), actual.size());
211     for(int i=0; i< expected.size(); i++){
212       assertEquals(expected.get(i), actual.get(i));
213       if(PRINT){
214         System.out.println("expected "+expected.get(i)+
215             ", actual " +actual.get(i));
216       }
217     }
218   }
219 
220 
221   /**
222    * Verify that {@link ScanQueryMatcher} only skips expired KeyValue
223    * instances and does not exit early from the row (skipping
224    * later non-expired KeyValues).  This version mimics a Get with
225    * explicitly specified column qualifiers.
226    *
227    * @throws IOException
228    */
229   @Test
230   public void testMatch_ExpiredExplicit()
231   throws IOException {
232 
233     long testTTL = 1000;
234     MatchCode [] expected = new MatchCode[] {
235         ScanQueryMatcher.MatchCode.SEEK_NEXT_COL,
236         ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL,
237         ScanQueryMatcher.MatchCode.SEEK_NEXT_COL,
238         ScanQueryMatcher.MatchCode.INCLUDE_AND_SEEK_NEXT_COL,
239         ScanQueryMatcher.MatchCode.SEEK_NEXT_ROW,
240         ScanQueryMatcher.MatchCode.DONE
241     };
242 
243     long now = EnvironmentEdgeManager.currentTime();
244     ScanQueryMatcher qm =
245       new ScanQueryMatcher(scan,
246         new ScanInfo(this.conf, fam2, 0, 1, testTTL, KeepDeletedCells.FALSE, 0,
247           rowComparator), get.getFamilyMap().get(fam2), now - testTTL, now);
248 
249     KeyValue [] kvs = new KeyValue[] {
250         new KeyValue(row1, fam2, col1, now-100, data),
251         new KeyValue(row1, fam2, col2, now-50, data),
252         new KeyValue(row1, fam2, col3, now-5000, data),
253         new KeyValue(row1, fam2, col4, now-500, data),
254         new KeyValue(row1, fam2, col5, now-10000, data),
255         new KeyValue(row2, fam1, col1, now-10, data)
256     };
257 
258     KeyValue k = kvs[0];
259     qm.setRow(k.getRowArray(), k.getRowOffset(), k.getRowLength());
260 
261     List<MatchCode> actual = new ArrayList<MatchCode>(kvs.length);
262     for (KeyValue kv : kvs) {
263       actual.add( qm.match(kv) );
264     }
265 
266     assertEquals(expected.length, actual.size());
267     for (int i=0; i<expected.length; i++) {
268       if(PRINT){
269         System.out.println("expected "+expected[i]+
270             ", actual " +actual.get(i));
271       }
272       assertEquals(expected[i], actual.get(i));
273     }
274   }
275 
276   /**
277    * Verify that {@link ScanQueryMatcher} only skips expired KeyValue
278    * instances and does not exit early from the row (skipping
279    * later non-expired KeyValues).  This version mimics a Get with
280    * wildcard-inferred column qualifiers.
281    *
282    * @throws IOException
283    */
284   @Test
285   public void testMatch_ExpiredWildcard()
286   throws IOException {
287 
288     long testTTL = 1000;
289     MatchCode [] expected = new MatchCode[] {
290         ScanQueryMatcher.MatchCode.INCLUDE,
291         ScanQueryMatcher.MatchCode.INCLUDE,
292         ScanQueryMatcher.MatchCode.SEEK_NEXT_COL,
293         ScanQueryMatcher.MatchCode.INCLUDE,
294         ScanQueryMatcher.MatchCode.SEEK_NEXT_COL,
295         ScanQueryMatcher.MatchCode.DONE
296     };
297 
298     long now = EnvironmentEdgeManager.currentTime();
299     ScanQueryMatcher qm = new ScanQueryMatcher(scan, new ScanInfo(this.conf, fam2,
300         0, 1, testTTL, KeepDeletedCells.FALSE, 0, rowComparator), null,
301         now - testTTL, now);
302 
303     KeyValue [] kvs = new KeyValue[] {
304         new KeyValue(row1, fam2, col1, now-100, data),
305         new KeyValue(row1, fam2, col2, now-50, data),
306         new KeyValue(row1, fam2, col3, now-5000, data),
307         new KeyValue(row1, fam2, col4, now-500, data),
308         new KeyValue(row1, fam2, col5, now-10000, data),
309         new KeyValue(row2, fam1, col1, now-10, data)
310     };
311     KeyValue k = kvs[0];
312     qm.setRow(k.getRowArray(), k.getRowOffset(), k.getRowLength());
313 
314     List<ScanQueryMatcher.MatchCode> actual =
315         new ArrayList<ScanQueryMatcher.MatchCode>(kvs.length);
316     for (KeyValue kv : kvs) {
317       actual.add( qm.match(kv) );
318     }
319 
320     assertEquals(expected.length, actual.size());
321     for (int i=0; i<expected.length; i++) {
322       if(PRINT){
323         System.out.println("expected "+expected[i]+
324             ", actual " +actual.get(i));
325       }
326       assertEquals(expected[i], actual.get(i));
327     }
328   }
329 
330   @Test
331   public void testMatch_PartialRangeDropDeletes() throws Exception {
332     // Some ranges.
333     testDropDeletes(
334         row2, row3, new byte[][] { row1, row2, row2, row3 }, INCLUDE, SKIP, SKIP, INCLUDE);
335     testDropDeletes(row2, row3, new byte[][] { row1, row1, row2 }, INCLUDE, INCLUDE, SKIP);
336     testDropDeletes(row2, row3, new byte[][] { row2, row3, row3 }, SKIP, INCLUDE, INCLUDE);
337     testDropDeletes(row1, row3, new byte[][] { row1, row2, row3 }, SKIP, SKIP, INCLUDE);
338     // Open ranges.
339     testDropDeletes(HConstants.EMPTY_START_ROW, row3,
340         new byte[][] { row1, row2, row3 }, SKIP, SKIP, INCLUDE);
341     testDropDeletes(row2, HConstants.EMPTY_END_ROW,
342         new byte[][] { row1, row2, row3 }, INCLUDE, SKIP, SKIP);
343     testDropDeletes(HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW,
344         new byte[][] { row1, row2, row3, row3 }, SKIP, SKIP, SKIP, SKIP);
345 
346     // No KVs in range.
347     testDropDeletes(row2, row3, new byte[][] { row1, row1, row3 }, INCLUDE, INCLUDE, INCLUDE);
348     testDropDeletes(row2, row3, new byte[][] { row3, row3 }, INCLUDE, INCLUDE);
349     testDropDeletes(row2, row3, new byte[][] { row1, row1 }, INCLUDE, INCLUDE);
350   }
351 
352   private void testDropDeletes(
353       byte[] from, byte[] to, byte[][] rows, MatchCode... expected) throws IOException {
354     long now = EnvironmentEdgeManager.currentTime();
355     // Set time to purge deletes to negative value to avoid it ever happening.
356     ScanInfo scanInfo =
357       new ScanInfo(this.conf, fam2, 0, 1, ttl, KeepDeletedCells.FALSE, -1L, rowComparator);
358     NavigableSet<byte[]> cols = get.getFamilyMap().get(fam2);
359 
360     ScanQueryMatcher qm = new ScanQueryMatcher(scan, scanInfo, cols, Long.MAX_VALUE,
361         HConstants.OLDEST_TIMESTAMP, HConstants.OLDEST_TIMESTAMP, now, from, to, null);
362     List<ScanQueryMatcher.MatchCode> actual =
363         new ArrayList<ScanQueryMatcher.MatchCode>(rows.length);
364     byte[] prevRow = null;
365     for (byte[] row : rows) {
366       if (prevRow == null || !Bytes.equals(prevRow, row)) {
367         qm.setRow(row, 0, (short)row.length);
368         prevRow = row;
369       }
370       actual.add(qm.match(new KeyValue(row, fam2, null, now, Type.Delete)));
371     }
372 
373     assertEquals(expected.length, actual.size());
374     for (int i = 0; i < expected.length; i++) {
375       if (PRINT) System.out.println("expected " + expected[i] + ", actual " + actual.get(i));
376       assertEquals(expected[i], actual.get(i));
377     }
378   }
379 }