summaryrefslogtreecommitdiffstats
path: root/src/bin/scripts/common.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/bin/scripts/common.c
parentInitial commit. (diff)
downloadpostgresql-14-upstream.tar.xz
postgresql-14-upstream.zip
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/scripts/common.c')
-rw-r--r--src/bin/scripts/common.c167
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..79cdc6c
--- /dev/null
+++ b/src/bin/scripts/common.c
@@ -0,0 +1,167 @@
+/*-------------------------------------------------------------------------
+ *
+ * common.c
+ * Common support routines for bin/scripts/
+ *
+ *
+ * Portions Copyright (c) 1996-2021, 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));
+ }
+}