001: package org.gomba;
002:
003: import java.sql.Connection;
004: import java.sql.PreparedStatement;
005: import java.sql.ResultSet;
006: import java.sql.SQLException;
007: import java.util.ArrayList;
008: import java.util.Iterator;
009: import java.util.List;
010:
011: /**
012: * Represents a JDBC query.
013: *
014: * @author Flavio Tordini
015: * @version $Id: Query.java,v 1.7 2005/01/10 11:07:06 flaviotordini Exp $
016: */
017: class Query {
018:
019: /**
020: * The SQL is contained here, along with the definition of parameters.
021: */
022: private final QueryDefinition queryDefinition;
023:
024: /**
025: * Parameters for a JDBC PreparedStatement. List of <code>Object</code>.
026: * Parameters are already converted to the required Java type.
027: */
028: private List statementParameters;
029:
030: private int skip = 0, max = 0;
031:
032: /**
033: * Constructor.
034: */
035: protected Query(QueryDefinition queryDefinition,
036: ParameterResolver parameterResolver) throws Exception {
037: this .queryDefinition = queryDefinition;
038:
039: // let's do the real work
040: this .statementParameters = readParameters(queryDefinition,
041: parameterResolver);
042:
043: // skip
044: Expression skipExpression = queryDefinition.getSkipExpression();
045: if (skipExpression != null) {
046: Object obj = skipExpression
047: .replaceParameters(parameterResolver);
048: if (obj instanceof java.lang.Integer) {
049: this .skip = ((Integer) obj).intValue();
050: } else if (obj instanceof java.lang.String) {
051: this .skip = Integer.parseInt((String) obj);
052: } else {
053: throw new Exception("Unsupported parameter type: "
054: + obj.getClass());
055: }
056: }
057:
058: // max
059: Expression maxExpression = queryDefinition.getMaxExpression();
060: if (maxExpression != null) {
061: Object obj = maxExpression
062: .replaceParameters(parameterResolver);
063: if (obj instanceof java.lang.Integer) {
064: this .max = ((Integer) obj).intValue();
065: } else if (obj instanceof java.lang.String) {
066: this .max = Integer.parseInt((String) obj);
067: } else {
068: throw new Exception("Unsupported parameter type: "
069: + obj.getClass());
070: }
071: }
072:
073: }
074:
075: /**
076: * Read parameters using the <code>QueryDefinition</code> info.
077: */
078: protected static List readParameters(
079: QueryDefinition queryDefinition,
080: ParameterResolver parameterResolver) throws Exception {
081:
082: List statementParameters = new ArrayList(queryDefinition
083: .getParameters().size());
084:
085: // loop through parameters in the definition
086: for (Iterator i = queryDefinition.getParameters().iterator(); i
087: .hasNext();) {
088:
089: ParameterDefinition parameterDefinition = (ParameterDefinition) i
090: .next();
091:
092: // try to get the parameter value
093: Object typedParameterValue = parameterResolver
094: .getParameterValue(parameterDefinition);
095:
096: // finally add it to the parameters used by the prepared statement
097: statementParameters.add(typedParameterValue);
098:
099: }
100:
101: return statementParameters;
102: }
103:
104: /**
105: * Execute this query. The query may return a resultset or an update count.
106: * In other words, it may read from the db or write to it.
107: *
108: * @param dataSource
109: * The JDBC data source to query.
110: * @return A <code>QueryResult</code> object that wraps a ResultSet. If
111: * the <code>skip</code> value is greater than 0, the resulset
112: * rows are skipped. If the query is write operation
113: * <code>null</code> is returned.
114: * @throws SQLException
115: */
116: public final QueryResult execute(final Connection connection)
117: throws SQLException {
118:
119: // JDBC resources that we should take special care of
120: // (don't forget the Connection)
121: PreparedStatement preparedStatement = null;
122: ResultSet resultSet = null;
123:
124: /*
125: // FIXME streaming resultset do not work with max & skip
126: boolean isJConnector = "MySQL-AB JDBC Driver".equals(connection
127: .getMetaData().getDriverName());
128:
129: // create the prepared statement
130: if (isJConnector) {
131: // enable streaming resultsets for MySQL
132: // Yes, this is awful. Go tell J-Connector developers...
133: preparedStatement = connection.prepareStatement(
134: this.queryDefinition.getSQLQuery(),
135: ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
136: preparedStatement.setFetchSize(Integer.MIN_VALUE);
137: } else {*/
138: preparedStatement = connection
139: .prepareStatement(this .queryDefinition.getSQLQuery());
140: /* } */
141:
142: // System.out.println("SQL: " + this.queryDefinition.getSQLQuery());
143: // set the prepared statement parameters
144: for (int i = 0; i < this .statementParameters.size(); i++) {
145: Object paramValue = this .statementParameters.get(i);
146: // System.out.println("param " + (i + 1) + ": " + paramValue
147: // + " of " + paramValue.getClass());
148: preparedStatement.setObject(i + 1, paramValue);
149: }
150:
151: // max rows
152: if (this .max > 0) {
153: int maxRows = this .skip + this .max;
154: preparedStatement.setMaxRows(maxRows);
155: }
156:
157: // Go!
158: boolean haveResultSet = preparedStatement.execute();
159:
160: // we may or may not have a resultset
161: if (haveResultSet) {
162: // we have a resultset
163: resultSet = preparedStatement.getResultSet();
164:
165: // skip rows
166: if (this .skip > 0) {
167: skipRows(resultSet, this .skip);
168: }
169:
170: // build a new QueryResult that wraps the ResultSet
171: QueryResult result = new QueryResult(preparedStatement,
172: resultSet);
173: return result;
174:
175: }
176: // we don't have a resultset
177:
178: // check if the update statement has modified at least one row
179: /*
180: if (preparedStatement.getUpdateCount() < 1) {
181: throw new SQLException("Update had no effect. No rows has been updated.");
182: }*/
183:
184: return null;
185:
186: }
187:
188: /**
189: * Skip rows of a resultset. The cursor is positioned on the first row to
190: * read, after the skipped rows. If the the number of rows to skip is
191: * greater than the number of rows in the resultset, the cursor will be
192: * positioned after the last row.
193: *
194: * <p>
195: * Rant: Why the hell there is nothing like a Statement.setRowsToSkip()?!
196: * </p>
197: *
198: * @param resultSet
199: * The resultset
200: * @param skip
201: * The number of rows to skip.
202: * @return true if the resultset holds at least <code>skip</code> rows.
203: */
204: private static boolean skipRows(ResultSet resultSet, int skip)
205: throws SQLException {
206: int i = 0;
207: while (i <= skip) {
208: if (!resultSet.next()) {
209: return false;
210: }
211: i++;
212: }
213: return true;
214: }
215:
216: /**
217: * Encapsulates JDBC objects. This is needed to pass around a ResultSet and
218: * still being able to close the statement and the connection.
219: */
220: static final class QueryResult {
221:
222: private final PreparedStatement preparedStatement;
223:
224: private final ResultSet resultSet;
225:
226: /**
227: * Private constructor. Nobody else can create instances of this class.
228: */
229: private QueryResult(PreparedStatement preparedStatement,
230: ResultSet resultSet) {
231: this .preparedStatement = preparedStatement;
232: this .resultSet = resultSet;
233: }
234:
235: /**
236: * Free JDBC resources.
237: */
238: private final static void freeResources(ResultSet resultSet,
239: PreparedStatement preparedStatement)
240: throws SQLException {
241: SQLException exception = null;
242: if (resultSet != null) {
243: try {
244: resultSet.close();
245: } catch (SQLException e) {
246: exception = e;
247: }
248: }
249: if (preparedStatement != null) {
250: try {
251: preparedStatement.close();
252: } catch (SQLException e) {
253: exception = e;
254: }
255: }
256: // FIXME only the last exception is thrown
257: if (exception != null) {
258: throw exception;
259: }
260: }
261:
262: /**
263: * Free JDBC resources. Do not forget to call this method when you're
264: * done!
265: */
266: public void close() throws SQLException {
267: freeResources(this .resultSet, this .preparedStatement);
268: }
269:
270: /**
271: * @return Returns the wrapped ResultSet. Don't close it! use the
272: * close() method of this class.
273: */
274: public ResultSet getResultSet() {
275: return this .resultSet;
276: }
277:
278: }
279:
280: /**
281: * @return The SQL for this query plus the list of parameters.
282: * @see java.lang.Object#toString()
283: */
284: public String toString() {
285: StringBuffer sb = new StringBuffer();
286: sb.append(this .queryDefinition.getSQLQuery());
287: sb.append(' ');
288: sb.append(this .statementParameters);
289: return sb.toString();
290: }
291:
292: /**
293: * @return Returns the statementParameters.
294: */
295: protected List getStatementParameters() {
296: return this.statementParameters;
297: }
298: }
|