1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.index; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.Arrays; |
10 | import java.util.Comparator; |
11 | import java.util.HashSet; |
12 | import java.util.List; |
13 | import org.h2.command.dml.Query; |
14 | import org.h2.engine.Session; |
15 | import org.h2.expression.Comparison; |
16 | import org.h2.expression.Expression; |
17 | import org.h2.expression.ExpressionColumn; |
18 | import org.h2.expression.ExpressionVisitor; |
19 | import org.h2.message.DbException; |
20 | import org.h2.result.ResultInterface; |
21 | import org.h2.table.Column; |
22 | import org.h2.table.Table; |
23 | import org.h2.util.StatementBuilder; |
24 | import org.h2.value.CompareMode; |
25 | import org.h2.value.Value; |
26 | |
27 | /** |
28 | * A index condition object is made for each condition that can potentially use |
29 | * an index. This class does not extend expression, but in general there is one |
30 | * expression that maps to each index condition. |
31 | * |
32 | * @author Thomas Mueller |
33 | * @author Noel Grandin |
34 | * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 |
35 | */ |
36 | public class IndexCondition { |
37 | |
38 | /** |
39 | * A bit of a search mask meaning 'equal'. |
40 | */ |
41 | public static final int EQUALITY = 1; |
42 | |
43 | /** |
44 | * A bit of a search mask meaning 'larger or equal'. |
45 | */ |
46 | public static final int START = 2; |
47 | |
48 | /** |
49 | * A bit of a search mask meaning 'smaller or equal'. |
50 | */ |
51 | public static final int END = 4; |
52 | |
53 | /** |
54 | * A search mask meaning 'between'. |
55 | */ |
56 | public static final int RANGE = START | END; |
57 | |
58 | /** |
59 | * A bit of a search mask meaning 'the condition is always false'. |
60 | */ |
61 | public static final int ALWAYS_FALSE = 8; |
62 | |
63 | /** |
64 | * A bit of a search mask meaning 'spatial intersection'. |
65 | */ |
66 | public static final int SPATIAL_INTERSECTS = 16; |
67 | |
68 | private final Column column; |
69 | /** |
70 | * see constants in {@link Comparison} |
71 | */ |
72 | private final int compareType; |
73 | |
74 | private final Expression expression; |
75 | private List<Expression> expressionList; |
76 | private Query expressionQuery; |
77 | |
78 | /** |
79 | * @param compareType the comparison type, see constants in |
80 | * {@link Comparison} |
81 | */ |
82 | private IndexCondition(int compareType, ExpressionColumn column, |
83 | Expression expression) { |
84 | this.compareType = compareType; |
85 | this.column = column == null ? null : column.getColumn(); |
86 | this.expression = expression; |
87 | } |
88 | |
89 | /** |
90 | * Create an index condition with the given parameters. |
91 | * |
92 | * @param compareType the comparison type, see constants in |
93 | * {@link Comparison} |
94 | * @param column the column |
95 | * @param expression the expression |
96 | * @return the index condition |
97 | */ |
98 | public static IndexCondition get(int compareType, ExpressionColumn column, |
99 | Expression expression) { |
100 | return new IndexCondition(compareType, column, expression); |
101 | } |
102 | |
103 | /** |
104 | * Create an index condition with the compare type IN_LIST and with the |
105 | * given parameters. |
106 | * |
107 | * @param column the column |
108 | * @param list the expression list |
109 | * @return the index condition |
110 | */ |
111 | public static IndexCondition getInList(ExpressionColumn column, |
112 | List<Expression> list) { |
113 | IndexCondition cond = new IndexCondition(Comparison.IN_LIST, column, null); |
114 | cond.expressionList = list; |
115 | return cond; |
116 | } |
117 | |
118 | /** |
119 | * Create an index condition with the compare type IN_QUERY and with the |
120 | * given parameters. |
121 | * |
122 | * @param column the column |
123 | * @param query the select statement |
124 | * @return the index condition |
125 | */ |
126 | public static IndexCondition getInQuery(ExpressionColumn column, Query query) { |
127 | IndexCondition cond = new IndexCondition(Comparison.IN_QUERY, column, null); |
128 | cond.expressionQuery = query; |
129 | return cond; |
130 | } |
131 | |
132 | /** |
133 | * Get the current value of the expression. |
134 | * |
135 | * @param session the session |
136 | * @return the value |
137 | */ |
138 | public Value getCurrentValue(Session session) { |
139 | return expression.getValue(session); |
140 | } |
141 | |
142 | /** |
143 | * Get the current value list of the expression. The value list is of the |
144 | * same type as the column, distinct, and sorted. |
145 | * |
146 | * @param session the session |
147 | * @return the value list |
148 | */ |
149 | public Value[] getCurrentValueList(Session session) { |
150 | HashSet<Value> valueSet = new HashSet<Value>(); |
151 | for (Expression e : expressionList) { |
152 | Value v = e.getValue(session); |
153 | v = column.convert(v); |
154 | valueSet.add(v); |
155 | } |
156 | Value[] array = new Value[valueSet.size()]; |
157 | valueSet.toArray(array); |
158 | final CompareMode mode = session.getDatabase().getCompareMode(); |
159 | Arrays.sort(array, new Comparator<Value>() { |
160 | @Override |
161 | public int compare(Value o1, Value o2) { |
162 | return o1.compareTo(o2, mode); |
163 | } |
164 | }); |
165 | return array; |
166 | } |
167 | |
168 | /** |
169 | * Get the current result of the expression. The rows may not be of the same |
170 | * type, therefore the rows may not be unique. |
171 | * |
172 | * @return the result |
173 | */ |
174 | public ResultInterface getCurrentResult() { |
175 | return expressionQuery.query(0); |
176 | } |
177 | |
178 | /** |
179 | * Get the SQL snippet of this comparison. |
180 | * |
181 | * @return the SQL snippet |
182 | */ |
183 | public String getSQL() { |
184 | if (compareType == Comparison.FALSE) { |
185 | return "FALSE"; |
186 | } |
187 | StatementBuilder buff = new StatementBuilder(); |
188 | buff.append(column.getSQL()); |
189 | switch(compareType) { |
190 | case Comparison.EQUAL: |
191 | buff.append(" = "); |
192 | break; |
193 | case Comparison.EQUAL_NULL_SAFE: |
194 | buff.append(" IS "); |
195 | break; |
196 | case Comparison.BIGGER_EQUAL: |
197 | buff.append(" >= "); |
198 | break; |
199 | case Comparison.BIGGER: |
200 | buff.append(" > "); |
201 | break; |
202 | case Comparison.SMALLER_EQUAL: |
203 | buff.append(" <= "); |
204 | break; |
205 | case Comparison.SMALLER: |
206 | buff.append(" < "); |
207 | break; |
208 | case Comparison.IN_LIST: |
209 | buff.append(" IN("); |
210 | for (Expression e : expressionList) { |
211 | buff.appendExceptFirst(", "); |
212 | buff.append(e.getSQL()); |
213 | } |
214 | buff.append(')'); |
215 | break; |
216 | case Comparison.IN_QUERY: |
217 | buff.append(" IN("); |
218 | buff.append(expressionQuery.getPlanSQL()); |
219 | buff.append(')'); |
220 | break; |
221 | case Comparison.SPATIAL_INTERSECTS: |
222 | buff.append(" && "); |
223 | break; |
224 | default: |
225 | DbException.throwInternalError("type="+compareType); |
226 | } |
227 | if (expression != null) { |
228 | buff.append(expression.getSQL()); |
229 | } |
230 | return buff.toString(); |
231 | } |
232 | |
233 | /** |
234 | * Get the comparison bit mask. |
235 | * |
236 | * @param indexConditions all index conditions |
237 | * @return the mask |
238 | */ |
239 | public int getMask(ArrayList<IndexCondition> indexConditions) { |
240 | switch (compareType) { |
241 | case Comparison.FALSE: |
242 | return ALWAYS_FALSE; |
243 | case Comparison.EQUAL: |
244 | case Comparison.EQUAL_NULL_SAFE: |
245 | return EQUALITY; |
246 | case Comparison.IN_LIST: |
247 | case Comparison.IN_QUERY: |
248 | if (indexConditions.size() > 1) { |
249 | if (!Table.TABLE.equals(column.getTable().getTableType())) { |
250 | // if combined with other conditions, |
251 | // IN(..) can only be used for regular tables |
252 | // test case: |
253 | // create table test(a int, b int, primary key(id, name)); |
254 | // create unique index c on test(b, a); |
255 | // insert into test values(1, 10), (2, 20); |
256 | // select * from (select * from test) |
257 | // where a=1 and b in(10, 20); |
258 | return 0; |
259 | } |
260 | } |
261 | return EQUALITY; |
262 | case Comparison.BIGGER_EQUAL: |
263 | case Comparison.BIGGER: |
264 | return START; |
265 | case Comparison.SMALLER_EQUAL: |
266 | case Comparison.SMALLER: |
267 | return END; |
268 | case Comparison.SPATIAL_INTERSECTS: |
269 | return SPATIAL_INTERSECTS; |
270 | default: |
271 | throw DbException.throwInternalError("type=" + compareType); |
272 | } |
273 | } |
274 | |
275 | /** |
276 | * Check if the result is always false. |
277 | * |
278 | * @return true if the result will always be false |
279 | */ |
280 | public boolean isAlwaysFalse() { |
281 | return compareType == Comparison.FALSE; |
282 | } |
283 | |
284 | /** |
285 | * Check if this index condition is of the type column larger or equal to |
286 | * value. |
287 | * |
288 | * @return true if this is a start condition |
289 | */ |
290 | public boolean isStart() { |
291 | switch (compareType) { |
292 | case Comparison.EQUAL: |
293 | case Comparison.EQUAL_NULL_SAFE: |
294 | case Comparison.BIGGER_EQUAL: |
295 | case Comparison.BIGGER: |
296 | return true; |
297 | default: |
298 | return false; |
299 | } |
300 | } |
301 | |
302 | /** |
303 | * Check if this index condition is of the type column smaller or equal to |
304 | * value. |
305 | * |
306 | * @return true if this is a end condition |
307 | */ |
308 | public boolean isEnd() { |
309 | switch (compareType) { |
310 | case Comparison.EQUAL: |
311 | case Comparison.EQUAL_NULL_SAFE: |
312 | case Comparison.SMALLER_EQUAL: |
313 | case Comparison.SMALLER: |
314 | return true; |
315 | default: |
316 | return false; |
317 | } |
318 | } |
319 | |
320 | /** |
321 | * Check if this index condition is of the type spatial column intersects |
322 | * value. |
323 | * |
324 | * @return true if this is a spatial intersects condition |
325 | */ |
326 | public boolean isSpatialIntersects() { |
327 | switch (compareType) { |
328 | case Comparison.SPATIAL_INTERSECTS: |
329 | return true; |
330 | default: |
331 | return false; |
332 | } |
333 | } |
334 | |
335 | /** |
336 | * Check if this index condition is of the type equality. |
337 | * |
338 | * @param constantExpression if the inner node |
339 | * is a constant expression |
340 | * @return true if this is a equality condition |
341 | */ |
342 | public boolean isEquality(boolean constantExpression) { |
343 | switch (compareType) { |
344 | case Comparison.EQUAL: |
345 | case Comparison.EQUAL_NULL_SAFE: |
346 | return !constantExpression || expression.isConstant(); |
347 | default: |
348 | return false; |
349 | } |
350 | } |
351 | |
352 | public int getCompareType() { |
353 | return compareType; |
354 | } |
355 | |
356 | /** |
357 | * Get the referenced column. |
358 | * |
359 | * @return the column |
360 | */ |
361 | public Column getColumn() { |
362 | return column; |
363 | } |
364 | |
365 | /** |
366 | * Check if the expression can be evaluated. |
367 | * |
368 | * @return true if it can be evaluated |
369 | */ |
370 | public boolean isEvaluatable() { |
371 | if (expression != null) { |
372 | return expression.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR); |
373 | } |
374 | if (expressionList != null) { |
375 | for (Expression e : expressionList) { |
376 | if (!e.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { |
377 | return false; |
378 | } |
379 | } |
380 | return true; |
381 | } |
382 | return expressionQuery.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR); |
383 | } |
384 | |
385 | } |