001: /*
002: Copyright (C) 2002-2006 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: */
022:
023: package com.mysql.jdbc;
024:
025: import java.sql.SQLException;
026: import java.util.ArrayList;
027: import java.util.List;
028:
029: /**
030: * Model for result set data backed by a cursor. Only works for forward-only
031: * result sets (but still works with updatable concurrency).
032: *
033: * @version $Id: CursorRowProvider.java,v 1.1.2.1 2005/05/19 18:31:49 mmatthews
034: * Exp $
035: */
036: public class RowDataCursor implements RowData {
037:
038: private final static int BEFORE_START_OF_ROWS = -1;
039:
040: /**
041: * The cache of rows we have retrieved from the server.
042: */
043: private List fetchedRows;
044:
045: /**
046: * Where we are positionaly in the entire result set, used mostly to
047: * facilitate easy 'isBeforeFirst()' and 'isFirst()' methods.
048: */
049: private int currentPositionInEntireResult = BEFORE_START_OF_ROWS;
050:
051: /**
052: * Position in cache of rows, used to determine if we need to fetch more
053: * rows from the server to satisfy a request for the next row.
054: */
055: private int currentPositionInFetchedRows = BEFORE_START_OF_ROWS;
056:
057: /**
058: * The result set that we 'belong' to.
059: */
060: private ResultSetImpl owner;
061:
062: /**
063: * Have we been told from the server that we have seen the last row?
064: */
065: private boolean lastRowFetched = false;
066:
067: /**
068: * Field-level metadata from the server. We need this, because it is not
069: * sent for each batch of rows, but we need the metadata to unpack the
070: * results for each field.
071: */
072: private Field[] metadata;
073:
074: /**
075: * Communications channel to the server
076: */
077: private MysqlIO mysql;
078:
079: /**
080: * Identifier for the statement that created this cursor.
081: */
082: private long statementIdOnServer;
083:
084: /**
085: * The prepared statement that created this cursor.
086: */
087: private ServerPreparedStatement prepStmt;
088:
089: /**
090: * The server status for 'last-row-sent'...This might belong in mysqldefs,
091: * but it it only ever referenced from here.
092: */
093: private static final int SERVER_STATUS_LAST_ROW_SENT = 128;
094:
095: /**
096: * Have we attempted to fetch any rows yet?
097: */
098: private boolean firstFetchCompleted = false;
099:
100: private boolean wasEmpty = false;
101:
102: private boolean useBufferRowExplicit = false;
103:
104: /**
105: * Creates a new cursor-backed row provider.
106: *
107: * @param ioChannel
108: * connection to the server.
109: * @param creatingStatement
110: * statement that opened the cursor.
111: * @param metadata
112: * field-level metadata for the results that this cursor covers.
113: */
114: public RowDataCursor(MysqlIO ioChannel,
115: ServerPreparedStatement creatingStatement, Field[] metadata) {
116: this .currentPositionInEntireResult = BEFORE_START_OF_ROWS;
117: this .metadata = metadata;
118: this .mysql = ioChannel;
119: this .statementIdOnServer = creatingStatement
120: .getServerStatementId();
121: this .prepStmt = creatingStatement;
122: this .useBufferRowExplicit = MysqlIO
123: .useBufferRowExplicit(this .metadata);
124:
125: }
126:
127: /**
128: * Returns true if we got the last element.
129: *
130: * @return DOCUMENT ME!
131: */
132: public boolean isAfterLast() {
133: return lastRowFetched
134: && this .currentPositionInFetchedRows > this .fetchedRows
135: .size();
136: }
137:
138: /**
139: * Only works on non dynamic result sets.
140: *
141: * @param index
142: * row number to get at
143: * @return row data at index
144: * @throws SQLException
145: * if a database error occurs
146: */
147: public ResultSetRow getAt(int ind) throws SQLException {
148: notSupported();
149:
150: return null;
151: }
152:
153: /**
154: * Returns if iteration has not occured yet.
155: *
156: * @return true if before first row
157: * @throws SQLException
158: * if a database error occurs
159: */
160: public boolean isBeforeFirst() throws SQLException {
161: return this .currentPositionInEntireResult < 0;
162: }
163:
164: /**
165: * Moves the current position in the result set to the given row number.
166: *
167: * @param rowNumber
168: * row to move to
169: * @throws SQLException
170: * if a database error occurs
171: */
172: public void setCurrentRow(int rowNumber) throws SQLException {
173: notSupported();
174: }
175:
176: /**
177: * Returns the current position in the result set as a row number.
178: *
179: * @return the current row number
180: * @throws SQLException
181: * if a database error occurs
182: */
183: public int getCurrentRowNumber() throws SQLException {
184: return this .currentPositionInEntireResult + 1;
185: }
186:
187: /**
188: * Returns true if the result set is dynamic.
189: *
190: * This means that move back and move forward won't work because we do not
191: * hold on to the records.
192: *
193: * @return true if this result set is streaming from the server
194: */
195: public boolean isDynamic() {
196: return true;
197: }
198:
199: /**
200: * Has no records.
201: *
202: * @return true if no records
203: * @throws SQLException
204: * if a database error occurs
205: */
206: public boolean isEmpty() throws SQLException {
207: return this .isBeforeFirst() && this .isAfterLast();
208: }
209:
210: /**
211: * Are we on the first row of the result set?
212: *
213: * @return true if on first row
214: * @throws SQLException
215: * if a database error occurs
216: */
217: public boolean isFirst() throws SQLException {
218: return this .currentPositionInEntireResult == 0;
219: }
220:
221: /**
222: * Are we on the last row of the result set?
223: *
224: * @return true if on last row
225: * @throws SQLException
226: * if a database error occurs
227: */
228: public boolean isLast() throws SQLException {
229: return this .lastRowFetched
230: && this .currentPositionInFetchedRows == (this .fetchedRows
231: .size() - 1);
232: }
233:
234: /**
235: * Adds a row to this row data.
236: *
237: * @param row
238: * the row to add
239: * @throws SQLException
240: * if a database error occurs
241: */
242: public void addRow(ResultSetRow row) throws SQLException {
243: notSupported();
244: }
245:
246: /**
247: * Moves to after last.
248: *
249: * @throws SQLException
250: * if a database error occurs
251: */
252: public void afterLast() throws SQLException {
253: notSupported();
254: }
255:
256: /**
257: * Moves to before first.
258: *
259: * @throws SQLException
260: * if a database error occurs
261: */
262: public void beforeFirst() throws SQLException {
263: notSupported();
264: }
265:
266: /**
267: * Moves to before last so next el is the last el.
268: *
269: * @throws SQLException
270: * if a database error occurs
271: */
272: public void beforeLast() throws SQLException {
273: notSupported();
274: }
275:
276: /**
277: * We're done.
278: *
279: * @throws SQLException
280: * if a database error occurs
281: */
282: public void close() throws SQLException {
283:
284: this .metadata = null;
285: this .owner = null;
286: }
287:
288: /**
289: * Returns true if another row exists.
290: *
291: * @return true if more rows
292: * @throws SQLException
293: * if a database error occurs
294: */
295: public boolean hasNext() throws SQLException {
296:
297: if (this .fetchedRows != null && this .fetchedRows.size() == 0) {
298: return false;
299: }
300:
301: if (this .owner != null && this .owner.owningStatement != null) {
302: int maxRows = this .owner.owningStatement.maxRows;
303:
304: if (maxRows != -1
305: && this .currentPositionInEntireResult + 1 > maxRows) {
306: return false;
307: }
308: }
309:
310: if (this .currentPositionInEntireResult != BEFORE_START_OF_ROWS) {
311: // Case, we've fetched some rows, but are not at end of fetched
312: // block
313: if (this .currentPositionInFetchedRows < (this .fetchedRows
314: .size() - 1)) {
315: return true;
316: } else if (this .currentPositionInFetchedRows == this .fetchedRows
317: .size()
318: && this .lastRowFetched) {
319: return false;
320: } else {
321: // need to fetch to determine
322: fetchMoreRows();
323:
324: return (this .fetchedRows.size() > 0);
325: }
326: }
327:
328: // Okay, no rows _yet_, so fetch 'em
329:
330: fetchMoreRows();
331:
332: return this .fetchedRows.size() > 0;
333: }
334:
335: /**
336: * Moves the current position relative 'rows' from the current position.
337: *
338: * @param rows
339: * the relative number of rows to move
340: * @throws SQLException
341: * if a database error occurs
342: */
343: public void moveRowRelative(int rows) throws SQLException {
344: notSupported();
345: }
346:
347: /**
348: * Returns the next row.
349: *
350: * @return the next row value
351: * @throws SQLException
352: * if a database error occurs
353: */
354: public ResultSetRow next() throws SQLException {
355: if (this .fetchedRows == null
356: && this .currentPositionInEntireResult != BEFORE_START_OF_ROWS) {
357: throw SQLError
358: .createSQLException(
359: Messages
360: .getString("ResultSet.Operation_not_allowed_after_ResultSet_closed_144"), //$NON-NLS-1$
361: SQLError.SQL_STATE_GENERAL_ERROR);
362: }
363:
364: if (!hasNext()) {
365: return null;
366: }
367:
368: this .currentPositionInEntireResult++;
369: this .currentPositionInFetchedRows++;
370:
371: // Catch the forced scroll-passed-end
372: if (this .fetchedRows != null && this .fetchedRows.size() == 0) {
373: return null;
374: }
375:
376: if (this .currentPositionInFetchedRows > (this .fetchedRows
377: .size() - 1)) {
378: fetchMoreRows();
379: this .currentPositionInFetchedRows = 0;
380: }
381:
382: ResultSetRow row = (ResultSetRow) this .fetchedRows
383: .get(this .currentPositionInFetchedRows);
384:
385: row.setMetadata(this .metadata);
386:
387: return row;
388: }
389:
390: /**
391: *
392: */
393: private void fetchMoreRows() throws SQLException {
394: if (this .lastRowFetched) {
395: this .fetchedRows = new ArrayList(0);
396: return;
397: }
398:
399: synchronized (this .owner.connection.getMutex()) {
400: boolean oldFirstFetchCompleted = this .firstFetchCompleted;
401:
402: if (!this .firstFetchCompleted) {
403: this .firstFetchCompleted = true;
404: }
405:
406: int numRowsToFetch = this .owner.getFetchSize();
407:
408: if (numRowsToFetch == 0) {
409: numRowsToFetch = this .prepStmt.getFetchSize();
410: }
411:
412: if (numRowsToFetch == Integer.MIN_VALUE) {
413: // Handle the case where the user used 'old'
414: // streaming result sets
415:
416: numRowsToFetch = 1;
417: }
418:
419: this .fetchedRows = this .mysql.fetchRowsViaCursor(
420: this .fetchedRows, this .statementIdOnServer,
421: this .metadata, numRowsToFetch,
422: this .useBufferRowExplicit);
423: this .currentPositionInFetchedRows = BEFORE_START_OF_ROWS;
424:
425: if ((this .mysql.getServerStatus() & SERVER_STATUS_LAST_ROW_SENT) != 0) {
426: this .lastRowFetched = true;
427:
428: if (!oldFirstFetchCompleted
429: && this .fetchedRows.size() == 0) {
430: this .wasEmpty = true;
431: }
432: }
433: }
434: }
435:
436: /**
437: * Removes the row at the given index.
438: *
439: * @param index
440: * the row to move to
441: * @throws SQLException
442: * if a database error occurs
443: */
444: public void removeRow(int ind) throws SQLException {
445: notSupported();
446: }
447:
448: /**
449: * Only works on non dynamic result sets.
450: *
451: * @return the size of this row data
452: */
453: public int size() {
454: return RESULT_SET_SIZE_UNKNOWN;
455: }
456:
457: private void nextRecord() throws SQLException {
458:
459: }
460:
461: private void notSupported() throws SQLException {
462: throw new OperationNotSupportedException();
463: }
464:
465: /*
466: * (non-Javadoc)
467: *
468: * @see com.mysql.jdbc.RowProvider#setOwner(com.mysql.jdbc.ResultSet)
469: */
470: public void setOwner(ResultSetImpl rs) {
471: this .owner = rs;
472: }
473:
474: /*
475: * (non-Javadoc)
476: *
477: * @see com.mysql.jdbc.RowProvider#getOwner()
478: */
479: public ResultSetInternalMethods getOwner() {
480: return this .owner;
481: }
482:
483: public boolean wasEmpty() {
484: return this .wasEmpty;
485: }
486:
487: public void setMetadata(Field[] metadata) {
488: this.metadata = metadata;
489: }
490:
491: }
|