On Fri, Jul 19, 2024 at 03:28:44PM +0200, Tomas Vondra wrote:
> OK, if you're already half-way through the review, I'll leave it up to
> you. I don't think we need to rush, and I'd have to learn about all the
> psql stuff first anyway.

It took me a couple of days to get back to it, but attached is what I
have finished with.  This was mostly OK, except for a few things:
- \close was inconsistent with the other two commands, where no
argument was treated as the unnamed prepared statement.  I think that
this should be made consistent with \parse and \bindx, requiring an
argument, where '' is the unnamed statement.
- The docs did not mention the case of the unnamed statement, so added
some notes about that.
- Some free() calls were not needed in the command executions, where
psql_scan_slash_option() returns NULL.
- Tests missing when no argument is provided for the new commands.

One last thing I have found really confusing is that this leads to the
addition of two more status flags in pset for the close and parse
parts, with \bind and \bindx sharing the third one while deciding
which path to use depending on if the statement name is provided.
That's fragile.  I think that it would be much cleaner to put all that
behind an enum, falling back to PQsendQuery() by default.  I am
attaching that as 0002, for clarity, but my plan is to merge both 0001
and 0002 together.
--
Michael
From 62303eeab20740f8e7bba36d6595b2a02771e5aa Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <anthonin.bonne...@datadoghq.com>
Date: Thu, 18 Jan 2024 08:46:33 +0100
Subject: [PATCH v6 1/2] psql: Add support for prepared stmt with extended
 protocol

Currently, only unnamed prepared statement is supported by psql with the
\bind command so it's not possible to test named statement creation and
execution through extended protocol.

This commit introduces three additional commands: \parse, \bindx and
\close.
\parse creates a prepared statement using extended protocol.
\bindx binds and execute an existing prepared statement using extended
protocol.
\close closes an existing prepared statement using extended protocol.
---
 src/bin/psql/command.c             | 128 +++++++++++++++++++++++++++++
 src/bin/psql/common.c              |  36 +++++++-
 src/bin/psql/help.c                |   4 +
 src/bin/psql/settings.h            |   6 ++
 src/bin/psql/tab-complete.c        |   6 +-
 src/test/regress/expected/psql.out |  55 +++++++++++++
 src/test/regress/sql/psql.sql      |  28 ++++++-
 doc/src/sgml/ref/psql-ref.sgml     |  90 ++++++++++++++++++++
 8 files changed, 346 insertions(+), 7 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 180781ecd0..f23a7404cc 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -64,10 +64,14 @@ static backslashResult exec_command(const char *cmd,
 									PQExpBuffer previous_buf);
 static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_bind(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_bindx(PsqlScanState scan_state, bool active_branch,
+										  const char *cmd);
 static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch,
 									   const char *cmd);
+static backslashResult exec_command_close(PsqlScanState scan_state, bool active_branch,
+										  const char *cmd);
 static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch);
@@ -116,6 +120,8 @@ static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_bra
 static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch,
 										  PQExpBuffer query_buf, PQExpBuffer previous_buf);
+static backslashResult exec_command_parse(PsqlScanState scan_state, bool active_branch,
+										  const char *cmd);
 static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
 										   const char *cmd);
@@ -312,12 +318,16 @@ exec_command(const char *cmd,
 		status = exec_command_a(scan_state, active_branch);
 	else if (strcmp(cmd, "bind") == 0)
 		status = exec_command_bind(scan_state, active_branch);
+	else if (strcmp(cmd, "bindx") == 0)
+		status = exec_command_bindx(scan_state, active_branch, cmd);
 	else if (strcmp(cmd, "C") == 0)
 		status = exec_command_C(scan_state, active_branch);
 	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
 		status = exec_command_connect(scan_state, active_branch);
 	else if (strcmp(cmd, "cd") == 0)
 		status = exec_command_cd(scan_state, active_branch, cmd);
+	else if (strcmp(cmd, "close") == 0)
+		status = exec_command_close(scan_state, active_branch, cmd);
 	else if (strcmp(cmd, "conninfo") == 0)
 		status = exec_command_conninfo(scan_state, active_branch);
 	else if (pg_strcasecmp(cmd, "copy") == 0)
@@ -379,6 +389,8 @@ exec_command(const char *cmd,
 	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
 		status = exec_command_print(scan_state, active_branch,
 									query_buf, previous_buf);
+	else if (strcmp(cmd, "parse") == 0)
+		status = exec_command_parse(scan_state, active_branch, cmd);
 	else if (strcmp(cmd, "password") == 0)
 		status = exec_command_password(scan_state, active_branch);
 	else if (strcmp(cmd, "prompt") == 0)
@@ -472,6 +484,7 @@ exec_command_bind(PsqlScanState scan_state, bool active_branch)
 		int			nalloc = 0;
 
 		pset.bind_params = NULL;
+		pset.stmtName = NULL;
 
 		while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)))
 		{
@@ -493,6 +506,56 @@ exec_command_bind(PsqlScanState scan_state, bool active_branch)
 	return status;
 }
 
+/*
+ * \bindx -- set query parameters for an existing prepared statement
+ */
+static backslashResult
+exec_command_bindx(PsqlScanState scan_state, bool active_branch,
+				   const char *cmd)
+{
+	backslashResult status = PSQL_CMD_SKIP_LINE;
+
+	if (active_branch)
+	{
+		char	   *opt;
+		int			nparams = 0;
+		int			nalloc = 0;
+
+		pset.bind_params = NULL;
+		pset.stmtName = NULL;
+
+		/* get the mandatory prepared statement name */
+		opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+		if (!opt)
+		{
+			pg_log_error("\\%s: missing required argument", cmd);
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			pset.stmtName = opt;
+			pset.bind_flag = true;
+
+			/* set of parameters */
+			while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)))
+			{
+				nparams++;
+				if (nparams > nalloc)
+				{
+					nalloc = nalloc ? nalloc * 2 : 1;
+					pset.bind_params = pg_realloc_array(pset.bind_params, char *, nalloc);
+				}
+				pset.bind_params[nparams - 1] = opt;
+			}
+			pset.bind_nparams = nparams;
+		}
+	}
+	else
+		ignore_slash_options(scan_state);
+
+	return status;
+}
+
 /*
  * \C -- override table title (formerly change HTML caption)
  */
@@ -643,6 +706,38 @@ exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
 	return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
 }
 
+/*
+ * \close -- close a previously prepared statement
+ */
+static backslashResult
+exec_command_close(PsqlScanState scan_state, bool active_branch, const char *cmd)
+{
+	backslashResult status = PSQL_CMD_SKIP_LINE;
+
+	if (active_branch)
+	{
+		char	   *opt = psql_scan_slash_option(scan_state,
+												 OT_NORMAL, NULL, false);
+
+		pset.stmtName = NULL;
+		if (!opt)
+		{
+			pg_log_error("\\%s: missing required argument", cmd);
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			pset.stmtName = opt;
+			pset.close_flag = true;
+			status = PSQL_CMD_SEND;
+		}
+	}
+	else
+		ignore_slash_options(scan_state);
+
+	return status;
+}
+
 /*
  * \conninfo -- display information about the current connection
  */
@@ -2096,6 +2191,39 @@ exec_command_print(PsqlScanState scan_state, bool active_branch,
 	return PSQL_CMD_SKIP_LINE;
 }
 
+/*
+ * \parse -- parse query
+ */
+static backslashResult
+exec_command_parse(PsqlScanState scan_state, bool active_branch,
+				   const char *cmd)
+{
+	backslashResult status = PSQL_CMD_SKIP_LINE;
+
+	if (active_branch)
+	{
+		char	   *opt = psql_scan_slash_option(scan_state,
+												 OT_NORMAL, NULL, false);
+
+		pset.stmtName = NULL;
+		if (!opt)
+		{
+			pg_log_error("\\%s: missing required argument", cmd);
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			pset.stmtName = opt;
+			pset.parse_flag = true;
+			status = PSQL_CMD_SEND;
+		}
+	}
+	else
+		ignore_slash_options(scan_state);
+
+	return status;
+}
+
 /*
  * \password -- set user password
  */
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index fe8e049c4c..1710ee9fc5 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1274,16 +1274,34 @@ sendquery_cleanup:
 		pset.gsavepopt = NULL;
 	}
 
-	/* clean up after \bind */
+	/* clean up after \bind or \bindx */
 	if (pset.bind_flag)
 	{
 		for (i = 0; i < pset.bind_nparams; i++)
 			free(pset.bind_params[i]);
 		free(pset.bind_params);
+		free(pset.stmtName);
 		pset.bind_params = NULL;
+		pset.stmtName = NULL;
 		pset.bind_flag = false;
 	}
 
+	/* clean up after \parse */
+	if (pset.parse_flag)
+	{
+		free(pset.stmtName);
+		pset.stmtName = NULL;
+		pset.parse_flag = false;
+	}
+
+	/* clean up after \close */
+	if (pset.close_flag)
+	{
+		free(pset.stmtName);
+		pset.stmtName = NULL;
+		pset.close_flag = false;
+	}
+
 	/* reset \gset trigger */
 	if (pset.gset_prefix)
 	{
@@ -1469,8 +1487,20 @@ ExecQueryAndProcessResults(const char *query,
 	else
 		INSTR_TIME_SET_ZERO(before);
 
-	if (pset.bind_flag)
-		success = PQsendQueryParams(pset.db, query, pset.bind_nparams, NULL, (const char *const *) pset.bind_params, NULL, NULL, 0);
+	if (pset.bind_flag && pset.stmtName == NULL)	/* \bind */
+		success = PQsendQueryParams(pset.db, query,
+									pset.bind_nparams, NULL,
+									(const char *const *) pset.bind_params,
+									NULL, NULL, 0);
+	else if (pset.bind_flag && pset.stmtName != NULL)	/* \bindx */
+		success = PQsendQueryPrepared(pset.db, pset.stmtName,
+									  pset.bind_nparams,
+									  (const char *const *) pset.bind_params,
+									  NULL, NULL, 0);
+	else if (pset.close_flag)	/* \close */
+		success = PQsendClosePrepared(pset.db, pset.stmtName);
+	else if (pset.parse_flag)	/* \parse */
+		success = PQsendPrepare(pset.db, pset.stmtName, query, 0, NULL);
 	else
 		success = PQsendQuery(pset.db, query);
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 6f58a11074..b1f167d1dd 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -165,6 +165,9 @@ slashUsage(unsigned short int pager)
 
 	HELP0("General\n");
 	HELP0("  \\bind [PARAM]...       set query parameters\n");
+	HELP0("  \\bindx STMT_NAME [PARAM]...\n"
+		  "                         set query parameters for an existing prepared statement\n");
+	HELP0("  \\close STMT_NAME       close an existing prepared statement\n");
 	HELP0("  \\copyright             show PostgreSQL usage and distribution terms\n");
 	HELP0("  \\crosstabview [COLUMNS] execute query and display result in crosstab\n");
 	HELP0("  \\errverbose            show most recent error message at maximum verbosity\n");
@@ -312,6 +315,7 @@ slashUsage(unsigned short int pager)
 			  "                         connect to new database (currently no connection)\n");
 	HELP0("  \\conninfo              display information about current connection\n");
 	HELP0("  \\encoding [ENCODING]   show or set client encoding\n");
+	HELP0("  \\parse STMT_NAME       create a prepared statement\n");
 	HELP0("  \\password [USERNAME]   securely change the password for a user\n");
 	HELP0("\n");
 
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 505f99d8e4..485fca871c 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -100,6 +100,12 @@ typedef struct _psqlSettings
 								 * protocol */
 	int			bind_nparams;	/* number of parameters */
 	char	  **bind_params;	/* parameters for extended query protocol call */
+	bool		close_flag;		/* one-shot request to close a prepared
+								 * statement using extended query protocol */
+	bool		parse_flag;		/* one-shot request to parse query using
+								 * extended query protocol */
+	char	   *stmtName;		/* prepared statement name used for extended
+								 * query protocol commands */
 	bool		crosstab_flag;	/* one-shot request to crosstab result */
 	char	   *ctv_args[4];	/* \crosstabview arguments */
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d453e224d9..6bf231cea6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1713,8 +1713,8 @@ psql_completion(const char *text, int start, int end)
 	/* psql's backslash commands. */
 	static const char *const backslash_commands[] = {
 		"\\a",
-		"\\bind",
-		"\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
+		"\\bind", "\\bindx",
+		"\\connect", "\\conninfo", "\\C", "\\cd", "\\close", "\\copy",
 		"\\copyright", "\\crosstabview",
 		"\\d", "\\da", "\\dA", "\\dAc", "\\dAf", "\\dAo", "\\dAp",
 		"\\db", "\\dc", "\\dconfig", "\\dC", "\\dd", "\\ddp", "\\dD",
@@ -1731,7 +1731,7 @@ psql_completion(const char *text, int start, int end)
 		"\\if", "\\include", "\\include_relative", "\\ir",
 		"\\list", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\out",
-		"\\password", "\\print", "\\prompt", "\\pset",
+		"\\parse", "\\password", "\\print", "\\prompt", "\\pset",
 		"\\qecho", "\\quit",
 		"\\reset",
 		"\\s", "\\set", "\\setenv", "\\sf", "\\sv",
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3bbe4c5f97..79e8e5f8cd 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -98,6 +98,53 @@ two | 2
    1 |   2
 (1 row)
 
+-- \parse (extended query protocol)
+\parse
+\parse: missing required argument
+SELECT 1 \parse ''
+SELECT 2 \parse stmt1
+SELECT $1 \parse stmt2
+SELECT $1, $2 \parse stmt3
+-- \bindx (extended query protocol)
+\bindx
+\bindx: missing required argument
+\bindx '' \g
+ ?column? 
+----------
+        1
+(1 row)
+
+\bindx stmt1 \g
+ ?column? 
+----------
+        2
+(1 row)
+
+\bindx stmt2 'foo' \g
+ ?column? 
+----------
+ foo
+(1 row)
+
+\bindx stmt3 'foo' 'bar' \g
+ ?column? | ?column? 
+----------+----------
+ foo      | bar
+(1 row)
+
+-- \close (extended query protocol)
+\close
+\close: missing required argument
+\close ''
+\close stmt2
+\close stmt2
+SELECT name, statement FROM pg_prepared_statements ORDER BY name;
+ name  |   statement    
+-------+----------------
+ stmt1 | SELECT 2 
+ stmt3 | SELECT $1, $2 
+(2 rows)
+
 -- \bind (extended query protocol)
 SELECT 1 \bind \g
  ?column? 
@@ -129,6 +176,11 @@ ERROR:  cannot insert multiple commands into a prepared statement
 -- bind error
 SELECT $1, $2 \bind 'foo' \g
 ERROR:  bind message supplies 1 parameters, but prepared statement "" requires 2
+-- bindx error
+\bindx stmt2 'baz' \g
+ERROR:  prepared statement "stmt2" does not exist
+\bindx stmt3 'baz' \g
+ERROR:  bind message supplies 1 parameters, but prepared statement "stmt3" requires 2
 -- \gset
 select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
 \echo :pref01_test01 :pref01_test02 :pref01_test03
@@ -4507,9 +4559,11 @@ bar 'bar' "bar"
 	\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
 	\a
 	SELECT $1 \bind 1 \g
+	\bindx stmt1 1 2 \g
 	\C arg1
 	\c arg1 arg2 arg3 arg4
 	\cd arg1
+	\close stmt1
 	\conninfo
 	\copy arg1 arg2 arg3 arg4 arg5 arg6
 	\copyright
@@ -4538,6 +4592,7 @@ invalid command \lo
 	\lo_list
 	\o arg1
 	\p
+	SELECT 1 \parse
 	\password arg1
 	\prompt arg1 arg2
 	\pset arg1 arg2
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3b3c6f6e29..da233109a0 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -45,8 +45,28 @@ SELECT 1 as one, 2 as two \g (format=csv csv_fieldsep='\t')
 SELECT 1 as one, 2 as two \gx (title='foo bar')
 \g
 
--- \bind (extended query protocol)
+-- \parse (extended query protocol)
+\parse
+SELECT 1 \parse ''
+SELECT 2 \parse stmt1
+SELECT $1 \parse stmt2
+SELECT $1, $2 \parse stmt3
 
+-- \bindx (extended query protocol)
+\bindx
+\bindx '' \g
+\bindx stmt1 \g
+\bindx stmt2 'foo' \g
+\bindx stmt3 'foo' 'bar' \g
+
+-- \close (extended query protocol)
+\close
+\close ''
+\close stmt2
+\close stmt2
+SELECT name, statement FROM pg_prepared_statements ORDER BY name;
+
+-- \bind (extended query protocol)
 SELECT 1 \bind \g
 SELECT $1 \bind 'foo' \g
 SELECT $1, $2 \bind 'foo' 'bar' \g
@@ -58,6 +78,9 @@ SELECT foo \bind \g
 SELECT 1 \; SELECT 2 \bind \g
 -- bind error
 SELECT $1, $2 \bind 'foo' \g
+-- bindx error
+\bindx stmt2 'baz' \g
+\bindx stmt3 'baz' \g
 
 -- \gset
 
@@ -990,9 +1013,11 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
 	\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
 	\a
 	SELECT $1 \bind 1 \g
+	\bindx stmt1 1 2 \g
 	\C arg1
 	\c arg1 arg2 arg3 arg4
 	\cd arg1
+	\close stmt1
 	\conninfo
 	\copy arg1 arg2 arg3 arg4 arg5 arg6
 	\copyright
@@ -1020,6 +1045,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
 	\lo_list
 	\o arg1
 	\p
+	SELECT 1 \parse
 	\password arg1
 	\prompt arg1 arg2
 	\pset arg1 arg2
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 830306ea1e..dd2f805357 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -916,6 +916,36 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
        </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-meta-command-bindx">
+       <term><literal>\bindx</literal> <replaceable class="parameter">statement_name</replaceable> [ <replaceable class="parameter">parameter</replaceable> ] ... </term>
+
+       <listitem>
+        <para>
+         <literal>\bindx</literal> is equivalent to <literal>\bind</literal>,
+         except that it takes the name of an existing prepared statement as
+         first parameter. An empty string denotes the unnamed prepared
+         statement.
+        </para>
+
+        <para>
+         Example:
+<programlisting>
+INSERT INTO tbls1 VALUES ($1, $2) \parse stmt1
+\bindx stmt1 'first value' 'second value' \g
+</programlisting>
+        </para>
+
+        <para>
+         This command causes the extended query protocol (see
+         <xref linkend="protocol-query-concepts"/>) to be used, unlike normal
+         <application>psql</application> operation, which uses the simple
+         query protocol. So this command can be useful to test the extended
+         query protocol from <application>psql</application>.
+        </para>
+
+       </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-meta-command-c-lc">
         <term><literal>\c</literal> or <literal>\connect [ -reuse-previous=<replaceable class="parameter">on|off</replaceable> ] [ <replaceable class="parameter">dbname</replaceable> [ <replaceable class="parameter">username</replaceable> ] [ <replaceable class="parameter">host</replaceable> ] [ <replaceable class="parameter">port</replaceable> ] | <replaceable class="parameter">conninfo</replaceable> ]</literal></term>
         <listitem>
@@ -1037,6 +1067,35 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-meta-command-close">
+       <term><literal>\close</literal> <replaceable class="parameter">prepared_statement_name</replaceable></term>
+
+       <listitem>
+        <para>
+         Closes the specified prepared statement. An empty string denotes the
+         unnamed prepared statement. If no prepared statement exists with this
+         name, the operation is a no-op.
+        </para>
+
+        <para>
+         Example:
+<programlisting>
+SELECT $1 \parse stmt1
+\close stmt1
+</programlisting>
+        </para>
+
+        <para>
+         This command causes the extended query protocol to be used,
+         unlike normal <application>psql</application> operation, which
+         uses the simple query protocol. So this command can be useful
+         to test the extended query protocol from
+         <application>psql</application>.
+        </para>
+
+       </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-meta-commands-copy">
         <term><literal>\copy { <replaceable class="parameter">table</replaceable> [ ( <replaceable class="parameter">column_list</replaceable> ) ] }
         <literal>from</literal>
@@ -2779,6 +2838,37 @@ lo_import 152801
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-meta-command-parse">
+        <term><literal>\parse <replaceable class="parameter">statement_name</replaceable></literal></term>
+        <listitem>
+        <para>
+         Creates a prepared statement from the current query buffer, based on
+         the name of a destination prepared-statement object. An empty string
+         denotes the unnamed prepared statement.
+        </para>
+
+        <para>
+         Example:
+<programlisting>
+SELECT $1 \parse stmt1
+</programlisting>
+        </para>
+
+        <para>
+         This command causes the extended query protocol to be used, unlike
+         normal <application>psql</application> operation, which uses the
+         simple query protocol. A
+         <xref linkend="protocol-message-formats-Parse"/>
+         message will be issued by this command so it can be useful to
+         test the extended query protocol from
+         <application>psql</application>. This command affects only the next
+         query executed; all subsequent queries will use the simple query
+         protocol by default.
+        </para>
+
+        </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-meta-command-password">
         <term><literal>\password [ <replaceable class="parameter">username</replaceable> ]</literal></term>
         <listitem>
-- 
2.45.2

From cce69afc97d2553ec66859fcac1ef75334ef2b60 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Wed, 24 Jul 2024 14:01:17 +0900
Subject: [PATCH v6 2/2] psql: Refactor status of extended protocol commands

Rather than three separate boolean flags, this groups the commands to be
handled with a single enum, able to control which libpq API to call.
---
 src/bin/psql/command.c  |  8 ++--
 src/bin/psql/common.c   | 87 ++++++++++++++++++++++-------------------
 src/bin/psql/settings.h | 17 +++++---
 3 files changed, 61 insertions(+), 51 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index f23a7404cc..6a3c58b20f 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -498,7 +498,7 @@ exec_command_bind(PsqlScanState scan_state, bool active_branch)
 		}
 
 		pset.bind_nparams = nparams;
-		pset.bind_flag = true;
+		pset.send_mode = PSQL_SEND_EXTENDED_QUERY_PARAMS;
 	}
 	else
 		ignore_slash_options(scan_state);
@@ -534,7 +534,7 @@ exec_command_bindx(PsqlScanState scan_state, bool active_branch,
 		else
 		{
 			pset.stmtName = opt;
-			pset.bind_flag = true;
+			pset.send_mode = PSQL_SEND_EXTENDED_QUERY_PREPARED;
 
 			/* set of parameters */
 			while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)))
@@ -728,7 +728,7 @@ exec_command_close(PsqlScanState scan_state, bool active_branch, const char *cmd
 		else
 		{
 			pset.stmtName = opt;
-			pset.close_flag = true;
+			pset.send_mode = PSQL_SEND_EXTENDED_CLOSE;
 			status = PSQL_CMD_SEND;
 		}
 	}
@@ -2214,7 +2214,7 @@ exec_command_parse(PsqlScanState scan_state, bool active_branch,
 		else
 		{
 			pset.stmtName = opt;
-			pset.parse_flag = true;
+			pset.send_mode = PSQL_SEND_EXTENDED_PARSE;
 			status = PSQL_CMD_SEND;
 		}
 	}
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 1710ee9fc5..7f3b6cbd07 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1274,33 +1274,28 @@ sendquery_cleanup:
 		pset.gsavepopt = NULL;
 	}
 
-	/* clean up after \bind or \bindx */
-	if (pset.bind_flag)
+	/* clean up after extended protocol queries */
+	switch (pset.send_mode)
 	{
-		for (i = 0; i < pset.bind_nparams; i++)
-			free(pset.bind_params[i]);
-		free(pset.bind_params);
-		free(pset.stmtName);
-		pset.bind_params = NULL;
-		pset.stmtName = NULL;
-		pset.bind_flag = false;
-	}
-
-	/* clean up after \parse */
-	if (pset.parse_flag)
-	{
-		free(pset.stmtName);
-		pset.stmtName = NULL;
-		pset.parse_flag = false;
-	}
-
-	/* clean up after \close */
-	if (pset.close_flag)
-	{
-		free(pset.stmtName);
-		pset.stmtName = NULL;
-		pset.close_flag = false;
+		case PSQL_SEND_EXTENDED_CLOSE:	/* \close */
+			free(pset.stmtName);
+			break;
+		case PSQL_SEND_EXTENDED_PARSE:	/* \parse */
+			free(pset.stmtName);
+			break;
+		case PSQL_SEND_EXTENDED_QUERY_PARAMS:	/* \bind */
+		case PSQL_SEND_EXTENDED_QUERY_PREPARED:	/* \bindx */
+			for (i = 0; i < pset.bind_nparams; i++)
+				free(pset.bind_params[i]);
+			free(pset.bind_params);
+			free(pset.stmtName);
+			pset.bind_params = NULL;
+			break;
+		case PSQL_SEND_QUERY:
+			break;
 	}
+	pset.stmtName = NULL;
+	pset.send_mode = PSQL_SEND_QUERY;
 
 	/* reset \gset trigger */
 	if (pset.gset_prefix)
@@ -1487,22 +1482,32 @@ ExecQueryAndProcessResults(const char *query,
 	else
 		INSTR_TIME_SET_ZERO(before);
 
-	if (pset.bind_flag && pset.stmtName == NULL)	/* \bind */
-		success = PQsendQueryParams(pset.db, query,
-									pset.bind_nparams, NULL,
-									(const char *const *) pset.bind_params,
-									NULL, NULL, 0);
-	else if (pset.bind_flag && pset.stmtName != NULL)	/* \bindx */
-		success = PQsendQueryPrepared(pset.db, pset.stmtName,
-									  pset.bind_nparams,
-									  (const char *const *) pset.bind_params,
-									  NULL, NULL, 0);
-	else if (pset.close_flag)	/* \close */
-		success = PQsendClosePrepared(pset.db, pset.stmtName);
-	else if (pset.parse_flag)	/* \parse */
-		success = PQsendPrepare(pset.db, pset.stmtName, query, 0, NULL);
-	else
-		success = PQsendQuery(pset.db, query);
+	switch (pset.send_mode)
+	{
+		case PSQL_SEND_EXTENDED_CLOSE:
+			success = PQsendClosePrepared(pset.db, pset.stmtName);
+			break;
+		case PSQL_SEND_EXTENDED_PARSE:
+			success = PQsendPrepare(pset.db, pset.stmtName, query, 0, NULL);
+			break;
+		case PSQL_SEND_EXTENDED_QUERY_PARAMS:
+			Assert(pset.stmtName == NULL);
+			success = PQsendQueryParams(pset.db, query,
+										pset.bind_nparams, NULL,
+										(const char *const *) pset.bind_params,
+										NULL, NULL, 0);
+			break;
+		case PSQL_SEND_EXTENDED_QUERY_PREPARED:
+			Assert(pset.stmtName != NULL);
+			success = PQsendQueryPrepared(pset.db, pset.stmtName,
+										  pset.bind_nparams,
+										  (const char *const *) pset.bind_params,
+										  NULL, NULL, 0);
+			break;
+		case PSQL_SEND_QUERY:
+			success = PQsendQuery(pset.db, query);
+			break;
+	}
 
 	if (!success)
 	{
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 485fca871c..a22de8ef78 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -62,6 +62,15 @@ typedef enum
 	PSQL_COMP_CASE_LOWER,
 } PSQL_COMP_CASE;
 
+typedef enum
+{
+	PSQL_SEND_QUERY,
+	PSQL_SEND_EXTENDED_CLOSE,
+	PSQL_SEND_EXTENDED_PARSE,
+	PSQL_SEND_EXTENDED_QUERY_PARAMS,
+	PSQL_SEND_EXTENDED_QUERY_PREPARED,
+} PSQL_SEND_MODE;
+
 typedef enum
 {
 	hctl_none = 0,
@@ -96,14 +105,10 @@ typedef struct _psqlSettings
 	char	   *gset_prefix;	/* one-shot prefix argument for \gset */
 	bool		gdesc_flag;		/* one-shot request to describe query result */
 	bool		gexec_flag;		/* one-shot request to execute query result */
-	bool		bind_flag;		/* one-shot request to use extended query
-								 * protocol */
+	PSQL_SEND_MODE send_mode;	/* one-shot request to send query with normal
+								 * or extended query protocol */
 	int			bind_nparams;	/* number of parameters */
 	char	  **bind_params;	/* parameters for extended query protocol call */
-	bool		close_flag;		/* one-shot request to close a prepared
-								 * statement using extended query protocol */
-	bool		parse_flag;		/* one-shot request to parse query using
-								 * extended query protocol */
 	char	   *stmtName;		/* prepared statement name used for extended
 								 * query protocol commands */
 	bool		crosstab_flag;	/* one-shot request to crosstab result */
-- 
2.45.2

Attachment: signature.asc
Description: PGP signature

Reply via email to