001: /*
002: * Jython Database Specification API 2.0
003: *
004: * $Id: Fetch.java 2542 2005-06-20 17:12:15Z fwierzbicki $
005: *
006: * Copyright (c) 2001 brian zimmer <bzimmer@ziclix.com>
007: *
008: */
009: package com.ziclix.python.sql;
010:
011: import org.python.core.Py;
012: import org.python.core.PyException;
013: import org.python.core.PyInteger;
014: import org.python.core.PyList;
015: import org.python.core.PyObject;
016: import org.python.core.PyTuple;
017: import org.python.core.__builtin__;
018:
019: import java.sql.CallableStatement;
020: import java.sql.DatabaseMetaData;
021: import java.sql.ResultSet;
022: import java.sql.ResultSetMetaData;
023: import java.sql.SQLException;
024: import java.sql.SQLWarning;
025: import java.sql.Types;
026: import java.util.ArrayList;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Set;
030:
031: /**
032: * <p>The responsibility of a Fetch instance is to manage the iteration of a
033: * ResultSet. Two different alogorithms are available: static or dynamic.</p>
034: * <p/>
035: * <p><b>Static</b> The static variety iterates the entire set immediately,
036: * creating the necessary Jython objects and storing them. It is able to
037: * immediately close the ResultSet so a call to close() is essentially a no-op
038: * from a database resource perspective (it does clear the results list however).
039: * This approach also allows for the correct rowcount to be determined since
040: * the entire result set has been iterated.</p>
041: * <p/>
042: * <p><b>Dynamic</b> The dynamic variety iterates the result set only as requested.
043: * This holds a bit truer to the intent of the API as the fetch*() methods actually
044: * fetch when instructed. This is especially useful for managing exeedingly large
045: * results, but is unable to determine the rowcount without having worked through
046: * the entire result set. The other disadvantage is the ResultSet remains open
047: * throughout the entire iteration. So the tradeoff is in open database resources
048: * versus JVM resources since the application can keep constant space if it doesn't
049: * require the entire result set be presented as one.</p>
050: *
051: * @author brian zimmer
052: * @version $Revision: 2542 $
053: */
054: abstract public class Fetch {
055:
056: /**
057: * The total number of rows in the result set.
058: * <p/>
059: * Note: since JDBC provides no means to get this information without iterating
060: * the entire result set, only those fetches which build the result statically
061: * will have an accurate row count.
062: */
063: protected int rowcount;
064:
065: /**
066: * The current row of the cursor (-1 if off either end).
067: */
068: protected int rownumber;
069:
070: /**
071: * Field cursor
072: */
073: private DataHandler datahandler;
074:
075: /**
076: * Field description
077: */
078: protected PyObject description;
079:
080: /**
081: * A list of warning listeners.
082: */
083: private List listeners;
084:
085: /**
086: * Constructor Fetch
087: *
088: * @param datahandler
089: */
090: protected Fetch(DataHandler datahandler) {
091:
092: this .rowcount = -1;
093: this .rownumber = -1;
094: this .description = Py.None;
095: this .datahandler = datahandler;
096: this .listeners = new ArrayList(3);
097: }
098:
099: /**
100: * Method newFetch
101: *
102: * @param datahandler
103: * @param dynamic
104: * @return Fetch
105: */
106: public static Fetch newFetch(DataHandler datahandler,
107: boolean dynamic) {
108:
109: if (dynamic) {
110: return new DynamicFetch(datahandler);
111: } else {
112: return new StaticFetch(datahandler);
113: }
114: }
115:
116: /**
117: * The number of rows in the current result set.
118: */
119: public int getRowCount() {
120: return this .rowcount;
121: }
122:
123: /**
124: * The description of each column, in order, for the data in the result
125: * set.
126: */
127: public PyObject getDescription() {
128: return this .description;
129: }
130:
131: /**
132: * Create the results after a successful execution and manages the result set.
133: *
134: * @param resultSet
135: */
136: abstract public void add(ResultSet resultSet);
137:
138: /**
139: * Create the results after a successful execution and manages the result set.
140: * Optionally takes a set of JDBC-indexed columns to automatically set to None
141: * primarily to support getTypeInfo() which sets a column type of a number but
142: * doesn't use the value so a driver is free to put anything it wants there.
143: *
144: * @param resultSet
145: * @param skipCols JDBC-indexed set of columns to be skipped
146: */
147: abstract public void add(ResultSet resultSet, Set skipCols);
148:
149: /**
150: * Method add
151: *
152: * @param callableStatement
153: * @param procedure
154: * @param params
155: */
156: abstract public void add(CallableStatement callableStatement,
157: Procedure procedure, PyObject params);
158:
159: /**
160: * Fetch the next row of a query result set, returning a single sequence,
161: * or None when no more data is available.
162: * <p/>
163: * An Error (or subclass) exception is raised if the previous call to
164: * executeXXX() did not produce any result set or no call was issued yet.
165: *
166: * @return a single sequence from the result set, or None when no more data is available
167: */
168: public PyObject fetchone() {
169:
170: PyObject sequence = fetchmany(1);
171:
172: if (sequence.__len__() == 1) {
173: return sequence.__getitem__(0);
174: } else {
175: return Py.None;
176: }
177: }
178:
179: /**
180: * Fetch all (remaining) rows of a query result, returning them as a sequence
181: * of sequences (e.g. a list of tuples). Note that the cursor's arraysize attribute
182: * can affect the performance of this operation.
183: * <p/>
184: * An Error (or subclass) exception is raised if the previous call to executeXXX()
185: * did not produce any result set or no call was issued yet.
186: *
187: * @return a sequence of sequences from the result set, or None when no more data is available
188: */
189: public abstract PyObject fetchall();
190:
191: /**
192: * Fetch the next set of rows of a query result, returning a sequence of
193: * sequences (e.g. a list of tuples). An empty sequence is returned when
194: * no more rows are available.
195: * <p/>
196: * The number of rows to fetch per call is specified by the parameter. If
197: * it is not given, the cursor's arraysize determines the number of rows
198: * to be fetched. The method should try to fetch as many rows as indicated
199: * by the size parameter. If this is not possible due to the specified number
200: * of rows not being available, fewer rows may be returned.
201: * <p/>
202: * An Error (or subclass) exception is raised if the previous call to executeXXX()
203: * did not produce any result set or no call was issued yet.
204: * <p/>
205: * Note there are performance considerations involved with the size parameter.
206: * For optimal performance, it is usually best to use the arraysize attribute.
207: * If the size parameter is used, then it is best for it to retain the same value
208: * from one fetchmany() call to the next.
209: *
210: * @return a sequence of sequences from the result set, or None when no more data is available
211: */
212: public abstract PyObject fetchmany(int size);
213:
214: /**
215: * Move the result pointer to the next set if available.
216: *
217: * @return true if more sets exist, else None
218: */
219: public abstract PyObject nextset();
220:
221: /**
222: * Scroll the cursor in the result set to a new position according
223: * to mode.
224: * <p/>
225: * If mode is 'relative' (default), value is taken as offset to
226: * the current position in the result set, if set to 'absolute',
227: * value states an absolute target position.
228: * <p/>
229: * An IndexError should be raised in case a scroll operation would
230: * leave the result set. In this case, the cursor position is left
231: * undefined (ideal would be to not move the cursor at all).
232: * <p/>
233: * Note: This method should use native scrollable cursors, if
234: * available, or revert to an emulation for forward-only
235: * scrollable cursors. The method may raise NotSupportedErrors to
236: * signal that a specific operation is not supported by the
237: * database (e.g. backward scrolling).
238: *
239: * @param value
240: * @param mode
241: */
242: public abstract void scroll(int value, String mode);
243:
244: /**
245: * Cleanup any resources.
246: */
247: public void close() throws SQLException {
248: this .listeners.clear();
249: }
250:
251: /**
252: * Builds a tuple containing the meta-information about each column.
253: * <p/>
254: * (name, type_code, display_size, internal_size, precision, scale, null_ok)
255: * <p/>
256: * precision and scale are only available for numeric types
257: */
258: protected PyObject createDescription(ResultSetMetaData meta)
259: throws SQLException {
260:
261: PyObject metadata = new PyList();
262:
263: for (int i = 1; i <= meta.getColumnCount(); i++) {
264: PyObject[] a = new PyObject[7];
265:
266: a[0] = Py.newString(meta.getColumnName(i));
267: a[1] = Py.newInteger(meta.getColumnType(i));
268: a[2] = Py.newInteger(meta.getColumnDisplaySize(i));
269: a[3] = Py.None;
270:
271: switch (meta.getColumnType(i)) {
272:
273: case Types.BIGINT:
274: case Types.BIT:
275: case Types.DECIMAL:
276: case Types.DOUBLE:
277: case Types.FLOAT:
278: case Types.INTEGER:
279: case Types.SMALLINT:
280: a[4] = Py.newInteger(meta.getPrecision(i));
281: a[5] = Py.newInteger(meta.getScale(i));
282: break;
283:
284: default:
285: a[4] = Py.None;
286: a[5] = Py.None;
287: break;
288: }
289:
290: a[6] = Py.newInteger(meta.isNullable(i));
291:
292: ((PyList) metadata).append(new PyTuple(a));
293: }
294:
295: return metadata;
296: }
297:
298: /**
299: * Builds a tuple containing the meta-information about each column.
300: * <p/>
301: * (name, type_code, display_size, internal_size, precision, scale, null_ok)
302: * <p/>
303: * precision and scale are only available for numeric types
304: */
305: protected PyObject createDescription(Procedure procedure)
306: throws SQLException {
307:
308: PyObject metadata = new PyList();
309:
310: for (int i = 0, len = procedure.columns.__len__(); i < len; i++) {
311: PyObject column = procedure.columns.__getitem__(i);
312: int colType = ((PyInteger) column.__getitem__(
313: Procedure.COLUMN_TYPE).__int__()).getValue();
314:
315: switch (colType) {
316:
317: case DatabaseMetaData.procedureColumnReturn:
318: PyObject[] a = new PyObject[7];
319:
320: a[0] = column.__getitem__(Procedure.NAME);
321: a[1] = column.__getitem__(Procedure.DATA_TYPE);
322: a[2] = Py.newInteger(-1);
323: a[3] = column.__getitem__(Procedure.LENGTH);
324:
325: switch (((PyInteger) a[1].__int__()).getValue()) {
326:
327: case Types.BIGINT:
328: case Types.BIT:
329: case Types.DECIMAL:
330: case Types.DOUBLE:
331: case Types.FLOAT:
332: case Types.INTEGER:
333: case Types.SMALLINT:
334: a[4] = column.__getitem__(Procedure.PRECISION);
335: a[5] = column.__getitem__(Procedure.SCALE);
336: break;
337:
338: default:
339: a[4] = Py.None;
340: a[5] = Py.None;
341: break;
342: }
343:
344: int nullable = ((PyInteger) column.__getitem__(
345: Procedure.NULLABLE).__int__()).getValue();
346:
347: a[6] = (nullable == DatabaseMetaData.procedureNullable) ? Py.One
348: : Py.Zero;
349:
350: ((PyList) metadata).append(new PyTuple(a));
351: break;
352: }
353: }
354:
355: return metadata;
356: }
357:
358: /**
359: * Method createResults
360: *
361: * @param callableStatement
362: * @param procedure
363: * @param params
364: * @return PyObject
365: * @throws SQLException
366: */
367: protected PyObject createResults(
368: CallableStatement callableStatement, Procedure procedure,
369: PyObject params) throws SQLException {
370:
371: PyList results = new PyList();
372:
373: for (int i = 0, j = 0, len = procedure.columns.__len__(); i < len; i++) {
374: PyObject obj = Py.None;
375: PyObject column = procedure.columns.__getitem__(i);
376: int colType = ((PyInteger) column.__getitem__(
377: Procedure.COLUMN_TYPE).__int__()).getValue();
378: int dataType = ((PyInteger) column.__getitem__(
379: Procedure.DATA_TYPE).__int__()).getValue();
380:
381: switch (colType) {
382:
383: case DatabaseMetaData.procedureColumnIn:
384: j++;
385: break;
386:
387: case DatabaseMetaData.procedureColumnOut:
388: case DatabaseMetaData.procedureColumnInOut:
389: obj = datahandler.getPyObject(callableStatement, i + 1,
390: dataType);
391:
392: params.__setitem__(j++, obj);
393: break;
394:
395: case DatabaseMetaData.procedureColumnReturn:
396: obj = datahandler.getPyObject(callableStatement, i + 1,
397: dataType);
398:
399: // Oracle sends ResultSets as a return value
400: Object rs = obj.__tojava__(ResultSet.class);
401:
402: if (rs == Py.NoConversion) {
403: results.append(obj);
404: } else {
405: add((ResultSet) rs);
406: }
407: break;
408: }
409: }
410:
411: if (results.__len__() == 0) {
412: return results;
413: }
414:
415: PyList ret = new PyList();
416:
417: ret.append(__builtin__.tuple(results));
418:
419: return ret;
420: }
421:
422: /**
423: * Creates the results of a query. Iterates through the list and builds the tuple.
424: *
425: * @param set result set
426: * @param skipCols set of JDBC-indexed columns to automatically set to None
427: * @return a list of tuples of the results
428: * @throws SQLException
429: */
430: protected PyList createResults(ResultSet set, Set skipCols,
431: PyObject metaData) throws SQLException {
432:
433: PyList res = new PyList();
434:
435: while (set.next()) {
436: PyObject tuple = createResult(set, skipCols, metaData);
437:
438: res.append(tuple);
439: }
440:
441: return res;
442: }
443:
444: /**
445: * Creates the individual result row from the current ResultSet row.
446: *
447: * @param set result set
448: * @param skipCols set of JDBC-indexed columns to automatically set to None
449: * @return a tuple of the results
450: * @throws SQLException
451: */
452: protected PyTuple createResult(ResultSet set, Set skipCols,
453: PyObject metaData) throws SQLException {
454:
455: int descriptionLength = metaData.__len__();
456: PyObject[] row = new PyObject[descriptionLength];
457:
458: for (int i = 0; i < descriptionLength; i++) {
459: if ((skipCols != null)
460: && skipCols.contains(new Integer(i + 1))) {
461: row[i] = Py.None;
462: } else {
463: int type = ((PyInteger) metaData.__getitem__(i)
464: .__getitem__(1)).getValue();
465:
466: row[i] = datahandler.getPyObject(set, i + 1, type);
467: }
468: }
469:
470: SQLWarning warning = set.getWarnings();
471:
472: if (warning != null) {
473: fireWarning(warning);
474: }
475:
476: PyTuple tuple = new PyTuple(row);
477:
478: return tuple;
479: }
480:
481: protected void fireWarning(SQLWarning warning) {
482:
483: WarningEvent event = new WarningEvent(this , warning);
484:
485: for (int i = listeners.size() - 1; i >= 0; i--) {
486: try {
487: ((WarningListener) listeners.get(i)).warning(event);
488: } catch (Throwable t) {
489: }
490: }
491: }
492:
493: public void addWarningListener(WarningListener listener) {
494: this .listeners.add(listener);
495: }
496:
497: public boolean removeWarningListener(WarningListener listener) {
498: return this .listeners.remove(listener);
499: }
500: }
501:
502: /**
503: * This version of fetch builds the results statically. This consumes more resources but
504: * allows for efficient closing of database resources because the contents of the result
505: * set are immediately consumed. It also allows for an accurate rowcount attribute, whereas
506: * a dynamic query is unable to provide this information until all the results have been
507: * consumed.
508: */
509: class StaticFetch extends Fetch {
510:
511: /**
512: * Field results
513: */
514: protected List results;
515:
516: /**
517: * Field descriptions
518: */
519: protected List descriptions;
520:
521: /**
522: * Construct a static fetch. The entire result set is iterated as it
523: * is added and the result set is immediately closed.
524: */
525: public StaticFetch(DataHandler datahandler) {
526:
527: super (datahandler);
528:
529: this .results = new LinkedList();
530: this .descriptions = new LinkedList();
531: }
532:
533: /**
534: * Method add
535: *
536: * @param resultSet
537: */
538: public void add(ResultSet resultSet) {
539: this .add(resultSet, null);
540: }
541:
542: /**
543: * Method add
544: *
545: * @param resultSet
546: * @param skipCols
547: */
548: public void add(ResultSet resultSet, Set skipCols) {
549:
550: try {
551: if ((resultSet != null)
552: && (resultSet.getMetaData() != null)) {
553: PyObject metadata = this .createDescription(resultSet
554: .getMetaData());
555: PyObject result = this .createResults(resultSet,
556: skipCols, metadata);
557:
558: this .results.add(result);
559: this .descriptions.add(metadata);
560:
561: // we want the rowcount of the first result set
562: this .rowcount = ((PyObject) this .results.get(0))
563: .__len__();
564:
565: // we want the description of the first result set
566: this .description = ((PyObject) this .descriptions.get(0));
567:
568: // set the current rownumber
569: this .rownumber = 0;
570: }
571: } catch (PyException e) {
572: throw e;
573: } catch (Throwable e) {
574: throw zxJDBC.makeException(e);
575: } finally {
576: try {
577: resultSet.close();
578: } catch (Throwable e) {
579: }
580: }
581: }
582:
583: /**
584: * Method add
585: *
586: * @param callableStatement
587: * @param procedure
588: * @param params
589: */
590: public void add(CallableStatement callableStatement,
591: Procedure procedure, PyObject params) {
592:
593: try {
594: PyObject result = this .createResults(callableStatement,
595: procedure, params);
596:
597: if (result.__len__() > 0) {
598: this .results.add(result);
599: this .descriptions
600: .add(this .createDescription(procedure));
601:
602: // we want the rowcount of the first result set
603: this .rowcount = ((PyObject) this .results.get(0))
604: .__len__();
605:
606: // we want the description of the first result set
607: this .description = ((PyObject) this .descriptions.get(0));
608:
609: // set the current rownumber
610: this .rownumber = 0;
611: }
612: } catch (PyException e) {
613: throw e;
614: } catch (Throwable e) {
615: throw zxJDBC.makeException(e);
616: }
617: }
618:
619: /**
620: * Fetch all (remaining) rows of a query result, returning them as a sequence
621: * of sequences (e.g. a list of tuples). Note that the cursor's arraysize attribute
622: * can affect the performance of this operation.
623: * <p/>
624: * An Error (or subclass) exception is raised if the previous call to executeXXX()
625: * did not produce any result set or no call was issued yet.
626: *
627: * @return a sequence of sequences from the result set, or an empty sequence when
628: * no more data is available
629: */
630: public PyObject fetchall() {
631: return fetchmany(this .rowcount);
632: }
633:
634: /**
635: * Fetch the next set of rows of a query result, returning a sequence of
636: * sequences (e.g. a list of tuples). An empty sequence is returned when
637: * no more rows are available.
638: * <p/>
639: * The number of rows to fetch per call is specified by the parameter. If
640: * it is not given, the cursor's arraysize determines the number of rows
641: * to be fetched. The method should try to fetch as many rows as indicated
642: * by the size parameter. If this is not possible due to the specified number
643: * of rows not being available, fewer rows may be returned.
644: * <p/>
645: * An Error (or subclass) exception is raised if the previous call to executeXXX()
646: * did not produce any result set or no call was issued yet.
647: * <p/>
648: * Note there are performance considerations involved with the size parameter.
649: * For optimal performance, it is usually best to use the arraysize attribute.
650: * If the size parameter is used, then it is best for it to retain the same value
651: * from one fetchmany() call to the next.
652: *
653: * @return a sequence of sequences from the result set, or an empty sequence when
654: * no more data is available
655: */
656: public PyObject fetchmany(int size) {
657:
658: if ((results == null) || (results.size() == 0)) {
659: throw zxJDBC.makeException(zxJDBC.DatabaseError,
660: "no results");
661: }
662:
663: PyObject res = new PyList();
664: PyObject current = (PyObject) results.get(0);
665:
666: if (size <= 0) {
667: size = this .rowcount;
668: }
669:
670: if (this .rownumber < this .rowcount) {
671: res = current.__getslice__(Py.newInteger(this .rownumber),
672: Py.newInteger(this .rownumber + size), Py.One);
673: this .rownumber += size;
674: }
675:
676: return res;
677: }
678:
679: /**
680: * Method scroll
681: *
682: * @param value
683: * @param mode 'relative' or 'absolute'
684: */
685: public void scroll(int value, String mode) {
686:
687: int pos;
688:
689: if ("relative".equals(mode)) {
690: pos = this .rownumber + value;
691: } else if ("absolute".equals(mode)) {
692: pos = value;
693: } else {
694: throw zxJDBC.makeException(zxJDBC.ProgrammingError,
695: "invalid cursor scroll mode [" + mode + "]");
696: }
697:
698: if ((pos >= 0) && (pos < this .rowcount)) {
699: this .rownumber = pos;
700: } else {
701: throw zxJDBC.makeException(Py.IndexError, "cursor index ["
702: + pos + "] out of range");
703: }
704: }
705:
706: /**
707: * Move the result pointer to the next set if available.
708: *
709: * @return true if more sets exist, else None
710: */
711: public PyObject nextset() {
712:
713: PyObject next = Py.None;
714:
715: if ((results != null) && (results.size() > 1)) {
716: this .results.remove(0);
717: this .descriptions.remove(0);
718:
719: next = (PyObject) this .results.get(0);
720: this .description = (PyObject) this .descriptions.get(0);
721: this .rowcount = next.__len__();
722: this .rownumber = 0;
723: }
724:
725: return (next == Py.None) ? Py.None : Py.One;
726: }
727:
728: /**
729: * Remove the results.
730: */
731: public void close() throws SQLException {
732:
733: super .close();
734:
735: this .rownumber = -1;
736:
737: this .results.clear();
738: }
739: }
740:
741: /**
742: * Dynamically construct the results from an execute*(). The static version builds the entire
743: * result set immediately upon completion of the query, however in some circumstances, this
744: * requires far too many resources to be efficient. In this version of the fetch the resources
745: * remain constant. The dis-advantage to this approach from an API perspective is its impossible
746: * to generate an accurate rowcount since not all the rows have been consumed.
747: */
748: class DynamicFetch extends Fetch {
749:
750: /**
751: * Field skipCols
752: */
753: protected Set skipCols;
754:
755: /**
756: * Field resultSet
757: */
758: protected ResultSet resultSet;
759:
760: /**
761: * Construct a dynamic fetch.
762: */
763: public DynamicFetch(DataHandler datahandler) {
764: super (datahandler);
765: }
766:
767: /**
768: * Add the result set to the results. If more than one result
769: * set is attempted to be added, an Error is raised since JDBC
770: * requires that only one ResultSet be iterated for one Statement
771: * at any one time. Since this is a dynamic iteration, it precludes
772: * the addition of more than one result set.
773: */
774: public void add(ResultSet resultSet) {
775: add(resultSet, null);
776: }
777:
778: /**
779: * Add the result set to the results. If more than one result
780: * set is attempted to be added, an Error is raised since JDBC
781: * requires that only one ResultSet be iterated for one Statement
782: * at any one time. Since this is a dynamic iteration, it precludes
783: * the addition of more than one result set.
784: */
785: public void add(ResultSet resultSet, Set skipCols) {
786:
787: if (this .resultSet != null) {
788: throw zxJDBC.makeException(zxJDBC
789: .getString("onlyOneResultSet"));
790: }
791:
792: try {
793: if ((resultSet != null)
794: && (resultSet.getMetaData() != null)) {
795: if (this .description == Py.None) {
796: this .description = this .createDescription(resultSet
797: .getMetaData());
798: }
799:
800: this .resultSet = resultSet;
801: this .skipCols = skipCols;
802:
803: // it would be more compliant if we knew the resultSet actually
804: // contained some rows, but since we don't make a stab at it so
805: // everything else looks better
806: this .rowcount = 0;
807: this .rownumber = 0;
808: }
809: } catch (PyException e) {
810: throw e;
811: } catch (Throwable e) {
812: throw zxJDBC.makeException(e);
813: }
814: }
815:
816: /**
817: * Method add
818: *
819: * @param callableStatement
820: * @param procedure
821: * @param params
822: */
823: public void add(CallableStatement callableStatement,
824: Procedure procedure, PyObject params) {
825: throw zxJDBC.makeException(zxJDBC.NotSupportedError, zxJDBC
826: .getString("nocallprocsupport"));
827: }
828:
829: /**
830: * Iterate the remaining contents of the ResultSet and return.
831: */
832: public PyObject fetchall() {
833: return fetch(0, true);
834: }
835:
836: /**
837: * Iterate up to size rows remaining in the ResultSet and return.
838: */
839: public PyObject fetchmany(int size) {
840: return fetch(size, false);
841: }
842:
843: /**
844: * Internal use only. If <i>all</i> is true, return everything
845: * that's left in the result set, otherwise return up to size. Fewer
846: * than size may be returned if fewer than size results are left in
847: * the set.
848: */
849: private PyObject fetch(int size, boolean all) {
850:
851: PyList res = new PyList();
852:
853: if (this .resultSet == null) {
854: throw zxJDBC.makeException(zxJDBC.DatabaseError,
855: "no results");
856: }
857:
858: try {
859: all = (size < 0) ? true : all;
860:
861: while (((size-- > 0) || all) && this .resultSet.next()) {
862: PyTuple tuple = createResult(this .resultSet,
863: this .skipCols, this .description);
864: res.append(tuple);
865: this .rowcount++;
866: this .rownumber = this .resultSet.getRow();
867: }
868: } catch (AbstractMethodError e) {
869: throw zxJDBC.makeException(zxJDBC.NotSupportedError, zxJDBC
870: .getString("nodynamiccursors"));
871: } catch (PyException e) {
872: throw e;
873: } catch (Throwable e) {
874: throw zxJDBC.makeException(e);
875: }
876:
877: return res;
878: }
879:
880: /**
881: * Always returns None.
882: */
883: public PyObject nextset() {
884: return Py.None;
885: }
886:
887: /**
888: * Method scroll
889: *
890: * @param value
891: * @param mode
892: */
893: public void scroll(int value, String mode) {
894:
895: try {
896: int type = this .resultSet.getType();
897:
898: if ((type != ResultSet.TYPE_FORWARD_ONLY) || (value > 0)) {
899: if ("relative".equals(mode)) {
900: if (value < 0) {
901: value = Math.abs(this .rownumber + value);
902: } else if (value > 0) {
903: value = this .rownumber + value + 1;
904: }
905: } else if ("absolute".equals(mode)) {
906: if (value < 0) {
907: throw zxJDBC.makeException(Py.IndexError,
908: "cursor index [" + value
909: + "] out of range");
910: }
911: } else {
912: throw zxJDBC
913: .makeException(zxJDBC.ProgrammingError,
914: "invalid cursor scroll mode ["
915: + mode + "]");
916: }
917:
918: if (value == 0) {
919: this .resultSet.beforeFirst();
920: } else {
921: if (!this .resultSet.absolute(value)) {
922: throw zxJDBC.makeException(Py.IndexError,
923: "cursor index [" + value
924: + "] out of range");
925: }
926: }
927:
928: // since .rownumber is the *next* row, then the JDBC value suits us fine
929: this .rownumber = this .resultSet.getRow();
930: } else {
931: String msg = "dynamic result set of type [" + type
932: + "] does not support scrolling";
933:
934: throw zxJDBC.makeException(zxJDBC.NotSupportedError,
935: msg);
936: }
937: } catch (AbstractMethodError e) {
938: throw zxJDBC.makeException(zxJDBC.NotSupportedError, zxJDBC
939: .getString("nodynamiccursors"));
940: } catch (SQLException e) {
941: throw zxJDBC.makeException(e);
942: } catch (Throwable t) {
943: throw zxJDBC.makeException(t);
944: }
945: }
946:
947: /**
948: * Close the underlying ResultSet.
949: */
950: public void close() throws SQLException {
951:
952: super .close();
953:
954: if (this .resultSet == null) {
955: return;
956: }
957:
958: this .rownumber = -1;
959:
960: try {
961: this.resultSet.close();
962: } finally {
963: this.resultSet = null;
964: }
965: }
966: }
|