package postgresql; import java.lang.*; import java.io.*; import java.math.*; import java.text.*; import java.util.*; import java.sql.*; import postgresql.largeobject.*; import postgresql.util.*; /** * A ResultSet provides access to a table of data generated by executing a * Statement. The table rows are retrieved in sequence. Within a row its * column values can be accessed in any order. * *
A ResultSet maintains a cursor pointing to its current row of data. * Initially the cursor is positioned before the first row. The 'next' * method moves the cursor to the next row. * *
The getXXX methods retrieve column values for the current row. You can * retrieve values either using the index number of the column, or by using * the name of the column. In general using the column index will be more * efficient. Columns are numbered from 1. * *
For maximum portability, ResultSet columns within each row should be read * in left-to-right order and each column should be read only once. * *
For the getXXX methods, the JDBC driver attempts to convert the * underlying data to the specified Java type and returns a suitable Java * value. See the JDBC specification for allowable mappings from SQL types * to Java types with the ResultSet getXXX methods. * *
Column names used as input to getXXX methods are case insenstive. When * performing a getXXX using a column name, if several columns have the same * name, then the value of the first matching column will be returned. The * column name option is designed to be used when column names are used in the * SQL Query. For columns that are NOT explicitly named in the query, it is * best to use column numbers. If column names were used there is no way for * the programmer to guarentee that they actually refer to the intended * columns. * *
A ResultSet is automatically closed by the Statement that generated it * when that Statement is closed, re-executed, or is used to retrieve the * next result from a sequence of multiple results. * *
The number, types and properties of a ResultSet's columns are provided by * the ResultSetMetaData object returned by the getMetaData method. * * @see ResultSetMetaData * @see java.sql.ResultSet */ public class ResultSet implements java.sql.ResultSet { Vector rows; // The results Field fields[]; // The field descriptions String status; // Status of the result int updateCount; // How many rows did we get back? int current_row; // Our pointer to where we are at byte[][] this_row; // the current row result Connection connection; // the connection which we returned from SQLWarning warnings = null; // The warning chain boolean wasNullFlag = false; // the flag for wasNull() // We can chain multiple resultSets together - this points to // next resultSet in the chain. private ResultSet next = null; /** * Create a new ResultSet - Note that we create ResultSets to * represent the results of everything. * * @param fields an array of Field objects (basically, the * ResultSet MetaData) * @param tuples Vector of the actual data * @param status the status string returned from the back end * @param updateCount the number of rows affected by the operation * @param cursor the positioned update/delete cursor name */ public ResultSet(Connection conn, Field[] fields, Vector tuples, String status, int updateCount) { this.connection = conn; this.fields = fields; this.rows = tuples; this.status = status; this.updateCount = updateCount; this.this_row = null; this.current_row = -1; } /** * A ResultSet is initially positioned before its first row, * the first call to next makes the first row the current row; * the second call makes the second row the current row, etc. * *
If an input stream from the previous row is open, it is * implicitly closed. The ResultSet's warning chain is cleared * when a new row is read * * @return true if the new current is valid; false if there are no * more rows * @exception SQLException if a database access error occurs */ public boolean next() throws SQLException { if (++current_row >= rows.size()) return false; this_row = (byte [][])rows.elementAt(current_row); return true; } /** * In some cases, it is desirable to immediately release a ResultSet * database and JDBC resources instead of waiting for this to happen * when it is automatically closed. The close method provides this * immediate release. * *
Note: A ResultSet is automatically closed by the Statement * the Statement that generated it when that Statement is closed, * re-executed, or is used to retrieve the next result from a sequence * of multiple results. A ResultSet is also automatically closed * when it is garbage collected. * * @exception SQLException if a database access error occurs */ public void close() throws SQLException { // No-op } /** * A column may have the value of SQL NULL; wasNull() reports whether * the last column read had this special value. Note that you must * first call getXXX on a column to try to read its value and then * call wasNull() to find if the value was SQL NULL * * @return true if the last column read was SQL NULL * @exception SQLException if a database access error occurred */ public boolean wasNull() throws SQLException { return wasNullFlag; } /** * Get the value of a column in the current row as a Java String * * @param columnIndex the first column is 1, the second is 2... * @return the column value, null for SQL NULL * @exception SQLException if a database access error occurs */ public String getString(int columnIndex) throws SQLException { //byte[] bytes = getBytes(columnIndex); // //if (bytes == null) //return null; //return new String(bytes); if (columnIndex < 1 || columnIndex > fields.length) throw new SQLException("Column Index out of range"); wasNullFlag = (this_row[columnIndex - 1] == null); if(wasNullFlag) return null; return new String(this_row[columnIndex - 1]); } /** * Get the value of a column in the current row as a Java boolean * * @param columnIndex the first column is 1, the second is 2... * @return the column value, false for SQL NULL * @exception SQLException if a database access error occurs */ public boolean getBoolean(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { int c = s.charAt(0); return ((c == 't') || (c == 'T')); } return false; // SQL NULL } /** * Get the value of a column in the current row as a Java byte. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public byte getByte(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Byte.parseByte(s); } catch (NumberFormatException e) { throw new SQLException("Bad Byte Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a Java short. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public short getShort(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Short.parseShort(s); } catch (NumberFormatException e) { throw new SQLException("Bad Short Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a Java int. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public int getInt(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { throw new SQLException ("Bad Integer Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a Java long. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public long getLong(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Long.parseLong(s); } catch (NumberFormatException e) { throw new SQLException ("Bad Long Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a Java float. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public float getFloat(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Float.valueOf(s).floatValue(); } catch (NumberFormatException e) { throw new SQLException ("Bad Float Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a Java double. * * @param columnIndex the first column is 1, the second is 2,... * @return the column value; 0 if SQL NULL * @exception SQLException if a database access error occurs */ public double getDouble(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { return Double.valueOf(s).doubleValue(); } catch (NumberFormatException e) { throw new SQLException ("Bad Double Form: " + s); } } return 0; // SQL NULL } /** * Get the value of a column in the current row as a * java.lang.BigDecimal object * * @param columnIndex the first column is 1, the second is 2... * @param scale the number of digits to the right of the decimal * @return the column value; if the value is SQL NULL, null * @exception SQLException if a database access error occurs */ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { String s = getString(columnIndex); BigDecimal val; if (s != null) { try { val = new BigDecimal(s); } catch (NumberFormatException e) { throw new SQLException ("Bad BigDecimal Form: " + s); } try { return val.setScale(scale); } catch (ArithmeticException e) { throw new SQLException ("Bad BigDecimal Form: " + s); } } return null; // SQL NULL } /** * Get the value of a column in the current row as a Java byte array. * *
In normal use, the bytes represent the raw values returned by the * backend. However, if the column is an OID, then it is assumed to * refer to a Large Object, and that object is returned as a byte array. * *
Be warned If the large object is huge, then you may run out * of memory. * * @param columnIndex the first column is 1, the second is 2, ... * @return the column value; if the value is SQL NULL, the result * is null * @exception SQLException if a database access error occurs */ public byte[] getBytes(int columnIndex) throws SQLException { if (columnIndex < 1 || columnIndex > fields.length) throw new SQLException("Column Index out of range"); wasNullFlag = (this_row[columnIndex - 1] == null); // Handle OID's as BLOBS if(!wasNullFlag) if( fields[columnIndex - 1].getOID() == 26) { LargeObjectManager lom = connection.getLargeObjectAPI(); LargeObject lob = lom.open(getInt(columnIndex)); byte buf[] = lob.read(lob.size()); lob.close(); return buf; } return this_row[columnIndex - 1]; } /** * Get the value of a column in the current row as a java.sql.Date * object * * @param columnIndex the first column is 1, the second is 2... * @return the column value; null if SQL NULL * @exception SQLException if a database access error occurs */ public java.sql.Date getDate(int columnIndex) throws SQLException { String s = getString(columnIndex); if(s==null) return null; SimpleDateFormat df = new SimpleDateFormat(connection.getDateStyle()); try { return new java.sql.Date(df.parse(s).getTime()); } catch (ParseException e) { throw new SQLException("Bad Date Format: at " + e.getErrorOffset() + " in " + s); } } /** * Get the value of a column in the current row as a java.sql.Time * object * * @param columnIndex the first column is 1, the second is 2... * @return the column value; null if SQL NULL * @exception SQLException if a database access error occurs */ public Time getTime(int columnIndex) throws SQLException { String s = getString(columnIndex); if (s != null) { try { if (s.length() != 5 && s.length() != 8) throw new NumberFormatException("Wrong Length!"); int hr = Integer.parseInt(s.substring(0,2)); int min = Integer.parseInt(s.substring(3,5)); int sec = (s.length() == 5) ? 0 : Integer.parseInt(s.substring(6)); return new Time(hr, min, sec); } catch (NumberFormatException e) { throw new SQLException ("Bad Time Form: " + s); } } return null; // SQL NULL } /** * Get the value of a column in the current row as a * java.sql.Timestamp object * * @param columnIndex the first column is 1, the second is 2... * @return the column value; null if SQL NULL * @exception SQLException if a database access error occurs */ public Timestamp getTimestamp(int columnIndex) throws SQLException { String s = getString(columnIndex); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzz"); if (s != null) { int TZ = new Float(s.substring(19)).intValue(); TZ = TZ * 60 * 60 * 1000; TimeZone zone = TimeZone.getDefault(); zone.setRawOffset(TZ); String nm = zone.getID(); s = s.substring(0,18) + nm; try { java.util.Date d = df.parse(s); return new Timestamp(d.getTime()); } catch (ParseException e) { throw new SQLException("Bad Timestamp Format: at " + e.getErrorOffset() + " in " + s); } } return null; // SQL NULL } /** * A column value can be retrieved as a stream of ASCII characters * and then read in chunks from the stream. This method is * particular suitable for retrieving large LONGVARCHAR values. * The JDBC driver will do any necessary conversion from the * database format into ASCII. * *
Note: All the data in the returned stream must be read * prior to getting the value of any other column. The next call * to a get method implicitly closes the stream. Also, a stream * may return 0 for available() whether there is data available * or not. * *
We implement an ASCII stream as a Binary stream - we should really * do the data conversion, but I cannot be bothered to implement this * right now. * * @param columnIndex the first column is 1, the second is 2, ... * @return a Java InputStream that delivers the database column * value as a stream of one byte ASCII characters. If the * value is SQL NULL then the result is null * @exception SQLException if a database access error occurs * @see getBinaryStream */ public InputStream getAsciiStream(int columnIndex) throws SQLException { return getBinaryStream(columnIndex); } /** * A column value can also be retrieved as a stream of Unicode * characters. We implement this as a binary stream. * * @param columnIndex the first column is 1, the second is 2... * @return a Java InputStream that delivers the database column value * as a stream of two byte Unicode characters. If the value is * SQL NULL, then the result is null * @exception SQLException if a database access error occurs * @see getAsciiStream * @see getBinaryStream */ public InputStream getUnicodeStream(int columnIndex) throws SQLException { return getBinaryStream(columnIndex); } /** * A column value can also be retrieved as a binary strea. This * method is suitable for retrieving LONGVARBINARY values. * * @param columnIndex the first column is 1, the second is 2... * @return a Java InputStream that delivers the database column value * as a stream of bytes. If the value is SQL NULL, then the result * is null * @exception SQLException if a database access error occurs * @see getAsciiStream * @see getUnicodeStream */ public InputStream getBinaryStream(int columnIndex) throws SQLException { byte b[] = getBytes(columnIndex); if (b != null) return new ByteArrayInputStream(b); return null; // SQL NULL } /** * The following routines simply convert the columnName into * a columnIndex and then call the appropriate routine above. * * @param columnName is the SQL name of the column * @return the column value * @exception SQLException if a database access error occurs */ public String getString(String columnName) throws SQLException { return getString(findColumn(columnName)); } public boolean getBoolean(String columnName) throws SQLException { return getBoolean(findColumn(columnName)); } public byte getByte(String columnName) throws SQLException { return getByte(findColumn(columnName)); } public short getShort(String columnName) throws SQLException { return getShort(findColumn(columnName)); } public int getInt(String columnName) throws SQLException { return getInt(findColumn(columnName)); } public long getLong(String columnName) throws SQLException { return getLong(findColumn(columnName)); } public float getFloat(String columnName) throws SQLException { return getFloat(findColumn(columnName)); } public double getDouble(String columnName) throws SQLException { return getDouble(findColumn(columnName)); } public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException { return getBigDecimal(findColumn(columnName), scale); } public byte[] getBytes(String columnName) throws SQLException { return getBytes(findColumn(columnName)); } public java.sql.Date getDate(String columnName) throws SQLException { return getDate(findColumn(columnName)); } public Time getTime(String columnName) throws SQLException { return getTime(findColumn(columnName)); } public Timestamp getTimestamp(String columnName) throws SQLException { return getTimestamp(findColumn(columnName)); } public InputStream getAsciiStream(String columnName) throws SQLException { return getAsciiStream(findColumn(columnName)); } public InputStream getUnicodeStream(String columnName) throws SQLException { return getUnicodeStream(findColumn(columnName)); } public InputStream getBinaryStream(String columnName) throws SQLException { return getBinaryStream(findColumn(columnName)); } /** * The first warning reported by calls on this ResultSet is * returned. Subsequent ResultSet warnings will be chained * to this SQLWarning. * *
The warning chain is automatically cleared each time a new * row is read. * *
Note: This warning chain only covers warnings caused by * ResultSet methods. Any warnings caused by statement methods * (such as reading OUT parameters) will be chained on the * Statement object. * * @return the first SQLWarning or null; * @exception SQLException if a database access error occurs. */ public SQLWarning getWarnings() throws SQLException { return warnings; } /** * After this call, getWarnings returns null until a new warning * is reported for this ResultSet * * @exception SQLException if a database access error occurs */ public void clearWarnings() throws SQLException { warnings = null; } /** * Get the name of the SQL cursor used by this ResultSet * *
In SQL, a result table is retrieved though a cursor that is * named. The current row of a result can be updated or deleted * using a positioned update/delete statement that references * the cursor name. * *
JDBC supports this SQL feature by providing the name of the * SQL cursor used by a ResultSet. The current row of a ResulSet * is also the current row of this SQL cursor. * *
Note: If positioned update is not supported, a SQLException * is thrown. * * @return the ResultSet's SQL cursor name. * @exception SQLException if a database access error occurs */ public String getCursorName() throws SQLException { return connection.getCursorName(); } /** * The numbers, types and properties of a ResultSet's columns are * provided by the getMetaData method * * @return a description of the ResultSet's columns * @exception SQLException if a database access error occurs */ public java.sql.ResultSetMetaData getMetaData() throws SQLException { return new ResultSetMetaData(rows, fields); } /** * Get the value of a column in the current row as a Java object * *
This method will return the value of the given column as a * Java object. The type of the Java object will be the default * Java Object type corresponding to the column's SQL type, following * the mapping specified in the JDBC specification. * *
This method may also be used to read database specific abstract * data types. * * @param columnIndex the first column is 1, the second is 2... * @return a Object holding the column value * @exception SQLException if a database access error occurs */ public Object getObject(int columnIndex) throws SQLException { Field field; if (columnIndex < 1 || columnIndex > fields.length) throw new SQLException("Column index out of range"); field = fields[columnIndex - 1]; // some fields can be null, mainly from those returned by MetaData methods if(field==null) { wasNullFlag=true; return null; } switch (field.getSQLType()) { case Types.BIT: return new Boolean(getBoolean(columnIndex)); case Types.SMALLINT: return new Integer(getInt(columnIndex)); case Types.INTEGER: return new Integer(getInt(columnIndex)); case Types.BIGINT: return new Long(getLong(columnIndex)); case Types.NUMERIC: return getBigDecimal(columnIndex, 0); case Types.REAL: return new Float(getFloat(columnIndex)); case Types.DOUBLE: return new Double(getDouble(columnIndex)); case Types.CHAR: case Types.VARCHAR: return getString(columnIndex); case Types.DATE: return getDate(columnIndex); case Types.TIME: return getTime(columnIndex); case Types.TIMESTAMP: return getTimestamp(columnIndex); default: return connection.getObject(field.getTypeName(), getString(columnIndex)); } } /** * Get the value of a column in the current row as a Java object * *
This method will return the value of the given column as a * Java object. The type of the Java object will be the default * Java Object type corresponding to the column's SQL type, following * the mapping specified in the JDBC specification. * *
This method may also be used to read database specific abstract * data types. * * @param columnName is the SQL name of the column * @return a Object holding the column value * @exception SQLException if a database access error occurs */ public Object getObject(String columnName) throws SQLException { return getObject(findColumn(columnName)); } /** * Map a ResultSet column name to a ResultSet column index * * @param columnName the name of the column * @return the column index * @exception SQLException if a database access error occurs */ public int findColumn(String columnName) throws SQLException { int i; for (i = 0 ; i < fields.length; ++i) if (fields[i].name.equalsIgnoreCase(columnName)) return (i+1); throw new SQLException ("Column name not found"); } // ************************************************************ // END OF PUBLIC INTERFACE // ************************************************************ /** * We at times need to know if the resultSet we are working * with is the result of an UPDATE, DELETE or INSERT (in which * case, we only have a row count), or of a SELECT operation * (in which case, we have multiple fields) - this routine * tells us. * * @return true if we have tuples available */ public boolean reallyResultSet() { return (fields != null); } /** * Since ResultSets can be chained, we need some method of * finding the next one in the chain. The method getNext() * returns the next one in the chain. * * @return the next ResultSet, or null if there are none */ public ResultSet getNext() { return next; } /** * This following method allows us to add a ResultSet object * to the end of the current chain. * * @param r the resultset to add to the end of the chain. */ public void append(ResultSet r) { if (next == null) next = r; else next.append(r); } /** * If we are just a place holder for results, we still need * to get an updateCount. This method returns it. * * @return the updateCount */ public int getResultCount() { return updateCount; } /** * We also need to provide a couple of auxiliary functions for * the implementation of the ResultMetaData functions. In * particular, we need to know the number of rows and the * number of columns. Rows are also known as Tuples * * @return the number of rows */ public int getTupleCount() { return rows.size(); } /** * getColumnCount returns the number of columns * * @return the number of columns */ public int getColumnCount() { return fields.length; } /** * Returns the status message from the backend.
* It is used internally by the driver. * * @return the status string from the backend */ public String getStatusString() { return status; } /** * returns the OID of a field.
* It is used internally by the driver. * * @param field field id * @return the oid of that field's type */ public int getColumnOID(int field) { return fields[field-1].getOID(); } }