Skip to content

Commit 49ca462

Browse files
committed
Add \gdesc psql command.
This command acts somewhat like \g, but instead of executing the query buffer, it merely prints a description of the columns that the query result would have. (Of course, this still requires parsing the query; if parse analysis fails, you get an error anyway.) We accomplish this using an unnamed prepared statement, which should be invisible to psql users. Pavel Stehule, reviewed by Fabien Coelho Discussion: https://postgr.es/m/CAFj8pRBhYVvO34FU=EKb=nAF5t3b++krKt1FneCmR0kuF5m-QA@mail.gmail.com
1 parent 6e427aa commit 49ca462

File tree

8 files changed

+293
-6
lines changed

8 files changed

+293
-6
lines changed

doc/src/sgml/ref/psql-ref.sgml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,25 @@ Tue Oct 26 21:40:57 CEST 1999
19491949
</varlistentry>
19501950

19511951

1952+
<varlistentry>
1953+
<term><literal>\gdesc</literal></term>
1954+
1955+
<listitem>
1956+
<para>
1957+
Shows the description (that is, the column names and data types)
1958+
of the result of the current query buffer. The query is not
1959+
actually executed; however, if it contains some type of syntax
1960+
error, that error will be reported in the normal way.
1961+
</para>
1962+
1963+
<para>
1964+
If the current query buffer is empty, the most recently sent query
1965+
is described instead.
1966+
</para>
1967+
</listitem>
1968+
</varlistentry>
1969+
1970+
19521971
<varlistentry>
19531972
<term><literal>\gexec</literal></term>
19541973

src/bin/psql/command.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool ac
8888
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
8989
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
9090
const char *cmd);
91+
static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch);
9192
static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
9293
static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
9394
static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
@@ -337,6 +338,8 @@ exec_command(const char *cmd,
337338
status = exec_command_f(scan_state, active_branch);
338339
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
339340
status = exec_command_g(scan_state, active_branch, cmd);
341+
else if (strcmp(cmd, "gdesc") == 0)
342+
status = exec_command_gdesc(scan_state, active_branch);
340343
else if (strcmp(cmd, "gexec") == 0)
341344
status = exec_command_gexec(scan_state, active_branch);
342345
else if (strcmp(cmd, "gset") == 0)
@@ -1330,6 +1333,23 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
13301333
return status;
13311334
}
13321335

1336+
/*
1337+
* \gdesc -- describe query result
1338+
*/
1339+
static backslashResult
1340+
exec_command_gdesc(PsqlScanState scan_state, bool active_branch)
1341+
{
1342+
backslashResult status = PSQL_CMD_SKIP_LINE;
1343+
1344+
if (active_branch)
1345+
{
1346+
pset.gdesc_flag = true;
1347+
status = PSQL_CMD_SEND;
1348+
}
1349+
1350+
return status;
1351+
}
1352+
13331353
/*
13341354
* \gexec -- send query and execute each field of result
13351355
*/

src/bin/psql/common.c

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "fe_utils/mbprint.h"
3030

3131

32+
static bool DescribeQuery(const char *query, double *elapsed_msec);
3233
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
3334
static bool command_no_begin(const char *query);
3435
static bool is_select_command(const char *query);
@@ -1323,8 +1324,15 @@ SendQuery(const char *query)
13231324
}
13241325
}
13251326

1326-
if (pset.fetch_count <= 0 || pset.gexec_flag ||
1327-
pset.crosstab_flag || !is_select_command(query))
1327+
if (pset.gdesc_flag)
1328+
{
1329+
/* Describe query's result columns, without executing it */
1330+
OK = DescribeQuery(query, &elapsed_msec);
1331+
ResetCancelConn();
1332+
results = NULL; /* PQclear(NULL) does nothing */
1333+
}
1334+
else if (pset.fetch_count <= 0 || pset.gexec_flag ||
1335+
pset.crosstab_flag || !is_select_command(query))
13281336
{
13291337
/* Default fetch-it-all-and-print mode */
13301338
instr_time before,
@@ -1467,6 +1475,9 @@ SendQuery(const char *query)
14671475
pset.gset_prefix = NULL;
14681476
}
14691477

1478+
/* reset \gdesc trigger */
1479+
pset.gdesc_flag = false;
1480+
14701481
/* reset \gexec trigger */
14711482
pset.gexec_flag = false;
14721483

@@ -1482,6 +1493,118 @@ SendQuery(const char *query)
14821493
}
14831494

14841495

1496+
/*
1497+
* DescribeQuery: describe the result columns of a query, without executing it
1498+
*
1499+
* Returns true if the operation executed successfully, false otherwise.
1500+
*
1501+
* If pset.timing is on, total query time (exclusive of result-printing) is
1502+
* stored into *elapsed_msec.
1503+
*/
1504+
static bool
1505+
DescribeQuery(const char *query, double *elapsed_msec)
1506+
{
1507+
PGresult *results;
1508+
bool OK;
1509+
instr_time before,
1510+
after;
1511+
1512+
*elapsed_msec = 0;
1513+
1514+
if (pset.timing)
1515+
INSTR_TIME_SET_CURRENT(before);
1516+
1517+
/*
1518+
* To parse the query but not execute it, we prepare it, using the unnamed
1519+
* prepared statement. This is invisible to psql users, since there's no
1520+
* way to access the unnamed prepared statement from psql user space. The
1521+
* next Parse or Query protocol message would overwrite the statement
1522+
* anyway. (So there's no great need to clear it when done, which is a
1523+
* good thing because libpq provides no easy way to do that.)
1524+
*/
1525+
results = PQprepare(pset.db, "", query, 0, NULL);
1526+
if (PQresultStatus(results) != PGRES_COMMAND_OK)
1527+
{
1528+
psql_error("%s", PQerrorMessage(pset.db));
1529+
ClearOrSaveResult(results);
1530+
return false;
1531+
}
1532+
PQclear(results);
1533+
1534+
results = PQdescribePrepared(pset.db, "");
1535+
OK = AcceptResult(results) &&
1536+
(PQresultStatus(results) == PGRES_COMMAND_OK);
1537+
if (OK && results)
1538+
{
1539+
if (PQnfields(results) > 0)
1540+
{
1541+
PQExpBufferData buf;
1542+
int i;
1543+
1544+
initPQExpBuffer(&buf);
1545+
1546+
printfPQExpBuffer(&buf,
1547+
"SELECT name AS \"%s\", pg_catalog.format_type(tp, tpm) AS \"%s\"\n"
1548+
"FROM (VALUES ",
1549+
gettext_noop("Column"),
1550+
gettext_noop("Type"));
1551+
1552+
for (i = 0; i < PQnfields(results); i++)
1553+
{
1554+
const char *name;
1555+
char *escname;
1556+
1557+
if (i > 0)
1558+
appendPQExpBufferStr(&buf, ",");
1559+
1560+
name = PQfname(results, i);
1561+
escname = PQescapeLiteral(pset.db, name, strlen(name));
1562+
1563+
if (escname == NULL)
1564+
{
1565+
psql_error("%s", PQerrorMessage(pset.db));
1566+
PQclear(results);
1567+
termPQExpBuffer(&buf);
1568+
return false;
1569+
}
1570+
1571+
appendPQExpBuffer(&buf, "(%s, '%u'::pg_catalog.oid, %d)",
1572+
escname,
1573+
PQftype(results, i),
1574+
PQfmod(results, i));
1575+
1576+
PQfreemem(escname);
1577+
}
1578+
1579+
appendPQExpBufferStr(&buf, ") s(name, tp, tpm)");
1580+
PQclear(results);
1581+
1582+
results = PQexec(pset.db, buf.data);
1583+
OK = AcceptResult(results);
1584+
1585+
if (pset.timing)
1586+
{
1587+
INSTR_TIME_SET_CURRENT(after);
1588+
INSTR_TIME_SUBTRACT(after, before);
1589+
*elapsed_msec += INSTR_TIME_GET_MILLISEC(after);
1590+
}
1591+
1592+
if (OK && results)
1593+
OK = PrintQueryResults(results);
1594+
1595+
termPQExpBuffer(&buf);
1596+
}
1597+
else
1598+
fprintf(pset.queryFout,
1599+
_("The command has no result, or the result has no columns.\n"));
1600+
}
1601+
1602+
ClearOrSaveResult(results);
1603+
1604+
return OK;
1605+
}
1606+
1607+
14851608
/*
14861609
* ExecQueryUsingCursor: run a SELECT-like query using a cursor
14871610
*
@@ -1627,7 +1750,9 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
16271750
break;
16281751
}
16291752

1630-
/* Note we do not deal with \gexec or \crosstabview modes here */
1753+
/*
1754+
* Note we do not deal with \gdesc, \gexec or \crosstabview modes here
1755+
*/
16311756

16321757
ntuples = PQntuples(results);
16331758

src/bin/psql/help.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,14 @@ slashUsage(unsigned short int pager)
167167
* Use "psql --help=commands | wc" to count correctly. It's okay to count
168168
* the USE_READLINE line even in builds without that.
169169
*/
170-
output = PageOutput(122, pager ? &(pset.popt.topt) : NULL);
170+
output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
171171

172172
fprintf(output, _("General\n"));
173173
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
174174
fprintf(output, _(" \\crosstabview [COLUMNS] execute query and display results in crosstab\n"));
175175
fprintf(output, _(" \\errverbose show most recent error message at maximum verbosity\n"));
176176
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
177+
fprintf(output, _(" \\gdesc describe result of query, without executing it\n"));
177178
fprintf(output, _(" \\gexec execute query, then execute each value in its result\n"));
178179
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
179180
fprintf(output, _(" \\gx [FILE] as \\g, but forces expanded output mode\n"));

src/bin/psql/settings.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ typedef struct _psqlSettings
9393
char *gfname; /* one-shot file output argument for \g */
9494
bool g_expanded; /* one-shot expanded output requested via \gx */
9595
char *gset_prefix; /* one-shot prefix argument for \gset */
96-
bool gexec_flag; /* one-shot flag to execute query's results */
96+
bool gdesc_flag; /* one-shot request to describe query results */
97+
bool gexec_flag; /* one-shot request to execute query results */
9798
bool crosstab_flag; /* one-shot request to crosstab results */
9899
char *ctv_args[4]; /* \crosstabview arguments */
99100

src/bin/psql/tab-complete.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,7 +1433,7 @@ psql_completion(const char *text, int start, int end)
14331433
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
14341434
"\\endif", "\\errverbose", "\\ev",
14351435
"\\f",
1436-
"\\g", "\\gexec", "\\gset", "\\gx",
1436+
"\\g", "\\gdesc", "\\gexec", "\\gset", "\\gx",
14371437
"\\h", "\\help", "\\H",
14381438
"\\i", "\\if", "\\ir",
14391439
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",

src/test/regress/expected/psql.out

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,91 @@ more than one row returned for \gset
126126
select 10 as test01, 20 as test02 from generate_series(1,0) \gset
127127
no rows returned for \gset
128128
\unset FETCH_COUNT
129+
-- \gdesc
130+
SELECT
131+
NULL AS zero,
132+
1 AS one,
133+
2.0 AS two,
134+
'three' AS three,
135+
$1 AS four,
136+
sin($2) as five,
137+
'foo'::varchar(4) as six,
138+
CURRENT_DATE AS now
139+
\gdesc
140+
Column | Type
141+
--------+----------------------
142+
zero | text
143+
one | integer
144+
two | numeric
145+
three | text
146+
four | text
147+
five | double precision
148+
six | character varying(4)
149+
now | date
150+
(8 rows)
151+
152+
-- should work with tuple-returning utilities, such as EXECUTE
153+
PREPARE test AS SELECT 1 AS first, 2 AS second;
154+
EXECUTE test \gdesc
155+
Column | Type
156+
--------+---------
157+
first | integer
158+
second | integer
159+
(2 rows)
160+
161+
EXPLAIN EXECUTE test \gdesc
162+
Column | Type
163+
------------+------
164+
QUERY PLAN | text
165+
(1 row)
166+
167+
-- should fail cleanly - syntax error
168+
SELECT 1 + \gdesc
169+
ERROR: syntax error at end of input
170+
LINE 1: SELECT 1 +
171+
^
172+
-- check behavior with empty results
173+
SELECT \gdesc
174+
The command has no result, or the result has no columns.
175+
CREATE TABLE bububu(a int) \gdesc
176+
The command has no result, or the result has no columns.
177+
-- subject command should not have executed
178+
TABLE bububu; -- fail
179+
ERROR: relation "bububu" does not exist
180+
LINE 1: TABLE bububu;
181+
^
182+
-- query buffer should remain unchanged
183+
SELECT 1 AS x, 'Hello', 2 AS y, true AS "dirty\name"
184+
\gdesc
185+
Column | Type
186+
------------+---------
187+
x | integer
188+
?column? | text
189+
y | integer
190+
dirty\name | boolean
191+
(4 rows)
192+
193+
\g
194+
x | ?column? | y | dirty\name
195+
---+----------+---+------------
196+
1 | Hello | 2 | t
197+
(1 row)
198+
199+
-- all on one line
200+
SELECT 3 AS x, 'Hello', 4 AS y, true AS "dirty\name" \gdesc \g
201+
Column | Type
202+
------------+---------
203+
x | integer
204+
?column? | text
205+
y | integer
206+
dirty\name | boolean
207+
(4 rows)
208+
209+
x | ?column? | y | dirty\name
210+
---+----------+---+------------
211+
3 | Hello | 4 | t
212+
(1 row)
213+
129214
-- \gexec
130215
create temporary table gexec_test(a int, b text, c date, d float);
131216
select format('create index on gexec_test(%I)', attname)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy