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