psql: add an optional execution-count limit to \watch.
\watch can now be told to stop after N executions of the query. With the idea that we might want to add more options to \watch in future, this patch generalizes the command's syntax to a list of name=value options, with the interval allowed to omit the name for backwards compatibility. Andrey Borodin, reviewed by Kyotaro Horiguchi, Nathan Bossart, Michael Paquier, Yugo Nagata, and myself Discussion: https://postgr.es/m/CAAhFRxiZ2-n_L1ErMm9AZjgmUK=qS6VHb+0SaMn8sqqbhF7How@mail.gmail.com
This commit is contained in:
parent
2820adf775
commit
00beecfe83
@ -3551,12 +3551,16 @@ testdb=> <userinput>\setenv LESS -imx4F</userinput>
|
|||||||
|
|
||||||
|
|
||||||
<varlistentry id="app-psql-meta-command-watch">
|
<varlistentry id="app-psql-meta-command-watch">
|
||||||
<term><literal>\watch [ <replaceable class="parameter">seconds</replaceable> ]</literal></term>
|
<term><literal>\watch [ i[nterval]=<replaceable class="parameter">seconds</replaceable> ] [ c[ount]=<replaceable class="parameter">times</replaceable> ] [ <replaceable class="parameter">seconds</replaceable> ]</literal></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Repeatedly execute the current query buffer (as <literal>\g</literal> does)
|
Repeatedly execute the current query buffer (as <literal>\g</literal> does)
|
||||||
until interrupted or the query fails. Wait the specified number of
|
until interrupted, or the query fails, or the execution count limit
|
||||||
seconds (default 2) between executions. Each query result is
|
(if given) is reached. Wait the specified number of
|
||||||
|
seconds (default 2) 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
|
||||||
displayed with a header that includes the <literal>\pset title</literal>
|
displayed with a header that includes the <literal>\pset title</literal>
|
||||||
string (if any), the time as of query start, and the delay interval.
|
string (if any), the time as of query start, and the delay interval.
|
||||||
</para>
|
</para>
|
||||||
|
@ -162,7 +162,7 @@ static bool do_connect(enum trivalue reuse_previous_specification,
|
|||||||
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
|
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
|
||||||
int lineno, bool discard_on_quit, bool *edited);
|
int lineno, bool discard_on_quit, bool *edited);
|
||||||
static bool do_shell(const char *command);
|
static bool do_shell(const char *command);
|
||||||
static bool do_watch(PQExpBuffer query_buf, double sleep);
|
static bool do_watch(PQExpBuffer query_buf, double sleep, int iter);
|
||||||
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
|
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
|
||||||
Oid *obj_oid);
|
Oid *obj_oid);
|
||||||
static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
|
static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
|
||||||
@ -2759,7 +2759,8 @@ exec_command_write(PsqlScanState scan_state, bool active_branch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* \watch -- execute a query every N seconds
|
* \watch -- execute a query every N seconds.
|
||||||
|
* Optionally, stop after M iterations.
|
||||||
*/
|
*/
|
||||||
static backslashResult
|
static backslashResult
|
||||||
exec_command_watch(PsqlScanState scan_state, bool active_branch,
|
exec_command_watch(PsqlScanState scan_state, bool active_branch,
|
||||||
@ -2769,32 +2770,109 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
|
|||||||
|
|
||||||
if (active_branch)
|
if (active_branch)
|
||||||
{
|
{
|
||||||
char *opt = psql_scan_slash_option(scan_state,
|
bool have_sleep = false;
|
||||||
OT_NORMAL, NULL, true);
|
bool have_iter = false;
|
||||||
double sleep = 2;
|
double sleep = 2;
|
||||||
|
int iter = 0;
|
||||||
|
|
||||||
/* Convert optional sleep-length argument */
|
/*
|
||||||
if (opt)
|
* Parse arguments. We allow either an unlabeled interval or
|
||||||
|
* "name=value", where name is from the set ('i', 'interval', 'c',
|
||||||
|
* 'count').
|
||||||
|
*/
|
||||||
|
while (success)
|
||||||
{
|
{
|
||||||
|
char *opt = psql_scan_slash_option(scan_state,
|
||||||
|
OT_NORMAL, NULL, true);
|
||||||
|
char *valptr;
|
||||||
char *opt_end;
|
char *opt_end;
|
||||||
|
|
||||||
errno = 0;
|
if (!opt)
|
||||||
sleep = strtod(opt, &opt_end);
|
break; /* no more arguments */
|
||||||
if (sleep < 0 || *opt_end || errno == ERANGE)
|
|
||||||
|
valptr = strchr(opt, '=');
|
||||||
|
if (valptr)
|
||||||
{
|
{
|
||||||
pg_log_error("\\watch: incorrect interval value '%s'", opt);
|
/* Labeled argument */
|
||||||
free(opt);
|
valptr++;
|
||||||
resetPQExpBuffer(query_buf);
|
if (strncmp("i=", opt, strlen("i=")) == 0 ||
|
||||||
psql_scan_reset(scan_state);
|
strncmp("interval=", opt, strlen("interval=")) == 0)
|
||||||
return PSQL_CMD_ERROR;
|
{
|
||||||
|
if (have_sleep)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: interval value is specified more than once");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
have_sleep = true;
|
||||||
|
errno = 0;
|
||||||
|
sleep = strtod(valptr, &opt_end);
|
||||||
|
if (sleep < 0 || *opt_end || errno == ERANGE)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: incorrect interval value \"%s\"", valptr);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strncmp("c=", opt, strlen("c=")) == 0 ||
|
||||||
|
strncmp("count=", opt, strlen("count=")) == 0)
|
||||||
|
{
|
||||||
|
if (have_iter)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: iteration count is specified more than once");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
have_iter = true;
|
||||||
|
errno = 0;
|
||||||
|
iter = strtoint(valptr, &opt_end, 10);
|
||||||
|
if (iter <= 0 || *opt_end || errno == ERANGE)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: incorrect iteration count \"%s\"", valptr);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: unrecognized parameter \"%s\"", opt);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Unlabeled argument: take it as interval */
|
||||||
|
if (have_sleep)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: interval value is specified more than once");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
have_sleep = true;
|
||||||
|
errno = 0;
|
||||||
|
sleep = strtod(opt, &opt_end);
|
||||||
|
if (sleep < 0 || *opt_end || errno == ERANGE)
|
||||||
|
{
|
||||||
|
pg_log_error("\\watch: incorrect interval value \"%s\"", opt);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
free(opt);
|
free(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If query_buf is empty, recall and execute previous query */
|
/* If we parsed arguments successfully, do the command */
|
||||||
(void) copy_previous_query(query_buf, previous_buf);
|
if (success)
|
||||||
|
{
|
||||||
|
/* If query_buf is empty, recall and execute previous query */
|
||||||
|
(void) copy_previous_query(query_buf, previous_buf);
|
||||||
|
|
||||||
success = do_watch(query_buf, sleep);
|
success = do_watch(query_buf, sleep, iter);
|
||||||
|
}
|
||||||
|
|
||||||
/* Reset the query buffer as though for \r */
|
/* Reset the query buffer as though for \r */
|
||||||
resetPQExpBuffer(query_buf);
|
resetPQExpBuffer(query_buf);
|
||||||
@ -5071,7 +5149,7 @@ do_shell(const char *command)
|
|||||||
* onto a bunch of exec_command's variables to silence stupider compilers.
|
* onto a bunch of exec_command's variables to silence stupider compilers.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
do_watch(PQExpBuffer query_buf, double sleep)
|
do_watch(PQExpBuffer query_buf, double sleep, int iter)
|
||||||
{
|
{
|
||||||
long sleep_ms = (long) (sleep * 1000);
|
long sleep_ms = (long) (sleep * 1000);
|
||||||
printQueryOpt myopt = pset.popt;
|
printQueryOpt myopt = pset.popt;
|
||||||
@ -5204,6 +5282,10 @@ do_watch(PQExpBuffer query_buf, double sleep)
|
|||||||
if (res <= 0)
|
if (res <= 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* If we have iteration count, check that it's not exceeded yet */
|
||||||
|
if (iter && (--iter <= 0))
|
||||||
|
break;
|
||||||
|
|
||||||
if (pagerpipe && ferror(pagerpipe))
|
if (pagerpipe && ferror(pagerpipe))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ slashUsage(unsigned short int pager)
|
|||||||
HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n");
|
HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n");
|
||||||
HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
|
HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
|
||||||
HELP0(" \\q quit psql\n");
|
HELP0(" \\q quit psql\n");
|
||||||
HELP0(" \\watch [SEC] execute query every SEC seconds\n");
|
HELP0(" \\watch [[i=]SEC] [c=N] execute query every SEC seconds, up to N times\n");
|
||||||
HELP0("\n");
|
HELP0("\n");
|
||||||
|
|
||||||
HELP0("Help\n");
|
HELP0("Help\n");
|
||||||
|
@ -350,21 +350,38 @@ psql_like(
|
|||||||
'\copy from with DEFAULT'
|
'\copy from with DEFAULT'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Check \watch
|
||||||
|
psql_like(
|
||||||
|
$node,
|
||||||
|
'SELECT 1 \watch c=3 i=0.01',
|
||||||
|
qr/1\n1\n1/,
|
||||||
|
'\watch with 3 iterations');
|
||||||
|
|
||||||
# Check \watch errors
|
# Check \watch errors
|
||||||
psql_fails_like(
|
psql_fails_like(
|
||||||
$node,
|
$node,
|
||||||
'SELECT 1;\watch -10',
|
'SELECT 1 \watch -10',
|
||||||
qr/incorrect interval value '-10'/,
|
qr/incorrect interval value "-10"/,
|
||||||
'\watch, negative interval');
|
'\watch, negative interval');
|
||||||
psql_fails_like(
|
psql_fails_like(
|
||||||
$node,
|
$node,
|
||||||
'SELECT 1;\watch 10ab',
|
'SELECT 1 \watch 10ab',
|
||||||
qr/incorrect interval value '10ab'/,
|
qr/incorrect interval value "10ab"/,
|
||||||
'\watch incorrect interval');
|
'\watch, incorrect interval');
|
||||||
psql_fails_like(
|
psql_fails_like(
|
||||||
$node,
|
$node,
|
||||||
'SELECT 1;\watch 10e400',
|
'SELECT 1 \watch 10e400',
|
||||||
qr/incorrect interval value '10e400'/,
|
qr/incorrect interval value "10e400"/,
|
||||||
'\watch out-of-range interval');
|
'\watch, out-of-range interval');
|
||||||
|
psql_fails_like(
|
||||||
|
$node,
|
||||||
|
'SELECT 1 \watch 1 1',
|
||||||
|
qr/interval value is specified more than once/,
|
||||||
|
'\watch, interval value is specified more than once');
|
||||||
|
psql_fails_like(
|
||||||
|
$node,
|
||||||
|
'SELECT 1 \watch c=1 c=1',
|
||||||
|
qr/iteration count is specified more than once/,
|
||||||
|
'\watch, iteration count is specified more than once');
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
@ -4536,7 +4536,7 @@ invalid command \lo
|
|||||||
\timing arg1
|
\timing arg1
|
||||||
\unset arg1
|
\unset arg1
|
||||||
\w arg1
|
\w arg1
|
||||||
\watch arg1
|
\watch arg1 arg2
|
||||||
\x arg1
|
\x arg1
|
||||||
-- \else here is eaten as part of OT_FILEPIPE argument
|
-- \else here is eaten as part of OT_FILEPIPE argument
|
||||||
\w |/no/such/file \else
|
\w |/no/such/file \else
|
||||||
|
@ -1022,7 +1022,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
|
|||||||
\timing arg1
|
\timing arg1
|
||||||
\unset arg1
|
\unset arg1
|
||||||
\w arg1
|
\w arg1
|
||||||
\watch arg1
|
\watch arg1 arg2
|
||||||
\x arg1
|
\x arg1
|
||||||
-- \else here is eaten as part of OT_FILEPIPE argument
|
-- \else here is eaten as part of OT_FILEPIPE argument
|
||||||
\w |/no/such/file \else
|
\w |/no/such/file \else
|
||||||
|
Loading…
x
Reference in New Issue
Block a user