From a55836c699abcc657e0a85ca2f6dd4e03ecf9f4d Mon Sep 17 00:00:00 2001 From: "Andrey M. Borodin" Date: Thu, 16 Feb 2023 15:07:50 -0800 Subject: [PATCH v10] Iteration count argument to psql \watch command If the argument is not provided - continue to \watch forever. Authour: Andrey Borodin Reviewed-by: Kyotaro Horiguchi, Nathan Bossart, Michael Paquier, Yugo Nagata Thread: https://postgr.es/m/CAAhFRxiZ2-n_L1ErMm9AZjgmUK%3DqS6VHb%2B0SaMn8sqqbhF7How%40mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 6 +- src/bin/psql/command.c | 105 ++++++++++++++++++++++++----- src/bin/psql/help.c | 2 +- src/bin/psql/t/001_basic.pl | 21 +++++- src/test/regress/expected/psql.out | 2 +- src/test/regress/sql/psql.sql | 2 +- 6 files changed, 117 insertions(+), 21 deletions(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 7b8ae9fac3..35944af97f 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3551,7 +3551,7 @@ testdb=> \setenv LESS -imx4F - \watch [ seconds ] + \watch [ i[nterval]=seconds ] [ c[ount]=times ] [ seconds ] Repeatedly execute the current query buffer (as \g does) @@ -3564,6 +3564,10 @@ testdb=> \setenv LESS -imx4F If the current query buffer is empty, the most recently sent query is re-executed instead. + + If number of iterations is specified, query will be executed only + given number of times. + diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 61ec049f05..cdfafac7a9 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -162,7 +162,7 @@ static bool do_connect(enum trivalue reuse_previous_specification, static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool discard_on_quit, bool *edited); 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, Oid *obj_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 for M iteration. */ static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch, @@ -2771,30 +2772,97 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch, { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + bool have_sleep = false; + bool have_iter = false; double sleep = 2; + int iter = 0; /* Convert optional sleep-length argument */ - if (opt) + + while (opt) { - char *opt_end; + /* + * We can have either sleep interval or "name=value", where name is + * from the set ('i','interval','c','count') + */ + char *valptr = strchr(opt, '='); + char *opt_end; - errno = 0; - sleep = strtod(opt, &opt_end); - if (sleep < 0 || *opt_end || errno == ERANGE) + if (valptr) { - pg_log_error("\\watch: incorrect interval value '%s'", opt); - free(opt); - resetPQExpBuffer(query_buf); - psql_scan_reset(scan_state); - return PSQL_CMD_ERROR; + valptr++; + if (strncmp("i", opt, strlen("i")) == 0 || + strncmp("interval", opt, strlen("interval")) == 0) + { + errno = 0; + sleep = strtod(valptr, &opt_end); + if (sleep < 0 || *opt_end || errno == ERANGE || have_sleep) + { + if (have_sleep) + pg_log_error("\\watch: interval value is specified more than once"); + else + pg_log_error("\\watch: incorrect interval value '%s'", valptr); + free(opt); + resetPQExpBuffer(query_buf); + psql_scan_reset(scan_state); + return PSQL_CMD_ERROR; + } + have_sleep = true; + } + else if (strncmp("c", opt, strlen("c")) == 0 || + strncmp("count", opt, strlen("count")) == 0) + { + errno = 0; + iter = strtol(valptr, &opt_end, 10); + if (iter <= 0 || *opt_end || errno == ERANGE || have_iter) + { + if (have_iter) + pg_log_error("\\watch: iteration count is specified more than once"); + else + pg_log_error("\\watch: incorrect iteration count '%s'", valptr); + free(opt); + resetPQExpBuffer(query_buf); + psql_scan_reset(scan_state); + return PSQL_CMD_ERROR; + } + have_iter = true; + } + else + { + pg_log_error("Unknown \\watch argument '%s'", opt); + free(opt); + resetPQExpBuffer(query_buf); + psql_scan_reset(scan_state); + return PSQL_CMD_ERROR; + } } + else + { + errno = 0; + sleep = strtod(opt, &opt_end); + if (sleep < 0 || *opt_end || errno == ERANGE || have_sleep) + { + if (have_sleep) + pg_log_error("\\watch: interval value is specified more than once"); + else + pg_log_error("\\watch: incorrect interval value '%s'", opt); + free(opt); + resetPQExpBuffer(query_buf); + psql_scan_reset(scan_state); + return PSQL_CMD_ERROR; + } + have_sleep = true; + } + free(opt); + opt = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); } /* 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 */ resetPQExpBuffer(query_buf); @@ -5056,7 +5124,7 @@ do_shell(const char *command) * onto a bunch of exec_command's variables to silence stupider compilers. */ static bool -do_watch(PQExpBuffer query_buf, double sleep) +do_watch(PQExpBuffer query_buf, double sleep, int iter) { long sleep_ms = (long) (sleep * 1000); printQueryOpt myopt = pset.popt; @@ -5158,11 +5226,18 @@ do_watch(PQExpBuffer query_buf, double sleep) title_len = (user_title ? strlen(user_title) : 0) + 256; title = pg_malloc(title_len); - for (;;) + for (int i = 1;;) { time_t timer; char timebuf[128]; + /* If we have iteration count - check that it's not exceeded yet */ + /* Keep in mind that first iteration was performed before do_watch() */ + if (iter && (i++ == iter)) + { + break; + } + /* * Prepare title for output. Note that we intentionally include a * newline at the end of the title; this is somewhat historical but it diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index e45c4aaca5..1062d0ed7b 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -200,7 +200,7 @@ slashUsage(unsigned short int pager) 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(" \\q quit psql\n"); - HELP0(" \\watch [SEC] execute query every SEC seconds\n"); + HELP0(" \\watch [c=N] [SEC] execute query every SEC seconds N times\n"); HELP0("\n"); HELP0("Help\n"); diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl index 64ce012062..b5cf7b1b43 100644 --- a/src/bin/psql/t/001_basic.pl +++ b/src/bin/psql/t/001_basic.pl @@ -350,6 +350,13 @@ psql_like( '\copy from with DEFAULT' ); +# Check \watch +psql_like( + $node, + 'SELECT 1;\watch c=3 i=0', + qr/1\n1\n1/, + '\watch with 3 iterations'); + # Check \watch errors psql_fails_like( $node, @@ -360,11 +367,21 @@ psql_fails_like( $node, 'SELECT 1;\watch 10ab', qr/incorrect interval value '10ab'/, - '\watch incorrect interval'); + '\watch, incorrect interval'); psql_fails_like( $node, 'SELECT 1;\watch 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/nterval 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(); diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index c00e28361c..956e475447 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4536,7 +4536,7 @@ invalid command \lo \timing arg1 \unset arg1 \w arg1 - \watch arg1 + \watch arg1 arg2 \x arg1 -- \else here is eaten as part of OT_FILEPIPE argument \w |/no/such/file \else diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 961783d6ea..630f638f02 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1022,7 +1022,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; \timing arg1 \unset arg1 \w arg1 - \watch arg1 + \watch arg1 arg2 \x arg1 -- \else here is eaten as part of OT_FILEPIPE argument \w |/no/such/file \else -- 2.37.1 (Apple Git-137.1)