From: Dave Cramer Date: Thu, 21 Mar 2002 02:39:06 +0000 (+0000) Subject: Applied Anders patch to move the startup code out of Connection into StartupPacket X-Git-Tag: REL7_3~1859 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d96c29ab301ac22488cf8443e1da819826c47ac1;p=postgresql Applied Anders patch to move the startup code out of Connection into StartupPacket * Introduces a new class, StartupPacket. * Moves a lot of constants from Connection to StartupPacket. * Makes two instance variables in Connection into locals. --- diff --git a/src/interfaces/jdbc/org/postgresql/Connection.java b/src/interfaces/jdbc/org/postgresql/Connection.java index ec3c49d437..3ee6deea0a 100644 --- a/src/interfaces/jdbc/org/postgresql/Connection.java +++ b/src/interfaces/jdbc/org/postgresql/Connection.java @@ -1,1217 +1,1215 @@ -package org.postgresql; - -import java.io.*; -import java.net.*; -import java.sql.*; -import java.util.*; -import org.postgresql.Field; -import org.postgresql.fastpath.*; -import org.postgresql.largeobject.*; -import org.postgresql.util.*; -import org.postgresql.core.*; - -/* - * $Id: Connection.java,v 1.43 2002/03/09 17:08:39 davec Exp $ - * - * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or - * JDBC2 versions of the Connection class. - * - */ -public abstract class Connection -{ - // This is the network stream associated with this connection - public 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; - private String compatible; - - /* - * The encoding to use for this connection. - */ - private Encoding encoding = Encoding.defaultEncoding(); - - private String dbVersionNumber; - - public boolean CONNECTION_OK = true; - public boolean CONNECTION_BAD = false; - - public boolean autoCommit = true; - public boolean readOnly = false; - - public Driver this_driver; - private String this_url; - private String cursor = null; // The positioned update cursor name - - // These are new for v6.3, they determine the current protocol versions - // supported by this version of the driver. They are defined in - // src/include/libpq/pqcomm.h - protected static final int PG_PROTOCOL_LATEST_MAJOR = 2; - protected static final int PG_PROTOCOL_LATEST_MINOR = 0; - private static final int SM_DATABASE = 64; - private static final int SM_USER = 32; - private static final int SM_OPTIONS = 64; - private static final int SM_UNUSED = 64; - private static final int SM_TTY = 64; - - private static final int AUTH_REQ_OK = 0; - private static final int AUTH_REQ_KRB4 = 1; - private static final int AUTH_REQ_KRB5 = 2; - private static final int AUTH_REQ_PASSWORD = 3; - private static final int AUTH_REQ_CRYPT = 4; - private static final int AUTH_REQ_MD5 = 5; - - // New for 6.3, salt value for crypt authorisation - private String salt; - - // These are used to cache oids, PGTypes and SQLTypes - private static Hashtable sqlTypeCache = new Hashtable(); // oid -> SQLType - private static Hashtable pgTypeCache = new Hashtable(); // oid -> PGType - private static Hashtable typeOidCache = new Hashtable(); //PGType -> oid - - // Now handle notices as warnings, so things like "show" now work - public SQLWarning firstWarning = null; - - /* - * Cache of the current isolation level - */ - private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED; - - // The PID an cancellation key we get from the backend process - public int pid; - public int ckey; - - /* - * This is called by Class.forName() from within org.postgresql.Driver - */ - public Connection() - {} - - public void cancelQuery() throws SQLException - { - PG_Stream cancelStream = null; - try { - cancelStream = new PG_Stream(PG_HOST, PG_PORT); - } catch (ConnectException cex) { - // Added by Peter Mount - // ConnectException is thrown when the connection cannot be made. - // we trap this an return a more meaningful message for the end user - throw new PSQLException ("postgresql.con.refused"); - } catch (IOException e) { - throw new PSQLException ("postgresql.con.failed",e); - } - - // Now we need to construct and send a cancel packet - try { - cancelStream.SendInteger(16, 4); - cancelStream.SendInteger(80877102, 4); - cancelStream.SendInteger(pid, 4); - cancelStream.SendInteger(ckey, 4); - cancelStream.flush(); - } - catch(IOException e) { - throw new PSQLException("postgresql.con.failed",e); - } - finally { - try { - if(cancelStream != null) - cancelStream.close(); - } - catch(IOException e) {} // Ignore - } - } - - /* - * This method actually opens the connection. It is called by Driver. - * - * @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 - */ - protected void openConnection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException - { - firstWarning = null; - - // Throw an exception if the user or password properties are missing - // This occasionally occurs when the client uses the properties version - // of getConnection(), and is a common question on the email lists - if (info.getProperty("user") == null) - throw new PSQLException("postgresql.con.user"); - - this_driver = d; - this_url = url; - PG_DATABASE = database; - PG_USER = info.getProperty("user"); - PG_PASSWORD = info.getProperty("password", ""); - PG_PORT = port; - PG_HOST = host; - PG_STATUS = CONNECTION_BAD; - if (info.getProperty("compatible") == null) - { - compatible = d.getMajorVersion() + "." + d.getMinorVersion(); - } - else - { - compatible = info.getProperty("compatible"); - } - - // Now make the initial connection - try - { - pg_stream = new PG_Stream(host, port); - } - catch (ConnectException cex) - { - // Added by Peter Mount - // ConnectException is thrown when the connection cannot be made. - // we trap this an return a more meaningful message for the end user - throw new PSQLException ("postgresql.con.refused"); - } - catch (IOException e) - { - throw new PSQLException ("postgresql.con.failed", e); - } - - // Now we need to construct and send a startup packet - try - { - // Ver 6.3 code - pg_stream.SendInteger(4 + 4 + SM_DATABASE + SM_USER + SM_OPTIONS + SM_UNUSED + SM_TTY, 4); - pg_stream.SendInteger(PG_PROTOCOL_LATEST_MAJOR, 2); - pg_stream.SendInteger(PG_PROTOCOL_LATEST_MINOR, 2); - pg_stream.Send(database.getBytes(), SM_DATABASE); - - // This last send includes the unused fields - pg_stream.Send(PG_USER.getBytes(), SM_USER + SM_OPTIONS + SM_UNUSED + SM_TTY); - - // now flush the startup packets to the backend - pg_stream.flush(); - - // Now get the response from the backend, either an error message - // or an authentication request - int areq = -1; // must have a value here - do - { - int beresp = pg_stream.ReceiveChar(); - switch (beresp) - { - case 'E': - // An error occured, so pass the error message to the - // user. - // - // The most common one to be thrown here is: - // "User authentication failed" - // - throw new PSQLException("postgresql.con.misc", pg_stream.ReceiveString(encoding)); - - case 'R': - // Get the type of request - areq = pg_stream.ReceiveIntegerR(4); - - // Get the crypt password salt if there is one - if (areq == AUTH_REQ_CRYPT) - { - byte[] rst = new byte[2]; - rst[0] = (byte)pg_stream.ReceiveChar(); - rst[1] = (byte)pg_stream.ReceiveChar(); - salt = new String(rst, 0, 2); - DriverManager.println("Crypt salt=" + salt); - } - - // Or get the md5 password salt if there is one - if (areq == AUTH_REQ_MD5) - { - byte[] rst = new byte[4]; - rst[0] = (byte)pg_stream.ReceiveChar(); - rst[1] = (byte)pg_stream.ReceiveChar(); - rst[2] = (byte)pg_stream.ReceiveChar(); - rst[3] = (byte)pg_stream.ReceiveChar(); - salt = new String(rst, 0, 4); - DriverManager.println("MD5 salt=" + salt); - } - - // now send the auth packet - switch (areq) - { - case AUTH_REQ_OK: - break; - - case AUTH_REQ_KRB4: - DriverManager.println("postgresql: KRB4"); - throw new PSQLException("postgresql.con.kerb4"); - - case AUTH_REQ_KRB5: - DriverManager.println("postgresql: KRB5"); - throw new PSQLException("postgresql.con.kerb5"); - - case AUTH_REQ_PASSWORD: - DriverManager.println("postgresql: PASSWORD"); - pg_stream.SendInteger(5 + PG_PASSWORD.length(), 4); - pg_stream.Send(PG_PASSWORD.getBytes()); - pg_stream.SendInteger(0, 1); - pg_stream.flush(); - break; - - case AUTH_REQ_CRYPT: - DriverManager.println("postgresql: CRYPT"); - String crypted = UnixCrypt.crypt(salt, PG_PASSWORD); - pg_stream.SendInteger(5 + crypted.length(), 4); - pg_stream.Send(crypted.getBytes()); - pg_stream.SendInteger(0, 1); - pg_stream.flush(); - break; - - case AUTH_REQ_MD5: - DriverManager.println("postgresql: MD5"); - byte[] digest = MD5Digest.encode(PG_USER, PG_PASSWORD, salt); - pg_stream.SendInteger(5 + digest.length, 4); - pg_stream.Send(digest); - pg_stream.SendInteger(0, 1); - pg_stream.flush(); - break; - - default: - throw new PSQLException("postgresql.con.auth", new Integer(areq)); - } - break; - - default: - throw new PSQLException("postgresql.con.authfail"); - } - } - while (areq != AUTH_REQ_OK); - - } - catch (IOException e) - { - throw new PSQLException("postgresql.con.failed", e); - } - - - // As of protocol version 2.0, we should now receive the cancellation key and the pid - int beresp = pg_stream.ReceiveChar(); - switch (beresp) - { - case 'K': - pid = pg_stream.ReceiveIntegerR(4); - ckey = pg_stream.ReceiveIntegerR(4); - break; - case 'E': - throw new PSQLException("postgresql.con.backend", pg_stream.ReceiveString(encoding)); - case 'N': - addWarning(pg_stream.ReceiveString(encoding)); - break; - default: - throw new PSQLException("postgresql.con.setup"); - } - - // Expect ReadyForQuery packet - beresp = pg_stream.ReceiveChar(); - switch (beresp) - { - case 'Z': - break; - case 'E': - throw new PSQLException("postgresql.con.backend", pg_stream.ReceiveString(encoding)); - default: - throw new PSQLException("postgresql.con.setup"); - } - - // "pg_encoding_to_char(1)" will return 'EUC_JP' for a backend compiled with multibyte, - // otherwise it's hardcoded to 'SQL_ASCII'. - // If the backend doesn't know about multibyte we can't assume anything about the encoding - // used, so we denote this with 'UNKNOWN'. - //Note: begining with 7.2 we should be using pg_client_encoding() which - //is new in 7.2. However it isn't easy to conditionally call this new - //function, since we don't yet have the information as to what server - //version we are talking to. Thus we will continue to call - //getdatabaseencoding() until we drop support for 7.1 and older versions - //or until someone comes up with a conditional way to run one or - //the other function depending on server version that doesn't require - //two round trips to the server per connection - - final String encodingQuery = - "case when pg_encoding_to_char(1) = 'SQL_ASCII' then 'UNKNOWN' else getdatabaseencoding() end"; - - // Set datestyle and fetch db encoding in a single call, to avoid making - // more than one round trip to the backend during connection startup. - - java.sql.ResultSet resultSet = - ExecSQL("set datestyle to 'ISO'; select version(), " + encodingQuery + ";"); - - if (! resultSet.next()) - { - throw new PSQLException("postgresql.con.failed", "failed getting backend encoding"); - } - String version = resultSet.getString(1); - dbVersionNumber = extractVersionNumber(version); - - String dbEncoding = resultSet.getString(2); - encoding = Encoding.getEncoding(dbEncoding, info.getProperty("charSet")); - - // Initialise object handling - initObjectTypes(); - - // Mark the connection as ok, and cleanup - PG_STATUS = CONNECTION_OK; - } - - // These methods used to be in the main Connection implementation. As they - // are common to all implementations (JDBC1 or 2), they are placed here. - // This should make it easy to maintain the two specifications. - - /* - * This adds a warning to the warning chain. - * @param msg message to add - */ - public void addWarning(String msg) - { - DriverManager.println(msg); - - // Add the warning to the chain - if (firstWarning != null) - firstWarning.setNextWarning(new SQLWarning(msg)); - else - firstWarning = new SQLWarning(msg); - - // Now check for some specific messages - - // This is obsolete in 6.5, but I've left it in here so if we need to use this - // technique again, we'll know where to place it. - // - // This is generated by the SQL "show datestyle" - //if (msg.startsWith("NOTICE:") && msg.indexOf("DateStyle")>0) { - //// 13 is the length off "DateStyle is " - //msg = msg.substring(msg.indexOf("DateStyle is ")+13); - // - //for(int i=0;iNote: 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 java.sql.ResultSet ExecSQL(String sql) throws SQLException - { - return ExecSQL(sql, null); - } - - /* - * Send a query to the backend. Returns one of the ResultSet - * objects. - * - * Note: there does not seem to be any method currently - * in existance to return the update count. - * - * @param sql the SQL statement to be executed - * @param stat The Statement associated with this query (may be null) - * @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(); - } - - /* - * 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... - */ - int lastMessage = 0; - public String getUserName() throws SQLException - { - return PG_USER; - } - - /* - * Get the character encoding to use for this connection. - */ - public Encoding getEncoding() throws SQLException - { - return encoding; - } - - /* - * This returns the Fastpath API for the current connection. - * - *

NOTE: This is not part of JDBC, but allows access to - * functions on the org.postgresql backend itself. - * - *

It is primarily used by the LargeObject API - * - *

The best way to use this is as follows: - * - *

-	 * import org.postgresql.fastpath.*;
-	 * ...
-	 * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
-	 * 
- * - *

where myconn is an open Connection to org.postgresql. - * - * @return Fastpath object allowing access to functions on the org.postgresql - * backend. - * @exception SQLException by Fastpath when initialising for first time - */ - public Fastpath getFastpathAPI() throws SQLException - { - if (fastpath == null) - fastpath = new Fastpath(this, pg_stream); - return fastpath; - } - - // This holds a reference to the Fastpath API if already open - private Fastpath fastpath = null; - - /* - * This returns the LargeObject API for the current connection. - * - *

NOTE: This is not part of JDBC, but allows access to - * functions on the org.postgresql backend itself. - * - *

The best way to use this is as follows: - * - *

-	 * import org.postgresql.largeobject.*;
-	 * ...
-	 * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
-	 * 
- * - *

where myconn is an open Connection to org.postgresql. - * - * @return LargeObject object that implements the API - * @exception SQLException by LargeObject when initialising for first time - */ - public LargeObjectManager getLargeObjectAPI() throws SQLException - { - if (largeobject == null) - largeobject = new LargeObjectManager(this); - return largeobject; - } - - // This holds a reference to the LargeObject API if already open - private LargeObjectManager largeobject = null; - - /* - * This method is used internally to return an object based around - * org.postgresql's more unique data types. - * - *

It uses an internal Hashtable to get the handling class. If the - * type is not supported, then an instance of org.postgresql.util.PGobject - * is returned. - * - * You can use the getValue() or setValue() methods to handle the returned - * object. Custom objects can have their own methods. - * - * In 6.4, this is extended to use the org.postgresql.util.Serialize class to - * allow the Serialization of Java Objects into the database without using - * Blobs. Refer to that class for details on how this new feature works. - * - * @return PGobject for this type, and set to value - * @exception SQLException if value is not correct for this type - * @see org.postgresql.util.Serialize - */ - public Object getObject(String type, String value) throws SQLException - { - try - { - Object o = objectTypes.get(type); - - // If o is null, then the type is unknown, so check to see if type - // is an actual table name. If it does, see if a Class is known that - // can handle it - if (o == null) - { - Serialize ser = new Serialize(this, type); - objectTypes.put(type, ser); - return ser.fetch(Integer.parseInt(value)); - } - - // If o is not null, and it is a String, then its a class name that - // extends PGobject. - // - // This is used to implement the org.postgresql unique types (like lseg, - // point, etc). - if (o instanceof String) - { - // 6.3 style extending PG_Object - PGobject obj = null; - obj = (PGobject)(Class.forName((String)o).newInstance()); - obj.setType(type); - obj.setValue(value); - return (Object)obj; - } - else - { - // If it's an object, it should be an instance of our Serialize class - // If so, then call it's fetch method. - if (o instanceof Serialize) - return ((Serialize)o).fetch(Integer.parseInt(value)); - } - } - catch (SQLException sx) - { - // rethrow the exception. Done because we capture any others next - sx.fillInStackTrace(); - throw sx; - } - catch (Exception ex) - { - throw new PSQLException("postgresql.con.creobj", type, ex); - } - - // should never be reached - return null; - } - - /* - * This stores an object into the database. This method was - * deprecated in 7.2 bacause an OID can be larger than the java signed - * int returned by this method. - * @deprecated Replaced by storeObject() in 7.2 - */ - public int putObject(Object o) throws SQLException - { - return (int) storeObject(o); - } - - /* - * This stores an object into the database. - * @param o Object to store - * @return OID of the new rectord - * @exception SQLException if value is not correct for this type - * @see org.postgresql.util.Serialize - * @since 7.2 - */ - public long storeObject(Object o) throws SQLException - { - try - { - String type = o.getClass().getName(); - Object x = objectTypes.get(type); - - // If x is null, then the type is unknown, so check to see if type - // is an actual table name. If it does, see if a Class is known that - // can handle it - if (x == null) - { - Serialize ser = new Serialize(this, type); - objectTypes.put(type, ser); - return ser.storeObject(o); - } - - // If it's an object, it should be an instance of our Serialize class - // If so, then call it's fetch method. - if (x instanceof Serialize) - return ((Serialize)x).storeObject(o); - - // Thow an exception because the type is unknown - throw new PSQLException("postgresql.con.strobj"); - - } - catch (SQLException sx) - { - // rethrow the exception. Done because we capture any others next - sx.fillInStackTrace(); - throw sx; - } - catch (Exception ex) - { - throw new PSQLException("postgresql.con.strobjex", ex); - } - } - - /* - * This allows client code to add a handler for one of org.postgresql's - * more unique data types. - * - *

NOTE: This is not part of JDBC, but an extension. - * - *

The best way to use this is as follows: - * - *

-	 * ...
-	 * ((org.postgresql.Connection)myconn).addDataType("mytype","my.class.name");
-	 * ...
-	 * 
- * - *

where myconn is an open Connection to org.postgresql. - * - *

The handling class must extend org.postgresql.util.PGobject - * - * @see org.postgresql.util.PGobject - */ - public void addDataType(String type, String name) - { - objectTypes.put(type, name); - } - - // This holds the available types - private Hashtable objectTypes = new Hashtable(); - - // This array contains the types that are supported as standard. - // - // The first entry is the types name on the database, the second - // the full class name of the handling class. - // - private static final String defaultObjectTypes[][] = { - {"box", "org.postgresql.geometric.PGbox"}, - {"circle", "org.postgresql.geometric.PGcircle"}, - {"line", "org.postgresql.geometric.PGline"}, - {"lseg", "org.postgresql.geometric.PGlseg"}, - {"path", "org.postgresql.geometric.PGpath"}, - {"point", "org.postgresql.geometric.PGpoint"}, - {"polygon", "org.postgresql.geometric.PGpolygon"}, - {"money", "org.postgresql.util.PGmoney"} - }; - - // This initialises the objectTypes hashtable - private void initObjectTypes() - { - for (int i = 0;i < defaultObjectTypes.length;i++) - objectTypes.put(defaultObjectTypes[i][0], defaultObjectTypes[i][1]); - } - - // These are required by other common classes - public abstract java.sql.Statement createStatement() throws SQLException; - - /* - * 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, long insertOID, boolean binaryCursor) throws SQLException; - - /* - * 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) - * - * Note: 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.SendChar('X'); - pg_stream.flush(); - pg_stream.close(); - } - catch (IOException e) - {} - pg_stream = null; - } - } - - /* - * 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; - } - - /* - * The first warning reported by calls on this Connection is - * returned. - * - * Note: 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 firstWarning; - } - - /* - * 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 - { - firstWarning = null; - } - - - /* - * You can put a connection in read-only mode as a hunt to enable - * database optimizations - * - * Note: 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; - } - - /* - * 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 - { - if (haveMinimumServerVersion("7.1")) - { - ExecSQL("begin;" + getIsolationLevelSQL()); - } - else - { - ExecSQL("begin"); - ExecSQL(getIsolationLevelSQL()); - } - } - 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; - if (haveMinimumServerVersion("7.1")) - { - ExecSQL("commit;begin;" + getIsolationLevelSQL()); - } - else - { - ExecSQL("commit"); - ExecSQL("begin"); - ExecSQL(getIsolationLevelSQL()); - } - } - - /* - * 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; - if (haveMinimumServerVersion("7.1")) - { - ExecSQL("rollback; begin;" + getIsolationLevelSQL()); - } - else - { - ExecSQL("rollback"); - ExecSQL("begin"); - ExecSQL(getIsolationLevelSQL()); - } - } - - /* - * 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 - { - clearWarnings(); - ExecSQL("show xactisolevel"); - - SQLWarning warning = getWarnings(); - if (warning != null) - { - String message = warning.getMessage(); - clearWarnings(); - if (message.indexOf("READ COMMITTED") != -1) - return java.sql.Connection.TRANSACTION_READ_COMMITTED; - else if (message.indexOf("READ UNCOMMITTED") != -1) - return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; - else if (message.indexOf("REPEATABLE READ") != -1) - return java.sql.Connection.TRANSACTION_REPEATABLE_READ; - else if (message.indexOf("SERIALIZABLE") != -1) - return java.sql.Connection.TRANSACTION_SERIALIZABLE; - } - return java.sql.Connection.TRANSACTION_READ_COMMITTED; - } - - /* - * You can call this method to try to change the transaction - * isolation level using one of the TRANSACTION_* values. - * - * Note: 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 - { - //In 7.1 and later versions of the server it is possible using - //the "set session" command to set this once for all future txns - //however in 7.0 and prior versions it is necessary to set it in - //each transaction, thus adding complexity below. - //When we decide to drop support for servers older than 7.1 - //this can be simplified - isolationLevel = level; - String isolationLevelSQL; - - if (!haveMinimumServerVersion("7.1")) - { - isolationLevelSQL = getIsolationLevelSQL(); - } - else - { - isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL "; - switch (isolationLevel) - { - case java.sql.Connection.TRANSACTION_READ_COMMITTED: - isolationLevelSQL += "READ COMMITTED"; - break; - case java.sql.Connection.TRANSACTION_SERIALIZABLE: - isolationLevelSQL += "SERIALIZABLE"; - break; - default: - throw new PSQLException("postgresql.con.isolevel", - new Integer(isolationLevel)); - } - } - ExecSQL(isolationLevelSQL); - } - - /* - * Helper method used by setTransactionIsolation(), commit(), rollback() - * and setAutoCommit(). This returns the SQL string needed to - * set the isolation level for a transaction. In 7.1 and later it - * is possible to set a default isolation level that applies to all - * future transactions, this method is only necesary for 7.0 and older - * servers, and should be removed when support for these older - * servers are dropped - */ - protected String getIsolationLevelSQL() throws SQLException - { - //7.1 and higher servers have a default specified so - //no additional SQL is required to set the isolation level - if (haveMinimumServerVersion("7.1")) - { - return ""; - } - StringBuffer sb = new StringBuffer("SET TRANSACTION ISOLATION LEVEL"); - - switch (isolationLevel) - { - case java.sql.Connection.TRANSACTION_READ_COMMITTED: - sb.append(" READ COMMITTED"); - break; - - case java.sql.Connection.TRANSACTION_SERIALIZABLE: - sb.append(" SERIALIZABLE"); - break; - - default: - throw new PSQLException("postgresql.con.isolevel", new Integer(isolationLevel)); - } - return sb.toString(); - } - - /* - * 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 PG_DATABASE; - } - - /* - * Overides finalize(). If called, it closes the connection. - * - * This was done at the request of Rachel Greenham - * who hit a problem where multiple - * clients didn't close the connection, and once a fortnight enough - * clients were open to kill the org.postgres server. - */ - public void finalize() throws Throwable - { - close(); - } - - private static String extractVersionNumber(String fullVersionString) - { - StringTokenizer versionParts = new StringTokenizer(fullVersionString); - versionParts.nextToken(); /* "PostgreSQL" */ - return versionParts.nextToken(); /* "X.Y.Z" */ - } - - /* - * Get server version number - */ - public String getDBVersionNumber() - { - return dbVersionNumber; - } - - public boolean haveMinimumServerVersion(String ver) throws SQLException - { - return (getDBVersionNumber().compareTo(ver) >= 0); - } - - /* - * This method returns true if the compatible level set in the connection - * (which can be passed into the connection or specified in the URL) - * is at least the value passed to this method. This is used to toggle - * between different functionality as it changes across different releases - * of the jdbc driver code. The values here are versions of the jdbc client - * and not server versions. For example in 7.1 get/setBytes worked on - * LargeObject values, in 7.2 these methods were changed to work on bytea - * values. This change in functionality could be disabled by setting the - * "compatible" level to be 7.1, in which case the driver will revert to - * the 7.1 functionality. - */ - public boolean haveMinimumCompatibleVersion(String ver) throws SQLException - { - return (compatible.compareTo(ver) >= 0); - } - - - /* - * This returns the java.sql.Types type for a PG type oid - * - * @param oid PostgreSQL type oid - * @return the java.sql.Types type - * @exception SQLException if a database access error occurs - */ - public int getSQLType(int oid) throws SQLException - { - Integer sqlType = (Integer)sqlTypeCache.get(new Integer(oid)); - - // it's not in the cache, so perform a query, and add the result to the cache - if (sqlType == null) - { - ResultSet result = (org.postgresql.ResultSet)ExecSQL("select typname from pg_type where oid = " + oid); - if (result.getColumnCount() != 1 || result.getTupleCount() != 1) - throw new PSQLException("postgresql.unexpected"); - result.next(); - String pgType = result.getString(1); - Integer iOid = new Integer(oid); - sqlType = new Integer(getSQLType(result.getString(1))); - sqlTypeCache.put(iOid, sqlType); - pgTypeCache.put(iOid, pgType); - result.close(); - } - - return sqlType.intValue(); - } - - /* - * This returns the java.sql.Types type for a PG type - * - * @param pgTypeName PostgreSQL type name - * @return the java.sql.Types type - */ - public abstract int getSQLType(String pgTypeName); - - /* - * This returns the oid for a given PG data type - * @param typeName PostgreSQL type name - * @return PostgreSQL oid value for a field of this type - */ - public int getOID(String typeName) throws SQLException - { - int oid = -1; - if (typeName != null) - { - Integer oidValue = (Integer) typeOidCache.get(typeName); - if (oidValue != null) - { - oid = oidValue.intValue(); - } - else - { - // it's not in the cache, so perform a query, and add the result to the cache - ResultSet result = (org.postgresql.ResultSet)ExecSQL("select oid from pg_type where typname='" - + typeName + "'"); - if (result.getColumnCount() != 1 || result.getTupleCount() != 1) - throw new PSQLException("postgresql.unexpected"); - result.next(); - oid = Integer.parseInt(result.getString(1)); - typeOidCache.put(typeName, new Integer(oid)); - result.close(); - } - } - return oid; - } - - /* - * We also need to get the PG type name as returned by the back end. - * - * @return the String representation of the type of this field - * @exception SQLException if a database access error occurs - */ - public String getPGType(int oid) throws SQLException - { - String pgType = (String) pgTypeCache.get(new Integer(oid)); - if (pgType == null) - { - getSQLType(oid); - pgType = (String) pgTypeCache.get(new Integer(oid)); - } - return pgType; - } -} - +package org.postgresql; + +import java.io.*; +import java.net.*; +import java.sql.*; +import java.util.*; +import org.postgresql.Field; +import org.postgresql.fastpath.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; +import org.postgresql.core.*; + +/* + * $Id: Connection.java,v 1.44 2002/03/21 02:39:06 davec Exp $ + * + * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or + * JDBC2 versions of the Connection class. + * + */ +public abstract class Connection +{ + // This is the network stream associated with this connection + public PG_Stream pg_stream; + + private String PG_HOST; + private int PG_PORT; + private String PG_USER; + private String PG_DATABASE; + private boolean PG_STATUS; + private String compatible; + + /* + * The encoding to use for this connection. + */ + private Encoding encoding = Encoding.defaultEncoding(); + + private String dbVersionNumber; + + public boolean CONNECTION_OK = true; + public boolean CONNECTION_BAD = false; + + public boolean autoCommit = true; + public boolean readOnly = false; + + public Driver this_driver; + private String this_url; + private String cursor = null; // The positioned update cursor name + + // These are new for v6.3, they determine the current protocol versions + // supported by this version of the driver. They are defined in + // src/include/libpq/pqcomm.h + protected static final int PG_PROTOCOL_LATEST_MAJOR = 2; + protected static final int PG_PROTOCOL_LATEST_MINOR = 0; + + private static final int AUTH_REQ_OK = 0; + private static final int AUTH_REQ_KRB4 = 1; + private static final int AUTH_REQ_KRB5 = 2; + private static final int AUTH_REQ_PASSWORD = 3; + private static final int AUTH_REQ_CRYPT = 4; + private static final int AUTH_REQ_MD5 = 5; + + public final static int PGASYNC_IDLE = 0; /* nothing's happening, dude */ + public final static int PGASYNC_BUSY = 1; /* query in progress */ + public final static int PGASYNC_READY = 2; /* result ready for PQgetResult */ + + + // These are used to cache oids, PGTypes and SQLTypes + private static Hashtable sqlTypeCache = new Hashtable(); // oid -> SQLType + private static Hashtable pgTypeCache = new Hashtable(); // oid -> PGType + private static Hashtable typeOidCache = new Hashtable(); //PGType -> oid + + // Now handle notices as warnings, so things like "show" now work + public SQLWarning firstWarning = null; + + /* + * Cache of the current isolation level + */ + private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED; + + // The PID an cancellation key we get from the backend process + public int pid; + public int ckey; + + public int asyncStatus = PGASYNC_READY; + /* + * This is called by Class.forName() from within org.postgresql.Driver + */ + public Connection() + {} + + public void cancelQuery() throws SQLException + { + PG_Stream cancelStream = null; + try { + cancelStream = new PG_Stream(PG_HOST, PG_PORT); + } catch (ConnectException cex) { + // Added by Peter Mount + // ConnectException is thrown when the connection cannot be made. + // we trap this an return a more meaningful message for the end user + throw new PSQLException ("postgresql.con.refused"); + } catch (IOException e) { + throw new PSQLException ("postgresql.con.failed",e); + } + + // Now we need to construct and send a cancel packet + try { + cancelStream.SendInteger(16, 4); + cancelStream.SendInteger(80877102, 4); + cancelStream.SendInteger(pid, 4); + cancelStream.SendInteger(ckey, 4); + cancelStream.flush(); + } + catch(IOException e) { + throw new PSQLException("postgresql.con.failed",e); + } + finally { + try { + if(cancelStream != null) + cancelStream.close(); + } + catch(IOException e) {} // Ignore + } + } + + /* + * This method actually opens the connection. It is called by Driver. + * + * @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 + */ + protected void openConnection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException + { + firstWarning = null; + + // Throw an exception if the user or password properties are missing + // This occasionally occurs when the client uses the properties version + // of getConnection(), and is a common question on the email lists + if (info.getProperty("user") == null) + throw new PSQLException("postgresql.con.user"); + + this_driver = d; + this_url = url; + + PG_DATABASE = database; + PG_USER = info.getProperty("user"); + + String password = info.getProperty("password", ""); + PG_PORT = port; + + PG_HOST = host; + PG_STATUS = CONNECTION_BAD; + + if (info.getProperty("compatible") == null) + { + compatible = d.getMajorVersion() + "." + d.getMinorVersion(); + } + else + { + compatible = info.getProperty("compatible"); + } + + // Now make the initial connection + try + { + pg_stream = new PG_Stream(host, port); + } + catch (ConnectException cex) + { + // Added by Peter Mount + // ConnectException is thrown when the connection cannot be made. + // we trap this an return a more meaningful message for the end user + throw new PSQLException ("postgresql.con.refused"); + } + catch (IOException e) + { + throw new PSQLException ("postgresql.con.failed", e); + } + + // Now we need to construct and send a startup packet + try + { + new StartupPacket(PG_PROTOCOL_LATEST_MAJOR, + PG_PROTOCOL_LATEST_MINOR, + PG_USER, + database).writeTo(pg_stream); + + // now flush the startup packets to the backend + pg_stream.flush(); + + // Now get the response from the backend, either an error message + // or an authentication request + int areq = -1; // must have a value here + do + { + int beresp = pg_stream.ReceiveChar(); + String salt = null; + switch (beresp) + { + case 'E': + // An error occured, so pass the error message to the + // user. + // + // The most common one to be thrown here is: + // "User authentication failed" + // + throw new PSQLException("postgresql.con.misc", pg_stream.ReceiveString(encoding)); + + case 'R': + // Get the type of request + areq = pg_stream.ReceiveIntegerR(4); + + // Get the crypt password salt if there is one + if (areq == AUTH_REQ_CRYPT) + { + byte[] rst = new byte[2]; + rst[0] = (byte)pg_stream.ReceiveChar(); + rst[1] = (byte)pg_stream.ReceiveChar(); + salt = new String(rst, 0, 2); + DriverManager.println("Crypt salt=" + salt); + } + + // Or get the md5 password salt if there is one + if (areq == AUTH_REQ_MD5) + { + byte[] rst = new byte[4]; + rst[0] = (byte)pg_stream.ReceiveChar(); + rst[1] = (byte)pg_stream.ReceiveChar(); + rst[2] = (byte)pg_stream.ReceiveChar(); + rst[3] = (byte)pg_stream.ReceiveChar(); + salt = new String(rst, 0, 4); + DriverManager.println("MD5 salt=" + salt); + } + + // now send the auth packet + switch (areq) + { + case AUTH_REQ_OK: + break; + + case AUTH_REQ_KRB4: + DriverManager.println("postgresql: KRB4"); + throw new PSQLException("postgresql.con.kerb4"); + + case AUTH_REQ_KRB5: + DriverManager.println("postgresql: KRB5"); + throw new PSQLException("postgresql.con.kerb5"); + + case AUTH_REQ_PASSWORD: + DriverManager.println("postgresql: PASSWORD"); + pg_stream.SendInteger(5 + password.length(), 4); + pg_stream.Send(password.getBytes()); + pg_stream.SendInteger(0, 1); + pg_stream.flush(); + break; + + case AUTH_REQ_CRYPT: + DriverManager.println("postgresql: CRYPT"); + String crypted = UnixCrypt.crypt(salt, password); + pg_stream.SendInteger(5 + crypted.length(), 4); + pg_stream.Send(crypted.getBytes()); + pg_stream.SendInteger(0, 1); + pg_stream.flush(); + break; + + case AUTH_REQ_MD5: + DriverManager.println("postgresql: MD5"); + byte[] digest = MD5Digest.encode(PG_USER, password, salt); + pg_stream.SendInteger(5 + digest.length, 4); + pg_stream.Send(digest); + pg_stream.SendInteger(0, 1); + pg_stream.flush(); + break; + + default: + throw new PSQLException("postgresql.con.auth", new Integer(areq)); + } + break; + + default: + throw new PSQLException("postgresql.con.authfail"); + } + } + while (areq != AUTH_REQ_OK); + + } + catch (IOException e) + { + throw new PSQLException("postgresql.con.failed", e); + } + + + // As of protocol version 2.0, we should now receive the cancellation key and the pid + int beresp = pg_stream.ReceiveChar(); + switch (beresp) + { + case 'K': + pid = pg_stream.ReceiveIntegerR(4); + ckey = pg_stream.ReceiveIntegerR(4); + break; + case 'E': + throw new PSQLException("postgresql.con.backend", pg_stream.ReceiveString(encoding)); + case 'N': + addWarning(pg_stream.ReceiveString(encoding)); + break; + default: + throw new PSQLException("postgresql.con.setup"); + } + + // Expect ReadyForQuery packet + beresp = pg_stream.ReceiveChar(); + switch (beresp) + { + case 'Z': + break; + case 'E': + throw new PSQLException("postgresql.con.backend", pg_stream.ReceiveString(encoding)); + default: + throw new PSQLException("postgresql.con.setup"); + } + + // "pg_encoding_to_char(1)" will return 'EUC_JP' for a backend compiled with multibyte, + // otherwise it's hardcoded to 'SQL_ASCII'. + // If the backend doesn't know about multibyte we can't assume anything about the encoding + // used, so we denote this with 'UNKNOWN'. + //Note: begining with 7.2 we should be using pg_client_encoding() which + //is new in 7.2. However it isn't easy to conditionally call this new + //function, since we don't yet have the information as to what server + //version we are talking to. Thus we will continue to call + //getdatabaseencoding() until we drop support for 7.1 and older versions + //or until someone comes up with a conditional way to run one or + //the other function depending on server version that doesn't require + //two round trips to the server per connection + + final String encodingQuery = + "case when pg_encoding_to_char(1) = 'SQL_ASCII' then 'UNKNOWN' else getdatabaseencoding() end"; + + // Set datestyle and fetch db encoding in a single call, to avoid making + // more than one round trip to the backend during connection startup. + + java.sql.ResultSet resultSet = + ExecSQL("set datestyle to 'ISO'; select version(), " + encodingQuery + ";"); + + if (! resultSet.next()) + { + throw new PSQLException("postgresql.con.failed", "failed getting backend encoding"); + } + String version = resultSet.getString(1); + dbVersionNumber = extractVersionNumber(version); + + String dbEncoding = resultSet.getString(2); + encoding = Encoding.getEncoding(dbEncoding, info.getProperty("charSet")); + + // Initialise object handling + initObjectTypes(); + + // Mark the connection as ok, and cleanup + PG_STATUS = CONNECTION_OK; + } + + // These methods used to be in the main Connection implementation. As they + // are common to all implementations (JDBC1 or 2), they are placed here. + // This should make it easy to maintain the two specifications. + + /* + * This adds a warning to the warning chain. + * @param msg message to add + */ + public void addWarning(String msg) + { + DriverManager.println(msg); + + // Add the warning to the chain + if (firstWarning != null) + firstWarning.setNextWarning(new SQLWarning(msg)); + else + firstWarning = new SQLWarning(msg); + + // Now check for some specific messages + + // This is obsolete in 6.5, but I've left it in here so if we need to use this + // technique again, we'll know where to place it. + // + // This is generated by the SQL "show datestyle" + //if (msg.startsWith("NOTICE:") && msg.indexOf("DateStyle")>0) { + //// 13 is the length off "DateStyle is " + //msg = msg.substring(msg.indexOf("DateStyle is ")+13); + // + //for(int i=0;iNote: 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 java.sql.ResultSet ExecSQL(String sql) throws SQLException + { + return ExecSQL(sql, null); + } + + /* + * Send a query to the backend. Returns one of the ResultSet + * objects. + * + * Note: there does not seem to be any method currently + * in existance to return the update count. + * + * @param sql the SQL statement to be executed + * @param stat The Statement associated with this query (may be null) + * @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 QueryExecutor2(sql, stat, pg_stream, this).execute(); + } + + /* + * 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... + */ + int lastMessage = 0; + public String getUserName() throws SQLException + { + return PG_USER; + } + + /* + * Get the character encoding to use for this connection. + */ + public Encoding getEncoding() throws SQLException + { + return encoding; + } + + /* + * This returns the Fastpath API for the current connection. + * + *

NOTE: This is not part of JDBC, but allows access to + * functions on the org.postgresql backend itself. + * + *

It is primarily used by the LargeObject API + * + *

The best way to use this is as follows: + * + *

+	 * import org.postgresql.fastpath.*;
+	 * ...
+	 * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
+	 * 
+ * + *

where myconn is an open Connection to org.postgresql. + * + * @return Fastpath object allowing access to functions on the org.postgresql + * backend. + * @exception SQLException by Fastpath when initialising for first time + */ + public Fastpath getFastpathAPI() throws SQLException + { + if (fastpath == null) + fastpath = new Fastpath(this, pg_stream); + return fastpath; + } + + // This holds a reference to the Fastpath API if already open + private Fastpath fastpath = null; + + /* + * This returns the LargeObject API for the current connection. + * + *

NOTE: This is not part of JDBC, but allows access to + * functions on the org.postgresql backend itself. + * + *

The best way to use this is as follows: + * + *

+	 * import org.postgresql.largeobject.*;
+	 * ...
+	 * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
+	 * 
+ * + *

where myconn is an open Connection to org.postgresql. + * + * @return LargeObject object that implements the API + * @exception SQLException by LargeObject when initialising for first time + */ + public LargeObjectManager getLargeObjectAPI() throws SQLException + { + if (largeobject == null) + largeobject = new LargeObjectManager(this); + return largeobject; + } + + // This holds a reference to the LargeObject API if already open + private LargeObjectManager largeobject = null; + + /* + * This method is used internally to return an object based around + * org.postgresql's more unique data types. + * + *

It uses an internal Hashtable to get the handling class. If the + * type is not supported, then an instance of org.postgresql.util.PGobject + * is returned. + * + * You can use the getValue() or setValue() methods to handle the returned + * object. Custom objects can have their own methods. + * + * In 6.4, this is extended to use the org.postgresql.util.Serialize class to + * allow the Serialization of Java Objects into the database without using + * Blobs. Refer to that class for details on how this new feature works. + * + * @return PGobject for this type, and set to value + * @exception SQLException if value is not correct for this type + * @see org.postgresql.util.Serialize + */ + public Object getObject(String type, String value) throws SQLException + { + try + { + Object o = objectTypes.get(type); + + // If o is null, then the type is unknown, so check to see if type + // is an actual table name. If it does, see if a Class is known that + // can handle it + if (o == null) + { + Serialize ser = new Serialize(this, type); + objectTypes.put(type, ser); + return ser.fetch(Integer.parseInt(value)); + } + + // If o is not null, and it is a String, then its a class name that + // extends PGobject. + // + // This is used to implement the org.postgresql unique types (like lseg, + // point, etc). + if (o instanceof String) + { + // 6.3 style extending PG_Object + PGobject obj = null; + obj = (PGobject)(Class.forName((String)o).newInstance()); + obj.setType(type); + obj.setValue(value); + return (Object)obj; + } + else + { + // If it's an object, it should be an instance of our Serialize class + // If so, then call it's fetch method. + if (o instanceof Serialize) + return ((Serialize)o).fetch(Integer.parseInt(value)); + } + } + catch (SQLException sx) + { + // rethrow the exception. Done because we capture any others next + sx.fillInStackTrace(); + throw sx; + } + catch (Exception ex) + { + throw new PSQLException("postgresql.con.creobj", type, ex); + } + + // should never be reached + return null; + } + + /* + * This stores an object into the database. This method was + * deprecated in 7.2 bacause an OID can be larger than the java signed + * int returned by this method. + * @deprecated Replaced by storeObject() in 7.2 + */ + public int putObject(Object o) throws SQLException + { + return (int) storeObject(o); + } + + /* + * This stores an object into the database. + * @param o Object to store + * @return OID of the new rectord + * @exception SQLException if value is not correct for this type + * @see org.postgresql.util.Serialize + * @since 7.2 + */ + public long storeObject(Object o) throws SQLException + { + try + { + String type = o.getClass().getName(); + Object x = objectTypes.get(type); + + // If x is null, then the type is unknown, so check to see if type + // is an actual table name. If it does, see if a Class is known that + // can handle it + if (x == null) + { + Serialize ser = new Serialize(this, type); + objectTypes.put(type, ser); + return ser.storeObject(o); + } + + // If it's an object, it should be an instance of our Serialize class + // If so, then call it's fetch method. + if (x instanceof Serialize) + return ((Serialize)x).storeObject(o); + + // Thow an exception because the type is unknown + throw new PSQLException("postgresql.con.strobj"); + + } + catch (SQLException sx) + { + // rethrow the exception. Done because we capture any others next + sx.fillInStackTrace(); + throw sx; + } + catch (Exception ex) + { + throw new PSQLException("postgresql.con.strobjex", ex); + } + } + + /* + * This allows client code to add a handler for one of org.postgresql's + * more unique data types. + * + *

NOTE: This is not part of JDBC, but an extension. + * + *

The best way to use this is as follows: + * + *

+	 * ...
+	 * ((org.postgresql.Connection)myconn).addDataType("mytype","my.class.name");
+	 * ...
+	 * 
+ * + *

where myconn is an open Connection to org.postgresql. + * + *

The handling class must extend org.postgresql.util.PGobject + * + * @see org.postgresql.util.PGobject + */ + public void addDataType(String type, String name) + { + objectTypes.put(type, name); + } + + // This holds the available types + private Hashtable objectTypes = new Hashtable(); + + // This array contains the types that are supported as standard. + // + // The first entry is the types name on the database, the second + // the full class name of the handling class. + // + private static final String defaultObjectTypes[][] = { + {"box", "org.postgresql.geometric.PGbox"}, + {"circle", "org.postgresql.geometric.PGcircle"}, + {"line", "org.postgresql.geometric.PGline"}, + {"lseg", "org.postgresql.geometric.PGlseg"}, + {"path", "org.postgresql.geometric.PGpath"}, + {"point", "org.postgresql.geometric.PGpoint"}, + {"polygon", "org.postgresql.geometric.PGpolygon"}, + {"money", "org.postgresql.util.PGmoney"} + }; + + // This initialises the objectTypes hashtable + private void initObjectTypes() + { + for (int i = 0;i < defaultObjectTypes.length;i++) + objectTypes.put(defaultObjectTypes[i][0], defaultObjectTypes[i][1]); + } + + // These are required by other common classes + public abstract java.sql.Statement createStatement() throws SQLException; + + /* + * 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, long insertOID, boolean binaryCursor) throws SQLException; + + /* + * 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) + * + * Note: 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.SendChar('X'); + pg_stream.flush(); + pg_stream.close(); + } + catch (IOException e) + {} + pg_stream = null; + } + } + + /* + * 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; + } + + /* + * The first warning reported by calls on this Connection is + * returned. + * + * Note: 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 firstWarning; + } + + /* + * 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 + { + firstWarning = null; + } + + + /* + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * Note: 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; + } + + /* + * 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 + { + if (haveMinimumServerVersion("7.1")) + { + ExecSQL("begin;" + getIsolationLevelSQL()); + } + else + { + ExecSQL("begin"); + ExecSQL(getIsolationLevelSQL()); + } + } + 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; + if (haveMinimumServerVersion("7.1")) + { + ExecSQL("commit;begin;" + getIsolationLevelSQL()); + } + else + { + ExecSQL("commit"); + ExecSQL("begin"); + ExecSQL(getIsolationLevelSQL()); + } + } + + /* + * 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; + if (haveMinimumServerVersion("7.1")) + { + ExecSQL("rollback; begin;" + getIsolationLevelSQL()); + } + else + { + ExecSQL("rollback"); + ExecSQL("begin"); + ExecSQL(getIsolationLevelSQL()); + } + } + + /* + * 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 + { + clearWarnings(); + ExecSQL("show xactisolevel"); + + SQLWarning warning = getWarnings(); + if (warning != null) + { + String message = warning.getMessage(); + clearWarnings(); + if (message.indexOf("READ COMMITTED") != -1) + return java.sql.Connection.TRANSACTION_READ_COMMITTED; + else if (message.indexOf("READ UNCOMMITTED") != -1) + return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; + else if (message.indexOf("REPEATABLE READ") != -1) + return java.sql.Connection.TRANSACTION_REPEATABLE_READ; + else if (message.indexOf("SERIALIZABLE") != -1) + return java.sql.Connection.TRANSACTION_SERIALIZABLE; + } + return java.sql.Connection.TRANSACTION_READ_COMMITTED; + } + + /* + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * Note: 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 + { + //In 7.1 and later versions of the server it is possible using + //the "set session" command to set this once for all future txns + //however in 7.0 and prior versions it is necessary to set it in + //each transaction, thus adding complexity below. + //When we decide to drop support for servers older than 7.1 + //this can be simplified + isolationLevel = level; + String isolationLevelSQL; + + if (!haveMinimumServerVersion("7.1")) + { + isolationLevelSQL = getIsolationLevelSQL(); + } + else + { + isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL "; + switch (isolationLevel) + { + case java.sql.Connection.TRANSACTION_READ_COMMITTED: + isolationLevelSQL += "READ COMMITTED"; + break; + case java.sql.Connection.TRANSACTION_SERIALIZABLE: + isolationLevelSQL += "SERIALIZABLE"; + break; + default: + throw new PSQLException("postgresql.con.isolevel", + new Integer(isolationLevel)); + } + } + ExecSQL(isolationLevelSQL); + } + + /* + * Helper method used by setTransactionIsolation(), commit(), rollback() + * and setAutoCommit(). This returns the SQL string needed to + * set the isolation level for a transaction. In 7.1 and later it + * is possible to set a default isolation level that applies to all + * future transactions, this method is only necesary for 7.0 and older + * servers, and should be removed when support for these older + * servers are dropped + */ + protected String getIsolationLevelSQL() throws SQLException + { + //7.1 and higher servers have a default specified so + //no additional SQL is required to set the isolation level + if (haveMinimumServerVersion("7.1")) + { + return ""; + } + StringBuffer sb = new StringBuffer("SET TRANSACTION ISOLATION LEVEL"); + + switch (isolationLevel) + { + case java.sql.Connection.TRANSACTION_READ_COMMITTED: + sb.append(" READ COMMITTED"); + break; + + case java.sql.Connection.TRANSACTION_SERIALIZABLE: + sb.append(" SERIALIZABLE"); + break; + + default: + throw new PSQLException("postgresql.con.isolevel", new Integer(isolationLevel)); + } + return sb.toString(); + } + + /* + * 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 PG_DATABASE; + } + + /* + * Overides finalize(). If called, it closes the connection. + * + * This was done at the request of Rachel Greenham + * who hit a problem where multiple + * clients didn't close the connection, and once a fortnight enough + * clients were open to kill the org.postgres server. + */ + public void finalize() throws Throwable + { + close(); + } + + private static String extractVersionNumber(String fullVersionString) + { + StringTokenizer versionParts = new StringTokenizer(fullVersionString); + versionParts.nextToken(); /* "PostgreSQL" */ + return versionParts.nextToken(); /* "X.Y.Z" */ + } + + /* + * Get server version number + */ + public String getDBVersionNumber() + { + return dbVersionNumber; + } + + public boolean haveMinimumServerVersion(String ver) throws SQLException + { + return (getDBVersionNumber().compareTo(ver) >= 0); + } + + /* + * This method returns true if the compatible level set in the connection + * (which can be passed into the connection or specified in the URL) + * is at least the value passed to this method. This is used to toggle + * between different functionality as it changes across different releases + * of the jdbc driver code. The values here are versions of the jdbc client + * and not server versions. For example in 7.1 get/setBytes worked on + * LargeObject values, in 7.2 these methods were changed to work on bytea + * values. This change in functionality could be disabled by setting the + * "compatible" level to be 7.1, in which case the driver will revert to + * the 7.1 functionality. + */ + public boolean haveMinimumCompatibleVersion(String ver) throws SQLException + { + return (compatible.compareTo(ver) >= 0); + } + + + /* + * This returns the java.sql.Types type for a PG type oid + * + * @param oid PostgreSQL type oid + * @return the java.sql.Types type + * @exception SQLException if a database access error occurs + */ + public int getSQLType(int oid) throws SQLException + { + Integer sqlType = (Integer)sqlTypeCache.get(new Integer(oid)); + + // it's not in the cache, so perform a query, and add the result to the cache + if (sqlType == null) + { + ResultSet result = (org.postgresql.ResultSet)ExecSQL("select typname from pg_type where oid = " + oid); + if (result.getColumnCount() != 1 || result.getTupleCount() != 1) + throw new PSQLException("postgresql.unexpected"); + result.next(); + String pgType = result.getString(1); + Integer iOid = new Integer(oid); + sqlType = new Integer(getSQLType(result.getString(1))); + sqlTypeCache.put(iOid, sqlType); + pgTypeCache.put(iOid, pgType); + result.close(); + } + + return sqlType.intValue(); + } + + /* + * This returns the java.sql.Types type for a PG type + * + * @param pgTypeName PostgreSQL type name + * @return the java.sql.Types type + */ + public abstract int getSQLType(String pgTypeName); + + /* + * This returns the oid for a given PG data type + * @param typeName PostgreSQL type name + * @return PostgreSQL oid value for a field of this type + */ + public int getOID(String typeName) throws SQLException + { + int oid = -1; + if (typeName != null) + { + Integer oidValue = (Integer) typeOidCache.get(typeName); + if (oidValue != null) + { + oid = oidValue.intValue(); + } + else + { + // it's not in the cache, so perform a query, and add the result to the cache + ResultSet result = (org.postgresql.ResultSet)ExecSQL("select oid from pg_type where typname='" + + typeName + "'"); + if (result.getColumnCount() != 1 || result.getTupleCount() != 1) + throw new PSQLException("postgresql.unexpected"); + result.next(); + oid = Integer.parseInt(result.getString(1)); + typeOidCache.put(typeName, new Integer(oid)); + result.close(); + } + } + return oid; + } + + /* + * We also need to get the PG type name as returned by the back end. + * + * @return the String representation of the type of this field + * @exception SQLException if a database access error occurs + */ + public String getPGType(int oid) throws SQLException + { + String pgType = (String) pgTypeCache.get(new Integer(oid)); + if (pgType == null) + { + getSQLType(oid); + pgType = (String) pgTypeCache.get(new Integer(oid)); + } + return pgType; + } +} +