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.command.Prepared;
012: import org.h2.constant.ErrorCode;
013: import org.h2.engine.Database;
014: import org.h2.engine.Session;
015: import org.h2.expression.Alias;
016: import org.h2.expression.Expression;
017: import org.h2.expression.ExpressionColumn;
018: import org.h2.expression.ExpressionVisitor;
019: import org.h2.expression.Parameter;
020: import org.h2.expression.ValueExpression;
021: import org.h2.message.Message;
022: import org.h2.result.LocalResult;
023: import org.h2.result.SortOrder;
024: import org.h2.table.ColumnResolver;
025: import org.h2.table.TableFilter;
026: import org.h2.util.ObjectArray;
027: import org.h2.value.Value;
028: import org.h2.value.ValueInt;
029: import org.h2.value.ValueNull;
030:
031: /**
032: * Represents a SELECT statement (simple, or union).
033: */
034: public abstract class Query extends Prepared {
035:
036: /**
037: * The limit expression as specified in the LIMIT or TOP clause.
038: */
039: protected Expression limit;
040:
041: /**
042: * The offset expression as specified in the LIMIT .. OFFSET clause.
043: */
044: protected Expression offset;
045:
046: /**
047: * The sample size
048: */
049: protected int sampleSize;
050:
051: private int lastLimit;
052: private long lastEvaluated;
053: private LocalResult lastResult;
054: private Value[] lastParameters;
055:
056: /**
057: * Execute the query without checking the cache.
058: *
059: * @param limit the limit as specified in the JDBC method call
060: * @return the result
061: */
062: abstract LocalResult queryWithoutCache(int limit)
063: throws SQLException;
064:
065: /**
066: * Initialize the query.
067: */
068: public abstract void init() throws SQLException;
069:
070: /**
071: * The the list of select expressions.
072: * This may include invisible expressions such as order by expressions.
073: *
074: * @return the list of expressions
075: */
076: public abstract ObjectArray getExpressions();
077:
078: /**
079: * Calculate the cost to execute this query.
080: *
081: * @return the cost
082: */
083: public abstract double getCost();
084:
085: /**
086: * Get all tables that are involved in this query.
087: *
088: * @return the set of tables
089: */
090: public abstract HashSet getTables();
091:
092: /**
093: * Set the order by list.
094: *
095: * @param order the order by list
096: */
097: public abstract void setOrder(ObjectArray order);
098:
099: /**
100: * Set the 'for update' flag.
101: *
102: * @param forUpdate the new setting
103: */
104: public abstract void setForUpdate(boolean forUpdate);
105:
106: /**
107: * Get the column count of this query.
108: *
109: * @return the column count
110: */
111: public abstract int getColumnCount();
112:
113: /**
114: * Map the columns to the given column resolver.
115: *
116: * @param resolver
117: * the resolver
118: * @param level
119: * the subquery level (0 is the top level query, 1 is the first
120: * subquery level)
121: */
122: public abstract void mapColumns(ColumnResolver resolver, int level)
123: throws SQLException;
124:
125: /**
126: * Change the evaluatable flag. This is used when building the execution plan.
127: *
128: * @param tableFilter the table filter
129: * @param b the new value
130: */
131: public abstract void setEvaluatable(TableFilter tableFilter,
132: boolean b);
133:
134: /**
135: * Add a condition to the query. This is used for views.
136: *
137: * @param param the parameter
138: * @param columnId the column index (0 meaning the first column)
139: * @param comparisonType the comparison type
140: */
141: public abstract void addGlobalCondition(Parameter param,
142: int columnId, int comparisonType) throws SQLException;
143:
144: /**
145: * Set the distinct flag.
146: *
147: * @param b the new value
148: */
149: public abstract void setDistinct(boolean b);
150:
151: /**
152: * Get the alias (or column name) of the first column.
153: * This is used to convert IN(SELECT ...) queries to inner joins.
154: *
155: * @param session the session
156: * @return the alias or column name
157: */
158: public abstract String getFirstColumnAlias(Session session);
159:
160: /**
161: * Check if this expression and all sub-expressions can fulfill a criteria.
162: * If any part returns false, the result is false.
163: *
164: * @param visitor the visitor
165: * @return if the criteria can be fulfilled
166: */
167: public abstract boolean isEverything(ExpressionVisitor visitor);
168:
169: /**
170: * Update all aggregate function values.
171: *
172: * @param session the session
173: */
174: public abstract void updateAggregate(Session session)
175: throws SQLException;
176:
177: public Query(Session session) {
178: super (session);
179: }
180:
181: public boolean isQuery() {
182: return true;
183: }
184:
185: public boolean isTransactional() {
186: return true;
187: }
188:
189: private boolean sameResultAsLast(Session session, Value[] params,
190: Value[] lastParams, long lastEvaluated) throws SQLException {
191: Database db = session.getDatabase();
192: for (int i = 0; i < params.length; i++) {
193: if (!db.areEqual(lastParams[i], params[i])) {
194: return false;
195: }
196: }
197: if (!isEverything(ExpressionVisitor.DETERMINISTIC)
198: || !isEverything(ExpressionVisitor.INDEPENDENT)) {
199: return false;
200: }
201: if (db.getModificationDataId() > lastEvaluated
202: && getMaxDataModificationId() > lastEvaluated) {
203: return false;
204: }
205: return true;
206: }
207:
208: public final Value[] getParameterValues() throws SQLException {
209: ObjectArray list = getParameters();
210: if (list == null) {
211: list = new ObjectArray();
212: }
213: Value[] params = new Value[list.size()];
214: for (int i = 0; i < list.size(); i++) {
215: Value v = ((Parameter) list.get(i)).getParamValue();
216: params[i] = v;
217: }
218: return params;
219: }
220:
221: public LocalResult query(int limit) throws SQLException {
222: if (!session.getDatabase().getOptimizeReuseResults()) {
223: return queryWithoutCache(limit);
224: }
225: Value[] params = getParameterValues();
226: long now = session.getDatabase().getModificationDataId();
227: if (lastResult != null && limit == lastLimit) {
228: if (sameResultAsLast(session, params, lastParameters,
229: lastEvaluated)) {
230: lastResult = lastResult.createShallowCopy(session);
231: if (lastResult != null) {
232: lastResult.reset();
233: return lastResult;
234: }
235: }
236: }
237: lastParameters = params;
238: closeLastResult();
239: lastResult = queryWithoutCache(limit);
240: this .lastEvaluated = now;
241: lastLimit = limit;
242: return lastResult;
243: }
244:
245: private void closeLastResult() {
246: if (lastResult != null) {
247: lastResult.close();
248: }
249: }
250:
251: protected void initOrder(ObjectArray expressions,
252: ObjectArray expressionSQL, ObjectArray orderList,
253: int visible, boolean mustBeInResult) throws SQLException {
254: for (int i = 0; i < orderList.size(); i++) {
255: SelectOrderBy o = (SelectOrderBy) orderList.get(i);
256: Expression e = o.expression;
257: if (e == null) {
258: continue;
259: }
260: // special case: SELECT 1 AS A FROM DUAL ORDER BY A
261: // (oracle supports it, but only in order by, not in group by and
262: // not in having):
263: // SELECT 1 AS A FROM DUAL ORDER BY -A
264: boolean isAlias = false;
265: int idx = expressions.size();
266: if (e instanceof ExpressionColumn) {
267: ExpressionColumn exprCol = (ExpressionColumn) e;
268: String alias = exprCol.getOriginalAliasName();
269: String col = exprCol.getOriginalColumnName();
270: for (int j = 0; j < visible; j++) {
271: boolean found = false;
272: Expression ec = (Expression) expressions.get(j);
273: if (ec instanceof ExpressionColumn) {
274: ExpressionColumn c = (ExpressionColumn) ec;
275: found = col.equals(c.getColumnName());
276: if (alias != null && found) {
277: String ca = c.getOriginalAliasName();
278: if (ca != null) {
279: found = alias.equals(ca);
280: }
281: }
282: } else if (!(ec instanceof Alias)) {
283: continue;
284: } else if (col.equals(ec.getAlias())) {
285: found = true;
286: } else {
287: Expression ec2 = ec.getNonAliasExpression();
288: if (ec2 instanceof ExpressionColumn) {
289: ExpressionColumn c2 = (ExpressionColumn) ec2;
290: String ta = exprCol.getSQL(); // exprCol.getTableAlias();
291: String tb = c2.getSQL(); // getTableAlias();
292: found = col.equals(c2.getColumnName());
293: if (ta == null || tb == null) {
294: if (ta != tb) {
295: found = false;
296: }
297: } else {
298: if (!ta.equals(tb)) {
299: found = false;
300: }
301: }
302: }
303: }
304: if (found) {
305: idx = j;
306: isAlias = true;
307: break;
308: }
309: }
310: } else {
311: String s = e.getSQL();
312: for (int j = 0; expressionSQL != null
313: && j < expressionSQL.size(); j++) {
314: String s2 = (String) expressionSQL.get(j);
315: if (s2.equals(s)) {
316: idx = j;
317: isAlias = true;
318: break;
319: }
320: }
321: }
322: if (!isAlias) {
323: if (mustBeInResult) {
324: throw Message.getSQLException(
325: ErrorCode.ORDER_BY_NOT_IN_RESULT, e
326: .getSQL());
327: }
328: expressions.add(e);
329: }
330: o.expression = null;
331: o.columnIndexExpr = ValueExpression.get(ValueInt
332: .get(idx + 1));
333: }
334: }
335:
336: /**
337: * Create a {@link SortOrder} object given the list of {@link SelectOrderBy}
338: * objects. The expression list is extended if necessary.
339: *
340: * @param orderList a list of {@link SelectOrderBy} elements
341: * @param expressionCount the number of columns in the query
342: * @return the {@link SortOrder} object
343: */
344: public SortOrder prepareOrder(ObjectArray orderList,
345: int expressionCount) throws SQLException {
346: int[] index = new int[orderList.size()];
347: int[] sortType = new int[orderList.size()];
348: for (int i = 0; i < orderList.size(); i++) {
349: SelectOrderBy o = (SelectOrderBy) orderList.get(i);
350: int idx;
351: boolean reverse = false;
352: if (o.expression != null) {
353: throw Message.getInternalError();
354: }
355: Expression expr = o.columnIndexExpr;
356: Value v = expr.getValue(null);
357: if (v == ValueNull.INSTANCE) {
358: // parameter not yet set - order by first column
359: idx = 0;
360: } else {
361: idx = v.getInt();
362: if (idx < 0) {
363: reverse = true;
364: idx = -idx;
365: }
366: idx -= 1;
367: if (idx < 0 || idx >= expressionCount) {
368: throw Message.getSQLException(
369: ErrorCode.ORDER_BY_NOT_IN_RESULT, ""
370: + (idx + 1));
371: }
372: }
373: index[i] = idx;
374: boolean desc = o.descending;
375: if (reverse) {
376: desc = !desc;
377: }
378: int type = desc ? SortOrder.DESCENDING
379: : SortOrder.ASCENDING;
380: if (o.nullsFirst) {
381: type += SortOrder.NULLS_FIRST;
382: } else if (o.nullsLast) {
383: type += SortOrder.NULLS_LAST;
384: }
385: sortType[i] = type;
386: }
387: return new SortOrder(session.getDatabase(), index, sortType);
388: }
389:
390: public void setOffset(Expression offset) {
391: this .offset = offset;
392: }
393:
394: public void setLimit(Expression limit) {
395: this .limit = limit;
396: }
397:
398: void addParameter(Parameter param) {
399: if (parameters == null) {
400: parameters = new ObjectArray();
401: }
402: parameters.add(param);
403: }
404:
405: public void setSampleSize(int sampleSize) {
406: this .sampleSize = sampleSize;
407: }
408:
409: public final long getMaxDataModificationId() {
410: ExpressionVisitor visitor = ExpressionVisitor
411: .get(ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID);
412: isEverything(visitor);
413: return visitor.getMaxDataModificationId();
414: }
415:
416: /**
417: * Visit all expressions and subqueries in this query using the visitor pattern.
418: *
419: * @param expressionVisitorType the visitor type
420: * @return true if no component returned false
421: */
422: public final boolean isEverything(int expressionVisitorType) {
423: ExpressionVisitor visitor = ExpressionVisitor
424: .get(expressionVisitorType);
425: return isEverything(visitor);
426: }
427:
428: }
|