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 }