Hi, Attached is my attempt to clean up the horrors of the ExecSQL() method in the JDBC driver. I've done this by extracting it into a new method object called QueryExecutor (should go into org/postgresql/core/) and then taking it apart into different methods in that class. A short summary: * Extracted ExecSQL() from Connection into a method object called QueryExecutor. * Moved ReceiveFields() from Connection to QueryExecutor. * Extracted parts of the original ExecSQL() method body into smaller methods on QueryExecutor. * Bug fix: The instance variable "pid" in Connection was used in two places with different meaning. Both were probably in dead code, but it's fixed anyway. /Anders PS.: If anyone has any idea what the variable names "fqp" and "hfr" stand for, please tell me! :) _____________________________________________________________________ A n d e r s B e n g t s s o n [EMAIL PROTECTED] Stockholm, Sweden
Index: src/interfaces/jdbc/org/postgresql/Connection.java =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/Connection.java,v retrieving revision 1.26 diff -c -r1.26 Connection.java *** src/interfaces/jdbc/org/postgresql/Connection.java 2001/08/24 16:50:12 1.26 --- src/interfaces/jdbc/org/postgresql/Connection.java 2001/08/26 18:33:48 *************** *** 8,14 **** import org.postgresql.fastpath.*; import org.postgresql.largeobject.*; import org.postgresql.util.*; ! import org.postgresql.core.Encoding; /** * $Id: Connection.java,v 1.26 2001/08/24 16:50:12 momjian Exp $ --- 8,14 ---- import org.postgresql.fastpath.*; import org.postgresql.largeobject.*; import org.postgresql.util.*; ! import org.postgresql.core.*; /** * $Id: Connection.java,v 1.26 2001/08/24 16:50:12 momjian Exp $ *************** *** 348,513 **** * @return a ResultSet holding the results * @exception SQLException if a database error occurs */ ! public java.sql.ResultSet ExecSQL(String sql,java.sql.Statement stat) throws SQLException { ! // added Jan 30 2001 to correct maxrows per statement ! int maxrows=0; ! if(stat!=null) ! maxrows=stat.getMaxRows(); ! ! // added Oct 7 1998 to give us thread safety. ! synchronized(pg_stream) { ! // Deallocate all resources in the stream associated ! // with a previous request. ! // This will let the driver reuse byte arrays that has already ! // been allocated instead of allocating new ones in order ! // to gain performance improvements. ! // PM 17/01/01: Commented out due to race bug. See comments in ! // PG_Stream ! //pg_stream.deallocate(); ! ! Field[] fields = null; ! Vector tuples = new Vector(); ! byte[] buf = null; ! int fqp = 0; ! boolean hfr = false; ! String recv_status = null, msg; ! int update_count = 1; ! int insert_oid = 0; ! SQLException final_error = null; ! ! buf = encoding.encode(sql); ! try ! { ! pg_stream.SendChar('Q'); ! pg_stream.Send(buf); ! pg_stream.SendChar(0); ! pg_stream.flush(); ! } catch (IOException e) { ! throw new PSQLException("postgresql.con.ioerror",e); ! } ! ! while (!hfr || fqp > 0) ! { ! Object tup=null; // holds rows as they are recieved ! ! int c = pg_stream.ReceiveChar(); ! ! switch (c) ! { ! case 'A': // Asynchronous Notify ! pid = pg_stream.ReceiveInteger(4); ! msg = pg_stream.ReceiveString(encoding); ! break; ! case 'B': // Binary Data Transfer ! if (fields == null) ! throw new PSQLException("postgresql.con.tuple"); ! tup = pg_stream.ReceiveTuple(fields.length, true); ! // This implements Statement.setMaxRows() ! if(maxrows==0 || tuples.size()<maxrows) ! tuples.addElement(tup); ! break; ! case 'C': // Command Status ! recv_status = pg_stream.ReceiveString(encoding); ! ! // Now handle the update count correctly. ! if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE") || recv_status.startsWith("MOVE")) { ! try { ! update_count = Integer.parseInt(recv_status.substring(1+recv_status.lastIndexOf(' '))); ! } catch(NumberFormatException nfe) { ! throw new PSQLException("postgresql.con.fathom",recv_status); ! } ! if(recv_status.startsWith("INSERT")) { ! try { ! insert_oid = Integer.parseInt(recv_status.substring(1+recv_status.indexOf(' '),recv_status.lastIndexOf(' '))); ! } catch(NumberFormatException nfe) { ! throw new PSQLException("postgresql.con.fathom",recv_status); ! } ! } ! } ! if (fields != null) ! hfr = true; ! else ! { ! try ! { ! pg_stream.SendChar('Q'); ! pg_stream.SendChar(' '); ! pg_stream.SendChar(0); ! pg_stream.flush(); ! } catch (IOException e) { ! throw new PSQLException("postgresql.con.ioerror",e); ! } ! fqp++; ! } ! break; ! case 'D': // Text Data Transfer ! if (fields == null) ! throw new PSQLException("postgresql.con.tuple"); ! tup = pg_stream.ReceiveTuple(fields.length, false); ! // This implements Statement.setMaxRows() ! if(maxrows==0 || tuples.size()<maxrows) ! tuples.addElement(tup); ! break; ! case 'E': // Error Message ! msg = pg_stream.ReceiveString(encoding); ! final_error = new SQLException(msg); ! hfr = true; ! break; ! case 'I': // Empty Query ! int t = pg_stream.ReceiveChar(); ! ! if (t != 0) ! throw new PSQLException("postgresql.con.garbled"); ! if (fqp > 0) ! fqp--; ! if (fqp == 0) ! hfr = true; ! break; ! case 'N': // Error Notification ! addWarning(pg_stream.ReceiveString(encoding)); ! break; ! case 'P': // Portal Name ! String pname = pg_stream.ReceiveString(encoding); ! break; ! case 'T': // MetaData Field Description ! if (fields != null) ! throw new PSQLException("postgresql.con.multres"); ! fields = ReceiveFields(); ! break; ! case 'Z': // backend ready for query, ignore for now :-) ! break; ! default: ! throw new PSQLException("postgresql.con.type",new Character((char)c)); ! } ! } ! if (final_error != null) ! throw final_error; ! ! return getResultSet(this, stat, fields, tuples, recv_status, update_count, insert_oid); ! } ! } ! ! /** ! * Receive the field descriptions from the back end ! * ! * @return an array of the Field object describing the fields ! * @exception SQLException if a database error occurs ! */ ! private Field[] ReceiveFields() throws SQLException ! { ! int nf = pg_stream.ReceiveIntegerR(2), i; ! Field[] fields = new Field[nf]; ! ! for (i = 0 ; i < nf ; ++i) ! { ! String typname = pg_stream.ReceiveString(encoding); ! int typid = pg_stream.ReceiveIntegerR(4); ! int typlen = pg_stream.ReceiveIntegerR(2); ! int typmod = pg_stream.ReceiveIntegerR(4); ! fields[i] = new Field(this, typname, typid, typlen, typmod); ! } ! return fields; } /** --- 348,356 ---- * @return a ResultSet holding the results * @exception SQLException if a database error occurs */ ! public java.sql.ResultSet ExecSQL(String sql, java.sql.Statement stat) throws SQLException { ! return new QueryExecutor(sql, stat, pg_stream, this).execute(); } /** *************** *** 793,799 **** * This returns a resultset. It must be overridden, so that the correct * version (from jdbc1 or jdbc2) are returned. */ ! protected abstract java.sql.ResultSet getResultSet(org.postgresql.Connection conn,java.sql.Statement stat, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException; /** * In some cases, it is desirable to immediately release a Connection's --- 636,642 ---- * This returns a resultset. It must be overridden, so that the correct * version (from jdbc1 or jdbc2) are returned. */ ! public abstract java.sql.ResultSet getResultSet(org.postgresql.Connection conn,java.sql.Statement stat, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException; /** * In some cases, it is desirable to immediately release a Connection's Index: src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java,v retrieving revision 1.8 diff -c -r1.8 Connection.java *** src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java 2001/08/24 16:50:15 1.8 --- src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java 2001/08/26 18:33:48 *************** *** 131,137 **** * This overides the method in org.postgresql.Connection and returns a * ResultSet. */ ! protected java.sql.ResultSet getResultSet(org.postgresql.Connection conn,java.sql.Statement stat, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException { // in jdbc1 stat is ignored. return new org.postgresql.jdbc1.ResultSet((org.postgresql.jdbc1.Connection)conn,fields,tuples,status,updateCount,insertOID); --- 131,137 ---- * This overides the method in org.postgresql.Connection and returns a * ResultSet. */ ! public java.sql.ResultSet getResultSet(org.postgresql.Connection conn,java.sql.Statement stat, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException { // in jdbc1 stat is ignored. return new org.postgresql.jdbc1.ResultSet((org.postgresql.jdbc1.Connection)conn,fields,tuples,status,updateCount,insertOID); Index: src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java,v retrieving revision 1.10 diff -c -r1.10 Connection.java *** src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java 2001/08/24 16:50:16 1.10 --- src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java 2001/08/26 18:33:49 *************** *** 204,210 **** * This overides the method in org.postgresql.Connection and returns a * ResultSet. */ ! protected java.sql.ResultSet getResultSet(org.postgresql.Connection conn, java.sql.Statement stat,Field[] fields, Vector tuples, String status, int updateCount, int insertOID) throws SQLException { // In 7.1 we now test concurrency to see which class to return. If we are not working with a // Statement then default to a normal ResultSet object. --- 204,210 ---- * This overides the method in org.postgresql.Connection and returns a * ResultSet. */ ! public java.sql.ResultSet getResultSet(org.postgresql.Connection conn, java.sql.Statement stat,Field[] fields, Vector tuples, String status, int updateCount, int insertOID) throws SQLException { // In 7.1 we now test concurrency to see which class to return. If we are not working with a // Statement then default to a normal ResultSet object.
package org.postgresql.core; import java.util.Vector; import java.io.IOException; import java.sql.*; import org.postgresql.*; import org.postgresql.util.PSQLException; /** * Executes a query on the backend. * * <p>The lifetime of a QueryExecutor object is from sending the query * until the response has been received from the backend. * * $Id$ */ public class QueryExecutor { private final String sql; private final java.sql.Statement statement; private final PG_Stream pg_stream; private final org.postgresql.Connection connection; public QueryExecutor(String sql, java.sql.Statement statement, PG_Stream pg_stream, org.postgresql.Connection connection) throws SQLException { this.sql = sql; this.statement = statement; this.pg_stream = pg_stream; this.connection = connection; if (statement != null) maxRows = statement.getMaxRows(); else maxRows = 0; } private Field[] fields = null; private Vector tuples = new Vector(); private String status = null; private int update_count = 1; private int insert_oid = 0; private int maxRows; /** * Execute a query on the backend. */ public java.sql.ResultSet execute() throws SQLException { int fqp = 0; boolean hfr = false; synchronized(pg_stream) { sendQuery(sql); while (!hfr || fqp > 0) { int c = pg_stream.ReceiveChar(); switch (c) { case 'A': // Asynchronous Notify int pid = pg_stream.ReceiveInteger(4); String msg = pg_stream.ReceiveString(connection.getEncoding()); break; case 'B': // Binary Data Transfer receiveTuple(true); break; case 'C': // Command Status receiveCommandStatus(); if (fields != null) hfr = true; else { sendQuery(" "); fqp++; } break; case 'D': // Text Data Transfer receiveTuple(false); break; case 'E': // Error Message throw new SQLException(pg_stream.ReceiveString(connection.getEncoding())); case 'I': // Empty Query int t = pg_stream.ReceiveChar(); if (t != 0) throw new PSQLException("postgresql.con.garbled"); if (fqp > 0) fqp--; if (fqp == 0) hfr = true; break; case 'N': // Error Notification connection.addWarning(pg_stream.ReceiveString(connection.getEncoding())); break; case 'P': // Portal Name String pname = pg_stream.ReceiveString(connection.getEncoding()); break; case 'T': // MetaData Field Description receiveFields(); break; case 'Z': // backend ready for query, ignore for now :-) break; default: throw new PSQLException("postgresql.con.type", new Character((char) c)); } } return connection.getResultSet(connection, statement, fields, tuples, status, update_count, insert_oid); } } /** * Send a query to the backend. */ private void sendQuery(String query) throws SQLException { try { pg_stream.SendChar('Q'); pg_stream.Send(connection.getEncoding().encode(query)); pg_stream.SendChar(0); pg_stream.flush(); } catch (IOException e) { throw new PSQLException("postgresql.con.ioerror", e); } } /** * Receive a tuple from the backend. * * @param isBinary set if the tuple should be treated as binary data */ private void receiveTuple(boolean isBinary) throws SQLException { if (fields == null) throw new PSQLException("postgresql.con.tuple"); Object tuple = pg_stream.ReceiveTuple(fields.length, isBinary); if (maxRows == 0 || tuples.size() < maxRows) tuples.addElement(tuple); } /** * Receive command status from the backend. */ private void receiveCommandStatus() throws SQLException { status = pg_stream.ReceiveString(connection.getEncoding()); try { // Now handle the update count correctly. if (status.startsWith("INSERT") || status.startsWith("UPDATE") || status.startsWith("DELETE") || status.startsWith("MOVE")) { update_count = Integer.parseInt(status.substring(1 + status.lastIndexOf(' '))); } if (status.startsWith("INSERT")) { insert_oid = Integer.parseInt(status.substring(1 + status.indexOf(' '), status.lastIndexOf(' '))); } } catch (NumberFormatException nfe) { throw new PSQLException("postgresql.con.fathom", status); } } /** * Receive the field descriptions from the back end. */ private void receiveFields() throws SQLException { if (fields != null) throw new PSQLException("postgresql.con.multres"); int size = pg_stream.ReceiveIntegerR(2); fields = new Field[size]; for (int i = 0; i < fields.length; i++) { String typeName = pg_stream.ReceiveString(connection.getEncoding()); int typeOid = pg_stream.ReceiveIntegerR(4); int typeLength = pg_stream.ReceiveIntegerR(2); int typeModifier = pg_stream.ReceiveIntegerR(4); fields[i] = new Field(connection, typeName, typeOid, typeLength, typeModifier); } } }
---------------------------(end of broadcast)--------------------------- TIP 3: if posting/reading through Usenet, please send an appropriate subscribe-nomail command to [EMAIL PROTECTED] so that your message can get through to the mailing list cleanly