2017-05-09 18:15 GMT+02:00 Fabien COELHO <coe...@cri.ensmp.fr>:

>
> Hello Pavel,
>
> Patch applies cleanly and compiles.
>>>
>>
> Idem for v2. "make check" ok. Tests look good.
>
> I would suggest some rewording, maybe:
>>>
>>> "Show the description of the result of the current query buffer without
>>> actually executing it, by considering it a prepared statement."
>>>
>>> done
>>
>
> Ok. If some native English speaker can clarify the sentence further, or
> imprive it anyway, thanks in advance!
>
>   SELECT $1 AS unknown_type \gdesc
>>>
>>
>> It is not unknown type - the default placeholder type is text
>>
>
> Indeed. I really meant something like:
>
>   calvin=# SELECT $1 + $2 \gdesc
>   ERROR:  operator is not unique: unknown + unknown
>   ...
>
> More comments:
>
> I propose that the help message could be "describe result of query without
> executing it".
>

done

>
> I found an issue. \gdesk fails when the command does not return a result:
>
>  calvin=# TRUNCATE pgbench_history \gdesc
>  ERROR:  syntax error at or near ")"
>  LINE 2:  (VALUES ) s (name, tp, tpm)
>
> I guess the issue is that PQdescribePrepared returns an empty description,
> which is fine, but then the second query should be skipped, and some
> message should be output instead, like "no result" or whatever...
>
> This need fixing, and a corresponding test should be added.
>

it is little bit worse. I cannot to distinguish between SELECT\gdesc and
TRUNCATE xxx\gdesc . All are valid commands and produce empty result, so
result of \gdesc command should be empty result too.

 postgres=# truncate table xx\gdesc
┌──────┬──────┐
│ Name │ Type │
╞══════╪══════╡
└──────┴──────┘
(0 rows)

postgres=# select \gdesc
┌──────┬──────┐
│ Name │ Type │
╞══════╪══════╡
└──────┴──────┘
(0 rows)


> Also I would suggest to add a \g after the first test, which would execute
> the current buffer after its description, to show that the current buffer
> does indeed hold the query:
>
>  calvin=# SELECT 1 as one, ... \gdesc \g
>  -- one | int
>  -- ...
>  -- 1 | ...
>
>
done

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..529034f 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1323,7 +1323,87 @@ 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)
+		{
+			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 ('',-1, -1)");
+
+			for(i = 0; i< PQnfields(results); i++)
+			{
+				char	*name;
+				char	*escname;
+				size_t		name_length;
+
+				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) WHERE tp >= 0");
+			PQclear(results);
+
+			results = PQexec(pset.db, buf.data);
+			OK = ProcessResult(&results);
+
+			if (OK && results)
+				OK = PrintQueryResults(results);
+
+			termPQExpBuffer(&buf);
+		}
+
+		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 +1558,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 183fc37..e6a0b4a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1410,7 +1410,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..bf604fb 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2964,3 +2964,62 @@ 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
+ Name | Type 
+------+------
+(0 rows)
+
+CREATE TABLE bububu(a int)\gdesc
+ Name | Type 
+------+------
+(0 rows)
+
+-- 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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to