View Javadoc

1   /*
2    * Copyright (c) 2011.  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  
21  package org.apache.hadoop.hbase.hbql.statement.args;
22  
23  import org.apache.expreval.client.InternalErrorException;
24  import org.apache.expreval.client.NullColumnValueException;
25  import org.apache.expreval.client.ResultMissingColumnException;
26  import org.apache.expreval.expr.TypeSupport;
27  import org.apache.expreval.expr.node.GenericValue;
28  import org.apache.hadoop.hbase.client.Get;
29  import org.apache.hadoop.hbase.client.Scan;
30  import org.apache.hadoop.hbase.hbql.client.HBqlException;
31  import org.apache.hadoop.hbase.hbql.io.IO;
32  import org.apache.hadoop.hbase.hbql.mapping.ColumnAttrib;
33  import org.apache.hadoop.hbase.hbql.statement.select.GetRequest;
34  import org.apache.hadoop.hbase.hbql.statement.select.IndexRequest;
35  import org.apache.hadoop.hbase.hbql.statement.select.RowRequest;
36  import org.apache.hadoop.hbase.hbql.statement.select.ScanRequest;
37  import org.apache.hadoop.hbase.hbql.util.Lists;
38  
39  import java.util.Arrays;
40  import java.util.Collection;
41  import java.util.List;
42  import java.util.Set;
43  
44  public class KeyRange extends SelectStatementArgs {
45  
46      private enum RangeType {
47          SINGLE, RANGE, FIRST, LAST, ALL
48      }
49  
50      private final RangeType rangeType;
51  
52      private KeyRange() {
53          super(ArgType.NOARGSKEY);
54          this.rangeType = RangeType.ALL;
55      }
56  
57      private KeyRange(final RangeType rangeType, final GenericValue arg0) {
58          super(ArgType.SINGLEKEY, arg0);
59          this.rangeType = rangeType;
60      }
61  
62      private KeyRange(final GenericValue arg0, final GenericValue arg1) {
63          super(ArgType.KEYRANGE, arg0, arg1);
64          this.rangeType = RangeType.RANGE;
65      }
66  
67      public static KeyRange newRange(final GenericValue arg0, final GenericValue arg1) {
68          return new KeyRange(arg0, arg1);
69      }
70  
71      public static KeyRange newSingleKey(final GenericValue arg0) {
72          return new KeyRange(RangeType.SINGLE, arg0);
73      }
74  
75      public static KeyRange newFirstRange(final GenericValue arg0) {
76          return new KeyRange(RangeType.FIRST, arg0);
77      }
78  
79      public static KeyRange newLastRange(final GenericValue arg0) {
80          return new KeyRange(RangeType.LAST, arg0);
81      }
82  
83      public static KeyRange newAllRange() {
84          return new KeyRange();
85      }
86  
87      private RangeType getKeyRangeType() {
88          return this.rangeType;
89      }
90  
91      public boolean isSingleKey() {
92          return this.getKeyRangeType() == RangeType.SINGLE;
93      }
94  
95      public void validate() throws HBqlException {
96          this.validateTypes(this.allowColumns(), this.isSingleKey());
97      }
98  
99      // This returns an Object because it might be a collection in the case of a named param
100     private Object getFirstArg(final boolean allowCollections) throws HBqlException {
101         return this.evaluateConstant(0, allowCollections);
102     }
103 
104     private String getSecondArg() throws HBqlException {
105         return (String)this.evaluateConstant(1, false);
106     }
107 
108     public List<RowRequest> getRowRequestList(final WithArgs withArgs,
109                                               final ColumnAttrib keyAttrib,
110                                               final Set<ColumnAttrib> columnAttribs) throws HBqlException {
111 
112         return this.isSingleKey() ? this.getGetRequest(withArgs, keyAttrib, columnAttribs)
113                                   : this.getScanRequest(withArgs, keyAttrib, columnAttribs);
114     }
115 
116     private List<RowRequest> getGetRequest(final WithArgs withArgs,
117                                            final ColumnAttrib keyAttrib,
118                                            final Set<ColumnAttrib> columnAttribs) throws HBqlException {
119 
120         final List<RowRequest> rowRequestList = Lists.newArrayList();
121 
122         // Check if the value returned is a collection
123         final Object objval = this.getFirstArg(true);
124         if (TypeSupport.isACollection(objval)) {
125             for (final GenericValue val : (Collection<GenericValue>)objval) {
126                 try {
127                     final String rangeValue = (String)val.getValue(null, null);
128                     final RowRequest rowRequest = this.newGet(withArgs, columnAttribs, keyAttrib, rangeValue);
129                     rowRequestList.add(rowRequest);
130                 }
131                 catch (ResultMissingColumnException e) {
132                     throw new InternalErrorException("Missing column: " + val.asString());
133                 }
134                 catch (NullColumnValueException e) {
135                     throw new InternalErrorException("Null value: " + e.getMessage());
136                 }
137             }
138         }
139         else {
140             final String rangeValue = (String)objval;
141             final RowRequest rowRequest = this.newGet(withArgs, columnAttribs, keyAttrib, rangeValue);
142             rowRequestList.add(rowRequest);
143         }
144 
145         return rowRequestList;
146     }
147 
148     private RowRequest newGet(final WithArgs withArgs,
149                               final Set<ColumnAttrib> columnAttribs,
150                               final ColumnAttrib keyAttrib,
151                               final String rangeValue) throws HBqlException {
152 
153         this.verifyRangeValueWidth(keyAttrib, rangeValue);
154 
155         final byte[] lowerBytes = IO.getSerialization().getStringAsBytes(rangeValue);
156 
157         if (withArgs.hasAnIndex()) {
158             final byte[] upperBytes = Arrays.copyOf(lowerBytes, lowerBytes.length);
159             // Increment final byte because the range in index is inclusive/exclusive
160             upperBytes[lowerBytes.length - 1]++;
161             return new IndexRequest(lowerBytes, upperBytes, columnAttribs);
162         }
163         else {
164             // Use Get if no server filter is used
165             if (withArgs.getServerExpressionTree() == null) {
166                 final Get get = new Get(lowerBytes);
167                 withArgs.setGetArgs(get, columnAttribs);
168                 return new GetRequest(get);
169             }
170             else {
171                 // TODO This is temporay until Get is fixed for Filters
172                 final Scan scan = new Scan();
173 
174                 scan.setStartRow(lowerBytes);
175 
176                 final byte[] upperBytes = Arrays.copyOf(lowerBytes, lowerBytes.length);
177                 // Increment final byte because the range in index is inclusive/exclusive
178                 upperBytes[lowerBytes.length - 1]++;
179                 scan.setStopRow(upperBytes);
180 
181                 withArgs.setScanArgs(scan, columnAttribs);
182                 return new ScanRequest(scan);
183             }
184         }
185     }
186 
187     private List<RowRequest> getScanRequest(final WithArgs withArgs,
188                                             final ColumnAttrib keyAttrib,
189                                             final Set<ColumnAttrib> columnAttribs) throws HBqlException {
190 
191         final Scan scan = new Scan();
192         this.setStartStopRows(scan, keyAttrib);
193         withArgs.setScanArgs(scan, columnAttribs);
194         final RowRequest rowRequest = withArgs.hasAnIndex()
195                                       ? new IndexRequest(scan.getStartRow(), scan.getStopRow(), columnAttribs)
196                                       : new ScanRequest(scan);
197 
198         return Lists.newArrayList(rowRequest);
199     }
200 
201     private void setStartStopRows(final Scan scan, final ColumnAttrib keyAttrib) throws HBqlException {
202 
203         switch (this.getKeyRangeType()) {
204             case ALL: {
205                 // Scan will default to all rows
206                 break;
207             }
208             case FIRST: {
209                 final String rangeValue = (String)this.getFirstArg(false);
210                 this.verifyRangeValueWidth(keyAttrib, rangeValue);
211                 final byte[] upperBytes = IO.getSerialization().getStringAsBytes(rangeValue);
212                 scan.setStopRow(upperBytes);
213                 break;
214             }
215             case LAST: {
216                 final String rangeValue = (String)this.getFirstArg(false);
217                 this.verifyRangeValueWidth(keyAttrib, rangeValue);
218                 final byte[] lowerBytes = IO.getSerialization().getStringAsBytes(rangeValue);
219                 scan.setStartRow(lowerBytes);
220                 break;
221             }
222             case RANGE: {
223                 final String firstRangeValue = (String)this.getFirstArg(false);
224                 final String secondRangeValue = this.getSecondArg();
225                 this.verifyRangeValueWidth(keyAttrib, firstRangeValue, secondRangeValue);
226                 final byte[] lowerBytes = IO.getSerialization().getStringAsBytes(firstRangeValue);
227                 final byte[] upperBytes = IO.getSerialization().getStringAsBytes(secondRangeValue);
228                 scan.setStartRow(lowerBytes);
229                 scan.setStopRow(upperBytes);
230                 break;
231             }
232             default:
233                 throw new InternalErrorException("Invalid range type: " + this.getKeyRangeType().name());
234         }
235     }
236 
237     private void verifyRangeValueWidth(final ColumnAttrib keyAttrib, final String... rangeValues) throws HBqlException {
238 
239         final ColumnWidth columnWidth = keyAttrib.getColumnDefinition().getColumnWidth();
240         if (columnWidth.isWidthSpecified()) {
241             for (final String rangeValue : rangeValues) {
242                 final int width = columnWidth.getWidth();
243                 if (width > 0 && rangeValue.length() != width)
244                     throw new HBqlException("Invalid key range length in " + this.asString()
245                                             + " expecting width " + width + " but found " + rangeValue.length()
246                                             + " with key \"" + rangeValue + "\"");
247             }
248         }
249     }
250 
251     public String asString() {
252 
253         final StringBuilder sbuf = new StringBuilder();
254 
255         switch (this.getKeyRangeType()) {
256             case ALL: {
257                 sbuf.append("KEYS ALL");
258                 break;
259             }
260             case SINGLE: {
261                 sbuf.append("KEY " + this.getGenericValue(0).asString());
262                 break;
263             }
264             case FIRST: {
265                 sbuf.append("KEYS FIRST TO " + this.getGenericValue(0).asString());
266                 break;
267             }
268             case LAST: {
269                 sbuf.append("KEYS " + this.getGenericValue(0).asString() + "' TO LAST");
270                 break;
271             }
272             case RANGE: {
273                 sbuf.append("KEYS " + this.getGenericValue(0).asString());
274                 sbuf.append(" TO ");
275                 sbuf.append(this.getGenericValue(1).asString());
276                 break;
277             }
278             default:
279                 throw new RuntimeException("Invalid range type: " + this.getKeyRangeType().name());
280         }
281 
282         return sbuf.toString();
283     }
284 }