2929#include "fe_utils/mbprint.h"
3030
3131
32+ static bool DescribeQuery (const char * query , double * elapsed_msec );
3233static bool ExecQueryUsingCursor (const char * query , double * elapsed_msec );
3334static bool command_no_begin (const char * query );
3435static 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
0 commit comments