Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * common.c
4 : * Common support routines for bin/scripts/
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * src/bin/scripts/common.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres_fe.h"
16 :
17 : #include <signal.h>
18 : #include <unistd.h>
19 :
20 : #include "common.h"
21 : #include "common/connect.h"
22 : #include "common/logging.h"
23 : #include "common/string.h"
24 : #include "fe_utils/query_utils.h"
25 : #include "fe_utils/string_utils.h"
26 :
27 : /*
28 : * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions. When you
29 : * finish using them, pg_free(*table). *columns is a pointer into "spec",
30 : * possibly to its NUL terminator.
31 : */
32 : void
33 98 : splitTableColumnsSpec(const char *spec, int encoding,
34 : char **table, const char **columns)
35 : {
36 98 : bool inquotes = false;
37 98 : const char *cp = spec;
38 :
39 : /*
40 : * Find the first '(' not identifier-quoted. Based on
41 : * dequote_downcase_identifier().
42 : */
43 1118 : while (*cp && (*cp != '(' || inquotes))
44 : {
45 1020 : if (*cp == '"')
46 : {
47 6 : if (inquotes && cp[1] == '"')
48 2 : cp++; /* pair does not affect quoting */
49 : else
50 4 : inquotes = !inquotes;
51 6 : cp++;
52 : }
53 : else
54 1014 : cp += PQmblenBounded(cp, encoding);
55 : }
56 98 : *table = pnstrdup(spec, cp - spec);
57 98 : *columns = cp;
58 98 : }
59 :
60 : /*
61 : * Break apart TABLE[(COLUMNS)] of "spec". With the reset_val of search_path
62 : * in effect, have regclassin() interpret the TABLE portion. Append to "buf"
63 : * the qualified name of TABLE, followed by any (COLUMNS). Exit on failure.
64 : * We use this to interpret --table=foo under the search path psql would get,
65 : * in advance of "ANALYZE public.foo" under the always-secure search path.
66 : */
67 : void
68 76 : appendQualifiedRelation(PQExpBuffer buf, const char *spec,
69 : PGconn *conn, bool echo)
70 : {
71 : char *table;
72 : const char *columns;
73 : PQExpBufferData sql;
74 : PGresult *res;
75 : int ntups;
76 :
77 76 : splitTableColumnsSpec(spec, PQclientEncoding(conn), &table, &columns);
78 :
79 : /*
80 : * Query must remain ABSOLUTELY devoid of unqualified names. This would
81 : * be unnecessary given a regclassin() variant taking a search_path
82 : * argument.
83 : */
84 76 : initPQExpBuffer(&sql);
85 76 : appendPQExpBufferStr(&sql,
86 : "SELECT c.relname, ns.nspname\n"
87 : " FROM pg_catalog.pg_class c,"
88 : " pg_catalog.pg_namespace ns\n"
89 : " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
90 : " AND c.oid OPERATOR(pg_catalog.=) ");
91 76 : appendStringLiteralConn(&sql, table, conn);
92 76 : appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
93 :
94 76 : executeCommand(conn, "RESET search_path;", echo);
95 :
96 : /*
97 : * One row is a typical result, as is a nonexistent relation ERROR.
98 : * regclassin() unconditionally accepts all-digits input as an OID; if no
99 : * relation has that OID; this query returns no rows. Catalog corruption
100 : * might elicit other row counts.
101 : */
102 76 : res = executeQuery(conn, sql.data, echo);
103 74 : ntups = PQntuples(res);
104 74 : if (ntups != 1)
105 : {
106 0 : pg_log_error(ngettext("query returned %d row instead of one: %s",
107 : "query returned %d rows instead of one: %s",
108 : ntups),
109 : ntups, sql.data);
110 0 : PQfinish(conn);
111 0 : exit(1);
112 : }
113 74 : appendPQExpBufferStr(buf,
114 74 : fmtQualifiedId(PQgetvalue(res, 0, 1),
115 74 : PQgetvalue(res, 0, 0)));
116 74 : appendPQExpBufferStr(buf, columns);
117 74 : PQclear(res);
118 74 : termPQExpBuffer(&sql);
119 74 : pg_free(table);
120 :
121 74 : PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
122 74 : }
123 :
124 :
125 : /*
126 : * Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither.
127 : */
128 :
129 : /* translator: abbreviation for "yes" */
130 : #define PG_YESLETTER gettext_noop("y")
131 : /* translator: abbreviation for "no" */
132 : #define PG_NOLETTER gettext_noop("n")
133 :
134 : bool
135 0 : yesno_prompt(const char *question)
136 : {
137 : char prompt[256];
138 :
139 : /*------
140 : translator: This is a question followed by the translated options for
141 : "yes" and "no". */
142 0 : snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "),
143 : _(question), _(PG_YESLETTER), _(PG_NOLETTER));
144 :
145 : for (;;)
146 0 : {
147 : char *resp;
148 :
149 0 : resp = simple_prompt(prompt, true);
150 :
151 0 : if (strcmp(resp, _(PG_YESLETTER)) == 0)
152 : {
153 0 : free(resp);
154 0 : return true;
155 : }
156 0 : if (strcmp(resp, _(PG_NOLETTER)) == 0)
157 : {
158 0 : free(resp);
159 0 : return false;
160 : }
161 0 : free(resp);
162 :
163 0 : printf(_("Please answer \"%s\" or \"%s\".\n"),
164 : _(PG_YESLETTER), _(PG_NOLETTER));
165 : }
166 : }
|