diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 07419a3b92e..3fd9959ed16 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -917,6 +917,36 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
+
+ \bind_named statement_name [ parameter ] ...
+
+
+
+ \bind_named is equivalent to \bind,
+ except that it takes the name of an existing prepared statement as
+ first parameter. An empty string denotes the unnamed prepared
+ statement.
+
+
+
+ Example:
+
+INSERT INTO tbls1 VALUES ($1, $2) \parse stmt1
+\bind_named stmt1 'first value' 'second value' \g
+
+
+
+
+ This command causes the extended query protocol (see
+ ) to be used, unlike normal
+ psql operation, which uses the simple
+ query protocol. So this command can be useful to test the extended
+ query protocol from psql.
+
+
+
+
+
\c or \connect [ -reuse-previous=on|off ] [ dbname [ username ] [ host ] [ port ] | conninfo ]
@@ -1038,6 +1068,35 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
+
+ \close prepared_statement_name
+
+
+
+ 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.
+
+
+
+ Example:
+
+SELECT $1 \parse stmt1
+\close stmt1
+
+
+
+
+ This command causes the extended query protocol to be used,
+ unlike normal psql operation, which
+ uses the simple query protocol. So this command can be useful
+ to test the extended query protocol from
+ psql.
+
+
+
+
+
\copy { table [ ( column_list ) ] }
from
@@ -2780,6 +2839,37 @@ lo_import 152801
+
+ \parse statement_name
+
+
+ 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.
+
+
+
+ Example:
+
+SELECT $1 \parse stmt1
+
+
+
+
+ This command causes the extended query protocol to be used, unlike
+ normal psql operation, which uses the
+ simple query protocol. A
+
+ message will be issued by this command so it can be useful to
+ test the extended query protocol from
+ psql. This command affects only the next
+ query executed; all subsequent queries will use the simple query
+ protocol by default.
+
+
+
+
+
\password [ username ]
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 180781ecd05..4dfc7b2d857 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_bind_named(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, "bind_named") == 0)
+ status = exec_command_bind_named(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)))
{
@@ -485,7 +498,57 @@ 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);
+
+ return status;
+}
+
+/*
+ * \bind_named -- set query parameters for an existing prepared statement
+ */
+static backslashResult
+exec_command_bind_named(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.send_mode = PSQL_SEND_EXTENDED_QUERY_PREPARED;
+
+ /* 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);
@@ -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.send_mode = PSQL_SEND_EXTENDED_CLOSE;
+ 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.send_mode = PSQL_SEND_EXTENDED_PARSE;
+ 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 fe8e049c4c1..be265aa05a4 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1274,15 +1274,28 @@ sendquery_cleanup:
pset.gsavepopt = NULL;
}
- /* clean up after \bind */
- 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);
- pset.bind_params = NULL;
- pset.bind_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: /* \bind_named */
+ 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)
@@ -1456,7 +1469,7 @@ ExecQueryAndProcessResults(const char *query,
const printQueryOpt *opt, FILE *printQueryFout)
{
bool timing = pset.timing;
- bool success;
+ bool success = false;
bool return_early = false;
instr_time before,
after;
@@ -1469,10 +1482,32 @@ 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);
- 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/help.c b/src/bin/psql/help.c
index 6f58a110748..19d20c58781 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(" \\bind_named 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 505f99d8e47..a22de8ef78e 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,10 +105,12 @@ 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 */
+ 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 024469474da..0d25981253e 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", "\\bind_named",
+ "\\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 3bbe4c5f974..6aeb7cb9636 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
+-- \bind_named (extended query protocol)
+\bind_named
+\bind_named: missing required argument
+\bind_named '' \g
+ ?column?
+----------
+ 1
+(1 row)
+
+\bind_named stmt1 \g
+ ?column?
+----------
+ 2
+(1 row)
+
+\bind_named stmt2 'foo' \g
+ ?column?
+----------
+ foo
+(1 row)
+
+\bind_named 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
+-- bind_named error
+\bind_named stmt2 'baz' \g
+ERROR: prepared statement "stmt2" does not exist
+\bind_named 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
+ \bind_named 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 3b3c6f6e294..0a2f8b46922 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
+-- \bind_named (extended query protocol)
+\bind_named
+\bind_named '' \g
+\bind_named stmt1 \g
+\bind_named stmt2 'foo' \g
+\bind_named 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
+-- bind_named error
+\bind_named stmt2 'baz' \g
+\bind_named 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
+ \bind_named 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/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6d424c89186..3f3a8f2634b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1958,6 +1958,7 @@ PSQL_COMP_CASE
PSQL_ECHO
PSQL_ECHO_HIDDEN
PSQL_ERROR_ROLLBACK
+PSQL_SEND_MODE
PTEntryArray
PTIterationArray
PTOKEN_PRIVILEGES