Hi

čt 8. 4. 2021 v 1:38 odesílatel Thomas Munro <thomas.mu...@gmail.com>
napsal:

> Here's a rebase, due to a conflict with 3a513067 "psql: Show all query
> results by default" which moved a few things around making it harder
> to use the pager for the right scope.  Lacking time, I came up with
> this change to PSQLexecWatch():
>
> +       if (printQueryFout)
> +       {
> +               restoreQueryFout = pset.queryFout;
> +               pset.queryFout = printQueryFout;
> +       }
> +
>         SetCancelConn(pset.db);
>         res = SendQueryAndProcessResults(query, &elapsed_msec, true);
>         ResetCancelConn();
>
>         fflush(pset.queryFout);
>
> +       if (restoreQueryFout)
> +               pset.queryFout = restoreQueryFout;
> +
>
> If someone has a tidier way to factor this, I'm keen to hear it.  I'd
> like to push this today.
>

here is an rebase of Thomas's implementation

Regards

Pavel
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index bd4f26e6cc..bdf78f153f 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3002,6 +3002,16 @@ lo_import 152801
           (such as <filename>more</filename>) is used.
           </para>
 
+          <para>
+          When using the <literal>\watch</literal> command to execute a query
+          repeatedly, the environment variable <envar>PSQL_WATCH_PAGER</envar>
+          is used to find the pager program instead, on Unix systems.  This is
+          configured separately because it may confuse traditional pagers, but
+          can be used to send output to tools that understand
+          <application>psql</application>'s output format (such as
+          <filename>pspg --stream</filename>).
+          </para>
+
           <para>
           When the <literal>pager</literal> option is <literal>off</literal>, the pager
           program is not used. When the <literal>pager</literal> option is
@@ -4672,6 +4682,24 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><envar>PSQL_WATCH_PAGER</envar></term>
+
+    <listitem>
+     <para>
+      When a query is executed repeatedly with the <command>\watch</command>
+      command, a pager is not used by default.  This behavior can be changed
+      by setting <envar>PSQL_WATCH_PAGER</envar> to a pager command, on Unix
+      systems.  The <literal>pspg</literal> pager (not part of
+      <productname>PostgreSQL</productname> but available in many open source
+      software distributions) can display the output of
+      <command>\watch</command> if started with the option
+      <literal>--stream</literal>.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><envar>PSQLRC</envar></term>
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 543401c6d6..5d03740a87 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -13,6 +13,7 @@
 #include <utime.h>
 #ifndef WIN32
 #include <sys/stat.h>			/* for stat() */
+#include <sys/time.h>			/* for setitimer() */
 #include <fcntl.h>				/* open() flags */
 #include <unistd.h>				/* for geteuid(), getpid(), stat() */
 #else
@@ -4894,8 +4895,17 @@ do_watch(PQExpBuffer query_buf, double sleep)
 	const char *strftime_fmt;
 	const char *user_title;
 	char	   *title;
+	const char *pagerprog = NULL;
+	FILE	   *pagerpipe = NULL;
 	int			title_len;
 	int			res = 0;
+#ifndef WIN32
+	sigset_t	sigalrm_sigchld_sigint;
+	sigset_t	sigalrm_sigchld;
+	sigset_t	sigint;
+	struct itimerval interval;
+	bool		done = false;
+#endif
 
 	if (!query_buf || query_buf->len <= 0)
 	{
@@ -4903,6 +4913,58 @@ do_watch(PQExpBuffer query_buf, double sleep)
 		return false;
 	}
 
+#ifndef WIN32
+	sigemptyset(&sigalrm_sigchld_sigint);
+	sigaddset(&sigalrm_sigchld_sigint, SIGCHLD);
+	sigaddset(&sigalrm_sigchld_sigint, SIGALRM);
+	sigaddset(&sigalrm_sigchld_sigint, SIGINT);
+
+	sigemptyset(&sigalrm_sigchld);
+	sigaddset(&sigalrm_sigchld, SIGCHLD);
+	sigaddset(&sigalrm_sigchld, SIGALRM);
+
+	sigemptyset(&sigint);
+	sigaddset(&sigint, SIGINT);
+
+	/*
+	 * Block SIGALRM and SIGCHLD before we start the timer and the pager (if
+	 * configured), to avoid races.  sigwait() will receive them.
+	 */
+	sigprocmask(SIG_BLOCK, &sigalrm_sigchld, NULL);
+
+	/*
+	 * Set a timer to interrupt sigwait() so we can run the query at the
+	 * requested intervals.
+	 */
+	interval.it_value.tv_sec = sleep_ms / 1000;
+	interval.it_value.tv_usec = (sleep_ms % 1000) * 1000;
+	interval.it_interval = interval.it_value;
+	if (setitimer(ITIMER_REAL, &interval, NULL) < 0)
+	{
+		pg_log_error("could not set timer: %m");
+		done = true;
+	}
+#endif
+
+	/*
+	 * For \watch, we ignore the size of the result and always use the pager if
+	 * PSQL_WATCH_PAGER is set.  We also ignore the regular PSQL_PAGER or PAGER
+	 * environment variables, because traditional pagers probably won't be very
+	 * useful for showing a stream of results.
+	 */
+#ifndef WIN32
+	pagerprog = getenv("PSQL_WATCH_PAGER");
+#endif
+	if (pagerprog && myopt.topt.pager)
+	{
+		disable_sigpipe_trap();
+		pagerpipe = popen(pagerprog, "w");
+
+		if (!pagerpipe)
+			/*silently proceed without pager */
+			restore_sigpipe_trap();
+	}
+
 	/*
 	 * Choose format for timestamps.  We might eventually make this a \pset
 	 * option.  In the meantime, using a variable for the format suppresses
@@ -4911,10 +4973,12 @@ do_watch(PQExpBuffer query_buf, double sleep)
 	strftime_fmt = "%c";
 
 	/*
-	 * Set up rendering options, in particular, disable the pager, because
-	 * nobody wants to be prompted while watching the output of 'watch'.
+	 * Set up rendering options, in particular, disable the pager unless
+	 * PSQL_WATCH_PAGER was successfully launched.
 	 */
-	myopt.topt.pager = 0;
+	if (!pagerpipe)
+		myopt.topt.pager = 0;
+
 
 	/*
 	 * If there's a title in the user configuration, make sure we have room
@@ -4929,7 +4993,6 @@ do_watch(PQExpBuffer query_buf, double sleep)
 	{
 		time_t		timer;
 		char		timebuf[128];
-		long		i;
 
 		/*
 		 * Prepare title for output.  Note that we intentionally include a
@@ -4948,7 +5011,7 @@ do_watch(PQExpBuffer query_buf, double sleep)
 		myopt.title = title;
 
 		/* Run the query and print out the results */
-		res = PSQLexecWatch(query_buf->data, &myopt);
+		res = PSQLexecWatch(query_buf->data, &myopt, pagerpipe);
 
 		/*
 		 * PSQLexecWatch handles the case where we can no longer repeat the
@@ -4957,6 +5020,10 @@ do_watch(PQExpBuffer query_buf, double sleep)
 		if (res <= 0)
 			break;
 
+		if (pagerpipe && ferror(pagerpipe))
+			break;
+
+#ifdef WIN32
 		/*
 		 * Set up cancellation of 'watch' via SIGINT.  We redo this each time
 		 * through the loop since it's conceivable something inside
@@ -4967,12 +5034,10 @@ do_watch(PQExpBuffer query_buf, double sleep)
 
 		/*
 		 * Enable 'watch' cancellations and wait a while before running the
-		 * query again.  Break the sleep into short intervals (at most 1s)
-		 * since pg_usleep isn't interruptible on some platforms.
+		 * query again.  Break the sleep into short intervals (at most 1s).
 		 */
 		sigint_interrupt_enabled = true;
-		i = sleep_ms;
-		while (i > 0)
+		for (int i = sleep_ms; i > 0;)
 		{
 			long		s = Min(i, 1000L);
 
@@ -4982,8 +5047,57 @@ do_watch(PQExpBuffer query_buf, double sleep)
 			i -= s;
 		}
 		sigint_interrupt_enabled = false;
+#else
+		/* sigwait() will handle SIGINT. */
+		sigprocmask(SIG_BLOCK, &sigint, NULL);
+		if (cancel_pressed)
+			done = true;
+
+		/* Wait for SIGINT, SIGCHLD or SIGALRM. */
+		while (!done)
+		{
+			int signal_received;
+
+			if (sigwait(&sigalrm_sigchld_sigint, &signal_received) < 0)
+			{
+				/* Some other signal arrived? */
+				if (errno == EINTR)
+					continue;
+				else
+				{
+					pg_log_error("could not wait for signals: %m");
+					done = true;
+					break;
+				}
+			}
+			/* On ^C or pager exit, it's time to stop running the query. */
+			if (signal_received == SIGINT || signal_received == SIGCHLD)
+				done = true;
+			/* Otherwise, we must have SIGALRM.  Time to run the query again. */
+			break;
+		}
+
+		/* Unblock SIGINT so that slow queries can be interrupted. */
+		sigprocmask(SIG_UNBLOCK, &sigint, NULL);
+		if (done)
+			break;
+#endif
 	}
 
+	if (pagerpipe)
+	{
+		pclose(pagerpipe);
+		restore_sigpipe_trap();
+	}
+
+#ifndef WIN32
+	/* Disable the interval timer. */
+	memset(&interval, 0, sizeof(interval));
+	setitimer(ITIMER_REAL, &interval, NULL);
+	/* Unblock SIGINT, SIGCHLD and SIGALRM. */
+	sigprocmask(SIG_UNBLOCK, &sigalrm_sigchld_sigint, NULL);
+#endif
+
 	pg_free(title);
 	return (res >= 0);
 }
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 7a95465111..73b6ecfa14 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -592,12 +592,13 @@ PSQLexec(const char *query)
  * e.g., because of the interrupt, -1 on error.
  */
 int
-PSQLexecWatch(const char *query, const printQueryOpt *opt)
+PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	PGresult   *res;
 	double		elapsed_msec = 0;
 	instr_time	before;
 	instr_time	after;
+	FILE	   *restoreQueryFout = NULL;
 
 	if (!pset.db)
 	{
@@ -605,6 +606,12 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 		return 0;
 	}
 
+	if (printQueryFout)
+	{
+		restoreQueryFout = pset.queryFout;
+		pset.queryFout = printQueryFout;
+	}
+
 	SetCancelConn(pset.db);
 
 	if (pset.timing)
@@ -670,6 +677,9 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 
 	fflush(pset.queryFout);
 
+	if (restoreQueryFout)
+		pset.queryFout = restoreQueryFout;
+
 	/* Possible microtiming output */
 	if (pset.timing)
 		PrintTiming(elapsed_msec);
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index 041b2ac068..d8538a4e06 100644
--- a/src/bin/psql/common.h
+++ b/src/bin/psql/common.h
@@ -29,7 +29,7 @@ extern sigjmp_buf sigint_interrupt_jmp;
 extern void psql_setup_cancel_handler(void);
 
 extern PGresult *PSQLexec(const char *query);
-extern int	PSQLexecWatch(const char *query, const printQueryOpt *opt);
+extern int	PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout);
 
 extern bool SendQuery(const char *query);
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 97fc680f1f..9c53eb06a2 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -347,7 +347,7 @@ helpVariables(unsigned short int pager)
 	 * Windows builds currently print one more line than non-Windows builds.
 	 * Using the larger number is fine.
 	 */
-	output = PageOutput(158, pager ? &(pset.popt.topt) : NULL);
+	output = PageOutput(160, pager ? &(pset.popt.topt) : NULL);
 
 	fprintf(output, _("List of specially treated variables\n\n"));
 
@@ -505,6 +505,10 @@ helpVariables(unsigned short int pager)
 					  "    alternative location for the command history file\n"));
 	fprintf(output, _("  PSQL_PAGER, PAGER\n"
 					  "    name of external pager program\n"));
+#ifndef WIN32
+	fprintf(output, _("  PSQL_WATCH_PAGER\n"
+					  "    name of external pager program used for \\watch\n"));
+#endif
 	fprintf(output, _("  PSQLRC\n"
 					  "    alternative location for the user's .psqlrc file\n"));
 	fprintf(output, _("  SHELL\n"
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 110906a4e9..369b34fd84 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -110,6 +110,11 @@ log_locus_callback(const char **filename, uint64 *lineno)
 	}
 }
 
+static void
+empty_signal_handler(SIGNAL_ARGS)
+{
+}
+
 /*
  *
  * main
@@ -302,6 +307,15 @@ main(int argc, char *argv[])
 
 	psql_setup_cancel_handler();
 
+	/*
+	 * do_watch() needs signal handlers installed (otherwise sigwait() will
+	 * filter them out on some platforms), but doesn't need them to do
+	 * anything, and they shouldn't ever run (unless perhaps a stray SIGALRM
+	 * arrives due to a race when do_watch() cancels an itimer).
+	 */
+	pqsignal(SIGCHLD, empty_signal_handler);
+	pqsignal(SIGALRM, empty_signal_handler);
+
 	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
 
 	SyncVariables();

Reply via email to