diff options
Diffstat (limited to 'src/bin/scripts/common.c')
-rw-r--r-- | src/bin/scripts/common.c | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c new file mode 100644 index 0000000..9e38b60 --- /dev/null +++ b/src/bin/scripts/common.c @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------- + * + * common.c + * Common support routines for bin/scripts/ + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/bin/scripts/common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include <signal.h> +#include <unistd.h> + +#include "common.h" +#include "common/connect.h" +#include "common/logging.h" +#include "common/string.h" +#include "fe_utils/cancel.h" +#include "fe_utils/query_utils.h" +#include "fe_utils/string_utils.h" + +/* + * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions. When you + * finish using them, pg_free(*table). *columns is a pointer into "spec", + * possibly to its NUL terminator. + */ +void +splitTableColumnsSpec(const char *spec, int encoding, + char **table, const char **columns) +{ + bool inquotes = false; + const char *cp = spec; + + /* + * Find the first '(' not identifier-quoted. Based on + * dequote_downcase_identifier(). + */ + while (*cp && (*cp != '(' || inquotes)) + { + if (*cp == '"') + { + if (inquotes && cp[1] == '"') + cp++; /* pair does not affect quoting */ + else + inquotes = !inquotes; + cp++; + } + else + cp += PQmblenBounded(cp, encoding); + } + *table = pnstrdup(spec, cp - spec); + *columns = cp; +} + +/* + * Break apart TABLE[(COLUMNS)] of "spec". With the reset_val of search_path + * in effect, have regclassin() interpret the TABLE portion. Append to "buf" + * the qualified name of TABLE, followed by any (COLUMNS). Exit on failure. + * We use this to interpret --table=foo under the search path psql would get, + * in advance of "ANALYZE public.foo" under the always-secure search path. + */ +void +appendQualifiedRelation(PQExpBuffer buf, const char *spec, + PGconn *conn, bool echo) +{ + char *table; + const char *columns; + PQExpBufferData sql; + PGresult *res; + int ntups; + + splitTableColumnsSpec(spec, PQclientEncoding(conn), &table, &columns); + + /* + * Query must remain ABSOLUTELY devoid of unqualified names. This would + * be unnecessary given a regclassin() variant taking a search_path + * argument. + */ + initPQExpBuffer(&sql); + appendPQExpBufferStr(&sql, + "SELECT c.relname, ns.nspname\n" + " FROM pg_catalog.pg_class c," + " pg_catalog.pg_namespace ns\n" + " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n" + " AND c.oid OPERATOR(pg_catalog.=) "); + appendStringLiteralConn(&sql, table, conn); + appendPQExpBufferStr(&sql, "::pg_catalog.regclass;"); + + executeCommand(conn, "RESET search_path;", echo); + + /* + * One row is a typical result, as is a nonexistent relation ERROR. + * regclassin() unconditionally accepts all-digits input as an OID; if no + * relation has that OID; this query returns no rows. Catalog corruption + * might elicit other row counts. + */ + res = executeQuery(conn, sql.data, echo); + ntups = PQntuples(res); + if (ntups != 1) + { + pg_log_error(ngettext("query returned %d row instead of one: %s", + "query returned %d rows instead of one: %s", + ntups), + ntups, sql.data); + PQfinish(conn); + exit(1); + } + appendPQExpBufferStr(buf, + fmtQualifiedId(PQgetvalue(res, 0, 1), + PQgetvalue(res, 0, 0))); + appendPQExpBufferStr(buf, columns); + PQclear(res); + termPQExpBuffer(&sql); + pg_free(table); + + PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo)); +} + + +/* + * Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither. + */ + +/* translator: abbreviation for "yes" */ +#define PG_YESLETTER gettext_noop("y") +/* translator: abbreviation for "no" */ +#define PG_NOLETTER gettext_noop("n") + +bool +yesno_prompt(const char *question) +{ + char prompt[256]; + + /*------ + translator: This is a question followed by the translated options for + "yes" and "no". */ + snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "), + _(question), _(PG_YESLETTER), _(PG_NOLETTER)); + + for (;;) + { + char *resp; + + resp = simple_prompt(prompt, true); + + if (strcmp(resp, _(PG_YESLETTER)) == 0) + { + free(resp); + return true; + } + if (strcmp(resp, _(PG_NOLETTER)) == 0) + { + free(resp); + return false; + } + free(resp); + + printf(_("Please answer \"%s\" or \"%s\".\n"), + _(PG_YESLETTER), _(PG_NOLETTER)); + } +} |