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 <da...@fetter.org> http://fetter.org/ Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter Skype: davidfetter XMPP: david.fet...@gmail.com 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 (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers