2017-05-09 23:00 GMT+02:00 Fabien COELHO <[email protected]>:
>
> What about detecting the empty result (eg PQntuples()==0?) and writing
>>> "Empty result" instead of the strange looking empty table above? That
>>> would
>>> just mean skipping the PrintQueryResult call in this case?
>>>
>>
>> PQntuples == 0 every time - the query is not executed.
>>
>
> I meant to test the query which collects type names, which is executed?
>
How it can help?
>
> Or check that PQnfields() == 0 on the PQdescribePrepared() result, so that
> there is no need to execute the type name collection query?
>
> For the case "SELECT;" the empty table is correct.
>>
>
> Ok. Then write "Empty table"?
>
> For TRUNCATE and similar command I am not sure. The empty table is maybe
>> unusual, but it is valid - like "SELECT;".
>>
>
> I would partly disagree:
>
> "SELECT;" does indeed return an empty relation, so I agree that an empty
> table is valid whether spelled out as "Empty table" or explicitly.
>
> However, ISTM that "TRUNCATE stuff;" does *NOT* return a relation, so
> maybe "No table" would be ok, but not an empty table... ?!
>
> So I could be okay with both:
>
> SELECT \gdesc
> -- "Empty table" or some other string
> Or
> -- Name | Type
>
> Although I prefer the first one, because the second looks like a bug
> somehow: I asked for a description, but nothing is described... even if the
> answer is somehow valid, it looks pretty strange.
>
> The same results do not realy suit "TRUNCATE Foo \gdesc", where "No table"
> would seem more appropriate?
>
> In both case, "Empty result" is kind of neutral, it does not promise a
> table or not. Hmmm. At least not too much. Or maybe some other string such
> as "Nothing" or "No result"?
>
> Now I wonder whether the No vs Empty cases can be distinguished?
No with standard libpq API :(
I am sending a variant with message instead empty result.
Personally I prefer empty result instead message. It is hard to choose some
good text of this message. Empty result is just empty result for all cases.
Regards
Pavel
>
> --
> Fabien.
>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 3b86612..2e46f4c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1957,6 +1957,16 @@ Tue Oct 26 21:40:57 CEST 1999
<varlistentry>
+ <term><literal>\gdesc</literal></term>
+ <listitem>
+ <para>
+ Show the description of the result of the current query buffer without
+ actually executing it, by considering it a prepared statement.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>\gexec</literal></term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index b3263a9..a1c8e1d 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -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_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);
@@ -337,6 +338,8 @@ exec_command(const char *cmd,
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)
@@ -1328,6 +1331,25 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
}
/*
+ * \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;
+ }
+ else
+ ignore_slash_filepipe(scan_state);
+
+ return status;
+}
+
+/*
* \gexec -- send query and execute each field of result
*/
static backslashResult
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index a2f1259..3cffb4a 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1323,7 +1323,93 @@ SendQuery(const char *query)
}
}
- if (pset.fetch_count <= 0 || pset.gexec_flag ||
+ if (pset.gdesc_flag)
+ {
+ /*
+ * Unnamed prepared statement is used. Is not possible to
+ * create any unnamed prepared statement from psql user space,
+ * so there should not be any conflict. In this moment is not
+ * possible to deallocate this prepared statement, so it should
+ * to live to end of session or to another \gdesc call.
+ */
+ results = PQprepare(pset.db, "", query, 0, NULL);
+ if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ {
+ psql_error("%s", PQerrorMessage(pset.db));
+ ClearOrSaveResult(results);
+ ResetCancelConn();
+ goto sendquery_cleanup;
+ }
+ PQclear(results);
+
+ results = PQdescribePrepared(pset.db, "");
+ OK = ProcessResult(&results);
+ if (OK && results)
+ {
+ if (PQnfields(results) > 0)
+ {
+ PQExpBufferData buf;
+ int i;
+
+ initPQExpBuffer(&buf);
+
+ /*
+ * The description can do empty result (when some commands doesn't
+ * produce result or another corner case "SELECT;". In this case
+ * the created query should to generate empty result too. The simply
+ * solution is special always filtered row, that ensures so query
+ * will be valid every time.
+ */
+ printfPQExpBuffer(&buf,
+ "SELECT name AS \"Name\", pg_catalog.format_type(tp, tpm) AS \"Type\" FROM\n"
+ "(VALUES");
+
+ for(i = 0; i< PQnfields(results); i++)
+ {
+ char *name;
+ char *escname;
+ size_t name_length;
+
+ if (i > 0)
+ appendPQExpBufferStr(&buf, ",");
+
+ name = PQfname(results, i);
+ name_length = strlen(name);
+ escname = PQescapeLiteral(pset.db, name, name_length);
+
+ if (escname == NULL)
+ {
+ psql_error("%s", PQerrorMessage(pset.db));
+ PQclear(results);
+ termPQExpBuffer(&buf);
+ goto sendquery_cleanup;
+ }
+
+ appendPQExpBuffer(&buf, "(%s, %d, %d)",
+ escname, PQftype(results,i), PQfmod(results,i));
+ PQfreemem(escname);
+ }
+
+ appendPQExpBuffer(&buf,") s (name, tp, tpm)");
+ PQclear(results);
+
+ results = PQexec(pset.db, buf.data);
+ OK = ProcessResult(&results);
+
+ if (OK && results)
+ OK = PrintQueryResults(results);
+
+ termPQExpBuffer(&buf);
+ }
+ else
+ fprintf(pset.queryFout, _("No columns or command has not result.\n"));
+ }
+
+ ClearOrSaveResult(results);
+ 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 */
@@ -1478,6 +1564,9 @@ sendquery_cleanup:
pset.ctv_args[i] = NULL;
}
+ /* reset \gdesc trigger */
+ pset.gdesc_flag = false;
+
return OK;
}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ac43522..68dfc10 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -167,12 +167,13 @@ slashUsage(unsigned short int pager)
* 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(123, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\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, _(" \\gx [FILE] as \\g, but forces expanded output mode\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"));
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 70ff181..a175912 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -95,6 +95,7 @@ typedef struct _psqlSettings
char *gset_prefix; /* one-shot prefix argument for \gset */
bool gexec_flag; /* one-shot flag to execute query's results */
bool crosstab_flag; /* one-shot request to crosstab results */
+ bool gdesc_flag; /* one-shot request to describe query */
char *ctv_args[4]; /* \crosstabview arguments */
bool notty; /* stdin or stdout is not a tty (as determined
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b9e3491..8bfdfdc 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1426,7 +1426,7 @@ psql_completion(const char *text, int start, int end)
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
"\\e", "\\echo", "\\ef", "\\encoding", "\\errverbose", "\\ev",
- "\\f", "\\g", "\\gexec", "\\gset", "\\gx", "\\h", "\\help", "\\H",
+ "\\f", "\\g", "\\gdesc", "\\gexec", "\\gset", "\\gx", "\\h", "\\help", "\\H",
"\\i", "\\ir", "\\l", "\\lo_import", "\\lo_export", "\\lo_list",
"\\lo_unlink", "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q",
"\\qecho", "\\r", "\\s", "\\set", "\\setenv", "\\sf", "\\sv", "\\t",
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index d602aee..91678c6 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2964,3 +2964,56 @@ SELECT 3
UNION SELECT 4
UNION SELECT 5
ORDER BY 1;
+-- gdesc tests
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ CURRENT_DATE AS now
+\gdesc
+ Name | Type
+-------+------------------
+ zero | text
+ one | integer
+ two | numeric
+ three | text
+ four | text
+ five | double precision
+ now | date
+(7 rows)
+
+PREPARE test AS SELECT 1 AS first;
+EXECUTE test \gdesc
+ Name | Type
+-------+---------
+ first | integer
+(1 row)
+
+-- should fail - syntax error
+SELECT 1 + \gdesc
+ERROR: syntax error at end of input
+LINE 1: SELECT 1 +
+ ^
+-- empty results
+SELECT \gdesc
+No columns or command has not result.
+CREATE TABLE bububu(a int)\gdesc
+No columns or command has not result.
+-- query buffer should not be broken
+SELECT 1 AS x, 'Hello' AS y
+\gdesc
+ Name | Type
+------+---------
+ x | integer
+ y | text
+(2 rows)
+
+\g
+ x | y
+---+-------
+ 1 | Hello
+(1 row)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index b56a05f..caaa64d 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -560,3 +560,29 @@ UNION SELECT 5
ORDER BY 1;
\r
\p
+
+-- gdesc tests
+SELECT
+ NULL AS zero,
+ 1 AS one,
+ 2.0 AS two,
+ 'three' AS three,
+ $1 AS four,
+ sin($2) as five,
+ CURRENT_DATE AS now
+\gdesc
+
+PREPARE test AS SELECT 1 AS first;
+EXECUTE test \gdesc
+
+-- should fail - syntax error
+SELECT 1 + \gdesc
+
+-- empty results
+SELECT \gdesc
+CREATE TABLE bububu(a int)\gdesc
+
+-- query buffer should not be broken
+SELECT 1 AS x, 'Hello' AS y
+\gdesc
+\g
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers