Age Owner Branch data TLA 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/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
1902 michael@paquier.xyz 34 :CBC 49 : splitTableColumnsSpec(const char *spec, int encoding,
35 : : char **table, const char **columns)
36 : : {
2239 noah@leadboat.com 37 : 49 : bool inquotes = false;
38 : 49 : const char *cp = spec;
39 : :
40 : : /*
41 : : * Find the first '(' not identifier-quoted. Based on
42 : : * dequote_downcase_identifier().
43 : : */
44 [ + + + + : 559 : while (*cp && (*cp != '(' || inquotes))
+ + ]
45 : : {
46 [ + + ]: 510 : 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
1042 tgl@sss.pgh.pa.us 55 : 507 : cp += PQmblenBounded(cp, encoding);
56 : : }
1593 alvherre@alvh.no-ip. 57 : 49 : *table = pnstrdup(spec, cp - spec);
2239 noah@leadboat.com 58 : 49 : *columns = cp;
59 : 49 : }
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 : 38 : 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 : :
1902 michael@paquier.xyz 78 : 38 : 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 : : */
2239 noah@leadboat.com 85 : 38 : initPQExpBuffer(&sql);
86 : 38 : 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 : 38 : appendStringLiteralConn(&sql, table, conn);
93 : 38 : appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
94 : :
1731 michael@paquier.xyz 95 : 38 : 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 : 38 : res = executeQuery(conn, sql.data, echo);
2239 noah@leadboat.com 104 : 37 : ntups = PQntuples(res);
105 [ - + ]: 37 : if (ntups != 1)
106 : : {
1840 peter@eisentraut.org 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);
2239 noah@leadboat.com 111 : 0 : PQfinish(conn);
112 : 0 : exit(1);
113 : : }
2239 noah@leadboat.com 114 :CBC 37 : appendPQExpBufferStr(buf,
2067 tgl@sss.pgh.pa.us 115 : 37 : fmtQualifiedId(PQgetvalue(res, 0, 1),
2239 noah@leadboat.com 116 : 37 : PQgetvalue(res, 0, 0)));
117 : 37 : appendPQExpBufferStr(buf, columns);
118 : 37 : PQclear(res);
119 : 37 : termPQExpBuffer(&sql);
120 : 37 : pg_free(table);
121 : :
1731 michael@paquier.xyz 122 : 37 : PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
2239 noah@leadboat.com 123 : 37 : }
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
6414 peter_e@gmx.net 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". */
6403 bruce@momjian.us 143 : 0 : snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "),
144 : : _(question), _(PG_YESLETTER), _(PG_NOLETTER));
145 : :
146 : : for (;;)
6414 peter_e@gmx.net 147 : 0 : {
148 : : char *resp;
149 : :
1319 tgl@sss.pgh.pa.us 150 : 0 : resp = simple_prompt(prompt, true);
151 : :
6414 peter_e@gmx.net 152 [ # # ]: 0 : if (strcmp(resp, _(PG_YESLETTER)) == 0)
153 : : {
1319 tgl@sss.pgh.pa.us 154 : 0 : free(resp);
6414 peter_e@gmx.net 155 : 0 : return true;
156 : : }
2784 tgl@sss.pgh.pa.us 157 [ # # ]: 0 : if (strcmp(resp, _(PG_NOLETTER)) == 0)
158 : : {
1319 159 : 0 : free(resp);
6414 peter_e@gmx.net 160 : 0 : return false;
161 : : }
1319 tgl@sss.pgh.pa.us 162 : 0 : free(resp);
163 : :
6414 164 : 0 : printf(_("Please answer \"%s\" or \"%s\".\n"),
165 : : _(PG_YESLETTER), _(PG_NOLETTER));
166 : : }
167 : : }
|