*** c/src/bin/psql/command.c --- w/src/bin/psql/command.c *************** *** 1433,1438 **** exec_command(const char *cmd, --- 1433,1562 ---- free(fname); } + /* \watch -- execute a query every N seconds */ + else if (strcmp(cmd, "watch") == 0) + { + char *value; + + /* Volatile to prevent clobbering by by longjmp. */ + volatile PGresult *res = NULL; + printQueryOpt myopt = pset.popt; + char quoted; + long sleep = 2; + int i; + char title[50]; + time_t timer; + + const int max_watch_delay = 86400; /* seconds in a day */ + + if (!query_buf || query_buf->len <= 0) + { + psql_error(_("Query buffer is empty, \\watch ignored.")); + + goto cleanup; + } + + /* + * Implement optional specification of time period for re-running the + * query. + */ + if ((value = psql_scan_slash_option(scan_state, + OT_NORMAL, "ed, false))) + { + sleep = strtol(value, NULL, 10); + + if (sleep <= 0) + sleep = 1; + else if (sleep > max_watch_delay) + sleep = max_watch_delay; + + free(value); + } + + /* + * Set up rendering options, in particular, disable the pager, because + * nobody wants to be prompted while watching the output of 'watch'. + */ + myopt.nullPrint = NULL; + myopt.topt.pager = 0; + + /* Set up cancellation of 'watch' via SIGINT. */ + if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) + goto cleanup; + + while (true) + { + timer = time(NULL); + + if (isatty(fileno(stdin))) + printf("%c[0;0H%c[2J", 0x1b, 0x1b); + + snprintf(title, sizeof(title), _("Watch every %lds\t%s"), sleep, + asctime(localtime(&timer))); + myopt.title = title; + + if (query_buf) + res = PSQLexec(query_buf->data, false); + else if (!pset.quiet) + { + psql_error(_("Query buffer is empty.")); + goto cleanup; + } + + /* + * If SIGINT is sent while the query is processing, PSQLexec will + * consume the interrupt. The user's intention, though, is to + * cancel the entire watch process, so detect a sent cancellation + * request and exit in this case. + */ + if (cancel_pressed) + goto cleanup; + + res = PSQLexec(query_buf->data, false); + + if (res) + { + ExecStatusType status = PQresultStatus((PGresult *) res); + + switch (status) + { + case PGRES_TUPLES_OK: + printQuery((PGresult *) res, &myopt, pset.queryFout, + pset.logfile); + break; + + case PGRES_EMPTY_QUERY: + psql_error( + _("\\watch cannot be used with an empty query\n")); + goto cleanup; + + default: + break; + } + + PQclear((PGresult *) res); + res = NULL; + } + + /* + * Enable 'watch' cancellations and wait a while before running the + * query again. + */ + sigint_interrupt_enabled = true; + for (i = 0; i < sleep; i++) + { + pg_usleep(1000000L); + if (cancel_pressed) + break; + } + sigint_interrupt_enabled = false; + } + + cleanup: + if (res) + PQclear((PGresult *) res); + } + /* \x -- set or toggle expanded table representation */ else if (strcmp(cmd, "x") == 0) { *** c/src/bin/psql/help.c --- w/src/bin/psql/help.c *************** *** 195,200 **** slashUsage(unsigned short int pager) --- 195,201 ---- fprintf(output, _(" \\ir FILE as \\i, but relative to location of current script\n")); fprintf(output, _(" \\o [FILE] send all query results to file or |pipe\n")); fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n")); + fprintf(output, _(" \\watch [SEC] execute query every SEC seconds\n")); fprintf(output, "\n"); fprintf(output, _("Informational\n")); *** c/src/bin/psql/tab-complete.c --- w/src/bin/psql/tab-complete.c *************** *** 900,906 **** psql_completion(char *text, int start, int end) "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\sf", "\\t", "\\T", ! "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL }; (void) end; /* not used */ --- 900,906 ---- "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\sf", "\\t", "\\T", ! "\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL }; (void) end; /* not used */