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
This commit is contained in:
parent
6e427aa4e5
commit
49ca462eb1
|
@ -1949,6 +1949,25 @@ Tue Oct 26 21:40:57 CEST 1999
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><literal>\gexec</literal></term>
|
<term><literal>\gexec</literal></term>
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool ac
|
||||||
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
|
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
|
||||||
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
|
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
|
||||||
const char *cmd);
|
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_gexec(PsqlScanState scan_state, bool active_branch);
|
||||||
static backslashResult exec_command_gset(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);
|
static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
|
||||||
|
@ -337,6 +338,8 @@ exec_command(const char *cmd,
|
||||||
status = exec_command_f(scan_state, active_branch);
|
status = exec_command_f(scan_state, active_branch);
|
||||||
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
|
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
|
||||||
status = exec_command_g(scan_state, active_branch, cmd);
|
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)
|
else if (strcmp(cmd, "gexec") == 0)
|
||||||
status = exec_command_gexec(scan_state, active_branch);
|
status = exec_command_gexec(scan_state, active_branch);
|
||||||
else if (strcmp(cmd, "gset") == 0)
|
else if (strcmp(cmd, "gset") == 0)
|
||||||
|
@ -1330,6 +1333,23 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
||||||
return status;
|
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
|
* \gexec -- send query and execute each field of result
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "fe_utils/mbprint.h"
|
#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 ExecQueryUsingCursor(const char *query, double *elapsed_msec);
|
||||||
static bool command_no_begin(const char *query);
|
static bool command_no_begin(const char *query);
|
||||||
static bool is_select_command(const char *query);
|
static bool is_select_command(const char *query);
|
||||||
|
@ -1323,7 +1324,14 @@ SendQuery(const char *query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pset.fetch_count <= 0 || pset.gexec_flag ||
|
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))
|
pset.crosstab_flag || !is_select_command(query))
|
||||||
{
|
{
|
||||||
/* Default fetch-it-all-and-print mode */
|
/* Default fetch-it-all-and-print mode */
|
||||||
|
@ -1467,6 +1475,9 @@ sendquery_cleanup:
|
||||||
pset.gset_prefix = NULL;
|
pset.gset_prefix = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* reset \gdesc trigger */
|
||||||
|
pset.gdesc_flag = false;
|
||||||
|
|
||||||
/* reset \gexec trigger */
|
/* reset \gexec trigger */
|
||||||
pset.gexec_flag = false;
|
pset.gexec_flag = false;
|
||||||
|
|
||||||
|
@ -1482,6 +1493,118 @@ sendquery_cleanup:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
* ExecQueryUsingCursor: run a SELECT-like query using a cursor
|
||||||
*
|
*
|
||||||
|
@ -1627,7 +1750,9 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
|
||||||
break;
|
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);
|
ntuples = PQntuples(results);
|
||||||
|
|
||||||
|
|
|
@ -167,13 +167,14 @@ slashUsage(unsigned short int pager)
|
||||||
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
||||||
* the USE_READLINE line even in builds without that.
|
* 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, _("General\n"));
|
||||||
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\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, _(" \\crosstabview [COLUMNS] execute query and display results in crosstab\n"));
|
||||||
fprintf(output, _(" \\errverbose show most recent error message at maximum verbosity\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, _(" \\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, _(" \\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, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
|
||||||
fprintf(output, _(" \\gx [FILE] as \\g, but forces expanded output mode\n"));
|
fprintf(output, _(" \\gx [FILE] as \\g, but forces expanded output mode\n"));
|
||||||
|
|
|
@ -93,7 +93,8 @@ typedef struct _psqlSettings
|
||||||
char *gfname; /* one-shot file output argument for \g */
|
char *gfname; /* one-shot file output argument for \g */
|
||||||
bool g_expanded; /* one-shot expanded output requested via \gx */
|
bool g_expanded; /* one-shot expanded output requested via \gx */
|
||||||
char *gset_prefix; /* one-shot prefix argument for \gset */
|
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 */
|
bool crosstab_flag; /* one-shot request to crosstab results */
|
||||||
char *ctv_args[4]; /* \crosstabview arguments */
|
char *ctv_args[4]; /* \crosstabview arguments */
|
||||||
|
|
||||||
|
|
|
@ -1433,7 +1433,7 @@ psql_completion(const char *text, int start, int end)
|
||||||
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
|
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
|
||||||
"\\endif", "\\errverbose", "\\ev",
|
"\\endif", "\\errverbose", "\\ev",
|
||||||
"\\f",
|
"\\f",
|
||||||
"\\g", "\\gexec", "\\gset", "\\gx",
|
"\\g", "\\gdesc", "\\gexec", "\\gset", "\\gx",
|
||||||
"\\h", "\\help", "\\H",
|
"\\h", "\\help", "\\H",
|
||||||
"\\i", "\\if", "\\ir",
|
"\\i", "\\if", "\\ir",
|
||||||
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
|
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
|
||||||
|
|
|
@ -126,6 +126,91 @@ more than one row returned for \gset
|
||||||
select 10 as test01, 20 as test02 from generate_series(1,0) \gset
|
select 10 as test01, 20 as test02 from generate_series(1,0) \gset
|
||||||
no rows returned for \gset
|
no rows returned for \gset
|
||||||
\unset FETCH_COUNT
|
\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
|
-- \gexec
|
||||||
create temporary table gexec_test(a int, b text, c date, d float);
|
create temporary table gexec_test(a int, b text, c date, d float);
|
||||||
select format('create index on gexec_test(%I)', attname)
|
select format('create index on gexec_test(%I)', attname)
|
||||||
|
|
|
@ -73,6 +73,42 @@ select 10 as test01, 20 as test02 from generate_series(1,0) \gset
|
||||||
|
|
||||||
\unset FETCH_COUNT
|
\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
|
-- \gexec
|
||||||
|
|
||||||
create temporary table gexec_test(a int, b text, c date, d float);
|
create temporary table gexec_test(a int, b text, c date, d float);
|
||||||
|
|
Loading…
Reference in New Issue