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.Mode; |
9 | import org.h2.engine.Session; |
10 | import org.h2.message.DbException; |
11 | import org.h2.table.ColumnResolver; |
12 | import org.h2.table.TableFilter; |
13 | import org.h2.util.MathUtils; |
14 | import org.h2.value.DataType; |
15 | import org.h2.value.Value; |
16 | import org.h2.value.ValueInt; |
17 | import org.h2.value.ValueNull; |
18 | import org.h2.value.ValueString; |
19 | |
20 | /** |
21 | * A mathematical expression, or string concatenation. |
22 | */ |
23 | public class Operation extends Expression { |
24 | |
25 | /** |
26 | * This operation represents a string concatenation as in |
27 | * 'Hello' || 'World'. |
28 | */ |
29 | public static final int CONCAT = 0; |
30 | |
31 | /** |
32 | * This operation represents an addition as in 1 + 2. |
33 | */ |
34 | public static final int PLUS = 1; |
35 | |
36 | /** |
37 | * This operation represents a subtraction as in 2 - 1. |
38 | */ |
39 | public static final int MINUS = 2; |
40 | |
41 | /** |
42 | * This operation represents a multiplication as in 2 * 3. |
43 | */ |
44 | public static final int MULTIPLY = 3; |
45 | |
46 | /** |
47 | * This operation represents a division as in 4 * 2. |
48 | */ |
49 | public static final int DIVIDE = 4; |
50 | |
51 | /** |
52 | * This operation represents a negation as in - ID. |
53 | */ |
54 | public static final int NEGATE = 5; |
55 | |
56 | /** |
57 | * This operation represents a modulus as in 5 % 2. |
58 | */ |
59 | public static final int MODULUS = 6; |
60 | |
61 | private int opType; |
62 | private Expression left, right; |
63 | private int dataType; |
64 | private boolean convertRight = true; |
65 | |
66 | public Operation(int opType, Expression left, Expression right) { |
67 | this.opType = opType; |
68 | this.left = left; |
69 | this.right = right; |
70 | } |
71 | |
72 | @Override |
73 | public String getSQL() { |
74 | String sql; |
75 | if (opType == NEGATE) { |
76 | // don't remove the space, otherwise it might end up some thing like |
77 | // --1 which is a line remark |
78 | sql = "- " + left.getSQL(); |
79 | } else { |
80 | // don't remove the space, otherwise it might end up some thing like |
81 | // --1 which is a line remark |
82 | sql = left.getSQL() + " " + getOperationToken() + " " + right.getSQL(); |
83 | } |
84 | return "(" + sql + ")"; |
85 | } |
86 | |
87 | private String getOperationToken() { |
88 | switch (opType) { |
89 | case NEGATE: |
90 | return "-"; |
91 | case CONCAT: |
92 | return "||"; |
93 | case PLUS: |
94 | return "+"; |
95 | case MINUS: |
96 | return "-"; |
97 | case MULTIPLY: |
98 | return "*"; |
99 | case DIVIDE: |
100 | return "/"; |
101 | case MODULUS: |
102 | return "%"; |
103 | default: |
104 | throw DbException.throwInternalError("opType=" + opType); |
105 | } |
106 | } |
107 | |
108 | @Override |
109 | public Value getValue(Session session) { |
110 | Value l = left.getValue(session).convertTo(dataType); |
111 | Value r; |
112 | if (right == null) { |
113 | r = null; |
114 | } else { |
115 | r = right.getValue(session); |
116 | if (convertRight) { |
117 | r = r.convertTo(dataType); |
118 | } |
119 | } |
120 | switch (opType) { |
121 | case NEGATE: |
122 | return l == ValueNull.INSTANCE ? l : l.negate(); |
123 | case CONCAT: { |
124 | Mode mode = session.getDatabase().getMode(); |
125 | if (l == ValueNull.INSTANCE) { |
126 | if (mode.nullConcatIsNull) { |
127 | return ValueNull.INSTANCE; |
128 | } |
129 | return r; |
130 | } else if (r == ValueNull.INSTANCE) { |
131 | if (mode.nullConcatIsNull) { |
132 | return ValueNull.INSTANCE; |
133 | } |
134 | return l; |
135 | } |
136 | String s1 = l.getString(), s2 = r.getString(); |
137 | StringBuilder buff = new StringBuilder(s1.length() + s2.length()); |
138 | buff.append(s1).append(s2); |
139 | return ValueString.get(buff.toString()); |
140 | } |
141 | case PLUS: |
142 | if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { |
143 | return ValueNull.INSTANCE; |
144 | } |
145 | return l.add(r); |
146 | case MINUS: |
147 | if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { |
148 | return ValueNull.INSTANCE; |
149 | } |
150 | return l.subtract(r); |
151 | case MULTIPLY: |
152 | if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { |
153 | return ValueNull.INSTANCE; |
154 | } |
155 | return l.multiply(r); |
156 | case DIVIDE: |
157 | if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { |
158 | return ValueNull.INSTANCE; |
159 | } |
160 | return l.divide(r); |
161 | case MODULUS: |
162 | if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { |
163 | return ValueNull.INSTANCE; |
164 | } |
165 | return l.modulus(r); |
166 | default: |
167 | throw DbException.throwInternalError("type=" + opType); |
168 | } |
169 | } |
170 | |
171 | @Override |
172 | public void mapColumns(ColumnResolver resolver, int level) { |
173 | left.mapColumns(resolver, level); |
174 | if (right != null) { |
175 | right.mapColumns(resolver, level); |
176 | } |
177 | } |
178 | |
179 | @Override |
180 | public Expression optimize(Session session) { |
181 | left = left.optimize(session); |
182 | switch (opType) { |
183 | case NEGATE: |
184 | dataType = left.getType(); |
185 | if (dataType == Value.UNKNOWN) { |
186 | dataType = Value.DECIMAL; |
187 | } |
188 | break; |
189 | case CONCAT: |
190 | right = right.optimize(session); |
191 | dataType = Value.STRING; |
192 | if (left.isConstant() && right.isConstant()) { |
193 | return ValueExpression.get(getValue(session)); |
194 | } |
195 | break; |
196 | case PLUS: |
197 | case MINUS: |
198 | case MULTIPLY: |
199 | case DIVIDE: |
200 | case MODULUS: |
201 | right = right.optimize(session); |
202 | int l = left.getType(); |
203 | int r = right.getType(); |
204 | if ((l == Value.NULL && r == Value.NULL) || |
205 | (l == Value.UNKNOWN && r == Value.UNKNOWN)) { |
206 | // (? + ?) - use decimal by default (the most safe data type) or |
207 | // string when text concatenation with + is enabled |
208 | if (opType == PLUS && session.getDatabase(). |
209 | getMode().allowPlusForStringConcat) { |
210 | dataType = Value.STRING; |
211 | opType = CONCAT; |
212 | } else { |
213 | dataType = Value.DECIMAL; |
214 | } |
215 | } else if (l == Value.DATE || l == Value.TIMESTAMP || |
216 | l == Value.TIME || r == Value.DATE || |
217 | r == Value.TIMESTAMP || r == Value.TIME) { |
218 | if (opType == PLUS) { |
219 | if (r != Value.getHigherOrder(l, r)) { |
220 | // order left and right: INT < TIME < DATE < TIMESTAMP |
221 | swap(); |
222 | int t = l; |
223 | l = r; |
224 | r = t; |
225 | } |
226 | if (l == Value.INT) { |
227 | // Oracle date add |
228 | Function f = Function.getFunction(session.getDatabase(), "DATEADD"); |
229 | f.setParameter(0, ValueExpression.get(ValueString.get("DAY"))); |
230 | f.setParameter(1, left); |
231 | f.setParameter(2, right); |
232 | f.doneWithParameters(); |
233 | return f.optimize(session); |
234 | } else if (l == Value.DECIMAL || l == Value.FLOAT || l == Value.DOUBLE) { |
235 | // Oracle date add |
236 | Function f = Function.getFunction(session.getDatabase(), "DATEADD"); |
237 | f.setParameter(0, ValueExpression.get(ValueString.get("SECOND"))); |
238 | left = new Operation(Operation.MULTIPLY, ValueExpression.get(ValueInt |
239 | .get(60 * 60 * 24)), left); |
240 | f.setParameter(1, left); |
241 | f.setParameter(2, right); |
242 | f.doneWithParameters(); |
243 | return f.optimize(session); |
244 | } else if (l == Value.TIME && r == Value.TIME) { |
245 | dataType = Value.TIME; |
246 | return this; |
247 | } else if (l == Value.TIME) { |
248 | dataType = Value.TIMESTAMP; |
249 | return this; |
250 | } |
251 | } else if (opType == MINUS) { |
252 | if ((l == Value.DATE || l == Value.TIMESTAMP) && r == Value.INT) { |
253 | // Oracle date subtract |
254 | Function f = Function.getFunction(session.getDatabase(), "DATEADD"); |
255 | f.setParameter(0, ValueExpression.get(ValueString.get("DAY"))); |
256 | right = new Operation(NEGATE, right, null); |
257 | right = right.optimize(session); |
258 | f.setParameter(1, right); |
259 | f.setParameter(2, left); |
260 | f.doneWithParameters(); |
261 | return f.optimize(session); |
262 | } else if ((l == Value.DATE || l == Value.TIMESTAMP) && |
263 | (r == Value.DECIMAL || r == Value.FLOAT || r == Value.DOUBLE)) { |
264 | // Oracle date subtract |
265 | Function f = Function.getFunction(session.getDatabase(), "DATEADD"); |
266 | f.setParameter(0, ValueExpression.get(ValueString.get("SECOND"))); |
267 | right = new Operation(Operation.MULTIPLY, ValueExpression.get(ValueInt |
268 | .get(60 * 60 * 24)), right); |
269 | right = new Operation(NEGATE, right, null); |
270 | right = right.optimize(session); |
271 | f.setParameter(1, right); |
272 | f.setParameter(2, left); |
273 | f.doneWithParameters(); |
274 | return f.optimize(session); |
275 | } else if (l == Value.DATE || l == Value.TIMESTAMP) { |
276 | if (r == Value.TIME) { |
277 | dataType = Value.TIMESTAMP; |
278 | return this; |
279 | } else if (r == Value.DATE || r == Value.TIMESTAMP) { |
280 | // Oracle date subtract |
281 | Function f = Function.getFunction(session.getDatabase(), "DATEDIFF"); |
282 | f.setParameter(0, ValueExpression.get(ValueString.get("DAY"))); |
283 | f.setParameter(1, right); |
284 | f.setParameter(2, left); |
285 | f.doneWithParameters(); |
286 | return f.optimize(session); |
287 | } |
288 | } else if (l == Value.TIME && r == Value.TIME) { |
289 | dataType = Value.TIME; |
290 | return this; |
291 | } |
292 | } else if (opType == MULTIPLY) { |
293 | if (l == Value.TIME) { |
294 | dataType = Value.TIME; |
295 | convertRight = false; |
296 | return this; |
297 | } else if (r == Value.TIME) { |
298 | swap(); |
299 | dataType = Value.TIME; |
300 | convertRight = false; |
301 | return this; |
302 | } |
303 | } else if (opType == DIVIDE) { |
304 | if (l == Value.TIME) { |
305 | dataType = Value.TIME; |
306 | convertRight = false; |
307 | return this; |
308 | } |
309 | } |
310 | throw DbException.getUnsupportedException( |
311 | DataType.getDataType(l).name + " " + |
312 | getOperationToken() + " " + |
313 | DataType.getDataType(r).name); |
314 | } else { |
315 | dataType = Value.getHigherOrder(l, r); |
316 | if (DataType.isStringType(dataType) && |
317 | session.getDatabase().getMode().allowPlusForStringConcat) { |
318 | opType = CONCAT; |
319 | } |
320 | } |
321 | break; |
322 | default: |
323 | DbException.throwInternalError("type=" + opType); |
324 | } |
325 | if (left.isConstant() && (right == null || right.isConstant())) { |
326 | return ValueExpression.get(getValue(session)); |
327 | } |
328 | return this; |
329 | } |
330 | |
331 | private void swap() { |
332 | Expression temp = left; |
333 | left = right; |
334 | right = temp; |
335 | } |
336 | |
337 | @Override |
338 | public void setEvaluatable(TableFilter tableFilter, boolean b) { |
339 | left.setEvaluatable(tableFilter, b); |
340 | if (right != null) { |
341 | right.setEvaluatable(tableFilter, b); |
342 | } |
343 | } |
344 | |
345 | @Override |
346 | public int getType() { |
347 | return dataType; |
348 | } |
349 | |
350 | @Override |
351 | public long getPrecision() { |
352 | if (right != null) { |
353 | switch (opType) { |
354 | case CONCAT: |
355 | return left.getPrecision() + right.getPrecision(); |
356 | default: |
357 | return Math.max(left.getPrecision(), right.getPrecision()); |
358 | } |
359 | } |
360 | return left.getPrecision(); |
361 | } |
362 | |
363 | @Override |
364 | public int getDisplaySize() { |
365 | if (right != null) { |
366 | switch (opType) { |
367 | case CONCAT: |
368 | return MathUtils.convertLongToInt((long) left.getDisplaySize() + |
369 | (long) right.getDisplaySize()); |
370 | default: |
371 | return Math.max(left.getDisplaySize(), right.getDisplaySize()); |
372 | } |
373 | } |
374 | return left.getDisplaySize(); |
375 | } |
376 | |
377 | @Override |
378 | public int getScale() { |
379 | if (right != null) { |
380 | return Math.max(left.getScale(), right.getScale()); |
381 | } |
382 | return left.getScale(); |
383 | } |
384 | |
385 | @Override |
386 | public void updateAggregate(Session session) { |
387 | left.updateAggregate(session); |
388 | if (right != null) { |
389 | right.updateAggregate(session); |
390 | } |
391 | } |
392 | |
393 | @Override |
394 | public boolean isEverything(ExpressionVisitor visitor) { |
395 | return left.isEverything(visitor) && |
396 | (right == null || right.isEverything(visitor)); |
397 | } |
398 | |
399 | @Override |
400 | public int getCost() { |
401 | return left.getCost() + 1 + (right == null ? 0 : right.getCost()); |
402 | } |
403 | |
404 | } |