/*------------------------------------------------------------------------- * * AbstractJdbc1Connection.java * This class defines methods of the jdbc1 specification. This class is * extended by org.postgresql.jdbc2.AbstractJdbc2Connection which adds * the jdbc2 methods. The real Connection class (for jdbc1) is * org.postgresql.jdbc1.Jdbc1Connection * * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Connection.java,v 1.21 2003/06/30 21:10:55 davec Exp $ * *------------------------------------------------------------------------- */ package org.postgresql.jdbc1; import java.io.IOException; import java.net.ConnectException; import java.sql.*; import java.util.*; import org.postgresql.Driver; import org.postgresql.PGNotification; import org.postgresql.core.BaseConnection; import org.postgresql.core.BaseResultSet; import org.postgresql.core.BaseStatement; import org.postgresql.core.Encoding; import org.postgresql.core.PGStream; import org.postgresql.core.QueryExecutor; import org.postgresql.core.StartupPacket; import org.postgresql.fastpath.Fastpath; import org.postgresql.largeobject.LargeObjectManager; import org.postgresql.util.MD5Digest; import org.postgresql.util.PGobject; import org.postgresql.util.PSQLException; import org.postgresql.util.UnixCrypt; public abstract class AbstractJdbc1Connection implements BaseConnection { // This is the network stream associated with this connection private PGStream pgStream; public PGStream getPGStream() { return pgStream; } protected String PG_HOST; protected int PG_PORT; protected String PG_USER; protected String PG_DATABASE; protected boolean PG_STATUS; protected String compatible; protected boolean useSSL; // The PID an cancellation key we get from the backend process protected int pid; protected int ckey; private Vector m_notifications; /* 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 private int PGProtocolVersionMajor = 2; private int PGProtocolVersionMinor = 0; public int getPGProtocolVersionMajor() { return PGProtocolVersionMajor; } public int getPGProtocolVersionMinor() { return PGProtocolVersionMinor; } 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; private static final int AUTH_REQ_SCM = 6; // 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 = Connection.TRANSACTION_READ_COMMITTED; public abstract Statement createStatement() throws SQLException; public abstract DatabaseMetaData getMetaData() throws SQLException; /* * 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 url the URL of the connection * @param d the Driver instantation of the connection * @exception SQLException if a database access error occurs */ public 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 = (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("ssl") != null && Driver.sslEnabled()) { useSSL = true; } else { useSSL = false; } if (info.getProperty("compatible") == null) { compatible = d.getMajorVersion() + "." + d.getMinorVersion(); } else { compatible = info.getProperty("compatible"); } //Read loglevel arg and set the loglevel based on this value //in addition to setting the log level enable output to //standard out if no other printwriter is set String l_logLevelProp = info.getProperty("loglevel", "0"); int l_logLevel = 0; try { l_logLevel = Integer.parseInt(l_logLevelProp); if (l_logLevel > Driver.DEBUG || l_logLevel < Driver.INFO) { l_logLevel = 0; } } catch (Exception l_e) { //invalid value for loglevel ignore } if (l_logLevel > 0) { Driver.setLogLevel(l_logLevel); enableDriverManagerLogging(); } //Print out the driver version number if (Driver.logInfo) Driver.info(Driver.getVersion()); if (Driver.logDebug) { Driver.debug(" ssl = " + useSSL); Driver.debug(" compatible = " + compatible); Driver.debug(" loglevel = " + l_logLevel); } // Now make the initial connection try { pgStream = new PGStream(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 do the protocol work if (haveMinimumCompatibleVersion("7.4")) { openConnectionV3(host,port,info,database,url,d,password); } else { openConnectionV2(host,port,info,database,url,d,password); } } private void openConnectionV3(String p_host, int p_port, Properties p_info, String p_database, String p_url, Driver p_d, String p_password) throws SQLException { PGProtocolVersionMajor = 3; if (Driver.logDebug) Driver.debug("Using Protocol Version3"); // Now we need to construct and send an ssl startup packet try { if (useSSL) { if (Driver.logDebug) Driver.debug("Asking server if it supports ssl"); pgStream.SendInteger(8,4); pgStream.SendInteger(80877103,4); // now flush the ssl packets to the backend pgStream.flush(); // Now get the response from the backend, either an error message // or an authentication request int beresp = pgStream.ReceiveChar(); if (Driver.logDebug) Driver.debug("Server response was (S=Yes,N=No): "+(char)beresp); 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", pgStream.ReceiveString(encoding)); case 'N': // Server does not support ssl throw new PSQLException("postgresql.con.sslnotsupported"); case 'S': // Server supports ssl if (Driver.logDebug) Driver.debug("server does support ssl"); Driver.makeSSL(pgStream); break; default: throw new PSQLException("postgresql.con.sslfail"); } } } catch (IOException e) { throw new PSQLException("postgresql.con.failed", e); } // Now we need to construct and send a startup packet try { new StartupPacket(PGProtocolVersionMajor, PGProtocolVersionMinor, PG_USER, p_database).writeTo(pgStream); // now flush the startup packets to the backend pgStream.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 = pgStream.ReceiveChar(); String salt = null; byte [] md5Salt = new byte[4]; 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" // int l_elen = pgStream.ReceiveIntegerR(4); if (l_elen > 30000) { //if the error length is > than 30000 we assume this is really a v2 protocol //server so try again with a v2 connection //need to create a new connection and try again try { pgStream = new PGStream(p_host, p_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); } openConnectionV2(p_host, p_port, p_info, p_database, p_url, p_d, p_password); return; } throw new PSQLException("postgresql.con.misc",encoding.decode(pgStream.Receive(l_elen-4))); case 'R': // Get the message length int l_msgLen = pgStream.ReceiveIntegerR(4); // Get the type of request areq = pgStream.ReceiveIntegerR(4); // Get the crypt password salt if there is one if (areq == AUTH_REQ_CRYPT) { byte[] rst = new byte[2]; rst[0] = (byte)pgStream.ReceiveChar(); rst[1] = (byte)pgStream.ReceiveChar(); salt = new String(rst, 0, 2); if (Driver.logDebug) Driver.debug("Crypt salt=" + salt); } // Or get the md5 password salt if there is one if (areq == AUTH_REQ_MD5) { md5Salt[0] = (byte)pgStream.ReceiveChar(); md5Salt[1] = (byte)pgStream.ReceiveChar(); md5Salt[2] = (byte)pgStream.ReceiveChar(); md5Salt[3] = (byte)pgStream.ReceiveChar(); salt = new String(md5Salt, 0, 4); if (Driver.logDebug) Driver.debug("MD5 salt=" + salt); } // now send the auth packet switch (areq) { case AUTH_REQ_OK: break; case AUTH_REQ_KRB4: if (Driver.logDebug) Driver.debug("postgresql: KRB4"); throw new PSQLException("postgresql.con.kerb4"); case AUTH_REQ_KRB5: if (Driver.logDebug) Driver.debug("postgresql: KRB5"); throw new PSQLException("postgresql.con.kerb5"); case AUTH_REQ_SCM: if (Driver.logDebug) Driver.debug("postgresql: SCM"); throw new PSQLException("postgresql.con.scm"); case AUTH_REQ_PASSWORD: if (Driver.logDebug) Driver.debug("postgresql: PASSWORD"); pgStream.SendChar('p'); pgStream.SendInteger(5 + p_password.length(), 4); pgStream.Send(p_password.getBytes()); pgStream.SendChar(0); pgStream.flush(); break; case AUTH_REQ_CRYPT: if (Driver.logDebug) Driver.debug("postgresql: CRYPT"); String crypted = UnixCrypt.crypt(salt, p_password); pgStream.SendChar('p'); pgStream.SendInteger(5 + crypted.length(), 4); pgStream.Send(crypted.getBytes()); pgStream.SendChar(0); pgStream.flush(); break; case AUTH_REQ_MD5: if (Driver.logDebug) Driver.debug("postgresql: MD5"); byte[] digest = MD5Digest.encode(PG_USER, p_password, md5Salt); pgStream.SendChar('p'); pgStream.SendInteger(5 + digest.length, 4); pgStream.Send(digest); pgStream.SendChar(0); pgStream.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); } int beresp; do { beresp = pgStream.ReceiveChar(); switch (beresp) { case 'Z': //ready for query break; case 'K': int l_msgLen = pgStream.ReceiveIntegerR(4); if (l_msgLen != 12) throw new PSQLException("postgresql.con.setup"); pid = pgStream.ReceiveIntegerR(4); ckey = pgStream.ReceiveIntegerR(4); break; case 'E': int l_elen = pgStream.ReceiveIntegerR(4); throw new PSQLException("postgresql.con.backend",encoding.decode(pgStream.Receive(l_elen-4))); case 'N': int l_nlen = pgStream.ReceiveIntegerR(4); addWarning(encoding.decode(pgStream.Receive(l_nlen-4))); break; case 'S': //TODO: handle parameter status messages int l_len = pgStream.ReceiveIntegerR(4); String l_pStatus = encoding.decode(pgStream.Receive(l_len-4)); if (Driver.logDebug) Driver.debug("ParameterStatus="+ l_pStatus); break; default: if (Driver.logDebug) Driver.debug("invalid state="+ (char)beresp); throw new PSQLException("postgresql.con.setup"); } } while (beresp != 'Z'); // read ReadyForQuery if (pgStream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup"); //TODO: handle transaction status char l_tStatus = (char)pgStream.ReceiveChar(); // "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. BaseResultSet 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, p_info.getProperty("charSet")); //In 7.3 we are forced to do a second roundtrip to handle the case //where a database may not be running in autocommit mode //jdbc by default assumes autocommit is on until setAutoCommit(false) //is called. Therefore we need to ensure a new connection is //initialized to autocommit on. //We also set the client encoding so that the driver only needs //to deal with utf8. We can only do this in 7.3 because multibyte //support is now always included if (haveMinimumServerVersion("7.3")) { BaseResultSet acRset = //TODO: if protocol V3 we can set the client encoding in startup execSQL("set client_encoding = 'UNICODE'"); //set encoding to be unicode encoding = Encoding.getEncoding("UNICODE", null); } // Initialise object handling initObjectTypes(); // Mark the connection as ok, and cleanup PG_STATUS = CONNECTION_OK; } private void openConnectionV2(String host, int port, Properties info, String database, String url, Driver d, String password) throws SQLException { PGProtocolVersionMajor = 2; if (Driver.logDebug) Driver.debug("Using Protocol Version2"); // Now we need to construct and send an ssl startup packet try { if (useSSL) { if (Driver.logDebug) Driver.debug("Asking server if it supports ssl"); pgStream.SendInteger(8,4); pgStream.SendInteger(80877103,4); // now flush the ssl packets to the backend pgStream.flush(); // Now get the response from the backend, either an error message // or an authentication request int beresp = pgStream.ReceiveChar(); if (Driver.logDebug) Driver.debug("Server response was (S=Yes,N=No): "+(char)beresp); 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", pgStream.ReceiveString(encoding)); case 'N': // Server does not support ssl throw new PSQLException("postgresql.con.sslnotsupported"); case 'S': // Server supports ssl if (Driver.logDebug) Driver.debug("server does support ssl"); Driver.makeSSL(pgStream); break; default: throw new PSQLException("postgresql.con.sslfail"); } } } catch (IOException e) { throw new PSQLException("postgresql.con.failed", e); } // Now we need to construct and send a startup packet try { new StartupPacket(PGProtocolVersionMajor, PGProtocolVersionMinor, PG_USER, database).writeTo(pgStream); // now flush the startup packets to the backend pgStream.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 = pgStream.ReceiveChar(); String salt = null; byte [] md5Salt = new byte[4]; 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", pgStream.ReceiveString(encoding)); case 'R': // Get the type of request areq = pgStream.ReceiveIntegerR(4); // Get the crypt password salt if there is one if (areq == AUTH_REQ_CRYPT) { byte[] rst = new byte[2]; rst[0] = (byte)pgStream.ReceiveChar(); rst[1] = (byte)pgStream.ReceiveChar(); salt = new String(rst, 0, 2); if (Driver.logDebug) Driver.debug("Crypt salt=" + salt); } // Or get the md5 password salt if there is one if (areq == AUTH_REQ_MD5) { md5Salt[0] = (byte)pgStream.ReceiveChar(); md5Salt[1] = (byte)pgStream.ReceiveChar(); md5Salt[2] = (byte)pgStream.ReceiveChar(); md5Salt[3] = (byte)pgStream.ReceiveChar(); salt = new String(md5Salt, 0, 4); if (Driver.logDebug) Driver.debug("MD5 salt=" + salt); } // now send the auth packet switch (areq) { case AUTH_REQ_OK: break; case AUTH_REQ_KRB4: if (Driver.logDebug) Driver.debug("postgresql: KRB4"); throw new PSQLException("postgresql.con.kerb4"); case AUTH_REQ_KRB5: if (Driver.logDebug) Driver.debug("postgresql: KRB5"); throw new PSQLException("postgresql.con.kerb5"); case AUTH_REQ_PASSWORD: if (Driver.logDebug) Driver.debug("postgresql: PASSWORD"); pgStream.SendInteger(5 + password.length(), 4); pgStream.Send(password.getBytes()); pgStream.SendInteger(0, 1); pgStream.flush(); break; case AUTH_REQ_CRYPT: if (Driver.logDebug) Driver.debug("postgresql: CRYPT"); String crypted = UnixCrypt.crypt(salt, password); pgStream.SendInteger(5 + crypted.length(), 4); pgStream.Send(crypted.getBytes()); pgStream.SendInteger(0, 1); pgStream.flush(); break; case AUTH_REQ_MD5: if (Driver.logDebug) Driver.debug("postgresql: MD5"); byte[] digest = MD5Digest.encode(PG_USER, password, md5Salt); pgStream.SendInteger(5 + digest.length, 4); pgStream.Send(digest); pgStream.SendInteger(0, 1); pgStream.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; do { beresp = pgStream.ReceiveChar(); switch (beresp) { case 'K': pid = pgStream.ReceiveIntegerR(4); ckey = pgStream.ReceiveIntegerR(4); break; case 'E': throw new PSQLException("postgresql.con.backend", pgStream.ReceiveString(encoding)); case 'N': addWarning(pgStream.ReceiveString(encoding)); break; default: throw new PSQLException("postgresql.con.setup"); } } while (beresp == 'N'); // Expect ReadyForQuery packet do { beresp = pgStream.ReceiveChar(); switch (beresp) { case 'Z': break; case 'N': addWarning(pgStream.ReceiveString(encoding)); break; case 'E': throw new PSQLException("postgresql.con.backend", pgStream.ReceiveString(encoding)); default: throw new PSQLException("postgresql.con.setup"); } } while (beresp == 'N'); // "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. BaseResultSet 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")); //TODO: remove this once the set is done as part of V3protocol connection initiation if (haveMinimumServerVersion("7.4")) { BaseResultSet acRset = execSQL("set client_encoding = 'UNICODE'"); //set encoding to be unicode encoding = Encoding.getEncoding("UNICODE", null); } //In 7.3 we are forced to do a second roundtrip to handle the case //where a database may not be running in autocommit mode //jdbc by default assumes autocommit is on until setAutoCommit(false) //is called. Therefore we need to ensure a new connection is //initialized to autocommit on. //We also set the client encoding so that the driver only needs //to deal with utf8. We can only do this in 7.3+ because multibyte //support is now always included if (haveMinimumServerVersion("7.3") && !haveMinimumServerVersion("7.4")) { BaseResultSet acRset = execSQL("set client_encoding = 'UNICODE'; show autocommit"); //set encoding to be unicode encoding = Encoding.getEncoding("UNICODE", null); if (!acRset.next()) { throw new PSQLException("postgresql.con.failed", "failed getting autocommit status"); } //if autocommit is currently off we need to turn it on //note that we will be in a transaction because the select above //will have initiated the transaction so we need a commit //to make the setting permanent if (acRset.getString(1).equals("off")) { execSQL("set autocommit = on; commit;"); } } // Initialise object handling initObjectTypes(); // Mark the connection as ok, and cleanup PG_STATUS = CONNECTION_OK; } /* * Return the instance of org.postgresql.Driver * that created this connection */ public Driver getDriver() { return this_driver; } /* * This adds a warning to the warning chain. * @param msg message to add */ public void addWarning(String 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: 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, pgStream); 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. * * @return PGobject for this type, and set to value * @exception SQLException if value is not correct for this type */ public Object getObject(String type, String value) throws SQLException { try { Object o = objectTypes.get(type); // If o is null, then the type is unknown. // 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 != null && 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; } } 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 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]); } /* * 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 (haveMinimumCompatibleVersion("7.4")) { closeV3(); } else { closeV2(); } } public void closeV3() throws SQLException { if (pgStream != null) { try { pgStream.SendChar('X'); pgStream.SendInteger(0,4); pgStream.flush(); pgStream.close(); } catch (IOException e) {} finally { pgStream = null; } } } public void closeV2() throws SQLException { if (pgStream != null) { try { pgStream.SendChar('X'); pgStream.flush(); pgStream.close(); } catch (IOException e) {} finally { pgStream = 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 ; //TODO: delay starting new transaction until first command 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 ; //TODO: delay starting transaction until first command 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 { String sql = "show transaction isolation level"; String level = null; if (haveMinimumServerVersion("7.3")) { BaseResultSet rs = execSQL(sql); if (rs.next()) { level = rs.getString(1); } rs.close(); } else { clearWarnings(); execSQL(sql); SQLWarning warning = getWarnings(); if (warning != null) { level = warning.getMessage(); } clearWarnings(); } if (level != null) { if (level.indexOf("READ COMMITTED") != -1) return Connection.TRANSACTION_READ_COMMITTED; else if (level.indexOf("READ UNCOMMITTED") != -1) return Connection.TRANSACTION_READ_UNCOMMITTED; else if (level.indexOf("REPEATABLE READ") != -1) return Connection.TRANSACTION_REPEATABLE_READ; else if (level.indexOf("SERIALIZABLE") != -1) return Connection.TRANSACTION_SERIALIZABLE; } return 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 Connection.TRANSACTION_READ_COMMITTED: isolationLevelSQL += "READ COMMITTED"; break; case 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 Connection.TRANSACTION_READ_COMMITTED: sb.append(" READ COMMITTED"); break; case 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; } // Parse a "dirty" integer surrounded by non-numeric characters private static int integerPart(String dirtyString) { int start, end; for (start = 0; start < dirtyString.length() && !Character.isDigit(dirtyString.charAt(start)); ++start) ; for (end = start; end < dirtyString.length() && Character.isDigit(dirtyString.charAt(end)); ++end) ; if (start == end) return 0; return Integer.parseInt(dirtyString.substring(start, end)); } /* * Get server major version */ public int getServerMajorVersion() { try { StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd return integerPart(versionTokens.nextToken()); // return X } catch (NoSuchElementException e) { return 0; } } /* * Get server minor version */ public int getServerMinorVersion() { try { StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd versionTokens.nextToken(); // Skip aaXbb return integerPart(versionTokens.nextToken()); // return Y } catch (NoSuchElementException e) { return 0; } } /** * Is the server we are connected to running at least this version? * This comparison method will fail whenever a major or minor version * goes to two digits (10.3.0) or (7.10.1). */ 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) { String pgType; // The opaque type does not exist in the system catalogs. if (oid == 0) { pgType = "opaque"; } else { String sql; if (haveMinimumServerVersion("7.3")) { sql = "SELECT typname FROM pg_catalog.pg_type WHERE oid = " +oid; } else { sql = "SELECT typname FROM pg_type WHERE oid = " +oid; } BaseResultSet result = execSQL(sql); if (result.getColumnCount() != 1 || result.getTupleCount() != 1) { throw new PSQLException("postgresql.unexpected"); } result.next(); pgType = result.getString(1); result.close(); } Integer iOid = new Integer(oid); sqlType = new Integer(getSQLType(pgType)); sqlTypeCache.put(iOid, sqlType); pgTypeCache.put(iOid, pgType); } return sqlType.intValue(); } /* * 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 getPGType(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 String sql; if (haveMinimumServerVersion("7.3")) { sql = "SELECT oid FROM pg_catalog.pg_type WHERE typname='" + typeName + "'"; } else { sql = "SELECT oid FROM pg_type WHERE typname='" + typeName + "'"; } BaseResultSet result = execSQL(sql); 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; } //Because the get/setLogStream methods are deprecated in JDBC2 //we use them for JDBC1 here and override this method in the jdbc2 //version of this class protected void enableDriverManagerLogging() { if (DriverManager.getLogStream() == null) { DriverManager.setLogStream(System.out); } } // This is a cache of the DatabaseMetaData instance for this connection protected java.sql.DatabaseMetaData metadata; /* * Tests to see if a Connection is closed * * @return the status of the connection * @exception SQLException (why?) */ public boolean isClosed() throws SQLException { return (pgStream == null); } /* * This implemetation uses the jdbc1Types array to support the jdbc1 * datatypes. Basically jdbc1 and jdbc2 are the same, except that * jdbc2 adds the Array types. */ public int getSQLType(String pgTypeName) { int sqlType = Types.OTHER; // default value for (int i = 0;i < jdbc1Types.length;i++) { if (pgTypeName.equals(jdbc1Types[i])) { sqlType = jdbc1Typei[i]; break; } } return sqlType; } /* * This table holds the org.postgresql names for the types supported. * Any types that map to Types.OTHER (eg POINT) don't go into this table. * They default automatically to Types.OTHER * * Note: This must be in the same order as below. * * Tip: keep these grouped together by the Types. value */ private static final String jdbc1Types[] = { "int2", "int4", "oid", "int8", "cash", "money", "numeric", "float4", "float8", "bpchar", "char", "char2", "char4", "char8", "char16", "varchar", "text", "name", "filename", "bytea", "bool", "bit", "date", "time", "abstime", "timestamp", "timestamptz" }; /* * This table holds the JDBC type for each entry above. * * Note: This must be in the same order as above * * Tip: keep these grouped together by the Types. value */ private static final int jdbc1Typei[] = { Types.SMALLINT, Types.INTEGER, Types.INTEGER, Types.BIGINT, Types.DOUBLE, Types.DOUBLE, Types.NUMERIC, Types.REAL, Types.DOUBLE, Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BINARY, Types.BIT, Types.BIT, Types.DATE, Types.TIME, Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP }; public void cancelQuery() throws SQLException { org.postgresql.core.PGStream cancelStream = null; try { cancelStream = new org.postgresql.core.PGStream(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 } } //Methods to support postgres notifications public void addNotification(org.postgresql.PGNotification p_notification) { if (m_notifications == null) m_notifications = new Vector(); m_notifications.addElement(p_notification); } public PGNotification[] getNotifications() { PGNotification[] l_return = null; if (m_notifications != null) { l_return = new PGNotification[m_notifications.size()]; m_notifications.copyInto(l_return); } m_notifications = null; return l_return; } }