mirror of
https://github.com/postgres/postgres.git
synced 2025-06-18 00:02:37 -04: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:
parent
f014b1b9bb
commit
7c09d2797e
@ -3002,6 +3002,16 @@ lo_import 152801
|
|||||||
(such as <filename>more</filename>) is used.
|
(such as <filename>more</filename>) is used.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
When the <literal>pager</literal> option is <literal>off</literal>, the pager
|
When the <literal>pager</literal> option is <literal>off</literal>, the pager
|
||||||
program is not used. When the <literal>pager</literal> option is
|
program is not used. When the <literal>pager</literal> option is
|
||||||
@ -4672,6 +4682,24 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><envar>PSQLRC</envar></term>
|
<term><envar>PSQLRC</envar></term>
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <sys/stat.h> /* for stat() */
|
#include <sys/stat.h> /* for stat() */
|
||||||
|
#include <sys/time.h> /* for setitimer() */
|
||||||
#include <fcntl.h> /* open() flags */
|
#include <fcntl.h> /* open() flags */
|
||||||
#include <unistd.h> /* for geteuid(), getpid(), stat() */
|
#include <unistd.h> /* for geteuid(), getpid(), stat() */
|
||||||
#else
|
#else
|
||||||
@ -4894,8 +4895,17 @@ do_watch(PQExpBuffer query_buf, double sleep)
|
|||||||
const char *strftime_fmt;
|
const char *strftime_fmt;
|
||||||
const char *user_title;
|
const char *user_title;
|
||||||
char *title;
|
char *title;
|
||||||
|
const char *pagerprog = NULL;
|
||||||
|
FILE *pagerpipe = NULL;
|
||||||
int title_len;
|
int title_len;
|
||||||
int res = 0;
|
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)
|
if (!query_buf || query_buf->len <= 0)
|
||||||
{
|
{
|
||||||
@ -4903,6 +4913,58 @@ do_watch(PQExpBuffer query_buf, double sleep)
|
|||||||
return false;
|
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
|
* Choose format for timestamps. We might eventually make this a \pset
|
||||||
* option. In the meantime, using a variable for the format suppresses
|
* 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";
|
strftime_fmt = "%c";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up rendering options, in particular, disable the pager, because
|
* Set up rendering options, in particular, disable the pager unless
|
||||||
* nobody wants to be prompted while watching the output of 'watch'.
|
* 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
|
* 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;
|
time_t timer;
|
||||||
char timebuf[128];
|
char timebuf[128];
|
||||||
long i;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare title for output. Note that we intentionally include a
|
* Prepare title for output. Note that we intentionally include a
|
||||||
@ -4948,7 +5011,7 @@ do_watch(PQExpBuffer query_buf, double sleep)
|
|||||||
myopt.title = title;
|
myopt.title = title;
|
||||||
|
|
||||||
/* Run the query and print out the results */
|
/* 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
|
* 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)
|
if (res <= 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (pagerpipe && ferror(pagerpipe))
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up cancellation of 'watch' via SIGINT. We redo this each time
|
* Set up cancellation of 'watch' via SIGINT. We redo this each time
|
||||||
* through the loop since it's conceivable something inside
|
* 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
|
* Enable 'watch' cancellations and wait a while before running the
|
||||||
* query again. Break the sleep into short intervals (at most 1s)
|
* query again. Break the sleep into short intervals (at most 1s).
|
||||||
* since pg_usleep isn't interruptible on some platforms.
|
|
||||||
*/
|
*/
|
||||||
sigint_interrupt_enabled = true;
|
sigint_interrupt_enabled = true;
|
||||||
i = sleep_ms;
|
for (long i = sleep_ms; i > 0;)
|
||||||
while (i > 0)
|
|
||||||
{
|
{
|
||||||
long s = Min(i, 1000L);
|
long s = Min(i, 1000L);
|
||||||
|
|
||||||
@ -4982,8 +5048,57 @@ do_watch(PQExpBuffer query_buf, double sleep)
|
|||||||
i -= s;
|
i -= s;
|
||||||
}
|
}
|
||||||
sigint_interrupt_enabled = false;
|
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);
|
pg_free(title);
|
||||||
return (res >= 0);
|
return (res >= 0);
|
||||||
}
|
}
|
||||||
|
@ -592,12 +592,13 @@ PSQLexec(const char *query)
|
|||||||
* e.g., because of the interrupt, -1 on error.
|
* e.g., because of the interrupt, -1 on error.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
PSQLexecWatch(const char *query, const printQueryOpt *opt)
|
PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
|
||||||
{
|
{
|
||||||
PGresult *res;
|
PGresult *res;
|
||||||
double elapsed_msec = 0;
|
double elapsed_msec = 0;
|
||||||
instr_time before;
|
instr_time before;
|
||||||
instr_time after;
|
instr_time after;
|
||||||
|
FILE *fout;
|
||||||
|
|
||||||
if (!pset.db)
|
if (!pset.db)
|
||||||
{
|
{
|
||||||
@ -638,14 +639,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fout = printQueryFout ? printQueryFout : pset.queryFout;
|
||||||
|
|
||||||
switch (PQresultStatus(res))
|
switch (PQresultStatus(res))
|
||||||
{
|
{
|
||||||
case PGRES_TUPLES_OK:
|
case PGRES_TUPLES_OK:
|
||||||
printQuery(res, opt, pset.queryFout, false, pset.logfile);
|
printQuery(res, opt, fout, false, pset.logfile);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PGRES_COMMAND_OK:
|
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;
|
break;
|
||||||
|
|
||||||
case PGRES_EMPTY_QUERY:
|
case PGRES_EMPTY_QUERY:
|
||||||
@ -668,7 +671,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
|
|||||||
|
|
||||||
PQclear(res);
|
PQclear(res);
|
||||||
|
|
||||||
fflush(pset.queryFout);
|
fflush(fout);
|
||||||
|
|
||||||
/* Possible microtiming output */
|
/* Possible microtiming output */
|
||||||
if (pset.timing)
|
if (pset.timing)
|
||||||
|
@ -29,7 +29,7 @@ extern sigjmp_buf sigint_interrupt_jmp;
|
|||||||
extern void psql_setup_cancel_handler(void);
|
extern void psql_setup_cancel_handler(void);
|
||||||
|
|
||||||
extern PGresult *PSQLexec(const char *query);
|
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);
|
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.
|
* Windows builds currently print one more line than non-Windows builds.
|
||||||
* Using the larger number is fine.
|
* 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"));
|
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"));
|
" alternative location for the command history file\n"));
|
||||||
fprintf(output, _(" PSQL_PAGER, PAGER\n"
|
fprintf(output, _(" PSQL_PAGER, PAGER\n"
|
||||||
" name of external pager program\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"
|
fprintf(output, _(" PSQLRC\n"
|
||||||
" alternative location for the user's .psqlrc file\n"));
|
" alternative location for the user's .psqlrc file\n"));
|
||||||
fprintf(output, _(" SHELL\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
|
* main
|
||||||
@ -302,6 +309,18 @@ main(int argc, char *argv[])
|
|||||||
|
|
||||||
psql_setup_cancel_handler();
|
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);
|
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
|
||||||
|
|
||||||
SyncVariables();
|
SyncVariables();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user