Another round of protocol changes. Backend-to-frontend messages now all
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 22 Apr 2003 00:08:07 +0000 (00:08 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 22 Apr 2003 00:08:07 +0000 (00:08 +0000)
have length words.  COPY OUT reimplemented per new protocol: it doesn't
need \. anymore, thank goodness.  COPY BINARY to/from frontend works,
at least as far as the backend is concerned --- libpq's PQgetline API
is not up to snuff, and will have to be replaced with something that is
null-safe.  libpq uses message length words for performance improvement
(no cycles wasted rescanning long messages), but not yet for error
recovery.

22 files changed:
doc/src/sgml/libpq.sgml
doc/src/sgml/protocol.sgml
src/backend/access/common/printtup.c
src/backend/commands/async.c
src/backend/commands/copy.c
src/backend/libpq/auth.c
src/backend/libpq/pqcomm.c
src/backend/libpq/pqformat.c
src/backend/postmaster/postmaster.c
src/backend/tcop/dest.c
src/backend/tcop/fastpath.c
src/backend/tcop/postgres.c
src/backend/utils/error/elog.c
src/include/libpq/pqcomm.h
src/include/libpq/pqformat.h
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/fe-misc.c
src/interfaces/libpq/libpq-int.h
src/test/regress/expected/alter_table.out
src/test/regress/expected/copy2.out
src/test/regress/expected/domain.out

index 7a7bb48ff390d26a8ffa25b6ad5e4c76807b55e1..5ba7e6468c313c81085002e0555cde8d2bb41886 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.119 2003/04/19 00:02:29 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.120 2003/04/22 00:08:06 tgl Exp $
 -->
 
  <chapter id="libpq">
@@ -175,7 +175,8 @@ PGconn *PQconnectdb(const char *conninfo);
      <term><literal>connect_timeout</literal></term>
      <listitem>
      <para>
-      Time space in seconds given to connection function. Zero or not set means infinite.
+      Maximum wait for connection, in seconds (write as a decimal integer
+      string). Zero or not specified means infinite.
      </para>
      </listitem>
     </varlistentry>
@@ -184,7 +185,7 @@ PGconn *PQconnectdb(const char *conninfo);
      <term><literal>options</literal></term>
      <listitem>
       <para>
-       Configuration options to be sent to the server.
+       Command-line options to be sent to the server.
       </para>
      </listitem>
     </varlistentry>
@@ -252,8 +253,9 @@ PGconn *PQsetdbLogin(const char *pghost,
 </para>
 
 <para>
-   This is the predecessor of <function>PQconnectdb</function> with a fixed number
-   of parameters but the same functionality.   
+   This is the predecessor of <function>PQconnectdb</function> with a fixed
+   number of parameters.  It has the same functionality except that the
+   missing parameters cannot be specified in the call.
    </para>
   </listitem>
  </varlistentry>
@@ -274,8 +276,8 @@ PGconn *PQsetdb(char *pghost,
 
 <para>
    This is a macro that calls <function>PQsetdbLogin</function> with null pointers
-   for the <parameter>login</> and <parameter>pwd</> parameters.  It is provided primarily
-   for backward compatibility with old programs.
+   for the <parameter>login</> and <parameter>pwd</> parameters.  It is provided
+   for backward compatibility with very old programs.
    </para>
   </listitem>
  </varlistentry>
@@ -454,7 +456,7 @@ switch(PQstatus(conn))
   </para>
 
   <para>
-   Finally, these functions leave the socket in a nonblocking state as if 
+   Finally, these functions leave the connection in a nonblocking state as if 
    <function>PQsetnonblocking</function> had been called.
   </para>
   </listitem>
@@ -486,8 +488,6 @@ typedef struct
 </para>
 
 <para>
-   converts an escaped string representation of binary data into binary
-   data --- the reverse of <function>PQescapeBytea</function>.
    Returns a connection options array.  This may
    be used to determine all possible <function>PQconnectdb</function> options and their
    current default values.  The return value points to an array of
@@ -683,7 +683,7 @@ char *PQtty(const PGconn *conn);
 <term><function>PQoptions</function></term>
 <listitem>
 <para>
-       Returns the configuration options passed in the connection request.
+       Returns the command-line options passed in the connection request.
 <synopsis>
 char *PQoptions(const PGconn *conn);
 </synopsis>
@@ -2047,13 +2047,13 @@ contains example functions that correctly handle the <command>COPY</command> pro
 <term><function>PQgetlineAsync</function></term>
 <listitem>
 <para>
-          Reads  a  newline-terminated  line  of  characters
+          Reads a row of COPY data
           (transmitted  by the server) into a buffer
           without blocking.
 <synopsis>
 int PQgetlineAsync(PGconn *conn,
                    char *buffer,
-                   int length);
+                   int bufsize);
 </synopsis>
 </para>
 
@@ -2070,24 +2070,27 @@ end-of-data signal is detected.
 <para>
 Unlike <function>PQgetline</function>, this function takes
 responsibility for detecting end-of-data.
-On each call, <function>PQgetlineAsync</function> will return data if a complete newline-
-terminated data line is available in <application>libpq</>'s input buffer, or if the
-incoming data line is too long to fit in the buffer offered by the caller.
-Otherwise, no data is returned until the rest of the line arrives.
+</para>
+<para>
+On each call, <function>PQgetlineAsync</function> will return data if a
+complete data row is available in <application>libpq</>'s input buffer.
+Otherwise, no data is returned until the rest of the row arrives.
 The function returns -1 if the end-of-copy-data marker has been recognized,
 or 0 if no data is available, or a positive number giving the number of
 bytes of data returned.  If -1 is returned, the caller must next call
 <function>PQendcopy</function>, and then return to normal processing.
 </para>
 <para>
-The data returned will not extend beyond a newline character.  If possible
-a whole line will be returned at one time.  But if the buffer offered by
-the caller is too small to hold a line sent by the server, then a partial
-data line will be returned.  This can be detected by testing whether the
-last returned byte is <literal>\n</literal> or not.
+The data returned will not extend beyond a data-row boundary.  If possible
+a whole row will be returned at one time.  But if the buffer offered by
+the caller is too small to hold a row sent by the server, then a partial
+data row will be returned.  With textual data this can be detected by testing
+whether the last returned byte is <literal>\n</literal> or not.  (In a binary
+COPY, actual parsing of the COPY data format will be needed to make the
+equivalent determination.)
 The returned string is not null-terminated.  (If you want to add a
-terminating null, be sure to pass a <parameter>length</parameter> one smaller than the room
-actually available.)
+terminating null, be sure to pass a <parameter>bufsize</parameter> one smaller
+than the room actually available.)
 </para>
 </listitem>
 </varlistentry>
@@ -2105,10 +2108,24 @@ int PQputline(PGconn *conn,
 </para>
 
 <para>
-Note the application must explicitly  send  the  two
-characters  <literal>\.</literal> on a final line  to indicate to
-the server that it has finished sending its data.
+The COPY datastream sent by a series of calls to
+<function>PQputline</function> has the same format as that returned by
+<function>PQgetlineAsync</function>, except that applications are not
+obliged to send exactly one data row per <function>PQputline</function>
+call; it is okay to send a partial line or multiple lines per call.
 </para>
+
+<note>
+<para>
+Before <productname>PostgreSQL</productname> 7.4, it was necessary for the
+application to explicitly send the two characters <literal>\.</literal> as a
+final line to indicate to the server that it had finished sending COPY data.
+While this still works, it is deprecated and the special meaning of
+<literal>\.</literal> can be expected to be removed in a future release.
+It is sufficient to call <function>PQendcopy</function> after having sent the
+actual data.
+</para>
+</note>
 </listitem>
 </varlistentry>
 
@@ -2126,9 +2143,9 @@ int PQputnbytes(PGconn *conn,
 </para>
 
 <para>
-This is exactly like <function>PQputline</function>, except that the data buffer need
-not be null-terminated since the number of bytes to send is
-specified directly.
+This is exactly like <function>PQputline</function>, except that the data
+buffer need not be null-terminated since the number of bytes to send is
+specified directly.  Use this procedure when sending binary data.
 </para>
 </listitem>
 </varlistentry>
@@ -2147,11 +2164,12 @@ int PQendcopy(PGconn *conn);
  sent  to  the  server using <function>PQputline</function> or when the
  last string has been  received  from  the  server
  using <function>PGgetline</function>.  It must be issued or the server
may get <quote>out of sync</quote> with  the client.   Upon
will get <quote>out of sync</quote> with  the client.   Upon
  return from this function, the server is ready to
  receive the next SQL command.
  The return value is 0  on  successful  completion,
- nonzero otherwise.
+ nonzero otherwise.  (Use <function>PQerrorMessage</function> to retrieve
+ details if the return value is nonzero.)
 </para>
 
 <para>
@@ -2187,7 +2205,6 @@ PQexec(conn, "COPY foo FROM STDIN;");
 PQputline(conn, "3\thello world\t4.5\n");
 PQputline(conn, "4\tgoodbye world\t7.11\n");
 ...
-PQputline(conn, "\\.\n");
 PQendcopy(conn);
 </programlisting>
 </para>
index 529baa1f31be37784e1b17dad8d805e27785b57c..52d2a60c3b2bc0169463cd587ca7e58ebaff41a0 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.28 2003/04/19 00:02:29 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.29 2003/04/22 00:08:06 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
@@ -3691,7 +3691,8 @@ Terminate (F)
 <para>
 This section describes the fields that may appear in ErrorResponse and
 NoticeResponse messages.  Each field type has a single-byte identification
-token.
+token.  Note that any given field type should appear at most once per
+message.
 </para>
 
 <VariableList>
@@ -3863,7 +3864,29 @@ PasswordMessage now has a type byte.
 
 <para>
 COPY data is now encapsulated into CopyData and CopyDone messages.  There
-is a well-defined way to recover from errors during COPY.
+is a well-defined way to recover from errors during COPY.  The special
+<quote><literal>\.</></quote> last line is not needed anymore, and is not sent
+during COPY OUT.
+(It is still recognized as a terminator during COPY IN, but its use is
+deprecated and will eventually be removed.)  Binary COPY is supported.
+The CopyInResponse and CopyOutResponse messages carry a field indicating
+whether the COPY operation is text or binary.
+</para>
+
+<para>
+The CursorResponse ('<literal>P</>') message is no longer generated by
+the backend.
+</para>
+
+<para>
+The NotificationResponse ('<literal>A</>') message has an additional string
+field, which is presently empty but may someday carry additional data passed
+from the NOTIFY event sender.
+</para>
+
+<para>
+The EmptyQueryResponse ('<literal>I</>') message used to include an empty
+string parameter; this has been removed.
 </para>
 
 <note>
index f1f96f18868e4f5a3e62f5e7da60290c9fea7db2..c88dedd93fd62697ae96e68b8bee444986155136 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.65 2002/09/04 20:31:08 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.66 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,15 +77,18 @@ static void
 printtup_setup(DestReceiver *self, int operation,
                           const char *portalName, TupleDesc typeinfo)
 {
-       /*
-        * Send portal name to frontend.
-        *
-        * If portal name not specified, use "blank" portal.
-        */
-       if (portalName == NULL)
-               portalName = "blank";
-
-       pq_puttextmessage('P', portalName);
+       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+       {
+               /*
+                * Send portal name to frontend (obsolete cruft, gone in proto 3.0)
+                *
+                * If portal name not specified, use "blank" portal.
+                */
+               if (portalName == NULL)
+                       portalName = "blank";
+
+               pq_puttextmessage('P', portalName);
+       }
 
        /*
         * if this is a retrieve, then we send back the tuple descriptor of
@@ -98,8 +101,7 @@ printtup_setup(DestReceiver *self, int operation,
                int                     i;
                StringInfoData buf;
 
-               pq_beginmessage(&buf);
-               pq_sendbyte(&buf, 'T'); /* tuple descriptor message type */
+               pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
                pq_sendint(&buf, natts, 2);             /* # of attrs in tuples */
 
                for (i = 0; i < natts; ++i)
@@ -174,8 +176,7 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
        /*
         * tell the frontend to expect new tuple data (in ASCII style)
         */
-       pq_beginmessage(&buf);
-       pq_sendbyte(&buf, 'D');
+       pq_beginmessage(&buf, 'D');
 
        /*
         * send a bitmap of which attributes are not null
@@ -388,8 +389,7 @@ printtup_internal(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
        /*
         * tell the frontend to expect new tuple data (in binary style)
         */
-       pq_beginmessage(&buf);
-       pq_sendbyte(&buf, 'B');
+       pq_beginmessage(&buf, 'B');
 
        /*
         * send a bitmap of which attributes are not null
index 169c4ce278c86f6489a3c9f610276708c10a3704..1d9fbf6580916998e52d4909fc64d9386085fdb5 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.92 2003/02/18 02:53:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.93 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -847,10 +847,14 @@ NotifyMyFrontEnd(char *relname, int32 listenerPID)
        {
                StringInfoData buf;
 
-               pq_beginmessage(&buf);
-               pq_sendbyte(&buf, 'A');
+               pq_beginmessage(&buf, 'A');
                pq_sendint(&buf, listenerPID, sizeof(int32));
                pq_sendstring(&buf, relname);
+               if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+               {
+                       /* XXX Add parameter string here later */
+                       pq_sendstring(&buf, "");
+               }
                pq_endmessage(&buf);
 
                /*
index 32e2362e99ba2b8e8c4ca363c0e61fb4b7a83c7a..40948e3a3b51c17df85a403ff5bbcf61a0d3a208 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.194 2003/04/19 20:36:03 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.195 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
 #define OCTVALUE(c) ((c) - '0')
 
-/* Default line termination */
-#ifndef WIN32
-#define PGEOL  "\n"
-#else
-#define PGEOL  "\r\n"
-#endif
-
 /*
  * Represents the different source/dest cases we need to worry about at
  * the bottom level
@@ -92,7 +85,7 @@ typedef enum EolType
 
 /* non-export function prototypes */
 static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
-                                  bool pipe, char *delim, char *null_print);
+                                  char *delim, char *null_print);
 static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
                                         char *delim, char *null_print);
 static Oid     GetInputFunction(Oid type);
@@ -101,8 +94,7 @@ static char *CopyReadAttribute(const char *delim, CopyReadResult *result);
 static void CopyAttributeOut(char *string, char *delim);
 static List *CopyGetAttnums(Relation rel, List *attnamelist);
 
-/* The trailing null is part of the signature */
-static const char BinarySignature[] = "PGBCOPY\n\377\r\n"; 
+static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
 
 /*
  * Static communication variables ... pretty grotty, but COPY has
@@ -135,10 +127,11 @@ static int        server_encoding;
  */
 static void SendCopyBegin(bool binary);
 static void ReceiveCopyBegin(bool binary);
-static void SendCopyEnd(bool binary, bool pipe);
+static void SendCopyEnd(bool binary);
 static void CopySendData(void *databuf, int datasize);
 static void CopySendString(const char *str);
 static void CopySendChar(char c);
+static void CopySendEndOfRow(bool binary);
 static void CopyGetData(void *databuf, int datasize);
 static int     CopyGetChar(void);
 #define CopyGetEof()  (fe_eof)
@@ -154,22 +147,32 @@ SendCopyBegin(bool binary)
 {
        if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
        {
-               pq_putbytes("H", 1);    /* new way */
-               /* XXX grottiness needed for old protocol */
-               pq_startcopyout();
+               /* new way */
+               StringInfoData buf;
+
+               pq_beginmessage(&buf, 'H');
+               pq_sendbyte(&buf, binary ? 1 : 0);
+               pq_endmessage(&buf);
                copy_dest = COPY_NEW_FE;
+               copy_msgbuf = makeStringInfo();
        }
        else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
        {
-               pq_putbytes("H", 1);    /* old way */
-               /* grottiness needed for old protocol */
+               /* old way */
+               if (binary)
+                       elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+               pq_putemptymessage('H');
+               /* grottiness needed for old COPY OUT protocol */
                pq_startcopyout();
                copy_dest = COPY_OLD_FE;
        }
        else
        {
-               pq_putbytes("B", 1);    /* very old way */
-               /* grottiness needed for old protocol */
+               /* very old way */
+               if (binary)
+                       elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+               pq_putemptymessage('B');
+               /* grottiness needed for old COPY OUT protocol */
                pq_startcopyout();
                copy_dest = COPY_OLD_FE;
        }
@@ -180,18 +183,29 @@ ReceiveCopyBegin(bool binary)
 {
        if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
        {
-               pq_putbytes("G", 1);    /* new way */
+               /* new way */
+               StringInfoData buf;
+
+               pq_beginmessage(&buf, 'G');
+               pq_sendbyte(&buf, binary ? 1 : 0);
+               pq_endmessage(&buf);
                copy_dest = COPY_NEW_FE;
                copy_msgbuf = makeStringInfo();
        }
        else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
        {
-               pq_putbytes("G", 1);    /* old way */
+               /* old way */
+               if (binary)
+                       elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+               pq_putemptymessage('G');
                copy_dest = COPY_OLD_FE;
        }
        else
        {
-               pq_putbytes("D", 1);    /* very old way */
+               /* very old way */
+               if (binary)
+                       elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+               pq_putemptymessage('D');
                copy_dest = COPY_OLD_FE;
        }
        /* We *must* flush here to ensure FE knows it can send. */
@@ -199,22 +213,39 @@ ReceiveCopyBegin(bool binary)
 }
 
 static void
-SendCopyEnd(bool binary, bool pipe)
+SendCopyEnd(bool binary)
 {
-       if (!binary)
+       if (copy_dest == COPY_NEW_FE)
        {
-               CopySendString("\\.");
-               CopySendString(!pipe ? PGEOL : "\n");
+               if (binary)
+               {
+                       /* Need to flush out file trailer word */
+                       CopySendEndOfRow(true);
+               }
+               else
+               {
+                       /* Shouldn't have any unsent data */
+                       Assert(copy_msgbuf->len == 0);
+               }
+               /* Send Copy Done message */
+               pq_putemptymessage('c');
+       }
+       else
+       {
+               /* The FE/BE protocol uses \n as newline for all platforms */
+               CopySendData("\\.\n", 3);
+               pq_endcopyout(false);
        }
-       pq_endcopyout(false);
 }
 
-/*
+/*----------
  * CopySendData sends output data to the destination (file or frontend)
  * CopySendString does the same for null-terminated strings
  * CopySendChar does the same for single characters
+ * CopySendEndOfRow does the appropriate thing at end of each data row
  *
  * NB: no data conversion is applied by these functions
+ *----------
  */
 static void
 CopySendData(void *databuf, int datasize)
@@ -228,12 +259,13 @@ CopySendData(void *databuf, int datasize)
                        break;
                case COPY_OLD_FE:
                        if (pq_putbytes((char *) databuf, datasize))
-                               fe_eof = true;
+                       {
+                               /* no hope of recovering connection sync, so FATAL */
+                               elog(FATAL, "CopySendData: connection lost");
+                       }
                        break;
                case COPY_NEW_FE:
-                       /* XXX fix later */
-                       if (pq_putbytes((char *) databuf, datasize))
-                               fe_eof = true;
+                       appendBinaryStringInfo(copy_msgbuf, (char *) databuf, datasize);
                        break;
        }
 }
@@ -250,6 +282,40 @@ CopySendChar(char c)
        CopySendData(&c, 1);
 }
 
+static void
+CopySendEndOfRow(bool binary)
+{
+       switch (copy_dest)
+       {
+               case COPY_FILE:
+                       if (!binary)
+                       {
+                               /* Default line termination depends on platform */
+#ifndef WIN32
+                               CopySendChar('\n');
+#else
+                               CopySendString("\r\n");
+#endif
+                       }
+                       break;
+               case COPY_OLD_FE:
+                       /* The FE/BE protocol uses \n as newline for all platforms */
+                       if (!binary)
+                               CopySendChar('\n');
+                       break;
+               case COPY_NEW_FE:
+                       /* The FE/BE protocol uses \n as newline for all platforms */
+                       if (!binary)
+                               CopySendChar('\n');
+                       /* Dump the accumulated row as one CopyData message */
+                       (void) pq_putmessage('d', copy_msgbuf->data, copy_msgbuf->len);
+                       /* Reset copy_msgbuf to empty */
+                       copy_msgbuf->len = 0;
+                       copy_msgbuf->data[0] = '\0';
+                       break;
+       }
+}
+
 /*
  * CopyGetData reads data from the source (file or frontend)
  * CopyGetChar does the same for single characters
@@ -568,13 +634,6 @@ DoCopy(const CopyStmt *stmt)
                         "directly to or from a file.  Anyone can COPY to stdout or "
                         "from stdin.  Psql's \\copy command also works for anyone.");
 
-       /*
-        * This restriction is unfortunate, but necessary until the frontend
-        * COPY protocol is redesigned to be binary-safe...
-        */
-       if (pipe && binary)
-               elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
-
        /*
         * Presently, only single-character delimiter strings are supported.
         */
@@ -698,13 +757,13 @@ DoCopy(const CopyStmt *stmt)
                                elog(ERROR, "COPY: %s is a directory", filename);
                        }
                }
-               CopyTo(rel, attnumlist, binary, oids, pipe, delim, null_print);
+               CopyTo(rel, attnumlist, binary, oids, delim, null_print);
        }
 
        if (!pipe)
                FreeFile(copy_file);
        else if (IsUnderPostmaster && !is_from)
-               SendCopyEnd(binary, pipe);
+               SendCopyEnd(binary);
        pfree(attribute_buf.data);
 
        /*
@@ -721,7 +780,7 @@ DoCopy(const CopyStmt *stmt)
  * Copy from relation TO file.
  */
 static void
-CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
+CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print)
 {
        HeapTuple       tuple;
@@ -786,7 +845,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
                int32           tmp;
 
                /* Signature */
-               CopySendData((char *) BinarySignature, sizeof(BinarySignature));
+               CopySendData((char *) BinarySignature, 12);
                /* Integer layout field */
                tmp = 0x01020304;
                CopySendData(&tmp, sizeof(int32));
@@ -918,8 +977,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
                        }
                }
 
-               if (!binary)
-                       CopySendString(!pipe ? PGEOL : "\n");
+               CopySendEndOfRow(binary);
 
                MemoryContextSwitchTo(oldcontext);
        }
@@ -1100,8 +1158,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 
                /* Signature */
                CopyGetData(readSig, 12);
-               if (CopyGetEof() || memcmp(readSig, BinarySignature,
-                                                                  sizeof(BinarySignature)) != 0)
+               if (CopyGetEof() || memcmp(readSig, BinarySignature, 12) != 0)
                        elog(ERROR, "COPY BINARY: file signature not recognized");
                /* Integer layout field */
                CopyGetData(&tmp, sizeof(int32));
index a5dc8eff2da02191773fc3439a6f60fa7c6cd83e..2edc919c6d25e19a3c338d1a5d8e10a9d5df9a03 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.99 2003/04/19 00:02:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.100 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -512,8 +512,7 @@ sendAuthRequest(Port *port, AuthRequest areq)
 {
        StringInfoData buf;
 
-       pq_beginmessage(&buf);
-       pq_sendbyte(&buf, 'R');
+       pq_beginmessage(&buf, 'R');
        pq_sendint(&buf, (int32) areq, sizeof(int32));
 
        /* Add the salt for encrypted passwords. */
index 9a4f51b7786939355d45e123ecbe1df133df4a3c..2cf2a36b7b3ca6dd86f78f9403af9a339785c041 100644 (file)
  * No other messages can be sent while COPY OUT is in progress; and if the
  * copy is aborted by an elog(ERROR), we need to close out the copy so that
  * the frontend gets back into sync.  Therefore, these routines have to be
- * aware of COPY OUT state.
+ * aware of COPY OUT state.  (New COPY-OUT is message-based and does *not*
+ * set the DoingCopyOut flag.)
  *
  * NOTE: generally, it's a bad idea to emit outgoing messages directly with
  * pq_putbytes(), especially if the message would require multiple calls
  * to send.  Instead, use the routines in pqformat.c to construct the message
- * in a buffer and then emit it in one call to pq_putmessage.  This helps
- * ensure that the channel will not be clogged by an incomplete message
- * if execution is aborted by elog(ERROR) partway through the message.
- * The only non-libpq code that should call pq_putbytes directly is COPY OUT.
+ * in a buffer and then emit it in one call to pq_putmessage.  This ensures
+ * that the channel will not be clogged by an incomplete message if execution
+ * is aborted by elog(ERROR) partway through the message.  The only non-libpq
+ * code that should call pq_putbytes directly is old-style COPY OUT.
  *
  * At one time, libpq was shared between frontend and backend, but now
  * the backend's "backend/libpq" is quite separate from "interfaces/libpq".
@@ -29,7 +30,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/libpq/pqcomm.c,v 1.150 2003/04/19 00:02:29 tgl Exp $
+ *     $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.151 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -846,13 +847,17 @@ pq_flush(void)
  *             pq_putmessage   - send a normal message (suppressed in COPY OUT mode)
  *
  *             If msgtype is not '\0', it is a message type code to place before
- *             the message body (len counts only the body size!).
- *             If msgtype is '\0', then the buffer already includes the type code.
+ *             the message body.  If msgtype is '\0', then the message has no type
+ *             code (this is only valid in pre-3.0 protocols).
  *
- *             All normal messages are suppressed while COPY OUT is in progress.
- *             (In practice only a few messages might get emitted then; dropping
- *             them is annoying, but at least they will still appear in the
- *             postmaster log.)
+ *             len is the length of the message body data at *s.  In protocol 3.0
+ *             and later, a message length word (equal to len+4 because it counts
+ *             itself too) is inserted by this routine.
+ *
+ *             All normal messages are suppressed while old-style COPY OUT is in
+ *             progress.  (In practice only a few notice messages might get emitted
+ *             then; dropping them is annoying, but at least they will still appear
+ *             in the postmaster log.)
  *
  *             returns 0 if OK, EOF if trouble
  * --------------------------------
@@ -865,6 +870,14 @@ pq_putmessage(char msgtype, const char *s, size_t len)
        if (msgtype)
                if (pq_putbytes(&msgtype, 1))
                        return EOF;
+       if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+       {
+               uint32          n32;
+
+               n32 = htonl((uint32) (len + 4));
+               if (pq_putbytes((char *) &n32, 4))
+                       return EOF;
+       }
        return pq_putbytes(s, len);
 }
 
@@ -880,12 +893,13 @@ pq_startcopyout(void)
 }
 
 /* --------------------------------
- *             pq_endcopyout   - end a COPY OUT transfer
+ *             pq_endcopyout   - end an old-style COPY OUT transfer
  *
  *             If errorAbort is indicated, we are aborting a COPY OUT due to an error,
  *             and must send a terminator line.  Since a partial data line might have
  *             been emitted, send a couple of newlines first (the first one could
- *             get absorbed by a backslash...)
+ *             get absorbed by a backslash...)  Note that old-style COPY OUT does
+ *             not allow binary transfers, so a textual terminator is always correct.
  * --------------------------------
  */
 void
@@ -893,8 +907,8 @@ pq_endcopyout(bool errorAbort)
 {
        if (!DoingCopyOut)
                return;
+       DoingCopyOut = false;
        if (errorAbort)
                pq_putbytes("\n\n\\.\n", 5);
        /* in non-error case, copy.c will have emitted the terminator line */
-       DoingCopyOut = false;
 }
index 80ca3190999c5f5eb30f1b4f810ae4dbb2dce9ec..dacfa93ecc7c0f2bef6a8d18e929df7ef122c6d1 100644 (file)
@@ -18,7 +18,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/libpq/pqformat.c,v 1.27 2003/04/19 00:02:29 tgl Exp $
+ *     $Header: /cvsroot/pgsql/src/backend/libpq/pqformat.c,v 1.28 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@
  *
  * Special-case message output:
  *             pq_puttextmessage - generate a character set-converted message in one step
+ *             pq_putemptymessage - convenience routine for message with empty body
  *
  * Message parsing after input:
  *             pq_getmsgbyte   - get a raw byte from a message buffer
 #include "mb/pg_wchar.h"
 
 
+/* --------------------------------
+ *             pq_beginmessage         - initialize for sending a message
+ * --------------------------------
+ */
+void
+pq_beginmessage(StringInfo buf, char msgtype)
+{
+       initStringInfo(buf);
+       /*
+        * We stash the message type into the buffer's cursor field, expecting
+        * that the pq_sendXXX routines won't touch it.  We could alternatively
+        * make it the first byte of the buffer contents, but this seems easier.
+        */
+       buf->cursor = msgtype;
+}
+
 /* --------------------------------
  *             pq_sendbyte             - append a raw byte to a StringInfo buffer
  * --------------------------------
@@ -176,7 +193,8 @@ pq_sendint(StringInfo buf, int i, int b)
 void
 pq_endmessage(StringInfo buf)
 {
-       (void) pq_putmessage('\0', buf->data, buf->len);
+       /* msgtype was saved in cursor field */
+       (void) pq_putmessage(buf->cursor, buf->data, buf->len);
        /* no need to complain about any failure, since pqcomm.c already did */
        pfree(buf->data);
        buf->data = NULL;
@@ -188,11 +206,9 @@ pq_endmessage(StringInfo buf)
  *             This is the same as the pqcomm.c routine pq_putmessage, except that
  *             the message body is a null-terminated string to which encoding
  *             conversion applies.
- *
- *             returns 0 if OK, EOF if trouble
  * --------------------------------
  */
-int
+void
 pq_puttextmessage(char msgtype, const char *str)
 {
        int                     slen = strlen(str);
@@ -201,12 +217,22 @@ pq_puttextmessage(char msgtype, const char *str)
        p = (char *) pg_server_to_client((unsigned char *) str, slen);
        if (p != str)                           /* actual conversion has been done? */
        {
-               int                     result = pq_putmessage(msgtype, p, strlen(p) + 1);
-
+               (void) pq_putmessage(msgtype, p, strlen(p) + 1);
                pfree(p);
-               return result;
+               return;
        }
-       return pq_putmessage(msgtype, str, slen + 1);
+       (void) pq_putmessage(msgtype, str, slen + 1);
+}
+
+
+/* --------------------------------
+ *             pq_putemptymessage - convenience routine for message with empty body
+ * --------------------------------
+ */
+void
+pq_putemptymessage(char msgtype)
+{
+       (void) pq_putmessage(msgtype, NULL, 0);
 }
 
 
index d6beb0fc1a6262a133518fd009bc2ef407656bf2..834b03ab6284bfdbb24c5915139220d6680d6b3f 100644 (file)
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.313 2003/04/19 00:02:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.314 2003/04/22 00:08:06 tgl Exp $
  *
  * NOTES
  *
@@ -1118,7 +1118,13 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
        if (pq_getbytes((char *) &len, 4) == EOF)
        {
-               elog(COMMERROR, "incomplete startup packet");
+               /*
+                * EOF after SSLdone probably means the client didn't like our
+                * response to NEGOTIATE_SSL_CODE.  That's not an error condition,
+                * so don't clutter the log with a complaint.
+                */
+               if (!SSLdone)
+                       elog(COMMERROR, "incomplete startup packet");
                return STATUS_ERROR;
        }
 
@@ -1127,7 +1133,10 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
        if (len < (int32) sizeof(ProtocolVersion) ||
                len > MAX_STARTUP_PACKET_LENGTH)
-               elog(FATAL, "invalid length of startup packet");
+       {
+               elog(COMMERROR, "invalid length of startup packet");
+               return STATUS_ERROR;
+       }
 
        /*
         * Allocate at least the size of an old-style startup packet, plus one
@@ -1173,7 +1182,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 #endif
                if (send(port->sock, &SSLok, 1, 0) != 1)
                {
-                       elog(LOG, "failed to send SSL negotiation response: %m");
+                       elog(COMMERROR, "failed to send SSL negotiation response: %m");
                        return STATUS_ERROR;    /* close the connection */
                }
 
@@ -1188,6 +1197,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
        /* Could add additional special packet types here */
 
+       /*
+        * Set FrontendProtocol now so that elog() knows what format to send
+        * if we fail during startup.
+        */
+       FrontendProtocol = proto;
 
        /*
         * XXX temporary for 3.0 protocol development: we are using the minor
index 07e4614e799be2c030a32320d49c07d2e19cc496..5ccaa60995c5ae39a4693c4a8ae877eab5ff13c1 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.52 2003/04/19 00:02:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.53 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -141,7 +141,9 @@ EndCommand(const char *commandTag, CommandDest dest)
  *             libpq's crufty way of determining whether a multiple-command
  *             query string is done.  In protocol 2.0 it's probably not really
  *             necessary to distinguish empty queries anymore, but we still do it
- *             for backwards compatibility with 1.0.
+ *             for backwards compatibility with 1.0.  In protocol 3.0 it has some
+ *             use again, since it ensures that there will be a recognizable end
+ *             to the response to an Execute message.
  * ----------------
  */
 void
@@ -153,9 +155,13 @@ NullCommand(CommandDest dest)
                case Remote:
 
                        /*
-                        * tell the fe that we saw an empty query string
+                        * tell the fe that we saw an empty query string.  In protocols
+                        * before 3.0 this has a useless empty-string message body.
                         */
-                       pq_putbytes("I", 2);    /* note we send I and \0 */
+                       if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+                               pq_putemptymessage('I');
+                       else
+                               pq_puttextmessage('I', "");
                        break;
 
                case Debug:
@@ -184,7 +190,7 @@ ReadyForQuery(CommandDest dest)
                case RemoteInternal:
                case Remote:
                        if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
-                               pq_putbytes("Z", 1);
+                               pq_putemptymessage('Z');
                        /* Flush output at end of cycle in any case. */
                        pq_flush();
                        break;
index eeddea6f6eb8cb82c6dc63f6e3754bbe20f4b983..b87509573497abb51715cac3a9e0cf3cd3bcd4fe 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.58 2003/04/19 00:02:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.59 2003/04/22 00:08:07 tgl Exp $
  *
  * NOTES
  *       This cruft is the server side of PQfn.
@@ -119,8 +119,7 @@ SendFunctionResult(Datum retval, bool retbyval, int retlen)
 {
        StringInfoData buf;
 
-       pq_beginmessage(&buf);
-       pq_sendbyte(&buf, 'V');
+       pq_beginmessage(&buf, 'V');
 
        if (retlen != 0)
        {
index fcc6591f7c017fa12aa4f9b394280618b6c26a5b..5c51a1056a266b582042a7114c9c51403a2d039e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.322 2003/04/19 00:02:29 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.323 2003/04/22 00:08:07 tgl Exp $
  *
  * NOTES
  *       this is the "main" module of the postgres backend and
@@ -1821,8 +1821,7 @@ PostgresMain(int argc, char *argv[], const char *username)
        {
                StringInfoData buf;
 
-               pq_beginmessage(&buf);
-               pq_sendbyte(&buf, 'K');
+               pq_beginmessage(&buf, 'K');
                pq_sendint(&buf, (int32) MyProcPid, sizeof(int32));
                pq_sendint(&buf, (int32) MyCancelKey, sizeof(int32));
                pq_endmessage(&buf);
@@ -1832,7 +1831,7 @@ PostgresMain(int argc, char *argv[], const char *username)
        if (!IsUnderPostmaster)
        {
                puts("\nPOSTGRES backend interactive interface ");
-               puts("$Revision: 1.322 $ $Date: 2003/04/19 00:02:29 $\n");
+               puts("$Revision: 1.323 $ $Date: 2003/04/22 00:08:07 $\n");
        }
 
        /*
index 763024b5773905f5d43124e0acbaff24f529ebee..01250f9a2f06495998b119783ab2e3b46f9244ba 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.107 2003/03/20 03:34:56 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.108 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -406,20 +406,19 @@ elog(int lev, const char *fmt,...)
                 */
                oldcxt = MemoryContextSwitchTo(ErrorContext);
 
-               if (lev <= WARNING)
-                       /* exclude the timestamp from msg sent to frontend */
-                       send_message_to_frontend(lev, msg_buf + timestamp_size);
-               else
+               if (lev >= ERROR)
                {
                        /*
                         * Abort any COPY OUT in progress when an error is detected.
-                        * This hack is necessary because of poor design of copy
-                        * protocol.
+                        * This hack is necessary because of poor design of old-style
+                        * copy protocol.
                         */
                        pq_endcopyout(true);
-                       send_message_to_frontend(ERROR, msg_buf + timestamp_size);
                }
 
+               /* Exclude the timestamp from msg sent to frontend */
+               send_message_to_frontend(lev, msg_buf + timestamp_size);
+
                MemoryContextSwitchTo(oldcxt);
        }
 
@@ -745,11 +744,9 @@ send_message_to_frontend(int type, const char *msg)
 {
        StringInfoData buf;
 
-       AssertArg(type <= ERROR);
-
-       pq_beginmessage(&buf);
        /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-       pq_sendbyte(&buf, type < ERROR ? 'N' : 'E');
+       pq_beginmessage(&buf, (type < ERROR) ? 'N' : 'E');
+       /* XXX more to do here */
        pq_sendstring(&buf, msg);
        pq_endmessage(&buf);
 
index 61aa695e27217356e9fdc99a1647c1b3f216cda5..420f1e438e328b9d024eed5ebe92c0486217dc30 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.77 2003/04/19 00:02:29 tgl Exp $
+ * $Id: pqcomm.h,v 1.78 2003/04/22 00:08:07 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,101) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST             PG_PROTOCOL(3,102) /* XXX temporary value */
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
 
index cb80ec2c2014dbee579aa3c4e223e0831eea8077..229de38c9b36d3e6213235bea6ee178d37f8cd15 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
  *
- * $Id: pqformat.h,v 1.14 2003/04/19 00:02:29 tgl Exp $
+ * $Id: pqformat.h,v 1.15 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,8 +15,7 @@
 
 #include "lib/stringinfo.h"
 
-#define pq_beginmessage(buf)  initStringInfo(buf)
-
+extern void pq_beginmessage(StringInfo buf, char msgtype);
 extern void pq_sendbyte(StringInfo buf, int byt);
 extern void pq_sendbytes(StringInfo buf, const char *data, int datalen);
 extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen);
@@ -24,7 +23,8 @@ extern void pq_sendstring(StringInfo buf, const char *str);
 extern void pq_sendint(StringInfo buf, int i, int b);
 extern void pq_endmessage(StringInfo buf);
 
-extern int     pq_puttextmessage(char msgtype, const char *str);
+extern void pq_puttextmessage(char msgtype, const char *str);
+extern void pq_putemptymessage(char msgtype);
 
 extern int     pq_getmsgbyte(StringInfo msg);
 extern unsigned int pq_getmsgint(StringInfo msg, int b);
index a322d8a73d132da84a37f51b043861ade6f7a9d5..086462094ff2c65a70b3cc9ead27b40ddb6a7d2e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.233 2003/04/19 00:02:30 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.234 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1328,6 +1328,8 @@ keep_going:                                               /* We will come back to here until there
                case CONNECTION_AWAITING_RESPONSE:
                        {
                                char            beresp;
+                               int                     msgLength;
+                               int                     avail;
                                AuthRequest areq;
 
                                /*
@@ -1337,15 +1339,58 @@ keep_going:                                             /* We will come back to here until there
                                 */
                                conn->inCursor = conn->inStart;
 
+                               /* Read type byte */
                                if (pqGetc(&beresp, conn))
                                {
                                        /* We'll come back when there is more data */
                                        return PGRES_POLLING_READING;
                                }
 
-                               /* Handle errors. */
-                               if (beresp == 'E')
+                               /*
+                                * Validate message type: we expect only an authentication
+                                * request or an error here.  Anything else probably means
+                                * it's not Postgres on the other end at all.
+                                */
+                               if (!(beresp == 'R' || beresp == 'E'))
+                               {
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext(
+                                                                 "expected authentication request from "
+                                                                                         "server, but received %c\n"
+                                                                                                       ),
+                                                                         beresp);
+                                       goto error_return;
+                               }
+
+                               /* Read message length word */
+                               if (pqGetInt(&msgLength, 4, conn))
+                               {
+                                       /* We'll come back when there is more data */
+                                       return PGRES_POLLING_READING;
+                               }
+
+                               /*
+                                * Try to validate message length before using it.
+                                * Authentication requests can't be very large.  Errors
+                                * can be a little larger, but not huge.  If we see a large
+                                * apparent length in an error, it means we're really talking
+                                * to a pre-3.0-protocol server; cope.
+                                */
+                               if (beresp == 'R' && (msgLength < 8 || msgLength > 100))
+                               {
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext(
+                                                                 "expected authentication request from "
+                                                                                         "server, but received %c\n"
+                                                                                                       ),
+                                                                         beresp);
+                                       goto error_return;
+                               }
+
+                               if (beresp == 'E' && (msgLength < 8 || msgLength > 30000))
                                {
+                                       /* Handle error from a pre-3.0 server */
+                                       conn->inCursor = conn->inStart + 1;     /* reread data */
                                        if (pqGets(&conn->errorMessage, conn))
                                        {
                                                /* We'll come back when there is more data */
@@ -1363,18 +1408,45 @@ keep_going:                                             /* We will come back to here until there
                                        goto error_return;
                                }
 
-                               /* Otherwise it should be an authentication request. */
-                               if (beresp != 'R')
+                               /*
+                                * Can't process if message body isn't all here yet.
+                                */
+                               msgLength -= 4;
+                               avail = conn->inEnd - conn->inCursor;
+                               if (avail < msgLength)
                                {
-                                       printfPQExpBuffer(&conn->errorMessage,
-                                                                         libpq_gettext(
-                                                                 "expected authentication request from "
-                                                                                         "server, but received %c\n"
-                                                                                                       ),
-                                                                         beresp);
+                                       /*
+                                        * Before returning, try to enlarge the input buffer if
+                                        * needed to hold the whole message; see notes in
+                                        * parseInput.
+                                        */
+                                       if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+                                               goto error_return;
+                                       /* We'll come back when there is more data */
+                                       return PGRES_POLLING_READING;
+                               }
+
+                               /* Handle errors. */
+                               if (beresp == 'E')
+                               {
+                                       if (pqGets(&conn->errorMessage, conn))
+                                       {
+                                               /* We'll come back when there is more data */
+                                               return PGRES_POLLING_READING;
+                                       }
+                                       /* OK, we read the message; mark data consumed */
+                                       conn->inStart = conn->inCursor;
+
+                                       /*
+                                        * The postmaster typically won't end its message with
+                                        * a newline, so add one to conform to libpq
+                                        * conventions.
+                                        */
+                                       appendPQExpBufferChar(&conn->errorMessage, '\n');
                                        goto error_return;
                                }
 
+                               /* It is an authentication request. */
                                /* Get the type of request. */
                                if (pqGetInt((int *) &areq, 4, conn))
                                {
index 487acff83dfcdca4be8a9a0c4eef8f92ff41f038..16e63f7f68f0944cbcd21f6429a0558f7cbd901e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.129 2003/04/19 00:02:30 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.130 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,7 @@ static PGresult *prepareAsyncResult(PGconn *conn);
 static int     addTuple(PGresult *res, PGresAttValue * tup);
 static void parseInput(PGconn *conn);
 static void handleSendFailure(PGconn *conn);
+static void handleSyncLoss(PGconn *conn, char id, int msgLength);
 static int     getRowDescriptions(PGconn *conn);
 static int     getAnotherTuple(PGconn *conn, int binary);
 static int     getNotify(PGconn *conn);
@@ -866,6 +867,8 @@ static void
 parseInput(PGconn *conn)
 {
        char            id;
+       int                     msgLength;
+       int                     avail;
        char            noticeWorkspace[128];
 
        /*
@@ -874,25 +877,63 @@ parseInput(PGconn *conn)
        for (;;)
        {
                /*
-                * Quit if in COPY_OUT state: we expect raw data from the server
-                * until PQendcopy is called.  Don't try to parse it according to
-                * the normal protocol.  (This is bogus.  The data lines ought to
-                * be part of the protocol and have identifying leading
-                * characters.)
+                * Try to read a message.  First get the type code and length.
+                * Return if not enough data.
                 */
-               if (conn->asyncStatus == PGASYNC_COPY_OUT)
+               conn->inCursor = conn->inStart;
+               if (pqGetc(&id, conn))
+                       return;
+               if (pqGetInt(&msgLength, 4, conn))
                        return;
 
                /*
-                * OK to try to read a message type code.
+                * Try to validate message type/length here.  A length less than 4
+                * is definitely broken.  Large lengths should only be believed
+                * for a few message types.
                 */
-               conn->inCursor = conn->inStart;
-               if (pqGetc(&id, conn))
+               if (msgLength < 4)
+               {
+                       handleSyncLoss(conn, id, msgLength);
+                       return;
+               }
+               if (msgLength > 30000 &&
+                       !(id == 'T' || id == 'D' || id == 'B' || id == 'd'))
+               {
+                       handleSyncLoss(conn, id, msgLength);
+                       return;
+               }
+
+               /*
+                * Can't process if message body isn't all here yet.
+                */
+               msgLength -= 4;
+               avail = conn->inEnd - conn->inCursor;
+               if (avail < msgLength)
+               {
+                       /*
+                        * Before returning, enlarge the input buffer if needed to hold
+                        * the whole message.  This is better than leaving it to
+                        * pqReadData because we can avoid multiple cycles of realloc()
+                        * when the message is large; also, we can implement a reasonable
+                        * recovery strategy if we are unable to make the buffer big
+                        * enough.
+                        */
+                       if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+                       {
+                               /*
+                                * XXX add some better recovery code... plan is to skip
+                                * over the message using its length, then report an error.
+                                * For the moment, just treat this like loss of sync (which
+                                * indeed it might be!)
+                                */
+                               handleSyncLoss(conn, id, msgLength);
+                       }
                        return;
+               }
 
                /*
-                * NOTIFY and NOTICE messages can happen in any state besides
-                * COPY OUT; always process them right away.
+                * NOTIFY and NOTICE messages can happen in any state; always process
+                * them right away.
                 *
                 * Most other messages should only be processed while in BUSY state.
                 * (In particular, in READY state we hold off further parsing
@@ -936,9 +977,8 @@ parseInput(PGconn *conn)
                                                 libpq_gettext("message type 0x%02x arrived from server while idle\n"),
                                                 id);
                                DONOTICE(conn, noticeWorkspace);
-                               /* Discard the unexpected message; good idea?? */
-                               conn->inStart = conn->inEnd;
-                               break;
+                               /* Discard the unexpected message */
+                               conn->inCursor += msgLength;
                        }
                }
                else
@@ -969,16 +1009,6 @@ parseInput(PGconn *conn)
                                        conn->asyncStatus = PGASYNC_IDLE;
                                        break;
                                case 'I':               /* empty query */
-                                       /* read and throw away the closing '\0' */
-                                       if (pqGetc(&id, conn))
-                                               return;
-                                       if (id != '\0')
-                                       {
-                                               snprintf(noticeWorkspace, sizeof(noticeWorkspace),
-                                                                libpq_gettext("unexpected character %c following empty query response (\"I\" message)\n"),
-                                                                id);
-                                               DONOTICE(conn, noticeWorkspace);
-                                       }
                                        if (conn->result == NULL)
                                                conn->result = PQmakeEmptyPGresult(conn,
                                                                                                          PGRES_EMPTY_QUERY);
@@ -996,11 +1026,6 @@ parseInput(PGconn *conn)
                                        if (pqGetInt(&(conn->be_key), 4, conn))
                                                return;
                                        break;
-                               case 'P':               /* synchronous (normal) portal */
-                                       if (pqGets(&conn->workBuffer, conn))
-                                               return;
-                                       /* We pretty much ignore this message type... */
-                                       break;
                                case 'T':               /* row descriptions (start of query
                                                                 * results) */
                                        if (conn->result == NULL)
@@ -1034,9 +1059,8 @@ parseInput(PGconn *conn)
                                                snprintf(noticeWorkspace, sizeof(noticeWorkspace),
                                                                 libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
                                                DONOTICE(conn, noticeWorkspace);
-                                               /* Discard the unexpected message; good idea?? */
-                                               conn->inStart = conn->inEnd;
-                                               return;
+                                               /* Discard the unexpected message */
+                                               conn->inCursor += msgLength;
                                        }
                                        break;
                                case 'B':               /* Binary data tuple */
@@ -1051,16 +1075,36 @@ parseInput(PGconn *conn)
                                                snprintf(noticeWorkspace, sizeof(noticeWorkspace),
                                                                 libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n"));
                                                DONOTICE(conn, noticeWorkspace);
-                                               /* Discard the unexpected message; good idea?? */
-                                               conn->inStart = conn->inEnd;
-                                               return;
+                                               /* Discard the unexpected message */
+                                               conn->inCursor += msgLength;
                                        }
                                        break;
                                case 'G':               /* Start Copy In */
+                                       if (pqGetc(&conn->copy_is_binary, conn))
+                                               return;
                                        conn->asyncStatus = PGASYNC_COPY_IN;
                                        break;
                                case 'H':               /* Start Copy Out */
+                                       if (pqGetc(&conn->copy_is_binary, conn))
+                                               return;
                                        conn->asyncStatus = PGASYNC_COPY_OUT;
+                                       conn->copy_already_done = 0;
+                                       break;
+                               case 'd':               /* Copy Data */
+                                       /*
+                                        * If we see Copy Data, just silently drop it.  This
+                                        * would only occur if application exits COPY OUT mode
+                                        * too early.
+                                        */
+                                       conn->inCursor += msgLength;
+                                       break;
+                               case 'c':               /* Copy Done */
+                                       /*
+                                        * If we see Copy Done, just silently drop it.  This
+                                        * is the normal case during PQendcopy.  We will keep
+                                        * swallowing data, expecting to see command-complete
+                                        * for the COPY command.
+                                        */
                                        break;
                                default:
                                        printfPQExpBuffer(&conn->errorMessage,
@@ -1069,17 +1113,54 @@ parseInput(PGconn *conn)
                                                                          id);
                                        /* build an error result holding the error message */
                                        saveErrorResult(conn);
-                                       /* Discard the unexpected message; good idea?? */
-                                       conn->inStart = conn->inEnd;
                                        conn->asyncStatus = PGASYNC_READY;
-                                       return;
+                                       /* Discard the unexpected message */
+                                       conn->inCursor += msgLength;
+                                       break;
                        }                                       /* switch on protocol character */
                }
                /* Successfully consumed this message */
-               conn->inStart = conn->inCursor;
+               if (conn->inCursor == conn->inStart + 5 + msgLength)
+               {
+                       /* Normal case: parsing agrees with specified length */
+                       conn->inStart = conn->inCursor;
+               }
+               else
+               {
+                       /* Trouble --- report it */
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"),
+                                                         id);
+                       /* build an error result holding the error message */
+                       saveErrorResult(conn);
+                       conn->asyncStatus = PGASYNC_READY;
+                       /* trust the specified message length as what to skip */
+                       conn->inStart += 5 + msgLength;
+               }
        }
 }
 
+/*
+ * handleSyncLoss: clean up after loss of message-boundary sync
+ *
+ * There isn't really a lot we can do here except abandon the connection.
+ */
+static void
+handleSyncLoss(PGconn *conn, char id, int msgLength)
+{
+       printfPQExpBuffer(&conn->errorMessage,
+                                         libpq_gettext(
+                                                 "lost synchronization with server: got message type \"%c\", length %d\n"),
+                                         id, msgLength);
+       conn->status = CONNECTION_BAD;          /* No more connection to backend */
+       pqsecure_close(conn);
+#ifdef WIN32
+       closesocket(conn->sock);
+#else
+       close(conn->sock);
+#endif
+       conn->sock = -1;
+}
 
 /*
  * parseInput subroutine to read a 'T' (row descriptions) message.
@@ -1100,7 +1181,7 @@ getRowDescriptions(PGconn *conn)
 
        result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
 
-       /* parseInput already read the 'T' label. */
+       /* parseInput already read the 'T' label and message length. */
        /* the next two bytes are the number of fields  */
        if (pqGetInt(&(result->numAttributes), 2, conn))
        {
@@ -1461,7 +1542,7 @@ errout:
 /*
  * Attempt to read a Notice response message.
  * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'N' flag character has already been consumed.
+ * Entry: 'N' message type and length have already been consumed.
  * Exit: returns 0 if successfully consumed Notice message.
  *              returns EOF if not enough data.
  */
@@ -1489,7 +1570,7 @@ getNotice(PGconn *conn)
 /*
  * Attempt to read a Notify response message.
  * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'A' flag character has already been consumed.
+ * Entry: 'A' message type and length have already been consumed.
  * Exit: returns 0 if successfully consumed Notify message.
  *              returns EOF if not enough data.
  */
@@ -1511,10 +1592,18 @@ getNotify(PGconn *conn)
         */
        newNotify = (PGnotify *) malloc(sizeof(PGnotify) +
                                                                        strlen(conn->workBuffer.data) +1);
-       newNotify->relname = (char *) newNotify + sizeof(PGnotify);
-       strcpy(newNotify->relname, conn->workBuffer.data);
-       newNotify->be_pid = be_pid;
-       DLAddTail(conn->notifyList, DLNewElem(newNotify));
+       if (newNotify)
+       {
+               newNotify->relname = (char *) newNotify + sizeof(PGnotify);
+               strcpy(newNotify->relname, conn->workBuffer.data);
+               newNotify->be_pid = be_pid;
+               DLAddTail(conn->notifyList, DLNewElem(newNotify));
+       }
+
+       /* Swallow extra string (not presently used) */
+       if (pqGets(&conn->workBuffer, conn))
+               return EOF;
+
        return 0;
 }
 
@@ -1556,6 +1645,9 @@ PQnotifies(PGconn *conn)
  * Chiefly here so that applications can use "COPY <rel> to stdout"
  * and read the output string. Returns a null-terminated string in s.
  *
+ * XXX this routine is now deprecated, because it can't handle binary data.
+ * If called during a COPY BINARY we return EOF.
+ *
  * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips
  * the terminating \n (like gets(3)).
  *
@@ -1563,7 +1655,7 @@ PQnotifies(PGconn *conn)
  * (a line containing just "\.") when using this routine.
  *
  * RETURNS:
- *             EOF if it is detected or invalid arguments are given
+ *             EOF if error (eg, invalid arguments are given)
  *             0 if EOL is reached (i.e., \n has been read)
  *                             (this is required for backward-compatibility -- this
  *                              routine used to always return EOF or 0, assuming that
@@ -1573,53 +1665,55 @@ PQnotifies(PGconn *conn)
 int
 PQgetline(PGconn *conn, char *s, int maxlen)
 {
-       int                     result = 1;             /* return value if buffer overflows */
+       int                     status;
 
-       if (!s || maxlen <= 0)
+       /* maxlen must be at least 3 to hold the \. terminator! */
+       if (!conn || !s || maxlen < 3)
                return EOF;
 
-       if (!conn || conn->sock < 0)
+       if (conn->sock < 0 ||
+               conn->asyncStatus != PGASYNC_COPY_OUT ||
+               conn->copy_is_binary)
        {
+               printfPQExpBuffer(&conn->errorMessage,
+                                       libpq_gettext("PQgetline: not doing text COPY OUT\n"));
                *s = '\0';
                return EOF;
        }
 
-       /*
-        * Since this is a purely synchronous routine, we don't bother to
-        * maintain conn->inCursor; there is no need to back up.
-        */
-       while (maxlen > 1)
+       while ((status = PQgetlineAsync(conn, s, maxlen-1)) == 0)
        {
-               if (conn->inStart < conn->inEnd)
-               {
-                       char            c = conn->inBuffer[conn->inStart++];
-
-                       if (c == '\n')
-                       {
-                               result = 0;             /* success exit */
-                               break;
-                       }
-                       *s++ = c;
-                       maxlen--;
-               }
-               else
+               /* need to load more data */
+               if (pqWait(TRUE, FALSE, conn) ||
+                       pqReadData(conn) < 0)
                {
-                       /* need to load more data */
-                       if (pqWait(TRUE, FALSE, conn) ||
-                               pqReadData(conn) < 0)
-                       {
-                               result = EOF;
-                               break;
-                       }
+                       *s = '\0';
+                       return EOF;
                }
        }
-       *s = '\0';
 
-       return result;
+       if (status < 0)
+       {
+               /* End of copy detected; gin up old-style terminator */
+               strcpy(s, "\\.");
+               return 0;
+       }
+
+       /* Add null terminator, and strip trailing \n if present */
+       if (s[status-1] == '\n')
+       {
+               s[status-1] = '\0';
+               return 0;
+       }
+       else
+       {
+               s[status] = '\0';
+               return 1;
+       }
 }
 
 /*
- * PQgetlineAsync - gets a newline-terminated string without blocking.
+ * PQgetlineAsync - gets a COPY data row without blocking.
  *
  * This routine is for applications that want to do "COPY <rel> to stdout"
  * asynchronously, that is without blocking.  Having issued the COPY command
@@ -1627,10 +1721,9 @@ PQgetline(PGconn *conn, char *s, int maxlen)
  * and this routine until the end-of-data signal is detected.  Unlike
  * PQgetline, this routine takes responsibility for detecting end-of-data.
  *
- * On each call, PQgetlineAsync will return data if a complete newline-
- * terminated data line is available in libpq's input buffer, or if the
- * incoming data line is too long to fit in the buffer offered by the caller.
- * Otherwise, no data is returned until the rest of the line arrives.
+ * On each call, PQgetlineAsync will return data if a complete data row
+ * is available in libpq's input buffer.  Otherwise, no data is returned
+ * until the rest of the row arrives.
  *
  * If -1 is returned, the end-of-data signal has been recognized (and removed
  * from libpq's input buffer).  The caller *must* next call PQendcopy and
@@ -1640,66 +1733,73 @@ PQgetline(PGconn *conn, char *s, int maxlen)
  *      -1    if the end-of-copy-data marker has been recognized
  *      0         if no data is available
  *      >0    the number of bytes returned.
- * The data returned will not extend beyond a newline character.  If possible
- * a whole line will be returned at one time.  But if the buffer offered by
- * the caller is too small to hold a line sent by the backend, then a partial
- * data line will be returned. This can be detected by testing whether the
- * last returned byte is '\n' or not.
- * The returned string is *not* null-terminated.
+ *
+ * The data returned will not extend beyond a data-row boundary.  If possible
+ * a whole row will be returned at one time.  But if the buffer offered by
+ * the caller is too small to hold a row sent by the backend, then a partial
+ * data row will be returned.  In text mode this can be detected by testing
+ * whether the last returned byte is '\n' or not.
+ *
+ * The returned data is *not* null-terminated.
  */
 
 int
 PQgetlineAsync(PGconn *conn, char *buffer, int bufsize)
 {
+       char            id;
+       int                     msgLength;
        int                     avail;
 
        if (!conn || conn->asyncStatus != PGASYNC_COPY_OUT)
                return -1;                              /* we are not doing a copy... */
 
        /*
-        * Move data from libpq's buffer to the caller's. We want to accept
-        * data only in units of whole lines, not partial lines.  This ensures
-        * that we can recognize the terminator line "\\.\n".  (Otherwise, if
-        * it happened to cross a packet/buffer boundary, we might hand the
-        * first one or two characters off to the caller, which we shouldn't.)
+        * Recognize the next input message.  To make life simpler for async
+        * callers, we keep returning 0 until the next message is fully available
+        * even if it is not Copy Data.  This should keep PQendcopy from blocking.
         */
-
        conn->inCursor = conn->inStart;
+       if (pqGetc(&id, conn))
+               return 0;
+       if (pqGetInt(&msgLength, 4, conn))
+               return 0;
+       avail = conn->inEnd - conn->inCursor;
+       if (avail < msgLength - 4)
+               return 0;
 
-       avail = bufsize;
-       while (avail > 0 && conn->inCursor < conn->inEnd)
-       {
-               char            c = conn->inBuffer[conn->inCursor++];
-
-               *buffer++ = c;
-               --avail;
-               if (c == '\n')
-               {
-                       /* Got a complete line; mark the data removed from libpq */
-                       conn->inStart = conn->inCursor;
-                       /* Is it the endmarker line? */
-                       if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.')
-                               return -1;
-                       /* No, return the data line to the caller */
-                       return bufsize - avail;
-               }
-       }
+       /*
+        * Cannot proceed unless it's a Copy Data message.  Anything else means
+        * end of copy mode.
+        */
+       if (id != 'd')
+               return -1;
 
        /*
-        * We don't have a complete line. We'd prefer to leave it in libpq's
-        * buffer until the rest arrives, but there is a special case: what if
-        * the line is longer than the buffer the caller is offering us?  In
-        * that case we'd better hand over a partial line, else we'd get into
-        * an infinite loop. Do this in a way that ensures we can't
-        * misrecognize a terminator line later: leave last 3 characters in
-        * libpq buffer.
+        * Move data from libpq's buffer to the caller's.  In the case where
+        * a prior call found the caller's buffer too small, we use
+        * conn->copy_already_done to remember how much of the row was already
+        * returned to the caller.
         */
-       if (avail == 0 && bufsize > 3)
+       conn->inCursor += conn->copy_already_done;
+       avail = msgLength - 4 - conn->copy_already_done;
+       if (avail <= bufsize)
        {
-               conn->inStart = conn->inCursor - 3;
-               return bufsize - 3;
+               /* Able to consume the whole message */
+               memcpy(buffer, &conn->inBuffer[conn->inCursor], avail);
+               /* Mark message consumed */
+               conn->inStart = conn->inCursor + avail;
+               /* Reset state for next time */
+               conn->copy_already_done = 0;
+               return avail;
+       }
+       else
+       {
+               /* We must return a partial message */
+               memcpy(buffer, &conn->inBuffer[conn->inCursor], bufsize);
+               /* The message is NOT consumed from libpq's buffer */
+               conn->copy_already_done += bufsize;
+               return bufsize;
        }
-       return 0;
 }
 
 /*
@@ -1774,14 +1874,21 @@ PQendcopy(PGconn *conn)
        if (pqFlush(conn) && pqIsnonblocking(conn))
                return (1);
 
-       /* non blocking connections may have to abort at this point. */
-       if (pqIsnonblocking(conn) && PQisBusy(conn))
-               return (1);
-
        /* Return to active duty */
        conn->asyncStatus = PGASYNC_BUSY;
        resetPQExpBuffer(&conn->errorMessage);
 
+       /*
+        * Non blocking connections may have to abort at this point.  If everyone
+        * played the game there should be no problem, but in error scenarios
+        * the expected messages may not have arrived yet.  (We are assuming that
+        * the backend's packetizing will ensure that CommandComplete arrives
+        * along with the CopyDone; are there corner cases where that doesn't
+        * happen?)
+        */
+       if (pqIsnonblocking(conn) && PQisBusy(conn))
+               return (1);
+
        /* Wait for the completion response */
        result = PQgetResult(conn);
 
@@ -1793,26 +1900,16 @@ PQendcopy(PGconn *conn)
        }
 
        /*
-        * Trouble. The worst case is that we've lost sync with the backend
-        * entirely due to application screwup of the copy in/out protocol. To
-        * recover, reset the connection (talk about using a sledgehammer...)
+        * Trouble. For backwards-compatibility reasons, we issue the error
+        * message as if it were a notice (would be nice to get rid of this
+        * silliness, but too many apps probably don't handle errors from
+        * PQendcopy reasonably).  Note that the app can still obtain the
+        * error status from the PGconn object.
         */
-       PQclear(result);
-
        if (conn->errorMessage.len > 0)
                DONOTICE(conn, conn->errorMessage.data);
 
-       DONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection\n"));
-
-       /*
-        * Users doing non-blocking connections need to handle the reset
-        * themselves, they'll need to check the connection status if we
-        * return an error.
-        */
-       if (pqIsnonblocking(conn))
-               PQresetStart(conn);
-       else
-               PQreset(conn);
+       PQclear(result);
 
        return 1;
 }
@@ -1853,6 +1950,8 @@ PQfn(PGconn *conn,
        bool            needInput = false;
        ExecStatusType status = PGRES_FATAL_ERROR;
        char            id;
+       int                     msgLength;
+       int                     avail;
        int                     i;
 
        *actual_result_len = 0;
@@ -1927,11 +2026,55 @@ PQfn(PGconn *conn,
                 * Scan the message. If we run out of data, loop around to try
                 * again.
                 */
-               conn->inCursor = conn->inStart;
                needInput = true;
 
+               conn->inCursor = conn->inStart;
                if (pqGetc(&id, conn))
                        continue;
+               if (pqGetInt(&msgLength, 4, conn))
+                       continue;
+
+               /*
+                * Try to validate message type/length here.  A length less than 4
+                * is definitely broken.  Large lengths should only be believed
+                * for a few message types.
+                */
+               if (msgLength < 4)
+               {
+                       handleSyncLoss(conn, id, msgLength);
+                       break;
+               }
+               if (msgLength > 30000 &&
+                       !(id == 'T' || id == 'D' || id == 'B' || id == 'd' || id == 'V'))
+               {
+                       handleSyncLoss(conn, id, msgLength);
+                       break;
+               }
+
+               /*
+                * Can't process if message body isn't all here yet.
+                */
+               msgLength -= 4;
+               avail = conn->inEnd - conn->inCursor;
+               if (avail < msgLength)
+               {
+                       /*
+                        * Before looping, enlarge the input buffer if needed to hold
+                        * the whole message.  See notes in parseInput.
+                        */
+                       if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+                       {
+                               /*
+                                * XXX add some better recovery code... plan is to skip
+                                * over the message using its length, then report an error.
+                                * For the moment, just treat this like loss of sync (which
+                                * indeed it might be!)
+                                */
+                               handleSyncLoss(conn, id, msgLength);
+                               break;
+                       }
+                       continue;
+               }
 
                /*
                 * We should see V or E response to the command, but might get N
@@ -1975,7 +2118,7 @@ PQfn(PGconn *conn,
                                                          libpq_gettext("protocol error: id=0x%x\n"),
                                                                          id);
                                        saveErrorResult(conn);
-                                       conn->inStart = conn->inCursor;
+                                       conn->inStart += 5 + msgLength;
                                        return prepareAsyncResult(conn);
                                }
                                break;
@@ -1998,7 +2141,8 @@ PQfn(PGconn *conn,
                                break;
                        case 'Z':                       /* backend is ready for new query */
                                /* consume the message and exit */
-                               conn->inStart = conn->inCursor;
+                               conn->inStart += 5 + msgLength;
+                               /* XXX expect additional fields here */
                                /* if we saved a result object (probably an error), use it */
                                if (conn->result)
                                        return prepareAsyncResult(conn);
@@ -2009,11 +2153,13 @@ PQfn(PGconn *conn,
                                                          libpq_gettext("protocol error: id=0x%x\n"),
                                                                  id);
                                saveErrorResult(conn);
-                               conn->inStart = conn->inCursor;
+                               /* trust the specified message length as what to skip */
+                               conn->inStart += 5 + msgLength;
                                return prepareAsyncResult(conn);
                }
                /* Completed this message, keep going */
-               conn->inStart = conn->inCursor;
+               /* trust the specified message length as what to skip */
+               conn->inStart += 5 + msgLength;
                needInput = false;
        }
 
index dfc46fdf5987ce9f031cae23bffc7cf62b143b69..76de4a8708628468a7696477744bb58a20e77fee 100644 (file)
@@ -23,7 +23,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.89 2003/04/19 00:02:30 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.90 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -277,12 +277,12 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
 
 /*
  * Make sure conn's output buffer can hold bytes_needed bytes (caller must
- * include existing outCount into the value!)
+ * include already-stored data into the value!)
  *
- * Returns 0 on success, EOF on error
+ * Returns 0 on success, EOF if failed to enlarge buffer
  */
 static int
-checkOutBufferSpace(int bytes_needed, PGconn *conn)
+pqCheckOutBufferSpace(int bytes_needed, PGconn *conn)
 {
        int                     newsize = conn->outBufSize;
        char       *newbuf;
@@ -335,6 +335,66 @@ checkOutBufferSpace(int bytes_needed, PGconn *conn)
        return EOF;
 }
 
+/*
+ * Make sure conn's input buffer can hold bytes_needed bytes (caller must
+ * include already-stored data into the value!)
+ *
+ * Returns 0 on success, EOF if failed to enlarge buffer
+ */
+int
+pqCheckInBufferSpace(int bytes_needed, PGconn *conn)
+{
+       int                     newsize = conn->inBufSize;
+       char       *newbuf;
+
+       if (bytes_needed <= newsize)
+               return 0;
+       /*
+        * If we need to enlarge the buffer, we first try to double it in size;
+        * if that doesn't work, enlarge in multiples of 8K.  This avoids
+        * thrashing the malloc pool by repeated small enlargements.
+        *
+        * Note: tests for newsize > 0 are to catch integer overflow.
+        */
+       do {
+               newsize *= 2;
+       } while (bytes_needed > newsize && newsize > 0);
+
+       if (bytes_needed <= newsize)
+       {
+               newbuf = realloc(conn->inBuffer, newsize);
+               if (newbuf)
+               {
+                       /* realloc succeeded */
+                       conn->inBuffer = newbuf;
+                       conn->inBufSize = newsize;
+                       return 0;
+               }
+       }
+
+       newsize = conn->inBufSize;
+       do {
+               newsize += 8192;
+       } while (bytes_needed > newsize && newsize > 0);
+
+       if (bytes_needed <= newsize)
+       {
+               newbuf = realloc(conn->inBuffer, newsize);
+               if (newbuf)
+               {
+                       /* realloc succeeded */
+                       conn->inBuffer = newbuf;
+                       conn->inBufSize = newsize;
+                       return 0;
+               }
+       }
+
+       /* realloc failed. Probably out of memory */
+       printfPQExpBuffer(&conn->errorMessage,
+                                         "cannot allocate memory for input buffer\n");
+       return EOF;
+}
+
 /*
  * pqPutMsgStart: begin construction of a message to the server
  *
@@ -364,7 +424,7 @@ pqPutMsgStart(char msg_type, PGconn *conn)
        else
                lenPos = conn->outCount;
        /* make sure there is room for it */
-       if (checkOutBufferSpace(lenPos + 4, conn))
+       if (pqCheckOutBufferSpace(lenPos + 4, conn))
                return EOF;
        /* okay, save the message type byte if any */
        if (msg_type)
@@ -390,7 +450,7 @@ static int
 pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
 {
        /* make sure there is room for it */
-       if (checkOutBufferSpace(conn->outMsgEnd + len, conn))
+       if (pqCheckOutBufferSpace(conn->outMsgEnd + len, conn))
                return EOF;
        /* okay, save the data */
        memcpy(conn->outBuffer + conn->outMsgEnd, buf, len);
@@ -486,13 +546,13 @@ pqReadData(PGconn *conn)
         */
        if (conn->inBufSize - conn->inEnd < 8192)
        {
-               int                     newSize = conn->inBufSize * 2;
-               char       *newBuf = (char *) realloc(conn->inBuffer, newSize);
-
-               if (newBuf)
+               if (pqCheckInBufferSpace(conn->inEnd + 8192, conn))
                {
-                       conn->inBuffer = newBuf;
-                       conn->inBufSize = newSize;
+                       /*
+                        * We don't insist that the enlarge worked, but we need some room
+                        */
+                       if (conn->inBufSize - conn->inEnd < 100)
+                               return -1;              /* errorMessage already set */
                }
        }
 
index 8671922547d1cfceb518ccb61ae777831977bdf8..35e3208eb0efb1e05302d2409828684acddeb9ca 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.62 2003/04/19 00:02:30 tgl Exp $
+ * $Id: libpq-int.h,v 1.63 2003/04/22 00:08:07 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,101) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ      PG_PROTOCOL(3,102) /* XXX temporary value */
 
 /*
  * POSTGRES backend dependent Constants.
@@ -216,7 +216,8 @@ struct pg_conn
                                                                 * is listening on; if NULL, uses a
                                                                 * default constructed from pgport */
        char       *pgtty;                      /* tty on which the backend messages is
-                                                                * displayed (NOT ACTUALLY USED???) */
+                                                                * displayed (OBSOLETE, NOT USED) */
+       char       *connect_timeout; /* connection timeout (numeric string) */
        char       *pgoptions;          /* options to start the backend with */
        char       *dbName;                     /* database name */
        char       *pguser;                     /* Postgres username and password, if any */
@@ -232,6 +233,10 @@ struct pg_conn
        /* Status indicators */
        ConnStatusType status;
        PGAsyncStatusType asyncStatus;
+       char            copy_is_binary; /* 1 = copy binary, 0 = copy text */
+       int                     copy_already_done; /* # bytes already returned in COPY OUT */
+       int                     nonblocking;    /* whether this connection is using a
+                                                                * blocking socket to the backend or not */
        Dllist     *notifyList;         /* Notify msgs not yet handed to
                                                                 * application */
 
@@ -246,6 +251,7 @@ struct pg_conn
        int                     be_key;                 /* key of backend --- needed for cancels */
        char            md5Salt[4];             /* password salt received from backend */
        char            cryptSalt[2];   /* password salt received from backend */
+       int                     client_encoding; /* encoding id */
        PGlobjfuncs *lobjfuncs;         /* private state for large-object access
                                                                 * fns */
 
@@ -258,9 +264,6 @@ struct pg_conn
        int                     inEnd;                  /* offset to first position after avail
                                                                 * data */
 
-       int                     nonblocking;    /* whether this connection is using a
-                                                                * blocking socket to the backend or not */
-
        /* Buffer for data not yet sent to backend */
        char       *outBuffer;          /* currently allocated buffer */
        int                     outBufSize;             /* allocated size of buffer */
@@ -291,10 +294,6 @@ struct pg_conn
 
        /* Buffer for receiving various parts of messages */
        PQExpBufferData workBuffer; /* expansible string */
-
-       int                     client_encoding;        /* encoding id */
-
-       char       *connect_timeout;
 };
 
 /* String descriptions of the ExecStatusTypes.
@@ -330,6 +329,7 @@ extern void pqClearAsyncResult(PGconn *conn);
   * for Get, EOF merely means the buffer is exhausted, not that there is
   * necessarily any error.
   */
+extern int     pqCheckInBufferSpace(int bytes_needed, PGconn *conn);
 extern int     pqGetc(char *result, PGconn *conn);
 extern int     pqPutc(char c, PGconn *conn);
 extern int     pqGets(PQExpBuffer buf, PGconn *conn);
index 341f4ded1e00ee64c458d7a9627bc70b96924e25..4d1458f891624917093aed3a72c11338370bbeda 100644 (file)
@@ -995,7 +995,6 @@ copy test("........pg.dropped.1........") to stdout;
 ERROR:  Relation "test" has no column "........pg.dropped.1........"
 copy test from stdin;
 ERROR:  copy: line 1, Extra data after last expected column
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 select * from test;
  b | c 
index cf28af8c198de5b26392bd50ce7e463df5eb3d75..983e6bb4a41af71dd2eb726a6110a12953d6728f 100644 (file)
@@ -35,17 +35,13 @@ ERROR:  Attribute "d" specified more than once
 -- missing data: should fail
 COPY x from stdin;
 ERROR:  copy: line 1, pg_atoi: zero-length string
-lost synchronization with server, resetting connection
 COPY x from stdin;
 ERROR:  copy: line 1, Missing data for column "e"
-lost synchronization with server, resetting connection
 COPY x from stdin;
 ERROR:  copy: line 1, Missing data for column "e"
-lost synchronization with server, resetting connection
 -- extra data: should fail
 COPY x from stdin;
 ERROR:  copy: line 1, Extra data after last expected column
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 -- various COPY options: delimiters, oids, NULL string
 COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
index 1aaa4a85ef43cbda576ba8a3fc2e362f021ab4f3..13eb14cfa2f0d17df66f8629fa1b81986c2161ad 100644 (file)
@@ -40,7 +40,6 @@ INSERT INTO basictest values ('88', 'haha', 'short', '123.1212');    -- Truncate
 -- Test copy
 COPY basictest (testvarchar) FROM stdin; -- fail
 ERROR:  copy: line 1, value too long for type character varying(5)
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 COPY basictest (testvarchar) FROM stdin;
 select * from basictest;
@@ -128,12 +127,10 @@ INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good
 -- Test copy
 COPY nulltest FROM stdin; --fail
 ERROR:  copy: line 1, Domain dcheck does not allow NULL values
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 -- Last row is bad
 COPY nulltest FROM stdin;
 ERROR:  copy: line 3, CopyFrom: rejected due to CHECK constraint "nulltest_col5" on "nulltest"
-lost synchronization with server, resetting connection
 select * from nulltest;
  col1 | col2 | col3 | col4 | col5 
 ------+------+------+------+------