/*------------------------------------------------------------------------- * * common.c * Common support routines for bin/scripts/ * * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/bin/scripts/common.c * *------------------------------------------------------------------------- */ #include "postgres_fe.h" #include #include #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)); } }