libpq: Add support for Close on portals and statements
authorMichael Paquier <michael@paquier.xyz>
Tue, 4 Jul 2023 05:48:10 +0000 (14:48 +0900)
committerMichael Paquier <michael@paquier.xyz>
Tue, 4 Jul 2023 05:48:10 +0000 (14:48 +0900)
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

doc/src/sgml/libpq.sgml
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h
src/test/modules/libpq_pipeline/libpq_pipeline.c
src/test/modules/libpq_pipeline/traces/prepared.trace

index 2225e4e0ef3f674db379f46227685f712b96a3e1..b6f5aba1b1887d6208df63fdf9099a8c59ec43fd 100644 (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>
 
index 7ded77aff37de5971ab0025ef453eeebb93de21d..850734ac96c5ca79a98a247b6f1403d9e6c9d67c 100644 (file)
@@ -187,3 +187,7 @@ PQsetTraceFlags           184
 PQmblenBounded            185
 PQsendFlushRequest        186
 PQconnectionUsedGSSAPI    187
+PQclosePrepared           188
+PQclosePortal             189
+PQsendClosePrepared       190
+PQsendClosePortal         191
index 14d706efd57b54bc099b22df982ccfb64b5f6aae..01f8efabbeffb7800db5e7420b10f37b0fd4020f 100644 (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
index 32b66d561cb3fae6fb3cfee18a5d51e5803c1dc6..7bc6355d17f912ae15d4e23c2b466a336b9c2821 100644 (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))
index 7476dbe0e90f51536cb6f640d311e24c4a54a1b4..97762d56f5de1deabd0096c851aa2b9fabcb1889 100644 (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);
 
index b70d4aee6a179349ff6eac2d1804c0626c53879b..9d63472c35b2d7847b63a8731f3bfe08c5292e2c 100644 (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;
 
 /*
index 1933b078ebfe7a0b13f345f684816ac07752a05b..9907bc860048a5c6370b213c271e77570933d6ba 100644 (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");
 }
 
index 1a7de5c3e65e35da3f711e0eeea961cb0b77c5cd..aeb5de109e02124b5ab94741ed933a057a0ec2fd 100644 (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