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-2025, 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
2412 michael@paquier.xyz 33 :GIC 63 : splitTableColumnsSpec(const char *spec, int encoding,
34 : : char **table, const char **columns)
35 : : {
2749 noah@leadboat.com 36 : 63 : bool inquotes = false;
37 : 63 : const char *cp = spec;
38 : :
39 : : /*
40 : : * Find the first '(' not identifier-quoted. Based on
41 : : * dequote_downcase_identifier().
42 : : */
43 [ + + + + : 865 : while (*cp && (*cp != '(' || inquotes))
+ + ]
44 : : {
45 [ + + ]: 802 : if (*cp == '"')
46 : : {
47 [ + + + + ]: 3 : if (inquotes && cp[1] == '"')
48 : 1 : cp++; /* pair does not affect quoting */
49 : : else
50 : 2 : inquotes = !inquotes;
51 : 3 : cp++;
52 : : }
53 : : else
1552 tgl@sss.pgh.pa.us 54 : 799 : cp += PQmblenBounded(cp, encoding);
55 : : }
2103 alvherre@alvh.no-ip. 56 : 63 : *table = pnstrdup(spec, cp - spec);
2749 noah@leadboat.com 57 : 63 : *columns = cp;
58 : 63 : }
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 : 42 : 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 : :
2412 michael@paquier.xyz 77 : 42 : 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 : : */
2749 noah@leadboat.com 84 : 42 : initPQExpBuffer(&sql);
85 : 42 : 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 : 42 : appendStringLiteralConn(&sql, table, conn);
92 : 42 : appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
93 : :
2241 michael@paquier.xyz 94 : 42 : 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 : 42 : res = executeQuery(conn, sql.data, echo);
2749 noah@leadboat.com 103 : 41 : ntups = PQntuples(res);
104 [ - + ]: 41 : if (ntups != 1)
105 : : {
2350 peter@eisentraut.org 106 :UIC 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);
2749 noah@leadboat.com 110 : 0 : PQfinish(conn);
111 : 0 : exit(1);
112 : : }
2749 noah@leadboat.com 113 :GIC 82 : appendPQExpBufferStr(buf,
208 andres@anarazel.de 114 : 41 : fmtQualifiedIdEnc(PQgetvalue(res, 0, 1),
115 : 41 : PQgetvalue(res, 0, 0),
116 : : PQclientEncoding(conn)));
2749 noah@leadboat.com 117 : 41 : appendPQExpBufferStr(buf, columns);
118 : 41 : PQclear(res);
119 : 41 : termPQExpBuffer(&sql);
120 : 41 : pg_free(table);
121 : :
2241 michael@paquier.xyz 122 : 41 : PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
2749 noah@leadboat.com 123 : 41 : }
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
6924 peter_e@gmx.net 136 :UIC 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". */
6913 bruce@momjian.us 143 : 0 : snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "),
144 : : _(question), _(PG_YESLETTER), _(PG_NOLETTER));
145 : :
146 : : for (;;)
6924 peter_e@gmx.net 147 : 0 : {
148 : : char *resp;
149 : :
1829 tgl@sss.pgh.pa.us 150 : 0 : resp = simple_prompt(prompt, true);
151 : :
6924 peter_e@gmx.net 152 [ # # ]: 0 : if (strcmp(resp, _(PG_YESLETTER)) == 0)
153 : : {
1829 tgl@sss.pgh.pa.us 154 : 0 : free(resp);
6924 peter_e@gmx.net 155 : 0 : return true;
156 : : }
3294 tgl@sss.pgh.pa.us 157 [ # # ]: 0 : if (strcmp(resp, _(PG_NOLETTER)) == 0)
158 : : {
1829 159 : 0 : free(resp);
6924 peter_e@gmx.net 160 : 0 : return false;
161 : : }
1829 tgl@sss.pgh.pa.us 162 : 0 : free(resp);
163 : :
6924 164 : 0 : printf(_("Please answer \"%s\" or \"%s\".\n"),
165 : : _(PG_YESLETTER), _(PG_NOLETTER));
166 : : }
167 : : }
|