diff --git a/org/postgresql/core/QueryExecutor.java b/org/postgresql/core/QueryExecutor.java index 73cbfac..12c978e 100644 --- a/org/postgresql/core/QueryExecutor.java +++ b/org/postgresql/core/QueryExecutor.java @@ -113,6 +113,7 @@ public interface QueryExecutor { ResultHandler handler, int maxRows, int fetchSize, + int timer, int flags) throws SQLException; @@ -140,6 +141,7 @@ public interface QueryExecutor { ResultHandler handler, int maxRows, int fetchSize, + int timer, int flags) throws SQLException; diff --git a/org/postgresql/core/v2/ConnectionFactoryImpl.java b/org/postgresql/core/v2/ConnectionFactoryImpl.java index cfa1df5..9fa6aeb 100644 --- a/org/postgresql/core/v2/ConnectionFactoryImpl.java +++ b/org/postgresql/core/v2/ConnectionFactoryImpl.java @@ -413,7 +413,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory { try { - executor.execute(query, null, handler, 0, 0, flags); + executor.execute(query, null, handler, 0, 0, 0, flags); } finally { diff --git a/org/postgresql/core/v2/QueryExecutorImpl.java b/org/postgresql/core/v2/QueryExecutorImpl.java index f06b7eb..ef0222b 100644 --- a/org/postgresql/core/v2/QueryExecutorImpl.java +++ b/org/postgresql/core/v2/QueryExecutorImpl.java @@ -253,17 +253,17 @@ public class QueryExecutorImpl implements QueryExecutor { public synchronized void execute(Query query, ParameterList parameters, ResultHandler handler, - int maxRows, int fetchSize, int flags) + int maxRows, int fetchSize, int timer, int flags) throws SQLException { - execute((V2Query)query, (SimpleParameterList)parameters, handler, maxRows, flags); + execute((V2Query)query, (SimpleParameterList)parameters, handler, maxRows, timer, flags); } // Nothing special yet, just run the queries one at a time. public synchronized void execute(Query[] queries, ParameterList[] parameters, ResultHandler handler, - int maxRows, int fetchSize, int flags) + int maxRows, int fetchSize, int timer, int flags) throws SQLException { final ResultHandler delegateHandler = handler; @@ -289,7 +289,7 @@ public class QueryExecutorImpl implements QueryExecutor { }; for (int i = 0; i < queries.length; ++i) - execute((V2Query)queries[i], (SimpleParameterList)parameters[i], handler, maxRows, flags); + execute((V2Query)queries[i], (SimpleParameterList)parameters[i], handler, maxRows, timer, flags); delegateHandler.handleCompletion(); } @@ -301,7 +301,7 @@ public class QueryExecutorImpl implements QueryExecutor { private void execute(V2Query query, SimpleParameterList parameters, ResultHandler handler, - int maxRows, int flags) throws SQLException + int maxRows, int timer, int flags) throws SQLException { // The V2 protocol has no support for retrieving metadata diff --git a/org/postgresql/core/v3/QueryExecutorImpl.java b/org/postgresql/core/v3/QueryExecutorImpl.java index 62678f9..b246f84 100644 --- a/org/postgresql/core/v3/QueryExecutorImpl.java +++ b/org/postgresql/core/v3/QueryExecutorImpl.java @@ -224,6 +224,7 @@ public class QueryExecutorImpl implements QueryExecutor { ResultHandler handler, int maxRows, int fetchSize, + int timer, int flags) throws SQLException { @@ -249,7 +250,7 @@ public class QueryExecutorImpl implements QueryExecutor { { try { - handler = sendQueryPreamble(handler, flags); + handler = sendQueryPreamble(handler, flags, timer); ErrorTrackingResultHandler trackingHandler = new ErrorTrackingResultHandler(handler); queryCount = 0; sendQuery((V3Query)query, (V3ParameterList)parameters, maxRows, fetchSize, flags, trackingHandler); @@ -362,6 +363,7 @@ public class QueryExecutorImpl implements QueryExecutor { ResultHandler handler, int maxRows, int fetchSize, + int timer, int flags) throws SQLException { @@ -384,7 +386,7 @@ public class QueryExecutorImpl implements QueryExecutor { try { - handler = sendQueryPreamble(handler, flags); + handler = sendQueryPreamble(handler, flags, timer); ErrorTrackingResultHandler trackingHandler = new ErrorTrackingResultHandler(handler); queryCount = 0; @@ -416,11 +418,33 @@ public class QueryExecutorImpl implements QueryExecutor { handler.handleCompletion(); } - private ResultHandler sendQueryPreamble(final ResultHandler delegateHandler, int flags) throws IOException { + private ResultHandler sendQueryPreamble(final ResultHandler delegateHandler, int timer, int flags) throws IOException { + // First, decide whether we need to do anything. Put that in final boolean so that we can use it later in anonymous class. + final boolean needBegin = ((flags & QueryExecutor.QUERY_SUPPRESS_BEGIN) != 0 || + protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE); + + final boolean needTimer = timer > 0; + // First, send CloseStatements for finalized SimpleQueries that had statement names assigned. processDeadParsedQueries(); processDeadPortals(); + if (!needBegin && !needTimer) + return delegateHandler; + + if (needBegin) { + // Need to send out a BEGIN preamble. + sendOneQuery(beginTransactionQuery, SimpleQuery.NO_PARAMETERS, 0, 0, QueryExecutor.QUERY_NO_METADATA); + } + + if (needTimer) { + // Need to send out a sesstion timer query, in ms. Timer is in sec. + SimpleQuery timerQuery = new SimpleQuery(new String[] { + "select set_session_timer(" + (timer * 1000) + "); " + }, null); + sendOneQuery(timerQuery, SimpleQuery.NO_PARAMETERS, 0, 0, QueryExecutor.QUERY_NO_METADATA); + } + // Send BEGIN on first statement in transaction. if ((flags & QueryExecutor.QUERY_SUPPRESS_BEGIN) != 0 || protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) @@ -430,39 +454,49 @@ public class QueryExecutorImpl implements QueryExecutor { // Insert a handler that intercepts the BEGIN. return new ResultHandler() { - private boolean sawBegin = false; - - public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { - if (sawBegin) - delegateHandler.handleResultRows(fromQuery, fields, tuples, cursor); - } - - public void handleCommandStatus(String status, int updateCount, long insertOID) { - if (!sawBegin) - { - sawBegin = true; - if (!status.equals("BEGIN")) - handleError(new PSQLException(GT.tr("Expected command status BEGIN, got {0}.", status), - PSQLState.PROTOCOL_VIOLATION)); + private boolean needHandleBegin = needBegin; + private boolean needHandleTimer = needTimer; + + public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { + if (!needHandleBegin) { + if (needHandleTimer) { + // ResultSet from timer query. Drop it on the floor. + needHandleTimer = false; } else { - delegateHandler.handleCommandStatus(status, updateCount, insertOID); + // Real query result. Pass it on. + delegateHandler.handleResultRows(fromQuery, fields, tuples, cursor); } } - - public void handleWarning(SQLWarning warning) { - delegateHandler.handleWarning(warning); + } + + public void handleCommandStatus(String status, int updateCount, long insertOID) { + if (needHandleBegin) + { + needHandleBegin = false; + if (!status.equals("BEGIN")) + handleError(new PSQLException(GT.tr("Expected command status BEGIN, got {0}.", status), + PSQLState.PROTOCOL_VIOLATION)); } - - public void handleError(SQLException error) { - delegateHandler.handleError(error); + else + { + delegateHandler.handleCommandStatus(status, updateCount, insertOID); } + } - public void handleCompletion() throws SQLException{ - delegateHandler.handleCompletion(); - } - }; + public void handleWarning(SQLWarning warning) { + delegateHandler.handleWarning(warning); + } + + public void handleError(SQLException error) { + delegateHandler.handleError(error); + } + + public void handleCompletion() throws SQLException{ + delegateHandler.handleCompletion(); + } + }; } // diff --git a/org/postgresql/jdbc2/AbstractJdbc2Connection.java b/org/postgresql/jdbc2/AbstractJdbc2Connection.java index 4571379..b24f3d2 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2Connection.java +++ b/org/postgresql/jdbc2/AbstractJdbc2Connection.java @@ -67,6 +67,8 @@ public abstract class AbstractJdbc2Connection implements BaseConnection public boolean autoCommit = true; // Connection's readonly state. public boolean readOnly = false; + // Respect it? + private boolean respectReadonly = false; // Bind String to UNSPECIFIED or VARCHAR? public final boolean bindStringAsVarchar; @@ -175,6 +177,12 @@ public abstract class AbstractJdbc2Connection implements BaseConnection openStackTrace = new Throwable("Connection was created at this point:"); enableDriverManagerLogging(); } + + String rdonly = info.getProperty("respectReadonly"); + if (rdonly != null) { + if (rdonly.equalsIgnoreCase("true")) + respectReadonly = true; + } } private final TimestampUtils timestampUtils; @@ -601,28 +609,36 @@ public abstract class AbstractJdbc2Connection implements BaseConnection /* - * You can put a connection in read-only mode as a hunt to enable + * You can put a connection in read-only mode as a hint to enable * database optimizations * * Note: setReadOnly cannot be called while in the middle * of a transaction * + * Note: This is only a hint, so we are free not to do + * anything. According to the JDBC specification, we should + * respectReadonly. However, this breaks a lot of popular + * software like Hibernate, so unless the end user *really* wants + * this behavior, which they can do by setting respectReadonly in + * the connection string, we will make this a no-op. + * * @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 { checkClosed(); - if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) - throw new PSQLException(GT.tr("Cannot change transaction read-only property in the middle of a transaction."), - PSQLState.ACTIVE_SQL_TRANSACTION); + if (respectReadonly) { + if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) + throw new PSQLException(GT.tr("Cannot change transaction read-only property in the middle of a transaction."), + PSQLState.ACTIVE_SQL_TRANSACTION); - if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) - { - String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); - execSQLUpdate(readOnlySql); // nb: no BEGIN triggered. + if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) + { + String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); + execSQLUpdate(readOnlySql); // nb: no BEGIN triggered. + } } - this.readOnly = readOnly; } @@ -683,7 +699,7 @@ public abstract class AbstractJdbc2Connection implements BaseConnection private void executeTransactionCommand(Query query) throws SQLException { getQueryExecutor().execute(query, null, new TransactionCommandHandler(), - 0, 0, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN); + 0, 0, 0, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN); } /* diff --git a/org/postgresql/jdbc2/AbstractJdbc2Statement.java b/org/postgresql/jdbc2/AbstractJdbc2Statement.java index 5e44a73..da2262b 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2Statement.java +++ b/org/postgresql/jdbc2/AbstractJdbc2Statement.java @@ -502,6 +502,7 @@ public abstract class AbstractJdbc2Statement implements BaseStatement handler, maxrows, fetchSize, + timeout, flags); result = firstUnclosedResult = handler.getResults(); @@ -652,9 +653,6 @@ public abstract class AbstractJdbc2Statement implements BaseStatement throw new PSQLException(GT.tr("Query timeout must be a value greater than or equals to 0."), PSQLState.INVALID_PARAMETER_VALUE); - if (seconds > 0) - throw Driver.notImplemented(this.getClass(), "setQueryTimeout(int)"); - timeout = seconds; } @@ -2739,6 +2737,7 @@ public abstract class AbstractJdbc2Statement implements BaseStatement handler, maxrows, fetchSize, + timeout, flags); return updateCounts; @@ -2827,7 +2826,7 @@ public abstract class AbstractJdbc2Statement implements BaseStatement int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; StatementResultHandler handler = new StatementResultHandler(); - connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, flags); + connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, timeout, flags); ResultWrapper wrapper = handler.getResults(); if (wrapper != null) { rs = wrapper.getResultSet(); diff --git a/org/postgresql/jdbc3/AbstractJdbc3Statement.java b/org/postgresql/jdbc3/AbstractJdbc3Statement.java index ecbfb76..e795ee8 100644 --- a/org/postgresql/jdbc3/AbstractJdbc3Statement.java +++ b/org/postgresql/jdbc3/AbstractJdbc3Statement.java @@ -411,7 +411,7 @@ public abstract class AbstractJdbc3Statement extends org.postgresql.jdbc2.Abstra { int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; StatementResultHandler handler = new StatementResultHandler(); - connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, flags); + connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, timeout, flags); int oids[] = preparedParameters.getTypeOIDs(); if (oids != null)