Extended query protocol: parse, bind, execute, describe FE/BE messages.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 5 May 2003 00:44:56 +0000 (00:44 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 5 May 2003 00:44:56 +0000 (00:44 +0000)
Only lightly tested as yet, since libpq doesn't know anything about 'em.

17 files changed:
doc/src/sgml/protocol.sgml
src/backend/access/common/printtup.c
src/backend/commands/portalcmds.c
src/backend/commands/prepare.c
src/backend/parser/analyze.c
src/backend/tcop/dest.c
src/backend/tcop/fastpath.c
src/backend/tcop/postgres.c
src/backend/utils/mmgr/portalmem.c
src/include/access/printtup.h
src/include/commands/portalcmds.h
src/include/commands/prepare.h
src/include/libpq/pqcomm.h
src/include/tcop/dest.h
src/include/tcop/tcopprot.h
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/libpq-int.h

index 4a01e310b30af6d508571ffc5c1b0c596bda8655..48ba74edb5442e8ff0a57ad75faaa89dbfade4da 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.33 2003/04/28 05:17:31 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.34 2003/05/05 00:44:55 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
    <para>
     If successfully created, a named prepared-statement object lasts till
     the end of the current session, unless explicitly destroyed.  An unnamed
-    prepared statement lasts only until the next Parse message is issued.
+    prepared statement lasts only until the next Parse statement specifying
+    the unnamed statement as destination is issued.  (Note that a simple
+    Query message also destroys the unnamed statement.)  Named prepared
+    statements must be explicitly closed before they can be redefined by
+    a Parse message, but this is not required for the unnamed statement.
     Named prepared statements can also be created and accessed at the SQL
     command level, using <command>PREPARE</> and <command>EXECUTE</>.
    </para>
    </para>
 
    <para>
-    If successfully created, a named portal object lasts till
-    the end of the current transaction, unless explicitly destroyed.  An
-    unnamed portal is destroyed at the end of the transaction, or as soon
-    as the next Parse or Bind message is executed.
+    If successfully created, a named portal object lasts till the end of the
+    current transaction, unless explicitly destroyed.  An unnamed portal is
+    destroyed at the end of the transaction, or as soon as the next Bind
+    statement specifying the unnamed portal as destination is issued.  (Note
+    that a simple Query message also destroys the unnamed portal.)  Named
+    portals must be explicitly closed before they can be redefined by a Bind
+    message, but this is not required for the unnamed portal.
     Named portals can also be created and accessed at the SQL
     command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
    </para>
     The Describe message (statement variant) specifies the name of an existing
     prepared statement (or an empty string for the unnamed prepared
     statement).  The response is a ParameterDescription message describing the
-    parameters needed by the statement (if any), followed by a RowDescription
-    message describing the rows that will be returned when the statement is
-    eventually executed (or NoData if there is no SELECT-type query in the
-    prepared statement).  ErrorResponse is issued if there is no such prepared
-    statement.  This message may be useful if the client library is
-    uncertain about the parameters needed by a prepared statement.
+    parameters needed by the statement.  ErrorResponse is issued if there is
+    no such prepared statement.  This message may be useful if the client
+    library is uncertain about the parameters needed by a prepared statement.
    </para>
 
    <para>
     The Close message closes an existing prepared statement or portal
-    and releases resources.
+    and releases resources.  It is not an error to issue Close against
+    a nonexistent statement or portal name.  The response is normally
+    CloseComplete, but could be ErrorResponse if some difficulty is
+    encountered while releasing resources.  Note that closing a prepared
+    statement implicitly closes any open portals that were constructed
+    from that statement.
    </para>
 
    <para>
     but forces the backend to deliver any data pending in its output
     buffers.  A Flush must be sent after any extended-query command except
     Sync, if the frontend wishes to examine the results of that command before
-    issuing more commands.  Without Flush, returning data will be combined
-    into the minimum possible number of packets to minimize network overhead.
+    issuing more commands.  Without Flush, messages returned by the backend
+    will be combined into the minimum possible number of packets to minimize
+    network overhead.
    </para>
 
    <note>
     <para>
      The simple Query message is approximately equivalent to the series Parse,
-     Bind, portal Describe, Execute, Sync, using the unnamed prepared statement
-     and portal objects and no parameters.  One difference is that it
-     will accept multiple SQL statements in the query string, automatically
+     Bind, portal Describe, Execute, Close, Sync, using the unnamed prepared
+     statement and portal objects and no parameters.  One difference is that
+     it will accept multiple SQL statements in the query string, automatically
      performing the bind/describe/execute sequence for each one in succession.
-     Another is that it will not return ParseComplete, BindComplete, or
-     NoData messages.
+     Another difference is that it will not return ParseComplete, BindComplete,
+     CloseComplete, or NoData messages.
     </para>
    </note>
   </sect2>
@@ -1917,6 +1927,41 @@ Close (F)
 </VarListEntry>
 
 
+<VarListEntry>
+<Term>
+CloseComplete (B)
+</Term>
+<ListItem>
+<Para>
+
+<VariableList>
+<VarListEntry>
+<Term>
+        Byte1('3')
+</Term>
+<ListItem>
+<Para>
+                Identifies the message as a Close-complete indicator.
+</Para>
+</ListItem>
+</VarListEntry>
+<VarListEntry>
+<Term>
+        Int32(4)
+</Term>
+<ListItem>
+<Para>
+                Length of message contents in bytes, including self.
+</Para>
+</ListItem>
+</VarListEntry>
+</VariableList>
+
+</Para>
+</ListItem>
+</VarListEntry>
+
+
 <VarListEntry>
 <Term>
 CommandComplete (B)
@@ -3875,6 +3920,15 @@ The ReadyForQuery ('<literal>Z</>') message includes a transaction status
 indicator.
 </para>
 
+<para>
+There is a new <quote>extended query</> sub-protocol, which adds the frontend
+message types Parse, Bind, Execute, Describe, Close, Flush, and Sync, and the
+backend message types ParseComplete, BindComplete, PortalSuspended,
+ParameterDescription, NoData, and CloseComplete.  Existing clients do not
+have to concern themselves with this sub-protocol, but making use of it
+may allow improvements in performance or functionality.
+</para>
+
 <para>
 COPY data is now encapsulated into CopyData and CopyDone messages.  There
 is a well-defined way to recover from errors during COPY.  The special
index 160b703223f5d973f7d9eb6bfc6f93a44cac869b..584233c1873f569788fa521708b85decdc04c6e1 100644 (file)
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.67 2003/04/26 20:22:58 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.68 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,6 +48,7 @@ typedef struct
 typedef struct
 {
        DestReceiver pub;                       /* publicly-known function pointers */
+       bool            sendDescrip;    /* send RowDescription at startup? */
        TupleDesc       attrinfo;               /* The attr info we are set up for */
        int                     nattrs;
        PrinttupAttrInfo *myinfo;       /* Cached info about each attr */
@@ -58,7 +59,7 @@ typedef struct
  * ----------------
  */
 DestReceiver *
-printtup_create_DR(bool isBinary)
+printtup_create_DR(bool isBinary, bool sendDescrip)
 {
        DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
 
@@ -66,6 +67,8 @@ printtup_create_DR(bool isBinary)
        self->pub.setup = printtup_setup;
        self->pub.cleanup = printtup_cleanup;
 
+       self->sendDescrip = sendDescrip;
+
        self->attrinfo = NULL;
        self->nattrs = 0;
        self->myinfo = NULL;
@@ -77,6 +80,8 @@ static void
 printtup_setup(DestReceiver *self, int operation,
                           const char *portalName, TupleDesc typeinfo)
 {
+       DR_printtup *myState = (DR_printtup *) self;
+
        if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
        {
                /*
@@ -91,41 +96,11 @@ printtup_setup(DestReceiver *self, int operation,
        }
 
        /*
-        * if this is a retrieve, then we send back the tuple descriptor of
-        * the tuples.
+        * If this is a retrieve, and we are supposed to emit row descriptions,
+        * then we send back the tuple descriptor of the tuples.  
         */
-       if (operation == CMD_SELECT)
-       {
-               Form_pg_attribute *attrs = typeinfo->attrs;
-               int                     natts = typeinfo->natts;
-               int                     proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
-               int                     i;
-               StringInfoData buf;
-
-               pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
-               pq_sendint(&buf, natts, 2);             /* # of attrs in tuples */
-
-               for (i = 0; i < natts; ++i)
-               {
-                       pq_sendstring(&buf, NameStr(attrs[i]->attname));
-                       /* column ID info appears in protocol 3.0 and up */
-                       if (proto >= 3)
-                       {
-                               /* XXX not yet implemented, send zeroes */
-                               pq_sendint(&buf, 0, 4);
-                               pq_sendint(&buf, 0, 2);
-                       }
-                       pq_sendint(&buf, (int) attrs[i]->atttypid,
-                                          sizeof(attrs[i]->atttypid));
-                       pq_sendint(&buf, attrs[i]->attlen,
-                                          sizeof(attrs[i]->attlen));
-                       /* typmod appears in protocol 2.0 and up */
-                       if (proto >= 2)
-                               pq_sendint(&buf, attrs[i]->atttypmod,
-                                                  sizeof(attrs[i]->atttypmod));
-               }
-               pq_endmessage(&buf);
-       }
+       if (operation == CMD_SELECT && myState->sendDescrip)
+               SendRowDescriptionMessage(typeinfo);
 
        /* ----------------
         * We could set up the derived attr info at this time, but we postpone it
@@ -139,6 +114,43 @@ printtup_setup(DestReceiver *self, int operation,
         */
 }
 
+/*
+ * SendRowDescriptionMessage --- send a RowDescription message to the frontend
+ */
+void
+SendRowDescriptionMessage(TupleDesc typeinfo)
+{
+       Form_pg_attribute *attrs = typeinfo->attrs;
+       int                     natts = typeinfo->natts;
+       int                     proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+       int                     i;
+       StringInfoData buf;
+
+       pq_beginmessage(&buf, 'T');             /* tuple descriptor message type */
+       pq_sendint(&buf, natts, 2);             /* # of attrs in tuples */
+
+       for (i = 0; i < natts; ++i)
+       {
+               pq_sendstring(&buf, NameStr(attrs[i]->attname));
+               /* column ID info appears in protocol 3.0 and up */
+               if (proto >= 3)
+               {
+                       /* XXX not yet implemented, send zeroes */
+                       pq_sendint(&buf, 0, 4);
+                       pq_sendint(&buf, 0, 2);
+               }
+               pq_sendint(&buf, (int) attrs[i]->atttypid,
+                                  sizeof(attrs[i]->atttypid));
+               pq_sendint(&buf, attrs[i]->attlen,
+                                  sizeof(attrs[i]->attlen));
+               /* typmod appears in protocol 2.0 and up */
+               if (proto >= 2)
+                       pq_sendint(&buf, attrs[i]->atttypmod,
+                                          sizeof(attrs[i]->atttypmod));
+       }
+       pq_endmessage(&buf);
+}
+
 static void
 printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 {
index 35ed8a270b5a475f090a418824c7c8aba800408d..82058ff5d10d01c699bfc986a69cf3efd835c8c5 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.14 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,7 +49,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
         * Disallow empty-string cursor name (conflicts with protocol-level
         * unnamed portal).
         */
-       if (strlen(stmt->portalname) == 0)
+       if (!stmt->portalname || stmt->portalname[0] == '\0')
                elog(ERROR, "Invalid cursor name: must not be empty");
 
        /*
@@ -148,6 +148,13 @@ PerformPortalFetch(FetchStmt *stmt,
        Portal          portal;
        long            nprocessed;
 
+       /*
+        * Disallow empty-string cursor name (conflicts with protocol-level
+        * unnamed portal).
+        */
+       if (!stmt->portalname || stmt->portalname[0] == '\0')
+               elog(ERROR, "Invalid cursor name: must not be empty");
+
        /* get the portal from the portal name */
        portal = GetPortalByName(stmt->portalname);
        if (!PortalIsValid(portal))
@@ -164,7 +171,9 @@ PerformPortalFetch(FetchStmt *stmt,
         * Adjust dest if needed.  MOVE wants dest = None.
         *
         * If fetching from a binary cursor and the requested destination is
-        * Remote, change it to RemoteInternal.
+        * Remote, change it to RemoteInternal.  Note we do NOT change if the
+        * destination is RemoteExecute --- so the Execute message's format
+        * specification wins out over the cursor's type.
         */
        if (stmt->ismove)
                dest = None;
@@ -189,10 +198,17 @@ PerformPortalFetch(FetchStmt *stmt,
  *             Close a cursor.
  */
 void
-PerformPortalClose(char *name)
+PerformPortalClose(const char *name)
 {
        Portal          portal;
 
+       /*
+        * Disallow empty-string cursor name (conflicts with protocol-level
+        * unnamed portal).
+        */
+       if (!name || name[0] == '\0')
+               elog(ERROR, "Invalid cursor name: must not be empty");
+
        /*
         * get the portal from the portal name
         */
index 5a3e3f589d19ffc971cd239e75f9e7534ab5a16e..3f8beac53c180adb85fd7eb07da24305d0afe9c9 100644 (file)
@@ -3,10 +3,14 @@
  * prepare.c
  *       Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
  *
- * Copyright (c) 2002, PostgreSQL Global Development Group
+ * This module also implements storage of prepared statements that are
+ * accessed via the extended FE/BE query protocol.
+ *
+ *
+ * Copyright (c) 2002-2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.15 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/memutils.h"
 
 
-#define HASH_KEY_LEN NAMEDATALEN
-
-/* All the data we need to remember about a stored query */
-typedef struct
-{
-       /* dynahash.c requires key to be first field */
-       char            key[HASH_KEY_LEN];
-       List       *query_list;         /* list of queries */
-       List       *plan_list;          /* list of plans */
-       List       *argtype_list;       /* list of parameter type OIDs */
-       MemoryContext context;          /* context containing this query */
-} QueryHashEntry;
-
 /*
  * The hash table in which prepared queries are stored. This is
  * per-backend: query plans are not shared between backends.
- * The keys for this hash table are the arguments to PREPARE
- * and EXECUTE ("plan names"); the entries are QueryHashEntry structs.
+ * The keys for this hash table are the arguments to PREPARE and EXECUTE
+ * (statement names); the entries are PreparedStatement structs.
  */
 static HTAB *prepared_queries = NULL;
 
 static void InitQueryHashTable(void);
-static void StoreQuery(const char *stmt_name, List *query_list,
-                                          List *plan_list, List *argtype_list);
-static QueryHashEntry *FetchQuery(const char *plan_name);
 static ParamListInfo EvaluateParams(EState *estate,
                                                                        List *params, List *argtypes);
 
@@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate,
 void
 PrepareQuery(PrepareStmt *stmt)
 {
+       const char *commandTag;
        List       *query_list,
                           *plan_list;
 
-       if (!stmt->name)
-               elog(ERROR, "No statement name given");
+       /*
+        * Disallow empty-string statement name (conflicts with protocol-level
+        * unnamed statement).
+        */
+       if (!stmt->name || stmt->name[0] == '\0')
+               elog(ERROR, "Invalid statement name: must not be empty");
 
-       if (stmt->query->commandType == CMD_UTILITY)
-               elog(ERROR, "Utility statements cannot be prepared");
+       switch (stmt->query->commandType)
+       {
+               case CMD_SELECT:
+                       commandTag = "SELECT";
+                       break;
+               case CMD_INSERT:
+                       commandTag = "INSERT";
+                       break;
+               case CMD_UPDATE:
+                       commandTag = "UPDATE";
+                       break;
+               case CMD_DELETE:
+                       commandTag = "DELETE";
+                       break;
+               default:
+                       elog(ERROR, "Utility statements cannot be prepared");
+                       commandTag = NULL;      /* keep compiler quiet */
+                       break;
+       }
 
        /*
         * Parse analysis is already done, but we must still rewrite and plan
@@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt)
        plan_list = pg_plan_queries(query_list, false);
 
        /* Save the results. */
-       StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
+       StorePreparedStatement(stmt->name,
+                                                  NULL, /* text form not available */
+                                                  commandTag,
+                                                  query_list,
+                                                  plan_list,
+                                                  stmt->argtype_oids);
 }
 
 /*
@@ -89,7 +104,8 @@ PrepareQuery(PrepareStmt *stmt)
 void
 ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 {
-       QueryHashEntry *entry;
+       PreparedStatement *entry;
+       char       *query_string;
        List       *query_list,
                           *plan_list;
        MemoryContext qcontext;
@@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
        Portal          portal;
 
        /* Look it up in the hash table */
-       entry = FetchQuery(stmt->name);
+       entry = FetchPreparedStatement(stmt->name, true);
 
+       query_string = entry->query_string;
        query_list = entry->query_list;
        plan_list = entry->plan_list;
        qcontext = entry->context;
@@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 
                oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
+               if (query_string)
+                       query_string = pstrdup(query_string);
                query_list = copyObject(query_list);
                plan_list = copyObject(plan_list);
                qcontext = PortalGetHeapMemory(portal);
@@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
        }
 
        PortalDefineQuery(portal,
-                                         NULL,         /* XXX fixme: can we save query text? */
-                                         NULL,         /* no command tag known either */
+                                         query_string,
+                                         entry->commandTag,
                                          query_list,
                                          plan_list,
                                          qcontext);
@@ -228,8 +247,8 @@ InitQueryHashTable(void)
 
        MemSet(&hash_ctl, 0, sizeof(hash_ctl));
 
-       hash_ctl.keysize = HASH_KEY_LEN;
-       hash_ctl.entrysize = sizeof(QueryHashEntry);
+       hash_ctl.keysize = NAMEDATALEN;
+       hash_ctl.entrysize = sizeof(PreparedStatement);
 
        prepared_queries = hash_create("Prepared Queries",
                                                                   32,
@@ -244,15 +263,24 @@ InitQueryHashTable(void)
  * Store all the data pertaining to a query in the hash table using
  * the specified key. A copy of the data is made in a memory context belonging
  * to the hash entry, so the caller can dispose of their copy.
+ *
+ * Exception: commandTag is presumed to be a pointer to a constant string,
+ * or possibly NULL, so it need not be copied.  Note that commandTag should
+ * be NULL only if the original query (before rewriting) was empty.
  */
-static void
-StoreQuery(const char *stmt_name, List *query_list,
-                  List *plan_list, List *argtype_list)
+void
+StorePreparedStatement(const char *stmt_name,
+                                          const char *query_string,
+                                          const char *commandTag,
+                                          List *query_list,
+                                          List *plan_list,
+                                          List *argtype_list)
 {
-       QueryHashEntry *entry;
+       PreparedStatement *entry;
        MemoryContext oldcxt,
                                entrycxt;
-       char            key[HASH_KEY_LEN];
+       char       *qstring;
+       char            key[NAMEDATALEN];
        bool            found;
 
        /* Initialize the hash table, if necessary */
@@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list,
                InitQueryHashTable();
 
        /* Check for pre-existing entry of same name */
-       /* See notes in FetchQuery */
+       /* See notes in FetchPreparedStatement */
        MemSet(key, 0, sizeof(key));
        strncpy(key, stmt_name, sizeof(key));
 
@@ -285,15 +313,16 @@ StoreQuery(const char *stmt_name, List *query_list,
         * out-of-memory failure only wastes memory and doesn't leave us with
         * an incomplete (ie corrupt) hashtable entry.
         */
+       qstring = query_string ? pstrdup(query_string) : (char *) NULL;
        query_list = (List *) copyObject(query_list);
        plan_list = (List *) copyObject(plan_list);
        argtype_list = listCopy(argtype_list);
 
        /* Now we can add entry to hash table */
-       entry = (QueryHashEntry *) hash_search(prepared_queries,
-                                                                                  key,
-                                                                                  HASH_ENTER,
-                                                                                  &found);
+       entry = (PreparedStatement *) hash_search(prepared_queries,
+                                                                                         key,
+                                                                                         HASH_ENTER,
+                                                                                         &found);
 
        /* Shouldn't get a failure, nor a duplicate entry */
        if (!entry || found)
@@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list,
                         stmt_name);
 
        /* Fill in the hash table entry with copied data */
+       entry->query_string = qstring;
+       entry->commandTag = commandTag;
        entry->query_list = query_list;
        entry->plan_list = plan_list;
        entry->argtype_list = argtype_list;
@@ -311,52 +342,53 @@ StoreQuery(const char *stmt_name, List *query_list,
 
 /*
  * Lookup an existing query in the hash table. If the query does not
- * actually exist, an elog(ERROR) is thrown.
+ * actually exist, throw elog(ERROR) or return NULL per second parameter.
  */
-static QueryHashEntry *
-FetchQuery(const char *plan_name)
+PreparedStatement *
+FetchPreparedStatement(const char *stmt_name, bool throwError)
 {
-       char            key[HASH_KEY_LEN];
-       QueryHashEntry *entry;
+       char            key[NAMEDATALEN];
+       PreparedStatement *entry;
 
        /*
         * If the hash table hasn't been initialized, it can't be storing
         * anything, therefore it couldn't possibly store our plan.
         */
-       if (!prepared_queries)
-               elog(ERROR, "Prepared statement with name \"%s\" does not exist",
-                        plan_name);
-
-       /*
-        * We can't just use the statement name as supplied by the user: the
-        * hash package is picky enough that it needs to be NULL-padded out to
-        * the appropriate length to work correctly.
-        */
-       MemSet(key, 0, sizeof(key));
-       strncpy(key, plan_name, sizeof(key));
+       if (prepared_queries)
+       {
+               /*
+                * We can't just use the statement name as supplied by the user: the
+                * hash package is picky enough that it needs to be NULL-padded out to
+                * the appropriate length to work correctly.
+                */
+               MemSet(key, 0, sizeof(key));
+               strncpy(key, stmt_name, sizeof(key));
 
-       entry = (QueryHashEntry *) hash_search(prepared_queries,
-                                                                                  key,
-                                                                                  HASH_FIND,
-                                                                                  NULL);
+               entry = (PreparedStatement *) hash_search(prepared_queries,
+                                                                                                 key,
+                                                                                                 HASH_FIND,
+                                                                                                 NULL);
+       }
+       else
+               entry = NULL;
 
-       if (!entry)
+       if (!entry && throwError)
                elog(ERROR, "Prepared statement with name \"%s\" does not exist",
-                        plan_name);
+                        stmt_name);
 
        return entry;
 }
 
 /*
- * Given a plan name, look up the stored plan (giving error if not found).
+ * Look up a prepared statement given the name (giving error if not found).
  * If found, return the list of argument type OIDs.
  */
 List *
-FetchQueryParams(const char *plan_name)
+FetchPreparedStatementParams(const char *stmt_name)
 {
-       QueryHashEntry *entry;
+       PreparedStatement *entry;
 
-       entry = FetchQuery(plan_name);
+       entry = FetchPreparedStatement(stmt_name, true);
 
        return entry->argtype_list;
 }
@@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name)
 void
 DeallocateQuery(DeallocateStmt *stmt)
 {
-       QueryHashEntry *entry;
+       DropPreparedStatement(stmt->name, true);
+}
+
+/*
+ * Internal version of DEALLOCATE
+ *
+ * If showError is false, dropping a nonexistent statement is a no-op.
+ */
+void
+DropPreparedStatement(const char *stmt_name, bool showError)
+{
+       PreparedStatement *entry;
 
-       /* Find the query's hash table entry */
-       entry = FetchQuery(stmt->name);
+       /* Find the query's hash table entry; raise error if wanted */
+       entry = FetchPreparedStatement(stmt_name, showError);
 
-       /* Drop any open portals that depend on this prepared statement */
-       Assert(MemoryContextIsValid(entry->context));
-       DropDependentPortals(entry->context);
+       if (entry)
+       {
+               /* Drop any open portals that depend on this prepared statement */
+               Assert(MemoryContextIsValid(entry->context));
+               DropDependentPortals(entry->context);
 
-       /* Flush the context holding the subsidiary data */
-       MemoryContextDelete(entry->context);
+               /* Flush the context holding the subsidiary data */
+               MemoryContextDelete(entry->context);
 
-       /* Now we can remove the hash table entry */
-       hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL);
+               /* Now we can remove the hash table entry */
+               hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
+       }
 }
 
 /*
@@ -391,7 +437,7 @@ void
 ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
 {
        ExecuteStmt        *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
-       QueryHashEntry *entry;
+       PreparedStatement *entry;
        List       *l,
                           *query_list,
                           *plan_list;
@@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
        Assert(execstmt && IsA(execstmt, ExecuteStmt));
 
        /* Look it up in the hash table */
-       entry = FetchQuery(execstmt->name);
+       entry = FetchPreparedStatement(execstmt->name, true);
 
        query_list = entry->query_list;
        plan_list = entry->plan_list;
index a488d1d91e5cfd4d2626fef40cf77a557784faa0..ad2d5ab5681b9be914f340fd2d4a8a70fc65ee61 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.269 2003/05/02 20:54:34 tgl Exp $
+ *     $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.270 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -83,6 +83,12 @@ typedef struct
        IndexStmt  *pkey;                       /* PRIMARY KEY index, if any */
 } CreateStmtContext;
 
+typedef struct
+{
+       Oid                *paramTypes;
+       int                     numParams;
+} check_parameter_resolution_context;
+
 
 static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
 static Query *transformStmt(ParseState *pstate, Node *stmt,
@@ -124,6 +130,8 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column);
 static bool relationHasPrimaryKey(Oid relationOid);
 static void release_pstate_resources(ParseState *pstate);
 static FromExpr *makeFromExpr(List *fromlist, Node *quals);
+static bool check_parameter_resolution_walker(Node *node,
+                                          check_parameter_resolution_context *context);
 
 
 /*
@@ -179,6 +187,16 @@ parse_analyze_varparams(Node *parseTree, Oid **paramTypes, int *numParams)
 
        pfree(pstate);
 
+       /* make sure all is well with parameter types */
+       if (*numParams > 0)
+       {
+               check_parameter_resolution_context context;
+
+               context.paramTypes = *paramTypes;
+               context.numParams = *numParams;
+               check_parameter_resolution_walker((Node *) result, &context);
+       }
+
        return result;
 }
 
@@ -2465,7 +2483,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
        result->commandType = CMD_UTILITY;
        result->utilityStmt = (Node *) stmt;
 
-       paramtypes = FetchQueryParams(stmt->name);
+       paramtypes = FetchPreparedStatementParams(stmt->name);
 
        if (stmt->params || paramtypes)
        {
@@ -2879,3 +2897,44 @@ analyzeCreateSchemaStmt(CreateSchemaStmt *stmt)
 
        return result;
 }
+
+/*
+ * Traverse a fully-analyzed tree to verify that parameter symbols
+ * match their types.  We need this because some Params might still
+ * be UNKNOWN, if there wasn't anything to force their coercion,
+ * and yet other instances seen later might have gotten coerced.
+ */
+static bool
+check_parameter_resolution_walker(Node *node,
+                                                                 check_parameter_resolution_context *context)
+{
+       if (node == NULL)
+               return false;
+       if (IsA(node, Param))
+       {
+               Param      *param = (Param *) node;
+
+               if (param->paramkind == PARAM_NUM)
+               {
+                       int                     paramno = param->paramid;
+
+                       if (paramno <= 0 ||             /* shouldn't happen, but... */
+                               paramno > context->numParams)
+                               elog(ERROR, "Parameter '$%d' is out of range", paramno);
+
+                       if (param->paramtype != context->paramTypes[paramno-1])
+                               elog(ERROR, "Could not determine datatype of parameter $%d",
+                                        paramno);
+               }
+               return false;
+       }
+       if (IsA(node, Query))
+       {
+               /* Recurse into RTE subquery or not-yet-planned sublink subquery */
+               return query_tree_walker((Query *) node,
+                                                                check_parameter_resolution_walker,
+                                                                (void *) context, 0);
+       }
+       return expression_tree_walker(node, check_parameter_resolution_walker,
+                                                                 (void *) context);
+}
index 41906a348a461fdd3b590f7a8a63187d7bba553f..a5905dedc7fd93ebfb6bf252057e36d6cc6f02a1 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.54 2003/04/26 20:22:59 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.55 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,10 +91,20 @@ DestToFunction(CommandDest dest)
        switch (dest)
        {
                case Remote:
-                       return printtup_create_DR(false);
+                       return printtup_create_DR(false, true);
 
                case RemoteInternal:
-                       return printtup_create_DR(true);
+                       return printtup_create_DR(true, true);
+
+               case RemoteExecute:
+                       /* like Remote, but suppress output of T message */
+                       return printtup_create_DR(false, false);
+
+               case RemoteExecuteInternal:
+                       return printtup_create_DR(true, false);
+
+               case None:
+                       return &donothingDR;
 
                case Debug:
                        return &debugtupDR;
@@ -104,9 +114,6 @@ DestToFunction(CommandDest dest)
 
                case Tuplestore:
                        return tstoreReceiverCreateDR();
-
-               case None:
-                       return &donothingDR;
        }
 
        /* should never get here */
@@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest)
        {
                case Remote:
                case RemoteInternal:
+               case RemoteExecute:
+               case RemoteExecuteInternal:
                        pq_puttextmessage('C', commandTag);
                        break;
 
                case None:
                case Debug:
-               case Tuplestore:
                case SPI:
+               case Tuplestore:
                        break;
        }
 }
@@ -152,8 +161,10 @@ NullCommand(CommandDest dest)
 {
        switch (dest)
        {
-               case RemoteInternal:
                case Remote:
+               case RemoteInternal:
+               case RemoteExecute:
+               case RemoteExecuteInternal:
 
                        /*
                         * tell the fe that we saw an empty query string.  In protocols
@@ -165,10 +176,10 @@ NullCommand(CommandDest dest)
                                pq_puttextmessage('I', "");
                        break;
 
+               case None:
                case Debug:
+               case SPI:
                case Tuplestore:
-               case None:
-               default:
                        break;
        }
 }
@@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest)
 {
        switch (dest)
        {
-               case RemoteInternal:
                case Remote:
+               case RemoteInternal:
+               case RemoteExecute:
+               case RemoteExecuteInternal:
                        if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
                        {
                                StringInfoData buf;
@@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest)
                        pq_flush();
                        break;
 
+               case None:
                case Debug:
+               case SPI:
                case Tuplestore:
-               case None:
-               default:
                        break;
        }
 }
index 78fcfdb7e0ef845230fb53c19fe40099973389cd..65161c54ff3cb6d2ad63da391862ad0da9223917 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.61 2003/05/05 00:44:56 tgl Exp $
  *
  * NOTES
  *       This cruft is the server side of PQfn.
@@ -310,6 +310,14 @@ HandleFunctionRequest(StringInfo msgBuf)
        if (aclresult != ACLCHECK_OK)
                aclcheck_error(aclresult, get_func_name(fid));
 
+       /*
+        * Set up a query snapshot in case function needs one.
+        */
+       SetQuerySnapshot();
+
+       /*
+        * Prepare function call info block.
+        */
        if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
                elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
                         nargs, fip->flinfo.fn_nargs);
@@ -359,12 +367,8 @@ HandleFunctionRequest(StringInfo msgBuf)
                }
        }
 
-       /*
-        * Set up a query snapshot in case function needs one.  (It is not safe
-        * to do this if we are in transaction-abort state, so we have to postpone
-        * it till now.  Ugh.)
-        */
-       SetQuerySnapshot();
+       /* Verify we reached the end of the message where expected. */
+       pq_getmsgend(msgBuf);
 
 #ifdef NO_FASTPATH
        /* force a NULL return */
index b60898270a6e3de6aa88b40ed6a979c0efc928db..d57ccd973b2665d3ef199119ce1cea3baad9c51e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.331 2003/05/03 05:13:20 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.332 2003/05/05 00:44:56 tgl Exp $
  *
  * NOTES
  *       this is the "main" module of the postgres backend and
 #include <getopt.h>
 #endif
 
+#include "access/printtup.h"
 #include "access/xlog.h"
+#include "catalog/pg_type.h"
 #include "commands/async.h"
+#include "commands/prepare.h"
 #include "commands/trigger.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,6 +57,7 @@
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
 #include "utils/guc.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "mb/pg_wchar.h"
@@ -82,7 +86,15 @@ bool         InError = false;
 
 extern bool    autocommit;
 
-static bool EchoQuery = false; /* default don't echo */
+/*
+ * Flags for expensive function optimization -- JMH 3/9/92
+ */
+int                    XfuncMode = 0;
+
+/* ----------------
+ *             private variables
+ * ----------------
+ */
 
 /*
  * Flag to mark SIGHUP. Whenever the main loop comes around it
@@ -91,23 +103,41 @@ static bool EchoQuery = false;     /* default don't echo */
  */
 static volatile bool got_SIGHUP = false;
 
-/* ----------------
- *             people who want to use EOF should #define DONTUSENEWLINE in
- *             tcop/tcopdebug.h
- * ----------------
+/*
+ * Flag to keep track of whether we have started a transaction.
+ * For extended query protocol this has to be remembered across messages.
+ */
+static bool xact_started = false;
+
+/*
+ * Flags to implement skip-till-Sync-after-error behavior for messages of
+ * the extended query protocol.
+ */
+static bool doing_extended_query_message = false;
+static bool ignore_till_sync = false;
+
+/*
+ * If an unnamed prepared statement exists, it's stored here.
+ * We keep it separate from the hashtable kept by commands/prepare.c
+ * in order to reduce overhead for short-lived queries.
+ */
+static MemoryContext unnamed_stmt_context = NULL;
+static PreparedStatement *unnamed_stmt_pstmt = NULL;
+
+
+static bool EchoQuery = false; /* default don't echo */
+
+/*
+ * people who want to use EOF should #define DONTUSENEWLINE in
+ * tcop/tcopdebug.h
  */
 #ifndef TCOP_DONTUSENEWLINE
-int                    UseNewLine = 1;         /* Use newlines query delimiters (the
+static int     UseNewLine = 1;         /* Use newlines query delimiters (the
                                                                 * default) */
-
 #else
-int                    UseNewLine = 0;         /* Use EOF as query delimiters */
+static int     UseNewLine = 0;         /* Use EOF as query delimiters */
 #endif   /* TCOP_DONTUSENEWLINE */
 
-/*
-** Flags for expensive function optimization -- JMH 3/9/92
-*/
-int                    XfuncMode = 0;
 
 /* ----------------------------------------------------------------
  *             decls for routines only used in this file
@@ -254,10 +284,14 @@ SocketBackend(StringInfo inBuf)
         * Validate message type code before trying to read body; if we have
         * lost sync, better to say "command unknown" than to run out of memory
         * because we used garbage as a length word.
+        *
+        * This also gives us a place to set the doing_extended_query_message
+        * flag as soon as possible.
         */
        switch (qtype)
        {
                case 'Q':                               /* simple query */
+                       doing_extended_query_message = false;
                        if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
                        {
                                /* old style without length word; convert */
@@ -270,15 +304,43 @@ SocketBackend(StringInfo inBuf)
                        break;
 
                case 'F':                               /* fastpath function call */
+                       /* we let fastpath.c cope with old-style input of this */
+                       doing_extended_query_message = false;
                        break;
 
                case 'X':                               /* terminate */
+                       doing_extended_query_message = false;
+                       break;
+
+               case 'B':                               /* bind */
+               case 'C':                               /* close */
+               case 'D':                               /* describe */
+               case 'E':                               /* execute */
+               case 'H':                               /* flush */
+               case 'P':                               /* parse */
+                       doing_extended_query_message = true;
+                       /* these are only legal in protocol 3 */
+                       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+                               elog(FATAL, "Socket command type %c unknown", qtype);
+                       break;
+
+               case 'S':                               /* sync */
+                       /* stop any active skip-till-Sync */
+                       ignore_till_sync = false;
+                       /* mark not-extended, so that a new error doesn't begin skip */
+                       doing_extended_query_message = false;
+                       /* only legal in protocol 3 */
+                       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+                               elog(FATAL, "Socket command type %c unknown", qtype);
                        break;
 
                case 'd':                               /* copy data */
                case 'c':                               /* copy done */
                case 'f':                               /* copy fail */
-                       /* Accept but ignore these messages, per protocol spec */
+                       doing_extended_query_message = false;
+                       /* these are only legal in protocol 3 */
+                       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+                               elog(FATAL, "Socket command type %c unknown", qtype);
                        break;
 
                default:
@@ -410,9 +472,6 @@ List *
 pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 {
        List       *querytree_list;
-       List       *list_item;
-       Query      *querytree;
-       List       *new_list;
 
        /*
         * (1) Perform parse analysis.
@@ -423,21 +482,35 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
        querytree_list = parse_analyze(parsetree, paramTypes, numParams);
 
        if (log_parser_stats)
-       {
                ShowUsage("PARSE ANALYSIS STATISTICS");
-               ResetUsage();
-       }
 
        /*
         * (2) Rewrite the queries, as necessary
-        *
+        */
+       querytree_list = pg_rewrite_queries(querytree_list);
+
+       return querytree_list;
+}
+
+/*
+ * Perform rewriting of a list of queries produced by parse analysis.
+ */
+List *
+pg_rewrite_queries(List *querytree_list)
+{
+       List       *new_list = NIL;
+       List       *list_item;
+
+       if (log_parser_stats)
+               ResetUsage();
+
+       /*
         * rewritten queries are collected in new_list.  Note there may be more
         * or fewer than in the original list.
         */
-       new_list = NIL;
        foreach(list_item, querytree_list)
        {
-               querytree = (Query *) lfirst(list_item);
+               Query      *querytree = (Query *) lfirst(list_item);
 
                if (Debug_print_parse)
                        elog_node_display(LOG, "parse tree", querytree,
@@ -471,7 +544,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
        new_list = (List *) copyObject(querytree_list);
        /* This checks both copyObject() and the equal() routines... */
        if (!equal(new_list, querytree_list))
-               elog(WARNING, "pg_analyze_and_rewrite: copyObject failed on parse tree");
+               elog(WARNING, "pg_rewrite_queries: copyObject failed on parse tree");
        else
                querytree_list = new_list;
 #endif
@@ -576,15 +649,13 @@ pg_plan_queries(List *querytrees, bool needSnapshot)
 
 
 /*
- * exec_simple_query()
+ * exec_simple_query
  *
  * Execute a "simple Query" protocol message.
  */
 static void
-exec_simple_query(const char *query_string,    /* string to execute */
-                                 CommandDest dest)                     /* where results should go */
+exec_simple_query(const char *query_string)
 {
-       bool            xact_started;
        MemoryContext oldcontext;
        List       *parsetree_list,
                           *parsetree_item;
@@ -619,13 +690,28 @@ exec_simple_query(const char *query_string,       /* string to execute */
         * that this will normally change current memory context.)
         */
        start_xact_command();
-       xact_started = true;
+
+       /*
+        * Zap any pre-existing unnamed statement.  (While not strictly
+        * necessary, it seems best to define simple-Query mode as if it
+        * used the unnamed statement and portal; this ensures we recover
+        * any storage used by prior unnamed operations.)
+        */
+       unnamed_stmt_pstmt = NULL;
+       if (unnamed_stmt_context)
+       {
+               DropDependentPortals(unnamed_stmt_context);
+               MemoryContextDelete(unnamed_stmt_context);
+       }
+       unnamed_stmt_context = NULL;
 
        /*
         * Switch to appropriate context for constructing parsetrees.
         */
        oldcontext = MemoryContextSwitchTo(MessageContext);
 
+       QueryContext = CurrentMemoryContext;
+
        /*
         * Do basic parsing of the query or queries (this should be safe even
         * if we are in aborted transaction state!)
@@ -659,7 +745,7 @@ exec_simple_query(const char *query_string, /* string to execute */
 
                set_ps_display(commandTag);
 
-               BeginCommand(commandTag, dest);
+               BeginCommand(commandTag, whereToSendOutput);
 
                /*
                 * If we are in an aborted transaction, reject all commands except
@@ -688,11 +774,7 @@ exec_simple_query(const char *query_string,        /* string to execute */
                }
 
                /* Make sure we are in a transaction command */
-               if (!xact_started)
-               {
-                       start_xact_command();
-                       xact_started = true;
-               }
+               start_xact_command();
 
                /* If we got a cancel signal in parsing or prior command, quit */
                CHECK_FOR_INTERRUPTS();
@@ -735,37 +817,40 @@ exec_simple_query(const char *query_string,       /* string to execute */
                 */
                PortalStart(portal, NULL);
 
-               (void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
+               (void) PortalRun(portal,
+                                                FETCH_ALL,
+                                                whereToSendOutput,
+                                                whereToSendOutput,
+                                                completionTag);
 
                PortalDrop(portal, false);
 
-               /*
-                * If this was a transaction control statement or a variable
-                * set/show/reset statement, commit it and arrange to start a
-                * new xact command for the next command (if any).
-                */
+
                if (IsA(parsetree, TransactionStmt) ||
                        IsA(parsetree, VariableSetStmt) ||
                        IsA(parsetree, VariableShowStmt) ||
                        IsA(parsetree, VariableResetStmt))
                {
+                       /*
+                        * If this was a transaction control statement or a variable
+                        * set/show/reset statement, commit it.  We will start a
+                        * new xact command for the next command (if any).
+                        */
                        finish_xact_command(true);
-                       xact_started = false;
                }
-               /*
-                * If this is the last parsetree of the query string, close down
-                * transaction statement before reporting command-complete.  This
-                * is so that any end-of-transaction errors are reported before
-                * the command-complete message is issued, to avoid confusing
-                * clients who will expect either a command-complete message or an
-                * error, not one and then the other.  But for compatibility with
-                * historical Postgres behavior, we do not force a transaction
-                * boundary between queries appearing in a single query string.
-                */
                else if (lnext(parsetree_item) == NIL || !autocommit)
                {
+                       /*
+                        * If this is the last parsetree of the query string, close down
+                        * transaction statement before reporting command-complete.  This
+                        * is so that any end-of-transaction errors are reported before
+                        * the command-complete message is issued, to avoid confusing
+                        * clients who will expect either a command-complete message or an
+                        * error, not one and then the other.  But for compatibility with
+                        * historical Postgres behavior, we do not force a transaction
+                        * boundary between queries appearing in a single query string.
+                        */
                        finish_xact_command(false);
-                       xact_started = false;
                }
                else
                {
@@ -783,20 +868,21 @@ exec_simple_query(const char *query_string,       /* string to execute */
                 * (But a command aborted by error will not send an EndCommand
                 * report at all.)
                 */
-               EndCommand(completionTag, dest);
+               EndCommand(completionTag, whereToSendOutput);
        }                                                       /* end loop over parsetrees */
 
        /*
         * If there were no parsetrees, return EmptyQueryResponse message.
         */
        if (!parsetree_list)
-               NullCommand(dest);
+               NullCommand(whereToSendOutput);
+
+       QueryContext = NULL;
 
        /*
         * Close down transaction statement, if one is open.
         */
-       if (xact_started)
-               finish_xact_command(false);
+       finish_xact_command(false);
 
        /*
         * Finish up monitoring.
@@ -820,39 +906,609 @@ exec_simple_query(const char *query_string,      /* string to execute */
        debug_query_string = NULL;
 }
 
+/*
+ * exec_parse_message
+ *
+ * Execute a "Parse" protocol message.
+ */
+static void
+exec_parse_message(const char *query_string,   /* string to execute */
+                                  const char *stmt_name,               /* name for prepared stmt */
+                                  Oid *paramTypes,                             /* parameter types */
+                                  int numParams)                               /* number of parameters */
+{
+       MemoryContext oldcontext;
+       List       *parsetree_list;
+       const char *commandTag;
+       List       *querytree_list,
+                          *plantree_list,
+                          *param_list;
+       bool            is_named;
+       bool            save_log_statement_stats = log_statement_stats;
+
+       /*
+        * Report query to various monitoring facilities.
+        */
+       debug_query_string = query_string;
+
+       pgstat_report_activity(query_string);
+
+       set_ps_display("PARSE");
+
+       if (save_log_statement_stats)
+               ResetUsage();
+
+       /*
+        * Start up a transaction command so we can run parse analysis etc.
+        * (Note that this will normally change current memory context.)
+        * Nothing happens if we are already in one.
+        */
+       start_xact_command();
+
+       /*
+        * Switch to appropriate context for constructing parsetrees.
+        *
+        * We have two strategies depending on whether the prepared statement
+        * is named or not.  For a named prepared statement, we do parsing
+        * in MessageContext and copy the finished trees into the prepared
+        * statement's private context; then the reset of MessageContext releases
+        * temporary space used by parsing and planning.  For an unnamed prepared
+        * statement, we assume the statement isn't going to hang around long,
+        * so getting rid of temp space quickly is probably not worth the costs
+        * of copying parse/plan trees.  So in this case, we set up a special
+        * context for the unnamed statement, and do all the parsing/planning
+        * therein.
+        */
+       is_named = (stmt_name[0] != '\0');
+       if (is_named)
+       {
+               /* Named prepared statement --- parse in MessageContext */
+               oldcontext = MemoryContextSwitchTo(MessageContext);
+       }
+       else
+       {
+               /* Unnamed prepared statement --- release any prior unnamed stmt */
+               unnamed_stmt_pstmt = NULL;
+               if (unnamed_stmt_context)
+               {
+                       DropDependentPortals(unnamed_stmt_context);
+                       MemoryContextDelete(unnamed_stmt_context);
+               }
+               unnamed_stmt_context = NULL;
+               /* create context for parsing/planning */
+               unnamed_stmt_context =
+                       AllocSetContextCreate(TopMemoryContext,
+                                                                 "unnamed prepared statement",
+                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                 ALLOCSET_DEFAULT_MAXSIZE);
+               oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
+       }
+
+       QueryContext = CurrentMemoryContext;
+
+       /*
+        * Do basic parsing of the query or queries (this should be safe even
+        * if we are in aborted transaction state!)
+        */
+       parsetree_list = pg_parse_query(query_string);
+
+       /*
+        * We only allow a single user statement in a prepared statement.
+        * This is mainly to keep the protocol simple --- otherwise we'd need
+        * to worry about multiple result tupdescs and things like that.
+        */
+       if (length(parsetree_list) > 1)
+               elog(ERROR, "Cannot insert multiple commands into a prepared statement");
+
+       if (parsetree_list != NIL)
+       {
+               Node   *parsetree = (Node *) lfirst(parsetree_list);
+               int             i;
+
+               /*
+                * Get the command name for possible use in status display.
+                */
+               commandTag = CreateCommandTag(parsetree);
+
+               /*
+                * If we are in an aborted transaction, reject all commands except
+                * COMMIT/ROLLBACK.  It is important that this test occur before we
+                * try to do parse analysis, rewrite, or planning, since all those
+                * phases try to do database accesses, which may fail in abort
+                * state. (It might be safe to allow some additional utility
+                * commands in this state, but not many...)
+                */
+               if (IsAbortedTransactionBlockState())
+               {
+                       bool            allowit = false;
+
+                       if (IsA(parsetree, TransactionStmt))
+                       {
+                               TransactionStmt *stmt = (TransactionStmt *) parsetree;
+
+                               if (stmt->kind == TRANS_STMT_COMMIT ||
+                                       stmt->kind == TRANS_STMT_ROLLBACK)
+                                       allowit = true;
+                       }
+
+                       if (!allowit)
+                               elog(ERROR, "current transaction is aborted, "
+                                        "queries ignored until end of transaction block");
+               }
+
+               /*
+                * OK to analyze, rewrite, and plan this query.  Note that the
+                * originally specified parameter set is not required to be
+                * complete, so we have to use parse_analyze_varparams().
+                */
+               if (log_parser_stats)
+                       ResetUsage();
+
+               querytree_list = parse_analyze_varparams(parsetree,
+                                                                                                &paramTypes,
+                                                                                                &numParams);
+
+               /*
+                * Check all parameter types got determined, and convert array
+                * representation to a list for storage.
+                */
+               param_list = NIL;
+               for (i = 0; i < numParams; i++)
+               {
+                       Oid             ptype = paramTypes[i];
+
+                       if (ptype == InvalidOid || ptype == UNKNOWNOID)
+                               elog(ERROR, "Could not determine datatype of parameter $%d",
+                                        i + 1);
+                       param_list = lappendo(param_list, ptype);
+               }
+
+               if (log_parser_stats)
+                       ShowUsage("PARSE ANALYSIS STATISTICS");
+
+               querytree_list = pg_rewrite_queries(querytree_list);
+
+               plantree_list = pg_plan_queries(querytree_list, true);
+       }
+       else
+       {
+               /* Empty input string.  This is legal. */
+               commandTag = NULL;
+               querytree_list = NIL;
+               plantree_list = NIL;
+               param_list = NIL;
+       }
+
+       /* If we got a cancel signal in analysis or planning, quit */
+       CHECK_FOR_INTERRUPTS();
+
+       /*
+        * Store the query as a prepared statement.  See above comments.
+        */
+       if (is_named)
+       {
+               StorePreparedStatement(stmt_name,
+                                                          query_string,
+                                                          commandTag,
+                                                          querytree_list,
+                                                          plantree_list,
+                                                          param_list);
+       }
+       else
+       {
+               PreparedStatement *pstmt;
+
+               pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
+               /* query_string needs to be copied into unnamed_stmt_context */
+               pstmt->query_string = pstrdup(query_string);
+               /* the rest is there already */
+               pstmt->commandTag = commandTag;
+               pstmt->query_list = querytree_list;
+               pstmt->plan_list = plantree_list;
+               pstmt->argtype_list = param_list;
+               pstmt->context = unnamed_stmt_context;
+               /* Now the unnamed statement is complete and valid */
+               unnamed_stmt_pstmt = pstmt;
+       }
+
+       MemoryContextSwitchTo(oldcontext);
+
+       QueryContext = NULL;
+
+       /*
+        * We do NOT close the open transaction command here; that only happens
+        * when the client sends Sync.  Instead, do CommandCounterIncrement just
+        * in case something happened during parse/plan.
+        */
+       CommandCounterIncrement();
+
+       /*
+        * Send ParseComplete.
+        */
+       if (whereToSendOutput == Remote)
+               pq_putemptymessage('1');
+
+       if (save_log_statement_stats)
+               ShowUsage("PARSE MESSAGE STATISTICS");
+
+       debug_query_string = NULL;
+}
+
+/*
+ * exec_bind_message
+ *
+ * Process a "Bind" message to create a portal from a prepared statement
+ */
+static void
+exec_bind_message(StringInfo input_message)
+{
+       const char *portal_name;
+       const char *stmt_name;
+       int                     is_binary;
+       int                     numParams;
+       PreparedStatement *pstmt;
+       Portal          portal;
+       ParamListInfo params;
+
+       pgstat_report_activity("<BIND>");
+
+       set_ps_display("BIND");
+
+       /*
+        * Start up a transaction command so we can call functions etc.
+        * (Note that this will normally change current memory context.)
+        * Nothing happens if we are already in one.
+        */
+       start_xact_command();
+
+       /* Get the fixed part of the message */
+       portal_name = pq_getmsgstring(input_message);
+       stmt_name = pq_getmsgstring(input_message);
+       is_binary = pq_getmsgbyte(input_message);
+       numParams = pq_getmsgint(input_message, 4);
+
+       if (is_binary)
+               elog(ERROR, "Binary BIND not implemented yet");
+
+       /* Find prepared statement */
+       if (stmt_name[0] != '\0')
+               pstmt = FetchPreparedStatement(stmt_name, true);
+       else
+       {
+               /* special-case the unnamed statement */
+               pstmt = unnamed_stmt_pstmt;
+               if (!pstmt)
+                       elog(ERROR, "Unnamed prepared statement does not exist");
+       }
+
+       if (numParams != length(pstmt->argtype_list))
+               elog(ERROR, "Bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
+                        numParams, stmt_name, length(pstmt->argtype_list));
+
+       /*
+        * Create the portal.  Allow silent replacement of an existing portal
+        * only if the unnamed portal is specified.
+        */
+       if (portal_name[0] == '\0')
+               portal = CreatePortal(portal_name, true, true);
+       else
+               portal = CreatePortal(portal_name, false, false);
+
+       PortalDefineQuery(portal,
+                                         pstmt->query_string,
+                                         pstmt->commandTag,
+                                         pstmt->query_list,
+                                         pstmt->plan_list,
+                                         pstmt->context);
+
+       /*
+        * Fetch parameters, if any, and store in the portal's memory context.
+        *
+        * In an aborted transaction, we can't risk calling user-defined functions,
+        * so bind all parameters to null values.
+        */
+       if (numParams > 0)
+       {
+               bool    isaborted = IsAbortedTransactionBlockState();
+               int             i = 0;
+               List   *l;
+               MemoryContext oldContext;
+
+               oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+               params = (ParamListInfo)
+                       palloc0((numParams + 1) * sizeof(ParamListInfoData));
+
+               foreach(l, pstmt->argtype_list)
+               {
+                       Oid                     ptype = lfirsto(l);
+                       bool            isNull;
+
+                       isNull = (pq_getmsgbyte(input_message) != 0) ? false : true;
+                       if (!isNull)
+                       {
+                               const char *ptext = pq_getmsgstring(input_message);
+
+                               if (isaborted)
+                                       isNull = true;
+                               else
+                               {
+                                       Oid                     typInput;
+                                       Oid                     typElem;
+
+                                       getTypeInputInfo(ptype, &typInput, &typElem);
+                                       params[i].value =
+                                               OidFunctionCall3(typInput,
+                                                                                CStringGetDatum(ptext),
+                                                                                ObjectIdGetDatum(typElem),
+                                                                                Int32GetDatum(-1));
+                               }
+                       }
+                       params[i].kind = PARAM_NUM;
+                       params[i].id = i + 1;
+                       params[i].isnull = isNull;
+
+                       i++;
+               }
+
+               params[i].kind = PARAM_INVALID;
+
+               MemoryContextSwitchTo(oldContext);
+       }
+       else
+               params = NULL;
+
+       pq_getmsgend(input_message);
+
+       /*
+        * Start portal execution.
+        */
+       PortalStart(portal, params);
+
+       /*
+        * Send BindComplete.
+        */
+       if (whereToSendOutput == Remote)
+               pq_putemptymessage('2');
+}
+
+/*
+ * exec_execute_message
+ *
+ * Process an "Execute" message for a portal
+ */
+static void
+exec_execute_message(const char *portal_name, int is_binary, long max_rows)
+{
+       CommandDest     dest;
+       Portal          portal;
+       bool            is_trans_stmt = false;
+       bool            is_trans_exit = false;
+       bool            completed;
+       char            completionTag[COMPLETION_TAG_BUFSIZE];
+
+       /* Adjust destination to tell printtup.c what to do */
+       dest = whereToSendOutput;
+       if (dest == Remote)
+               dest = is_binary ? RemoteExecuteInternal : RemoteExecute;
+
+       portal = GetPortalByName(portal_name);
+       if (!PortalIsValid(portal))
+               elog(ERROR, "Portal \"%s\" not found", portal_name);
+
+       /*
+        * If the original query was a null string, just return EmptyQueryResponse.
+        */
+       if (portal->commandTag == NULL)
+       {
+               Assert(portal->parseTrees == NIL);
+               NullCommand(dest);
+               return;
+       }
+
+       if (portal->sourceText)
+       {
+               debug_query_string = portal->sourceText;
+               pgstat_report_activity(portal->sourceText);
+       }
+       else
+       {
+               debug_query_string = "execute message";
+               pgstat_report_activity("<EXECUTE>");
+       }
+
+       set_ps_display(portal->commandTag);
+
+       BeginCommand(portal->commandTag, dest);
+
+       /* Check for transaction-control commands */
+       if (length(portal->parseTrees) == 1)
+       {
+               Query *query = (Query *) lfirst(portal->parseTrees);
+
+               if (query->commandType == CMD_UTILITY &&
+                       query->utilityStmt != NULL &&
+                       IsA(query->utilityStmt, TransactionStmt))
+               {
+                       TransactionStmt *stmt = (TransactionStmt *) query->utilityStmt;
+
+                       is_trans_stmt = true;
+                       if (stmt->kind == TRANS_STMT_COMMIT ||
+                               stmt->kind == TRANS_STMT_ROLLBACK)
+                               is_trans_exit = true;
+               }
+       }
+
+       /*
+        * Ensure we are in a transaction command (this should normally be
+        * the case already due to prior BIND).
+        */
+       start_xact_command();
+
+       /*
+        * If we are in aborted transaction state, the only portals we can
+        * actually run are those containing COMMIT or ROLLBACK commands.
+        */
+       if (IsAbortedTransactionBlockState())
+       {
+               if (!is_trans_exit)
+                       elog(ERROR, "current transaction is aborted, "
+                                "queries ignored until end of transaction block");
+       }
+
+       /* Check for cancel signal before we start execution */
+       CHECK_FOR_INTERRUPTS();
+
+       /*
+        * Okay to run the portal.
+        */
+       if (max_rows <= 0)
+               max_rows = FETCH_ALL;
+
+       completed = PortalRun(portal,
+                                                 max_rows,
+                                                 dest,
+                                                 dest,
+                                                 completionTag);
+
+       if (completed)
+       {
+               if (is_trans_stmt)
+               {
+                       /*
+                        * If this was a transaction control statement, commit it.  We will
+                        * start a new xact command for the next command (if any).
+                        */
+                       finish_xact_command(true);
+               }
+               else
+               {
+                       /*
+                        * We need a CommandCounterIncrement after every query,
+                        * except those that start or end a transaction block.
+                        */
+                       CommandCounterIncrement();
+               }
+
+               /* Send appropriate CommandComplete to client */
+               EndCommand(completionTag, dest);
+       }
+       else
+       {
+               /* Portal run not complete, so send PortalSuspended */
+               if (whereToSendOutput == Remote)
+                       pq_putemptymessage('s');
+       }
+
+       debug_query_string = NULL;
+}
+
+/*
+ * exec_describe_statement_message
+ *
+ * Process a "Describe" message for a prepared statement
+ */
+static void
+exec_describe_statement_message(const char *stmt_name)
+{
+       PreparedStatement *pstmt;
+       List       *l;
+       StringInfoData buf;
+
+       /* Find prepared statement */
+       if (stmt_name[0] != '\0')
+               pstmt = FetchPreparedStatement(stmt_name, true);
+       else
+       {
+               /* special-case the unnamed statement */
+               pstmt = unnamed_stmt_pstmt;
+               if (!pstmt)
+                       elog(ERROR, "Unnamed prepared statement does not exist");
+       }
+
+       if (whereToSendOutput != Remote)
+               return;                                 /* can't actually do anything... */
+
+       pq_beginmessage(&buf, 't');             /* parameter description message type */
+       pq_sendint(&buf, length(pstmt->argtype_list), 4);
+
+       foreach(l, pstmt->argtype_list)
+       {
+               Oid                     ptype = lfirsto(l);
+
+               pq_sendint(&buf, (int) ptype, 4);
+       }
+       pq_endmessage(&buf);
+}
+
+/*
+ * exec_describe_portal_message
+ *
+ * Process a "Describe" message for a portal
+ */
+static void
+exec_describe_portal_message(const char *portal_name)
+{
+       Portal          portal;
+
+       portal = GetPortalByName(portal_name);
+       if (!PortalIsValid(portal))
+               elog(ERROR, "Portal \"%s\" not found", portal_name);
+
+       if (whereToSendOutput != Remote)
+               return;                                 /* can't actually do anything... */
+
+       if (portal->tupDesc)
+               SendRowDescriptionMessage(portal->tupDesc);
+       else
+               pq_putemptymessage('n');        /* NoData */
+}
+
+
 /*
  * Convenience routines for starting/committing a single command.
  */
 static void
 start_xact_command(void)
 {
-       elog(DEBUG1, "StartTransactionCommand");
-       StartTransactionCommand(false);
+       if (!xact_started)
+       {
+               elog(DEBUG2, "StartTransactionCommand");
+               StartTransactionCommand(false);
+
+               /* Set statement timeout running, if any */
+               if (StatementTimeout > 0)
+                       enable_sig_alarm(StatementTimeout, true);
 
-       /* Set statement timeout running, if any */
-       if (StatementTimeout > 0)
-               enable_sig_alarm(StatementTimeout, true);
+               xact_started = true;
+       }
 }
 
 static void
 finish_xact_command(bool forceCommit)
 {
-       /* Invoke IMMEDIATE constraint triggers */
-       DeferredTriggerEndQuery();
+       if (xact_started)
+       {
+               /* Invoke IMMEDIATE constraint triggers */
+               DeferredTriggerEndQuery();
 
-       /* Cancel any active statement timeout before committing */
-       disable_sig_alarm(true);
+               /* Cancel any active statement timeout before committing */
+               disable_sig_alarm(true);
 
-       /* Now commit the command */
-       elog(DEBUG1, "CommitTransactionCommand");
+               /* Now commit the command */
+               elog(DEBUG2, "CommitTransactionCommand");
 
-       CommitTransactionCommand(forceCommit);
+               CommitTransactionCommand(forceCommit);
 
 #ifdef SHOW_MEMORY_STATS
-       /* Print mem stats at each commit for leak tracking */
-       if (ShowStats)
-               MemoryContextStats(TopMemoryContext);
+               /* Print mem stats at each commit for leak tracking */
+               if (ShowStats)
+                       MemoryContextStats(TopMemoryContext);
 #endif
+
+               xact_started = false;
+       }
 }
 
 
@@ -1679,7 +2335,7 @@ PostgresMain(int argc, char *argv[], const char *username)
        if (!IsUnderPostmaster)
        {
                puts("\nPOSTGRES backend interactive interface ");
-               puts("$Revision: 1.331 $ $Date: 2003/05/03 05:13:20 $\n");
+               puts("$Revision: 1.332 $ $Date: 2003/05/05 00:44:56 $\n");
        }
 
        /*
@@ -1756,6 +2412,14 @@ PostgresMain(int argc, char *argv[], const char *username)
                 * successfully.  (Flag was set in elog.c before longjmp().)
                 */
                InError = false;
+               xact_started = false;
+
+               /*
+                * If we were handling an extended-query-protocol message,
+                * initiate skip till next Sync.
+                */
+               if (doing_extended_query_message)
+                       ignore_till_sync = true;
 
                /*
                 * Exit interrupt holdoff section we implicitly established above.
@@ -1775,6 +2439,12 @@ PostgresMain(int argc, char *argv[], const char *username)
 
        for (;;)
        {
+               /*
+                * At top of loop, reset extended-query-message flag, so that
+                * any errors encountered in "idle" state don't provoke skip.
+                */
+               doing_extended_query_message = false;
+
                /*
                 * Release storage left over from prior query cycle, and create a
                 * new query input buffer in the cleared MessageContext.
@@ -1853,20 +2523,74 @@ PostgresMain(int argc, char *argv[], const char *username)
                }
 
                /*
-                * (6) process the command.
+                * (6) process the command.  But ignore it if we're skipping till Sync.
                 */
+               if (ignore_till_sync)
+                       continue;
+
                switch (firstchar)
                {
                        case 'Q':                       /* simple query */
                                {
-                                       const char *query_string = pq_getmsgstring(input_message);
+                                       const char *query_string;
+
+                                       query_string = pq_getmsgstring(input_message);
+                                       pq_getmsgend(input_message);
 
-                                       exec_simple_query(query_string, whereToSendOutput);
+                                       exec_simple_query(query_string);
 
                                        send_rfq = true;
                                }
                                break;
 
+                       case 'P':                       /* parse */
+                               {
+                                       const char *stmt_name;
+                                       const char *query_string;
+                                       int                     numParams;
+                                       Oid                *paramTypes = NULL;
+
+                                       stmt_name = pq_getmsgstring(input_message);
+                                       query_string = pq_getmsgstring(input_message);
+                                       numParams = pq_getmsgint(input_message, 4);
+                                       if (numParams > 0)
+                                       {
+                                               int             i;
+
+                                               paramTypes = (Oid *) palloc(numParams * sizeof(Oid));
+                                               for (i = 0; i < numParams; i++)
+                                                       paramTypes[i] = pq_getmsgint(input_message, 4);
+                                       }
+                                       pq_getmsgend(input_message);
+
+                                       exec_parse_message(query_string, stmt_name,
+                                                                          paramTypes, numParams);
+                               }
+                               break;
+
+                       case 'B':                       /* bind */
+                               /*
+                                * this message is complex enough that it seems best to put
+                                * the field extraction out-of-line
+                                */
+                               exec_bind_message(input_message);
+                               break;
+
+                       case 'E':                       /* execute */
+                               {
+                                       const char *portal_name;
+                                       int             is_binary;
+                                       int             max_rows;
+
+                                       portal_name = pq_getmsgstring(input_message);
+                                       is_binary = pq_getmsgbyte(input_message);
+                                       max_rows = pq_getmsgint(input_message, 4);
+                                       pq_getmsgend(input_message);
+
+                                       exec_execute_message(portal_name, is_binary, max_rows);
+                               }
+                               break;
+
                        case 'F':                       /* fastpath function call */
                                /* Tell the collector what we're doing */
                                pgstat_report_activity("<FASTPATH> function call");
@@ -1894,6 +2618,89 @@ PostgresMain(int argc, char *argv[], const char *username)
                                send_rfq = true;
                                break;
 
+                       case 'C':                               /* close */
+                               {
+                                       int             close_type;
+                                       const char *close_target;
+
+                                       close_type = pq_getmsgbyte(input_message);
+                                       close_target = pq_getmsgstring(input_message);
+                                       pq_getmsgend(input_message);
+
+                                       switch (close_type)
+                                       {
+                                               case 'S':
+                                                       if (close_target[0] != '\0')
+                                                               DropPreparedStatement(close_target, false);
+                                                       else
+                                                       {
+                                                               /* special-case the unnamed statement */
+                                                               unnamed_stmt_pstmt = NULL;
+                                                               if (unnamed_stmt_context)
+                                                               {
+                                                                       DropDependentPortals(unnamed_stmt_context);
+                                                                       MemoryContextDelete(unnamed_stmt_context);
+                                                               }
+                                                               unnamed_stmt_context = NULL;
+                                                       }
+                                                       break;
+                                               case 'P':
+                                                       {
+                                                               Portal          portal;
+
+                                                               portal = GetPortalByName(close_target);
+                                                               if (PortalIsValid(portal))
+                                                                       PortalDrop(portal, false);
+                                                       }
+                                                       break;
+                                               default:
+                                                       elog(ERROR, "Invalid Close message subtype %d",
+                                                                close_type);
+                                                       break;
+                                       }
+
+                                       if (whereToSendOutput == Remote)
+                                               pq_putemptymessage('3'); /* CloseComplete */
+                               }
+                               break;
+
+                       case 'D':                       /* describe */
+                               {
+                                       int             describe_type;
+                                       const char *describe_target;
+
+                                       describe_type = pq_getmsgbyte(input_message);
+                                       describe_target = pq_getmsgstring(input_message);
+                                       pq_getmsgend(input_message);
+
+                                       switch (describe_type)
+                                       {
+                                               case 'S':
+                                                       exec_describe_statement_message(describe_target);
+                                                       break;
+                                               case 'P':
+                                                       exec_describe_portal_message(describe_target);
+                                                       break;
+                                               default:
+                                                       elog(ERROR, "Invalid Describe message subtype %d",
+                                                                describe_type);
+                                                       break;
+                                       }
+                               }
+                               break;
+
+                       case 'H':                               /* flush */
+                               pq_getmsgend(input_message);
+                               if (whereToSendOutput == Remote)
+                                       pq_flush();
+                               break;
+
+                       case 'S':                               /* sync */
+                               pq_getmsgend(input_message);
+                               finish_xact_command(false);
+                               send_rfq = true;
+                               break;
+
                                /*
                                 * 'X' means that the frontend is closing down the socket.
                                 * EOF means unexpected loss of frontend connection.
index 974e69a2f1aee9189c15f1ba12acf1ef69f0473b..2fb62f4abae36c605a152942377bbe2cbd354e02 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.57 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -222,11 +222,12 @@ CreateNewPortal(void)
  * PortalDefineQuery
  *             A simple subroutine to establish a portal's query.
  *
- * Notes: the passed commandTag must be a pointer to a constant string,
- * since it is not copied.  The caller is responsible for ensuring that
- * the passed sourceText (if any), parse and plan trees have adequate
- * lifetime.  Also, queryContext must accurately describe the location
- * of the parse and plan trees.
+ * Notes: commandTag shall be NULL if and only if the original query string
+ * (before rewriting) was an empty string.  Also, the passed commandTag must
+ * be a pointer to a constant string, since it is not copied.  The caller is
+ * responsible for ensuring that the passed sourceText (if any), parse and
+ * plan trees have adequate lifetime.  Also, queryContext must accurately
+ * describe the location of the parse and plan trees.
  */
 void
 PortalDefineQuery(Portal portal,
@@ -241,6 +242,8 @@ PortalDefineQuery(Portal portal,
 
        Assert(length(parseTrees) == length(planTrees));
 
+       Assert(commandTag != NULL || parseTrees == NIL);
+
        portal->sourceText = sourceText;
        portal->commandTag = commandTag;
        portal->parseTrees = parseTrees;
index 728b0c8990600ce3ceca2de2ce09f353607ef255..688a75cd2db48b340c050472309e3d3660428aac 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: printtup.h,v 1.23 2003/01/21 22:06:12 tgl Exp $
+ * $Id: printtup.h,v 1.24 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,9 @@
 
 #include "tcop/dest.h"
 
-extern DestReceiver *printtup_create_DR(bool isBinary);
+extern DestReceiver *printtup_create_DR(bool isBinary, bool sendDescrip);
+
+extern void SendRowDescriptionMessage(TupleDesc typeinfo);
 
 extern void debugSetup(DestReceiver *self, int operation,
                   const char *portalName, TupleDesc typeinfo);
index efa60869fa68e27c8479d3f761945ec1ef98ae6e..f89ac36fa575957b8cd3f6ed8cbeb1895a2739a7 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $
+ * $Id: portalcmds.h,v 1.9 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,7 +22,7 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
 extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
                                                           char *completionTag);
 
-extern void PerformPortalClose(char *name);
+extern void PerformPortalClose(const char *name);
 
 extern void PortalCleanup(Portal portal, bool isError);
 
index aad641667574af69ca62595c74c4ee3cc0013ff8..5d24c579a4034472c920b77434b3d5a36903070c 100644 (file)
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * prepare.h
- *       PREPARE, EXECUTE and DEALLOCATE command prototypes
+ *       PREPARE, EXECUTE and DEALLOCATE commands, and prepared-stmt storage
  *
  *
- * Copyright (c) 2002, PostgreSQL Global Development Group
+ * Copyright (c) 2002-2003, PostgreSQL Global Development Group
  *
- * $Id: prepare.h,v 1.3 2003/02/02 23:46:38 tgl Exp $
+ * $Id: prepare.h,v 1.4 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "tcop/dest.h"
 
 
+/*
+ * The data structure representing a prepared statement
+ *
+ * Note: all subsidiary storage lives in the context denoted by the context
+ * field.  However, the string referenced by commandTag is not subsidiary
+ * storage; it is assumed to be a compile-time-constant string.  As with
+ * portals, commandTag shall be NULL if and only if the original query string
+ * (before rewriting) was an empty string.
+ */
+typedef struct
+{
+       /* dynahash.c requires key to be first field */
+       char            stmt_name[NAMEDATALEN];
+       char       *query_string;       /* text of query, or NULL */
+       const char *commandTag;         /* command tag (a constant!), or NULL */
+       List       *query_list;         /* list of queries */
+       List       *plan_list;          /* list of plans */
+       List       *argtype_list;       /* list of parameter type OIDs */
+       MemoryContext context;          /* context containing this query */
+} PreparedStatement;
+
+
+/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
 extern void PrepareQuery(PrepareStmt *stmt);
 extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
 extern void DeallocateQuery(DeallocateStmt *stmt);
-extern List *FetchQueryParams(const char *plan_name);
 extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate);
 
+/* Low-level access to stored prepared statements */
+extern void StorePreparedStatement(const char *stmt_name,
+                                                                  const char *query_string,
+                                                                  const char *commandTag,
+                                                                  List *query_list,
+                                                                  List *plan_list,
+                                                                  List *argtype_list);
+extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
+                                                                                                bool throwError);
+extern void DropPreparedStatement(const char *stmt_name, bool showError);
+extern List *FetchPreparedStatementParams(const char *stmt_name);
+
 #endif   /* PREPARE_H */
index d5374a68fe6d53edc0f5415194760165765d1160..b6961e824cd8627fff65c282563ac34e1915f2f9 100644 (file)
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pqcomm.h,v 1.81 2003/04/26 20:22:59 tgl Exp $
+ * $Id: pqcomm.h,v 1.82 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,7 +106,7 @@ typedef union SockAddr
 /* The earliest and latest frontend/backend protocol version supported. */
 
 #define PG_PROTOCOL_EARLIEST   PG_PROTOCOL(1,0)
-#define PG_PROTOCOL_LATEST             PG_PROTOCOL(3,105) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST             PG_PROTOCOL(3,106) /* XXX temporary value */
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
 
index 39063af6e16e1d251b76e41e7643f89e17d3e03b..5fbe9d33afebd39d890af3952da8dc8637d18521 100644 (file)
@@ -44,7 +44,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: dest.h,v 1.34 2003/04/19 00:02:30 tgl Exp $
+ * $Id: dest.h,v 1.35 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /* ----------------
  *             CommandDest is a simplistic means of identifying the desired
  *             destination.  Someday this will probably need to be improved.
+ *
+ * Note: only the values None, Debug, Remote are legal for the global
+ * variable whereToSendOutput.  The other values may be selected
+ * as the destination for individual commands.
  * ----------------
  */
 typedef enum
@@ -71,7 +75,9 @@ typedef enum
        RemoteInternal,                         /* results sent to frontend process in
                                                                 * internal (binary) form */
        SPI,                                            /* results sent to SPI manager */
-       Tuplestore                                      /* results sent to Tuplestore */
+       Tuplestore,                                     /* results sent to Tuplestore */
+       RemoteExecute,                          /* sent to frontend, in Execute command */
+       RemoteExecuteInternal           /* same, but binary format */
 } CommandDest;
 
 /* ----------------
index c1fa9c1a6d737a27a7ff0d9006c3f89e5334a164..b5e171e1d36b9f98c69377ccd7162ecfc6c4e693 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $
+ * $Id: tcopprot.h,v 1.57 2003/05/05 00:44:56 tgl Exp $
  *
  * OLD COMMENTS
  *       This file was created so that other c files could get the two
@@ -35,11 +35,12 @@ extern DLLIMPORT const char *debug_query_string;
 
 #ifndef BOOTSTRAP_INCLUDE
 
+extern List *pg_parse_and_rewrite(const char *query_string,
+                                        Oid *paramTypes, int numParams);
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree,
                                                                        Oid *paramTypes, int numParams);
-extern List *pg_parse_and_rewrite(const char *query_string,
-                                        Oid *paramTypes, int numParams);
+extern List *pg_rewrite_queries(List *querytree_list);
 extern Plan *pg_plan_query(Query *querytree);
 extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
 
index 3bf27da42784effdf4800eafe90c6c6fb5980202..75bcb43433b464b1ce0c3d54f59579e4c59791b9 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.239 2003/04/28 04:52:13 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.240 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -375,6 +375,17 @@ connectOptions1(PGconn *conn, const char *conninfo)
 static bool
 connectOptions2(PGconn *conn)
 {
+       /*
+        * If database name was not given, default it to equal user name
+        */
+       if ((conn->dbName == NULL || conn->dbName[0] == '\0')
+               && conn->pguser != NULL)
+       {
+               if (conn->dbName)
+                       free(conn->dbName);
+               conn->dbName = strdup(conn->pguser);
+       }
+
        /*
         * Supply default password if none given
         */
index a5e6bceef4216315b922b0f443b1faa1f0082100..3fcecd63e23cf9a48cc99d0d2677906f76ac3e15 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.66 2003/04/26 20:23:00 tgl Exp $
+ * $Id: libpq-int.h,v 1.67 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,7 +56,7 @@ typedef int ssize_t;                  /* ssize_t doesn't exist in VC (atleast
  * pqcomm.h describe what the backend knows, not what libpq knows.
  */
 
-#define PG_PROTOCOL_LIBPQ      PG_PROTOCOL(3,105) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ      PG_PROTOCOL(3,106) /* XXX temporary value */
 
 /*
  * POSTGRES backend dependent Constants.