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