PHOENIX-4658 IllegalStateException: requestSeek cannot be called on ReversedKeyValueH...
[phoenix.git] / phoenix-core / src / main / java / org / apache / phoenix / parse / HintNode.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.phoenix.parse;
19
20 import java.util.HashMap;
21 import java.util.Map;
22
23 import org.apache.phoenix.util.SchemaUtil;
24 import org.apache.phoenix.util.StringUtil;
25
26 import com.google.common.collect.ImmutableMap;
27
28
29 /**
30 * Node representing optimizer hints in SQL
31 */
32 public class HintNode {
33 public static final HintNode EMPTY_HINT_NODE = new HintNode();
34
35 public static final char SEPARATOR = ' ';
36 public static final String PREFIX = "(";
37 public static final String SUFFIX = ")";
38 // Split on whitespace and parenthesis, keeping the parenthesis in the token array
39 private static final String SPLIT_REGEXP = "\\s+|((?<=\\" + PREFIX + ")|(?=\\" + PREFIX + "))|((?<=\\" + SUFFIX + ")|(?=\\" + SUFFIX + "))";
40
41 public enum Hint {
42 /**
43 * Forces a range scan to be used to process the query.
44 */
45 RANGE_SCAN,
46 /**
47 * Forces a skip scan to be used to process the query.
48 */
49 SKIP_SCAN,
50 /**
51 * Prevents the usage of child-parent-join optimization.
52 */
53 NO_CHILD_PARENT_JOIN_OPTIMIZATION,
54 /**
55 * Prevents the usage of indexes, forcing usage
56 * of the data table for a query.
57 */
58 NO_INDEX,
59 /**
60 * Hint of the form INDEX(<table_name> <index_name>...)
61 * to suggest usage of the index if possible. The first
62 * usable index in the list of indexes will be choosen.
63 * Table and index names may be surrounded by double quotes
64 * if they are case sensitive.
65 */
66 INDEX,
67 /**
68 * All things being equal, use the data table instead of
69 * the index table when optimizing.
70 */
71 USE_DATA_OVER_INDEX_TABLE,
72 /**
73 * All things being equal, use the index table instead of
74 * the data table when optimizing.
75 */
76 USE_INDEX_OVER_DATA_TABLE,
77 /**
78 * Avoid caching any HBase blocks loaded by this query.
79 */
80 NO_CACHE,
81 /**
82 * Use sort-merge join algorithm instead of broadcast join (hash join) algorithm.
83 */
84 USE_SORT_MERGE_JOIN,
85 /**
86 * Avoid using star-join optimization. Used for broadcast join (hash join) only.
87 */
88 NO_STAR_JOIN,
89 /**
90 * Avoid using the no seek optimization. When there are many columns which are not selected coming in between 2
91 * selected columns and/or versions of columns, this should be used.
92 */
93 SEEK_TO_COLUMN,
94 /**
95 * Avoid seeks to select specified columns. When there are very less number of columns which are not selected in
96 * between 2 selected columns this will be give better performance.
97 */
98 NO_SEEK_TO_COLUMN,
99 /**
100 * Saves an RPC call on the scan. See Scan.setSmall(true) in HBase documentation.
101 */
102 SMALL,
103 /**
104 * Enforces a serial scan.
105 */
106 SERIAL,
107 /**
108 * Enforces a forward scan.
109 */
110 FORWARD_SCAN,
111 };
112
113 private final Map<Hint,String> hints;
114
115 public static HintNode create(HintNode hintNode, Hint hint) {
116 return create(hintNode, hint, "");
117 }
118
119 public static HintNode create(HintNode hintNode, Hint hint, String value) {
120 Map<Hint,String> hints = new HashMap<Hint,String>(hintNode.hints);
121 hints.put(hint, value);
122 return new HintNode(hints);
123 }
124
125 public static HintNode combine(HintNode hintNode, HintNode override) {
126 Map<Hint,String> hints = new HashMap<Hint,String>(hintNode.hints);
127 hints.putAll(override.hints);
128 return new HintNode(hints);
129 }
130
131 public static HintNode subtract(HintNode hintNode, Hint[] remove) {
132 Map<Hint,String> hints = new HashMap<Hint,String>(hintNode.hints);
133 for (Hint hint : remove) {
134 hints.remove(hint);
135 }
136 return new HintNode(hints);
137 }
138
139 private HintNode() {
140 hints = new HashMap<Hint,String>();
141 }
142
143 private HintNode(Map<Hint,String> hints) {
144 this.hints = ImmutableMap.copyOf(hints);
145 }
146
147 public HintNode(String hint) {
148 Map<Hint,String> hints = new HashMap<Hint,String>();
149 // Split on whitespace or parenthesis. We do not need to handle escaped or
150 // embedded whitespace/parenthesis, since we are parsing what will be HBase
151 // table names which are not allowed to contain whitespace or parenthesis.
152 String[] hintWords = hint.split(SPLIT_REGEXP);
153 for (int i = 0; i < hintWords.length; i++) {
154 String hintWord = hintWords[i];
155 if (hintWord.isEmpty()) {
156 continue;
157 }
158 try {
159 Hint key = Hint.valueOf(hintWord.toUpperCase());
160 String hintValue = "";
161 if (i+1 < hintWords.length && PREFIX.equals(hintWords[i+1])) {
162 StringBuffer hintValueBuf = new StringBuffer(hint.length());
163 hintValueBuf.append(PREFIX);
164 i+=2;
165 while (i < hintWords.length && !SUFFIX.equals(hintWords[i])) {
166 hintValueBuf.append(SchemaUtil.normalizeIdentifier(hintWords[i++]));
167 hintValueBuf.append(SEPARATOR);
168 }
169 // Replace trailing separator with suffix
170 hintValueBuf.replace(hintValueBuf.length()-1, hintValueBuf.length(), SUFFIX);
171 hintValue = hintValueBuf.toString();
172 }
173 String oldValue = hints.put(key, hintValue);
174 // Concatenate together any old value with the new value
175 if (oldValue != null) {
176 hints.put(key, oldValue + hintValue);
177 }
178 } catch (IllegalArgumentException e) { // Ignore unknown/invalid hints
179 }
180 }
181 this.hints = ImmutableMap.copyOf(hints);
182 }
183
184 public boolean isEmpty() {
185 return hints.isEmpty();
186 }
187
188 /**
189 * Gets the value of the hint or null if the hint is not present.
190 * @param hint the hint
191 * @return the value specified in parenthesis following the hint or null
192 * if the hint is not present.
193 *
194 */
195 public String getHint(Hint hint) {
196 return hints.get(hint);
197 }
198
199 /**
200 * Tests for the presence of a hint in a query
201 * @param hint the hint
202 * @return true if the hint is present and false otherwise
203 */
204 public boolean hasHint(Hint hint) {
205 return hints.containsKey(hint);
206 }
207
208 @Override
209 public String toString() {
210 if (hints.isEmpty()) {
211 return StringUtil.EMPTY_STRING;
212 }
213 StringBuilder buf = new StringBuilder("/*+ ");
214 for (Map.Entry<Hint, String> entry : hints.entrySet()) {
215 buf.append(entry.getKey());
216 buf.append(entry.getValue());
217 buf.append(' ');
218 }
219 buf.append("*/ ");
220 return buf.toString();
221 }
222
223 @Override
224 public int hashCode() {
225 final int prime = 31;
226 int result = 1;
227 result = prime * result + ((hints == null) ? 0 : hints.hashCode());
228 return result;
229 }
230
231 @Override
232 public boolean equals(Object obj) {
233 if (this == obj) return true;
234 if (obj == null) return false;
235 if (getClass() != obj.getClass()) return false;
236 HintNode other = (HintNode)obj;
237 if (hints == null) {
238 if (other.hints != null) return false;
239 } else if (!hints.equals(other.hints)) return false;
240 return true;
241 }
242 }