diff options
author | Marc G. Fournier | 1997-08-31 08:15:13 +0000 |
---|---|---|
committer | Marc G. Fournier | 1997-08-31 08:15:13 +0000 |
commit | 6a061da272f04e1463864065f87f1f3fd61d6162 (patch) | |
tree | 729b39b845cb8410c69ac7714ae701302e9f4295 /src/interfaces/jdbc/postgresql/Connection.java | |
parent | 0b6dc93b32c8e57b66893c01ea3ab19abecc8a02 (diff) |
Update patch from Peter <[email protected]>
Diffstat (limited to 'src/interfaces/jdbc/postgresql/Connection.java')
-rw-r--r-- | src/interfaces/jdbc/postgresql/Connection.java | 1613 |
1 files changed, 806 insertions, 807 deletions
diff --git a/src/interfaces/jdbc/postgresql/Connection.java b/src/interfaces/jdbc/postgresql/Connection.java index aa354b61fe1..a208970ae24 100644 --- a/src/interfaces/jdbc/postgresql/Connection.java +++ b/src/interfaces/jdbc/postgresql/Connection.java @@ -28,570 +28,569 @@ import postgresql.*; */ public class Connection implements java.sql.Connection { - private PG_Stream pg_stream; - - private String PG_HOST; - private int PG_PORT; - private String PG_USER; - private String PG_PASSWORD; - private String PG_DATABASE; - private boolean PG_STATUS; - - public boolean CONNECTION_OK = true; - public boolean CONNECTION_BAD = false; - - private int STARTUP_CODE = 7; - - private boolean autoCommit = true; - private boolean readOnly = false; - - private Driver this_driver; - private String this_url; - private String cursor = null; // The positioned update cursor name - - /** - * Connect to a PostgreSQL database back end. - * - * @param host the hostname of the database back end - * @param port the port number of the postmaster process - * @param info a Properties[] thing of the user and password - * @param database the database to connect to - * @param u the URL of the connection - * @param d the Driver instantation of the connection - * @return a valid connection profile - * @exception SQLException if a database access error occurs - */ - public Connection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException - { - int len = 288; // Length of a startup packet - - this_driver = d; - this_url = new String(url); - PG_DATABASE = new String(database); - PG_PASSWORD = new String(info.getProperty("password")); - PG_USER = new String(info.getProperty("user")); - PG_PORT = port; - PG_HOST = new String(host); - PG_STATUS = CONNECTION_BAD; - - try - { - pg_stream = new PG_Stream(host, port); - } catch (IOException e) { - throw new SQLException ("Connection failed: " + e.toString()); - } - - // Now we need to construct and send a startup packet - try - { - pg_stream.SendInteger(len, 4); len -= 4; - pg_stream.SendInteger(STARTUP_CODE, 4); len -= 4; - pg_stream.Send(database.getBytes(), 64); len -= 64; - pg_stream.Send(PG_USER.getBytes(), len); - } catch (IOException e) { - throw new SQLException("Connection failed: " + e.toString()); - } - ExecSQL(" "); // Test connection - PG_STATUS = CONNECTION_OK; - } - - /** - * SQL statements without parameters are normally executed using - * Statement objects. If the same SQL statement is executed many - * times, it is more efficient to use a PreparedStatement - * - * @return a new Statement object - * @exception SQLException passed through from the constructor - */ - public java.sql.Statement createStatement() throws SQLException - { - return new Statement(this); - } - - /** - * A SQL statement with or without IN parameters can be pre-compiled - * and stored in a PreparedStatement object. This object can then - * be used to efficiently execute this statement multiple times. - * - * <B>Note:</B> This method is optimized for handling parametric - * SQL statements that benefit from precompilation if the drivers - * supports precompilation. PostgreSQL does not support precompilation. - * In this case, the statement is not sent to the database until the - * PreparedStatement is executed. This has no direct effect on users; - * however it does affect which method throws certain SQLExceptions - * - * @param sql a SQL statement that may contain one or more '?' IN - * parameter placeholders - * @return a new PreparedStatement object containing the pre-compiled - * statement. - * @exception SQLException if a database access error occurs. - */ - public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException - { - return new PreparedStatement(this, sql); - } - - /** - * A SQL stored procedure call statement is handled by creating a - * CallableStatement for it. The CallableStatement provides methods - * for setting up its IN and OUT parameters and methods for executing - * it. - * - * <B>Note:</B> This method is optimised for handling stored procedure - * call statements. Some drivers may send the call statement to the - * database when the prepareCall is done; others may wait until the - * CallableStatement is executed. This has no direct effect on users; - * however, it does affect which method throws certain SQLExceptions - * - * @param sql a SQL statement that may contain one or more '?' parameter - * placeholders. Typically this statement is a JDBC function call - * escape string. - * @return a new CallableStatement object containing the pre-compiled - * SQL statement - * @exception SQLException if a database access error occurs - */ - public java.sql.CallableStatement prepareCall(String sql) throws SQLException - { - throw new SQLException("Callable Statements are not supported at this time"); -// return new CallableStatement(this, sql); - } - - /** - * A driver may convert the JDBC sql grammar into its system's - * native SQL grammar prior to sending it; nativeSQL returns the - * native form of the statement that the driver would have sent. - * - * @param sql a SQL statement that may contain one or more '?' - * parameter placeholders - * @return the native form of this statement - * @exception SQLException if a database access error occurs - */ - public String nativeSQL(String sql) throws SQLException - { - return sql; - } - - /** - * If a connection is in auto-commit mode, than all its SQL - * statements will be executed and committed as individual - * transactions. Otherwise, its SQL statements are grouped - * into transactions that are terminated by either commit() - * or rollback(). By default, new connections are in auto- - * commit mode. The commit occurs when the statement completes - * or the next execute occurs, whichever comes first. In the - * case of statements returning a ResultSet, the statement - * completes when the last row of the ResultSet has been retrieved - * or the ResultSet has been closed. In advanced cases, a single - * statement may return multiple results as well as output parameter - * values. Here the commit occurs when all results and output param - * values have been retrieved. - * - * @param autoCommit - true enables auto-commit; false disables it - * @exception SQLException if a database access error occurs - */ - public void setAutoCommit(boolean autoCommit) throws SQLException - { - if (this.autoCommit == autoCommit) - return; - if (autoCommit) - ExecSQL("end"); - else - ExecSQL("begin"); - this.autoCommit = autoCommit; - } - - /** - * gets the current auto-commit state - * - * @return Current state of the auto-commit mode - * @exception SQLException (why?) - * @see setAutoCommit - */ - public boolean getAutoCommit() throws SQLException - { - return this.autoCommit; - } - - /** - * The method commit() makes all changes made since the previous - * commit/rollback permanent and releases any database locks currently - * held by the Connection. This method should only be used when - * auto-commit has been disabled. (If autoCommit == true, then we - * just return anyhow) - * - * @exception SQLException if a database access error occurs - * @see setAutoCommit - */ - public void commit() throws SQLException - { - if (autoCommit) - return; - ExecSQL("commit"); - autoCommit = true; - ExecSQL("begin"); - autoCommit = false; - } - - /** - * The method rollback() drops all changes made since the previous - * commit/rollback and releases any database locks currently held by - * the Connection. - * - * @exception SQLException if a database access error occurs - * @see commit - */ - public void rollback() throws SQLException - { - if (autoCommit) - return; - ExecSQL("rollback"); - autoCommit = true; - ExecSQL("begin"); - autoCommit = false; - } - - /** - * In some cases, it is desirable to immediately release a Connection's - * database and JDBC resources instead of waiting for them to be - * automatically released (cant think why off the top of my head) - * - * <B>Note:</B> A Connection is automatically closed when it is - * garbage collected. Certain fatal errors also result in a closed - * connection. - * - * @exception SQLException if a database access error occurs - */ - public void close() throws SQLException - { - if (pg_stream != null) - { - try - { - pg_stream.close(); - } catch (IOException e) {} - pg_stream = null; - } - } - - /** - * Tests to see if a Connection is closed - * - * @return the status of the connection - * @exception SQLException (why?) - */ - public boolean isClosed() throws SQLException - { - return (pg_stream == null); - } - - /** - * A connection's database is able to provide information describing - * its tables, its supported SQL grammar, its stored procedures, the - * capabilities of this connection, etc. This information is made - * available through a DatabaseMetaData object. - * - * @return a DatabaseMetaData object for this connection - * @exception SQLException if a database access error occurs - */ - public java.sql.DatabaseMetaData getMetaData() throws SQLException - { -// return new DatabaseMetaData(this); - throw new SQLException("DatabaseMetaData not supported"); - } - - /** - * You can put a connection in read-only mode as a hunt to enable - * database optimizations - * - * <B>Note:</B> setReadOnly cannot be called while in the middle - * of a transaction - * - * @param readOnly - true enables read-only mode; false disables it - * @exception SQLException if a database access error occurs - */ - public void setReadOnly (boolean readOnly) throws SQLException - { - this.readOnly = readOnly; - } - - /** - * Tests to see if the connection is in Read Only Mode. Note that - * we cannot really put the database in read only mode, but we pretend - * we can by returning the value of the readOnly flag - * - * @return true if the connection is read only - * @exception SQLException if a database access error occurs - */ - public boolean isReadOnly() throws SQLException - { - return readOnly; - } - - /** - * A sub-space of this Connection's database may be selected by - * setting a catalog name. If the driver does not support catalogs, - * it will silently ignore this request - * - * @exception SQLException if a database access error occurs - */ - public void setCatalog(String catalog) throws SQLException - { - // No-op - } - - /** - * Return the connections current catalog name, or null if no - * catalog name is set, or we dont support catalogs. - * - * @return the current catalog name or null - * @exception SQLException if a database access error occurs - */ - public String getCatalog() throws SQLException - { - return null; - } - - /** - * You can call this method to try to change the transaction - * isolation level using one of the TRANSACTION_* values. - * - * <B>Note:</B> setTransactionIsolation cannot be called while - * in the middle of a transaction - * - * @param level one of the TRANSACTION_* isolation values with - * the exception of TRANSACTION_NONE; some databases may - * not support other values - * @exception SQLException if a database access error occurs - * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel - */ - public void setTransactionIsolation(int level) throws SQLException - { - throw new SQLException("Transaction Isolation Levels are not implemented"); - } - - /** - * Get this Connection's current transaction isolation mode. - * - * @return the current TRANSACTION_* mode value - * @exception SQLException if a database access error occurs - */ - public int getTransactionIsolation() throws SQLException - { - return java.sql.Connection.TRANSACTION_SERIALIZABLE; - } - - /** - * The first warning reported by calls on this Connection is - * returned. - * - * <B>Note:</B> Sebsequent warnings will be changed to this - * SQLWarning - * - * @return the first SQLWarning or null - * @exception SQLException if a database access error occurs - */ - public SQLWarning getWarnings() throws SQLException - { - return null; // We handle warnings as errors - } - - /** - * After this call, getWarnings returns null until a new warning - * is reported for this connection. - * - * @exception SQLException if a database access error occurs - */ - public void clearWarnings() throws SQLException - { - // Not handles since we handle wanrings as errors - } - - // ********************************************************** - // END OF PUBLIC INTERFACE - // ********************************************************** - - /** - * Send a query to the backend. Returns one of the ResultSet - * objects. - * - * <B>Note:</B> there does not seem to be any method currently - * in existance to return the update count. - * - * @param sql the SQL statement to be executed - * @return a ResultSet holding the results - * @exception SQLException if a database error occurs - */ - public synchronized ResultSet ExecSQL(String sql) throws SQLException - { - Field[] fields = null; - Vector tuples = new Vector(); - byte[] buf = new byte[sql.length()]; - int fqp = 0; - boolean hfr = false; - String recv_status = null, msg; - SQLException final_error = null; - - if (sql.length() > 8192) - throw new SQLException("SQL Statement too long: " + sql); - try - { - pg_stream.SendChar('Q'); - buf = sql.getBytes(); - pg_stream.Send(buf); - pg_stream.SendChar(0); - } catch (IOException e) { - throw new SQLException("I/O Error: " + e.toString()); - } - - while (!hfr || fqp > 0) - { - int c = pg_stream.ReceiveChar(); - - switch (c) - { - case 'A': // Asynchronous Notify - int pid = pg_stream.ReceiveInteger(4); - msg = pg_stream.ReceiveString(8192); - break; - case 'B': // Binary Data Transfer - if (fields == null) - throw new SQLException("Tuple received before MetaData"); - tuples.addElement(pg_stream.ReceiveTuple(fields.length, true)); - break; - case 'C': // Command Status - recv_status = pg_stream.ReceiveString(8192); - if (fields != null) - hfr = true; - else - { - try - { - pg_stream.SendChar('Q'); - pg_stream.SendChar(' '); - pg_stream.SendChar(0); - } catch (IOException e) { - throw new SQLException("I/O Error: " + e.toString()); - } - fqp++; - } - break; - case 'D': // Text Data Transfer - if (fields == null) - throw new SQLException("Tuple received before MetaData"); - tuples.addElement(pg_stream.ReceiveTuple(fields.length, false)); - break; - case 'E': // Error Message - msg = pg_stream.ReceiveString(4096); - final_error = new SQLException(msg); - hfr = true; - break; - case 'I': // Empty Query - int t = pg_stream.ReceiveChar(); - - if (t != 0) - throw new SQLException("Garbled Data"); - if (fqp > 0) - fqp--; - if (fqp == 0) - hfr = true; - break; - case 'N': // Error Notification - msg = pg_stream.ReceiveString(4096); - PrintStream log = DriverManager.getLogStream(); - log.println(msg); - break; - case 'P': // Portal Name - String pname = pg_stream.ReceiveString(8192); - break; - case 'T': // MetaData Field Description - if (fields != null) - throw new SQLException("Cannot handle multiple result groups"); - fields = ReceiveFields(); - break; - default: - throw new SQLException("Unknown Response Type: " + (char)c); - } - } - if (final_error != null) - throw final_error; - return new ResultSet(this, fields, tuples, recv_status, 1); - } - - /** - * 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.ReceiveInteger(2), i; - Field[] fields = new Field[nf]; - - for (i = 0 ; i < nf ; ++i) + private PG_Stream pg_stream; + + private String PG_HOST; + private int PG_PORT; + private String PG_USER; + private String PG_PASSWORD; + private String PG_DATABASE; + private boolean PG_STATUS; + + public boolean CONNECTION_OK = true; + public boolean CONNECTION_BAD = false; + + private int STARTUP_CODE = 7; + + private boolean autoCommit = true; + private boolean readOnly = false; + + protected Driver this_driver; + private String this_url; + private String cursor = null; // The positioned update cursor name + + /** + * Connect to a PostgreSQL database back end. + * + * @param host the hostname of the database back end + * @param port the port number of the postmaster process + * @param info a Properties[] thing of the user and password + * @param database the database to connect to + * @param u the URL of the connection + * @param d the Driver instantation of the connection + * @return a valid connection profile + * @exception SQLException if a database access error occurs + */ + public Connection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException + { + int len = 288; // Length of a startup packet + + this_driver = d; + this_url = new String(url); + PG_DATABASE = new String(database); + PG_PASSWORD = new String(info.getProperty("password")); + PG_USER = new String(info.getProperty("user")); + PG_PORT = port; + PG_HOST = new String(host); + PG_STATUS = CONNECTION_BAD; + + try + { + pg_stream = new PG_Stream(host, port); + } catch (IOException e) { + throw new SQLException ("Connection failed: " + e.toString()); + } + + // Now we need to construct and send a startup packet + try + { + pg_stream.SendInteger(len, 4); len -= 4; + pg_stream.SendInteger(STARTUP_CODE, 4); len -= 4; + pg_stream.Send(database.getBytes(), 64); len -= 64; + pg_stream.Send(PG_USER.getBytes(), len); + } catch (IOException e) { + throw new SQLException("Connection failed: " + e.toString()); + } + ExecSQL(" "); // Test connection + PG_STATUS = CONNECTION_OK; + } + + /** + * SQL statements without parameters are normally executed using + * Statement objects. If the same SQL statement is executed many + * times, it is more efficient to use a PreparedStatement + * + * @return a new Statement object + * @exception SQLException passed through from the constructor + */ + public java.sql.Statement createStatement() throws SQLException + { + return new Statement(this); + } + + /** + * A SQL statement with or without IN parameters can be pre-compiled + * and stored in a PreparedStatement object. This object can then + * be used to efficiently execute this statement multiple times. + * + * <B>Note:</B> This method is optimized for handling parametric + * SQL statements that benefit from precompilation if the drivers + * supports precompilation. PostgreSQL does not support precompilation. + * In this case, the statement is not sent to the database until the + * PreparedStatement is executed. This has no direct effect on users; + * however it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' IN + * parameter placeholders + * @return a new PreparedStatement object containing the pre-compiled + * statement. + * @exception SQLException if a database access error occurs. + */ + public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException + { + return new PreparedStatement(this, sql); + } + + /** + * A SQL stored procedure call statement is handled by creating a + * CallableStatement for it. The CallableStatement provides methods + * for setting up its IN and OUT parameters and methods for executing + * it. + * + * <B>Note:</B> This method is optimised for handling stored procedure + * call statements. Some drivers may send the call statement to the + * database when the prepareCall is done; others may wait until the + * CallableStatement is executed. This has no direct effect on users; + * however, it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' parameter + * placeholders. Typically this statement is a JDBC function call + * escape string. + * @return a new CallableStatement object containing the pre-compiled + * SQL statement + * @exception SQLException if a database access error occurs + */ + public java.sql.CallableStatement prepareCall(String sql) throws SQLException + { + throw new SQLException("Callable Statements are not supported at this time"); + // return new CallableStatement(this, sql); + } + + /** + * A driver may convert the JDBC sql grammar into its system's + * native SQL grammar prior to sending it; nativeSQL returns the + * native form of the statement that the driver would have sent. + * + * @param sql a SQL statement that may contain one or more '?' + * parameter placeholders + * @return the native form of this statement + * @exception SQLException if a database access error occurs + */ + public String nativeSQL(String sql) throws SQLException + { + return sql; + } + + /** + * If a connection is in auto-commit mode, than all its SQL + * statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped + * into transactions that are terminated by either commit() + * or rollback(). By default, new connections are in auto- + * commit mode. The commit occurs when the statement completes + * or the next execute occurs, whichever comes first. In the + * case of statements returning a ResultSet, the statement + * completes when the last row of the ResultSet has been retrieved + * or the ResultSet has been closed. In advanced cases, a single + * statement may return multiple results as well as output parameter + * values. Here the commit occurs when all results and output param + * values have been retrieved. + * + * @param autoCommit - true enables auto-commit; false disables it + * @exception SQLException if a database access error occurs + */ + public void setAutoCommit(boolean autoCommit) throws SQLException + { + if (this.autoCommit == autoCommit) + return; + if (autoCommit) + ExecSQL("end"); + else + ExecSQL("begin"); + this.autoCommit = autoCommit; + } + + /** + * gets the current auto-commit state + * + * @return Current state of the auto-commit mode + * @exception SQLException (why?) + * @see setAutoCommit + */ + public boolean getAutoCommit() throws SQLException + { + return this.autoCommit; + } + + /** + * The method commit() makes all changes made since the previous + * commit/rollback permanent and releases any database locks currently + * held by the Connection. This method should only be used when + * auto-commit has been disabled. (If autoCommit == true, then we + * just return anyhow) + * + * @exception SQLException if a database access error occurs + * @see setAutoCommit + */ + public void commit() throws SQLException + { + if (autoCommit) + return; + ExecSQL("commit"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * The method rollback() drops all changes made since the previous + * commit/rollback and releases any database locks currently held by + * the Connection. + * + * @exception SQLException if a database access error occurs + * @see commit + */ + public void rollback() throws SQLException + { + if (autoCommit) + return; + ExecSQL("rollback"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * In some cases, it is desirable to immediately release a Connection's + * database and JDBC resources instead of waiting for them to be + * automatically released (cant think why off the top of my head) + * + * <B>Note:</B> A Connection is automatically closed when it is + * garbage collected. Certain fatal errors also result in a closed + * connection. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + if (pg_stream != null) + { + try + { + pg_stream.close(); + } catch (IOException e) {} + pg_stream = null; + } + } + + /** + * Tests to see if a Connection is closed + * + * @return the status of the connection + * @exception SQLException (why?) + */ + public boolean isClosed() throws SQLException + { + return (pg_stream == null); + } + + /** + * A connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is made + * available through a DatabaseMetaData object. + * + * @return a DatabaseMetaData object for this connection + * @exception SQLException if a database access error occurs + */ + public java.sql.DatabaseMetaData getMetaData() throws SQLException + { + return new DatabaseMetaData(this); + } + + /** + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * <B>Note:</B> setReadOnly cannot be called while in the middle + * of a transaction + * + * @param readOnly - true enables read-only mode; false disables it + * @exception SQLException if a database access error occurs + */ + public void setReadOnly (boolean readOnly) throws SQLException + { + this.readOnly = readOnly; + } + + /** + * Tests to see if the connection is in Read Only Mode. Note that + * we cannot really put the database in read only mode, but we pretend + * we can by returning the value of the readOnly flag + * + * @return true if the connection is read only + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return readOnly; + } + + /** + * A sub-space of this Connection's database may be selected by + * setting a catalog name. If the driver does not support catalogs, + * it will silently ignore this request + * + * @exception SQLException if a database access error occurs + */ + public void setCatalog(String catalog) throws SQLException + { + // No-op + } + + /** + * Return the connections current catalog name, or null if no + * catalog name is set, or we dont support catalogs. + * + * @return the current catalog name or null + * @exception SQLException if a database access error occurs + */ + public String getCatalog() throws SQLException + { + return null; + } + + /** + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * <B>Note:</B> setTransactionIsolation cannot be called while + * in the middle of a transaction + * + * @param level one of the TRANSACTION_* isolation values with + * the exception of TRANSACTION_NONE; some databases may + * not support other values + * @exception SQLException if a database access error occurs + * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel + */ + public void setTransactionIsolation(int level) throws SQLException + { + throw new SQLException("Transaction Isolation Levels are not implemented"); + } + + /** + * Get this Connection's current transaction isolation mode. + * + * @return the current TRANSACTION_* mode value + * @exception SQLException if a database access error occurs + */ + public int getTransactionIsolation() throws SQLException + { + return java.sql.Connection.TRANSACTION_SERIALIZABLE; + } + + /** + * The first warning reported by calls on this Connection is + * returned. + * + * <B>Note:</B> Sebsequent warnings will be changed to this + * SQLWarning + * + * @return the first SQLWarning or null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return null; // We handle warnings as errors + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this connection. + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + // Not handles since we handle wanrings as errors + } + + // ********************************************************** + // END OF PUBLIC INTERFACE + // ********************************************************** + + /** + * Send a query to the backend. Returns one of the ResultSet + * objects. + * + * <B>Note:</B> there does not seem to be any method currently + * in existance to return the update count. + * + * @param sql the SQL statement to be executed + * @return a ResultSet holding the results + * @exception SQLException if a database error occurs + */ + public synchronized ResultSet ExecSQL(String sql) throws SQLException + { + Field[] fields = null; + Vector tuples = new Vector(); + byte[] buf = new byte[sql.length()]; + int fqp = 0; + boolean hfr = false; + String recv_status = null, msg; + SQLException final_error = null; + + if (sql.length() > 8192) + throw new SQLException("SQL Statement too long: " + sql); + try + { + pg_stream.SendChar('Q'); + buf = sql.getBytes(); + pg_stream.Send(buf); + pg_stream.SendChar(0); + } catch (IOException e) { + throw new SQLException("I/O Error: " + e.toString()); + } + + while (!hfr || fqp > 0) + { + int c = pg_stream.ReceiveChar(); + + switch (c) + { + case 'A': // Asynchronous Notify + int pid = pg_stream.ReceiveInteger(4); + msg = pg_stream.ReceiveString(8192); + break; + case 'B': // Binary Data Transfer + if (fields == null) + throw new SQLException("Tuple received before MetaData"); + tuples.addElement(pg_stream.ReceiveTuple(fields.length, true)); + break; + case 'C': // Command Status + recv_status = pg_stream.ReceiveString(8192); + if (fields != null) + hfr = true; + else { - String typname = pg_stream.ReceiveString(8192); - int typid = pg_stream.ReceiveInteger(4); - int typlen = pg_stream.ReceiveInteger(2); - fields[i] = new Field(this, typname, typid, typlen); + try + { + pg_stream.SendChar('Q'); + pg_stream.SendChar(' '); + pg_stream.SendChar(0); + } catch (IOException e) { + throw new SQLException("I/O Error: " + e.toString()); + } + fqp++; } - return fields; - } - - /** - * In SQL, a result table can be retrieved through 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. - * - * We support one cursor per connection. - * - * setCursorName sets the cursor name. - * - * @param cursor the cursor name - * @exception SQLException if a database access error occurs - */ - public void setCursorName(String cursor) throws SQLException - { - this.cursor = cursor; - } - - /** - * getCursorName gets the cursor name. - * - * @return the current cursor name - * @exception SQLException if a database access error occurs - */ - public String getCursorName() throws SQLException - { - return cursor; - } - - /** - * We are required to bring back certain information by - * the DatabaseMetaData class. These functions do that. - * - * Method getURL() brings back the URL (good job we saved it) - * - * @return the url - * @exception SQLException just in case... - */ - public String getURL() throws SQLException - { - return this_url; - } - - /** - * Method getUserName() brings back the User Name (again, we - * saved it) - * - * @return the user name - * @exception SQLException just in case... - */ - public String getUserName() throws SQLException - { - return PG_USER; - } + break; + case 'D': // Text Data Transfer + if (fields == null) + throw new SQLException("Tuple received before MetaData"); + tuples.addElement(pg_stream.ReceiveTuple(fields.length, false)); + break; + case 'E': // Error Message + msg = pg_stream.ReceiveString(4096); + final_error = new SQLException(msg); + hfr = true; + break; + case 'I': // Empty Query + int t = pg_stream.ReceiveChar(); + + if (t != 0) + throw new SQLException("Garbled Data"); + if (fqp > 0) + fqp--; + if (fqp == 0) + hfr = true; + break; + case 'N': // Error Notification + msg = pg_stream.ReceiveString(4096); + PrintStream log = DriverManager.getLogStream(); + log.println(msg); + break; + case 'P': // Portal Name + String pname = pg_stream.ReceiveString(8192); + break; + case 'T': // MetaData Field Description + if (fields != null) + throw new SQLException("Cannot handle multiple result groups"); + fields = ReceiveFields(); + break; + default: + throw new SQLException("Unknown Response Type: " + (char)c); + } + } + if (final_error != null) + throw final_error; + return new ResultSet(this, fields, tuples, recv_status, 1); + } + + /** + * 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.ReceiveInteger(2), i; + Field[] fields = new Field[nf]; + + for (i = 0 ; i < nf ; ++i) + { + String typname = pg_stream.ReceiveString(8192); + int typid = pg_stream.ReceiveInteger(4); + int typlen = pg_stream.ReceiveInteger(2); + fields[i] = new Field(this, typname, typid, typlen); + } + return fields; + } + + /** + * In SQL, a result table can be retrieved through 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. + * + * We support one cursor per connection. + * + * setCursorName sets the cursor name. + * + * @param cursor the cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String cursor) throws SQLException + { + this.cursor = cursor; + } + + /** + * getCursorName gets the cursor name. + * + * @return the current cursor name + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return cursor; + } + + /** + * We are required to bring back certain information by + * the DatabaseMetaData class. These functions do that. + * + * Method getURL() brings back the URL (good job we saved it) + * + * @return the url + * @exception SQLException just in case... + */ + public String getURL() throws SQLException + { + return this_url; + } + + /** + * Method getUserName() brings back the User Name (again, we + * saved it) + * + * @return the user name + * @exception SQLException just in case... + */ + public String getUserName() throws SQLException + { + return PG_USER; + } } // *********************************************************************** @@ -599,249 +598,249 @@ public class Connection implements java.sql.Connection // This class handles all the Streamed I/O for a postgresql connection class PG_Stream { - private Socket connection; - private InputStream pg_input; - private OutputStream pg_output; - - /** - * Constructor: Connect to the PostgreSQL back end and return - * a stream connection. - * - * @param host the hostname to connect to - * @param port the port number that the postmaster is sitting on - * @exception IOException if an IOException occurs below it. - */ - public PG_Stream(String host, int port) throws IOException - { - connection = new Socket(host, port); - pg_input = connection.getInputStream(); - pg_output = connection.getOutputStream(); - } - - /** - * Sends a single character to the back end - * - * @param val the character to be sent - * @exception IOException if an I/O error occurs - */ - public void SendChar(int val) throws IOException - { - pg_output.write(val); - } - - /** - * Sends an integer to the back end - * - * @param val the integer to be sent - * @param siz the length of the integer in bytes (size of structure) - * @exception IOException if an I/O error occurs - */ - public void SendInteger(int val, int siz) throws IOException - { - byte[] buf = new byte[siz]; - - while (siz-- > 0) - { - buf[siz] = (byte)(val & 0xff); - val >>= 8; - } - Send(buf); - } - - /** - * Send an array of bytes to the backend - * - * @param buf The array of bytes to be sent - * @exception IOException if an I/O error occurs - */ - public void Send(byte buf[]) throws IOException - { - pg_output.write(buf); - } - - /** - * Send an exact array of bytes to the backend - if the length - * has not been reached, send nulls until it has. - * - * @param buf the array of bytes to be sent - * @param siz the number of bytes to be sent - * @exception IOException if an I/O error occurs - */ - public void Send(byte buf[], int siz) throws IOException - { - int i; - - pg_output.write(buf, 0, (buf.length < siz ? buf.length : siz)); - if (buf.length < siz) - { - for (i = buf.length ; i < siz ; ++i) - { - pg_output.write(0); - } - } - } - - /** - * Receives a single character from the backend - * - * @return the character received - * @exception SQLException if an I/O Error returns - */ - public int ReceiveChar() throws SQLException - { - int c = 0; - - try - { - c = pg_input.read(); - if (c < 0) throw new IOException("EOF"); - } catch (IOException e) { - throw new SQLException("Error reading from backend: " + e.toString()); - } - return c; - } - - /** - * Receives an integer from the backend - * - * @param siz length of the integer in bytes - * @return the integer received from the backend - * @exception SQLException if an I/O error occurs - */ - public int ReceiveInteger(int siz) throws SQLException - { - int n = 0; - - try - { - for (int i = 0 ; i < siz ; i++) - { - int b = pg_input.read(); - - if (b < 0) - throw new IOException("EOF"); - n = n | (b >> (8 * i)) ; - } - } catch (IOException e) { - throw new SQLException("Error reading from backend: " + e.toString()); - } - return n; - } - - /** - * Receives a null-terminated string from the backend. Maximum of - * maxsiz bytes - if we don't see a null, then we assume something - * has gone wrong. - * - * @param maxsiz maximum length of string - * @return string from back end - * @exception SQLException if an I/O error occurs - */ - public String ReceiveString(int maxsiz) throws SQLException - { - byte[] rst = new byte[maxsiz]; - int s = 0; - - try - { - while (s < maxsiz) - { - int c = pg_input.read(); - if (c < 0) - throw new IOException("EOF"); - else if (c == 0) - break; - else - rst[s++] = (byte)c; - } - if (s >= maxsiz) - throw new IOException("Too Much Data"); - } catch (IOException e) { - throw new SQLException("Error reading from backend: " + e.toString()); - } - String v = new String(rst, 0, s); - return v; - } - - /** - * Read a tuple from the back end. A tuple is a two dimensional - * array of bytes - * - * @param nf the number of fields expected - * @param bin true if the tuple is a binary tuple - * @return null if the current response has no more tuples, otherwise - * an array of strings - * @exception SQLException if a data I/O error occurs - */ - public byte[][] ReceiveTuple(int nf, boolean bin) throws SQLException - { - int i, bim = (nf + 7)/8; - byte[] bitmask = Receive(bim); - byte[][] answer = new byte[nf][0]; - - int whichbit = 0x80; - int whichbyte = 0; - - for (i = 0 ; i < nf ; ++i) - { - boolean isNull = ((bitmask[whichbyte] & whichbit) == 0); - whichbit >>= 1; - if (whichbit == 0) - { - ++whichbyte; - whichbit = 0x80; - } - if (isNull) - answer[i] = null; - else - { - int len = ReceiveInteger(4); - if (!bin) - len -= 4; - if (len < 0) - len = 0; - answer[i] = Receive(len); - } - } - return answer; - } - - /** - * Reads in a given number of bytes from the backend - * - * @param siz number of bytes to read - * @return array of bytes received - * @exception SQLException if a data I/O error occurs - */ - private byte[] Receive(int siz) throws SQLException - { - byte[] answer = new byte[siz]; - int s = 0; - - try - { - while (s < siz) - { - int w = pg_input.read(answer, s, siz - s); - if (w < 0) - throw new IOException("EOF"); - s += w; - } - } catch (IOException e) { - throw new SQLException("Error reading from backend: " + e.toString()); - } - return answer; - } - - /** - * Closes the connection - * - * @exception IOException if a IO Error occurs - */ - public void close() throws IOException - { - pg_output.close(); - pg_input.close(); - connection.close(); - } + private Socket connection; + private InputStream pg_input; + private OutputStream pg_output; + + /** + * Constructor: Connect to the PostgreSQL back end and return + * a stream connection. + * + * @param host the hostname to connect to + * @param port the port number that the postmaster is sitting on + * @exception IOException if an IOException occurs below it. + */ + public PG_Stream(String host, int port) throws IOException + { + connection = new Socket(host, port); + pg_input = connection.getInputStream(); + pg_output = connection.getOutputStream(); + } + + /** + * Sends a single character to the back end + * + * @param val the character to be sent + * @exception IOException if an I/O error occurs + */ + public void SendChar(int val) throws IOException + { + pg_output.write(val); + } + + /** + * Sends an integer to the back end + * + * @param val the integer to be sent + * @param siz the length of the integer in bytes (size of structure) + * @exception IOException if an I/O error occurs + */ + public void SendInteger(int val, int siz) throws IOException + { + byte[] buf = new byte[siz]; + + while (siz-- > 0) + { + buf[siz] = (byte)(val & 0xff); + val >>= 8; + } + Send(buf); + } + + /** + * Send an array of bytes to the backend + * + * @param buf The array of bytes to be sent + * @exception IOException if an I/O error occurs + */ + public void Send(byte buf[]) throws IOException + { + pg_output.write(buf); + } + + /** + * Send an exact array of bytes to the backend - if the length + * has not been reached, send nulls until it has. + * + * @param buf the array of bytes to be sent + * @param siz the number of bytes to be sent + * @exception IOException if an I/O error occurs + */ + public void Send(byte buf[], int siz) throws IOException + { + int i; + + pg_output.write(buf, 0, (buf.length < siz ? buf.length : siz)); + if (buf.length < siz) + { + for (i = buf.length ; i < siz ; ++i) + { + pg_output.write(0); + } + } + } + + /** + * Receives a single character from the backend + * + * @return the character received + * @exception SQLException if an I/O Error returns + */ + public int ReceiveChar() throws SQLException + { + int c = 0; + + try + { + c = pg_input.read(); + if (c < 0) throw new IOException("EOF"); + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return c; + } + + /** + * Receives an integer from the backend + * + * @param siz length of the integer in bytes + * @return the integer received from the backend + * @exception SQLException if an I/O error occurs + */ + public int ReceiveInteger(int siz) throws SQLException + { + int n = 0; + + try + { + for (int i = 0 ; i < siz ; i++) + { + int b = pg_input.read(); + + if (b < 0) + throw new IOException("EOF"); + n = n | (b >> (8 * i)) ; + } + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return n; + } + + /** + * Receives a null-terminated string from the backend. Maximum of + * maxsiz bytes - if we don't see a null, then we assume something + * has gone wrong. + * + * @param maxsiz maximum length of string + * @return string from back end + * @exception SQLException if an I/O error occurs + */ + public String ReceiveString(int maxsiz) throws SQLException + { + byte[] rst = new byte[maxsiz]; + int s = 0; + + try + { + while (s < maxsiz) + { + int c = pg_input.read(); + if (c < 0) + throw new IOException("EOF"); + else if (c == 0) + break; + else + rst[s++] = (byte)c; + } + if (s >= maxsiz) + throw new IOException("Too Much Data"); + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + String v = new String(rst, 0, s); + return v; + } + + /** + * Read a tuple from the back end. A tuple is a two dimensional + * array of bytes + * + * @param nf the number of fields expected + * @param bin true if the tuple is a binary tuple + * @return null if the current response has no more tuples, otherwise + * an array of strings + * @exception SQLException if a data I/O error occurs + */ + public byte[][] ReceiveTuple(int nf, boolean bin) throws SQLException + { + int i, bim = (nf + 7)/8; + byte[] bitmask = Receive(bim); + byte[][] answer = new byte[nf][0]; + + int whichbit = 0x80; + int whichbyte = 0; + + for (i = 0 ; i < nf ; ++i) + { + boolean isNull = ((bitmask[whichbyte] & whichbit) == 0); + whichbit >>= 1; + if (whichbit == 0) + { + ++whichbyte; + whichbit = 0x80; + } + if (isNull) + answer[i] = null; + else + { + int len = ReceiveInteger(4); + if (!bin) + len -= 4; + if (len < 0) + len = 0; + answer[i] = Receive(len); + } + } + return answer; + } + + /** + * Reads in a given number of bytes from the backend + * + * @param siz number of bytes to read + * @return array of bytes received + * @exception SQLException if a data I/O error occurs + */ + private byte[] Receive(int siz) throws SQLException + { + byte[] answer = new byte[siz]; + int s = 0; + + try + { + while (s < siz) + { + int w = pg_input.read(answer, s, siz - s); + if (w < 0) + throw new IOException("EOF"); + s += w; + } + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return answer; + } + + /** + * Closes the connection + * + * @exception IOException if a IO Error occurs + */ + public void close() throws IOException + { + pg_output.close(); + pg_input.close(); + connection.close(); + } } |