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.api.ErrorCode; |
9 | import org.h2.command.dml.Query; |
10 | import org.h2.engine.Database; |
11 | import org.h2.engine.Session; |
12 | import org.h2.index.IndexCondition; |
13 | import org.h2.message.DbException; |
14 | import org.h2.result.LocalResult; |
15 | import org.h2.table.ColumnResolver; |
16 | import org.h2.table.TableFilter; |
17 | import org.h2.util.StringUtils; |
18 | import org.h2.value.Value; |
19 | import org.h2.value.ValueBoolean; |
20 | import org.h2.value.ValueNull; |
21 | |
22 | /** |
23 | * An 'in' condition with a subquery, as in WHERE ID IN(SELECT ...) |
24 | */ |
25 | public class ConditionInSelect extends Condition { |
26 | |
27 | private final Database database; |
28 | private Expression left; |
29 | private final Query query; |
30 | private final boolean all; |
31 | private final int compareType; |
32 | private int queryLevel; |
33 | |
34 | public ConditionInSelect(Database database, Expression left, Query query, |
35 | boolean all, int compareType) { |
36 | this.database = database; |
37 | this.left = left; |
38 | this.query = query; |
39 | this.all = all; |
40 | this.compareType = compareType; |
41 | } |
42 | |
43 | @Override |
44 | public Value getValue(Session session) { |
45 | query.setSession(session); |
46 | query.setDistinct(true); |
47 | LocalResult rows = query.query(0); |
48 | try { |
49 | Value l = left.getValue(session); |
50 | if (rows.getRowCount() == 0) { |
51 | return ValueBoolean.get(all); |
52 | } else if (l == ValueNull.INSTANCE) { |
53 | return l; |
54 | } |
55 | if (!session.getDatabase().getSettings().optimizeInSelect) { |
56 | return getValueSlow(rows, l); |
57 | } |
58 | if (all || (compareType != Comparison.EQUAL && |
59 | compareType != Comparison.EQUAL_NULL_SAFE)) { |
60 | return getValueSlow(rows, l); |
61 | } |
62 | int dataType = rows.getColumnType(0); |
63 | if (dataType == Value.NULL) { |
64 | return ValueBoolean.get(false); |
65 | } |
66 | l = l.convertTo(dataType); |
67 | if (rows.containsDistinct(new Value[] { l })) { |
68 | return ValueBoolean.get(true); |
69 | } |
70 | if (rows.containsDistinct(new Value[] { ValueNull.INSTANCE })) { |
71 | return ValueNull.INSTANCE; |
72 | } |
73 | return ValueBoolean.get(false); |
74 | } finally { |
75 | rows.close(); |
76 | } |
77 | } |
78 | |
79 | private Value getValueSlow(LocalResult rows, Value l) { |
80 | // this only returns the correct result if the result has at least one |
81 | // row, and if l is not null |
82 | boolean hasNull = false; |
83 | boolean result = all; |
84 | while (rows.next()) { |
85 | boolean value; |
86 | Value r = rows.currentRow()[0]; |
87 | if (r == ValueNull.INSTANCE) { |
88 | value = false; |
89 | hasNull = true; |
90 | } else { |
91 | value = Comparison.compareNotNull(database, l, r, compareType); |
92 | } |
93 | if (!value && all) { |
94 | result = false; |
95 | break; |
96 | } else if (value && !all) { |
97 | result = true; |
98 | break; |
99 | } |
100 | } |
101 | if (!result && hasNull) { |
102 | return ValueNull.INSTANCE; |
103 | } |
104 | return ValueBoolean.get(result); |
105 | } |
106 | |
107 | @Override |
108 | public void mapColumns(ColumnResolver resolver, int level) { |
109 | left.mapColumns(resolver, level); |
110 | query.mapColumns(resolver, level + 1); |
111 | this.queryLevel = Math.max(level, this.queryLevel); |
112 | } |
113 | |
114 | @Override |
115 | public Expression optimize(Session session) { |
116 | left = left.optimize(session); |
117 | query.setRandomAccessResult(true); |
118 | query.prepare(); |
119 | if (query.getColumnCount() != 1) { |
120 | throw DbException.get(ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN); |
121 | } |
122 | // Can not optimize: the data may change |
123 | return this; |
124 | } |
125 | |
126 | @Override |
127 | public void setEvaluatable(TableFilter tableFilter, boolean b) { |
128 | left.setEvaluatable(tableFilter, b); |
129 | query.setEvaluatable(tableFilter, b); |
130 | } |
131 | |
132 | @Override |
133 | public String getSQL() { |
134 | StringBuilder buff = new StringBuilder(); |
135 | buff.append('(').append(left.getSQL()).append(' '); |
136 | if (all) { |
137 | buff.append(Comparison.getCompareOperator(compareType)). |
138 | append(" ALL"); |
139 | } else { |
140 | if (compareType == Comparison.EQUAL) { |
141 | buff.append("IN"); |
142 | } else { |
143 | buff.append(Comparison.getCompareOperator(compareType)). |
144 | append(" ANY"); |
145 | } |
146 | } |
147 | buff.append("(\n").append(StringUtils.indent(query.getPlanSQL(), 4, false)). |
148 | append("))"); |
149 | return buff.toString(); |
150 | } |
151 | |
152 | @Override |
153 | public void updateAggregate(Session session) { |
154 | left.updateAggregate(session); |
155 | query.updateAggregate(session); |
156 | } |
157 | |
158 | @Override |
159 | public boolean isEverything(ExpressionVisitor visitor) { |
160 | return left.isEverything(visitor) && query.isEverything(visitor); |
161 | } |
162 | |
163 | @Override |
164 | public int getCost() { |
165 | return left.getCost() + query.getCostAsExpression(); |
166 | } |
167 | |
168 | @Override |
169 | public void createIndexConditions(Session session, TableFilter filter) { |
170 | if (!session.getDatabase().getSettings().optimizeInList) { |
171 | return; |
172 | } |
173 | if (!(left instanceof ExpressionColumn)) { |
174 | return; |
175 | } |
176 | ExpressionColumn l = (ExpressionColumn) left; |
177 | if (filter != l.getTableFilter()) { |
178 | return; |
179 | } |
180 | ExpressionVisitor visitor = ExpressionVisitor.getNotFromResolverVisitor(filter); |
181 | if (!query.isEverything(visitor)) { |
182 | return; |
183 | } |
184 | filter.addIndexCondition(IndexCondition.getInQuery(l, query)); |
185 | } |
186 | |
187 | @Override |
188 | public boolean isDisjunctive() { |
189 | return true; |
190 | } |
191 | |
192 | } |