Folks,
Please find enclosed a WIP patch from one of my co-workers intended to
support JDBC's setQueryTimeout, along with the patch for JDBC that
uses it.
I think this is an especially handy capability, and goes to the number
one TODO on the JDBC compliance list.
http://jdbc.postgresql.org/todo.html
Cheers,
David.
--
David Fetter <[email protected]> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: [email protected]
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e4a7dd9..06c8fce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -51,6 +51,8 @@
/* GUC variables */
int DeadlockTimeout = 1000;
int StatementTimeout = 0;
+int SessionTimerTarget = 0;
+
bool log_lock_waits = false;
/* Pointer to this process's PGPROC struct, if any */
@@ -1550,6 +1552,10 @@ CheckStatementTimeout(void)
/* Time to die */
statement_timeout_active = false;
cancel_from_timeout = true;
+
+ /* reset session timer. Never fire twice. */
+ set_session_timer_ms(0);
+
#ifdef HAVE_SETSID
/* try to signal whole process group */
kill(-MyProcPid, SIGINT);
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cba90a9..769ac2c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -2445,28 +2445,43 @@ exec_describe_portal_message(const char *portal_name)
pq_putemptymessage('n'); /* NoData */
}
-
/*
* Convenience routines for starting/committing a single command.
*/
static void
start_xact_command(void)
{
+ int64 timeout = 0;
+
if (!xact_started)
{
- ereport(DEBUG3,
- (errmsg_internal("StartTransactionCommand")));
+ ereport(DEBUG3, (errmsg_internal("StartTransactionCommand")));
StartTransactionCommand();
- /* Set statement timeout running, if any */
- /* NB: this mustn't be enabled until we are within an xact */
- if (StatementTimeout > 0)
- enable_sig_alarm(StatementTimeout, true);
- else
- cancel_from_timeout = false;
+ if (timeout == 0 ||
+ (StatementTimeout > 0 && timeout > StatementTimeout)) {
+ timeout = StatementTimeout;
+ }
+
+ /* Set statement timeout running, if any */
+ /* NB: this mustn't be enabled until we are within an xact */
+ if (StatementTimeout > 0)
+ enable_sig_alarm(StatementTimeout, true);
+ else
+ cancel_from_timeout = false;
xact_started = true;
- }
+ }
+
+ timeout = get_session_timer_ms();
+ if (timeout > 0) {
+ if (StatementTimeout == 0 || timeout < StatementTimeout) {
+ ereport(DEBUG3, (errmsg_internal("Enable an once session
timer")));
+ enable_sig_alarm(timeout, true);
+ } else {
+ cancel_from_timeout = false;
+ }
+ }
}
static void
diff --git a/src/backend/utils/adt/timestamp.c
b/src/backend/utils/adt/timestamp.c
index c6e1d13..d998df6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -4824,3 +4824,50 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
SRF_RETURN_DONE(funcctx);
}
}
+
+/* Session timer target, in ms */
+static int64 sessionTimerTarget;
+
+Datum set_session_timer(PG_FUNCTION_ARGS)
+{
+ int64 ms = PG_GETARG_INT64(0);
+ set_session_timer_ms(ms);
+ PG_RETURN_DATUM(0);
+}
+
+#ifndef HAVE_INT64_TIMESTAMP
+#error Assumes int64 impl of timestamp
+#endif
+
+void set_session_timer_ms(int64 ms)
+{
+ if (ms == 0) {
+ sessionTimerTarget = 0;
+ } else {
+ /* GetCurrentTimestamp is in us */
+ int64 time_now = GetCurrentTimestamp() / 1000;
+ sessionTimerTarget = time_now + ms;
+ }
+}
+
+int64 get_session_timer_ms()
+{
+ int64 ret = 0;
+ if (sessionTimerTarget != 0) {
+ int64 time_now = GetCurrentTimestamp() / 1000;
+ ret = sessionTimerTarget - time_now;
+
+ if (ret <= 0) {
+ /* Timer already passed. This maybe possible if some statement
+ * set the timer, finished so the timer is disabled, next statement
+ * set the timer again, but too late. We want to go through the
+ * same routine and fire the timer. Return 1 ms.
+ */
+ ret = 1;
+ }
+
+ sessionTimerTarget = 0;
+ }
+
+ return ret;
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 910474c..904a821 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201010101
+#define CATALOG_VERSION_NO 201010102
#endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 61c6b27..308f014 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4849,6 +4849,8 @@ DATA(insert OID = 3113 ( last_value PGNSP PGUID 12
1 0 0 f t f t f i 1 0 2283 "
DESCR("fetch the last row value");
DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 f t f t f
i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_
_null_ ));
DESCR("fetch the Nth row value");
+DATA(insert OID = 3115 ( set_session_timer PGNSP PGUID 12 1 0 0 f f f t f i
1 0 20 "20" _null_ _null_ _null_ _null_ set_session_timer _null_ _null_ _null_
));
+DESCR("Set a timer for the session");
/*
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index db7b729..c60d109 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -310,6 +310,11 @@ extern Datum pg_conf_load_time(PG_FUNCTION_ARGS);
extern Datum generate_series_timestamp(PG_FUNCTION_ARGS);
extern Datum generate_series_timestamptz(PG_FUNCTION_ARGS);
+/* session timer, for jdbc setQueryTimeout and like */
+extern Datum set_session_timer(PG_FUNCTION_ARGS);
+extern void set_session_timer_ms(int64 ms);
+extern int64 get_session_timer_ms(void);
+
/* Internal routines (not fmgr-callable) */
extern TimestampTz GetCurrentTimestamp(void);
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
*
* <B>Note:</B> setReadOnly cannot be called while in the middle
* of a transaction
*
+ * <B>Note:</B> 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)
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers