postgresql/src/bin/scripts/common.c

168 lines
4.2 KiB
C

/*-------------------------------------------------------------------------
*
* common.c
* Common support routines for bin/scripts/
*
*
* Portions Copyright (c) 1996-2024, 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));
}
}