001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.command.dml;
007:
008: import java.sql.SQLException;
009: import java.util.HashSet;
010:
011: import org.h2.constant.ErrorCode;
012: import org.h2.constant.SysProperties;
013: import org.h2.engine.Session;
014: import org.h2.expression.Expression;
015: import org.h2.expression.ExpressionColumn;
016: import org.h2.expression.ExpressionVisitor;
017: import org.h2.expression.Parameter;
018: import org.h2.expression.ValueExpression;
019: import org.h2.message.Message;
020: import org.h2.result.LocalResult;
021: import org.h2.result.SortOrder;
022: import org.h2.table.Column;
023: import org.h2.table.ColumnResolver;
024: import org.h2.table.TableFilter;
025: import org.h2.util.ObjectArray;
026: import org.h2.util.StringUtils;
027: import org.h2.value.Value;
028: import org.h2.value.ValueInt;
029:
030: /**
031: * Represents a union SELECT statement.
032: */
033: public class SelectUnion extends Query {
034: public static final int UNION = 0, UNION_ALL = 1, EXCEPT = 2,
035: INTERSECT = 3;
036: private int unionType;
037: private Query left, right;
038: private ObjectArray expressions;
039: private ObjectArray orderList;
040: private SortOrder sort;
041: private boolean distinct;
042: private boolean isPrepared, checkInit;
043: private boolean isForUpdate;
044:
045: public SelectUnion(Session session, Query query) {
046: super (session);
047: this .left = query;
048: }
049:
050: public void setUnionType(int type) {
051: this .unionType = type;
052: }
053:
054: public void setRight(Query select) throws SQLException {
055: right = select;
056: }
057:
058: public void setSQL(String sql) {
059: this .sql = sql;
060: }
061:
062: public void setOrder(ObjectArray order) {
063: orderList = order;
064: }
065:
066: private Value[] convert(Value[] values, int columnCount)
067: throws SQLException {
068: for (int i = 0; i < columnCount; i++) {
069: Expression e = (Expression) expressions.get(i);
070: values[i] = values[i].convertTo(e.getType());
071: }
072: return values;
073: }
074:
075: public LocalResult queryMeta() throws SQLException {
076: ObjectArray expressions = left.getExpressions();
077: int columnCount = left.getColumnCount();
078: LocalResult result = new LocalResult(session, expressions,
079: columnCount);
080: result.done();
081: return result;
082: }
083:
084: /**
085: * Execute the query without using the cache.
086: *
087: * @param maxrows the maximum number of rows to return
088: * @return the result
089: */
090: public LocalResult queryWithoutCache(int maxrows)
091: throws SQLException {
092: if (maxrows != 0) {
093: if (limit != null) {
094: maxrows = Math.min(limit.getValue(session).getInt(),
095: maxrows);
096: }
097: limit = ValueExpression.get(ValueInt.get(maxrows));
098: }
099: int columnCount = left.getColumnCount();
100: LocalResult result = new LocalResult(session, expressions,
101: columnCount);
102: result.setSortOrder(sort);
103: if (distinct) {
104: left.setDistinct(true);
105: right.setDistinct(true);
106: result.setDistinct();
107: }
108: switch (unionType) {
109: case UNION:
110: case EXCEPT:
111: left.setDistinct(true);
112: right.setDistinct(true);
113: result.setDistinct();
114: break;
115: case UNION_ALL:
116: break;
117: case INTERSECT:
118: left.setDistinct(true);
119: right.setDistinct(true);
120: break;
121: default:
122: throw Message.getInternalError("type=" + unionType);
123: }
124: LocalResult l = left.query(0);
125: LocalResult r = right.query(0);
126: l.reset();
127: r.reset();
128: switch (unionType) {
129: case UNION_ALL:
130: case UNION: {
131: while (l.next()) {
132: result.addRow(convert(l.currentRow(), columnCount));
133: }
134: while (r.next()) {
135: result.addRow(convert(r.currentRow(), columnCount));
136: }
137: break;
138: }
139: case EXCEPT: {
140: while (l.next()) {
141: result.addRow(convert(l.currentRow(), columnCount));
142: }
143: while (r.next()) {
144: result.removeDistinct(convert(r.currentRow(),
145: columnCount));
146: }
147: break;
148: }
149: case INTERSECT: {
150: LocalResult temp = new LocalResult(session, expressions,
151: columnCount);
152: temp.setDistinct();
153: while (l.next()) {
154: temp.addRow(convert(l.currentRow(), columnCount));
155: }
156: while (r.next()) {
157: Value[] values = convert(r.currentRow(), columnCount);
158: if (temp.containsDistinct(values)) {
159: result.addRow(values);
160: }
161: }
162: break;
163: }
164: default:
165: throw Message.getInternalError("type=" + unionType);
166: }
167: if (offset != null) {
168: result.setOffset(offset.getValue(session).getInt());
169: }
170: if (limit != null) {
171: result.setLimit(limit.getValue(session).getInt());
172: }
173: result.done();
174: return result;
175: }
176:
177: public void init() throws SQLException {
178: if (SysProperties.CHECK && checkInit) {
179: throw Message.getInternalError();
180: }
181: checkInit = true;
182: left.init();
183: right.init();
184: int len = left.getColumnCount();
185: if (len != right.getColumnCount()) {
186: throw Message
187: .getSQLException(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
188: }
189: ObjectArray le = left.getExpressions();
190: // set the expressions to get the right column count and names,
191: // but can't validate at this time
192: expressions = new ObjectArray();
193: for (int i = 0; i < len; i++) {
194: Expression l = (Expression) le.get(i);
195: expressions.add(l);
196: }
197: }
198:
199: public void prepare() throws SQLException {
200: if (isPrepared) {
201: // sometimes a subquery is prepared twice (CREATE TABLE AS SELECT)
202: return;
203: }
204: if (SysProperties.CHECK && !checkInit) {
205: throw Message.getInternalError("not initialized");
206: }
207: isPrepared = true;
208: left.prepare();
209: right.prepare();
210: int len = left.getColumnCount();
211: // set the correct expressions now
212: expressions = new ObjectArray();
213: ObjectArray le = left.getExpressions();
214: ObjectArray re = right.getExpressions();
215: for (int i = 0; i < len; i++) {
216: Expression l = (Expression) le.get(i);
217: Expression r = (Expression) re.get(i);
218: int type = Value.getHigherOrder(l.getType(), r.getType());
219: long prec = Math.max(l.getPrecision(), r.getPrecision());
220: int scale = Math.max(l.getScale(), r.getScale());
221: int displaySize = Math.max(l.getDisplaySize(), r
222: .getDisplaySize());
223: Column col = new Column(l.getAlias(), type, prec, scale,
224: displaySize);
225: Expression e = new ExpressionColumn(session.getDatabase(),
226: col);
227: expressions.add(e);
228: }
229: if (orderList != null) {
230: initOrder(expressions, null, orderList, getColumnCount(),
231: true);
232: sort = prepareOrder(orderList, expressions.size());
233: orderList = null;
234: }
235: }
236:
237: public double getCost() {
238: return left.getCost() + right.getCost();
239: }
240:
241: public HashSet getTables() {
242: HashSet set = left.getTables();
243: set.addAll(right.getTables());
244: return set;
245: }
246:
247: public void setDistinct(boolean b) {
248: distinct = b;
249: }
250:
251: public ObjectArray getExpressions() {
252: return expressions;
253: }
254:
255: public void setForUpdate(boolean forUpdate) {
256: left.setForUpdate(forUpdate);
257: right.setForUpdate(forUpdate);
258: isForUpdate = forUpdate;
259: }
260:
261: public int getColumnCount() {
262: return left.getColumnCount();
263: }
264:
265: public void mapColumns(ColumnResolver resolver, int level)
266: throws SQLException {
267: left.mapColumns(resolver, level);
268: right.mapColumns(resolver, level);
269: }
270:
271: public void setEvaluatable(TableFilter tableFilter, boolean b) {
272: left.setEvaluatable(tableFilter, b);
273: right.setEvaluatable(tableFilter, b);
274: }
275:
276: public void addGlobalCondition(Parameter param, int columnId,
277: int comparisonType) throws SQLException {
278: addParameter(param);
279: switch (unionType) {
280: case UNION_ALL:
281: case UNION:
282: case INTERSECT: {
283: left.addGlobalCondition(param, columnId, comparisonType);
284: right.addGlobalCondition(param, columnId, comparisonType);
285: break;
286: }
287: case EXCEPT: {
288: left.addGlobalCondition(param, columnId, comparisonType);
289: break;
290: }
291: default:
292: throw Message.getInternalError("type=" + unionType);
293: }
294: }
295:
296: public String getPlanSQL() {
297: StringBuffer buff = new StringBuffer();
298: buff.append('(');
299: buff.append(left.getPlanSQL());
300: buff.append(") ");
301: switch (unionType) {
302: case UNION_ALL:
303: buff.append("UNION ALL ");
304: break;
305: case UNION:
306: buff.append("UNION ");
307: break;
308: case INTERSECT:
309: buff.append("INTERSECT ");
310: break;
311: case EXCEPT:
312: buff.append("EXCEPT ");
313: break;
314: default:
315: throw Message.getInternalError("type=" + unionType);
316: }
317: buff.append('(');
318: buff.append(right.getPlanSQL());
319: buff.append(')');
320: Expression[] exprList = new Expression[expressions.size()];
321: expressions.toArray(exprList);
322: if (sort != null) {
323: buff.append(" ORDER BY ");
324: buff.append(sort.getSQL(exprList, exprList.length));
325: }
326: // TODO refactoring: limit and order by could be in Query (now in
327: // SelectUnion and in Select)
328: if (limit != null) {
329: buff.append(" LIMIT ");
330: buff.append(StringUtils.unEnclose(limit.getSQL()));
331: if (offset != null) {
332: buff.append(" OFFSET ");
333: buff.append(StringUtils.unEnclose(offset.getSQL()));
334: }
335: }
336: if (isForUpdate) {
337: buff.append(" FOR UPDATE");
338: }
339: return buff.toString();
340: }
341:
342: public LocalResult query(int limit) throws SQLException {
343: // union doesn't always know the parameter list of the left and right queries
344: return queryWithoutCache(limit);
345: }
346:
347: public boolean isEverything(ExpressionVisitor visitor) {
348: return left.isEverything(visitor)
349: && right.isEverything(visitor);
350: }
351:
352: public boolean isReadOnly() {
353: return left.isReadOnly() && right.isReadOnly();
354: }
355:
356: public void updateAggregate(Session session) throws SQLException {
357: left.updateAggregate(session);
358: right.updateAggregate(session);
359: }
360:
361: public String getFirstColumnAlias(Session session) {
362: return null;
363: }
364:
365: }
|