mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Add PSQL_WATCH_PAGER for psql's \watch command.
Allow a pager to be used by the \watch command. This works but isn't very useful with traditional pagers like "less", so use a different environment variable. The popular open source tool "pspg" (also by Pavel) knows how to display the output if you set PSQL_WATCH_PAGER="pspg --stream". To make \watch react quickly when the user quits the pager or presses ^C, and also to increase the accuracy of its timing and decrease the rate of useless context switches, change the main loop of the \watch command to use sigwait() rather than a sleeping/polling loop, on Unix. Supported on Unix only for now (like pspg). Author: Pavel Stehule <pavel.stehule@gmail.com> Author: Thomas Munro <thomas.munro@gmail.com> Discussion: https://postgr.es/m/CAFj8pRBfzUUPz-3gN5oAzto9SDuRSq-TQPfXU_P6h0L7hO%2BEhg%40mail.gmail.com
This commit is contained in:
		@@ -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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,11 +4973,13 @@ 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.
 | 
			
		||||
	 */
 | 
			
		||||
	if (!pagerpipe)
 | 
			
		||||
		myopt.topt.pager = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If there's a title in the user configuration, make sure we have room
 | 
			
		||||
	 * for it in the title buffer.  Allow 128 bytes for the timestamp plus 128
 | 
			
		||||
@@ -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,11 @@ 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 +5035,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 (long i = sleep_ms; i > 0;)
 | 
			
		||||
		{
 | 
			
		||||
			long		s = Min(i, 1000L);
 | 
			
		||||
 | 
			
		||||
@@ -4982,7 +5048,56 @@ 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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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	   *fout;
 | 
			
		||||
 | 
			
		||||
	if (!pset.db)
 | 
			
		||||
	{
 | 
			
		||||
@@ -638,14 +639,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fout = printQueryFout ? printQueryFout : pset.queryFout;
 | 
			
		||||
 | 
			
		||||
	switch (PQresultStatus(res))
 | 
			
		||||
	{
 | 
			
		||||
		case PGRES_TUPLES_OK:
 | 
			
		||||
			printQuery(res, opt, pset.queryFout, false, pset.logfile);
 | 
			
		||||
			printQuery(res, opt, fout, false, pset.logfile);
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case PGRES_COMMAND_OK:
 | 
			
		||||
			fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
 | 
			
		||||
			fprintf(fout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case PGRES_EMPTY_QUERY:
 | 
			
		||||
@@ -668,7 +671,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 | 
			
		||||
 | 
			
		||||
	PQclear(res);
 | 
			
		||||
 | 
			
		||||
	fflush(pset.queryFout);
 | 
			
		||||
	fflush(fout);
 | 
			
		||||
 | 
			
		||||
	/* Possible microtiming output */
 | 
			
		||||
	if (pset.timing)
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -110,6 +110,13 @@ log_locus_callback(const char **filename, uint64 *lineno)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
static void
 | 
			
		||||
empty_signal_handler(SIGNAL_ARGS)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * main
 | 
			
		||||
@@ -302,6 +309,18 @@ main(int argc, char *argv[])
 | 
			
		||||
 | 
			
		||||
	psql_setup_cancel_handler();
 | 
			
		||||
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * 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);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
 | 
			
		||||
 | 
			
		||||
	SyncVariables();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user