Skip to content

Commit

Permalink
psql: Add command to use extended query protocol
Browse files Browse the repository at this point in the history
This adds a new psql command \bind that sets query parameters and
causes the next query to be sent using the extended query protocol.
Example:

    SELECT $1, $2 \bind 'foo' 'bar' \g

This may be useful for psql scripting, but one of the main purposes is
also to be able to test various aspects of the extended query protocol
from psql and to write tests more easily.

Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/e8dd1cd5-0e04-3598-0518-a605159fe314@enterprisedb.com
  • Loading branch information
petere committed Nov 15, 2022
1 parent a9e9a9f commit 5b66de3
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 1 deletion.
36 changes: 36 additions & 0 deletions doc/src/sgml/ref/psql-ref.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,42 @@ testdb=&gt;
</listitem>
</varlistentry>

<varlistentry>
<term><literal>\bind</literal> [ <replaceable class="parameter">parameter</replaceable> ] ... </term>

<listitem>
<para>
Sets query parameters for the next query execution, with the
specified parameters passed for any parameter placeholders
(<literal>$1</literal> etc.).
</para>

<para>
Example:
<programlisting>
INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
</programlisting>
</para>

<para>
This also works for query-execution commands besides
<literal>\g</literal>, such as <literal>\gx</literal> and
<literal>\gset</literal>.
</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 psql. (The extended query protocol is used even
if the query has no parameters and this command specifies zero
parameters.) This command affects only the next query executed; all
subsequent queries will use the simple query protocol by default.
</para>
</listitem>
</varlistentry>

<varlistentry>
<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>
Expand Down
37 changes: 37 additions & 0 deletions src/bin/psql/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ static backslashResult exec_command(const char *cmd,
PQExpBuffer query_buf,
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_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,
Expand Down Expand Up @@ -308,6 +309,8 @@ exec_command(const char *cmd,

if (strcmp(cmd, "a") == 0)
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, "C") == 0)
status = exec_command_C(scan_state, active_branch);
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
Expand Down Expand Up @@ -453,6 +456,40 @@ exec_command_a(PsqlScanState scan_state, bool active_branch)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}

/*
* \bind -- set query parameters
*/
static backslashResult
exec_command_bind(PsqlScanState scan_state, bool active_branch)
{
backslashResult status = PSQL_CMD_SKIP_LINE;

if (active_branch)
{
char *opt;
int nparams = 0;
int nalloc = 0;

pset.bind_params = NULL;

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] = pg_strdup(opt);
}

pset.bind_nparams = nparams;
pset.bind_flag = true;
}

return status;
}

/*
* \C -- override table title (formerly change HTML caption)
*/
Expand Down
15 changes: 14 additions & 1 deletion src/bin/psql/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,16 @@ SendQuery(const char *query)
pset.gsavepopt = NULL;
}

/* clean up after \bind */
if (pset.bind_flag)
{
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;
}

/* reset \gset trigger */
if (pset.gset_prefix)
{
Expand Down Expand Up @@ -1397,7 +1407,10 @@ ExecQueryAndProcessResults(const char *query,
if (timing)
INSTR_TIME_SET_CURRENT(before);

success = PQsendQuery(pset.db, query);
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);

if (!success)
{
Expand Down
1 change: 1 addition & 0 deletions src/bin/psql/help.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ slashUsage(unsigned short int pager)
initPQExpBuffer(&buf);

HELP0("General\n");
HELP0(" \\bind [PARAM]... set query parameters\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");
Expand Down
3 changes: 3 additions & 0 deletions src/bin/psql/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ 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 */
int bind_nparams; /* number of parameters */
char **bind_params; /* parameters for extended query protocol call */
bool crosstab_flag; /* one-shot request to crosstab result */
char *ctv_args[4]; /* \crosstabview arguments */

Expand Down
1 change: 1 addition & 0 deletions src/bin/psql/tab-complete.c
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,7 @@ 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",
"\\copyright", "\\crosstabview",
"\\d", "\\da", "\\dA", "\\dAc", "\\dAf", "\\dAo", "\\dAp",
Expand Down
31 changes: 31 additions & 0 deletions src/test/regress/expected/psql.out
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ two | 2
1 | 2
(1 row)

-- \bind (extended query protocol)
SELECT 1 \bind \g
?column?
----------
1
(1 row)

SELECT $1 \bind 'foo' \g
?column?
----------
foo
(1 row)

SELECT $1, $2 \bind 'foo' 'bar' \g
?column? | ?column?
----------+----------
foo | bar
(1 row)

-- errors
-- parse error
SELECT foo \bind \g
ERROR: column "foo" does not exist
LINE 1: SELECT foo
^
-- tcop error
SELECT 1 \; SELECT 2 \bind \g
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
-- \gset
select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
\echo :pref01_test01 :pref01_test02 :pref01_test03
Expand Down
14 changes: 14 additions & 0 deletions src/test/regress/sql/psql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ 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)

SELECT 1 \bind \g
SELECT $1 \bind 'foo' \g
SELECT $1, $2 \bind 'foo' 'bar' \g

-- errors
-- parse error
SELECT foo \bind \g
-- tcop error
SELECT 1 \; SELECT 2 \bind \g
-- bind error
SELECT $1, $2 \bind 'foo' \g

-- \gset

select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
Expand Down

0 comments on commit 5b66de3

Please sign in to comment.