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.expression; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.Comparator; |
10 | import java.util.TreeSet; |
11 | import org.h2.engine.Session; |
12 | import org.h2.index.IndexCondition; |
13 | import org.h2.message.DbException; |
14 | import org.h2.table.ColumnResolver; |
15 | import org.h2.table.TableFilter; |
16 | import org.h2.util.StatementBuilder; |
17 | import org.h2.value.Value; |
18 | import org.h2.value.ValueBoolean; |
19 | import org.h2.value.ValueNull; |
20 | |
21 | /** |
22 | * Used for optimised IN(...) queries where the contents of the IN list are all |
23 | * constant and of the same type. |
24 | * <p> |
25 | * Checking using a HashSet is has time complexity O(1), instead of O(n) for |
26 | * checking using an array. |
27 | */ |
28 | public class ConditionInConstantSet extends Condition { |
29 | |
30 | private Expression left; |
31 | private int queryLevel; |
32 | private final ArrayList<Expression> valueList; |
33 | private final TreeSet<Value> valueSet; |
34 | |
35 | /** |
36 | * Create a new IN(..) condition. |
37 | * |
38 | * @param session the session |
39 | * @param left the expression before IN |
40 | * @param valueList the value list (at least two elements) |
41 | */ |
42 | public ConditionInConstantSet(final Session session, Expression left, |
43 | ArrayList<Expression> valueList) { |
44 | this.left = left; |
45 | this.valueList = valueList; |
46 | this.valueSet = new TreeSet<Value>(new Comparator<Value>() { |
47 | @Override |
48 | public int compare(Value o1, Value o2) { |
49 | return session.getDatabase().compare(o1, o2); |
50 | } |
51 | }); |
52 | int type = left.getType(); |
53 | for (Expression expression : valueList) { |
54 | valueSet.add(expression.getValue(session).convertTo(type)); |
55 | } |
56 | } |
57 | |
58 | @Override |
59 | public Value getValue(Session session) { |
60 | Value x = left.getValue(session); |
61 | if (x == ValueNull.INSTANCE) { |
62 | return x; |
63 | } |
64 | boolean result = valueSet.contains(x); |
65 | if (!result) { |
66 | boolean setHasNull = valueSet.contains(ValueNull.INSTANCE); |
67 | if (setHasNull) { |
68 | return ValueNull.INSTANCE; |
69 | } |
70 | } |
71 | return ValueBoolean.get(result); |
72 | } |
73 | |
74 | @Override |
75 | public void mapColumns(ColumnResolver resolver, int level) { |
76 | left.mapColumns(resolver, level); |
77 | this.queryLevel = Math.max(level, this.queryLevel); |
78 | } |
79 | |
80 | @Override |
81 | public Expression optimize(Session session) { |
82 | left = left.optimize(session); |
83 | return this; |
84 | } |
85 | |
86 | @Override |
87 | public void createIndexConditions(Session session, TableFilter filter) { |
88 | if (!(left instanceof ExpressionColumn)) { |
89 | return; |
90 | } |
91 | ExpressionColumn l = (ExpressionColumn) left; |
92 | if (filter != l.getTableFilter()) { |
93 | return; |
94 | } |
95 | if (session.getDatabase().getSettings().optimizeInList) { |
96 | filter.addIndexCondition(IndexCondition.getInList(l, valueList)); |
97 | return; |
98 | } |
99 | } |
100 | |
101 | @Override |
102 | public void setEvaluatable(TableFilter tableFilter, boolean b) { |
103 | left.setEvaluatable(tableFilter, b); |
104 | } |
105 | |
106 | @Override |
107 | public String getSQL() { |
108 | StatementBuilder buff = new StatementBuilder("("); |
109 | buff.append(left.getSQL()).append(" IN("); |
110 | for (Expression e : valueList) { |
111 | buff.appendExceptFirst(", "); |
112 | buff.append(e.getSQL()); |
113 | } |
114 | return buff.append("))").toString(); |
115 | } |
116 | |
117 | @Override |
118 | public void updateAggregate(Session session) { |
119 | // nothing to do |
120 | } |
121 | |
122 | @Override |
123 | public boolean isEverything(ExpressionVisitor visitor) { |
124 | if (!left.isEverything(visitor)) { |
125 | return false; |
126 | } |
127 | switch (visitor.getType()) { |
128 | case ExpressionVisitor.OPTIMIZABLE_MIN_MAX_COUNT_ALL: |
129 | case ExpressionVisitor.DETERMINISTIC: |
130 | case ExpressionVisitor.READONLY: |
131 | case ExpressionVisitor.INDEPENDENT: |
132 | case ExpressionVisitor.EVALUATABLE: |
133 | case ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID: |
134 | case ExpressionVisitor.NOT_FROM_RESOLVER: |
135 | case ExpressionVisitor.GET_DEPENDENCIES: |
136 | case ExpressionVisitor.QUERY_COMPARABLE: |
137 | case ExpressionVisitor.GET_COLUMNS: |
138 | return true; |
139 | default: |
140 | throw DbException.throwInternalError("type=" + visitor.getType()); |
141 | } |
142 | } |
143 | |
144 | @Override |
145 | public int getCost() { |
146 | int cost = left.getCost(); |
147 | return cost; |
148 | } |
149 | |
150 | @Override |
151 | public boolean isDisjunctive() { |
152 | return true; |
153 | } |
154 | |
155 | /** |
156 | * Add an additional element if possible. Example: given two conditions |
157 | * A IN(1, 2) OR A=3, the constant 3 is added: A IN(1, 2, 3). |
158 | * |
159 | * @param session the session |
160 | * @param other the second condition |
161 | * @return null if the condition was not added, or the new condition |
162 | */ |
163 | Expression getAdditional(Session session, Comparison other) { |
164 | Expression add = other.getIfEquals(left); |
165 | if (add != null) { |
166 | if (add.isConstant()) { |
167 | valueList.add(add); |
168 | valueSet.add(add.getValue(session).convertTo(left.getType())); |
169 | return this; |
170 | } |
171 | } |
172 | return null; |
173 | } |
174 | } |