1 | /* |
2 | Copyright (C) 2002-2004 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | */ |
22 | |
23 | package com.mysql.jdbc; |
24 | |
25 | import java.sql.SQLException; |
26 | import java.util.ArrayList; |
27 | import java.util.List; |
28 | |
29 | /** |
30 | * Model for result set data backed by a cursor. Only works for forward-only |
31 | * result sets (but still works with updatable concurrency). |
32 | * |
33 | * @version $Id: CursorRowProvider.java,v 1.1.2.1 2005/05/19 18:31:49 mmatthews |
34 | * Exp $ |
35 | */ |
36 | public class CursorRowProvider implements RowData { |
37 | |
38 | private final static int BEFORE_START_OF_ROWS = -1; |
39 | |
40 | /** |
41 | * The cache of rows we have retrieved from the server. |
42 | */ |
43 | private List fetchedRows; |
44 | |
45 | /** |
46 | * Where we are positionaly in the entire result set, used mostly to |
47 | * facilitate easy 'isBeforeFirst()' and 'isFirst()' methods. |
48 | */ |
49 | private int currentPositionInEntireResult = BEFORE_START_OF_ROWS; |
50 | |
51 | /** |
52 | * Position in cache of rows, used to determine if we need to fetch more |
53 | * rows from the server to satisfy a request for the next row. |
54 | */ |
55 | private int currentPositionInFetchedRows = BEFORE_START_OF_ROWS; |
56 | |
57 | /** |
58 | * The result set that we 'belong' to. |
59 | */ |
60 | private ResultSet owner; |
61 | |
62 | /** |
63 | * Have we been told from the server that we have seen the last row? |
64 | */ |
65 | private boolean lastRowFetched = false; |
66 | |
67 | /** |
68 | * Field-level metadata from the server. We need this, because it is not |
69 | * sent for each batch of rows, but we need the metadata to unpack the |
70 | * results for each field. |
71 | */ |
72 | private Field[] fields; |
73 | |
74 | /** |
75 | * Communications channel to the server |
76 | */ |
77 | private MysqlIO mysql; |
78 | |
79 | /** |
80 | * Identifier for the statement that created this cursor. |
81 | */ |
82 | private long statementIdOnServer; |
83 | |
84 | /** |
85 | * The prepared statement that created this cursor. |
86 | */ |
87 | private ServerPreparedStatement prepStmt; |
88 | |
89 | /** |
90 | * The server status for 'last-row-sent'...This might belong in mysqldefs, |
91 | * but it it only ever referenced from here. |
92 | */ |
93 | private static final int SERVER_STATUS_LAST_ROW_SENT = 128; |
94 | |
95 | /** |
96 | * Have we attempted to fetch any rows yet? |
97 | */ |
98 | private boolean firstFetchCompleted = false; |
99 | |
100 | /** |
101 | * Creates a new cursor-backed row provider. |
102 | * |
103 | * @param ioChannel |
104 | * connection to the server. |
105 | * @param creatingStatement |
106 | * statement that opened the cursor. |
107 | * @param metadata |
108 | * field-level metadata for the results that this cursor covers. |
109 | */ |
110 | public CursorRowProvider(MysqlIO ioChannel, |
111 | ServerPreparedStatement creatingStatement, Field[] metadata) { |
112 | this.currentPositionInEntireResult = BEFORE_START_OF_ROWS; |
113 | this.fields = metadata; |
114 | this.mysql = ioChannel; |
115 | this.statementIdOnServer = creatingStatement.getServerStatementId(); |
116 | this.prepStmt = creatingStatement; |
117 | } |
118 | |
119 | /** |
120 | * Returns true if we got the last element. |
121 | * |
122 | * @return DOCUMENT ME! |
123 | */ |
124 | public boolean isAfterLast() { |
125 | return lastRowFetched |
126 | && this.currentPositionInFetchedRows > this.fetchedRows.size(); |
127 | } |
128 | |
129 | /** |
130 | * Only works on non dynamic result sets. |
131 | * |
132 | * @param index |
133 | * row number to get at |
134 | * @return row data at index |
135 | * @throws SQLException |
136 | * if a database error occurs |
137 | */ |
138 | public Object[] getAt(int ind) throws SQLException { |
139 | notSupported(); |
140 | |
141 | return null; |
142 | } |
143 | |
144 | /** |
145 | * Returns if iteration has not occured yet. |
146 | * |
147 | * @return true if before first row |
148 | * @throws SQLException |
149 | * if a database error occurs |
150 | */ |
151 | public boolean isBeforeFirst() throws SQLException { |
152 | return this.currentPositionInEntireResult < 0; |
153 | } |
154 | |
155 | /** |
156 | * Moves the current position in the result set to the given row number. |
157 | * |
158 | * @param rowNumber |
159 | * row to move to |
160 | * @throws SQLException |
161 | * if a database error occurs |
162 | */ |
163 | public void setCurrentRow(int rowNumber) throws SQLException { |
164 | notSupported(); |
165 | } |
166 | |
167 | /** |
168 | * Returns the current position in the result set as a row number. |
169 | * |
170 | * @return the current row number |
171 | * @throws SQLException |
172 | * if a database error occurs |
173 | */ |
174 | public int getCurrentRowNumber() throws SQLException { |
175 | return this.currentPositionInEntireResult + 1; |
176 | } |
177 | |
178 | /** |
179 | * Returns true if the result set is dynamic. |
180 | * |
181 | * This means that move back and move forward won't work because we do not |
182 | * hold on to the records. |
183 | * |
184 | * @return true if this result set is streaming from the server |
185 | */ |
186 | public boolean isDynamic() { |
187 | return true; |
188 | } |
189 | |
190 | /** |
191 | * Has no records. |
192 | * |
193 | * @return true if no records |
194 | * @throws SQLException |
195 | * if a database error occurs |
196 | */ |
197 | public boolean isEmpty() throws SQLException { |
198 | return this.isBeforeFirst() && this.isAfterLast(); |
199 | } |
200 | |
201 | /** |
202 | * Are we on the first row of the result set? |
203 | * |
204 | * @return true if on first row |
205 | * @throws SQLException |
206 | * if a database error occurs |
207 | */ |
208 | public boolean isFirst() throws SQLException { |
209 | return this.currentPositionInEntireResult == 0; |
210 | } |
211 | |
212 | /** |
213 | * Are we on the last row of the result set? |
214 | * |
215 | * @return true if on last row |
216 | * @throws SQLException |
217 | * if a database error occurs |
218 | */ |
219 | public boolean isLast() throws SQLException { |
220 | return this.lastRowFetched |
221 | && this.currentPositionInFetchedRows == (this.fetchedRows |
222 | .size() - 1); |
223 | } |
224 | |
225 | /** |
226 | * Adds a row to this row data. |
227 | * |
228 | * @param row |
229 | * the row to add |
230 | * @throws SQLException |
231 | * if a database error occurs |
232 | */ |
233 | public void addRow(byte[][] row) throws SQLException { |
234 | notSupported(); |
235 | } |
236 | |
237 | /** |
238 | * Moves to after last. |
239 | * |
240 | * @throws SQLException |
241 | * if a database error occurs |
242 | */ |
243 | public void afterLast() throws SQLException { |
244 | notSupported(); |
245 | } |
246 | |
247 | /** |
248 | * Moves to before first. |
249 | * |
250 | * @throws SQLException |
251 | * if a database error occurs |
252 | */ |
253 | public void beforeFirst() throws SQLException { |
254 | notSupported(); |
255 | } |
256 | |
257 | /** |
258 | * Moves to before last so next el is the last el. |
259 | * |
260 | * @throws SQLException |
261 | * if a database error occurs |
262 | */ |
263 | public void beforeLast() throws SQLException { |
264 | notSupported(); |
265 | } |
266 | |
267 | /** |
268 | * We're done. |
269 | * |
270 | * @throws SQLException |
271 | * if a database error occurs |
272 | */ |
273 | public void close() throws SQLException { |
274 | |
275 | this.fields = null; |
276 | this.owner = null; |
277 | } |
278 | |
279 | /** |
280 | * Returns true if another row exists. |
281 | * |
282 | * @return true if more rows |
283 | * @throws SQLException |
284 | * if a database error occurs |
285 | */ |
286 | public boolean hasNext() throws SQLException { |
287 | |
288 | if (this.fetchedRows != null && this.fetchedRows.size() == 0) { |
289 | return false; |
290 | } |
291 | |
292 | if (this.currentPositionInEntireResult != BEFORE_START_OF_ROWS) { |
293 | // Case, we've fetched some rows, but are not at end of fetched |
294 | // block |
295 | if (this.currentPositionInFetchedRows < (this.fetchedRows.size() - 1)) { |
296 | return true; |
297 | } else if (this.currentPositionInFetchedRows == this.fetchedRows |
298 | .size() |
299 | && this.lastRowFetched) { |
300 | return false; |
301 | } else { |
302 | // need to fetch to determine |
303 | fetchMoreRows(); |
304 | |
305 | return (this.fetchedRows.size() > 0); |
306 | } |
307 | } |
308 | |
309 | // Okay, no rows _yet_, so fetch 'em |
310 | |
311 | fetchMoreRows(); |
312 | |
313 | return this.fetchedRows.size() > 0; |
314 | } |
315 | |
316 | /** |
317 | * Moves the current position relative 'rows' from the current position. |
318 | * |
319 | * @param rows |
320 | * the relative number of rows to move |
321 | * @throws SQLException |
322 | * if a database error occurs |
323 | */ |
324 | public void moveRowRelative(int rows) throws SQLException { |
325 | notSupported(); |
326 | } |
327 | |
328 | /** |
329 | * Returns the next row. |
330 | * |
331 | * @return the next row value |
332 | * @throws SQLException |
333 | * if a database error occurs |
334 | */ |
335 | public Object[] next() throws SQLException { |
336 | |
337 | this.currentPositionInEntireResult++; |
338 | this.currentPositionInFetchedRows++; |
339 | |
340 | // Catch the forced scroll-passed-end |
341 | if (this.fetchedRows != null && this.fetchedRows.size() == 0) { |
342 | return null; |
343 | } |
344 | |
345 | if (this.currentPositionInFetchedRows > (this.fetchedRows.size() - 1)) { |
346 | fetchMoreRows(); |
347 | this.currentPositionInFetchedRows = 0; |
348 | } |
349 | |
350 | Object[] row = (Object[]) this.fetchedRows |
351 | .get(this.currentPositionInFetchedRows); |
352 | |
353 | return row; |
354 | } |
355 | |
356 | /** |
357 | * |
358 | */ |
359 | private void fetchMoreRows() throws SQLException { |
360 | if (this.lastRowFetched) { |
361 | this.fetchedRows = new ArrayList(0); |
362 | return; |
363 | } |
364 | |
365 | synchronized (this.owner.connection.getMutex()) { |
366 | if (!this.firstFetchCompleted) { |
367 | this.firstFetchCompleted = true; |
368 | } |
369 | |
370 | int numRowsToFetch = this.owner.getFetchSize(); |
371 | |
372 | if (numRowsToFetch == 0) { |
373 | numRowsToFetch = this.prepStmt.getFetchSize(); |
374 | } |
375 | |
376 | if (numRowsToFetch == Integer.MIN_VALUE) { |
377 | // Handle the case where the user used 'old' |
378 | // streaming result sets |
379 | |
380 | numRowsToFetch = 1; |
381 | } |
382 | |
383 | this.fetchedRows = this.mysql.fetchRowsViaCursor(this.fetchedRows, |
384 | this.statementIdOnServer, this.fields, numRowsToFetch); |
385 | this.currentPositionInFetchedRows = BEFORE_START_OF_ROWS; |
386 | |
387 | if ((this.mysql.getServerStatus() & SERVER_STATUS_LAST_ROW_SENT) != 0) { |
388 | this.lastRowFetched = true; |
389 | } |
390 | } |
391 | } |
392 | |
393 | /** |
394 | * Removes the row at the given index. |
395 | * |
396 | * @param index |
397 | * the row to move to |
398 | * @throws SQLException |
399 | * if a database error occurs |
400 | */ |
401 | public void removeRow(int ind) throws SQLException { |
402 | notSupported(); |
403 | } |
404 | |
405 | /** |
406 | * Only works on non dynamic result sets. |
407 | * |
408 | * @return the size of this row data |
409 | */ |
410 | public int size() { |
411 | return RESULT_SET_SIZE_UNKNOWN; |
412 | } |
413 | |
414 | private void nextRecord() throws SQLException { |
415 | |
416 | } |
417 | |
418 | private void notSupported() throws SQLException { |
419 | throw new OperationNotSupportedException(); |
420 | } |
421 | |
422 | /* |
423 | * (non-Javadoc) |
424 | * |
425 | * @see com.mysql.jdbc.RowProvider#setOwner(com.mysql.jdbc.ResultSet) |
426 | */ |
427 | public void setOwner(ResultSet rs) { |
428 | this.owner = rs; |
429 | } |
430 | |
431 | /* |
432 | * (non-Javadoc) |
433 | * |
434 | * @see com.mysql.jdbc.RowProvider#getOwner() |
435 | */ |
436 | public ResultSet getOwner() { |
437 | return this.owner; |
438 | } |
439 | |
440 | } |