psql: Make default \watch interval configurable
authorDaniel Gustafsson <dgustafsson@postgresql.org>
Tue, 25 Mar 2025 16:53:33 +0000 (17:53 +0100)
committerDaniel Gustafsson <dgustafsson@postgresql.org>
Tue, 25 Mar 2025 16:53:33 +0000 (17:53 +0100)
The default interval for \watch to wait between executing queries,
when executed without a specified interval, was hardcoded to two
seconds.  This adds the new variable WATCH_INTERVAL which is used
to set the default interval, making it configurable for the user.
This makes \watch the first command which has a user configurable
default setting.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Masahiro Ikeda <ikedamsh@oss.nttdata.com>
Reviewed-by: Laurenz Albe <laurenz.albe@cybertec.at>
Reviewed-by: Greg Sabino Mullane <htamfids@gmail.com>
Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Discussion: https://postgr.es/m/B2FD26B4-8F64-4552-A603-5CC3DF1C7103@yesql.se

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/help.c
src/bin/psql/settings.h
src/bin/psql/startup.c
src/bin/psql/t/001_basic.pl
src/bin/psql/variables.c
src/bin/psql/variables.h

index f083dba49a94df144f7febe0c1a831686da71e9b..f7c8bc16a7fcf685d84639b44813cf47601a5590 100644 (file)
@@ -3852,6 +3852,8 @@ SELECT 1 \bind \sendpipeline
         until interrupted, or the query fails, or the execution count limit
         (if given) is reached, or the query no longer returns the minimum number
         of rows. Wait the specified number of seconds (default 2) between executions.
+        The default wait can be changed with the variable
+        <xref linkend="app-psql-variables-watch-interval"/>).
         For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
@@ -4746,6 +4748,17 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-variables-watch-interval">
+        <term><varname>WATCH_INTERVAL</varname></term>
+        <listitem>
+        <para>
+        This variable sets the default interval which <command>\watch</command>
+        waits between executing the query.  Specifying an interval in the
+        command overrides this variable.
+        </para>
+        </listitem>
+      </varlistentry>
+
     </variablelist>
 
    </refsect3>
index bbe337780ff12ac87d1a1c7cf8fcdf8f56d18280..a8a13c2b88bc7dc154d137a83ecaa638179d4e52 100644 (file)
@@ -3278,7 +3278,7 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
        bool        have_sleep = false;
        bool        have_iter = false;
        bool        have_min_rows = false;
-       double      sleep = 2;
+       double      sleep = pset.watch_interval;
        int         iter = 0;
        int         min_rows = 0;
 
@@ -3292,7 +3292,9 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
        /*
         * Parse arguments.  We allow either an unlabeled interval or
         * "name=value", where name is from the set ('i', 'interval', 'c',
-        * 'count', 'm', 'min_rows').
+        * 'count', 'm', 'min_rows').  The parsing of interval value should be
+        * kept in sync with ParseVariableDouble which is used for setting the
+        * default interval value.
         */
        while (success)
        {
index e47cad24de9c2e48ce39acd98a2742c12d287716..fe96e3e1de99ea81f25d9f425dc19f6fc477e0c8 100644 (file)
@@ -460,6 +460,8 @@ helpVariables(unsigned short int pager)
          "  VERSION_NAME\n"
          "  VERSION_NUM\n"
          "    psql's version (in verbose string, short string, or numeric format)\n");
+   HELP0("  WATCH_INTERVAL\n"
+         "    number of seconds \\watch by default waits between executing the query buffer\n");
 
    HELP0("\nDisplay settings:\n");
    HELP0("Usage:\n");
index 71f553c22ad62b930abac354ad381b36f5c6ef3d..fd82303f776c4e3c46eddccbedbc91bfe57628a5 100644 (file)
 #define DEFAULT_PROMPT2 "%/%R%x%# "
 #define DEFAULT_PROMPT3 ">> "
 
+#define DEFAULT_WATCH_INTERVAL "2"
+/*
+ * Limit the max default setting to a value which should be safe for the
+ * itimer call, yet large enough to cover all realistic usecases.
+ */
+#define DEFAULT_WATCH_INTERVAL_MAX (1000*1000)
 /*
  * Note: these enums should generally be chosen so that zero corresponds
  * to the default behavior.
@@ -166,6 +172,7 @@ typedef struct _psqlSettings
    int         fetch_count;
    int         histsize;
    int         ignoreeof;
+   double      watch_interval;
    PSQL_ECHO   echo;
    PSQL_ECHO_HIDDEN echo_hidden;
    PSQL_ERROR_ROLLBACK on_error_rollback;
index 5018eedf1e57040595fc6fad40818a7612ea0e68..249b6aa516902f229f436fcaa99a3bd9ce97ef61 100644 (file)
@@ -944,6 +944,21 @@ histsize_hook(const char *newval)
    return ParseVariableNum(newval, "HISTSIZE", &pset.histsize);
 }
 
+static char *
+watch_interval_substitute_hook(char *newval)
+{
+   if (newval == NULL)
+       newval = pg_strdup(DEFAULT_WATCH_INTERVAL);
+   return newval;
+}
+
+static bool
+watch_interval_hook(const char *newval)
+{
+   return ParseVariableDouble(newval, "WATCH_INTERVAL", &pset.watch_interval,
+                              0, DEFAULT_WATCH_INTERVAL_MAX);
+}
+
 static char *
 ignoreeof_substitute_hook(char *newval)
 {
@@ -1270,4 +1285,7 @@ EstablishVariableSpace(void)
    SetVariableHooks(pset.vars, "HIDE_TABLEAM",
                     bool_substitute_hook,
                     hide_tableam_hook);
+   SetVariableHooks(pset.vars, "WATCH_INTERVAL",
+                    watch_interval_substitute_hook,
+                    watch_interval_hook);
 }
index dca34ac975aa6cb5aa7cb3c3f73a1674e9a493cd..7192d96049dd62e83b743175a9d88765617a4a4d 100644 (file)
@@ -375,6 +375,12 @@ psql_like(
    $node, sprintf('SELECT 1 \watch c=3 i=%g', 0.0001),
    qr/1\n1\n1/, '\watch with 3 iterations, interval of 0.0001');
 
+# Test zero interval
+psql_like(
+   $node, '\set WATCH_INTERVAL 0
+SELECT 1 \watch c=3',
+   qr/1\n1\n1/, '\watch with 3 iterations, interval of 0');
+
 # Check \watch minimum row count
 psql_fails_like(
    $node,
@@ -426,6 +432,24 @@ psql_fails_like(
    qr/iteration count is specified more than once/,
    '\watch, iteration count is specified more than once');
 
+# Check WATCH_INTERVAL
+psql_like(
+   $node,
+   '\echo :WATCH_INTERVAL
+\set WATCH_INTERVAL 0.001
+\echo :WATCH_INTERVAL
+\unset WATCH_INTERVAL
+\echo :WATCH_INTERVAL',
+   qr/^2$
+^0.001$
+^2$/m,
+   'WATCH_INTERVAL variable is set and updated');
+psql_fails_like(
+   $node,
+   '\set WATCH_INTERVAL 1e500',
+   qr/is out of range/,
+   'WATCH_INTERVAL variable is out of range');
+
 # Test \g output piped into a program.
 # The program is perl -pe '' to simply copy the input to the output.
 my $g_file = "$tempdir/g_file_1.out";
index 59956028918aebcef7617b52d6a4cb48b7d5a720..5150eb0532ba1e88e82a73ca742b42acbd13332f 100644 (file)
@@ -7,6 +7,8 @@
  */
 #include "postgres_fe.h"
 
+#include <math.h>
+
 #include "common.h"
 #include "common/logging.h"
 #include "variables.h"
@@ -179,6 +181,74 @@ ParseVariableNum(const char *value, const char *name, int *result)
    }
 }
 
+/*
+ * Try to interpret "value" as a double value, and if successful store it in
+ * *result. If unsuccessful, *result isn't clobbered. "name" is the variable
+ * which is being assigned, the value of which is only used to produce a good
+ * error message. Pass NULL as the name to suppress the error message.  The
+ * value must be within the range [min,max] in order to be considered valid.
+ *
+ * Returns true, with *result containing the interpreted value, if "value" is
+ * syntactically valid, else false (with *result unchanged).
+ */
+bool
+ParseVariableDouble(const char *value, const char *name, double *result, double min, double max)
+{
+   char       *end;
+   double      dblval;
+
+   /*
+    * Empty-string input has historically been treated differently by strtod
+    * on various platforms, so handle that by specifically checking for it.
+    */
+   if ((value == NULL) || (*value == '\0'))
+   {
+       if (name)
+           pg_log_error("invalid input syntax for \"%s\"", name);
+       return false;
+   }
+
+   errno = 0;
+   dblval = strtod(value, &end);
+   if (errno == 0 && *end == '\0' && end != value)
+   {
+       if (dblval < min)
+       {
+           if (name)
+               pg_log_error("invalid value \"%s\" for \"%s\": must be greater than %.2f",
+                            value, name, min);
+           return false;
+       }
+       else if (dblval > max)
+       {
+           if (name)
+               pg_log_error("invalid value \"%s\" for \"%s\": must be less than %.2f",
+                            value, name, max);
+       }
+       *result = dblval;
+       return true;
+   }
+
+   /*
+    * Cater for platforms which treat values which aren't zero, but that are
+    * too close to zero to have full precision, by checking for zero or real
+    * out-of-range values.
+    */
+   else if ((errno = ERANGE) &&
+            (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
+   {
+       if (name)
+           pg_log_error("\"%s\" is out of range for \"%s\"", value, name);
+       return false;
+   }
+   else
+   {
+       if (name)
+           pg_log_error("invalid value \"%s\" for \"%s\"", value, name);
+       return false;
+   }
+}
+
 /*
  * Print values of all variables.
  */
index a95bc29f4077d6fbe209e71da973eb9580986c61..df23ccb987d72d55c7b7ddcb87bc5f3eb00da5b2 100644 (file)
@@ -81,6 +81,9 @@ bool      ParseVariableBool(const char *value, const char *name,
 bool       ParseVariableNum(const char *value, const char *name,
                             int *result);
 
+bool       ParseVariableDouble(const char *value, const char *name,
+                               double *result, double min, double max);
+
 void       PrintVariables(VariableSpace space);
 
 bool       SetVariable(VariableSpace space, const char *name, const char *value);