</varlistentry>
+ <varlistentry>
+ <term><literal>\gdesc</literal></term>
+
+ <listitem>
+ <para>
+ Shows the description (that is, the column names and data types)
+ of the result of the current query buffer. The query is not
+ actually executed; however, if it contains some type of syntax
+ error, that error will be reported in the normal way.
+ </para>
+
+ <para>
+ If the current query buffer is empty, the most recently sent query
+ is described instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><literal>\gexec</literal></term>
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
const char *cmd);
+static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
status = exec_command_f(scan_state, active_branch);
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
status = exec_command_g(scan_state, active_branch, cmd);
+ else if (strcmp(cmd, "gdesc") == 0)
+ status = exec_command_gdesc(scan_state, active_branch);
else if (strcmp(cmd, "gexec") == 0)
status = exec_command_gexec(scan_state, active_branch);
else if (strcmp(cmd, "gset") == 0)
return status;
}
+/*
+ * \gdesc -- describe query result
+ */
+static backslashResult
+exec_command_gdesc(PsqlScanState scan_state, bool active_branch)
+{
+ backslashResult status = PSQL_CMD_SKIP_LINE;
+
+ if (active_branch)
+ {
+ pset.gdesc_flag = true;
+ status = PSQL_CMD_SEND;
+ }
+
+ return status;
+}
+
/*
* \gexec -- send query and execute each field of result
*/
#include "fe_utils/mbprint.h"
+static bool DescribeQuery(const char *query, double *elapsed_msec);
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
static bool command_no_begin(const char *query);
static bool is_select_command(const char *query);
}
}
- if (pset.fetch_count <= 0 || pset.gexec_flag ||
- pset.crosstab_flag || !is_select_command(query))
+ if (pset.gdesc_flag)
+ {
+ /* Describe query's result columns, without executing it */
+ OK = DescribeQuery(query, &elapsed_msec);
+ ResetCancelConn();
+ results = NULL; /* PQclear(NULL) does nothing */
+ }
+ else if (pset.fetch_count <= 0 || pset.gexec_flag ||
+ pset.crosstab_flag || !is_select_command(query))
{
/* Default fetch-it-all-and-print mode */
instr_time before,
pset.gset_prefix = NULL;
}
+ /* reset \gdesc trigger */
+ pset.gdesc_flag = false;
+
/* reset \gexec trigger */
pset.gexec_flag = false;
}
+/*
+ * DescribeQuery: describe the result columns of a query, without executing it
+ *
+ * Returns true if the operation executed successfully, false otherwise.
+ *
+ * If pset.timing is on, total query time (exclusive of result-printing) is
+ * stored into *elapsed_msec.
+ */
+static bool
+DescribeQuery(const char *query, double *elapsed_msec)
+{
+ PGresult *results;
+ bool OK;
+ instr_time before,
+ after;
+
+ *elapsed_msec = 0;
+
+ if (pset.timing)
+ INSTR_TIME_SET_CURRENT(before);
+
+ /*
+ * To parse the query but not execute it, we prepare it, using the unnamed
+ * prepared statement. This is invisible to psql users, since there's no
+ * way to access the unnamed prepared statement from psql user space. The
+ * next Parse or Query protocol message would overwrite the statement
+ * anyway. (So there's no great need to clear it when done, which is a
+ * good thing because libpq provides no easy way to do that.)
+ */
+ results = PQprepare(pset.db, "", query, 0, NULL);
+ if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ {
+ psql_error("%s", PQerrorMessage(pset.db));
+ ClearOrSaveResult(results);
+ return false;
+ }
+ PQclear(results);
+
+ results = PQdescribePrepared(pset.db, "");
+ OK = AcceptResult(results) &&
+ (PQresultStatus(results) == PGRES_COMMAND_OK);
+ if (OK && results)
+ {
+ if (PQnfields(results) > 0)
+ {
+ PQExpBufferData buf;
+ int i;
+
+ initPQExpBuffer(&buf);
+
+ printfPQExpBuffer(&buf,
+ "SELECT name AS \"%s\", pg_catalog.format_type(tp, tpm) AS \"%s\"\n"
+ "FROM (VALUES ",
+ gettext_noop("Column"),
+ gettext_noop("Type"));
+
+ for (i = 0; i < PQnfields(results); i++)
+ {
+ const char *name;
+ char *escname;
+
+ if (i > 0)
+ appendPQExpBufferStr(&buf, ",");
+
+ name = PQfname(results, i);
+ escname = PQescapeLiteral(pset.db, name, strlen(name));
+
+ if (escname == NULL)
+ {
+ psql_error("%s", PQerrorMessage(pset.db));
+ PQclear(results);
+ termPQExpBuffer(&buf);
+ return false;
+ }
+
+ appendPQExpBuffer(&buf, "(%s, '%u'::pg_catalog.oid, %d)",
+ escname,
+ PQftype(results, i),
+ PQfmod(results, i));
+
+ PQfreemem(escname);
+ }
+
+ appendPQExpBufferStr(&buf, ") s(name, tp, tpm)");
+ PQclear(results);
+
+ results = PQexec(pset.db, buf.data);
+ OK = AcceptResult(results);
+
+ if (pset.timing)
+ {
+ INSTR_TIME_SET_CURRENT(after);
+ INSTR_TIME_SUBTRACT(after, before);
+ *elapsed_msec += INSTR_TIME_GET_MILLISEC(after);
+ }
+
+ if (OK && results)
+ OK = PrintQueryResults(results);
+
+ termPQExpBuffer(&buf);
+ }
+ else
+ fprintf(pset.queryFout,
+ _("The command has no result, or the result has no columns.\n"));
+ }
+
+ ClearOrSaveResult(results);
+
+ return OK;
+}
+
+
/*
* ExecQueryUsingCursor: run a SELECT-like query using a cursor
*
break;
}
- /* Note we do not deal with \gexec or \crosstabview modes here */
+ /*
+ * Note we do not deal with \gdesc, \gexec or \crosstabview modes here
+ */
ntuples = PQntuples(results);
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
- output = PageOutput(122, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
fprintf(output, _(" \\crosstabview [COLUMNS] execute query and display results in crosstab\n"));
fprintf(output, _(" \\errverbose show most recent error message at maximum verbosity\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
+ fprintf(output, _(" \\gdesc describe result of query, without executing it\n"));
fprintf(output, _(" \\gexec execute query, then execute each value in its result\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\gx [FILE] as \\g, but forces expanded output mode\n"));
char *gfname; /* one-shot file output argument for \g */
bool g_expanded; /* one-shot expanded output requested via \gx */
char *gset_prefix; /* one-shot prefix argument for \gset */
- bool gexec_flag; /* one-shot flag to execute query's results */
+ bool gdesc_flag; /* one-shot request to describe query results */
+ bool gexec_flag; /* one-shot request to execute query results */
bool crosstab_flag; /* one-shot request to crosstab results */
char *ctv_args[4]; /* \crosstabview arguments */
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
"\\endif", "\\errverbose", "\\ev",
"\\f",
- "\\g", "\\gexec", "\\gset", "\\gx",
+ "\\g", "\\gdesc", "\\gexec", "\\gset", "\\gx",
"\\h", "\\help", "\\H",
"\\i", "\\if", "\\ir",
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
select 10 as test01, 20 as test02 from generate_series(1,0) \gset
no rows returned for \gset
\unset FETCH_COUNT
+-- \gdesc
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ 'foo'::varchar(4) as six,
+ CURRENT_DATE AS now
+\gdesc
+ Column | Type
+--------+----------------------
+ zero | text
+ one | integer
+ two | numeric
+ three | text
+ four | text
+ five | double precision
+ six | character varying(4)
+ now | date
+(8 rows)
+
+-- should work with tuple-returning utilities, such as EXECUTE
+PREPARE test AS SELECT 1 AS first, 2 AS second;
+EXECUTE test \gdesc
+ Column | Type
+--------+---------
+ first | integer
+ second | integer
+(2 rows)
+
+EXPLAIN EXECUTE test \gdesc
+ Column | Type
+------------+------
+ QUERY PLAN | text
+(1 row)
+
+-- should fail cleanly - syntax error
+SELECT 1 + \gdesc
+ERROR: syntax error at end of input
+LINE 1: SELECT 1 +
+ ^
+-- check behavior with empty results
+SELECT \gdesc
+The command has no result, or the result has no columns.
+CREATE TABLE bububu(a int) \gdesc
+The command has no result, or the result has no columns.
+-- subject command should not have executed
+TABLE bububu; -- fail
+ERROR: relation "bububu" does not exist
+LINE 1: TABLE bububu;
+ ^
+-- query buffer should remain unchanged
+SELECT 1 AS x, 'Hello', 2 AS y, true AS "dirty\name"
+\gdesc
+ Column | Type
+------------+---------
+ x | integer
+ ?column? | text
+ y | integer
+ dirty\name | boolean
+(4 rows)
+
+\g
+ x | ?column? | y | dirty\name
+---+----------+---+------------
+ 1 | Hello | 2 | t
+(1 row)
+
+-- all on one line
+SELECT 3 AS x, 'Hello', 4 AS y, true AS "dirty\name" \gdesc \g
+ Column | Type
+------------+---------
+ x | integer
+ ?column? | text
+ y | integer
+ dirty\name | boolean
+(4 rows)
+
+ x | ?column? | y | dirty\name
+---+----------+---+------------
+ 3 | Hello | 4 | t
+(1 row)
+
-- \gexec
create temporary table gexec_test(a int, b text, c date, d float);
select format('create index on gexec_test(%I)', attname)
\unset FETCH_COUNT
+-- \gdesc
+
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ 'foo'::varchar(4) as six,
+ CURRENT_DATE AS now
+\gdesc
+
+-- should work with tuple-returning utilities, such as EXECUTE
+PREPARE test AS SELECT 1 AS first, 2 AS second;
+EXECUTE test \gdesc
+EXPLAIN EXECUTE test \gdesc
+
+-- should fail cleanly - syntax error
+SELECT 1 + \gdesc
+
+-- check behavior with empty results
+SELECT \gdesc
+CREATE TABLE bububu(a int) \gdesc
+
+-- subject command should not have executed
+TABLE bububu; -- fail
+
+-- query buffer should remain unchanged
+SELECT 1 AS x, 'Hello', 2 AS y, true AS "dirty\name"
+\gdesc
+\g
+
+-- all on one line
+SELECT 3 AS x, 'Hello', 4 AS y, true AS "dirty\name" \gdesc \g
+
-- \gexec
create temporary table gexec_test(a int, b text, c date, d float);