Skip to content

Commit 00beecf

Browse files
committed
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
1 parent 2820adf commit 00beecf

File tree

6 files changed

+135
-32
lines changed

6 files changed

+135
-32
lines changed

doc/src/sgml/ref/psql-ref.sgml

+7-3
Original file line numberDiff line numberDiff line change
@@ -3551,12 +3551,16 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
35513551

35523552

35533553
<varlistentry id="app-psql-meta-command-watch">
3554-
<term><literal>\watch [ <replaceable class="parameter">seconds</replaceable> ]</literal></term>
3554+
<term><literal>\watch [ i[nterval]=<replaceable class="parameter">seconds</replaceable> ] [ c[ount]=<replaceable class="parameter">times</replaceable> ] [ <replaceable class="parameter">seconds</replaceable> ]</literal></term>
35553555
<listitem>
35563556
<para>
35573557
Repeatedly execute the current query buffer (as <literal>\g</literal> does)
3558-
until interrupted or the query fails. Wait the specified number of
3559-
seconds (default 2) between executions. Each query result is
3558+
until interrupted, or the query fails, or the execution count limit
3559+
(if given) is reached. Wait the specified number of
3560+
seconds (default 2) between executions. For backwards compatibility,
3561+
<replaceable class="parameter">seconds</replaceable> can be specified
3562+
with or without an <literal>interval=</literal> prefix.
3563+
Each query result is
35603564
displayed with a header that includes the <literal>\pset title</literal>
35613565
string (if any), the time as of query start, and the delay interval.
35623566
</para>

src/bin/psql/command.c

+100-18
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ static bool do_connect(enum trivalue reuse_previous_specification,
162162
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
163163
int lineno, bool discard_on_quit, bool *edited);
164164
static bool do_shell(const char *command);
165-
static bool do_watch(PQExpBuffer query_buf, double sleep);
165+
static bool do_watch(PQExpBuffer query_buf, double sleep, int iter);
166166
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
167167
Oid *obj_oid);
168168
static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
@@ -2759,7 +2759,8 @@ exec_command_write(PsqlScanState scan_state, bool active_branch,
27592759
}
27602760

27612761
/*
2762-
* \watch -- execute a query every N seconds
2762+
* \watch -- execute a query every N seconds.
2763+
* Optionally, stop after M iterations.
27632764
*/
27642765
static backslashResult
27652766
exec_command_watch(PsqlScanState scan_state, bool active_branch,
@@ -2769,32 +2770,109 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
27692770

27702771
if (active_branch)
27712772
{
2772-
char *opt = psql_scan_slash_option(scan_state,
2773-
OT_NORMAL, NULL, true);
2773+
bool have_sleep = false;
2774+
bool have_iter = false;
27742775
double sleep = 2;
2776+
int iter = 0;
27752777

2776-
/* Convert optional sleep-length argument */
2777-
if (opt)
2778+
/*
2779+
* Parse arguments. We allow either an unlabeled interval or
2780+
* "name=value", where name is from the set ('i', 'interval', 'c',
2781+
* 'count').
2782+
*/
2783+
while (success)
27782784
{
2785+
char *opt = psql_scan_slash_option(scan_state,
2786+
OT_NORMAL, NULL, true);
2787+
char *valptr;
27792788
char *opt_end;
27802789

2781-
errno = 0;
2782-
sleep = strtod(opt, &opt_end);
2783-
if (sleep < 0 || *opt_end || errno == ERANGE)
2790+
if (!opt)
2791+
break; /* no more arguments */
2792+
2793+
valptr = strchr(opt, '=');
2794+
if (valptr)
27842795
{
2785-
pg_log_error("\\watch: incorrect interval value '%s'", opt);
2786-
free(opt);
2787-
resetPQExpBuffer(query_buf);
2788-
psql_scan_reset(scan_state);
2789-
return PSQL_CMD_ERROR;
2796+
/* Labeled argument */
2797+
valptr++;
2798+
if (strncmp("i=", opt, strlen("i=")) == 0 ||
2799+
strncmp("interval=", opt, strlen("interval=")) == 0)
2800+
{
2801+
if (have_sleep)
2802+
{
2803+
pg_log_error("\\watch: interval value is specified more than once");
2804+
success = false;
2805+
}
2806+
else
2807+
{
2808+
have_sleep = true;
2809+
errno = 0;
2810+
sleep = strtod(valptr, &opt_end);
2811+
if (sleep < 0 || *opt_end || errno == ERANGE)
2812+
{
2813+
pg_log_error("\\watch: incorrect interval value \"%s\"", valptr);
2814+
success = false;
2815+
}
2816+
}
2817+
}
2818+
else if (strncmp("c=", opt, strlen("c=")) == 0 ||
2819+
strncmp("count=", opt, strlen("count=")) == 0)
2820+
{
2821+
if (have_iter)
2822+
{
2823+
pg_log_error("\\watch: iteration count is specified more than once");
2824+
success = false;
2825+
}
2826+
else
2827+
{
2828+
have_iter = true;
2829+
errno = 0;
2830+
iter = strtoint(valptr, &opt_end, 10);
2831+
if (iter <= 0 || *opt_end || errno == ERANGE)
2832+
{
2833+
pg_log_error("\\watch: incorrect iteration count \"%s\"", valptr);
2834+
success = false;
2835+
}
2836+
}
2837+
}
2838+
else
2839+
{
2840+
pg_log_error("\\watch: unrecognized parameter \"%s\"", opt);
2841+
success = false;
2842+
}
2843+
}
2844+
else
2845+
{
2846+
/* Unlabeled argument: take it as interval */
2847+
if (have_sleep)
2848+
{
2849+
pg_log_error("\\watch: interval value is specified more than once");
2850+
success = false;
2851+
}
2852+
else
2853+
{
2854+
have_sleep = true;
2855+
errno = 0;
2856+
sleep = strtod(opt, &opt_end);
2857+
if (sleep < 0 || *opt_end || errno == ERANGE)
2858+
{
2859+
pg_log_error("\\watch: incorrect interval value \"%s\"", opt);
2860+
success = false;
2861+
}
2862+
}
27902863
}
2864+
27912865
free(opt);
27922866
}
27932867

2794-
/* If query_buf is empty, recall and execute previous query */
2795-
(void) copy_previous_query(query_buf, previous_buf);
2868+
/* If we parsed arguments successfully, do the command */
2869+
if (success)
2870+
{
2871+
/* If query_buf is empty, recall and execute previous query */
2872+
(void) copy_previous_query(query_buf, previous_buf);
27962873

2797-
success = do_watch(query_buf, sleep);
2874+
success = do_watch(query_buf, sleep, iter);
2875+
}
27982876

27992877
/* Reset the query buffer as though for \r */
28002878
resetPQExpBuffer(query_buf);
@@ -5071,7 +5149,7 @@ do_shell(const char *command)
50715149
* onto a bunch of exec_command's variables to silence stupider compilers.
50725150
*/
50735151
static bool
5074-
do_watch(PQExpBuffer query_buf, double sleep)
5152+
do_watch(PQExpBuffer query_buf, double sleep, int iter)
50755153
{
50765154
long sleep_ms = (long) (sleep * 1000);
50775155
printQueryOpt myopt = pset.popt;
@@ -5204,6 +5282,10 @@ do_watch(PQExpBuffer query_buf, double sleep)
52045282
if (res <= 0)
52055283
break;
52065284

5285+
/* If we have iteration count, check that it's not exceeded yet */
5286+
if (iter && (--iter <= 0))
5287+
break;
5288+
52075289
if (pagerpipe && ferror(pagerpipe))
52085290
break;
52095291

src/bin/psql/help.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ slashUsage(unsigned short int pager)
200200
HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n");
201201
HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
202202
HELP0(" \\q quit psql\n");
203-
HELP0(" \\watch [SEC] execute query every SEC seconds\n");
203+
HELP0(" \\watch [[i=]SEC] [c=N] execute query every SEC seconds, up to N times\n");
204204
HELP0("\n");
205205

206206
HELP0("Help\n");

src/bin/psql/t/001_basic.pl

+25-8
Original file line numberDiff line numberDiff line change
@@ -350,21 +350,38 @@ sub psql_fails_like
350350
'\copy from with DEFAULT'
351351
);
352352

353+
# Check \watch
354+
psql_like(
355+
$node,
356+
'SELECT 1 \watch c=3 i=0.01',
357+
qr/1\n1\n1/,
358+
'\watch with 3 iterations');
359+
353360
# Check \watch errors
354361
psql_fails_like(
355362
$node,
356-
'SELECT 1;\watch -10',
357-
qr/incorrect interval value '-10'/,
363+
'SELECT 1 \watch -10',
364+
qr/incorrect interval value "-10"/,
358365
'\watch, negative interval');
359366
psql_fails_like(
360367
$node,
361-
'SELECT 1;\watch 10ab',
362-
qr/incorrect interval value '10ab'/,
363-
'\watch incorrect interval');
368+
'SELECT 1 \watch 10ab',
369+
qr/incorrect interval value "10ab"/,
370+
'\watch, incorrect interval');
371+
psql_fails_like(
372+
$node,
373+
'SELECT 1 \watch 10e400',
374+
qr/incorrect interval value "10e400"/,
375+
'\watch, out-of-range interval');
376+
psql_fails_like(
377+
$node,
378+
'SELECT 1 \watch 1 1',
379+
qr/interval value is specified more than once/,
380+
'\watch, interval value is specified more than once');
364381
psql_fails_like(
365382
$node,
366-
'SELECT 1;\watch 10e400',
367-
qr/incorrect interval value '10e400'/,
368-
'\watch out-of-range interval');
383+
'SELECT 1 \watch c=1 c=1',
384+
qr/iteration count is specified more than once/,
385+
'\watch, iteration count is specified more than once');
369386

370387
done_testing();

src/test/regress/expected/psql.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -4536,7 +4536,7 @@ invalid command \lo
45364536
\timing arg1
45374537
\unset arg1
45384538
\w arg1
4539-
\watch arg1
4539+
\watch arg1 arg2
45404540
\x arg1
45414541
-- \else here is eaten as part of OT_FILEPIPE argument
45424542
\w |/no/such/file \else

src/test/regress/sql/psql.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
10221022
\timing arg1
10231023
\unset arg1
10241024
\w arg1
1025-
\watch arg1
1025+
\watch arg1 arg2
10261026
\x arg1
10271027
-- \else here is eaten as part of OT_FILEPIPE argument
10281028
\w |/no/such/file \else

0 commit comments

Comments
 (0)