0001: /*-------------------------------------------------------------------------
0002: *
0003: * Copyright (c) 2004-2005, PostgreSQL Global Development Group
0004: *
0005: * IDENTIFICATION
0006: * $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Connection.java,v 1.43 2007/07/27 10:15:32 jurka Exp $
0007: *
0008: *-------------------------------------------------------------------------
0009: */
0010: package org.postgresql.jdbc2;
0011:
0012: import java.io.IOException;
0013: import java.io.PrintWriter;
0014: import java.sql.*;
0015: import java.util.*;
0016: import org.postgresql.core.*;
0017: import org.postgresql.Driver;
0018: import org.postgresql.PGNotification;
0019: import org.postgresql.fastpath.Fastpath;
0020: import org.postgresql.largeobject.LargeObjectManager;
0021: import org.postgresql.util.PSQLState;
0022: import org.postgresql.util.PGobject;
0023: import org.postgresql.util.PSQLException;
0024: import org.postgresql.util.GT;
0025:
0026: /**
0027: * This class defines methods of the jdbc2 specification.
0028: * The real Connection class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Connection
0029: */
0030: public abstract class AbstractJdbc2Connection implements BaseConnection {
0031: //
0032: // Driver-wide connection ID counter, used for logging
0033: //
0034: private static int nextConnectionID = 1;
0035:
0036: //
0037: // Data initialized on construction:
0038: //
0039:
0040: // Per-connection logger
0041: private final Logger logger;
0042:
0043: /* URL we were created via */
0044: private final String creatingURL;
0045:
0046: private Throwable openStackTrace;
0047:
0048: /* Actual network handler */
0049: private final ProtocolConnection protoConnection;
0050: /* Compatibility version */
0051: private final String compatible;
0052: /* Actual server version */
0053: private final String dbVersionNumber;
0054:
0055: /* Query that runs COMMIT */
0056: private final Query commitQuery;
0057: /* Query that runs ROLLBACK */
0058: private final Query rollbackQuery;
0059:
0060: private TypeInfoCache _typeCache;
0061:
0062: // Default statement prepare threshold.
0063: protected int prepareThreshold;
0064: // Connection's autocommit state.
0065: public boolean autoCommit = true;
0066: // Connection's readonly state.
0067: public boolean readOnly = false;
0068:
0069: // Bind String to UNSPECIFIED or VARCHAR?
0070: public final boolean bindStringAsVarchar;
0071:
0072: // Current warnings; there might be more on protoConnection too.
0073: public SQLWarning firstWarning = null;
0074:
0075: public abstract DatabaseMetaData getMetaData() throws SQLException;
0076:
0077: //
0078: // Ctor.
0079: //
0080: protected AbstractJdbc2Connection(String host, int port,
0081: String user, String database, Properties info, String url)
0082: throws SQLException {
0083: this .creatingURL = url;
0084:
0085: // Read loglevel arg and set the loglevel based on this value;
0086: // In addition to setting the log level, enable output to
0087: // standard out if no other printwriter is set
0088:
0089: int logLevel = Driver.getLogLevel();
0090: String connectionLogLevel = info.getProperty("loglevel");
0091: if (connectionLogLevel != null) {
0092: try {
0093: logLevel = Integer.parseInt(connectionLogLevel);
0094: } catch (Exception l_e) {
0095: // XXX revisit
0096: // invalid value for loglevel; ignore it
0097: }
0098: }
0099:
0100: synchronized (AbstractJdbc2Connection.class) {
0101: logger = new Logger(nextConnectionID++);
0102: logger.setLogLevel(logLevel);
0103: }
0104:
0105: if (logLevel > 0)
0106: enableDriverManagerLogging();
0107:
0108: prepareThreshold = 5;
0109: try {
0110: prepareThreshold = Integer.parseInt(info.getProperty(
0111: "prepareThreshold", "5"));
0112: if (prepareThreshold < 0)
0113: prepareThreshold = 0;
0114: } catch (Exception e) {
0115: }
0116:
0117: //Print out the driver version number
0118: if (logger.logInfo())
0119: logger.info(Driver.getVersion());
0120:
0121: // Now make the initial connection and set up local state
0122: this .protoConnection = ConnectionFactory.openConnection(host,
0123: port, user, database, info, logger);
0124: this .dbVersionNumber = protoConnection.getServerVersion();
0125: this .compatible = info.getProperty("compatible",
0126: Driver.MAJORVERSION + "." + Driver.MINORVERSION);
0127:
0128: if (logger.logDebug()) {
0129: logger.debug(" compatible = " + compatible);
0130: logger.debug(" loglevel = " + logLevel);
0131: logger.debug(" prepare threshold = " + prepareThreshold);
0132: }
0133:
0134: //
0135: // String -> text or unknown?
0136: //
0137:
0138: String stringType = info.getProperty("stringtype");
0139: if (stringType != null) {
0140: if (stringType.equalsIgnoreCase("unspecified"))
0141: bindStringAsVarchar = false;
0142: else if (stringType.equalsIgnoreCase("varchar"))
0143: bindStringAsVarchar = true;
0144: else
0145: throw new PSQLException(
0146: GT
0147: .tr(
0148: "Unsupported value for stringtype parameter: {0}",
0149: stringType),
0150: PSQLState.INVALID_PARAMETER_VALUE);
0151: } else {
0152: bindStringAsVarchar = haveMinimumCompatibleVersion("8.0");
0153: }
0154:
0155: // Initialize timestamp stuff
0156: timestampUtils = new TimestampUtils(
0157: haveMinimumServerVersion("7.4"),
0158: haveMinimumServerVersion("8.2"));
0159:
0160: // Initialize common queries.
0161: commitQuery = getQueryExecutor().createSimpleQuery("COMMIT");
0162: rollbackQuery = getQueryExecutor()
0163: .createSimpleQuery("ROLLBACK");
0164:
0165: // Initialize object handling
0166: _typeCache = new TypeInfoCache(this );
0167: initObjectTypes(info);
0168:
0169: if (Boolean.valueOf(info.getProperty("logUnclosedConnections"))
0170: .booleanValue()) {
0171: openStackTrace = new Throwable(
0172: "Connection was created at this point:");
0173: enableDriverManagerLogging();
0174: }
0175: }
0176:
0177: private final TimestampUtils timestampUtils;
0178:
0179: public TimestampUtils getTimestampUtils() {
0180: return timestampUtils;
0181: }
0182:
0183: /*
0184: * The current type mappings
0185: */
0186: protected java.util.Map typemap;
0187:
0188: public java.sql.Statement createStatement() throws SQLException {
0189: // We now follow the spec and default to TYPE_FORWARD_ONLY.
0190: return createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
0191: java.sql.ResultSet.CONCUR_READ_ONLY);
0192: }
0193:
0194: public abstract java.sql.Statement createStatement(
0195: int resultSetType, int resultSetConcurrency)
0196: throws SQLException;
0197:
0198: public java.sql.PreparedStatement prepareStatement(String sql)
0199: throws SQLException {
0200: return prepareStatement(sql,
0201: java.sql.ResultSet.TYPE_FORWARD_ONLY,
0202: java.sql.ResultSet.CONCUR_READ_ONLY);
0203: }
0204:
0205: public abstract java.sql.PreparedStatement prepareStatement(
0206: String sql, int resultSetType, int resultSetConcurrency)
0207: throws SQLException;
0208:
0209: public java.sql.CallableStatement prepareCall(String sql)
0210: throws SQLException {
0211: return prepareCall(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY,
0212: java.sql.ResultSet.CONCUR_READ_ONLY);
0213: }
0214:
0215: public abstract java.sql.CallableStatement prepareCall(String sql,
0216: int resultSetType, int resultSetConcurrency)
0217: throws SQLException;
0218:
0219: public java.util.Map getTypeMap() throws SQLException {
0220: return typemap;
0221: }
0222:
0223: // Query executor associated with this connection.
0224: public QueryExecutor getQueryExecutor() {
0225: return protoConnection.getQueryExecutor();
0226: }
0227:
0228: /*
0229: * This adds a warning to the warning chain.
0230: * @param warn warning to add
0231: */
0232: public void addWarning(SQLWarning warn) {
0233: // Add the warning to the chain
0234: if (firstWarning != null)
0235: firstWarning.setNextWarning(warn);
0236: else
0237: firstWarning = warn;
0238:
0239: }
0240:
0241: public ResultSet execSQLQuery(String s) throws SQLException {
0242: return execSQLQuery(s, ResultSet.TYPE_FORWARD_ONLY,
0243: ResultSet.CONCUR_READ_ONLY);
0244: }
0245:
0246: /**
0247: * Simple query execution.
0248: */
0249: public ResultSet execSQLQuery(String s, int resultSetType,
0250: int resultSetConcurrency) throws SQLException {
0251: BaseStatement stat = (BaseStatement) createStatement(
0252: resultSetType, resultSetConcurrency);
0253: boolean hasResultSet = stat.executeWithFlags(s,
0254: QueryExecutor.QUERY_SUPPRESS_BEGIN);
0255:
0256: while (!hasResultSet && stat.getUpdateCount() != -1)
0257: hasResultSet = stat.getMoreResults();
0258:
0259: if (!hasResultSet)
0260: throw new PSQLException(GT
0261: .tr("No results were returned by the query."),
0262: PSQLState.NO_DATA);
0263:
0264: // Transfer warnings to the connection, since the user never
0265: // has a chance to see the statement itself.
0266: SQLWarning warnings = stat.getWarnings();
0267: if (warnings != null)
0268: addWarning(warnings);
0269:
0270: return stat.getResultSet();
0271: }
0272:
0273: public void execSQLUpdate(String s) throws SQLException {
0274: BaseStatement stmt = (BaseStatement) createStatement();
0275: if (stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA
0276: | QueryExecutor.QUERY_NO_RESULTS
0277: | QueryExecutor.QUERY_SUPPRESS_BEGIN))
0278: throw new PSQLException(
0279: GT
0280: .tr("A result was returned when none was expected."),
0281: PSQLState.TOO_MANY_RESULTS);
0282:
0283: // Transfer warnings to the connection, since the user never
0284: // has a chance to see the statement itself.
0285: SQLWarning warnings = stmt.getWarnings();
0286: if (warnings != null)
0287: addWarning(warnings);
0288:
0289: stmt.close();
0290: }
0291:
0292: /*
0293: * In SQL, a result table can be retrieved through a cursor that
0294: * is named. The current row of a result can be updated or deleted
0295: * using a positioned update/delete statement that references the
0296: * cursor name.
0297: *
0298: * We do not support positioned update/delete, so this is a no-op.
0299: *
0300: * @param cursor the cursor name
0301: * @exception SQLException if a database access error occurs
0302: */
0303: public void setCursorName(String cursor) throws SQLException {
0304: // No-op.
0305: }
0306:
0307: /*
0308: * getCursorName gets the cursor name.
0309: *
0310: * @return the current cursor name
0311: * @exception SQLException if a database access error occurs
0312: */
0313: public String getCursorName() throws SQLException {
0314: return null;
0315: }
0316:
0317: /*
0318: * We are required to bring back certain information by
0319: * the DatabaseMetaData class. These functions do that.
0320: *
0321: * Method getURL() brings back the URL (good job we saved it)
0322: *
0323: * @return the url
0324: * @exception SQLException just in case...
0325: */
0326: public String getURL() throws SQLException {
0327: return creatingURL;
0328: }
0329:
0330: /*
0331: * Method getUserName() brings back the User Name (again, we
0332: * saved it)
0333: *
0334: * @return the user name
0335: * @exception SQLException just in case...
0336: */
0337: public String getUserName() throws SQLException {
0338: return protoConnection.getUser();
0339: }
0340:
0341: /*
0342: * This returns the Fastpath API for the current connection.
0343: *
0344: * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
0345: * functions on the org.postgresql backend itself.
0346: *
0347: * <p>It is primarily used by the LargeObject API
0348: *
0349: * <p>The best way to use this is as follows:
0350: *
0351: * <p><pre>
0352: * import org.postgresql.fastpath.*;
0353: * ...
0354: * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
0355: * </pre>
0356: *
0357: * <p>where myconn is an open Connection to org.postgresql.
0358: *
0359: * @return Fastpath object allowing access to functions on the org.postgresql
0360: * backend.
0361: * @exception SQLException by Fastpath when initialising for first time
0362: */
0363: public Fastpath getFastpathAPI() throws SQLException {
0364: if (fastpath == null)
0365: fastpath = new Fastpath(this );
0366: return fastpath;
0367: }
0368:
0369: // This holds a reference to the Fastpath API if already open
0370: private Fastpath fastpath = null;
0371:
0372: /*
0373: * This returns the LargeObject API for the current connection.
0374: *
0375: * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
0376: * functions on the org.postgresql backend itself.
0377: *
0378: * <p>The best way to use this is as follows:
0379: *
0380: * <p><pre>
0381: * import org.postgresql.largeobject.*;
0382: * ...
0383: * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
0384: * </pre>
0385: *
0386: * <p>where myconn is an open Connection to org.postgresql.
0387: *
0388: * @return LargeObject object that implements the API
0389: * @exception SQLException by LargeObject when initialising for first time
0390: */
0391: public LargeObjectManager getLargeObjectAPI() throws SQLException {
0392: if (largeobject == null)
0393: largeobject = new LargeObjectManager(this );
0394: return largeobject;
0395: }
0396:
0397: // This holds a reference to the LargeObject API if already open
0398: private LargeObjectManager largeobject = null;
0399:
0400: /*
0401: * This method is used internally to return an object based around
0402: * org.postgresql's more unique data types.
0403: *
0404: * <p>It uses an internal Hashtable to get the handling class. If the
0405: * type is not supported, then an instance of org.postgresql.util.PGobject
0406: * is returned.
0407: *
0408: * You can use the getValue() or setValue() methods to handle the returned
0409: * object. Custom objects can have their own methods.
0410: *
0411: * @return PGobject for this type, and set to value
0412: * @exception SQLException if value is not correct for this type
0413: */
0414: public Object getObject(String type, String value)
0415: throws SQLException {
0416: if (typemap != null) {
0417: SQLData d = (SQLData) typemap.get(type);
0418: if (d != null) {
0419: // Handle the type (requires SQLInput & SQLOutput classes to be implemented)
0420: if (logger.logDebug())
0421: logger
0422: .debug("getObject(String,String) with custom typemap");
0423: throw org.postgresql.Driver.notImplemented(this
0424: .getClass(), "getObject(String,String)");
0425: }
0426: }
0427:
0428: PGobject obj = null;
0429:
0430: if (logger.logDebug())
0431: logger.debug("Constructing object from type=" + type
0432: + " value=<" + value + ">");
0433:
0434: try {
0435: Class klass = _typeCache.getPGobject(type);
0436:
0437: // If className is not null, then try to instantiate it,
0438: // It must be basetype PGobject
0439:
0440: // This is used to implement the org.postgresql unique types (like lseg,
0441: // point, etc).
0442:
0443: if (klass != null) {
0444: obj = (PGobject) (klass.newInstance());
0445: obj.setType(type);
0446: obj.setValue(value);
0447: } else {
0448: // If className is null, then the type is unknown.
0449: // so return a PGobject with the type set, and the value set
0450: obj = new PGobject();
0451: obj.setType(type);
0452: obj.setValue(value);
0453: }
0454:
0455: return obj;
0456: } catch (SQLException sx) {
0457: // rethrow the exception. Done because we capture any others next
0458: throw sx;
0459: } catch (Exception ex) {
0460: throw new PSQLException(GT.tr(
0461: "Failed to create object for: {0}.", type),
0462: PSQLState.CONNECTION_FAILURE, ex);
0463: }
0464: }
0465:
0466: public void addDataType(String type, String name) {
0467: try {
0468: addDataType(type, Class.forName(name));
0469: } catch (Exception e) {
0470: throw new RuntimeException("Cannot register new type: " + e);
0471: }
0472: }
0473:
0474: public void addDataType(String type, Class klass)
0475: throws SQLException {
0476: _typeCache.addDataType(type, klass);
0477: }
0478:
0479: // This initialises the objectTypes hashtable
0480: private void initObjectTypes(Properties info) throws SQLException {
0481: // Add in the types that come packaged with the driver.
0482: // These can be overridden later if desired.
0483: addDataType("box", org.postgresql.geometric.PGbox.class);
0484: addDataType("circle", org.postgresql.geometric.PGcircle.class);
0485: addDataType("line", org.postgresql.geometric.PGline.class);
0486: addDataType("lseg", org.postgresql.geometric.PGlseg.class);
0487: addDataType("path", org.postgresql.geometric.PGpath.class);
0488: addDataType("point", org.postgresql.geometric.PGpoint.class);
0489: addDataType("polygon", org.postgresql.geometric.PGpolygon.class);
0490: addDataType("money", org.postgresql.util.PGmoney.class);
0491: addDataType("interval", org.postgresql.util.PGInterval.class);
0492:
0493: for (Enumeration e = info.propertyNames(); e.hasMoreElements();) {
0494: String propertyName = (String) e.nextElement();
0495: if (propertyName.startsWith("datatype.")) {
0496: String typeName = propertyName.substring(9);
0497: String className = info.getProperty(propertyName);
0498: Class klass;
0499:
0500: try {
0501: klass = Class.forName(className);
0502: } catch (ClassNotFoundException cnfe) {
0503: throw new PSQLException(
0504: GT
0505: .tr(
0506: "Unable to load the class {0} responsible for the datatype {1}",
0507: new Object[] { className,
0508: typeName }),
0509: PSQLState.SYSTEM_ERROR, cnfe);
0510: }
0511:
0512: addDataType(typeName, klass);
0513: }
0514: }
0515: }
0516:
0517: /**
0518: * In some cases, it is desirable to immediately release a Connection's
0519: * database and JDBC resources instead of waiting for them to be
0520: * automatically released.
0521: *
0522: * <B>Note:</B> A Connection is automatically closed when it is
0523: * garbage collected. Certain fatal errors also result in a closed
0524: * connection.
0525: *
0526: * @exception SQLException if a database access error occurs
0527: */
0528: public void close() {
0529: protoConnection.close();
0530: openStackTrace = null;
0531: }
0532:
0533: /*
0534: * A driver may convert the JDBC sql grammar into its system's
0535: * native SQL grammar prior to sending it; nativeSQL returns the
0536: * native form of the statement that the driver would have sent.
0537: *
0538: * @param sql a SQL statement that may contain one or more '?'
0539: * parameter placeholders
0540: * @return the native form of this statement
0541: * @exception SQLException if a database access error occurs
0542: */
0543: public String nativeSQL(String sql) throws SQLException {
0544: StringBuffer buf = new StringBuffer(sql.length());
0545: AbstractJdbc2Statement.parseSql(sql, 0, buf, false,
0546: getStandardConformingStrings());
0547: return buf.toString();
0548: }
0549:
0550: /*
0551: * The first warning reported by calls on this Connection is
0552: * returned.
0553: *
0554: * <B>Note:</B> Sebsequent warnings will be changed to this
0555: * SQLWarning
0556: *
0557: * @return the first SQLWarning or null
0558: * @exception SQLException if a database access error occurs
0559: */
0560: public synchronized SQLWarning getWarnings() throws SQLException {
0561: SQLWarning newWarnings = protoConnection.getWarnings(); // NB: also clears them.
0562: if (firstWarning == null)
0563: firstWarning = newWarnings;
0564: else
0565: firstWarning.setNextWarning(newWarnings); // Chain them on.
0566:
0567: return firstWarning;
0568: }
0569:
0570: /*
0571: * After this call, getWarnings returns null until a new warning
0572: * is reported for this connection.
0573: *
0574: * @exception SQLException if a database access error occurs
0575: */
0576: public synchronized void clearWarnings() throws SQLException {
0577: protoConnection.getWarnings(); // Clear and discard.
0578: firstWarning = null;
0579: }
0580:
0581: /*
0582: * You can put a connection in read-only mode as a hunt to enable
0583: * database optimizations
0584: *
0585: * <B>Note:</B> setReadOnly cannot be called while in the middle
0586: * of a transaction
0587: *
0588: * @param readOnly - true enables read-only mode; false disables it
0589: * @exception SQLException if a database access error occurs
0590: */
0591: public void setReadOnly(boolean readOnly) throws SQLException {
0592: if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
0593: throw new PSQLException(
0594: GT
0595: .tr("Cannot change transaction read-only property in the middle of a transaction."),
0596: PSQLState.ACTIVE_SQL_TRANSACTION);
0597:
0598: if (haveMinimumServerVersion("7.4")
0599: && readOnly != this .readOnly) {
0600: String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION "
0601: + (readOnly ? "READ ONLY" : "READ WRITE");
0602: execSQLUpdate(readOnlySql); // nb: no BEGIN triggered.
0603: }
0604:
0605: this .readOnly = readOnly;
0606: }
0607:
0608: /*
0609: * Tests to see if the connection is in Read Only Mode.
0610: *
0611: * @return true if the connection is read only
0612: * @exception SQLException if a database access error occurs
0613: */
0614: public boolean isReadOnly() throws SQLException {
0615: return readOnly;
0616: }
0617:
0618: /*
0619: * If a connection is in auto-commit mode, than all its SQL
0620: * statements will be executed and committed as individual
0621: * transactions. Otherwise, its SQL statements are grouped
0622: * into transactions that are terminated by either commit()
0623: * or rollback(). By default, new connections are in auto-
0624: * commit mode. The commit occurs when the statement completes
0625: * or the next execute occurs, whichever comes first. In the
0626: * case of statements returning a ResultSet, the statement
0627: * completes when the last row of the ResultSet has been retrieved
0628: * or the ResultSet has been closed. In advanced cases, a single
0629: * statement may return multiple results as well as output parameter
0630: * values. Here the commit occurs when all results and output param
0631: * values have been retrieved.
0632: *
0633: * @param autoCommit - true enables auto-commit; false disables it
0634: * @exception SQLException if a database access error occurs
0635: */
0636: public void setAutoCommit(boolean autoCommit) throws SQLException {
0637: if (this .autoCommit == autoCommit)
0638: return;
0639:
0640: if (!this .autoCommit)
0641: commit();
0642:
0643: this .autoCommit = autoCommit;
0644: }
0645:
0646: /*
0647: * gets the current auto-commit state
0648: *
0649: * @return Current state of the auto-commit mode
0650: * @see setAutoCommit
0651: */
0652: public boolean getAutoCommit() {
0653: return this .autoCommit;
0654: }
0655:
0656: private void executeTransactionCommand(Query query)
0657: throws SQLException {
0658: getQueryExecutor().execute(
0659: query,
0660: null,
0661: new TransactionCommandHandler(),
0662: 0,
0663: 0,
0664: QueryExecutor.QUERY_NO_METADATA
0665: | QueryExecutor.QUERY_NO_RESULTS
0666: | QueryExecutor.QUERY_SUPPRESS_BEGIN);
0667: }
0668:
0669: /*
0670: * The method commit() makes all changes made since the previous
0671: * commit/rollback permanent and releases any database locks currently
0672: * held by the Connection. This method should only be used when
0673: * auto-commit has been disabled. (If autoCommit == true, then we
0674: * just return anyhow)
0675: *
0676: * @exception SQLException if a database access error occurs
0677: * @see setAutoCommit
0678: */
0679: public void commit() throws SQLException {
0680: if (autoCommit)
0681: return;
0682:
0683: if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
0684: executeTransactionCommand(commitQuery);
0685: }
0686:
0687: /*
0688: * The method rollback() drops all changes made since the previous
0689: * commit/rollback and releases any database locks currently held by
0690: * the Connection.
0691: *
0692: * @exception SQLException if a database access error occurs
0693: * @see commit
0694: */
0695: public void rollback() throws SQLException {
0696: if (autoCommit)
0697: return;
0698:
0699: if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
0700: executeTransactionCommand(rollbackQuery);
0701: }
0702:
0703: /*
0704: * Get this Connection's current transaction isolation mode.
0705: *
0706: * @return the current TRANSACTION_* mode value
0707: * @exception SQLException if a database access error occurs
0708: */
0709: public int getTransactionIsolation() throws SQLException {
0710: String level = null;
0711:
0712: if (haveMinimumServerVersion("7.3")) {
0713: // 7.3+ returns the level as a query result.
0714: ResultSet rs = execSQLQuery("SHOW TRANSACTION ISOLATION LEVEL"); // nb: no BEGIN triggered
0715: if (rs.next())
0716: level = rs.getString(1);
0717: rs.close();
0718: } else {
0719: // 7.2 returns the level as an INFO message. Ew.
0720: // We juggle the warning chains a bit here.
0721:
0722: // Swap out current warnings.
0723: SQLWarning saveWarnings = getWarnings();
0724: clearWarnings();
0725:
0726: // Run the query any examine any resulting warnings.
0727: execSQLUpdate("SHOW TRANSACTION ISOLATION LEVEL"); // nb: no BEGIN triggered
0728: SQLWarning warning = getWarnings();
0729: if (warning != null)
0730: level = warning.getMessage();
0731:
0732: // Swap original warnings back.
0733: clearWarnings();
0734: if (saveWarnings != null)
0735: addWarning(saveWarnings);
0736: }
0737:
0738: // XXX revisit: throw exception instead of silently eating the error in unkwon cases?
0739: if (level == null)
0740: return Connection.TRANSACTION_READ_COMMITTED; // Best guess.
0741:
0742: level = level.toUpperCase();
0743: if (level.indexOf("READ COMMITTED") != -1)
0744: return Connection.TRANSACTION_READ_COMMITTED;
0745: if (level.indexOf("READ UNCOMMITTED") != -1)
0746: return Connection.TRANSACTION_READ_UNCOMMITTED;
0747: if (level.indexOf("REPEATABLE READ") != -1)
0748: return Connection.TRANSACTION_REPEATABLE_READ;
0749: if (level.indexOf("SERIALIZABLE") != -1)
0750: return Connection.TRANSACTION_SERIALIZABLE;
0751:
0752: return Connection.TRANSACTION_READ_COMMITTED; // Best guess.
0753: }
0754:
0755: /*
0756: * You can call this method to try to change the transaction
0757: * isolation level using one of the TRANSACTION_* values.
0758: *
0759: * <B>Note:</B> setTransactionIsolation cannot be called while
0760: * in the middle of a transaction
0761: *
0762: * @param level one of the TRANSACTION_* isolation values with
0763: * the exception of TRANSACTION_NONE; some databases may
0764: * not support other values
0765: * @exception SQLException if a database access error occurs
0766: * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel
0767: */
0768: public void setTransactionIsolation(int level) throws SQLException {
0769: if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
0770: throw new PSQLException(
0771: GT
0772: .tr("Cannot change transaction isolation level in the middle of a transaction."),
0773: PSQLState.ACTIVE_SQL_TRANSACTION);
0774:
0775: String isolationLevelName = getIsolationLevelName(level);
0776: if (isolationLevelName == null)
0777: throw new PSQLException(GT.tr(
0778: "Transaction isolation level {0} not supported.",
0779: new Integer(level)), PSQLState.NOT_IMPLEMENTED);
0780:
0781: String isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL "
0782: + isolationLevelName;
0783: execSQLUpdate(isolationLevelSQL); // nb: no BEGIN triggered
0784: }
0785:
0786: protected String getIsolationLevelName(int level) {
0787: boolean pg80 = haveMinimumServerVersion("8.0");
0788:
0789: if (level == Connection.TRANSACTION_READ_COMMITTED) {
0790: return "READ COMMITTED";
0791: } else if (level == Connection.TRANSACTION_SERIALIZABLE) {
0792: return "SERIALIZABLE";
0793: } else if (pg80
0794: && level == Connection.TRANSACTION_READ_UNCOMMITTED) {
0795: return "READ UNCOMMITTED";
0796: } else if (pg80
0797: && level == Connection.TRANSACTION_REPEATABLE_READ) {
0798: return "REPEATABLE READ";
0799: }
0800:
0801: return null;
0802: }
0803:
0804: /*
0805: * A sub-space of this Connection's database may be selected by
0806: * setting a catalog name. If the driver does not support catalogs,
0807: * it will silently ignore this request
0808: *
0809: * @exception SQLException if a database access error occurs
0810: */
0811: public void setCatalog(String catalog) throws SQLException {
0812: //no-op
0813: }
0814:
0815: /*
0816: * Return the connections current catalog name, or null if no
0817: * catalog name is set, or we dont support catalogs.
0818: *
0819: * @return the current catalog name or null
0820: * @exception SQLException if a database access error occurs
0821: */
0822: public String getCatalog() throws SQLException {
0823: return protoConnection.getDatabase();
0824: }
0825:
0826: /*
0827: * Overides finalize(). If called, it closes the connection.
0828: *
0829: * This was done at the request of Rachel Greenham
0830: * <rachel@enlarion.demon.co.uk> who hit a problem where multiple
0831: * clients didn't close the connection, and once a fortnight enough
0832: * clients were open to kill the postgres server.
0833: */
0834: protected void finalize() throws Throwable {
0835: if (openStackTrace != null)
0836: logger
0837: .log(
0838: GT
0839: .tr("Finalizing a Connection that was never closed:"),
0840: openStackTrace);
0841:
0842: close();
0843: }
0844:
0845: /*
0846: * Get server version number
0847: */
0848: public String getDBVersionNumber() {
0849: return dbVersionNumber;
0850: }
0851:
0852: // Parse a "dirty" integer surrounded by non-numeric characters
0853: private static int integerPart(String dirtyString) {
0854: int start, end;
0855:
0856: for (start = 0; start < dirtyString.length()
0857: && !Character.isDigit(dirtyString.charAt(start)); ++start)
0858: ;
0859:
0860: for (end = start; end < dirtyString.length()
0861: && Character.isDigit(dirtyString.charAt(end)); ++end)
0862: ;
0863:
0864: if (start == end)
0865: return 0;
0866:
0867: return Integer.parseInt(dirtyString.substring(start, end));
0868: }
0869:
0870: /*
0871: * Get server major version
0872: */
0873: public int getServerMajorVersion() {
0874: try {
0875: StringTokenizer versionTokens = new StringTokenizer(
0876: dbVersionNumber, "."); // aaXbb.ccYdd
0877: return integerPart(versionTokens.nextToken()); // return X
0878: } catch (NoSuchElementException e) {
0879: return 0;
0880: }
0881: }
0882:
0883: /*
0884: * Get server minor version
0885: */
0886: public int getServerMinorVersion() {
0887: try {
0888: StringTokenizer versionTokens = new StringTokenizer(
0889: dbVersionNumber, "."); // aaXbb.ccYdd
0890: versionTokens.nextToken(); // Skip aaXbb
0891: return integerPart(versionTokens.nextToken()); // return Y
0892: } catch (NoSuchElementException e) {
0893: return 0;
0894: }
0895: }
0896:
0897: /**
0898: * Is the server we are connected to running at least this version?
0899: * This comparison method will fail whenever a major or minor version
0900: * goes to two digits (10.3.0) or (7.10.1).
0901: */
0902: public boolean haveMinimumServerVersion(String ver) {
0903: return (dbVersionNumber.compareTo(ver) >= 0);
0904: }
0905:
0906: /*
0907: * This method returns true if the compatible level set in the connection
0908: * (which can be passed into the connection or specified in the URL)
0909: * is at least the value passed to this method. This is used to toggle
0910: * between different functionality as it changes across different releases
0911: * of the jdbc driver code. The values here are versions of the jdbc client
0912: * and not server versions. For example in 7.1 get/setBytes worked on
0913: * LargeObject values, in 7.2 these methods were changed to work on bytea
0914: * values. This change in functionality could be disabled by setting the
0915: * "compatible" level to be 7.1, in which case the driver will revert to
0916: * the 7.1 functionality.
0917: */
0918: public boolean haveMinimumCompatibleVersion(String ver) {
0919: return (compatible.compareTo(ver) >= 0);
0920: }
0921:
0922: public Encoding getEncoding() {
0923: return protoConnection.getEncoding();
0924: }
0925:
0926: public byte[] encodeString(String str) throws SQLException {
0927: try {
0928: return getEncoding().encode(str);
0929: } catch (IOException ioe) {
0930: throw new PSQLException(
0931: GT
0932: .tr("Unable to translate data into the desired encoding."),
0933: PSQLState.DATA_ERROR, ioe);
0934: }
0935: }
0936:
0937: public String escapeString(String str) throws SQLException {
0938: return Utils.appendEscapedLiteral(null, str,
0939: protoConnection.getStandardConformingStrings())
0940: .toString();
0941: }
0942:
0943: public boolean getStandardConformingStrings() {
0944: return protoConnection.getStandardConformingStrings();
0945: }
0946:
0947: /*
0948: * This returns the java.sql.Types type for a PG type oid
0949: *
0950: * @param oid PostgreSQL type oid
0951: * @return the java.sql.Types type
0952: * @exception SQLException if a database access error occurs
0953: */
0954: public int getSQLType(int oid) throws SQLException {
0955: return _typeCache.getSQLType(oid);
0956: }
0957:
0958: public Iterator getPGTypeNamesWithSQLTypes() {
0959: return _typeCache.getPGTypeNamesWithSQLTypes();
0960: }
0961:
0962: /*
0963: * This returns the oid for a given PG data type
0964: * @param typeName PostgreSQL type name
0965: * @return PostgreSQL oid value for a field of this type, or 0 if not found
0966: */
0967: public int getPGType(String typeName) throws SQLException {
0968: return _typeCache.getPGType(typeName);
0969: }
0970:
0971: public String getJavaClass(int oid) throws SQLException {
0972: return _typeCache.getJavaClass(oid);
0973: }
0974:
0975: /*
0976: * We also need to get the PG type name as returned by the back end.
0977: *
0978: * @return the String representation of the type, or null if not fould
0979: * @exception SQLException if a database access error occurs
0980: */
0981: public String getPGType(int oid) throws SQLException {
0982: return _typeCache.getPGType(oid);
0983: }
0984:
0985: // This is a cache of the DatabaseMetaData instance for this connection
0986: protected java.sql.DatabaseMetaData metadata;
0987:
0988: /*
0989: * Tests to see if a Connection is closed
0990: *
0991: * @return the status of the connection
0992: * @exception SQLException (why?)
0993: */
0994: public boolean isClosed() throws SQLException {
0995: return protoConnection.isClosed();
0996: }
0997:
0998: public void cancelQuery() throws SQLException {
0999: protoConnection.sendQueryCancel();
1000: }
1001:
1002: public PGNotification[] getNotifications() throws SQLException {
1003: // Backwards-compatibility hand-holding.
1004: PGNotification[] notifications = protoConnection
1005: .getNotifications();
1006: return (notifications.length == 0 ? null : notifications);
1007: }
1008:
1009: //
1010: // Handler for transaction queries
1011: //
1012: private class TransactionCommandHandler implements ResultHandler {
1013: private SQLException error;
1014:
1015: public void handleResultRows(Query fromQuery, Field[] fields,
1016: Vector tuples, ResultCursor cursor) {
1017: }
1018:
1019: public void handleCommandStatus(String status, int updateCount,
1020: long insertOID) {
1021: }
1022:
1023: public void handleWarning(SQLWarning warning) {
1024: AbstractJdbc2Connection.this .addWarning(warning);
1025: }
1026:
1027: public void handleError(SQLException newError) {
1028: if (error == null)
1029: error = newError;
1030: else
1031: error.setNextException(newError);
1032: }
1033:
1034: public void handleCompletion() throws SQLException {
1035: if (error != null)
1036: throw error;
1037: }
1038: }
1039:
1040: public int getPrepareThreshold() {
1041: return prepareThreshold;
1042: }
1043:
1044: public void setPrepareThreshold(int newThreshold) {
1045: this .prepareThreshold = (newThreshold <= 0 ? 0 : newThreshold);
1046: }
1047:
1048: public void setTypeMapImpl(java.util.Map map) throws SQLException {
1049: typemap = map;
1050: }
1051:
1052: public Logger getLogger() {
1053: return logger;
1054: }
1055:
1056: //Because the get/setLogStream methods are deprecated in JDBC2
1057: //we use the get/setLogWriter methods here for JDBC2 by overriding
1058: //the base version of this method
1059: protected void enableDriverManagerLogging() {
1060: if (DriverManager.getLogWriter() == null) {
1061: DriverManager
1062: .setLogWriter(new PrintWriter(System.out, true));
1063: }
1064: }
1065:
1066: public int getSQLType(String pgTypeName) {
1067: return _typeCache.getSQLType(pgTypeName);
1068: }
1069:
1070: public int getProtocolVersion() {
1071: return protoConnection.getProtocolVersion();
1072: }
1073:
1074: public boolean getStringVarcharFlag() {
1075: return bindStringAsVarchar;
1076: }
1077: }
|