Allow default \watch interval in psql to be configured

Started by Daniel Gustafssonover 1 year ago32 messages
#1Daniel Gustafsson
daniel@yesql.se
1 attachment(s)

Scratching an old itch; I've long wanted to be able to set the default interval
for \watch in psql since I almost never want a 2 second wait. The interval can
of course be set by passing it to \watch but it's handy during testing and
debugging to save that with just quick \watch.

The attached adds a new variable, WATCH_INTERVAL, which is used as the default
value in case no value is defined when executing the command. The defualt of
this remains at 2 seconds as it is now. The count and min_rows values are not
affected by this as those seem less meaningful to set defaults on.

--
Daniel Gustafsson

Attachments:

0001-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=0001-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From eea647a8d0c68de4654ccb38f1898e5ca8be7c12 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Wed, 9 Oct 2024 14:53:48 +0200
Subject: [PATCH] Make default \watch interval configurable

The default interval which \watch waits between executing queries
was hardcoded to two seconds. This adds the variable WATCH_INTERVAL
which is used to set the default, making it configurable for the
user.
---
 doc/src/sgml/ref/psql-ref.sgml | 16 ++++++++++++++--
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 ++
 src/bin/psql/settings.h        |  1 +
 src/bin/psql/startup.c         | 17 +++++++++++++++++
 src/bin/psql/variables.c       | 33 +++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 +++
 7 files changed, 71 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b825ca96a2..5b1026731f 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3664,8 +3664,9 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4517,6 +4518,17 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-variables-watch-interval">
+        <term><varname>WATCH_INTERVAL</varname></term>
+        <listitem>
+        <para>
+        This variable set 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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 2bb8789750..cd91ca7851 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2897,7 +2897,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19d20c5878..375c3d138f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -455,6 +455,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 waits beetween queries\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index a22de8ef78..8a66e1c197 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -154,6 +154,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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 036caaec2f..600389410d 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -939,6 +939,20 @@ 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("2");
+	return newval;
+}
+
+static bool
+watch_interval_hook(const char *newval)
+{
+	return ParseVariableDouble(newval, "WATCH_INTERVAL", &pset.watch_interval, 0, 1000);
+}
+
 static char *
 ignoreeof_substitute_hook(char *newval)
 {
@@ -1265,4 +1279,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);
 }
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 56d70f3a10..bb8f01bf3a 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -179,6 +179,39 @@ ParseVariableNum(const char *value, const char *name, int *result)
 	}
 }
 
+bool
+ParseVariableDouble(const char *value, const char *name, double *result, double min)
+{
+	char	   *end;
+	double		dblval;
+
+	if (value == NULL)
+		value = "";
+
+	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;
+		}
+		*result = dblval;
+		return true;
+
+	}
+	else
+	{
+		if (name)
+			pg_log_error("invalid value \"%s\" for \"%s\"",
+						 value, name);
+		return false;
+	}
+}
+
 /*
  * Print values of all variables.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index dca4f06dbb..f8adcbe3dd 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
+
 void		PrintVariables(VariableSpace space);
 
 bool		SetVariable(VariableSpace space, const char *name, const char *value);
-- 
2.39.3 (Apple Git-146)

#2Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Daniel Gustafsson (#1)
Re: Allow default \watch interval in psql to be configured

On 09/10/2024 16:08, Daniel Gustafsson wrote:

Scratching an old itch; I've long wanted to be able to set the default interval
for \watch in psql since I almost never want a 2 second wait. The interval can
of course be set by passing it to \watch but it's handy during testing and
debugging to save that with just quick \watch.

The attached adds a new variable, WATCH_INTERVAL, which is used as the default
value in case no value is defined when executing the command. The defualt of
this remains at 2 seconds as it is now. The count and min_rows values are not
affected by this as those seem less meaningful to set defaults on.

../src/bin/psql/startup.c:953:80: error: too many arguments to function
call, expected 4, have 5
return ParseVariableDouble(newval, "WATCH_INTERVAL",
&pset.watch_interval, 0, 1000);
~~~~~~~~~~~~~~~~~~~
^~~~
../src/bin/psql/variables.h:84:7: note: 'ParseVariableDouble' declared here
bool ParseVariableDouble(const char *value, const char *name,
^
1 error generated.

I guess the '1000' was supposed to be the maximum, but
ParseVariableDouble doesn't take a maximum.

After fixing that by removing the '1000' argument:

postgres=# \set WATCH_INTERVAL -10
invalid value "-10" for "WATCH_INTERVAL": must be greater than 0.00

That's a little inaccurate: 0 is also accepted, so should be "must be
greater than *or equal to* 0". Or maybe "cannot be negative". -0 is also
accepted, though.

+ This variable set the default interval which <command>\watch</command>

set -> sets

+       HELP0("  WATCH_INTERVAL\n"
+                 "    number of seconds \\watch waits beetween queries\n");

beetween -> between

--
Heikki Linnakangas
Neon (https://neon.tech)

#3Daniel Gustafsson
daniel@yesql.se
In reply to: Heikki Linnakangas (#2)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 9 Oct 2024, at 16:05, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Thanks for looking!

I guess the '1000' was supposed to be the maximum, but ParseVariableDouble doesn't take a maximum.

Doh, I had a max parameter during hacking but removed it since I didn't see a
clear usecase for it. Since it's not an externally published API we can
alwasys add it when there is need. Clearly I managed to generate the patch at
the wrong time without noticing. Fixed.

That's a little inaccurate: 0 is also accepted, so should be "must be greater than *or equal to* 0". Or maybe "cannot be negative". -0 is also accepted, though.

I changed to "must be at least XX" to keep the message short.

set -> sets

beetween -> between

Fixed.

--
Daniel Gustafsson

Attachments:

v2-0001-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v2-0001-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From 728f6f7028773fd4e5d66c6427fae3992032062c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Wed, 9 Oct 2024 14:53:48 +0200
Subject: [PATCH v2] Make default \watch interval configurable

The default interval which \watch waits between executing queries
was hardcoded to two seconds. This adds the variable WATCH_INTERVAL
which is used to set the default, making it configurable for the
user.
---
 doc/src/sgml/ref/psql-ref.sgml | 16 ++++++++++++++--
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 ++
 src/bin/psql/settings.h        |  1 +
 src/bin/psql/startup.c         | 17 +++++++++++++++++
 src/bin/psql/variables.c       | 33 +++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 +++
 7 files changed, 71 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b825ca96a2..6ad7048dcf 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3664,8 +3664,9 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4517,6 +4518,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 2bb8789750..cd91ca7851 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2897,7 +2897,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19d20c5878..7ffd4a1297 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -455,6 +455,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 waits between queries\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index a22de8ef78..8a66e1c197 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -154,6 +154,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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 036caaec2f..276afa3f24 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -939,6 +939,20 @@ 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("2");
+	return newval;
+}
+
+static bool
+watch_interval_hook(const char *newval)
+{
+	return ParseVariableDouble(newval, "WATCH_INTERVAL", &pset.watch_interval, 0);
+}
+
 static char *
 ignoreeof_substitute_hook(char *newval)
 {
@@ -1265,4 +1279,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);
 }
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 56d70f3a10..c92d7991e5 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -179,6 +179,39 @@ ParseVariableNum(const char *value, const char *name, int *result)
 	}
 }
 
+bool
+ParseVariableDouble(const char *value, const char *name, double *result, double min)
+{
+	char	   *end;
+	double		dblval;
+
+	if (value == NULL)
+		value = "";
+
+	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 at least %.2f",
+							 value, name, min);
+			return false;
+		}
+		*result = dblval;
+		return true;
+
+	}
+	else
+	{
+		if (name)
+			pg_log_error("invalid value \"%s\" for \"%s\"",
+						 value, name);
+		return false;
+	}
+}
+
 /*
  * Print values of all variables.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index dca4f06dbb..f8adcbe3dd 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
+
 void		PrintVariables(VariableSpace space);
 
 bool		SetVariable(VariableSpace space, const char *name, const char *value);
-- 
2.39.3 (Apple Git-146)

#4Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#3)
Re: Allow default \watch interval in psql to be configured

On Wed, Oct 09, 2024 at 04:24:27PM +0200, Daniel Gustafsson wrote:

Fixed.

-        double        sleep = 2;
+        double        sleep = pset.watch_interval;

This forces the use of seconds as unit. The interval values I have
been using a lot myself are between 0.2s and 0.5s because I usually
want a lot more granularity in my lookups than the 1s interval. Could
it be better to allow values lower than 1s or let this value be a
string with optional "s" or "ms" units?
--
Michael

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Michael Paquier (#4)
Re: Allow default \watch interval in psql to be configured

čt 10. 10. 2024 v 2:02 odesílatel Michael Paquier <michael@paquier.xyz>
napsal:

On Wed, Oct 09, 2024 at 04:24:27PM +0200, Daniel Gustafsson wrote:

Fixed.

-        double        sleep = 2;
+        double        sleep = pset.watch_interval;

This forces the use of seconds as unit. The interval values I have
been using a lot myself are between 0.2s and 0.5s because I usually
want a lot more granularity in my lookups than the 1s interval. Could
it be better to allow values lower than 1s or let this value be a
string with optional "s" or "ms" units?

Linux "watch" uses just seconds. If I remember correctly the psql doesn't
use units in settings, so I prefer just the value from interval 0.1 .. 3600
* n

and the number can be rounded to 0.1

Regards

Pavel

Show quoted text

--
Michael

#6Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#4)
Re: Allow default \watch interval in psql to be configured

On 10 Oct 2024, at 02:01, Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Oct 09, 2024 at 04:24:27PM +0200, Daniel Gustafsson wrote:

Fixed.

-        double        sleep = 2;
+        double        sleep = pset.watch_interval;

This forces the use of seconds as unit. The interval values I have
been using a lot myself are between 0.2s and 0.5s because I usually
want a lot more granularity in my lookups than the 1s interval. Could
it be better to allow values lower than 1s or let this value be a
string with optional "s" or "ms" units?

I'm not sure I follow, it's true that the unit is seconds but the patch doesn't
change the ability to use fractions of a second that we already support today.

db=# \echo :WATCH_INTERVAL
2
db=# \set WATCH_INTERVAL 0.1
db=# \echo :WATCH_INTERVAL
0.1
db=# select 1;
?column?
----------
1
(1 row)

danielg=# \watch
Thu Oct 10 09:32:05 2024 (every 0.1s)

?column?
----------
1
(1 row)

Thu Oct 10 09:32:05 2024 (every 0.1s)

?column?
----------
1
(1 row)

Or did I misunderstand your email?

We could support passing in an optional unit, and assume the unit to be seconds
if none was used, but it doesn't really fit nicely with the current API we have
so I wonder if the added complexity is worth it?

--
Daniel Gustafsson

#7Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#6)
Re: Allow default \watch interval in psql to be configured

On Thu, Oct 10, 2024 at 09:43:27AM +0200, Daniel Gustafsson wrote:

I'm not sure I follow, it's true that the unit is seconds but the patch doesn't
change the ability to use fractions of a second that we already support today.

db=# \echo :WATCH_INTERVAL
2
db=# \set WATCH_INTERVAL 0.1
db=# \echo :WATCH_INTERVAL
0.1
db=# select 1;
?column?
----------
1
(1 row)

Or did I misunderstand your email?

Nope, I just got it wrong. No need to bother about my previous
message :)
--
Michael

#8Kirill Reshke
reshkekirill@gmail.com
In reply to: Daniel Gustafsson (#3)
Re: Allow default \watch interval in psql to be configured

On Wed, 9 Oct 2024 at 19:24, Daniel Gustafsson <daniel@yesql.se> wrote:

On 9 Oct 2024, at 16:05, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

Thanks for looking!

I guess the '1000' was supposed to be the maximum, but ParseVariableDouble doesn't take a maximum.

Doh, I had a max parameter during hacking but removed it since I didn't see a
clear usecase for it. Since it's not an externally published API we can
alwasys add it when there is need. Clearly I managed to generate the patch at
the wrong time without noticing. Fixed.

That's a little inaccurate: 0 is also accepted, so should be "must be greater than *or equal to* 0". Or maybe "cannot be negative". -0 is also accepted, though.

I changed to "must be at least XX" to keep the message short.

set -> sets

beetween -> between

Fixed.

--
Daniel Gustafsson

Hi!
I'm mostly fine with this patch, but maybe we need to handle `errno ==
ERANGE` inside ParseVariableDouble and provide a better error msg in
this case?
Something like:
```
reshke=# \set WATCH_INTERVAL -1e-309
underflow while parsing parameter
```
Also, maybe we should provide `double max` arg to the
ParseVariableDouble function, because this is a general-use function?

--
Best regards,
Kirill Reshke

#9Daniel Gustafsson
daniel@yesql.se
In reply to: Kirill Reshke (#8)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 4 Nov 2024, at 14:55, Kirill Reshke <reshkekirill@gmail.com> wrote:

I'm mostly fine with this patch,

Thanks for review!

but maybe we need to handle `errno ==
ERANGE` inside ParseVariableDouble and provide a better error msg in
this case?
Something like:
```
reshke=# \set WATCH_INTERVAL -1e-309
underflow while parsing parameter
```

Fair point, I've added ERANGE handling in the attached v3.

Also, maybe we should provide `double max` arg to the
ParseVariableDouble function, because this is a general-use function?

It's general use, but not generally used. Since it's not an exposed API it
seems premature to add handling of a max value when there is no need, once
there is a caller (if one ever comes) that needs it we can deal with it at that
point.

--
Daniel Gustafsson

Attachments:

v3-0001-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v3-0001-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From 354acecf442baabada83b0f7cd6b1b850ba712c5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Wed, 9 Oct 2024 14:53:48 +0200
Subject: [PATCH v3] Make default \watch interval configurable

The default interval which \watch waits between executing queries
was hardcoded to two seconds. This adds the variable WATCH_INTERVAL
which is used to set the default, making it configurable for the
user.
---
 doc/src/sgml/ref/psql-ref.sgml | 16 +++++++++--
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 ++
 src/bin/psql/settings.h        |  1 +
 src/bin/psql/startup.c         | 17 +++++++++++
 src/bin/psql/variables.c       | 52 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 7 files changed, 90 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index e42073ed748..57bfa340460 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3665,8 +3665,9 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4518,6 +4519,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 1f3cbb11f7c..78930dc061b 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2894,7 +2894,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 02fe5d151e0..76a30b7b708 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -452,6 +452,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 waits between queries\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index a22de8ef78e..8a66e1c197f 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -154,6 +154,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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 036caaec2ff..276afa3f248 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -939,6 +939,20 @@ 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("2");
+	return newval;
+}
+
+static bool
+watch_interval_hook(const char *newval)
+{
+	return ParseVariableDouble(newval, "WATCH_INTERVAL", &pset.watch_interval, 0);
+}
+
 static char *
 ignoreeof_substitute_hook(char *newval)
 {
@@ -1265,4 +1279,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);
 }
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 56d70f3a109..0283a2b3c7d 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -179,6 +179,58 @@ ParseVariableNum(const char *value, const char *name, int *result)
 	}
 }
 
+bool
+ParseVariableDouble(const char *value, const char *name, double *result, double min)
+{
+	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 at least %.2f",
+							 value, name, min);
+			return false;
+		}
+		*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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index dca4f06dbbb..f8adcbe3dd5 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
+
 void		PrintVariables(VariableSpace space);
 
 bool		SetVariable(VariableSpace space, const char *name, const char *value);
-- 
2.39.3 (Apple Git-146)

#10Masahiro Ikeda
ikedamsh@oss.nttdata.com
In reply to: Daniel Gustafsson (#9)
Re: Allow default \watch interval in psql to be configured

Hi,

Thanks for developing this useful feature!
I've tested it and reviewed the patch. I'd like to provide some
feedback.

(1)

I tested with v3 patch and found the following compile error.
It seems that math.h needs to be included in variables.c.

variables.c: In function 'ParseVariableDouble':
variables.c:220:54: error: 'HUGE_VAL' undeclared (first use in this
function)
220 | (dblval == 0.0 || dblval >= HUGE_VAL ||
dblval <= -HUGE_VAL))
| ^~~~~~~~
variables.c:220:54: note: each undeclared identifier is reported only
once for each function it appears in
variables.c:232:1: warning: control reaches end of non-void function
[-Wreturn-type]
232 | }
| ^

(2)

Although the error handling logic is being discussed now, I think it
would be better,
at least, to align the logic and messages of exec_command_watch() and
ParseVariableDouble().
I understand that the error message 'for "WATCH_INTERVAL"' will remain
as a difference
since it should be considered when loaded via psqlrc.

# v3 patch test result

* minus value
=# \watch i=-1
\watch: incorrect interval value "-1"

=# \set WATCH_INTERVAL -1
invalid value "-1" for "WATCH_INTERVAL": must be at least 0.00

* not interval value
=# \watch i=1s
\watch: incorrect interval value "1s"

=# \set WATCH_INTERVAL 1s
invalid value "1s" for "WATCH_INTERVAL"

* maximum value
=# \watch i=1e500
\watch: incorrect interval value "1e500"

=# \set WATCH_INTERVAL 1e500
"1e500" is out of range for "WATCH_INTERVAL"

(3)

ParseVariableDouble() doesn't have a comment yet, though you may be
planning to add one later.

(4)

I believe the default value is 2 after the WATCH_INTERVAL is specified
because \unset
WATCH_INTERVAL sets it to '2'. So, why not update the following sentence
accordingly?

-        of rows. Wait the specified number of seconds (default 2) 
between executions.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2, which 
can be
+        changed with the variable
+        between executions.  For backwards compatibility,

For example,

Wait <varname>WATCH_INTERVAL</varname> seconds unless the interval
option is specified.
If the interval option is specified, wait the specified number of
seconds instead.

+        This variable sets the default interval which 
<command>\watch</command>
+        waits between executing the query.  Specifying an interval in 
the
+        command overrides this variable.

This variable sets the interval in seconds that
<command>\watch</command> waits
between executions. The default value is 2.0.

(5)

I think it's better to replace queries with executions because the
\watch
documentation says so.

+	HELP0("  WATCH_INTERVAL\n"
+		  "    number of seconds \\watch waits between queries\n");

Regards,
--
Masahiro Ikeda
NTT DATA CORPORATION

#11Daniel Gustafsson
daniel@yesql.se
In reply to: Masahiro Ikeda (#10)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 19 Nov 2024, at 11:20, Masahiro Ikeda <ikedamsh@oss.nttdata.com> wrote:

I've tested it and reviewed the patch. I'd like to provide some feedback.

Thank you very much for your review and I do apologize for the late response.

I tested with v3 patch and found the following compile error.
It seems that math.h needs to be included in variables.c.

variables.c: In function 'ParseVariableDouble':
variables.c:220:54: error: 'HUGE_VAL' undeclared (first use in this function)
220 | (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
| ^~~~~~~~

Odd, I don't see that, but I've nonetheless fixed it by including math.h

Although the error handling logic is being discussed now, I think it would be better,
at least, to align the logic and messages of exec_command_watch() and ParseVariableDouble().

I see your point, especially since ParseVariableBuffer is only used for this at
the moment. It is however intended as generic functionality and would require
to diverge from the other ParseVariableXXX to support custom error messages.
I'm not sure it's worth the added complexity for something which is likely to
be a quite niche feature.

ParseVariableDouble() doesn't have a comment yet, though you may be planning to add one later.

Fixed.

I believe the default value is 2 after the WATCH_INTERVAL is specified because \unset
WATCH_INTERVAL sets it to '2'. So, why not update the following sentence accordingly?

-        of rows. Wait the specified number of seconds (default 2) between executions.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2, which can be
+        changed with the variable
+        between executions.  For backwards compatibility,

For example,

Wait <varname>WATCH_INTERVAL</varname> seconds unless the interval option is specified.
If the interval option is specified, wait the specified number of seconds instead.

+        This variable sets the default interval which <command>\watch</command>
+        waits between executing the query.  Specifying an interval in the
+        command overrides this variable.

This variable sets the interval in seconds that <command>\watch</command> waits
between executions. The default value is 2.0.

I don't think this improves the documentation. The patch only changes the
defult interval which is used if the interval is omitted from the command, the
above sections discuss how interval is handled when specified. I did some
minor tweaks to the docs to try and clarify things.

I think it's better to replace queries with executions because the \watch
documentation says so.

+ HELP0("  WATCH_INTERVAL\n"
+  "    number of seconds \\watch waits between queries\n");

Fair point, fixed.

The attached v4 fixes the above and also adds tests.

--
Daniel Gustafsson

Attachments:

v4-0001-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v4-0001-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From 154073d4def6dc8f2c4715a5bdf488cef143de37 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Sun, 2 Feb 2025 19:21:24 +0100
Subject: [PATCH v4] Make default \watch interval configurable

The default interval which \watch waits between executing queries
was hardcoded to two seconds. This adds the variable WATCH_INTERVAL
which is used to set the default, making it configurable for the
user.

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>
Discussion: https://postgr.es/m/B2FD26B4-8F64-4552-A603-5CC3DF1C7103@yesql.se
---
 doc/src/sgml/ref/psql-ref.sgml | 16 +++++++--
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 ++
 src/bin/psql/settings.h        |  3 ++
 src/bin/psql/startup.c         | 17 +++++++++
 src/bin/psql/t/001_basic.pl    | 18 ++++++++++
 src/bin/psql/variables.c       | 63 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 8 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index f3044fac1fa..e23cd6170f7 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3777,8 +3777,9 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows. Wait the specified number of seconds (default 2 if omitted, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4641,6 +4642,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 6c75c8da6da..895b24dc213 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2939,7 +2939,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index da8e1ade5df..00cbbde8c38 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -452,6 +452,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 waits between executing the query buffer\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 2a8fe12eb55..b843489de4a 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,8 @@
 #define DEFAULT_PROMPT2 "%/%R%x%# "
 #define DEFAULT_PROMPT3 ">> "
 
+#define DEFAULT_WATCH_INTERVAL "2"
+
 /*
  * Note: these enums should generally be chosen so that zero corresponds
  * to the default behavior.
@@ -154,6 +156,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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 703f3f582c1..0c25b512872 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -939,6 +939,20 @@ 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);
+}
+
 static char *
 ignoreeof_substitute_hook(char *newval)
 {
@@ -1265,4 +1279,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);
 }
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 3170bc86856..2c8786dbe94 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -425,6 +425,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";
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 59956028918..4cf24871119 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -7,6 +7,8 @@
  */
 #include "postgres_fe.h"
 
+#include <math.h>
+
 #include "common.h"
 #include "common/logging.h"
 #include "variables.h"
@@ -179,6 +181,67 @@ 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.
+ *
+ * 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)
+{
+	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 at least %.2f",
+							 value, name, min);
+			return false;
+		}
+		*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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index a95bc29f407..26a0b360d86 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
+
 void		PrintVariables(VariableSpace space);
 
 bool		SetVariable(VariableSpace space, const char *name, const char *value);
-- 
2.39.3 (Apple Git-146)

#12Daniel Gustafsson
daniel@yesql.se
In reply to: Daniel Gustafsson (#11)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 2 Feb 2025, at 19:25, Daniel Gustafsson <daniel@yesql.se> wrote:

Attached is rebase with some small levels of polish, unless objected to I would
like to go ahead with this version.

--
Daniel Gustafsson

Attachments:

v5-0001-psql-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v5-0001-psql-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From c260f7e0ad6dfea50a34e168e73ba50392da4313 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 14 Feb 2025 15:11:23 +0100
Subject: [PATCH v5] psql: Make default \watch interval configurable

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.

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>
Discussion: https://postgr.es/m/B2FD26B4-8F64-4552-A603-5CC3DF1C7103@yesql.se
---
 doc/src/sgml/ref/psql-ref.sgml | 16 +++++++-
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 +
 src/bin/psql/settings.h        |  7 ++++
 src/bin/psql/startup.c         | 18 +++++++++
 src/bin/psql/t/001_basic.pl    | 18 +++++++++
 src/bin/psql/variables.c       | 69 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 8 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index f3044fac1fa..73abbc5cf56 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3777,8 +3777,9 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows.  Wait the specified number of seconds (defaults to 2 if omitted, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4641,6 +4642,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 26dfdde195a..27b2f7d47f4 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2940,7 +2940,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index da8e1ade5df..00cbbde8c38 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -452,6 +452,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 waits between executing the query buffer\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 2a8fe12eb55..2f0b064cf5d 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,12 @@
 #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.
@@ -154,6 +160,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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 703f3f582c1..d41214908d1 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -939,6 +939,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)
 {
@@ -1265,4 +1280,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);
 }
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 3170bc86856..2c8786dbe94 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -425,6 +425,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";
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 59956028918..4be7a3f4980 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -7,6 +7,8 @@
  */
 #include "postgres_fe.h"
 
+#include <math.h>
+
 #include "common.h"
 #include "common/logging.h"
 #include "variables.h"
@@ -179,6 +181,73 @@ 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.
+ *
+ * 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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index a95bc29f407..df23ccb987d 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
-- 
2.39.3 (Apple Git-146)

#13Laurenz Albe
laurenz.albe@cybertec.at
In reply to: Daniel Gustafsson (#12)
Re: Allow default \watch interval in psql to be configured

On Fri, 2025-02-14 at 16:15 +0100, Daniel Gustafsson wrote:

On 2 Feb 2025, at 19:25, Daniel Gustafsson <daniel@yesql.se> wrote:

Attached is rebase with some small levels of polish, unless objected to I would
like to go ahead with this version.

I gave the patch a try, and it works as expected.
The code looks good to me.

Yours,
Laurenz Albe

#14Greg Sabino Mullane
htamfids@gmail.com
In reply to: Daniel Gustafsson (#12)
Re: Allow default \watch interval in psql to be configured

Patch looks good. One minor issue:

greg=# \set WATCH_INTERVAL -1
invalid value "-1" for "WATCH_INTERVAL": must be greater than 0.00
greg=# \set WATCH_INTERVAL 0.00
greg=#

We should disallow 0 as the error message implies

I've long wanted to be able to set the default interval for \watch in psql

since I almost never want a 2 second wait.

Curious what other's personal defaults are? I usually use 1 second or 0.5
depending on things.

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

#15Daniel Gustafsson
daniel@yesql.se
In reply to: Greg Sabino Mullane (#14)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 13 Mar 2025, at 15:03, Greg Sabino Mullane <htamfids@gmail.com> wrote:

Patch looks good. One minor issue:

greg=# \set WATCH_INTERVAL -1
invalid value "-1" for "WATCH_INTERVAL": must be greater than 0.00
greg=# \set WATCH_INTERVAL 0.00
greg=#

We should disallow 0 as the error message implies

Ah, nice catch, fixed in the attached along with a test for the minimum bound (ie zero).

I've long wanted to be able to set the default interval for \watch in psql since I almost never want a 2 second wait.

Curious what other's personal defaults are? I usually use 1 second or 0.5 depending on things.

I rarely use anything higher than 0.5.

--
Daniel Gustafsson

Attachments:

v6-0001-psql-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v6-0001-psql-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From ef148b73a62160b4205cb1eb7485d52a4a8f4d35 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 14 Feb 2025 15:11:23 +0100
Subject: [PATCH v6] psql: Make default \watch interval configurable

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.

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>
Discussion: https://postgr.es/m/B2FD26B4-8F64-4552-A603-5CC3DF1C7103@yesql.se
---
 doc/src/sgml/ref/psql-ref.sgml | 16 +++++++-
 src/bin/psql/command.c         |  2 +-
 src/bin/psql/help.c            |  2 +
 src/bin/psql/settings.h        |  7 ++++
 src/bin/psql/startup.c         | 18 +++++++++
 src/bin/psql/t/001_basic.pl    | 23 +++++++++++
 src/bin/psql/variables.c       | 70 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 8 files changed, 138 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index cedccc14129..d73207fa9d3 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3851,8 +3851,9 @@ SELECT 1 \bind \g
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows.  Wait the specified number of seconds (defaults to 2 if omitted, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4748,6 +4749,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index fb0b27568c5..17ea6871346 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3239,7 +3239,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;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 714b8619233..1ad298f4cdb 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -459,6 +459,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 waits between executing the query buffer\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 71f553c22ad..fd82303f776 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,12 @@
 #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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 5018eedf1e5..249b6aa5169 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -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);
 }
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 3170bc86856..baffe95c8a7 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -425,6 +425,29 @@ 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');
+psql_fails_like(
+	$node,
+	'\set WATCH_INTERVAL 0',
+	qr/must be greater than 0\.00/,
+	'WATCH_INTERVAL variable is zero');
+
 # 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";
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 59956028918..517159bc13c 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index a95bc29f407..df23ccb987d 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
-- 
2.39.3 (Apple Git-146)

#16Greg Sabino Mullane
htamfids@gmail.com
In reply to: Daniel Gustafsson (#15)
Re: Allow default \watch interval in psql to be configured

New patch looks good. TIL I learned you can even use things like

\set WATCH_INTERVAL 0xAp-4

:)

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

#17Michael Paquier
michael@paquier.xyz
In reply to: Greg Sabino Mullane (#16)
Re: Allow default \watch interval in psql to be configured

On Fri, Mar 14, 2025 at 09:11:15AM -0400, Greg Sabino Mullane wrote:

New patch looks good. TIL I learned you can even use things like

\set WATCH_INTERVAL 0xAp-4

:)

You have taste.
--
Michael

#18Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Daniel Gustafsson (#15)
Re: Allow default \watch interval in psql to be configured

Hi Daniel,

On Fri, Mar 14, 2025 at 2:26 PM Daniel Gustafsson <daniel@yesql.se> wrote:

On 13 Mar 2025, at 15:03, Greg Sabino Mullane <htamfids@gmail.com> wrote:

Patch looks good. One minor issue:

greg=# \set WATCH_INTERVAL -1
invalid value "-1" for "WATCH_INTERVAL": must be greater than 0.00
greg=# \set WATCH_INTERVAL 0.00
greg=#

We should disallow 0 as the error message implies

Ah, nice catch, fixed in the attached along with a test for the minimum bound (ie zero).

#\watch c=4 i=
Mon 17 Mar 2025 05:52:50 PM IST (every 0s)

?column?
----------
1
(1 row)

Mon 17 Mar 2025 05:52:50 PM IST (every 0s)

?column?
----------
1
(1 row)

Mon 17 Mar 2025 05:52:50 PM IST (every 0s)

?column?
----------
1
(1 row)

Mon 17 Mar 2025 05:52:50 PM IST (every 0s)

?column?
----------
1
(1 row)

0 is an accepted value for interval, even though it might look insensible.

The behaviour should be same in both cases \watch i=<some value> and
\set WATCH_INTERVAL <some value> \watch. In this case it's not. In
fact, we should use the same validation code in both the cases. Why
don't we perform the same additional validations in
ParseVariableDouble() in exec_watch_command() as well?

The test only validate default variable value. We need a test where we
see variable value being honored lie tests between 369 to 376 in the
same file.

--
Best Wishes,
Ashutosh Bapat

#19Daniel Gustafsson
daniel@yesql.se
In reply to: Ashutosh Bapat (#18)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 17 Mar 2025, at 13:37, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:

0 is an accepted value for interval, even though it might look insensible.

The behaviour should be same in both cases \watch i=<some value> and
\set WATCH_INTERVAL <some value> \watch. In this case it's not.

Having a watch interval of zero is IMHO somewhat nonsensical, but since it was
done intentionally in 6f9ee74d45 (which I had missed) I agree that the default
should support it as well. Fixed.

In fact, we should use the same validation code in both the cases. Why
don't we perform the same additional validations in
ParseVariableDouble() in exec_watch_command() as well?

Well, they don't use the same code since they are two different things
(variables and command input, while using the same syscalls they have different
errorhandling requirements). If executing \watch would use ParseVariableDouble
it would make errorhandling quite awkward at best. I added a comment in
exec_command_watch that any changes in internval parsing should take default
intervals into consideration.

The test only validate default variable value. We need a test where we
see variable value being honored lie tests between 369 to 376 in the
same file.

I'm not sure it's worth spending test cycles on as this code doesn't affect the
execution of \watch at all. That being said, I added a testcase which sets a
default and then executes \watch. It doesn't test that the interval was
correct (we don't do that for any), but at least it will catch if setting a
default totally breaks \watch.

--
Daniel Gustafsson

Attachments:

v7-0001-psql-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v7-0001-psql-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From 77a162f968c36e18890d0f845a8ebbb501769c10 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 14 Feb 2025 15:11:23 +0100
Subject: [PATCH v7] psql: Make default \watch interval configurable

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.

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 | 16 +++++++-
 src/bin/psql/command.c         |  6 ++-
 src/bin/psql/help.c            |  2 +
 src/bin/psql/settings.h        |  7 ++++
 src/bin/psql/startup.c         | 18 +++++++++
 src/bin/psql/t/001_basic.pl    | 24 ++++++++++++
 src/bin/psql/variables.c       | 70 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 8 files changed, 142 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index f083dba49a9..5ead1c784a0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3851,8 +3851,9 @@ SELECT 1 \bind \sendpipeline
         Repeatedly execute the current query buffer (as <literal>\g</literal> does)
         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.
-        For backwards compatibility,
+        of rows.  Wait the specified number of seconds (defaults to 2 if omitted, which can be
+        changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+        between executions.  For backwards compatibility,
         <replaceable class="parameter">seconds</replaceable> can be specified
         with or without an <literal>interval=</literal> prefix.
         Each query result is
@@ -4746,6 +4747,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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index bbe337780ff..015ece77aec 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -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)
 		{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index e47cad24de9..30e3792d263 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -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 waits between executing the query buffer\n");
 
 	HELP0("\nDisplay settings:\n");
 	HELP0("Usage:\n");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 71f553c22ad..fd82303f776 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,12 @@
 #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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 5018eedf1e5..249b6aa5169 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -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);
 }
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index dca34ac975a..7192d96049d 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -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";
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 59956028918..5150eb0532b 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index a95bc29f407..df23ccb987d 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
-- 
2.39.3 (Apple Git-146)

#20Greg Sabino Mullane
htamfids@gmail.com
In reply to: Daniel Gustafsson (#19)
Re: Allow default \watch interval in psql to be configured

On Thu, Mar 20, 2025 at 4:45 PM Daniel Gustafsson <daniel@yesql.se> wrote:

Having a watch interval of zero is IMHO somewhat nonsensical, but since it
was done intentionally in 6f9ee74d45 (which I had missed) I agree that the
default should support it as well. Fixed.

Yeah, I forgot about that too. The new patch looks good except for one
tiny little thing: the error should say "must be at least 0.00" or similar,
instead of "must be greater than 0.00" now that we allow 0.00

Cheers,
Greg

--
Crunchy Data - https://www.crunchydata.com
Enterprise Postgres Software Products & Tech Support

#21Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Daniel Gustafsson (#19)
Re: Allow default \watch interval in psql to be configured

On Fri, Mar 21, 2025 at 2:15 AM Daniel Gustafsson <daniel@yesql.se> wrote:

On 17 Mar 2025, at 13:37, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:

0 is an accepted value for interval, even though it might look insensible.

The behaviour should be same in both cases \watch i=<some value> and
\set WATCH_INTERVAL <some value> \watch. In this case it's not.

Having a watch interval of zero is IMHO somewhat nonsensical, but since it was
done intentionally in 6f9ee74d45 (which I had missed) I agree that the default
should support it as well. Fixed.

In fact, we should use the same validation code in both the cases. Why
don't we perform the same additional validations in
ParseVariableDouble() in exec_watch_command() as well?

Well, they don't use the same code since they are two different things
(variables and command input, while using the same syscalls they have different
errorhandling requirements). If executing \watch would use ParseVariableDouble
it would make errorhandling quite awkward at best. I added a comment in
exec_command_watch that any changes in internval parsing should take default
intervals into consideration.

There are following differences between command input parsing and
variable value parsing
1. empty string is considered as 0 value while parsing command input
whereas it wiil cause error when setting to a variable.
#\watch c=1 i=
Fri 21 Mar 2025 08:27:25 AM IST (every 0s)

?column?
----------
1
(1 row)
#\set WATCH_INTERVAL
invalid input syntax for "WATCH_INTERVAL"

That can be considered as an existing bug and maybe fixed later.

2. The maximum value that can be specified as command input is limited
by what strtod can handle but for variable it's 1000000 which is 15
days. I can't imagine someone would want to set default value higher
than that but we need to be prepared to change it if a request comes.
Or increase it to a much higher value like seconds worth 100 years.

3. ParseVariableDouble() gives better error message when strtod() can
not handle the input string by looking at the output as well as errno.
But exec_command_watch() lacks that finesse.

ParseVariableDouble is better at parsing the input string than
exec_command_watch(). But that's something that should have been
tackled when exec_command_watch()'s parsing code was written instead
of ParseVariableDouble().

The test only validate default variable value. We need a test where we
see variable value being honored lie tests between 369 to 376 in the
same file.

I'm not sure it's worth spending test cycles on as this code doesn't affect the
execution of \watch at all. That being said, I added a testcase which sets a
default and then executes \watch. It doesn't test that the interval was
correct (we don't do that for any), but at least it will catch if setting a
default totally breaks \watch.

This looks ok. We do both test 0 as well as the variable.

With this patch, we are doing something unprecedented (at least
AFAIK); allowing command arguments defaults to be configurable through
a psql variable (as against an environment variable). I admit that
configurable through a psql variable is better since it doesn't meddle
with environment. Glancing through psql documentation, I didn't find a
lot of command which may need default argument to be configurable.
Nonetheless we should mention why this is special and set some
guidance for such future additions - preferrably in code or at least
in the commit message.

- of rows. Wait the specified number of seconds (default 2) between executions.
- For backwards compatibility,
+ of rows. Wait the specified number of seconds (defaults to 2 if
omitted, which can be
+ changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+ between executions. For backwards compatibility,

The text in parenthesis is quite long and it's hard to read ...
seconds between execution. I suggest
"Wait the specified number of seconds (default 2) between executions.
The default wait can be changed by setting the variable <xref
linkend="app-psql-variables-watch-interval"/>."

+ " number of seconds \\watch waits between executing the query buffer\n");

I am feeling that this should mention "default" somewhere - maybe just
add it before "number of ".

--
Best Wishes,
Ashutosh Bapat

#22Daniel Gustafsson
daniel@yesql.se
In reply to: Ashutosh Bapat (#21)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

On 21 Mar 2025, at 11:34, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:

There are following differences between command input parsing and
variable value parsing
1. empty string is considered as 0 value while parsing command input
whereas it wiil cause error when setting to a variable.
#\watch c=1 i=
Fri 21 Mar 2025 08:27:25 AM IST (every 0s)

?column?
----------
1
(1 row)
#\set WATCH_INTERVAL
invalid input syntax for "WATCH_INTERVAL"

That can be considered as an existing bug and maybe fixed later.

An empty interval in command parsing implies "use the default", I don't think
there is a sensical counterpart in parsing actually setting the default value.
I think trying to define the default value without providing a value is an
error condition.

With this patch, we are doing something unprecedented (at least
AFAIK); allowing command arguments defaults to be configurable through
a psql variable (as against an environment variable). I admit that
configurable through a psql variable is better since it doesn't meddle
with environment. Glancing through psql documentation, I didn't find a
lot of command which may need default argument to be configurable.
Nonetheless we should mention why this is special and set some
guidance for such future additions - preferrably in code or at least
in the commit message.

Sure, I'll mention it in the commit message.

- of rows. Wait the specified number of seconds (default 2) between executions.
- For backwards compatibility,
+ of rows. Wait the specified number of seconds (defaults to 2 if
omitted, which can be
+ changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+ between executions. For backwards compatibility,

The text in parenthesis is quite long and it's hard to read ...
seconds between execution. I suggest
"Wait the specified number of seconds (default 2) between executions.
The default wait can be changed by setting the variable <xref
linkend="app-psql-variables-watch-interval"/>."

Fixed.

+ " number of seconds \\watch waits between executing the query buffer\n");

I am feeling that this should mention "default" somewhere - maybe just
add it before "number of ".

Fixed.

--
Daniel Gustafsson

Attachments:

v8-0001-psql-Make-default-watch-interval-configurable.patchapplication/octet-stream; name=v8-0001-psql-Make-default-watch-interval-configurable.patch; x-unix-mode=0644Download
From 7e6e470b95c21b9565e0fa36c62de466f945c28f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 14 Feb 2025 15:11:23 +0100
Subject: [PATCH v8] psql: Make default \watch interval configurable

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.

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 | 13 +++++++
 src/bin/psql/command.c         |  6 ++-
 src/bin/psql/help.c            |  2 +
 src/bin/psql/settings.h        |  7 ++++
 src/bin/psql/startup.c         | 18 +++++++++
 src/bin/psql/t/001_basic.pl    | 24 ++++++++++++
 src/bin/psql/variables.c       | 70 ++++++++++++++++++++++++++++++++++
 src/bin/psql/variables.h       |  3 ++
 8 files changed, 141 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index f083dba49a9..f7c8bc16a7f 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index bbe337780ff..015ece77aec 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -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)
 		{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index e47cad24de9..fe96e3e1de9 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -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");
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 71f553c22ad..fd82303f776 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,12 @@
 #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;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 5018eedf1e5..249b6aa5169 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -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);
 }
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index dca34ac975a..7192d96049d 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -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";
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 59956028918..5150eb0532b 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -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.
  */
diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h
index a95bc29f407..df23ccb987d 100644
--- a/src/bin/psql/variables.h
+++ b/src/bin/psql/variables.h
@@ -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);
-- 
2.39.3 (Apple Git-146)

#23Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Daniel Gustafsson (#22)
Re: Allow default \watch interval in psql to be configured

On Mon, Mar 24, 2025 at 5:40 PM Daniel Gustafsson <daniel@yesql.se> wrote:

With this patch, we are doing something unprecedented (at least
AFAIK); allowing command arguments defaults to be configurable through
a psql variable (as against an environment variable). I admit that
configurable through a psql variable is better since it doesn't meddle
with environment. Glancing through psql documentation, I didn't find a
lot of command which may need default argument to be configurable.
Nonetheless we should mention why this is special and set some
guidance for such future additions - preferrably in code or at least
in the commit message.

Sure, I'll mention it in the commit message.

- of rows. Wait the specified number of seconds (default 2) between executions.
- For backwards compatibility,
+ of rows. Wait the specified number of seconds (defaults to 2 if
omitted, which can be
+ changed with the variable <xref linkend="app-psql-variables-watch-interval"/>)
+ between executions. For backwards compatibility,

The text in parenthesis is quite long and it's hard to read ...
seconds between execution. I suggest
"Wait the specified number of seconds (default 2) between executions.
The default wait can be changed by setting the variable <xref
linkend="app-psql-variables-watch-interval"/>."

Fixed.

+ " number of seconds \\watch waits between executing the query buffer\n");

I am feeling that this should mention "default" somewhere - maybe just
add it before "number of ".

Fixed.

LGTM. I think this is RFC. Updated CF entry.

--
Best Wishes,
Ashutosh Bapat

#24Daniel Gustafsson
daniel@yesql.se
In reply to: Ashutosh Bapat (#23)
Re: Allow default \watch interval in psql to be configured

On 24 Mar 2025, at 13:42, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:

LGTM. I think this is RFC. Updated CF entry.

Thanks all for review, committed.

--
Daniel Gustafsson

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Daniel Gustafsson (#24)
Re: Allow default \watch interval in psql to be configured

Hi

út 25. 3. 2025 v 20:09 odesílatel Daniel Gustafsson <daniel@yesql.se>
napsal:

On 24 Mar 2025, at 13:42, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>

wrote:

LGTM. I think this is RFC. Updated CF entry.

Thanks all for review, committed.

regress tests fails now in my

make[2]: Vstupuje se do adresáře „/home/pavel/src/postgresql/src/bin/psql“
echo "# +++ tap check in src/bin/psql +++" && rm -rf
'/home/pavel/src/postgresql/src/bin/psql'/tmp_check && /usr/bin/mkdir -p
'/home/pavel/src/postgresql/src/bin/psql'/tmp_check && cd . &&
TESTLOGDIR='/home/pavel/src/postgresql/src/bin/psql/tmp_check/log'
TESTDATADIR='/home/pavel/src/postgresql/src/bin/psql/tmp_check'
PATH="/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/bin:/home/pavel/src/postgresql/src/bin/psql:$PATH"
LD_LIBRARY_PATH="/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/lib"
INITDB_TEMPLATE='/home/pavel/src/postgresql'/tmp_install/initdb-template
PGPORT='65432'
top_builddir='/home/pavel/src/postgresql/src/bin/psql/../../..'
PG_REGRESS='/home/pavel/src/postgresql/src/bin/psql/../../../src/test/regress/pg_regress'
share_contrib_dir='/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/share/'
/usr/bin/prove -I ../../../src/test/perl/ -I . t/*.pl
# +++ tap check in src/bin/psql +++
t/001_basic.pl ........... 46/?
# Failed test 'WATCH_INTERVAL variable is set and updated: exit code 0'
# at t/001_basic.pl line 436.
# got: '3'
# expected: '0'

# Failed test 'WATCH_INTERVAL variable is set and updated: no stderr'
# at t/001_basic.pl line 436.
# got: 'psql:<stdin>:2: error: "0.001" is out of range for
"WATCH_INTERVAL"'
# expected: ''

# Failed test 'WATCH_INTERVAL variable is set and updated: matches'
# at t/001_basic.pl line 436.
# '2'
# doesn't match '(?^lm:^2$
# ^0.001$
# ^2$)'
# Looks like you failed 3 tests of 116.
t/001_basic.pl ........... Dubious, test returned 3 (wstat 768, 0x300)
Failed 3/116 subtests
t/010_tab_completion.pl .. ok
t/020_cancel.pl .......... ok

Test Summary Report
-------------------
t/001_basic.pl (Wstat: 768 (exited 3) Tests: 116 Failed: 3)
Failed tests: 95-97
Non-zero exit status: 3
Files=3, Tests=207, 7 wallclock secs ( 0.12 usr 0.02 sys + 3.38 cusr
1.72 csys = 5.24 CPU)
Result: FAIL

The reason is probably my LANG=cs_CZ.UTF8. When I switched to LANG=C, then
tests passed

Regards

Pavel

Show quoted text

--
Daniel Gustafsson

#26Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#25)
Re: Allow default \watch interval in psql to be configured

st 26. 3. 2025 v 7:59 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

Hi

út 25. 3. 2025 v 20:09 odesílatel Daniel Gustafsson <daniel@yesql.se>
napsal:

On 24 Mar 2025, at 13:42, Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>

wrote:

LGTM. I think this is RFC. Updated CF entry.

Thanks all for review, committed.

regress tests fails now in my

make[2]: Vstupuje se do adresáře „/home/pavel/src/postgresql/src/bin/psql“
echo "# +++ tap check in src/bin/psql +++" && rm -rf
'/home/pavel/src/postgresql/src/bin/psql'/tmp_check && /usr/bin/mkdir -p
'/home/pavel/src/postgresql/src/bin/psql'/tmp_check && cd . &&
TESTLOGDIR='/home/pavel/src/postgresql/src/bin/psql/tmp_check/log'
TESTDATADIR='/home/pavel/src/postgresql/src/bin/psql/tmp_check'
PATH="/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/bin:/home/pavel/src/postgresql/src/bin/psql:$PATH"
LD_LIBRARY_PATH="/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/lib"
INITDB_TEMPLATE='/home/pavel/src/postgresql'/tmp_install/initdb-template
PGPORT='65432'
top_builddir='/home/pavel/src/postgresql/src/bin/psql/../../..'
PG_REGRESS='/home/pavel/src/postgresql/src/bin/psql/../../../src/test/regress/pg_regress'
share_contrib_dir='/home/pavel/src/postgresql/tmp_install/usr/local/pgsql/master/share/'
/usr/bin/prove -I ../../../src/test/perl/ -I . t/*.pl
# +++ tap check in src/bin/psql +++
t/001_basic.pl ........... 46/?
# Failed test 'WATCH_INTERVAL variable is set and updated: exit code 0'
# at t/001_basic.pl line 436.
# got: '3'
# expected: '0'

# Failed test 'WATCH_INTERVAL variable is set and updated: no stderr'
# at t/001_basic.pl line 436.
# got: 'psql:<stdin>:2: error: "0.001" is out of range for
"WATCH_INTERVAL"'
# expected: ''

# Failed test 'WATCH_INTERVAL variable is set and updated: matches'
# at t/001_basic.pl line 436.
# '2'
# doesn't match '(?^lm:^2$
# ^0.001$
# ^2$)'
# Looks like you failed 3 tests of 116.
t/001_basic.pl ........... Dubious, test returned 3 (wstat 768, 0x300)
Failed 3/116 subtests
t/010_tab_completion.pl .. ok
t/020_cancel.pl .......... ok

Test Summary Report
-------------------
t/001_basic.pl (Wstat: 768 (exited 3) Tests: 116 Failed: 3)
Failed tests: 95-97
Non-zero exit status: 3
Files=3, Tests=207, 7 wallclock secs ( 0.12 usr 0.02 sys + 3.38 cusr
1.72 csys = 5.24 CPU)
Result: FAIL

The reason is probably my LANG=cs_CZ.UTF8. When I switched to LANG=C, then
tests passed.

The main problem is in numeric format. Czech uses the comma instead of the
dot.

pavel@nemesis:~/src/postgresql/src/bin/psql$ LANG=C psql -c "\set
WATCH_INTERVAL 0.001"
pavel@nemesis:~/src/postgresql/src/bin/psql$ LANG=cs_CZ.UTF8 psql -c "\set
WATCH_INTERVAL 0.001"
"0.001" is out of range for "WATCH_INTERVAL"
pavel@nemesis:~/src/postgresql/src/bin/psql$ LANG=cs_CZ.UTF8 psql -c "\set
WATCH_INTERVAL 0,001"

Regards

Pavel

Show quoted text

Regards

Pavel

--
Daniel Gustafsson

#27Daniel Gustafsson
daniel@yesql.se
In reply to: Pavel Stehule (#26)
Re: Allow default \watch interval in psql to be configured

On 26 Mar 2025, at 08:42, Pavel Stehule <pavel.stehule@gmail.com> wrote:

The reason is probably my LANG=cs_CZ.UTF8. When I switched to LANG=C, then tests passed.

The main problem is in numeric format. Czech uses the comma instead of the dot.

Thanks for investigating! The main value of the test is to test setting value
and unsetting it again, so we could just as well use an integer value like the
diff below. Does it pass for you with that instead?

diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 7192d96049d..739cb439708 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -436,12 +436,12 @@ psql_fails_like(
 psql_like(
        $node,
        '\echo :WATCH_INTERVAL
-\set WATCH_INTERVAL 0.001
+\set WATCH_INTERVAL 10
 \echo :WATCH_INTERVAL
 \unset WATCH_INTERVAL
 \echo :WATCH_INTERVAL',
        qr/^2$
-^0.001$
+^10$
 ^2$/m,
        'WATCH_INTERVAL variable is set and updated');
 psql_fails_like(

--
Daniel Gustafsson

#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Daniel Gustafsson (#27)
Re: Allow default \watch interval in psql to be configured

st 26. 3. 2025 v 9:05 odesílatel Daniel Gustafsson <daniel@yesql.se> napsal:

On 26 Mar 2025, at 08:42, Pavel Stehule <pavel.stehule@gmail.com> wrote:

The reason is probably my LANG=cs_CZ.UTF8. When I switched to LANG=C,

then tests passed.

The main problem is in numeric format. Czech uses the comma instead of

the dot.

Thanks for investigating! The main value of the test is to test setting
value
and unsetting it again, so we could just as well use an integer value like
the
diff below. Does it pass for you with that instead?

diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 7192d96049d..739cb439708 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -436,12 +436,12 @@ psql_fails_like(
psql_like(
$node,
'\echo :WATCH_INTERVAL
-\set WATCH_INTERVAL 0.001
+\set WATCH_INTERVAL 10
\echo :WATCH_INTERVAL
\unset WATCH_INTERVAL
\echo :WATCH_INTERVAL',
qr/^2$
-^0.001$
+^10$
^2$/m,
'WATCH_INTERVAL variable is set and updated');
psql_fails_like(

--
Daniel Gustafsson

yes, it is ok after this change

Pavel

#29Daniel Gustafsson
daniel@yesql.se
In reply to: Pavel Stehule (#28)
Re: Allow default \watch interval in psql to be configured

On 26 Mar 2025, at 10:12, Pavel Stehule <pavel.stehule@gmail.com> wrote:

yes, it is ok after this change

Thanks for confirming, committed.

--
Daniel Gustafsson

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Daniel Gustafsson (#29)
Re: Allow default \watch interval in psql to be configured

st 26. 3. 2025 v 13:24 odesílatel Daniel Gustafsson <daniel@yesql.se>
napsal:

On 26 Mar 2025, at 10:12, Pavel Stehule <pavel.stehule@gmail.com> wrote:

yes, it is ok after this change

Thanks for confirming, committed.

Thank you

Pavel

Show quoted text

--
Daniel Gustafsson

#31Ranier Vilela
ranier.vf@gmail.com
In reply to: Daniel Gustafsson (#29)
1 attachment(s)
Re: Allow default \watch interval in psql to be configured

Hi.

Em qua., 26 de mar. de 2025 às 09:24, Daniel Gustafsson <daniel@yesql.se>
escreveu:

On 26 Mar 2025, at 10:12, Pavel Stehule <pavel.stehule@gmail.com> wrote:

yes, it is ok after this change

Thanks for confirming, committed.

Per Coverity.

I think that commit 1a759c8
<http://1a759c83278fcdae11ee5da8318b646b9d47129c&gt;
left a minor oversight.

Typo comparison?

a trivial fix attached.

best regards,
Ranier Vilela

Attachments:

fix-typo-comparison-variables.patchapplication/octet-stream; name=fix-typo-comparison-variables.patchDownload
diff --git a/doc/src/sgml/ref/reindexdb.sgml b/doc/src/sgml/ref/reindexdb.sgml
index 98c3333228..a877439dc5 100644
--- a/doc/src/sgml/ref/reindexdb.sgml
+++ b/doc/src/sgml/ref/reindexdb.sgml
@@ -179,7 +179,8 @@ PostgreSQL documentation
         setting is high enough to accommodate all connections.
        </para>
        <para>
-        Note that this option is incompatible with the <option>--system</option> option.
+        Note that this option is incompatible with the <option>--index</option>
+        and <option>--system</option> options.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 9ce628e62a..2ee7679cce 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -1353,7 +1353,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
 		}
 
 		/* Do nothing if slot is unused */
-		if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
+		if (shared->page_status[slotno] != SLRU_PAGE_VALID)
 			continue;
 
 		SlruInternalWritePage(ctl, slotno, &fdata);
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index 9a39451a29..14dc9b116c 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -125,50 +125,26 @@ TransactionLogFetch(TransactionId transactionId)
 bool							/* true if given transaction committed */
 TransactionIdDidCommit(TransactionId transactionId)
 {
-	XidStatus	xidstatus;
-
-	xidstatus = TransactionLogFetch(transactionId);
-
-	/*
-	 * If it's marked committed, it's committed.
-	 */
-	if (xidstatus == TRANSACTION_STATUS_COMMITTED)
-		return true;
-
-	/*
-	 * If it's marked subcommitted, we have to check the parent recursively.
-	 * However, if it's older than TransactionXmin, we can't look at
-	 * pg_subtrans; instead assume that the parent crashed without cleaning up
-	 * its children.
-	 *
-	 * Originally we Assert'ed that the result of SubTransGetParent was not
-	 * zero. However with the introduction of prepared transactions, there can
-	 * be a window just after database startup where we do not have complete
-	 * knowledge in pg_subtrans of the transactions after TransactionXmin.
-	 * StartupSUBTRANS() has ensured that any missing information will be
-	 * zeroed.  Since this case should not happen under normal conditions, it
-	 * seems reasonable to emit a WARNING for it.
-	 */
-	if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
-	{
-		TransactionId parentXid;
-
-		if (TransactionIdPrecedes(transactionId, TransactionXmin))
-			return false;
-		parentXid = SubTransGetParent(transactionId);
-		if (!TransactionIdIsValid(parentXid))
-		{
-			elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
-				 transactionId);
-			return false;
-		}
-		return TransactionIdDidCommit(parentXid);
-	}
-
-	/*
-	 * It's not committed.
-	 */
-	return false;
+    XidStatus xidstatus;
+    
+    while (true) {
+        xidstatus = TransactionLogFetch(transactionId);
+        
+        if (xidstatus == TRANSACTION_STATUS_COMMITTED)
+            return true;
+        
+        if (xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
+            return false;
+        
+        if (unlikely(TransactionIdPrecedes(transactionId, TransactionXmin)))
+            return false;
+        
+        transactionId = SubTransGetParent(transactionId);
+        if (unlikely(!TransactionIdIsValid(transactionId))) {
+            elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId);
+            return false;
+        }
+    }
 }
 
 /*
@@ -187,43 +163,26 @@ TransactionIdDidCommit(TransactionId transactionId)
 bool							/* true if given transaction aborted */
 TransactionIdDidAbort(TransactionId transactionId)
 {
-	XidStatus	xidstatus;
-
-	xidstatus = TransactionLogFetch(transactionId);
-
-	/*
-	 * If it's marked aborted, it's aborted.
-	 */
-	if (xidstatus == TRANSACTION_STATUS_ABORTED)
-		return true;
-
-	/*
-	 * If it's marked subcommitted, we have to check the parent recursively.
-	 * However, if it's older than TransactionXmin, we can't look at
-	 * pg_subtrans; instead assume that the parent crashed without cleaning up
-	 * its children.
-	 */
-	if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
-	{
-		TransactionId parentXid;
-
-		if (TransactionIdPrecedes(transactionId, TransactionXmin))
-			return true;
-		parentXid = SubTransGetParent(transactionId);
-		if (!TransactionIdIsValid(parentXid))
-		{
-			/* see notes in TransactionIdDidCommit */
-			elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
-				 transactionId);
-			return true;
-		}
-		return TransactionIdDidAbort(parentXid);
-	}
-
-	/*
-	 * It's not aborted.
-	 */
-	return false;
+    XidStatus xidstatus;
+
+    while (true) {
+        xidstatus = TransactionLogFetch(transactionId);
+        
+        if (xidstatus == TRANSACTION_STATUS_ABORTED)
+            return true;
+        
+        if (xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
+            return false;
+        
+        if (unlikely(TransactionIdPrecedes(transactionId, TransactionXmin)))
+            return true;
+        
+        transactionId = SubTransGetParent(transactionId);
+        if (unlikely(!TransactionIdIsValid(transactionId))) {
+            elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId);
+            return true;
+        }
+    }
 }
 
 /*
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 54a08e4102..17f2e0665a 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -949,7 +949,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	 * tells us it's cheaper.  Otherwise, always indexscan if an index is
 	 * provided, else plain seqscan.
 	 */
-	if (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)
+	if (OldIndex != NULL)
 		use_sort = plan_cluster_use_sort(RelationGetRelid(OldHeap),
 										 RelationGetRelid(OldIndex));
 	else
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 391b34a2af..5803b404a6 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -738,14 +738,14 @@ ExplainPrintSettings(ExplainState *es)
 
 		for (int i = 0; i < num; i++)
 		{
-			char	   *setting;
 			struct config_generic *conf = gucs[i];
+			char	   *setting;
+
+			setting = GetConfigOptionByName(conf->name, NULL, true);
 
 			if (i > 0)
 				appendStringInfoString(&str, ", ");
 
-			setting = GetConfigOptionByName(conf->name, NULL, true);
-
 			if (setting)
 				appendStringInfo(&str, "%s = '%s'", conf->name, setting);
 			else
diff --git a/src/backend/lib/dshash.c b/src/backend/lib/dshash.c
index b8d031f201..3a70c8d6ee 100644
--- a/src/backend/lib/dshash.c
+++ b/src/backend/lib/dshash.c
@@ -74,6 +74,7 @@ typedef struct dshash_partition
 {
 	LWLock		lock;			/* Protects all buckets in this partition. */
 	size_t		count;			/* # of items in this partition's buckets */
+	char		padding[PG_CACHE_LINE_SIZE - sizeof(LWLock) - sizeof(size_t)];	
 } dshash_partition;
 
 /*
@@ -656,86 +657,58 @@ dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table,
 void *
 dshash_seq_next(dshash_seq_status *status)
 {
-	dsa_pointer next_item_pointer;
-
-	/*
-	 * Not yet holding any partition locks. Need to determine the size of the
-	 * hash table, it could have been resized since we were looking last.
-	 * Since we iterate in partition order, we can start by unconditionally
-	 * lock partition 0.
-	 *
-	 * Once we hold the lock, no resizing can happen until the scan ends. So
-	 * we don't need to repeatedly call ensure_valid_bucket_pointers().
-	 */
-	if (status->curpartition == -1)
-	{
-		Assert(status->curbucket == 0);
-		ASSERT_NO_PARTITION_LOCKS_HELD_BY_ME(status->hash_table);
-
-		status->curpartition = 0;
-
-		LWLockAcquire(PARTITION_LOCK(status->hash_table,
-									 status->curpartition),
-					  status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
-
-		ensure_valid_bucket_pointers(status->hash_table);
-
-		status->nbuckets =
-			NUM_BUCKETS(status->hash_table->control->size_log2);
-		next_item_pointer = status->hash_table->buckets[status->curbucket];
-	}
-	else
-		next_item_pointer = status->pnextitem;
-
-	Assert(LWLockHeldByMeInMode(PARTITION_LOCK(status->hash_table,
-											   status->curpartition),
-								status->exclusive ? LW_EXCLUSIVE : LW_SHARED));
-
-	/* Move to the next bucket if we finished the current bucket */
-	while (!DsaPointerIsValid(next_item_pointer))
-	{
-		int			next_partition;
-
-		if (++status->curbucket >= status->nbuckets)
-		{
-			/* all buckets have been scanned. finish. */
-			return NULL;
-		}
-
-		/* Check if move to the next partition */
-		next_partition =
-			PARTITION_FOR_BUCKET_INDEX(status->curbucket,
-									   status->hash_table->size_log2);
-
-		if (status->curpartition != next_partition)
-		{
-			/*
-			 * Move to the next partition. Lock the next partition then
-			 * release the current, not in the reverse order to avoid
-			 * concurrent resizing.  Avoid dead lock by taking lock in the
-			 * same order with resize().
-			 */
-			LWLockAcquire(PARTITION_LOCK(status->hash_table,
-										 next_partition),
-						  status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
-			LWLockRelease(PARTITION_LOCK(status->hash_table,
-										 status->curpartition));
-			status->curpartition = next_partition;
-		}
-
-		next_item_pointer = status->hash_table->buckets[status->curbucket];
-	}
-
-	status->curitem =
-		dsa_get_address(status->hash_table->area, next_item_pointer);
-
-	/*
-	 * The caller may delete the item. Store the next item in case of
-	 * deletion.
-	 */
-	status->pnextitem = status->curitem->next;
-
-	return ENTRY_FROM_ITEM(status->curitem);
+    dsa_pointer next_item_pointer;
+
+    if (status->curpartition == -1)
+    {
+        /* Initialize the scan. */
+        status->curpartition = 0;
+        LWLockAcquire(PARTITION_LOCK(status->hash_table,
+                                     status->curpartition),
+                      status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
+        ensure_valid_bucket_pointers(status->hash_table);
+        status->nbuckets =
+            NUM_BUCKETS(status->hash_table->control->size_log2);
+        next_item_pointer = status->hash_table->buckets[status->curbucket];
+    }
+    else
+        next_item_pointer = status->pnextitem;
+
+    while (true)
+    {
+        /* Process the current bucket. */
+        while (DsaPointerIsValid(next_item_pointer))
+        {
+            status->curitem =
+                dsa_get_address(status->hash_table->area, next_item_pointer);
+            status->pnextitem = status->curitem->next;
+            return ENTRY_FROM_ITEM(status->curitem);
+        }
+
+        /* Move to the next bucket. */
+        if (++status->curbucket >= status->nbuckets)
+        {
+            /* All buckets have been scanned. */
+            return NULL;
+        }
+
+        /* Check if we need to switch partitions. */
+        int next_partition =
+            PARTITION_FOR_BUCKET_INDEX(status->curbucket,
+                                       status->hash_table->size_log2);
+
+        if (status->curpartition != next_partition)
+        {
+            LWLockAcquire(PARTITION_LOCK(status->hash_table,
+                                         next_partition),
+                          status->exclusive ? LW_EXCLUSIVE : LW_SHARED);
+            LWLockRelease(PARTITION_LOCK(status->hash_table,
+                                         status->curpartition));
+            status->curpartition = next_partition;
+        }
+
+        next_item_pointer = status->hash_table->buckets[status->curbucket];
+    }
 }
 
 /*
@@ -857,75 +830,65 @@ delete_item(dshash_table *hash_table, dshash_table_item *item)
 static void
 resize(dshash_table *hash_table, size_t new_size_log2)
 {
-	dsa_pointer old_buckets;
-	dsa_pointer new_buckets_shared;
-	dsa_pointer *new_buckets;
-	size_t		size;
-	size_t		new_size = ((size_t) 1) << new_size_log2;
-	size_t		i;
-
-	/*
-	 * Acquire the locks for all lock partitions.  This is expensive, but we
-	 * shouldn't have to do it many times.
-	 */
-	for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
-	{
-		Assert(!LWLockHeldByMe(PARTITION_LOCK(hash_table, i)));
-
-		LWLockAcquire(PARTITION_LOCK(hash_table, i), LW_EXCLUSIVE);
-		if (i == 0 && hash_table->control->size_log2 >= new_size_log2)
-		{
-			/*
-			 * Another backend has already increased the size; we can avoid
-			 * obtaining all the locks and return early.
-			 */
-			LWLockRelease(PARTITION_LOCK(hash_table, 0));
-			return;
-		}
-	}
-
-	Assert(new_size_log2 == hash_table->control->size_log2 + 1);
-
-	/* Allocate the space for the new table. */
-	new_buckets_shared =
-		dsa_allocate_extended(hash_table->area,
-							  sizeof(dsa_pointer) * new_size,
-							  DSA_ALLOC_HUGE | DSA_ALLOC_ZERO);
-	new_buckets = dsa_get_address(hash_table->area, new_buckets_shared);
-
-	/*
-	 * We've allocated the new bucket array; all that remains to do now is to
-	 * reinsert all items, which amounts to adjusting all the pointers.
-	 */
-	size = ((size_t) 1) << hash_table->control->size_log2;
-	for (i = 0; i < size; ++i)
-	{
-		dsa_pointer item_pointer = hash_table->buckets[i];
-
-		while (DsaPointerIsValid(item_pointer))
-		{
-			dshash_table_item *item;
-			dsa_pointer next_item_pointer;
-
-			item = dsa_get_address(hash_table->area, item_pointer);
-			next_item_pointer = item->next;
-			insert_item_into_bucket(hash_table, item_pointer, item,
-									&new_buckets[BUCKET_INDEX_FOR_HASH_AND_SIZE(item->hash,
-																				new_size_log2)]);
-			item_pointer = next_item_pointer;
-		}
-	}
-
-	/* Swap the hash table into place and free the old one. */
-	old_buckets = hash_table->control->buckets;
-	hash_table->control->buckets = new_buckets_shared;
-	hash_table->control->size_log2 = new_size_log2;
-	hash_table->buckets = new_buckets;
-	dsa_free(hash_table->area, old_buckets);
-
-	/* Release all the locks. */
-	for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
-		LWLockRelease(PARTITION_LOCK(hash_table, i));
+    dsa_pointer old_buckets;
+    dsa_pointer new_buckets_shared;
+    dsa_pointer *new_buckets;
+    size_t      size;
+    size_t      new_size = ((size_t) 1) << new_size_log2;
+    size_t      i;
+
+    /* Allocate and initialize the new bucket array without locks. */
+    new_buckets_shared =
+        dsa_allocate_extended(hash_table->area,
+                              sizeof(dsa_pointer) * new_size,
+                              DSA_ALLOC_HUGE | DSA_ALLOC_ZERO);
+    new_buckets = dsa_get_address(hash_table->area, new_buckets_shared);
+
+    /* Acquire locks and update pointers. */
+    for (i = 0; i < DSHASH_NUM_PARTITIONS; i++)
+        LWLockAcquire(PARTITION_LOCK(hash_table, i), LW_EXCLUSIVE);
+
+    /* Check if another backend has already resized the table. */
+    if (hash_table->control->size_log2 >= new_size_log2)
+    {
+        dsa_free(hash_table->area, new_buckets_shared);
+
+        for (i = 0; i < DSHASH_NUM_PARTITIONS; i++)
+            LWLockRelease(PARTITION_LOCK(hash_table, i));
+
+        return;
+    }
+
+    /* Reinsert all items into the new bucket array. */
+    size = ((size_t) 1) << hash_table->control->size_log2;
+    for (i = 0; i < size; i++)
+    {
+        dsa_pointer item_pointer = hash_table->buckets[i];
+
+        while (DsaPointerIsValid(item_pointer))
+        {
+            dshash_table_item *item;
+            dsa_pointer next_item_pointer;
+
+            item = dsa_get_address(hash_table->area, item_pointer);
+            next_item_pointer = item->next;
+            insert_item_into_bucket(hash_table, item_pointer, item,
+                                    &new_buckets[BUCKET_INDEX_FOR_HASH_AND_SIZE(item->hash,
+                                                                                new_size_log2)]);
+            item_pointer = next_item_pointer;
+        }
+    }
+
+    /* Swap the hash table into place and free the old one. */
+    old_buckets = hash_table->control->buckets;
+    hash_table->control->buckets = new_buckets_shared;
+    hash_table->control->size_log2 = new_size_log2;
+    hash_table->buckets = new_buckets;
+    dsa_free(hash_table->area, old_buckets);
+
+    /* Release all the locks. */
+    for (i = 0; i < DSHASH_NUM_PARTITIONS; i++)
+        LWLockRelease(PARTITION_LOCK(hash_table, i));
 }
 
 /*
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 64ff3ce3d6..947636816a 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -765,56 +765,60 @@ ssize_t
 be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
 {
 	ssize_t		n;
-	int			err;
-	unsigned long ecode;
 
 	errno = 0;
 	ERR_clear_error();
 	n = SSL_read(port->ssl, ptr, len);
-	err = SSL_get_error(port->ssl, n);
-	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
-	switch (err)
+	if (n <= 0)
 	{
-		case SSL_ERROR_NONE:
-			/* a-ok */
-			break;
-		case SSL_ERROR_WANT_READ:
-			*waitfor = WL_SOCKET_READABLE;
-			errno = EWOULDBLOCK;
-			n = -1;
-			break;
-		case SSL_ERROR_WANT_WRITE:
-			*waitfor = WL_SOCKET_WRITEABLE;
-			errno = EWOULDBLOCK;
-			n = -1;
-			break;
-		case SSL_ERROR_SYSCALL:
-			/* leave it to caller to ereport the value of errno */
-			if (n != -1 || errno == 0)
-			{
+		int			err;
+		unsigned long ecode;
+
+		err = SSL_get_error(port->ssl, n);
+		ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+		switch (err)
+		{
+			case SSL_ERROR_NONE:
+				/* a-ok */
+				break;
+			case SSL_ERROR_WANT_READ:
+				*waitfor = WL_SOCKET_READABLE;
+				errno = EWOULDBLOCK;
+				n = -1;
+				break;
+			case SSL_ERROR_WANT_WRITE:
+				*waitfor = WL_SOCKET_WRITEABLE;
+				errno = EWOULDBLOCK;
+				n = -1;
+				break;
+			case SSL_ERROR_SYSCALL:
+				/* leave it to caller to ereport the value of errno */
+				if (n != -1 || errno == 0)
+				{
+					errno = ECONNRESET;
+					n = -1;
+				}
+				break;
+			case SSL_ERROR_SSL:
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						errmsg("SSL error: %s", SSLerrmessage(ecode))));
 				errno = ECONNRESET;
 				n = -1;
-			}
-			break;
-		case SSL_ERROR_SSL:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("SSL error: %s", SSLerrmessage(ecode))));
-			errno = ECONNRESET;
-			n = -1;
-			break;
-		case SSL_ERROR_ZERO_RETURN:
-			/* connection was cleanly shut down by peer */
-			n = 0;
-			break;
-		default:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("unrecognized SSL error code: %d",
-							err)));
-			errno = ECONNRESET;
-			n = -1;
-			break;
+				break;
+			case SSL_ERROR_ZERO_RETURN:
+				/* connection was cleanly shut down by peer */
+				n = 0;
+				break;
+			default:
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						errmsg("unrecognized SSL error code: %d",
+								err)));
+				errno = ECONNRESET;
+				n = -1;
+				break;
+		}
 	}
 
 	return n;
@@ -824,67 +828,71 @@ ssize_t
 be_tls_write(Port *port, const void *ptr, size_t len, int *waitfor)
 {
 	ssize_t		n;
-	int			err;
-	unsigned long ecode;
 
 	errno = 0;
 	ERR_clear_error();
 	n = SSL_write(port->ssl, ptr, len);
-	err = SSL_get_error(port->ssl, n);
-	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
-	switch (err)
+	if (n <= 0)
 	{
-		case SSL_ERROR_NONE:
-			/* a-ok */
-			break;
-		case SSL_ERROR_WANT_READ:
-			*waitfor = WL_SOCKET_READABLE;
-			errno = EWOULDBLOCK;
-			n = -1;
-			break;
-		case SSL_ERROR_WANT_WRITE:
-			*waitfor = WL_SOCKET_WRITEABLE;
-			errno = EWOULDBLOCK;
-			n = -1;
-			break;
-		case SSL_ERROR_SYSCALL:
+		int			err;
+		unsigned long ecode;
 
-			/*
-			 * Leave it to caller to ereport the value of errno.  However, if
-			 * errno is still zero then assume it's a read EOF situation, and
-			 * report ECONNRESET.  (This seems possible because SSL_write can
-			 * also do reads.)
-			 */
-			if (n != -1 || errno == 0)
-			{
+		err = SSL_get_error(port->ssl, n);
+		ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+		switch (err)
+		{
+			case SSL_ERROR_NONE:
+				/* a-ok */
+				break;
+			case SSL_ERROR_WANT_READ:
+				*waitfor = WL_SOCKET_READABLE;
+				errno = EWOULDBLOCK;
+				n = -1;
+				break;
+			case SSL_ERROR_WANT_WRITE:
+				*waitfor = WL_SOCKET_WRITEABLE;
+				errno = EWOULDBLOCK;
+				n = -1;
+				break;
+			case SSL_ERROR_SYSCALL:
+
+				/*
+				 * Leave it to caller to ereport the value of errno.  However, if
+				 * errno is still zero then assume it's a read EOF situation, and
+				 * report ECONNRESET.  (This seems possible because SSL_write can
+				 * also do reads.)
+				 */
+				if (n != -1 || errno == 0)
+				{
+					errno = ECONNRESET;
+					n = -1;
+				}
+				break;
+				case SSL_ERROR_SSL:
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						errmsg("SSL error: %s", SSLerrmessage(ecode))));
 				errno = ECONNRESET;
 				n = -1;
-			}
-			break;
-		case SSL_ERROR_SSL:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("SSL error: %s", SSLerrmessage(ecode))));
-			errno = ECONNRESET;
-			n = -1;
-			break;
-		case SSL_ERROR_ZERO_RETURN:
+				break;
+			case SSL_ERROR_ZERO_RETURN:
 
-			/*
-			 * the SSL connection was closed, leave it to the caller to
-			 * ereport it
-			 */
-			errno = ECONNRESET;
-			n = -1;
-			break;
-		default:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("unrecognized SSL error code: %d",
-							err)));
-			errno = ECONNRESET;
-			n = -1;
-			break;
+				/*
+				 * the SSL connection was closed, leave it to the caller to
+				 * ereport it
+				 */
+				errno = ECONNRESET;
+				n = -1;
+				break;
+			default:
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						errmsg("unrecognized SSL error code: %d",
+								err)));
+				errno = ECONNRESET;
+				n = -1;
+				break;
+		}
 	}
 
 	return n;
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index bf512cf806..d65c2ea429 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -831,16 +831,13 @@ bms_add_member(Bitmapset *a, int x)
 	if (wordnum >= a->nwords)
 	{
 		int			oldnwords = a->nwords;
-		int			i;
 
 		a = (Bitmapset *) repalloc(a, BITMAPSET_SIZE(wordnum + 1));
-		a->nwords = wordnum + 1;
+
 		/* zero out the enlarged portion */
-		i = oldnwords;
-		do
-		{
-			a->words[i] = 0;
-		} while (++i < a->nwords);
+		memset(&a->words[oldnwords], 0, (wordnum + 1 - oldnwords) * sizeof(bitmapword));
+
+		a->nwords = wordnum + 1;
 	}
 
 	a->words[wordnum] |= ((bitmapword) 1 << bitnum);
@@ -971,8 +968,6 @@ bms_add_members(Bitmapset *a, const Bitmapset *b)
 Bitmapset *
 bms_replace_members(Bitmapset *a, const Bitmapset *b)
 {
-	int			i;
-
 	Assert(bms_is_valid_set(a));
 	Assert(bms_is_valid_set(b));
 
@@ -987,11 +982,7 @@ bms_replace_members(Bitmapset *a, const Bitmapset *b)
 	if (a->nwords < b->nwords)
 		a = (Bitmapset *) repalloc(a, BITMAPSET_SIZE(b->nwords));
 
-	i = 0;
-	do
-	{
-		a->words[i] = b->words[i];
-	} while (++i < b->nwords);
+	memcpy(a->words, b->words, b->nwords * sizeof(bitmapword));
 
 	a->nwords = b->nwords;
 
@@ -1049,17 +1040,14 @@ bms_add_range(Bitmapset *a, int lower, int upper)
 	else if (uwordnum >= a->nwords)
 	{
 		int			oldnwords = a->nwords;
-		int			i;
 
 		/* ensure we have enough words to store the upper bit */
 		a = (Bitmapset *) repalloc(a, BITMAPSET_SIZE(uwordnum + 1));
-		a->nwords = uwordnum + 1;
+
 		/* zero out the enlarged portion */
-		i = oldnwords;
-		do
-		{
-			a->words[i] = 0;
-		} while (++i < a->nwords);
+		memset(&a->words[oldnwords], 0, (uwordnum + 1 - oldnwords) * sizeof(bitmapword));
+
+		a->nwords = uwordnum + 1;
 	}
 
 	wordnum = lwordnum = WORDNUM(lower);
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 5d51f97f21..a4882081ae 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -128,6 +128,9 @@ clauselist_selectivity_ext(PlannerInfo *root,
 	ListCell   *l;
 	int			listidx;
 
+	if (clauses == NIL)
+		return s1;
+
 	/*
 	 * If there's exactly one clause, just go directly to
 	 * clause_selectivity_ext(). None of what we might do below is relevant.
@@ -525,6 +528,9 @@ find_single_rel_for_clauses(PlannerInfo *root, List *clauses)
 	int			lastrelid = 0;
 	ListCell   *l;
 
+	if (clauses == NIL)
+		return NULL;
+
 	foreach(l, clauses)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index a43ca16d68..c983753f84 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -4286,7 +4286,8 @@ relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
 				{
 					matched = true; /* column is unique */
 
-					if (bms_membership(rinfo->clause_relids) == BMS_SINGLETON)
+					if (extra_clauses &&
+						bms_membership(rinfo->clause_relids) == BMS_SINGLETON)
 					{
 						MemoryContext oldMemCtx =
 							MemoryContextSwitchTo(root->planner_cxt);
@@ -4298,8 +4299,8 @@ relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
 						 */
 						Assert(bms_is_empty(rinfo->left_relids) ||
 							   bms_is_empty(rinfo->right_relids));
-						if (extra_clauses)
-							exprs = lappend(exprs, rinfo);
+
+						exprs = lappend(exprs, rinfo);
 						MemoryContextSwitchTo(oldMemCtx);
 					}
 
diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c
index f26e38c655..a3fe316d8a 100644
--- a/src/backend/optimizer/util/joininfo.c
+++ b/src/backend/optimizer/util/joininfo.c
@@ -106,33 +106,11 @@ add_join_clause_to_rels(PlannerInfo *root,
 		return;
 
 	/*
-	 * Substitute the origin qual with constant-FALSE if it is provably always
+	 * Substitute the origin qual with constant-FALSE if it is probably always
 	 * false.
-	 *
-	 * Note that we need to keep the same rinfo_serial, since it is in
-	 * practice the same condition.  We also need to reset the
-	 * last_rinfo_serial counter, which is essential to ensure that the
-	 * RestrictInfos for the "same" qual condition get identical serial
-	 * numbers (see deconstruct_distribute_oj_quals).
 	 */
 	if (restriction_is_always_false(root, restrictinfo))
-	{
-		int			save_rinfo_serial = restrictinfo->rinfo_serial;
-		int			save_last_rinfo_serial = root->last_rinfo_serial;
-
-		restrictinfo = make_restrictinfo(root,
-										 (Expr *) makeBoolConst(false, false),
-										 restrictinfo->is_pushed_down,
-										 restrictinfo->has_clone,
-										 restrictinfo->is_clone,
-										 restrictinfo->pseudoconstant,
-										 0, /* security_level */
-										 restrictinfo->required_relids,
-										 restrictinfo->incompatible_relids,
-										 restrictinfo->outer_relids);
-		restrictinfo->rinfo_serial = save_rinfo_serial;
-		root->last_rinfo_serial = save_last_rinfo_serial;
-	}
+		restrictinfo->clause = (Expr *) makeBoolConst(false, false);
 
 	cur_relid = -1;
 	while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 0af26d6acf..0b6634f8b5 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -158,9 +158,6 @@ indexam_property(FunctionCallInfo fcinfo,
 	IndexAMProperty prop;
 	IndexAmRoutine *routine;
 
-	/* Try to convert property name to enum (no error if not known) */
-	prop = lookup_prop_name(propname);
-
 	/* If we have an index OID, look up the AM, and get # of columns too */
 	if (OidIsValid(index_oid))
 	{
@@ -199,6 +196,9 @@ indexam_property(FunctionCallInfo fcinfo,
 	if (routine == NULL)
 		PG_RETURN_NULL();
 
+	/* Try to convert property name to enum (no error if not known) */
+	prop = lookup_prop_name(propname);
+
 	/*
 	 * If there's an AM property routine, give it a chance to override the
 	 * generic logic.  Proceed if it returns false.
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 860bbd40d4..7f6e64e78f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -584,6 +584,14 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * worthy of panic, depending on which subprocess returns it.
 		 */
 		proc_exit(1);
+
+		/*
+		 * Closes all of the calling process's open streams.
+		 * On Windows, close and remove all temporary files created by tmpfile function.
+		 */
+		#ifdef NDEBUG
+		fcloseall();
+		#endif
 	}
 
 	if (elevel >= PANIC)
@@ -596,6 +604,15 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * children...
 		 */
 		fflush(NULL);
+
+		/*
+		 * Closes all of the calling process's open streams.
+		 * On Windows, close and remove all temporary files created by tmpfile function.
+		 */
+		#ifdef NDEBUG
+		fcloseall();
+		#endif
+
 		abort();
 	}
 
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 8a405ff122..2cf8b0be8f 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -356,7 +356,7 @@ readfile(const char *path, int *numlines)
 	if (len != statbuf.st_size)
 	{
 		/* oops, the file size changed between fstat and read */
-		free(buffer);
+		pg_free(buffer);
 		return NULL;
 	}
 
@@ -397,7 +397,7 @@ readfile(const char *path, int *numlines)
 	}
 	result[n] = NULL;
 
-	free(buffer);
+	pg_free(buffer);
 
 	return result;
 }
@@ -1942,7 +1942,7 @@ GetPrivilegesToDelete(HANDLE hToken)
 	{
 		write_stderr(_("%s: could not get token information: error code %lu\n"),
 					 progname, (unsigned long) GetLastError());
-		free(tokenPrivs);
+		pg_free(tokenPrivs);
 		return NULL;
 	}
 
@@ -2168,7 +2168,7 @@ adjust_data_dir(void)
 		write_stderr(_("%s: could not determine the data directory using command \"%s\"\n"), progname, cmd);
 		exit(1);
 	}
-	free(my_exec_path);
+	pg_free(my_exec_path);
 
 	/* strip trailing newline and carriage return */
 	(void) pg_strip_crlf(filename);
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c
index 5150eb0532..ae2d0e5ed3 100644
--- a/src/bin/psql/variables.c
+++ b/src/bin/psql/variables.c
@@ -234,7 +234,7 @@ ParseVariableDouble(const char *value, const char *name, double *result, double
 	 * too close to zero to have full precision, by checking for zero or real
 	 * out-of-range values.
 	 */
-	else if ((errno = ERANGE) &&
+	else if ((errno == ERANGE) &&
 			 (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
 	{
 		if (name)
diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c
index 7b8f7d4c52..9d5ff712e1 100644
--- a/src/bin/scripts/common.c
+++ b/src/bin/scripts/common.c
@@ -151,15 +151,15 @@ yesno_prompt(const char *question)
 
 		if (strcmp(resp, _(PG_YESLETTER)) == 0)
 		{
-			free(resp);
+			pg_free(resp);
 			return true;
 		}
 		if (strcmp(resp, _(PG_NOLETTER)) == 0)
 		{
-			free(resp);
+			pg_free(resp);
 			return false;
 		}
-		free(resp);
+		pg_free(resp);
 
 		printf(_("Please answer \"%s\" or \"%s\".\n"),
 			   _(PG_YESLETTER), _(PG_NOLETTER));
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 81e6abfc46..853afde3ba 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -237,7 +237,7 @@ main(int argc, char *argv[])
 			fprintf(stderr, _("Passwords didn't match.\n"));
 			exit(1);
 		}
-		free(pw2);
+		pg_free(pw2);
 	}
 
 	if (superuser == TRI_DEFAULT)
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index 860a0fcb46..d944876eea 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -348,13 +348,6 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 				process_list = get_parallel_tables_list(conn, process_type,
 														user_list, echo);
 				process_type = REINDEX_TABLE;
-
-				/* Bail out if nothing to process */
-				if (process_list == NULL)
-				{
-					PQfinish(conn);
-					return;
-				}
 				break;
 
 			case REINDEX_INDEX:
@@ -380,7 +373,6 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 
 			case REINDEX_SYSTEM:
 				/* not supported */
-				process_list = NULL;
 				Assert(false);
 				break;
 
@@ -390,6 +382,13 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 		}
 	}
 
+	/* Bail out if nothing to process */
+	if (process_list == NULL)
+	{
+		PQfinish(conn);
+		return;
+	}
+
 	/*
 	 * Adjust the number of concurrent connections depending on the items in
 	 * the list.  We choose the minimum between the number of concurrent
@@ -434,7 +433,7 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 
 		ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
 		initPQExpBuffer(&sql);
-		if (parallel && process_type == REINDEX_INDEX)
+		if (parallel && process_type == REINDEX_INDEX && indices_tables_cell)
 		{
 			/*
 			 * For parallel index-level REINDEX, the indices of the same table
@@ -444,17 +443,16 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 			 */
 			gen_reindex_command(free_slot->connection, process_type, objname,
 								echo, verbose, concurrently, tablespace, &sql);
-			while (indices_tables_cell->next &&
+			while (indices_tables_cell && indices_tables_cell->next &&
 				   indices_tables_cell->val == indices_tables_cell->next->val)
 			{
-				indices_tables_cell = indices_tables_cell->next;
 				cell = cell->next;
 				objname = cell->val;
 				appendPQExpBufferChar(&sql, '\n');
 				gen_reindex_command(free_slot->connection, process_type, objname,
 									echo, verbose, concurrently, tablespace, &sql);
+				indices_tables_cell = indices_tables_cell->next;
 			}
-			indices_tables_cell = indices_tables_cell->next;
 		}
 		else
 		{
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 935e6da3c1..5368ed4a89 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -536,12 +536,12 @@ vacuum_one_database(ConnParams *cparams,
 	bool		failed = false;
 	const char *initcmd;
 	SimpleStringList *ret = NULL;
-	const char *stage_commands[] = {
+	static const char *stage_commands[] = {
 		"SET default_statistics_target=1; SET vacuum_cost_delay=0;",
 		"SET default_statistics_target=10; RESET vacuum_cost_delay;",
 		"RESET default_statistics_target;"
 	};
-	const char *stage_messages[] = {
+	static const char *stage_messages[] = {
 		gettext_noop("Generating minimal optimizer statistics (1 target)"),
 		gettext_noop("Generating medium optimizer statistics (10 targets)"),
 		gettext_noop("Generating default (full) optimizer statistics")
@@ -793,7 +793,6 @@ static SimpleStringList *
 retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
 				 SimpleStringList *objects, bool echo)
 {
-	PQExpBufferData buf;
 	PQExpBufferData catalog_query;
 	PGresult   *res;
 	SimpleStringListCell *cell;
@@ -1031,28 +1030,23 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
 	appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
 	executeCommand(conn, "RESET search_path;", echo);
 	res = executeQuery(conn, catalog_query.data, echo);
-	termPQExpBuffer(&catalog_query);
 	PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
+	termPQExpBuffer(&catalog_query);
 
 	/*
 	 * Build qualified identifiers for each table, including the column list
 	 * if given.
 	 */
-	initPQExpBuffer(&buf);
 	for (int i = 0; i < PQntuples(res); i++)
 	{
-		appendPQExpBufferStr(&buf,
+		simple_string_list_append(found_objs, 
 							 fmtQualifiedIdEnc(PQgetvalue(res, i, 1),
 											   PQgetvalue(res, i, 0),
 											   PQclientEncoding(conn)));
 
 		if (objects_listed && !PQgetisnull(res, i, 2))
-			appendPQExpBufferStr(&buf, PQgetvalue(res, i, 2));
-
-		simple_string_list_append(found_objs, buf.data);
-		resetPQExpBuffer(&buf);
+			simple_string_list_append(found_objs, PQgetvalue(res, i, 2));
 	}
-	termPQExpBuffer(&buf);
 	PQclear(res);
 
 	return found_objs;
diff --git a/src/common/username.c b/src/common/username.c
index ae5f02d96b..aa5f236d88 100644
--- a/src/common/username.c
+++ b/src/common/username.c
@@ -48,9 +48,12 @@ get_user_name(char **errstr)
 
 	return pw->pw_name;
 #else
-	/* Microsoft recommends buffer size of UNLEN+1, where UNLEN = 256 */
+	#include "Lmcons.h"
+
+	/* Microsoft recommends buffer size of UNLEN + 1 */
+	/* UNLEN is defined in Lmcons.h */
 	/* "static" variable remains after function exit */
-	static char username[256 + 1];
+	static char username[UNLEN + 1];
 	DWORD		len = sizeof(username);
 
 	*errstr = NULL;
diff --git a/src/include/lib/simplehash.h b/src/include/lib/simplehash.h
index 327274c234..91211c7abc 100644
--- a/src/include/lib/simplehash.h
+++ b/src/include/lib/simplehash.h
@@ -149,7 +149,7 @@ typedef struct SH_TYPE
 	 * tables.  Note that the maximum number of elements is lower
 	 * (SH_MAX_FILLFACTOR)
 	 */
-	uint64		size;
+	uint32		size;
 
 	/* how many elements have valid contents */
 	uint32		members;
@@ -204,8 +204,8 @@ SH_SCOPE void SH_DESTROY(SH_TYPE * tb);
 /* void <prefix>_reset(<prefix>_hash *tb) */
 SH_SCOPE void SH_RESET(SH_TYPE * tb);
 
-/* void <prefix>_grow(<prefix>_hash *tb, uint64 newsize) */
-SH_SCOPE void SH_GROW(SH_TYPE * tb, uint64 newsize);
+/* void <prefix>_grow(<prefix>_hash *tb, uint63 newsize) */
+SH_SCOPE void SH_GROW(SH_TYPE * tb, uint32 newsize);
 
 /* <element> *<prefix>_insert(<prefix>_hash *tb, <key> key, bool *found) */
 SH_SCOPE	SH_ELEMENT_TYPE *SH_INSERT(SH_TYPE * tb, SH_KEY_TYPE key, bool *found);
@@ -256,7 +256,7 @@ SH_SCOPE void SH_STAT(SH_TYPE * tb);
 #endif
 
 /* max data array size,we allow up to PG_UINT32_MAX buckets, including 0 */
-#define SH_MAX_SIZE (((uint64) PG_UINT32_MAX) + 1)
+#define SH_MAX_SIZE PG_UINT32_MAX
 
 /* normal fillfactor, unless already close to maximum */
 #ifndef SH_FILLFACTOR
@@ -307,10 +307,10 @@ SH_SCOPE void SH_STAT(SH_TYPE * tb);
  * Compute allocation size for hashtable. Result can be passed to
  * SH_UPDATE_PARAMETERS.
  */
-static inline uint64
-SH_COMPUTE_SIZE(uint64 newsize)
+static inline uint32
+SH_COMPUTE_SIZE(uint32 newsize)
 {
-	uint64		size;
+	uint32		size;
 
 	/* supporting zero sized hashes would complicate matters */
 	size = Max(newsize, 2);
@@ -326,7 +326,7 @@ SH_COMPUTE_SIZE(uint64 newsize)
 	if (unlikely((((uint64) sizeof(SH_ELEMENT_TYPE)) * size) >= SIZE_MAX / 2))
 		sh_error("hash table too large");
 
-	return size;
+	return (uint32) size;
 }
 
 /*
@@ -334,9 +334,9 @@ SH_COMPUTE_SIZE(uint64 newsize)
  * the hashtable.
  */
 static inline void
-SH_UPDATE_PARAMETERS(SH_TYPE * tb, uint64 newsize)
+SH_UPDATE_PARAMETERS(SH_TYPE * tb, uint32 newsize)
 {
-	uint64		size = SH_COMPUTE_SIZE(newsize);
+	uint32		size = SH_COMPUTE_SIZE(newsize);
 
 	/* now set size */
 	tb->size = size;
@@ -446,7 +446,7 @@ SH_CREATE(MemoryContext ctx, uint32 nelements, void *private_data)
 #endif
 {
 	SH_TYPE    *tb;
-	uint64		size;
+	uint32		size;
 
 #ifdef SH_RAW_ALLOCATOR
 	tb = (SH_TYPE *) SH_RAW_ALLOCATOR(sizeof(SH_TYPE));
@@ -491,9 +491,9 @@ SH_RESET(SH_TYPE * tb)
  * performance-wise, when known at some point.
  */
 SH_SCOPE void
-SH_GROW(SH_TYPE * tb, uint64 newsize)
+SH_GROW(SH_TYPE * tb, uint32 newsize)
 {
-	uint64		oldsize = tb->size;
+	uint32		oldsize = tb->size;
 	SH_ELEMENT_TYPE *olddata = tb->data;
 	SH_ELEMENT_TYPE *newdata;
 	uint32		i;
@@ -982,7 +982,7 @@ SH_DELETE_ITEM(SH_TYPE * tb, SH_ELEMENT_TYPE * entry)
 SH_SCOPE void
 SH_START_ITERATE(SH_TYPE * tb, SH_ITERATOR * iter)
 {
-	uint64		startelem = PG_UINT64_MAX;
+	uint32		startelem = PG_UINT32_MAX;
 
 	/*
 	 * Search for the first empty element. As deletions during iterations are
diff --git a/src/include/port/pg_bitutils.h b/src/include/port/pg_bitutils.h
index 62554ce685..569646c13e 100644
--- a/src/include/port/pg_bitutils.h
+++ b/src/include/port/pg_bitutils.h
@@ -301,7 +301,7 @@ pg_ceil_log2_64(uint64 num)
 #ifdef TRY_POPCNT_FAST
 /* Attempt to use the POPCNT instruction, but perform a runtime check first */
 extern PGDLLIMPORT int (*pg_popcount32) (uint32 word);
-extern PGDLLIMPORT int (*pg_popcount64) (uint64 word);
+extern PGDLLIMPORT uint64 (*pg_popcount64) (uint64 word);
 extern PGDLLIMPORT uint64 (*pg_popcount_optimized) (const char *buf, int bytes);
 extern PGDLLIMPORT uint64 (*pg_popcount_masked_optimized) (const char *buf, int bytes, bits8 mask);
 
@@ -318,7 +318,7 @@ extern uint64 pg_popcount_masked_avx512(const char *buf, int bytes, bits8 mask);
 #else
 /* Use a portable implementation -- no need for a function pointer. */
 extern int	pg_popcount32(uint32 word);
-extern int	pg_popcount64(uint64 word);
+extern uint64 pg_popcount64(uint64 word);
 extern uint64 pg_popcount_optimized(const char *buf, int bytes);
 extern uint64 pg_popcount_masked_optimized(const char *buf, int bytes, bits8 mask);
 
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index c11d8ce330..5af801969b 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -70,6 +70,7 @@ extern ReadStream *read_stream_begin_relation(int flags,
 extern Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data);
 extern BlockNumber read_stream_next_block(ReadStream *stream,
 										  BufferAccessStrategy *strategy);
+extern size_t read_stream_per_buffer_data_size(ReadStream *stream);
 extern ReadStream *read_stream_begin_smgr_relation(int flags,
 												   BufferAccessStrategy strategy,
 												   SMgrRelation smgr,
@@ -81,4 +82,67 @@ extern ReadStream *read_stream_begin_smgr_relation(int flags,
 extern void read_stream_reset(ReadStream *stream);
 extern void read_stream_end(ReadStream *stream);
 
+/*
+ * Get the next buffer from a stream that is not using per-buffer data.
+ */
+static inline Buffer
+read_stream_get_buffer(ReadStream *stream)
+{
+	Assert(read_stream_per_buffer_data_size(stream) == 0);
+	return read_stream_next_buffer(stream, NULL);
+}
+
+/*
+ * Helper for read_stream_get_buffer_and_value().
+ */
+static inline Buffer
+read_stream_get_buffer_and_value_with_size(ReadStream *stream,
+										   void *output_data,
+										   size_t output_data_size)
+{
+	Buffer		buffer;
+	void	   *per_buffer_data;
+
+	Assert(read_stream_per_buffer_data_size(stream) == output_data_size);
+	buffer = read_stream_next_buffer(stream, &per_buffer_data);
+	if (buffer != InvalidBuffer)
+		memcpy(output_data, per_buffer_data, output_data_size);
+
+	return buffer;
+}
+
+/*
+ * Get the next buffer and a copy of the associated per-buffer data.
+ * InvalidBuffer means end-of-stream, and in that case the per-buffer data is
+ * undefined.  Example of use:
+ *
+ * int my_int;
+ *
+ * buf = read_stream_get_buffer_and_value(stream, &my_int);
+ */
+#define read_stream_get_buffer_and_value(stream, vp) \
+	read_stream_get_buffer_and_value_with_size((stream), (vp), sizeof(*(vp)))
+
+/*
+ * Get the next buffer and a pointer to the associated per-buffer data.  This
+ * avoids casts in the calling code, and asserts that we received a pointer to
+ * a pointer to a type that doesn't exceed the storage size.  For example:
+ *
+ * int *my_int_p;
+ *
+ * buf = read_stream_get_buffer_and_pointer(stream, &my_int_p);
+ */
+#define read_stream_get_buffer_and_pointer(stream, pointer) \
+	(AssertMacro(sizeof(**(pointer)) <= read_stream_per_buffer_data_size(stream)), \
+	 read_stream_next_buffer((stream), ((void **) (pointer))))
+
+/*
+ * Set the per-buffer data by value.  This can be called from inside a
+ * callback that is returning block numbers.  It asserts that the value's size
+ * matches the available space.
+ */
+#define read_stream_put_value(stream, per_buffer_data, value) \
+	(AssertMacro(sizeof(value) == read_stream_per_buffer_data_size(stream)), \
+	 memcpy((per_buffer_data), &(value), sizeof(value)))
+
 #endif							/* READ_STREAM_H */
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index d78445c70a..abc99caef2 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -580,7 +580,7 @@ int
 pqReadData(PGconn *conn)
 {
 	int			someread = 0;
-	int			nread;
+	ssize_t	nread;
 
 	if (conn->sock == PGINVALID_SOCKET)
 	{
@@ -838,7 +838,7 @@ pqSendSome(PGconn *conn, int len)
 	/* while there's still data to send */
 	while (len > 0)
 	{
-		int			sent;
+		ssize_t		sent;
 
 #ifndef WIN32
 		sent = pqsecure_write(conn, ptr, len);
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 5bb9d9779d..f0eaf8d4f1 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -118,9 +118,6 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
 {
 	ssize_t		n;
 	int			result_errno = 0;
-	char		sebuf[PG_STRERROR_R_BUFLEN];
-	int			err;
-	unsigned long ecode;
 
 rloop:
 
@@ -136,91 +133,98 @@ rloop:
 	SOCK_ERRNO_SET(0);
 	ERR_clear_error();
 	n = SSL_read(conn->ssl, ptr, len);
-	err = SSL_get_error(conn->ssl, n);
-
-	/*
-	 * Other clients of OpenSSL may fail to call ERR_get_error(), but we
-	 * always do, so as to not cause problems for OpenSSL clients that don't
-	 * call ERR_clear_error() defensively.  Be sure that this happens by
-	 * calling now.  SSL_get_error() relies on the OpenSSL per-thread error
-	 * queue being intact, so this is the earliest possible point
-	 * ERR_get_error() may be called.
-	 */
-	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
-	switch (err)
+	if (n <= 0)
 	{
-		case SSL_ERROR_NONE:
-			if (n < 0)
-			{
-				/* Not supposed to happen, so we don't translate the msg */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 "SSL_read failed but did not provide error information\n");
-				/* assume the connection is broken */
-				result_errno = ECONNRESET;
-			}
-			break;
-		case SSL_ERROR_WANT_READ:
-			n = 0;
-			break;
-		case SSL_ERROR_WANT_WRITE:
+		char		sebuf[PG_STRERROR_R_BUFLEN];
+		int			err;
+		unsigned long ecode;
 
-			/*
-			 * Returning 0 here would cause caller to wait for read-ready,
-			 * which is not correct since what SSL wants is wait for
-			 * write-ready.  The former could get us stuck in an infinite
-			 * wait, so don't risk it; busy-loop instead.
-			 */
-			goto rloop;
-		case SSL_ERROR_SYSCALL:
-			if (n < 0 && SOCK_ERRNO != 0)
-			{
-				result_errno = SOCK_ERRNO;
-				if (result_errno == EPIPE ||
-					result_errno == ECONNRESET)
-					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
-											"\tThis probably means the server terminated abnormally\n"
-											"\tbefore or while processing the request.");
+		err = SSL_get_error(conn->ssl, n);
+
+		/*
+		* Other clients of OpenSSL may fail to call ERR_get_error(), but we
+		* always do, so as to not cause problems for OpenSSL clients that don't
+		* call ERR_clear_error() defensively.  Be sure that this happens by
+		* calling now.  SSL_get_error() relies on the OpenSSL per-thread error
+		* queue being intact, so this is the earliest possible point
+		* ERR_get_error() may be called.
+		*/
+		ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+		switch (err)
+		{
+			case SSL_ERROR_NONE:
+				if (n < 0)
+				{
+					/* Not supposed to happen, so we don't translate the msg */
+					appendPQExpBufferStr(&conn->errorMessage,
+										"SSL_read failed but did not provide error information\n");
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+				}
+				break;
+			case SSL_ERROR_WANT_READ:
+				n = 0;
+				break;
+			case SSL_ERROR_WANT_WRITE:
+
+				/*
+				* Returning 0 here would cause caller to wait for read-ready,
+				* which is not correct since what SSL wants is wait for
+				* write-ready.  The former could get us stuck in an infinite
+				* wait, so don't risk it; busy-loop instead.
+				*/
+				goto rloop;
+			case SSL_ERROR_SYSCALL:
+				if (n < 0 && SOCK_ERRNO != 0)
+				{
+					result_errno = SOCK_ERRNO;
+					if (result_errno == EPIPE ||
+						result_errno == ECONNRESET)
+						libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+												"\tThis probably means the server terminated abnormally\n"
+												"\tbefore or while processing the request.");
+					else
+						libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
+												SOCK_STRERROR(result_errno,
+															sebuf, sizeof(sebuf)));
+				}
 				else
-					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
-											SOCK_STRERROR(result_errno,
-														  sebuf, sizeof(sebuf)));
-			}
-			else
-			{
-				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
-				/* assume the connection is broken */
+				{
+					libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+					n = -1;
+				}
+				break;
+			case SSL_ERROR_SSL:
+				{
+					char	   *errm = SSLerrmessage(ecode);
+
+					libpq_append_conn_error(conn, "SSL error: %s", errm);
+					SSLerrfree(errm);
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+					n = -1;
+					break;
+				}
+			case SSL_ERROR_ZERO_RETURN:
+
+				/*
+				* Per OpenSSL documentation, this error code is only returned for
+				* a clean connection closure, so we should not report it as a
+				* server crash.
+				*/
+				libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
 				result_errno = ECONNRESET;
 				n = -1;
-			}
-			break;
-		case SSL_ERROR_SSL:
-			{
-				char	   *errm = SSLerrmessage(ecode);
-
-				libpq_append_conn_error(conn, "SSL error: %s", errm);
-				SSLerrfree(errm);
+				break;
+			default:
+				libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
 				n = -1;
 				break;
-			}
-		case SSL_ERROR_ZERO_RETURN:
-
-			/*
-			 * Per OpenSSL documentation, this error code is only returned for
-			 * a clean connection closure, so we should not report it as a
-			 * server crash.
-			 */
-			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
-			result_errno = ECONNRESET;
-			n = -1;
-			break;
-		default:
-			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
-			/* assume the connection is broken */
-			result_errno = ECONNRESET;
-			n = -1;
-			break;
+		}
 	}
 
 	/* ensure we return the intended errno to caller */
@@ -240,93 +244,97 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 {
 	ssize_t		n;
 	int			result_errno = 0;
-	char		sebuf[PG_STRERROR_R_BUFLEN];
-	int			err;
-	unsigned long ecode;
 
 	SOCK_ERRNO_SET(0);
 	ERR_clear_error();
 	n = SSL_write(conn->ssl, ptr, len);
-	err = SSL_get_error(conn->ssl, n);
-	ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
-	switch (err)
+	if (n <= 0)
 	{
-		case SSL_ERROR_NONE:
-			if (n < 0)
-			{
-				/* Not supposed to happen, so we don't translate the msg */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 "SSL_write failed but did not provide error information\n");
-				/* assume the connection is broken */
-				result_errno = ECONNRESET;
-			}
-			break;
-		case SSL_ERROR_WANT_READ:
+		char		sebuf[PG_STRERROR_R_BUFLEN];
+		int			err;
+		unsigned long ecode;
 
-			/*
-			 * Returning 0 here causes caller to wait for write-ready, which
-			 * is not really the right thing, but it's the best we can do.
-			 */
-			n = 0;
-			break;
-		case SSL_ERROR_WANT_WRITE:
-			n = 0;
-			break;
-		case SSL_ERROR_SYSCALL:
+		err = SSL_get_error(conn->ssl, n);
+		ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
+		switch (err)
+		{
+			case SSL_ERROR_NONE:
+				if (n < 0)
+				{
+					/* Not supposed to happen, so we don't translate the msg */
+					appendPQExpBufferStr(&conn->errorMessage,
+										"SSL_write failed but did not provide error information\n");
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+				}
+				break;
+			case SSL_ERROR_WANT_READ:
 
-			/*
-			 * If errno is still zero then assume it's a read EOF situation,
-			 * and report EOF.  (This seems possible because SSL_write can
-			 * also do reads.)
-			 */
-			if (n < 0 && SOCK_ERRNO != 0)
-			{
-				result_errno = SOCK_ERRNO;
-				if (result_errno == EPIPE || result_errno == ECONNRESET)
-					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
-											"\tThis probably means the server terminated abnormally\n"
-											"\tbefore or while processing the request.");
+				/*
+				* Returning 0 here causes caller to wait for write-ready, which
+				* is not really the right thing, but it's the best we can do.
+				*/
+				n = 0;
+				break;
+			case SSL_ERROR_WANT_WRITE:
+				n = 0;
+				break;
+			case SSL_ERROR_SYSCALL:
+
+				/*
+				* If errno is still zero then assume it's a read EOF situation,
+				* and report EOF.  (This seems possible because SSL_write can
+				* also do reads.)
+				*/
+				if (n < 0 && SOCK_ERRNO != 0)
+				{
+					result_errno = SOCK_ERRNO;
+					if (result_errno == EPIPE || result_errno == ECONNRESET)
+						libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+												"\tThis probably means the server terminated abnormally\n"
+												"\tbefore or while processing the request.");
+					else
+						libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
+												SOCK_STRERROR(result_errno,
+															sebuf, sizeof(sebuf)));
+				}
 				else
-					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
-											SOCK_STRERROR(result_errno,
-														  sebuf, sizeof(sebuf)));
-			}
-			else
-			{
-				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
-				/* assume the connection is broken */
+				{
+					libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+					n = -1;
+				}
+				break;
+			case SSL_ERROR_SSL:
+				{
+					char	   *errm = SSLerrmessage(ecode);
+
+					libpq_append_conn_error(conn, "SSL error: %s", errm);
+					SSLerrfree(errm);
+					/* assume the connection is broken */
+					result_errno = ECONNRESET;
+					n = -1;
+					break;
+				}
+			case SSL_ERROR_ZERO_RETURN:
+
+				/*
+				* Per OpenSSL documentation, this error code is only returned for
+				* a clean connection closure, so we should not report it as a
+				* server crash.
+				*/
+				libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
 				result_errno = ECONNRESET;
 				n = -1;
-			}
-			break;
-		case SSL_ERROR_SSL:
-			{
-				char	   *errm = SSLerrmessage(ecode);
-
-				libpq_append_conn_error(conn, "SSL error: %s", errm);
-				SSLerrfree(errm);
+				break;
+			default:
+				libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
 				n = -1;
 				break;
-			}
-		case SSL_ERROR_ZERO_RETURN:
-
-			/*
-			 * Per OpenSSL documentation, this error code is only returned for
-			 * a clean connection closure, so we should not report it as a
-			 * server crash.
-			 */
-			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
-			result_errno = ECONNRESET;
-			n = -1;
-			break;
-		default:
-			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
-			/* assume the connection is broken */
-			result_errno = ECONNRESET;
-			n = -1;
-			break;
+		}
 	}
 
 	/* ensure we return the intended errno to caller */
diff --git a/src/port/pg_bitutils.c b/src/port/pg_bitutils.c
index 5677525693..b375326f92 100644
--- a/src/port/pg_bitutils.c
+++ b/src/port/pg_bitutils.c
@@ -104,23 +104,23 @@ const uint8 pg_number_of_ones[256] = {
 };
 
 static inline int pg_popcount32_slow(uint32 word);
-static inline int pg_popcount64_slow(uint64 word);
+static inline uint64 pg_popcount64_slow(uint64 word);
 static uint64 pg_popcount_slow(const char *buf, int bytes);
 static uint64 pg_popcount_masked_slow(const char *buf, int bytes, bits8 mask);
 
 #ifdef TRY_POPCNT_FAST
 static bool pg_popcount_available(void);
 static int	pg_popcount32_choose(uint32 word);
-static int	pg_popcount64_choose(uint64 word);
+static uint64 pg_popcount64_choose(uint64 word);
 static uint64 pg_popcount_choose(const char *buf, int bytes);
 static uint64 pg_popcount_masked_choose(const char *buf, int bytes, bits8 mask);
 static inline int pg_popcount32_fast(uint32 word);
-static inline int pg_popcount64_fast(uint64 word);
+static inline uint64 pg_popcount64_fast(uint64 word);
 static uint64 pg_popcount_fast(const char *buf, int bytes);
 static uint64 pg_popcount_masked_fast(const char *buf, int bytes, bits8 mask);
 
 int			(*pg_popcount32) (uint32 word) = pg_popcount32_choose;
-int			(*pg_popcount64) (uint64 word) = pg_popcount64_choose;
+uint64		(*pg_popcount64) (uint64 word) = pg_popcount64_choose;
 uint64		(*pg_popcount_optimized) (const char *buf, int bytes) = pg_popcount_choose;
 uint64		(*pg_popcount_masked_optimized) (const char *buf, int bytes, bits8 mask) = pg_popcount_masked_choose;
 #endif							/* TRY_POPCNT_FAST */
@@ -186,7 +186,7 @@ pg_popcount32_choose(uint32 word)
 	return pg_popcount32(word);
 }
 
-static int
+static uint64
 pg_popcount64_choose(uint64 word)
 {
 	choose_popcount_functions();
@@ -228,7 +228,7 @@ __asm__ __volatile__(" popcntl %1,%0\n":"=q"(res):"rm"(word):"cc");
  * pg_popcount64_fast
  *		Return the number of 1 bits set in word
  */
-static inline int
+static inline uint64
 pg_popcount64_fast(uint64 word)
 {
 #ifdef _MSC_VER
@@ -237,7 +237,7 @@ pg_popcount64_fast(uint64 word)
 	uint64		res;
 
 __asm__ __volatile__(" popcntq %1,%0\n":"=q"(res):"rm"(word):"cc");
-	return (int) res;
+	return res;
 #endif
 }
 
@@ -366,7 +366,7 @@ pg_popcount32_slow(uint32 word)
  * pg_popcount64_slow
  *		Return the number of 1 bits set in word
  */
-static inline int
+static inline uint64
 pg_popcount64_slow(uint64 word)
 {
 #ifdef HAVE__BUILTIN_POPCOUNT
@@ -378,7 +378,7 @@ pg_popcount64_slow(uint64 word)
 #error "cannot find integer of the same size as uint64_t"
 #endif
 #else							/* !HAVE__BUILTIN_POPCOUNT */
-	int			result = 0;
+	uint64		result = 0;
 
 	while (word != 0)
 	{
diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index f8f2018ea0..a040428204 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -328,7 +328,7 @@ static void fmtchar(int value, int leftjust, int minlen, PrintfTarget *target);
 static void fmtfloat(double value, char type, int forcesign,
 					 int leftjust, int minlen, int zpad, int precision, int pointflag,
 					 PrintfTarget *target);
-static void dostr(const char *str, int slen, PrintfTarget *target);
+static void dostr(const char *str, size_t slen, PrintfTarget *target);
 static void dopr_outch(int c, PrintfTarget *target);
 static void dopr_outchmulti(int c, int slen, PrintfTarget *target);
 static int	adjust_sign(int is_negative, int forcesign, int *signvalue);
@@ -1410,7 +1410,7 @@ fail:
 
 
 static void
-dostr(const char *str, int slen, PrintfTarget *target)
+dostr(const char *str, size_t slen, PrintfTarget *target)
 {
 	/* fast path for common case of slen == 1 */
 	if (slen == 1)
@@ -1421,13 +1421,13 @@ dostr(const char *str, int slen, PrintfTarget *target)
 
 	while (slen > 0)
 	{
-		int			avail;
+		size_t		avail;
 
 		if (target->bufend != NULL)
 			avail = target->bufend - target->bufptr;
 		else
 			avail = slen;
-		if (avail <= 0)
+		if (avail == 0)
 		{
 			/* buffer full, can we dump to stream? */
 			if (target->stream == NULL)
diff --git a/src/port/win32setlocale.c b/src/port/win32setlocale.c
index 7c0982439d..3ba73d71a7 100644
--- a/src/port/win32setlocale.c
+++ b/src/port/win32setlocale.c
@@ -146,10 +146,10 @@ map_locale(const struct locale_map *map, const char *locale)
 		if (match_start)
 		{
 			/* Found a match. Replace the matched string. */
-			int			matchpos = match_start - locale;
-			int			replacementlen = strlen(replacement);
+			size_t		matchpos = match_start - locale;
+			size_t		replacementlen = strlen(replacement);
 			char	   *rest = match_end;
-			int			restlen = strlen(rest);
+			size_t		restlen = strlen(rest);
 
 			/* check that the result fits in the static buffer */
 			if (matchpos + replacementlen + restlen + 1 > MAX_LOCALE_NAME_LEN)
diff --git a/src/test/modules/test_escape/test_escape.c b/src/test/modules/test_escape/test_escape.c
index f6b3644897..47fd75f008 100644
--- a/src/test/modules/test_escape/test_escape.c
+++ b/src/test/modules/test_escape/test_escape.c
@@ -143,7 +143,8 @@ escape_string_conn(PGconn *conn, PQExpBuffer target,
 	size_t		sz;
 
 	appendPQExpBufferChar(target, '\'');
-	enlargePQExpBuffer(target, unescaped_len * 2 + 1);
+	if (!enlargePQExpBuffer(target, unescaped_len * 2 + 1))
+		return false;
 	sz = PQescapeStringConn(conn, target->data + target->len,
 							unescaped, unescaped_len,
 							&error);
@@ -173,7 +174,8 @@ escape_string(PGconn *conn, PQExpBuffer target,
 	size_t		sz;
 
 	appendPQExpBufferChar(target, '\'');
-	enlargePQExpBuffer(target, unescaped_len * 2 + 1);
+	if (!enlargePQExpBuffer(target, unescaped_len * 2 + 1))
+		return false;
 	sz = PQescapeString(target->data + target->len,
 						unescaped, unescaped_len);
 	target->len += sz;
#32Daniel Gustafsson
daniel@yesql.se
In reply to: Ranier Vilela (#31)
Re: Allow default \watch interval in psql to be configured

On 27 Mar 2025, at 14:04, Ranier Vilela <ranier.vf@gmail.com> wrote:

Hi.

Em qua., 26 de mar. de 2025 às 09:24, Daniel Gustafsson <daniel@yesql.se> escreveu:

On 26 Mar 2025, at 10:12, Pavel Stehule <pavel.stehule@gmail.com> wrote:

yes, it is ok after this change

Thanks for confirming, committed.
Per Coverity.

I think that commit 1a759c8
left a minor oversight.

Thanks for reporting, fixing.

--
Daniel Gustafsson