001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.fetch;
012:
013: import com.versant.core.jdbc.sql.exp.SelectExp;
014: import com.versant.core.jdbc.sql.exp.SqlExp;
015: import com.versant.core.jdbc.sql.SqlDriver;
016: import com.versant.core.jdbc.JdbcStorageManager;
017: import com.versant.core.common.BindingSupportImpl;
018:
019: import java.io.PrintStream;
020: import java.sql.Connection;
021: import java.sql.PreparedStatement;
022: import java.sql.ResultSet;
023: import java.sql.SQLException;
024:
025: /**
026: * This specifies how each row from a SQL query is processed and helps to
027: * generate the query. A FetchSpec contains a list of FetchOp's, each of
028: * which fetch something from the ResultSet. FetchOp's may provide data
029: * to other subsequent FetchOp's so complex operations can be broken down
030: * into simple operations. A FetchOp may have a nested FetchSpec of its own
031: * to be executed once for each row or in parallel with the 'parent'
032: * FetchSpec.
033: */
034: public class FetchSpec {
035:
036: private FetchOptions options = new FetchOptions();
037: private FetchOp[] ops = new FetchOp[2];
038: private int opCount;
039: private FetchOp[] resultOps = new FetchOp[2];
040: private int resultOpCount;
041: private boolean singleObjectRow;
042:
043: private SqlDriver sqlDriver;
044: private SelectExp root;
045: private SqlExp pos;
046: private int totColumnCount;
047: private boolean inAddFetchOp;
048:
049: private SqlBuffer sqlBuffer;
050:
051: public FetchSpec(SelectExp root, SqlDriver sqlDriver) {
052: this .root = root;
053: this .sqlDriver = sqlDriver;
054: }
055:
056: /**
057: * Get the topmost SELECT for this spec.
058: */
059: public SelectExp getRoot() {
060: return root;
061: }
062:
063: /**
064: * Add a new FetchOp to this plan. If includeInResult is true the the
065: * result of the op is included in the projection returned by the
066: * FetchSpec.
067: */
068: public void addFetchOp(FetchOp op, boolean includeInResult) {
069: if (opCount == ops.length) {
070: FetchOp[] a = new FetchOp[opCount * 2];
071: System.arraycopy(ops, 0, a, 0, opCount);
072: ops = a;
073: }
074: op.setIndex(opCount);
075: ops[opCount++] = op;
076: if (includeInResult) {
077: if (resultOpCount == resultOps.length) {
078: FetchOp[] a = new FetchOp[resultOpCount * 2];
079: System.arraycopy(resultOps, 0, a, 0, resultOpCount);
080: resultOps = a;
081: }
082: resultOps[resultOpCount++] = op;
083: }
084: // Process newly added ops in a loop protected by a flag so that
085: // recursively added ops are processed in add order. This ensures
086: // that the ResultSet columns will be read in ascending order.
087: if (!inAddFetchOp) {
088: try {
089: inAddFetchOp = true;
090: for (int i = opCount - 1; i < opCount; i++) {
091: SqlExp list = ops[i].init(root, totColumnCount + 1);
092: if (list != null) {
093: ++totColumnCount;
094: if (pos == null) {
095: pos = root.selectList = list;
096: } else {
097: pos.next = list;
098: }
099: for (; pos.next != null; pos = pos.next) {
100: ++totColumnCount;
101: }
102: }
103: }
104: } finally {
105: inAddFetchOp = false;
106: }
107: }
108: }
109:
110: /**
111: * Get the number of FetchOp's in this spec.
112: */
113: public int getFetchOpCount() {
114: return opCount;
115: }
116:
117: /**
118: * Get the types of the objects in our projection.
119: */
120: public int[] getProjectionTypes() {
121: int[] a = new int[resultOpCount];
122: for (int i = 0; i < resultOpCount; i++) {
123: a[i] = resultOps[i].getResultType();
124: }
125: return a;
126: }
127:
128: /**
129: * Get the default FetchOptions for this spec.
130: */
131: public FetchOptions getOptions() {
132: return options;
133: }
134:
135: /**
136: * Set the compiled parameter info.
137: */
138: public void setParamList(SqlBuffer.Param paramList) {
139: if (sqlBuffer == null) {
140: generateSQL();
141: }
142: sqlBuffer.setParamList(paramList);
143: }
144:
145: /**
146: * Print a user understandable description of this operation.
147: */
148: public void printPlan(PrintStream p, String indent) {
149: for (int i = 0; i < opCount; i++) {
150: ops[i].printPlan(p, indent);
151: }
152: }
153:
154: /**
155: * Finish creating this spec and generate the SQL buffer for our query.
156: * This is a NOP if already done.
157: */
158: public synchronized void generateSQL() {
159: if (sqlBuffer != null) {
160: return;
161: }
162: sqlBuffer = new SqlBuffer();
163: int aliasCount = root.createAlias(0);
164: if (aliasCount == 1) {
165: root.alias = null;
166: sqlBuffer.setFirstTableOrAlias(root.table.name);
167: } else {
168: sqlBuffer.setFirstTableOrAlias(root.alias);
169: }
170: root.appendSQL(sqlDriver, sqlBuffer.getSqlbuf(), null);
171: sqlBuffer.setSelectListRange(root.distinct,
172: root.selectListStartIndex,
173: root.selectListFirstColEndIndex,
174: root.selectListEndIndex);
175: sqlBuffer.setOrderByRange(root.orderByStartIndex,
176: root.orderByEndIndex);
177: // work around bug with replace in CharBuffer class
178: sqlBuffer.getSqlbuf().append(' ');
179:
180: for (int i = 0; i < opCount; i++) {
181: ops[i].generateSQL();
182: }
183:
184: // clear fields we dont need any more now that we have the SQL
185: root = null;
186: pos = null;
187: }
188:
189: /**
190: * Create a FetchResult to execute our query. This will execute the
191: * query as soon as the data is needed.
192: *
193: * @param forUpdate Generate SELECT FOR UPDATE type query
194: * @param forCount Generate a COUNT(*) query to just count the rows
195: * @param fromIncl Index of first row to return
196: * @param toExcl Index of row after last row to return (-1 for all)
197: * @param scrollable Use a scrollable ResultSet
198: *
199: * @see FetchResultImp#execute()
200: */
201: public FetchResult createFetchResult(JdbcStorageManager sm,
202: Connection con, Object[] params, boolean forUpdate,
203: boolean forCount, long fromIncl, long toExcl,
204: int fetchSize, boolean scrollable) {
205:
206: if (scrollable && !sqlDriver.isScrollableResultSetSupported()) {
207: throw BindingSupportImpl.getInstance().datastore(
208: "Scrollable ResultSet's not supported for "
209: + sqlDriver.getName()
210: + " using JDBC driver "
211: + sm.getJdbcConnectionSource()
212: .getDriverName());
213: }
214:
215: if (sqlBuffer == null) {
216: generateSQL();
217: }
218: String sql = sqlBuffer.getSql(sqlDriver, params, forUpdate,
219: forCount, fromIncl, toExcl);
220: boolean error = true;
221: PreparedStatement ps = null;
222: try {
223: try {
224: if (scrollable) {
225: ps = con.prepareStatement(sql,
226: ResultSet.TYPE_SCROLL_INSENSITIVE,
227: ResultSet.CONCUR_READ_ONLY);
228: } else {
229: ps = con.prepareStatement(sql);
230: }
231: } catch (Exception e) {
232: throw BindingSupportImpl.getInstance().datastore(
233: "Error creating PreparedStatement: " + e
234: + "\nSQL:\n" + sql);
235: }
236: sqlBuffer.setParamsOnPS(sm.getJmd(), sqlDriver, ps, params,
237: sql);
238: if (fetchSize > 0) {
239: try {
240: ps.setFetchSize(fetchSize);
241: } catch (Exception e) {
242: throw sqlDriver.mapException(e, e.toString(), true);
243: }
244: }
245: FetchResultImp ans = new FetchResultImp(this , ps, sql,
246: scrollable);
247: error = false;
248: return ans;
249: } finally {
250: if (error && ps != null) {
251: try {
252: ps.close();
253: } catch (SQLException e) {
254: // ignore
255: }
256: }
257: }
258: }
259:
260: /**
261: * This is invoked by one of our results when it is closed. We call
262: * fetchResultClosed on all of our ops so they have a chance to close
263: * any nested results.
264: */
265: public void fetchResultClosed(FetchResult fetchResult) {
266: for (int i = 0; i < opCount; i++) {
267: ops[i].fetchResultClosed(fetchResult);
268: }
269: }
270:
271: public SqlDriver getSqlDriver() {
272: return sqlDriver;
273: }
274:
275: public boolean isSingleObjectRow() {
276: return singleObjectRow;
277: }
278:
279: /**
280: * If singleObjectRow is true and the projection only has one Object
281: * then this is returned as is and not in an Object[1].
282: */
283: public void setSingleObjectRow(boolean singleObjectRow) {
284: this .singleObjectRow = singleObjectRow;
285: }
286:
287: /**
288: * Process the current row in fetchResult's ResultSet and return our
289: * projection.
290: */
291: public Object createRow(FetchResultImp fetchResult) {
292: for (int i = 0; i < opCount; i++) {
293: try {
294: ops[i].fetch(fetchResult);
295: } catch (Exception e) {
296: throw sqlDriver.mapException(e, e.toString()
297: + "\nProcessing " + ops[i].getIndex() + ": "
298: + ops[i].getDescription(), true);
299: }
300: }
301: if (singleObjectRow && resultOpCount == 1) {
302: return resultOps[0].getResult(fetchResult);
303: } else {
304: Object[] a = new Object[resultOpCount];
305: for (int i = 0; i < resultOpCount; i++) {
306: a[i] = resultOps[i].getResult(fetchResult);
307: }
308: return a;
309: }
310: }
311:
312: }
|