From b798ec8e78a12c08237340cdaaa269b2ae4d49ac Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Fri, 16 Jun 2023 17:15:48 +0200 Subject: [PATCH v1] Support sending Close messages from libpq This part of the protocol did not have a libpq implementation yet. That is a shame because connection poolers can much more easily intercept these Close messages than a DEALLOCATE query. Odyssey has prepared statement support implemented using the Close message, and PgBouncer is currently trying to do the same. But because Close message cannot be sent from libpq, libpq based clients won't benefit from this completely. --- doc/src/sgml/libpq.sgml | 120 +++++++++++++++++++++++-- src/interfaces/libpq/fe-exec.c | 133 ++++++++++++++++++++++++++++ src/interfaces/libpq/fe-protocol3.c | 19 +++- src/interfaces/libpq/libpq-fe.h | 6 ++ src/interfaces/libpq/libpq-int.h | 2 +- 5 files changed, 273 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 2225e4e0ef3..293af6ae4f3 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -3360,6 +3360,66 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName); + + + PQclosePreparedPQclosePrepared + + + + Submits a request to obtain information about the specified + prepared statement, and waits for completion. + +PGresult *PQclosePrepared(PGconn *conn, const char *stmtName); + + + + + allows an application to close + a previously prepared statement. + + + + stmtName can be "" or NULL to reference + the unnamed statement, it is fine if no statement exists with this name. + On success, a PGresult with + status PGRES_COMMAND_OK is returned. + + + + + + PQclosePortalPQclosePortal + + + + Submits a request to close the the specified + portal, and waits for completion. + +PGresult *PQclosePortal(PGconn *conn, const char *portalName); + + + + + allows an application to release + resources related to a portal previously created portal. If it was a + named portal the name can be reused after the close has completed. + (libpq does not provide any direct access to + portals, but you can use this function to inspect the properties + of a cursor created with a DECLARE CURSOR SQL command.) + + + + portalName can be "" or NULL to reference + the unnamed portal, it is fine if no portal exists with this name. + portal. On success, a PGresult with status + PGRES_COMMAND_OK is returned. The functions + , , + , etc. can be applied to the + PGresult to obtain information about the result + columns (if any) of the portal. + + + @@ -4851,15 +4911,19 @@ unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length); , , , - , and + , , + , and + , which can be used with to duplicate the functionality of , , , - , and + , + , and + respectively. @@ -5008,6 +5072,46 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName); + + PQsendClosePreparedPQsendClosePrepared + + + + Submits a request to close the specified prepared statement, without + waiting for completion. + +int PQsendClosePrepared(PGconn *conn, const char *stmtName); + + + This is an asynchronous version of : + it returns 1 if it was able to dispatch the request, and 0 if not. + After a successful call, call to + obtain the results. The function's parameters are handled + identically to . + + + + + + PQsendClosePortalPQsendClosePortal + + + + Submits a request to close specified portal, without waiting for + completion. + +int PQsendClosePortal(PGconn *conn, const char *portalName); + + + This is an asynchronous version of : + it returns 1 if it was able to dispatch the request, and 0 if not. + After a successful call, call to + obtain the results. The function's parameters are handled + identically to . + + + + PQgetResultPQgetResult @@ -5019,7 +5123,9 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName); , , , - , or + , + , + , or call, and returns it. A null pointer is returned when the command is complete and there @@ -5350,6 +5456,8 @@ int PQflush(PGconn *conn); PQexecPrepared, PQdescribePrepared, PQdescribePortal, + PQclosePrepared, + PQclosePortal, is an error condition. PQsendQuery is also disallowed, because it uses the simple query protocol. @@ -5389,8 +5497,10 @@ int PQflush(PGconn *conn); establish a synchronization point in the pipeline, or when is called. The functions , - , and - also work in pipeline mode. + , + , + , and + also work in pipeline mode. Result processing is described below. diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 14d706efd57..ce70d32185e 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -79,6 +79,8 @@ static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); +static int PQsendClose(PGconn *conn, char desc_type, + const char *desc_target); static int check_field_number(const PGresult *res, int field_num); static void pqPipelineProcessQueue(PGconn *conn); static int pqPipelineFlush(PGconn *conn); @@ -2534,6 +2536,137 @@ sendFailed: return 0; } +/* + * PQclosePrepared + * Obtain information about 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, and its parameter and column-heading fields close + * the statement's inputs and outputs respectively. + * 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 (!PQsendClose(conn, 'S', stmt)) + return NULL; + return PQexecFinish(conn); +} + +/* + * PQclosePortal + * Obtain information about a previously created portal + * + * This is much like PQclosePrepared, except that no parameter info is + * returned. 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 (!PQsendClose(conn, '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 PQsendClose(conn, '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 PQsendClose(conn, 'P', portal); +} + +/* + * PQsendClose + * Common code to send a Close command + * + * Available options for close_type are + * 'S' to close a prepared statement; or + * 'P' to close a portal. + * Returns 1 on success and 0 on failure. + */ +static int +PQsendClose(PGconn *conn, char close_type, const char *close_target) +{ + PGcmdQueueEntry *entry = NULL; + + /* Treat null close_target as empty string */ + if (!close_target) + close_target = ""; + + if (!PQsendQueryStart(conn, true)) + return 0; + + entry = pqAllocCmdQueueEntry(conn); + if (entry == NULL) + return 0; /* error msg already set */ + + /* construct the Close message */ + if (pqPutMsgStart('C', conn) < 0 || + pqPutc(close_type, conn) < 0 || + pqPuts(close_target, conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Sync message */ + if (conn->pipelineStatus == PQ_PIPELINE_OFF) + { + if (pqPutMsgStart('S', conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + } + + /* remember we are doing a Close */ + entry->queryclass = PGQUERY_CLOSE; + + /* + * Give the data a push (in pipeline mode, only if we're past the size + * threshold). In nonblock mode, don't complain if we're unable to send + * it all; PQgetResult() will do any additional flushing needed. + */ + if (pqPipelineFlush(conn) < 0) + goto sendFailed; + + /* OK, it's launched! */ + pqAppendCmdQueueEntry(conn, entry); + + return 1; + +sendFailed: + pqRecycleCmdQueueEntry(conn, entry); + /* error message should be set up already */ + return 0; +} + + /* * PQnotifies * returns a PGnotify* structure of the latest async notification diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 32b66d561cb..7bc6355d17f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -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)) diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 7476dbe0e90..97762d56f5d 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -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); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 0045f83cbfd..02681ae7a16 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -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 Statoment or Portal */ } PGQueryClass; /* base-commit: c0d951262c80f42b3bfe037f940e103a24da84f4 -- 2.34.1