Hi,

[Sending this message (first) to -hackers for discussion about the
extension and followed implementation.]

On Apr 01 09:39, Volkan YAZICI wrote:
> I've prepared a patch for the Describe <-> ParameterDescription
> messaging which is available via current extended query protocol.

Here's a written from scratch patch for the above mentioned extension.
It adds PQdescribePrepared() and PQdescribePortal() functions to the
libpq. New functions work as follows:

1. Issue a PQdescribePrepared() call.
2. First PQgetResult() will return a PGresult with input parameter
   types of the prepared statement. (You can use PQftype() on this
   PGresult to extract information.)
3. Second PQgetResult() will return another PGresult which holds the
   column information for the will be returned tuples. (All PQf*()
   functions can be used on this result.)

(A PQdescribePortal() call will just skip the 2nd step in the above
list.)

Patch passes regression tests and there're two examples attached for
PQdescribePrepared() and PQdescribePortal() usage.

To mention about the followed implementation, it needed some hack on
pqParseInput3() code to make it understand if a received message is
a reponse to a Describe ('D') query or to another tuple returning
query. To summarize problem, there're two possible forms of a 'D'
response:

 1. Description of a prepared statement: t, T, Z
 2. Description of a portal: T, Z

The problem is, AFAICS, it's not possible to distinguish between a tuple
returning query (T, ..., C, Z or T, E) and a description of a portal (T,
Z). Therefore, I've created a global flag (parsing_row_desc) which is
turned on when we receive a 'T' and turned off if we receive a 'C' or
'E'. It's a kind of ugly method but the only solution I could come up
with.


Regards.
Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.483
diff -c -r1.483 postgres.c
*** src/backend/tcop/postgres.c 4 Apr 2006 19:35:35 -0000       1.483
--- src/backend/tcop/postgres.c 15 Apr 2006 07:39:49 -0000
***************
*** 1870,1875 ****
--- 1870,1876 ----
  static void
  exec_describe_statement_message(const char *stmt_name)
  {
+       MemoryContext   oldContext;
        PreparedStatement *pstmt;
        TupleDesc       tupdesc;
        ListCell   *l;
***************
*** 1882,1888 ****
        start_xact_command();
  
        /* Switch back to message context */
!       MemoryContextSwitchTo(MessageContext);
  
        /* Find prepared statement */
        if (stmt_name[0] != '\0')
--- 1883,1889 ----
        start_xact_command();
  
        /* Switch back to message context */
!       oldContext = MemoryContextSwitchTo(MessageContext);
  
        /* Find prepared statement */
        if (stmt_name[0] != '\0')
***************
*** 1940,1946 ****
--- 1941,1950 ----
                                                                  NULL);
        else
                pq_putemptymessage('n');        /* NoData */
+       
+       MemoryContextSwitchTo(oldContext);
  
+       finish_xact_command();
  }
  
  /*
***************
*** 1951,1956 ****
--- 1955,1961 ----
  static void
  exec_describe_portal_message(const char *portal_name)
  {
+       MemoryContext   oldContext;
        Portal          portal;
  
        /*
***************
*** 1960,1966 ****
        start_xact_command();
  
        /* Switch back to message context */
!       MemoryContextSwitchTo(MessageContext);
  
        portal = GetPortalByName(portal_name);
        if (!PortalIsValid(portal))
--- 1965,1971 ----
        start_xact_command();
  
        /* Switch back to message context */
!       oldContext = MemoryContextSwitchTo(MessageContext);
  
        portal = GetPortalByName(portal_name);
        if (!PortalIsValid(portal))
***************
*** 1992,1997 ****
--- 1997,2006 ----
                                                                  
portal->formats);
        else
                pq_putemptymessage('n');        /* NoData */
+       
+       MemoryContextSwitchTo(oldContext);
+ 
+       finish_xact_command();
  }
  
  
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.182
diff -c -r1.182 fe-exec.c
*** src/interfaces/libpq/fe-exec.c      14 Mar 2006 22:48:23 -0000      1.182
--- src/interfaces/libpq/fe-exec.c      15 Apr 2006 07:39:58 -0000
***************
*** 55,60 ****
--- 55,62 ----
  static void parseInput(PGconn *conn);
  static bool PQexecStart(PGconn *conn);
  static PGresult *PQexecFinish(PGconn *conn);
+ static int pqDescribe(PGconn *conn, const char desc_type,
+                                         const char *desc_target);
  
  
  /* ----------------
***************
*** 2281,2286 ****
--- 2283,2392 ----
                return 0;
  }
  
+ 
+ /*
+  * pqDescribe - Describe given prepared statement or portal.
+  *
+  * Available options for target_type are
+  *   'S' to describe a prepared statement; or
+  *   'P' to describe a portal.
+  * Returns 0 on success and 1 on failure.
+  *
+  * By issuing a PQgetResult(), response from the server will be placed
+  * in an empty PGresult which will be extractable via PQf*() function family.
+  */
+ static int
+ pqDescribe(PGconn *conn, const char desc_type, const char *desc_target)
+ {
+       int     ret;
+ 
+       /* This isn't gonna work on a 2.0 server. */
+       if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("function 
requires at least protocol version 3.0\n"));
+               return 1;
+       }
+       
+       if (!conn)
+               return 1;
+       
+       /* Clear the connection error message. */
+       resetPQExpBuffer(&conn->errorMessage);
+ 
+       /* Don't try to send if we know there's no live connection. */
+       if (conn->status != CONNECTION_OK)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("no connection 
to the server\n"));
+               return 1;
+       }
+       
+       /* Can't send while already busy, either. */
+       if (conn->asyncStatus != PGASYNC_IDLE)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("another 
command is already in progress\n"));
+               return 1;
+       }
+ 
+       /* Initialize async result-accumulation state. */
+       conn->result = NULL;
+       conn->curTuple = NULL;
+       
+       if (desc_target)
+               ret = (pqPutMsgStart('D', false, conn) < 0 ||
+                          pqPutc(desc_type, conn) < 0 ||
+                          pqPuts(desc_target, conn) < 0 ||
+                          pqPutMsgEnd(conn) < 0);
+       
+       /* NULL desc_target values will be treated as an empty string. */
+       else
+               ret = (pqPutMsgStart('D', false, conn) < 0 ||
+                          pqPutc(desc_type, conn) < 0 ||
+                          pqPutc('\0', conn) < 0 ||
+                          pqPutMsgEnd(conn) < 0);
+       
+       if (ret)
+               goto SendFailure;
+       
+       /* Extended protocol requires a Sync message. */
+       if (pqPutMsgStart('S', false, conn) < 0 ||
+               pqPutMsgEnd(conn) < 0)
+               goto SendFailure;
+ 
+       /* Remember we are using extended query protocol. */
+       conn->queryclass = PGQUERY_EXTENDED;
+ 
+       /* Free last query string. */
+       if (conn->last_query)
+       {
+               free(conn->last_query);
+               conn->last_query = NULL;
+       }
+ 
+       /* Describe request is sent. */
+       conn->asyncStatus = PGASYNC_BUSY;
+       return 0;
+ 
+ SendFailure:
+       pqHandleSendFailure(conn);
+       return 1;
+ }
+ 
+ int
+ PQdescribePrepared(PGconn *conn, const char *stmt)
+ {
+       return pqDescribe(conn, 'S', stmt);
+ }
+ 
+ int
+ PQdescribePortal(PGconn *conn, const char *portal)
+ {
+       return pqDescribe(conn, 'P', portal);
+ }
+ 
+ 
  /* PQsetnonblocking:
   *    sets the PGconn's database connection non-blocking if the arg is TRUE
   *    or makes it non-blocking if the arg is FALSE, this will not protect
***************
*** 2346,2351 ****
--- 2452,2458 ----
        free(ptr);
  }
  
+ 
  /*
   * PQfreeNotify - free's the memory associated with a PGnotify
   *
Index: src/interfaces/libpq/fe-protocol3.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-protocol3.c,v
retrieving revision 1.26
diff -c -r1.26 fe-protocol3.c
*** src/interfaces/libpq/fe-protocol3.c 14 Mar 2006 22:48:23 -0000      1.26
--- src/interfaces/libpq/fe-protocol3.c 15 Apr 2006 07:40:05 -0000
***************
*** 42,50 ****
--- 42,56 ----
        ((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
         (id) == 'E' || (id) == 'N' || (id) == 'A')
  
+ /*
+  * Flag to distinguish between an odd RowDescription and start of
+  * a tuple returning query.
+  */
+ static bool parsing_row_desc = false;
  
  static void handleSyncLoss(PGconn *conn, char id, int msgLength);
  static int    getRowDescriptions(PGconn *conn);
+ static int    getParamDescriptions(PGconn *conn);
  static int    getAnotherTuple(PGconn *conn, int msgLength);
  static int    getParameterStatus(PGconn *conn);
  static int    getNotify(PGconn *conn);
***************
*** 206,221 ****
--- 212,247 ----
                                        }
                                        strncpy(conn->result->cmdStatus, 
conn->workBuffer.data,
                                                        CMDSTATUS_LEN);
+ 
+                                       /* This cannot be a Describe ('D') 
response no more. */
+                                       parsing_row_desc = false;
+                                       
                                        conn->asyncStatus = PGASYNC_READY;
                                        break;
                                case 'E':               /* error return */
                                        if (pqGetErrorNotice3(conn, true))
                                                return;
                                        conn->asyncStatus = PGASYNC_READY;
+ 
+                                       /* No further RowDescription parsing is 
possible. */
+                                       parsing_row_desc = false;
                                        break;
                                case 'Z':               /* backend is ready for 
new query */
                                        if (getReadyForQuery(conn))
                                                return;
+                                       
+                                       if (parsing_row_desc)
+                                       {
+                                               /*
+                                                * This is probably received 
from a Describe ('D')
+                                                * query's response.
+                                                */
+                                               parsing_row_desc = false;
+                                               conn->asyncStatus = 
PGASYNC_READY;
+                                               return;
+                                       }
+                                       
+                                       /* Backend is ready for a new query. */
                                        conn->asyncStatus = PGASYNC_IDLE;
                                        break;
                                case 'I':               /* empty query */
***************
*** 268,273 ****
--- 294,302 ----
                                                /* First 'T' in a query 
sequence */
                                                if (getRowDescriptions(conn))
                                                        return;
+                                               
+                                               /* Turning "RowDesc is parsed" 
flag to on. */
+                                               parsing_row_desc = true;
                                        }
                                        else
                                        {
***************
*** 294,299 ****
--- 323,348 ----
                                                conn->result = 
PQmakeEmptyPGresult(conn,
                                                                                
                                   PGRES_COMMAND_OK);
                                        break;
+                               case 't':               /* Parameter 
Description */
+                                       if (!conn->result)
+                                       {
+                                               /* A new PGresult will be 
created to load parsed data into. */
+                                               if (getParamDescriptions(conn))
+                                                       return;
+                                               
+                                               /* Result is ready. */
+                                               conn->asyncStatus = 
PGASYNC_READY;
+                                       }
+                                       else
+                                       {
+                                               /*
+                                                * We'll wait until the 
application accepts the current
+                                                * existing result.
+                                                */
+                                               conn->asyncStatus = 
PGASYNC_READY;
+                                               return;
+                                       }
+                                       break;
                                case 'D':               /* Data Row */
                                        if (conn->result != NULL &&
                                                conn->result->resultStatus == 
PGRES_TUPLES_OK)
***************
*** 500,505 ****
--- 549,620 ----
  }
  
  /*
+  * parseInput subroutine to read a 't' (ParameterDescription) message.
+  * Returns 0 if parsing is completed, 1 if there isn't enough data yet
+  * and EOF if an error occurs.
+  *
+  * Parsed data will get loaded into the existing conn->result.
+  */
+ static int
+ getParamDescriptions(PGconn *conn)
+ {
+       PGresult        *res;
+       int                      nattr;
+       int                      i;
+       Oid                      typid;
+       
+       res = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK);
+       if (!res)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("out of 
memory\n"));
+               EOF;
+       }
+ 
+       /* First extracting number of attributes from the message. */
+       if (pqGetInt(&nattr, 2, conn) < 0)
+               goto NotEnoughData;
+       res->numAttributes = nattr;
+ 
+       /* Allocate space for the attribute descriptors. */             
+       if (nattr > 0)
+       {
+               int sz = nattr * sizeof(PGresAttDesc);
+               
+               res->attDescs = (PGresAttDesc *) pqResultAlloc(res, sz, TRUE);
+               if (!res->attDescs)
+               {
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("out of 
memory\n"));
+                       goto Error;
+               }
+               
+               /* Reset attribute values. */
+               MemSet(res->attDescs, 0, sz);
+       }
+ 
+       /* Loop through attribute's type OIDs. */
+       for (i = 0; i < nattr; i++)
+       {
+               if (pqGetInt(&typid, 4, conn) < 0)
+                       goto NotEnoughData;
+               res->attDescs[i].typid = typid;
+       }
+ 
+       /* Success. */
+       conn->result = res;
+       return 0;
+ 
+ NotEnoughData:
+       PQclear(res);
+       return 1;
+ 
+ Error:
+       PQclear(res);
+       return EOF;
+ }
+ 
+ /*
   * parseInput subroutine to read a 'D' (row data) message.
   * We add another tuple to the existing PGresult structure.
   * Returns: 0 if completed message, EOF if error or not enough data yet.
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.126
diff -c -r1.126 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h     20 Mar 2006 15:07:05 -0000      1.126
--- src/interfaces/libpq/libpq-fe.h     15 Apr 2006 07:40:08 -0000
***************
*** 414,419 ****
--- 414,423 ----
  extern int    PQgetlength(const PGresult *res, int tup_num, int field_num);
  extern int    PQgetisnull(const PGresult *res, int tup_num, int field_num);
  
+ /* Describe prepared statements and portals. */
+ extern int    PQdescribePrepared(PGconn *conn, const char *stmt);
+ extern int    PQdescribePortal(PGconn *conn, const char *portal);
+ 
  /* Delete a PGresult */
  extern void PQclear(PGresult *res);
  
#include <stdio.h>

#include "libpq-fe.h"

int
main(void)
{
        PGconn          *conn = PQconnectdb("dbname=template1");
        PGresult        *res;
        int                      ret;

        /*
         * Make an erronous Describe ('D') request.
         */
        printf("=== PQdescribePrepared>\n");
        ret = PQdescribePrepared(conn, "not_exists");
        if (ret)
        {
                printf("PQdescribePrepared() failed! [%s]\n", 
PQerrorMessage(conn));
                
                PQfinish(conn);
                return 1;
        }

        /*
         * Receive erronous describe response.
         */
        printf("=== PQgetResult>\n");
        res = PQgetResult(conn);
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                printf("PQgetResult() failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));

                printf("(***Expected Error.***)\n");
        }
        PQclear(res);

        /*
         * Flush result set.
         */
        printf("=== PQgetResult> (Flush result set)\n");
        res = PQgetResult(conn);
        if (res)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }

        /*
         * PREPARE a statement to describe.
         */
        printf("=== PREPARE>\n");
        res = PQexec(conn,
                                 "PREPARE my_plan (int4, text, float) AS"
                                 "  SELECT "
                                 "    (random()*$1)::float8 AS c1, "
                                 "    $2 AS c2, "
                                 "    (random()*$3)::int8 AS c3, "
                                 "    'some text' AS c4;");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        PQclear(res);
        
        /*
         * Make a Describe ('D') request.
         */
        printf("=== PQdescribePrepared>\n");
        ret = PQdescribePrepared(conn, "my_plan");
        if (ret)
        {
                printf("PQdescribePrepared() failed! [%s]\n", 
PQerrorMessage(conn));
                
                PQfinish(conn);
                return 1;
        }

        /*
         * Receive Describe response (ParameterDescription).
         */
        printf("=== PQgetResult>\n");
        res = PQgetResult(conn);
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                printf("PQgetResult() failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        else
        {
                int i, n = PQnfields(res);

                printf("PQftype\n");
                for (i = 0; i < n; i++)
                        printf(" %d:%d\n", i, PQftype(res, i));
        }
        PQclear(res);

        printf("=== PQgetResult>\n");
        res = PQgetResult(conn);
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
                printf("PQgetResult() failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        else
        {
                int i, n = PQnfields(res);

                printf("PQfname\n");
                for (i = 0; i < n; i++)
                        printf(" %d:%s\n", i, PQfname(res, i));
        }
        PQclear(res);

        /*
         * Flush result set.
         */
        printf("=== PQgetResult> (Flush result set)\n");
        res = PQgetResult(conn);
        if (res)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        PQclear(res);

        PQfinish(conn);
        return 0;
}
#include <stdio.h>

#include "libpq-fe.h"

int
main(void)
{
        PGconn          *conn = PQconnectdb("dbname=template1");
        PGresult        *res;
        int                      ret;

        /*
         * Make an erronous Describe ('D') request.
         */
        printf("=== PQdescribePrepared>\n");
        ret = PQdescribePrepared(conn, "not_exists");
        if (ret)
        {
                printf("PQdescribePrepared() failed! [%s]\n", 
PQerrorMessage(conn));
                
                PQfinish(conn);
                return 1;
        }

        /*
         * Receive erronous describe response.
         */
        printf("=== PQgetResult>\n");
        res = PQgetResult(conn);
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                printf("PQgetResult() failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));

                printf("(***Expected Error.***)\n");
        }
        PQclear(res);

        /*
         * Flush result set.
         */
        printf("=== PQgetResult> (Flush result set)\n");
        res = PQgetResult(conn);
        if (res)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }

        /*
         * DECLARE a statement to describe.
         */
        printf("=== DECLARE>\n");
        res = PQexec(conn,
                                 "BEGIN; DECLARE curs CURSOR FOR"
                                 "  SELECT "
                                 "    (random()*100)::float8 AS c1, "
                                 "    43 AS c2, "
                                 "    (random()*1000)::int8 AS c3, "
                                 "    'some text' AS c4;");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        PQclear(res);
        
        /*
         * Make a Describe ('D') request.
         */
        printf("=== PQdescribePortal>\n");
        ret = PQdescribePortal(conn, "curs");
        if (ret)
        {
                printf("PQdescribePortal() failed! [%s]\n", 
PQerrorMessage(conn));
                
                PQfinish(conn);
                return 1;
        }

        /*
         * Receive Describe response (ParameterDescription).
         */
        printf("=== PQgetResult>\n");
        res = PQgetResult(conn);
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
                printf("PQgetResult() failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        else
        {
                int i, n = PQnfields(res);

                printf("PQfname\n");
                for (i = 0; i < n; i++)
                        printf(" %d:%s\n", i, PQfname(res, i));
        }
        PQclear(res);

        /*
         * Flush result set.
         */
        printf("=== PQgetResult> (Flush result set)\n");
        res = PQgetResult(conn);
        if (res)
        {
                printf("Failed! %s [%s || Conn: %s]\n",
                           PQresStatus(PQresultStatus(res)),
                           PQresultErrorMessage(res),
                           PQerrorMessage(conn));
                
                PQclear(res);
                PQfinish(conn);
                return 1;
        }
        PQclear(res);

        PQfinish(conn);
        return 0;
}
---------------------------(end of broadcast)---------------------------
TIP 2: Don't 'kill -9' the postmaster

Reply via email to