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

Reply via email to