1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

libpq: Add support for Close on portals and statements

The following routines are added to libpq:
PGresult *PQclosePrepared(PGconn *conn, const char *stmt);
PGresult *PQclosePortal(PGconn *conn, const char *portal);
int PQsendClosePrepared(PGconn *conn, const char *stmt);
int PQsendClosePortal(PGconn *conn, const char *portal);

The "send" routines are non-blocking versions of the two others.

Close messages are part of the protocol but they did not have a libpq
implementation.  And, having these routines is for instance useful with
connection poolers as these can detect more easily Close messages
than DEALLOCATE queries.

The implementation takes advantage of what the Describe routines rely on
for portals and statements.  Some regression tests are added in
libpq_pipeline, for the four new routines, by closing portals and
statements created already by the tests.

Author: Jelte Fennema
Reviewed-by: Jian He, Michael Paquier
Discussion: https://postgr.es/m/CAGECzQTb4xFAopAVokudB+L62Kt44mNAL4Z9zZ7UTrs1TRFvWA@mail.gmail.com
This commit is contained in:
Michael Paquier
2023-07-04 14:48:10 +09:00
parent 03f80daac8
commit 28b5726561
8 changed files with 337 additions and 32 deletions

View File

@ -3250,10 +3250,7 @@ PGresult *PQprepare(PGconn *conn,
Prepared statements for use with <xref linkend="libpq-PQexecPrepared"/> can also
be created by executing SQL <xref linkend="sql-prepare"/>
statements. Also, although there is no <application>libpq</application>
function for deleting a prepared statement, the SQL <xref
linkend="sql-deallocate"/> statement
can be used for that purpose.
statements.
</para>
<para>
@ -3360,6 +3357,66 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName);
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-PQclosePrepared">
<term><function>PQclosePrepared</function><indexterm><primary>PQclosePrepared</primary></indexterm></term>
<listitem>
<para>
Submits a request to close the specified prepared statement, and waits
for completion.
<synopsis>
PGresult *PQclosePrepared(PGconn *conn, const char *stmtName);
</synopsis>
</para>
<para>
<xref linkend="libpq-PQclosePrepared"/> allows an application to close
a previously prepared statement. Closing a statement releases all
of its associated resources on the server and allows its name to be
reused.
</para>
<para>
<parameter>stmtName</parameter> can be <literal>""</literal> or
<symbol>NULL</symbol> to reference the unnamed statement. It is fine
if no statement exists with this name, in that case the operation is a
no-op. On success, a <structname>PGresult</structname> with
status <literal>PGRES_COMMAND_OK</literal> is returned.
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-PQclosePortal">
<term><function>PQclosePortal</function><indexterm><primary>PQclosePortal</primary></indexterm></term>
<listitem>
<para>
Submits a request to close the specified portal, and waits for
completion.
<synopsis>
PGresult *PQclosePortal(PGconn *conn, const char *portalName);
</synopsis>
</para>
<para>
<xref linkend="libpq-PQclosePortal"/> allows an application to trigger
a close of a previously created portal. Closing a portal releases all
of its associated resources on the server and allows its name to be
reused. (<application>libpq</application> does not provide any
direct access to portals, but you can use this function to close a
cursor created with a <command>DECLARE CURSOR</command> SQL command.)
</para>
<para>
<parameter>portalName</parameter> can be <literal>""</literal> or
<symbol>NULL</symbol> to reference the unnamed portal. It is fine
if no portal exists with this name, in that case the operation is a
no-op. On success, a <structname>PGresult</structname> with status
<literal>PGRES_COMMAND_OK</literal> is returned.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
@ -4851,15 +4908,19 @@ unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length);
<xref linkend="libpq-PQsendQueryParams"/>,
<xref linkend="libpq-PQsendPrepare"/>,
<xref linkend="libpq-PQsendQueryPrepared"/>,
<xref linkend="libpq-PQsendDescribePrepared"/>, and
<xref linkend="libpq-PQsendDescribePrepared"/>,
<xref linkend="libpq-PQsendDescribePortal"/>,
<xref linkend="libpq-PQsendClosePrepared"/>, and
<xref linkend="libpq-PQsendClosePortal"/>,
which can be used with <xref linkend="libpq-PQgetResult"/> to duplicate
the functionality of
<xref linkend="libpq-PQexecParams"/>,
<xref linkend="libpq-PQprepare"/>,
<xref linkend="libpq-PQexecPrepared"/>,
<xref linkend="libpq-PQdescribePrepared"/>, and
<xref linkend="libpq-PQdescribePrepared"/>,
<xref linkend="libpq-PQdescribePortal"/>
<xref linkend="libpq-PQclosePrepared"/>, and
<xref linkend="libpq-PQclosePortal"/>
respectively.
<variablelist>
@ -5008,6 +5069,46 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName);
</listitem>
</varlistentry>
<varlistentry id="libpq-PQsendClosePrepared">
<term><function>PQsendClosePrepared</function><indexterm><primary>PQsendClosePrepared</primary></indexterm></term>
<listitem>
<para>
Submits a request to close the specified prepared statement, without
waiting for completion.
<synopsis>
int PQsendClosePrepared(PGconn *conn, const char *stmtName);
</synopsis>
This is an asynchronous version of <xref linkend="libpq-PQclosePrepared"/>:
it returns 1 if it was able to dispatch the request, and 0 if not.
After a successful call, call <xref linkend="libpq-PQgetResult"/> to
obtain the results. The function's parameters are handled
identically to <xref linkend="libpq-PQclosePrepared"/>.
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-PQsendClosePortal">
<term><function>PQsendClosePortal</function><indexterm><primary>PQsendClosePortal</primary></indexterm></term>
<listitem>
<para>
Submits a request to close specified portal, without waiting for
completion.
<synopsis>
int PQsendClosePortal(PGconn *conn, const char *portalName);
</synopsis>
This is an asynchronous version of <xref linkend="libpq-PQclosePortal"/>:
it returns 1 if it was able to dispatch the request, and 0 if not.
After a successful call, call <xref linkend="libpq-PQgetResult"/> to
obtain the results. The function's parameters are handled
identically to <xref linkend="libpq-PQclosePortal"/>.
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-PQgetResult">
<term><function>PQgetResult</function><indexterm><primary>PQgetResult</primary></indexterm></term>
@ -5019,7 +5120,9 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName);
<xref linkend="libpq-PQsendPrepare"/>,
<xref linkend="libpq-PQsendQueryPrepared"/>,
<xref linkend="libpq-PQsendDescribePrepared"/>,
<xref linkend="libpq-PQsendDescribePortal"/>, or
<xref linkend="libpq-PQsendDescribePortal"/>,
<xref linkend="libpq-PQsendClosePrepared"/>,
<xref linkend="libpq-PQsendClosePortal"/>, or
<xref linkend="libpq-PQpipelineSync"/>
call, and returns it.
A null pointer is returned when the command is complete and there
@ -5350,6 +5453,8 @@ int PQflush(PGconn *conn);
<function>PQexecPrepared</function>,
<function>PQdescribePrepared</function>,
<function>PQdescribePortal</function>,
<function>PQclosePrepared</function>,
<function>PQclosePortal</function>,
is an error condition.
<function>PQsendQuery</function> is
also disallowed, because it uses the simple query protocol.
@ -5389,8 +5494,10 @@ int PQflush(PGconn *conn);
establish a synchronization point in the pipeline,
or when <xref linkend="libpq-PQflush"/> is called.
The functions <xref linkend="libpq-PQsendPrepare"/>,
<xref linkend="libpq-PQsendDescribePrepared"/>, and
<xref linkend="libpq-PQsendDescribePortal"/> also work in pipeline mode.
<xref linkend="libpq-PQsendDescribePrepared"/>,
<xref linkend="libpq-PQsendDescribePortal"/>,
<xref linkend="libpq-PQsendClosePrepared"/>, and
<xref linkend="libpq-PQsendClosePortal"/> also work in pipeline mode.
Result processing is described below.
</para>

View File

@ -187,3 +187,7 @@ PQsetTraceFlags 184
PQmblenBounded 185
PQsendFlushRequest 186
PQconnectionUsedGSSAPI 187
PQclosePrepared 188
PQclosePortal 189
PQsendClosePrepared 190
PQsendClosePortal 191

View File

@ -77,8 +77,8 @@ static void parseInput(PGconn *conn);
static PGresult *getCopyResult(PGconn *conn, ExecStatusType copytype);
static bool PQexecStart(PGconn *conn);
static PGresult *PQexecFinish(PGconn *conn);
static int PQsendDescribe(PGconn *conn, char desc_type,
const char *desc_target);
static int PQsendTypedCommand(PGconn *conn, char command, char type,
const char *target);
static int check_field_number(const PGresult *res, int field_num);
static void pqPipelineProcessQueue(PGconn *conn);
static int pqPipelineFlush(PGconn *conn);
@ -2422,7 +2422,7 @@ PQdescribePrepared(PGconn *conn, const char *stmt)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendDescribe(conn, 'S', stmt))
if (!PQsendTypedCommand(conn, 'D', 'S', stmt))
return NULL;
return PQexecFinish(conn);
}
@ -2441,7 +2441,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendDescribe(conn, 'P', portal))
if (!PQsendTypedCommand(conn, 'D', 'P', portal))
return NULL;
return PQexecFinish(conn);
}
@ -2456,7 +2456,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
int
PQsendDescribePrepared(PGconn *conn, const char *stmt)
{
return PQsendDescribe(conn, 'S', stmt);
return PQsendTypedCommand(conn, 'D', 'S', stmt);
}
/*
@ -2469,26 +2469,96 @@ PQsendDescribePrepared(PGconn *conn, const char *stmt)
int
PQsendDescribePortal(PGconn *conn, const char *portal)
{
return PQsendDescribe(conn, 'P', portal);
return PQsendTypedCommand(conn, 'D', 'P', portal);
}
/*
* PQsendDescribe
* Common code to send a Describe command
* PQclosePrepared
* Close a previously prepared statement
*
* If the query was not even sent, return NULL; conn->errorMessage is set to
* a relevant message.
* If the query was sent, a new PGresult is returned (which could indicate
* either success or failure). On success, the PGresult contains status
* PGRES_COMMAND_OK. The user is responsible for freeing the PGresult via
* PQclear() when done with it.
*/
PGresult *
PQclosePrepared(PGconn *conn, const char *stmt)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendTypedCommand(conn, 'C', 'S', stmt))
return NULL;
return PQexecFinish(conn);
}
/*
* PQclosePortal
* Close a previously created portal
*
* This is exactly like PQclosePrepared, but for portals. Note that at the
* moment, libpq doesn't really expose portals to the client; but this can be
* used with a portal created by a SQL DECLARE CURSOR command.
*/
PGresult *
PQclosePortal(PGconn *conn, const char *portal)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendTypedCommand(conn, 'C', 'P', portal))
return NULL;
return PQexecFinish(conn);
}
/*
* PQsendClosePrepared
* Submit a Close Statement command, but don't wait for it to finish
*
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
int
PQsendClosePrepared(PGconn *conn, const char *stmt)
{
return PQsendTypedCommand(conn, 'C', 'S', stmt);
}
/*
* PQsendClosePortal
* Submit a Close Portal command, but don't wait for it to finish
*
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
int
PQsendClosePortal(PGconn *conn, const char *portal)
{
return PQsendTypedCommand(conn, 'C', 'P', portal);
}
/*
* PQsendTypedCommand
* Common code to send a Describe or Close command
*
* Available options for "command" are
* 'C' for Close; or
* 'D' for Describe.
*
* Available options for "type" are
* 'S' to run a command on a prepared statement; or
* 'P' to run a command on a portal.
*
* Available options for desc_type are
* 'S' to describe a prepared statement; or
* 'P' to describe a portal.
* Returns 1 on success and 0 on failure.
*/
static int
PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
PQsendTypedCommand(PGconn *conn, char command, char type, const char *target)
{
PGcmdQueueEntry *entry = NULL;
/* Treat null desc_target as empty string */
if (!desc_target)
desc_target = "";
/* Treat null target as empty string */
if (!target)
target = "";
if (!PQsendQueryStart(conn, true))
return 0;
@ -2497,10 +2567,10 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
if (entry == NULL)
return 0; /* error msg already set */
/* construct the Describe message */
if (pqPutMsgStart('D', conn) < 0 ||
pqPutc(desc_type, conn) < 0 ||
pqPuts(desc_target, conn) < 0 ||
/* construct the Close message */
if (pqPutMsgStart(command, conn) < 0 ||
pqPutc(type, conn) < 0 ||
pqPuts(target, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
goto sendFailed;
@ -2512,8 +2582,20 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
goto sendFailed;
}
/* remember we are doing a Describe */
entry->queryclass = PGQUERY_DESCRIBE;
/* remember if we are doing a Close or a Describe */
if (command == 'C')
{
entry->queryclass = PGQUERY_CLOSE;
}
else if (command == 'D')
{
entry->queryclass = PGQUERY_DESCRIBE;
}
else
{
libpq_append_conn_error(conn, "unknown command type provided");
goto sendFailed;
}
/*
* Give the data a push (in pipeline mode, only if we're past the size

View File

@ -278,8 +278,25 @@ pqParseInput3(PGconn *conn)
}
break;
case '2': /* Bind Complete */
/* Nothing to do for this message type */
break;
case '3': /* Close Complete */
/* Nothing to do for these message types */
/* If we're doing PQsendClose, we're done; else ignore */
if (conn->cmd_queue_head &&
conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
{
if (!pgHavePendingResult(conn))
{
conn->result = PQmakeEmptyPGresult(conn,
PGRES_COMMAND_OK);
if (!conn->result)
{
libpq_append_conn_error(conn, "out of memory");
pqSaveErrorResult(conn);
}
}
conn->asyncStatus = PGASYNC_READY;
}
break;
case 'S': /* parameter status */
if (getParameterStatus(conn))

View File

@ -548,6 +548,12 @@ extern PGresult *PQdescribePortal(PGconn *conn, const char *portal);
extern int PQsendDescribePrepared(PGconn *conn, const char *stmt);
extern int PQsendDescribePortal(PGconn *conn, const char *portal);
/* Close prepared statements and portals */
extern PGresult *PQclosePrepared(PGconn *conn, const char *stmt);
extern PGresult *PQclosePortal(PGconn *conn, const char *portal);
extern int PQsendClosePrepared(PGconn *conn, const char *stmt);
extern int PQsendClosePortal(PGconn *conn, const char *portal);
/* Delete a PGresult */
extern void PQclear(PGresult *res);

View File

@ -324,7 +324,7 @@ typedef enum
PGQUERY_PREPARE, /* Parse only (PQprepare) */
PGQUERY_DESCRIBE, /* Describe Statement or Portal */
PGQUERY_SYNC, /* Sync (at end of a pipeline) */
PGQUERY_CLOSE
PGQUERY_CLOSE /* Close Statement or Portal */
} PGQueryClass;
/*

View File

@ -947,9 +947,42 @@ test_prepared(PGconn *conn)
if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
fprintf(stderr, "closing statement..");
if (PQsendClosePrepared(conn, "select_one") != 1)
pg_fatal("PQsendClosePrepared failed: %s", PQerrorMessage(conn));
if (PQpipelineSync(conn) != 1)
pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
res = PQgetResult(conn);
if (res == NULL)
pg_fatal("expected non-NULL result");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
PQclear(res);
res = PQgetResult(conn);
if (res != NULL)
pg_fatal("expected NULL result");
res = PQgetResult(conn);
if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
if (PQexitPipelineMode(conn) != 1)
pg_fatal("could not exit pipeline mode: %s", PQerrorMessage(conn));
/* Now that it's closed we should get an error when describing */
res = PQdescribePrepared(conn, "select_one");
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
pg_fatal("expected FATAL_ERROR, got %s", PQresStatus(PQresultStatus(res)));
/*
* Also test the blocking close, this should not fail since closing a
* non-existent prepared statement is a no-op
*/
res = PQclosePrepared(conn, "select_one");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
fprintf(stderr, "creating portal... ");
PQexec(conn, "BEGIN");
PQexec(conn, "DECLARE cursor_one CURSOR FOR SELECT 1");
PQenterPipelineMode(conn);
@ -975,9 +1008,41 @@ test_prepared(PGconn *conn)
if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
fprintf(stderr, "closing portal... ");
if (PQsendClosePortal(conn, "cursor_one") != 1)
pg_fatal("PQsendClosePortal failed: %s", PQerrorMessage(conn));
if (PQpipelineSync(conn) != 1)
pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
res = PQgetResult(conn);
if (res == NULL)
pg_fatal("expected non-NULL result");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
PQclear(res);
res = PQgetResult(conn);
if (res != NULL)
pg_fatal("expected NULL result");
res = PQgetResult(conn);
if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
pg_fatal("expected PGRES_PIPELINE_SYNC, got %s", PQresStatus(PQresultStatus(res)));
if (PQexitPipelineMode(conn) != 1)
pg_fatal("could not exit pipeline mode: %s", PQerrorMessage(conn));
/* Now that it's closed we should get an error when describing */
res = PQdescribePortal(conn, "cursor_one");
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
pg_fatal("expected FATAL_ERROR, got %s", PQresStatus(PQresultStatus(res)));
/*
* Also test the blocking close, this should not fail since closing a
* non-existent portal is a no-op
*/
res = PQclosePortal(conn, "cursor_one");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pg_fatal("expected COMMAND_OK, got %s", PQresStatus(PQresultStatus(res)));
fprintf(stderr, "ok\n");
}

View File

@ -5,6 +5,18 @@ B 4 ParseComplete
B 10 ParameterDescription 1 NNNN
B 113 RowDescription 4 "?column?" NNNN 0 NNNN 4 -1 0 "?column?" NNNN 0 NNNN 65535 -1 0 "numeric" NNNN 0 NNNN 65535 -1 0 "interval" NNNN 0 NNNN 16 -1 0
B 5 ReadyForQuery I
F 16 Close S "select_one"
F 4 Sync
B 4 CloseComplete
B 5 ReadyForQuery I
F 16 Describe S "select_one"
F 4 Sync
B NN ErrorResponse S "ERROR" V "ERROR" C "26000" M "prepared statement "select_one" does not exist" F "SSSS" L "SSSS" R "SSSS" \x00
B 5 ReadyForQuery I
F 16 Close S "select_one"
F 4 Sync
B 4 CloseComplete
B 5 ReadyForQuery I
F 10 Query "BEGIN"
B 10 CommandComplete "BEGIN"
B 5 ReadyForQuery T
@ -15,4 +27,16 @@ F 16 Describe P "cursor_one"
F 4 Sync
B 33 RowDescription 1 "?column?" NNNN 0 NNNN 4 -1 0
B 5 ReadyForQuery T
F 16 Close P "cursor_one"
F 4 Sync
B 4 CloseComplete
B 5 ReadyForQuery T
F 16 Describe P "cursor_one"
F 4 Sync
B NN ErrorResponse S "ERROR" V "ERROR" C "34000" M "portal "cursor_one" does not exist" F "SSSS" L "SSSS" R "SSSS" \x00
B 5 ReadyForQuery E
F 16 Close P "cursor_one"
F 4 Sync
B 4 CloseComplete
B 5 ReadyForQuery E
F 4 Terminate