001: package simpleorm.core;
002:
003: import simpleorm.properties.*;
004: import java.sql.*;
005:
006: /*
007: * Copyright (c) 2002 Southern Cross Software Queensland (SCSQ). All rights
008: * reserved. See COPYRIGHT.txt included in this distribution.
009: */
010:
011: /** This class is analagous to a JDBC result set. It is returned by
012: <code>sPreparedStatement.execute()</code> and is then used to access
013: individual rows.<p>
014:
015: @see SPreparedStatement
016: */
017: public class SResultSet extends SPropertyMap {
018: SPreparedStatement sPreparedStatement = null;
019: ResultSet jdbcResultSet = null;
020: long nrRetrieved = 0;
021: boolean finished = false;
022:
023: SResultSet() {
024: }
025:
026: SResultSet(ResultSet rs, SPreparedStatement ps) {
027: jdbcResultSet = rs;
028: sPreparedStatement = ps;
029:
030: SConnection scon = SConnection.getBegunConnection();
031: scon.getDriver().initResultSet(this );
032: }
033:
034: public ResultSet getDBResultSet() {
035: return jdbcResultSet;
036: }
037:
038: public SPreparedStatement getSPreparedStatement() {
039: return sPreparedStatement;
040: }
041:
042: /** See if there is another record to retrieve. The loop is the
043: same as JDBC, ie. <code>while (rs.hasNext()) {rec = getRecord();}</code><p>
044:
045: If more than <code>maxRows</code> are retrieved an exception is
046: thrown. Use this to prevent run away queries.<p>
047:
048: Note that this is different from SPreparedStatement.Offset and
049: Limit, which may change the generated SQL to only retrieve this
050: number of rows.<p>
051:
052: Automatically closes the cursor when the last record is retrieved.<p>
053: */
054: public boolean hasNext(int maxRows) {
055: if (finished)
056: throw new SException.Error(
057: "hasNext called twice after last record read "
058: + this );
059: if (nrRetrieved >= sPreparedStatement.limit
060: && sPreparedStatement.limit > 0) {
061: // This may not be necessary if the driver handles offset/limit OK.
062: finished = true;
063: close();
064: return false;
065: }
066: boolean next = false;
067: try {
068: next = jdbcResultSet.next();
069: } catch (Exception ne1) {
070: throw new SException.JDBC(ne1);
071: }
072: if (next)
073: nrRetrieved++;
074: if (nrRetrieved > maxRows) {
075: close();
076: throw new SException.Data("Nr Rows Retrieved "
077: + nrRetrieved + " > max rows " + maxRows + " for "
078: + this );
079: }
080: if (!next) {
081: finished = true;
082: close();
083: }
084: return next;
085: }
086:
087: public boolean hasNext() {
088: return hasNext(Integer.MAX_VALUE);
089: }
090:
091: /** Returns the number of records retrieved so far.
092: This excludes any rows required to implement Offset. */
093: public long getNrRetrieved() {
094: return nrRetrieved;
095: }
096:
097: /** Retrieves the next record from the result set.
098: <code>hasNext()</code> must have been called and returned
099: true. */
100: public SRecordInstance getRecord() {
101:
102: if (finished)
103: throw new SException.Error(
104: "Attempt to getRecord after hasNext false.");
105: if (sPreparedStatement == null)
106: throw new SException.Error("Closed Connection");
107:
108: /// Double check same thread
109: SConnection scon = SConnection.getBegunConnection();
110: if (sPreparedStatement.sConnection != scon)
111: throw new SException.Error("Inconsistent Connections "
112: + sPreparedStatement
113: + sPreparedStatement.sConnection + scon);
114:
115: /// Create New Instance. Too early to know if already in transCache.
116: SRecordInstance instance = null;
117: SRecordMeta sRecordMeta = sPreparedStatement.sRecordMeta;
118: try {
119: instance = (SRecordInstance) sRecordMeta.userClass
120: .newInstance(); // an SRecordInstance
121: } catch (Exception ie) {
122: throw new SException.Data(ie);
123: }
124:
125: /// Retrieve the row into Record.
126: SFieldMeta[] selectList = sPreparedStatement.selectList;
127: SRecordFinder.retrieveRecord(instance, selectList,
128: jdbcResultSet, sPreparedStatement.readOnly,
129: sPreparedStatement.optimistic, false);
130:
131: /// Check to see if it is already in Transaction Cache
132: SRecordInstance cache = (SRecordInstance) scon.transactionCache
133: .get(instance);
134: //System.out.println("CACHED " + cache);
135: if (cache == null) { // New record, add to cache.
136: scon.transactionCache.put(instance, instance);
137: instance.sConnection = scon;
138: if (SLog.slog.enableQueries())
139: SLog.slog.queries("getRecord: " + instance
140: + " (from database)");
141: return instance;
142: } else {
143: if (cache.sConnection != scon)
144: throw new SException.Error("Inconsistent Connections "
145: + cache + cache.sConnection + scon);
146: if (SLog.slog.enableQueries())
147: SLog.slog.queries("getRecord: " + cache
148: + " (from cache)");
149: return cache;
150: // ### But does not promote to non-ReadOnly or add extra fields.
151: }
152: } // getRecord()
153:
154: /**
155: Retrieves all rows as an SArrayList. An exception is thrown if
156: more than maxRows are retrieved to trap unbounded queries, and is
157: mandatory. Provide a generous value, eg. if you expect about 10
158: rows, specify 1000.<p>
159:
160: Note that there is no point in creating the SArrayList object if
161: you just want to iterate over the rows. An explicit loop can
162: also give you more control over how a collection is created.*/
163: public SArrayList getArrayList(int maxRows) {
164: SArrayList al = new SArrayList();
165: for (int ax = 0; hasNext(); ax++) {
166: if (ax >= maxRows) {
167: close();
168: throw new SException.Error("More than " + maxRows
169: + " rows in " + this );
170: }
171: al.add(getRecord());
172: }
173: return al;
174: }
175:
176: /** Convenience routine for retrieving at most one row. Throws an
177: exception if there are multiple rows. Returns null if no rows
178: (not a dummy SRecrodInstance -- we may not have a key). */
179: public SRecordInstance getOnlyRecord() {
180: if (!hasNext())
181: return null;
182: SRecordInstance res = getRecord();
183: if (hasNext()) {
184: String msg = "More than one record in result set " + this
185: + getRecord();
186: close();
187: throw new SException.Error(msg);
188: }
189: return res;
190: }
191:
192: /** Close the underlying JDBC result set, if required. SimpleORM
193: normally closes the cursor automatically by {@link #hasNext},
194: provided that you retrieve all the records (the common case).
195: Some JDBC dirvers (eg. Oracle) have bugs that require cusrors to
196: always be closed. Closing a cursor twice is OK. From 1.05
197: closing the cursor also closes the prepared statement. */
198: public void close() {
199: if (jdbcResultSet != null)
200: try {
201: jdbcResultSet.close();
202: } catch (Exception ce) {
203: throw new SException.JDBC("Closing " + this , ce);
204: }
205: jdbcResultSet = null;
206:
207: if (sPreparedStatement != null) {
208: sPreparedStatement.close();
209: sPreparedStatement = null;
210: }
211: }
212:
213: /** Retrieves the underlying JDBC result set. Dangerous. But
214: allows arbitrary JDBC calls to be made if really necessary. */
215: public ResultSet getJDBCResultSet() {
216: return jdbcResultSet;
217: }
218:
219: public String toString() {
220: return "[SResultSet " + sPreparedStatement + "]";
221: }
222: }
|