View Javadoc

1   /*
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.NavigableSet;
25  
26  import org.apache.hadoop.hbase.util.Bytes;
27  
28  /**
29   * This class is used for the tracking and enforcement of columns and numbers
30   * of versions during the course of a Get or Scan operation, when explicit
31   * column qualifiers have been asked for in the query.
32   *
33   * With a little magic (see {@link ScanQueryMatcher}), we can use this matcher
34   * for both scans and gets.  The main difference is 'next' and 'done' collapse
35   * for the scan case (since we see all columns in order), and we only reset
36   * between rows.
37   *
38   * <p>
39   * This class is utilized by {@link ScanQueryMatcher} through two methods:
40   * <ul><li>{@link #checkColumn} is called when a Put satisfies all other
41   * conditions of the query.  This method returns a {@link org.apache.hadoop.hbase.regionserver.ScanQueryMatcher.MatchCode} to define
42   * what action should be taken.
43   * <li>{@link #update} is called at the end of every StoreFile or memstore.
44   * <p>
45   * This class is NOT thread-safe as queries are never multi-threaded
46   */
47  public class ExplicitColumnTracker implements ColumnTracker {
48  
49    private final int maxVersions;
50    private final List<ColumnCount> columns;
51    private final List<ColumnCount> columnsToReuse;
52    private int index;
53    private ColumnCount column;
54  
55    /**
56     * Default constructor.
57     * @param columns columns specified user in query
58     * @param maxVersions maximum versions to return per column
59     */
60    public ExplicitColumnTracker(NavigableSet<byte[]> columns, int maxVersions) {
61      this.maxVersions = maxVersions;
62      this.columns = new ArrayList<ColumnCount>(columns.size());
63      this.columnsToReuse = new ArrayList<ColumnCount>(columns.size());
64      for(byte [] column : columns) {
65        this.columnsToReuse.add(new ColumnCount(column,maxVersions));
66      }
67      reset();
68    }
69  
70    /**
71     * Done when there are no more columns to match against.
72     */
73    public boolean done() {
74      return this.columns.size() == 0;
75    }
76  
77    public ColumnCount getColumnHint() {
78      return this.column;
79    }
80  
81    /**
82     * Checks against the parameters of the query and the columns which have
83     * already been processed by this query.
84     * @param bytes KeyValue buffer
85     * @param offset offset to the start of the qualifier
86     * @param length length of the qualifier
87     * @return MatchCode telling ScanQueryMatcher what action to take
88     */
89    public ScanQueryMatcher.MatchCode checkColumn(byte [] bytes, int offset, int length) {
90      do {
91        // No more columns left, we are done with this query
92        if(this.columns.size() == 0) {
93          return ScanQueryMatcher.MatchCode.DONE; // done_row
94        }
95  
96        // No more columns to match against, done with storefile
97        if(this.column == null) {
98          return ScanQueryMatcher.MatchCode.NEXT; // done_row
99        }
100 
101       // Compare specific column to current column
102       int ret = Bytes.compareTo(column.getBuffer(), column.getOffset(),
103           column.getLength(), bytes, offset, length);
104 
105       // Matches, decrement versions left and include
106       if(ret == 0) {
107         if(this.column.decrement() == 0) {
108           // Done with versions for this column
109           this.columns.remove(this.index);
110           if(this.columns.size() == this.index) {
111             // Will not hit any more columns in this storefile
112             this.column = null;
113           } else {
114             this.column = this.columns.get(this.index);
115           }
116         }
117         return ScanQueryMatcher.MatchCode.INCLUDE;
118       }
119 
120 
121       if (ret > 0) {
122          // Specified column is smaller than the current, skip to next column.
123         return ScanQueryMatcher.MatchCode.SKIP;
124       }
125 
126       // Specified column is bigger than current column
127       // Move down current column and check again
128       if(ret <= -1) {
129         if(++this.index == this.columns.size()) {
130           // No more to match, do not include, done with storefile
131           return ScanQueryMatcher.MatchCode.NEXT; // done_row
132         }
133         // This is the recursive case.
134         this.column = this.columns.get(this.index);
135       }
136     } while(true);
137   }
138 
139   /**
140    * Called at the end of every StoreFile or memstore.
141    */
142   public void update() {
143     if(this.columns.size() != 0) {
144       this.index = 0;
145       this.column = this.columns.get(this.index);
146     } else {
147       this.index = -1;
148       this.column = null;
149     }
150   }
151 
152   // Called between every row.
153   public void reset() {
154     buildColumnList();
155     this.index = 0;
156     this.column = this.columns.get(this.index);
157   }
158 
159   private void buildColumnList() {
160     this.columns.clear();
161     this.columns.addAll(this.columnsToReuse);
162     for(ColumnCount col : this.columns) {
163       col.setCount(this.maxVersions);
164     }
165   }
166 
167   /**
168    * This method is used to inform the column tracker that we are done with
169    * this column. We may get this information from external filters or
170    * timestamp range and we then need to indicate this information to
171    * tracker. It is required only in case of ExplicitColumnTracker.
172    * @param bytes
173    * @param offset
174    * @param length
175    */
176   public void doneWithColumn(byte [] bytes, int offset, int length) {
177     while (this.column != null) {
178       int compare = Bytes.compareTo(column.getBuffer(), column.getOffset(),
179           column.getLength(), bytes, offset, length);
180       if (compare == 0) {
181         this.columns.remove(this.index);
182         if (this.columns.size() == this.index) {
183           // Will not hit any more columns in this storefile
184           this.column = null;
185         } else {
186           this.column = this.columns.get(this.index);
187         }
188         return;
189       } else if ( compare <= -1) {
190         if(++this.index != this.columns.size()) {
191           this.column = this.columns.get(this.index);
192         } else {
193           this.column = null;
194         }
195       } else {
196         return;
197       }
198     }
199   }
200 
201 }