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 org.h2.engine.Session; |
9 | import org.h2.engine.SysProperties; |
10 | import org.h2.message.DbException; |
11 | import org.h2.table.ColumnResolver; |
12 | import org.h2.table.TableFilter; |
13 | import org.h2.value.Value; |
14 | import org.h2.value.ValueBoolean; |
15 | import org.h2.value.ValueNull; |
16 | |
17 | /** |
18 | * An 'and' or 'or' condition as in WHERE ID=1 AND NAME=? |
19 | */ |
20 | public class ConditionAndOr extends Condition { |
21 | |
22 | /** |
23 | * The AND condition type as in ID=1 AND NAME='Hello'. |
24 | */ |
25 | public static final int AND = 0; |
26 | |
27 | /** |
28 | * The OR condition type as in ID=1 OR NAME='Hello'. |
29 | */ |
30 | public static final int OR = 1; |
31 | |
32 | private final int andOrType; |
33 | private Expression left, right; |
34 | |
35 | public ConditionAndOr(int andOrType, Expression left, Expression right) { |
36 | this.andOrType = andOrType; |
37 | this.left = left; |
38 | this.right = right; |
39 | if (SysProperties.CHECK && (left == null || right == null)) { |
40 | DbException.throwInternalError(); |
41 | } |
42 | } |
43 | |
44 | @Override |
45 | public String getSQL() { |
46 | String sql; |
47 | switch (andOrType) { |
48 | case AND: |
49 | sql = left.getSQL() + "\n AND " + right.getSQL(); |
50 | break; |
51 | case OR: |
52 | sql = left.getSQL() + "\n OR " + right.getSQL(); |
53 | break; |
54 | default: |
55 | throw DbException.throwInternalError("andOrType=" + andOrType); |
56 | } |
57 | return "(" + sql + ")"; |
58 | } |
59 | |
60 | @Override |
61 | public void createIndexConditions(Session session, TableFilter filter) { |
62 | if (andOrType == AND) { |
63 | left.createIndexConditions(session, filter); |
64 | right.createIndexConditions(session, filter); |
65 | } |
66 | } |
67 | |
68 | @Override |
69 | public Expression getNotIfPossible(Session session) { |
70 | // (NOT (A OR B)): (NOT(A) AND NOT(B)) |
71 | // (NOT (A AND B)): (NOT(A) OR NOT(B)) |
72 | Expression l = left.getNotIfPossible(session); |
73 | if (l == null) { |
74 | l = new ConditionNot(left); |
75 | } |
76 | Expression r = right.getNotIfPossible(session); |
77 | if (r == null) { |
78 | r = new ConditionNot(right); |
79 | } |
80 | int reversed = andOrType == AND ? OR : AND; |
81 | return new ConditionAndOr(reversed, l, r); |
82 | } |
83 | |
84 | @Override |
85 | public Value getValue(Session session) { |
86 | Value l = left.getValue(session); |
87 | Value r; |
88 | switch (andOrType) { |
89 | case AND: { |
90 | if (Boolean.FALSE.equals(l.getBoolean())) { |
91 | return l; |
92 | } |
93 | r = right.getValue(session); |
94 | if (Boolean.FALSE.equals(r.getBoolean())) { |
95 | return r; |
96 | } |
97 | if (l == ValueNull.INSTANCE) { |
98 | return l; |
99 | } |
100 | if (r == ValueNull.INSTANCE) { |
101 | return r; |
102 | } |
103 | return ValueBoolean.get(true); |
104 | } |
105 | case OR: { |
106 | if (Boolean.TRUE.equals(l.getBoolean())) { |
107 | return l; |
108 | } |
109 | r = right.getValue(session); |
110 | if (Boolean.TRUE.equals(r.getBoolean())) { |
111 | return r; |
112 | } |
113 | if (l == ValueNull.INSTANCE) { |
114 | return l; |
115 | } |
116 | if (r == ValueNull.INSTANCE) { |
117 | return r; |
118 | } |
119 | return ValueBoolean.get(false); |
120 | } |
121 | default: |
122 | throw DbException.throwInternalError("type=" + andOrType); |
123 | } |
124 | } |
125 | |
126 | @Override |
127 | public Expression optimize(Session session) { |
128 | // NULL handling: see wikipedia, |
129 | // http://www-cs-students.stanford.edu/~wlam/compsci/sqlnulls |
130 | left = left.optimize(session); |
131 | right = right.optimize(session); |
132 | int lc = left.getCost(), rc = right.getCost(); |
133 | if (rc < lc) { |
134 | Expression t = left; |
135 | left = right; |
136 | right = t; |
137 | } |
138 | // this optimization does not work in the following case, |
139 | // but NOT is optimized before: |
140 | // CREATE TABLE TEST(A INT, B INT); |
141 | // INSERT INTO TEST VALUES(1, NULL); |
142 | // SELECT * FROM TEST WHERE NOT (B=A AND B=0); // no rows |
143 | // SELECT * FROM TEST WHERE NOT (B=A AND B=0 AND A=0); // 1, NULL |
144 | if (session.getDatabase().getSettings().optimizeTwoEquals && |
145 | andOrType == AND) { |
146 | // try to add conditions (A=B AND B=1: add A=1) |
147 | if (left instanceof Comparison && right instanceof Comparison) { |
148 | Comparison compLeft = (Comparison) left; |
149 | Comparison compRight = (Comparison) right; |
150 | Expression added = compLeft.getAdditional( |
151 | session, compRight, true); |
152 | if (added != null) { |
153 | added = added.optimize(session); |
154 | ConditionAndOr a = new ConditionAndOr(AND, this, added); |
155 | return a; |
156 | } |
157 | } |
158 | } |
159 | // TODO optimization: convert ((A=1 AND B=2) OR (A=1 AND B=3)) to |
160 | // (A=1 AND (B=2 OR B=3)) |
161 | if (andOrType == OR && |
162 | session.getDatabase().getSettings().optimizeOr) { |
163 | // try to add conditions (A=B AND B=1: add A=1) |
164 | if (left instanceof Comparison && |
165 | right instanceof Comparison) { |
166 | Comparison compLeft = (Comparison) left; |
167 | Comparison compRight = (Comparison) right; |
168 | Expression added = compLeft.getAdditional( |
169 | session, compRight, false); |
170 | if (added != null) { |
171 | return added.optimize(session); |
172 | } |
173 | } else if (left instanceof ConditionIn && |
174 | right instanceof Comparison) { |
175 | Expression added = ((ConditionIn) left). |
176 | getAdditional((Comparison) right); |
177 | if (added != null) { |
178 | return added.optimize(session); |
179 | } |
180 | } else if (right instanceof ConditionIn && |
181 | left instanceof Comparison) { |
182 | Expression added = ((ConditionIn) right). |
183 | getAdditional((Comparison) left); |
184 | if (added != null) { |
185 | return added.optimize(session); |
186 | } |
187 | } else if (left instanceof ConditionInConstantSet && |
188 | right instanceof Comparison) { |
189 | Expression added = ((ConditionInConstantSet) left). |
190 | getAdditional(session, (Comparison) right); |
191 | if (added != null) { |
192 | return added.optimize(session); |
193 | } |
194 | } else if (right instanceof ConditionInConstantSet && |
195 | left instanceof Comparison) { |
196 | Expression added = ((ConditionInConstantSet) right). |
197 | getAdditional(session, (Comparison) left); |
198 | if (added != null) { |
199 | return added.optimize(session); |
200 | } |
201 | } |
202 | } |
203 | // TODO optimization: convert .. OR .. to UNION if the cost is lower |
204 | Value l = left.isConstant() ? left.getValue(session) : null; |
205 | Value r = right.isConstant() ? right.getValue(session) : null; |
206 | if (l == null && r == null) { |
207 | return this; |
208 | } |
209 | if (l != null && r != null) { |
210 | return ValueExpression.get(getValue(session)); |
211 | } |
212 | switch (andOrType) { |
213 | case AND: |
214 | if (l != null) { |
215 | if (Boolean.FALSE.equals(l.getBoolean())) { |
216 | return ValueExpression.get(l); |
217 | } else if (Boolean.TRUE.equals(l.getBoolean())) { |
218 | return right; |
219 | } |
220 | } else if (r != null) { |
221 | if (Boolean.FALSE.equals(r.getBoolean())) { |
222 | return ValueExpression.get(r); |
223 | } else if (Boolean.TRUE.equals(r.getBoolean())) { |
224 | return left; |
225 | } |
226 | } |
227 | break; |
228 | case OR: |
229 | if (l != null) { |
230 | if (Boolean.TRUE.equals(l.getBoolean())) { |
231 | return ValueExpression.get(l); |
232 | } else if (Boolean.FALSE.equals(l.getBoolean())) { |
233 | return right; |
234 | } |
235 | } else if (r != null) { |
236 | if (Boolean.TRUE.equals(r.getBoolean())) { |
237 | return ValueExpression.get(r); |
238 | } else if (Boolean.FALSE.equals(r.getBoolean())) { |
239 | return left; |
240 | } |
241 | } |
242 | break; |
243 | default: |
244 | DbException.throwInternalError("type=" + andOrType); |
245 | } |
246 | return this; |
247 | } |
248 | |
249 | @Override |
250 | public void addFilterConditions(TableFilter filter, boolean outerJoin) { |
251 | if (andOrType == AND) { |
252 | left.addFilterConditions(filter, outerJoin); |
253 | right.addFilterConditions(filter, outerJoin); |
254 | } else { |
255 | super.addFilterConditions(filter, outerJoin); |
256 | } |
257 | } |
258 | |
259 | @Override |
260 | public void mapColumns(ColumnResolver resolver, int level) { |
261 | left.mapColumns(resolver, level); |
262 | right.mapColumns(resolver, level); |
263 | } |
264 | |
265 | @Override |
266 | public void setEvaluatable(TableFilter tableFilter, boolean b) { |
267 | left.setEvaluatable(tableFilter, b); |
268 | right.setEvaluatable(tableFilter, b); |
269 | } |
270 | |
271 | @Override |
272 | public void updateAggregate(Session session) { |
273 | left.updateAggregate(session); |
274 | right.updateAggregate(session); |
275 | } |
276 | |
277 | @Override |
278 | public boolean isEverything(ExpressionVisitor visitor) { |
279 | return left.isEverything(visitor) && right.isEverything(visitor); |
280 | } |
281 | |
282 | @Override |
283 | public int getCost() { |
284 | return left.getCost() + right.getCost(); |
285 | } |
286 | |
287 | @Override |
288 | public boolean isDisjunctive() { |
289 | return andOrType == OR || left.isDisjunctive() || right.isDisjunctive(); |
290 | } |
291 | |
292 | /** |
293 | * Get the left or the right sub-expression of this condition. |
294 | * |
295 | * @param getLeft true to get the left sub-expression, false to get the |
296 | * right sub-expression. |
297 | * @return the sub-expression |
298 | */ |
299 | public Expression getExpression(boolean getLeft) { |
300 | return getLeft ? this.left : right; |
301 | } |
302 | |
303 | } |