This patch (against the current CVS sources) adds to libpq the functions
authorBruce Momjian
Tue, 30 Nov 1999 03:08:19 +0000 (03:08 +0000)
committerBruce Momjian
Tue, 30 Nov 1999 03:08:19 +0000 (03:08 +0000)
PQconnectStart
PQconnectPoll
PQresetStart
PQresetPoll
PQsetenvStart
PQsetenvPoll
PQsetenvAbort

and brings into the published interface

PQsetenv.

The first four are asynchronous analogues of PQconnectdb and PQreset -
they allow an application to connect to the DB without blocking on
remote I/O.

The PQsetenv functions perform an environment negotiation with the
server.

Internal to libpq, pqReadReady and pqWriteReady have been made available
across the library (they were previously static functions inside
fe-misc.c).  A lot of internal rearrangement has been necessary to
support these changes.

The API documentation has been updated also.

Caveats:

  o  The Windows code does not default to using non-blocking sockets,
since I have no documentation: Define WIN32_NON_BLOCKING_CONNECTIONS to
do that.

  o  The SSL code still blocks.

Ewan Mellor.

doc/src/sgml/libpq.sgml
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-misc.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h

index 8f926922241e84c98fabc07053f6a51f91b48848..560feffadc988300919c1e05af992660301380ea 100644 (file)
@@ -57,14 +57,13 @@ header file libpq-fe.h and must link with the
 
 PGconn *PQconnectdb(const char *conninfo)
 
-   This routine opens a new database connection using the parameters
-   taken from the string conninfo.  Unlike PQsetdbLogin()
-   below, the parameter set
-   can be extended without changing the function signature, so use
-   of this routine is prefered for application programming.  The passed
-   string can be empty to use all default
-   parameters, or it can contain one or more parameter settings
-   separated by whitespace. 
+   This routine opens a new database connection using the parameters taken
+   from the string conninfo.  Unlike PQsetdbLogin() below,
+   the parameter set can be extended without changing the function signature,
+   so use either of this routine or the non-blocking analogues PQconnectStart
+   / PQconnectPoll is prefered for application programming.  The passed string
+   can be empty to use all default parameters, or it can contain one or more
+   parameter settings separated by whitespace.
    
    
    Each parameter setting is in the form keyword = value.
@@ -80,8 +79,38 @@ PGconn *PQconnectdb(const char *conninfo)
      host
      
      
-      Host to connect to. If a non-zero-length string is specified, TCP/IP communication is used.
-      Without a host name, libpq will connect using a local Unix domain socket.
+      Name of host to connect to. If a non-zero-length string is specified, TCP/IP
+      communication is used.  Using this parameter causes a hostname look-up.
+      See hostaddr.
+     
+     
+    
+
+    
+     hostaddr
+     
+     
+      IP address of host to connect to. This should be in standard
+      numbers-and-dots form, as used by the BSD functions inet_aton et al. If
+      a non-zero-length string is specified, TCP/IP communication is used.
+     
+     
+      Using hostaddr instead of host allows the application to avoid a host
+      name look-up, which may be important in applications with time
+      constraints. However, Kerberos authentication requires the host
+      name. The following therefore applies. If host is specified without
+      hostaddr, a hostname look-up is forced. If hostaddr is specified without
+      host, the value for hostaddr gives the remote address; if Kerberos is
+      used, this causes a reverse name query. If both host and hostaddr are
+      specified, the value for hostaddr gives the remote address; the value
+      for host is ignored, unless Kerberos is used, in which case that value
+      is used for Kerberos authentication. Note that authentication is likely
+      to fail if libpq is passed a host name which is not the name of the
+      machine at hostaddr.
+     
+     
+      Without both a host name and host address, libpq will connect using a
+      local Unix domain socket.
      
      
     
@@ -149,6 +178,9 @@ PGconn *PQconnectdb(const char *conninfo)
    The return value is a pointer to an abstract struct
    representing the connection to the backend.
    
+   
+    This function is not thread-safe.
+   
   
 
   
@@ -167,6 +199,9 @@ PGconn *PQsetdbLogin(const char *pghost,
    This is the predecessor of PQconnectdb with a fixed number
    of parameters but the same functionality.   
    
+   
+    This function is not thread-safe.
+   
   
 
   
@@ -185,6 +220,173 @@ PGconn *PQsetdb(char *pghost,
    
   
 
+  
+   PQconnectStart
+   PQconnectPoll
+   Make a connection to the database server in a non-blocking manner.
+
+PGconn *PQconnectStart(const char *conninfo)
+
+
+PostgresPollingStatusType *PQconnectPoll(PQconn *conn)
+
+   These two routines are used to open a connection to a database server such
+   that your application's thread of execution is not blocked on remote I/O
+   whilst doing so.
+  
+  
+   The database connection is made using the parameters taken from the string
+   conninfo, passed to PQconnectStart. This string is in
+   the same format as described above for PQconnectdb.
+  
+  
+   Neither PQconnectStart nor PQconnectPoll will block, as long as a number of
+   restrictions are met:
+   
+    
+     
+      The hostaddr and host parameters are used appropriately to ensure that
+      name and reverse name queries are not made. See the documentation of
+      these parameters under PQconnectdb above for details.
+     
+    
+
+    
+     
+      If you call PQtrace, ensure that the stream object into which you trace
+      will not block.
+     
+    
+
+    
+     
+      You ensure for yourself that the socket is in the appropriate state
+      before calling PQconnectPoll, as described below.
+     
+    
+   
+  
+
+  
+   To begin, call conn=PQconnectStart("<connection_info_string>"). If
+   conn is NULL, then libpq has been unable to allocate a new PGconn
+   structure. Otherwise, a valid PGconn pointer is returned (though not yet
+   representing a valid connection to the database). On return from
+   PQconnectStart, call status=PQstatus(conn). If status equals
+   CONNECTION_BAD, PQconnectStart has failed.
+  
+  
+   If PQconnectStart succeeds, the next stage is to poll libpq so that it may
+   proceed with the connection sequence.  Loop thus: Consider a connection
+   'inactive' by default. If PQconnectPoll last returned PGRES_POLLING_ACTIVE,
+   consider it 'active' instead. If PQconnectPoll(conn) last returned
+   PGRES_POLLING_READING, perform a select for reading on PQsocket(conn). If
+   it last returned PGRES_POLLING_WRITING, perform a select for writing on
+   PQsocket(conn). If you have yet to call PQconnectPoll, i.e. after the call
+   to PQconnectStart, behave as if it last returned PGRES_POLLING_WRITING.  If
+   the select shows that the socket is ready, consider it 'active'. If it has
+   been decided that this connection is 'active', call PQconnectPoll(conn)
+   again. If this call returns PGRES_POLLING_FAILED, the connection procedure
+   has failed.  If this call returns PGRES_POLLING_OK, the connection has been
+   successfully made.
+  
+  
+    Note that the use of select() to ensure that the socket is ready is merely
+    a (likely) example; those with other facilities available, such as a
+    poll() call, may of course use that instead.
+  
+  
+    At any time during connection, the status of the connection may be
+    checked, by calling PQstatus. If this is CONNECTION_BAD, then the
+    connection procedure has failed; if this is CONNECTION_OK, then the
+    connection is ready.  Either of these states should be equally detectable
+    from the return value of PQconnectPoll, as above. Other states may be
+    shown during (and only during) an asynchronous connection procedure. These
+    indicate the current stage of the connection procedure, and may be useful
+    to provide feedback to the user for example. These statuses may include:
+    
+     
+      
+      CONNECTION_STARTED: Waiting for connection to be made.
+      
+     
+     
+      
+      CONNECTION_MADE: Connection OK; waiting to send.
+      
+     
+     
+      
+      CONNECTION_AWAITING_RESPONSE: Waiting for a response from the backend.
+      
+     
+     
+      
+      CONNECTION_AUTH_RESPONSE: Got an authentication response; about to deal
+      with it.
+      
+     
+     
+      
+      CONNECTION_ERROR_RESPONSE: Got an error response; about to deal with it.
+      
+     
+     
+      
+      CONNECTION_AUTH_OK: Received authentication; waiting for ReadyForQuery etc.
+      
+     
+     
+      
+      CONNECTION_SETENV: Negotiating environment.
+      
+     
+    
+
+    Note that, although these constants will remain (in order to maintain
+    compatibility) an application should never rely upon these appearing in a
+    particular order, or at all, or on the status always being one of these
+    documented values. An application may do something like this:
+
+    switch(PQstatus(conn))
+    {
+        case CONNECTION_STARTED:
+            feedback = "Connecting...";
+       break;
+
+        case CONNECTION_MADE:
+            feedback = "Connected to server...";
+            break;
+.
+.
+.
+        default:
+       feedback = "Connecting...";
+    }
+
+  
+  
+   Note that if PQconnectStart returns a non-NULL pointer, you must call
+   PQfinish upon that, when you are finished with it, in order to dispose of
+   the structure and any associated memory blocks. This must be done even if a
+   call to PQconnectStart or PQconnectPoll failed.
+  
+  
+   PQconnectPoll will currently block if libpq is compiled with USE_SSL
+   defined. This restriction may be removed in the future.
+  
+  
+   PQconnectPoll will currently block under Windows, unless libpq is compiled
+   with WIN32_NON_BLOCKING_CONNECTIONS defined. This code has not yet been
+   tested under Windows, and so it is currently off by default. This may be
+   changed in the future.
+  
+  
+   These functions are not thread-safe.
+  
+
   
    
    PQconndefaults Returns the default connection options.
@@ -215,6 +417,9 @@ struct PQconninfoOption
    will depend on environment variables and other context.
    Callers must treat the connection options data as read-only.
    
+   
+    This function is not thread-safe.
+   
   
 
   
@@ -247,6 +452,31 @@ void PQreset(PGconn *conn)
    
   
 
+  
+   
+   PQresetStart
+   PQresetPoll
+   Reset the communication  port  with  the  backend, in a non-blocking manner.
+
+int PQresetStart(PGconn *conn);
+
+
+PostgresPollingStatusType PQresetPoll(PGconn *conn);
+
+    These functions will close the connection to the backend and attempt to
+    reestablish a new connection to the same postmaster, using all the same
+    parameters previously used. This may be useful for error recovery if a
+    working connection is lost. They differ from PQreset (above) in that they
+    act in a non-blocking manner. These functions suffer from the same
+    restrictions as PQconnectStart and PQconnectPoll.
+   
+   
+    Call PQresetStart. If it returns 0, the reset has failed. If it returns 1,
+    poll the reset using PQresetPoll in exactly the same way as you would
+    create the connection using PQconnectPoll.
+   
+  
+
  
 
 
@@ -338,19 +568,25 @@ const char *PQoptions(const PGconn *conn)
 
 PQstatus
          Returns the status of the connection. 
-         The status can be CONNECTION_OK or CONNECTION_BAD.
 
 ConnStatusType PQstatus(const PGconn *conn)
 
 
 
 
-A failed connection attempt is signaled by status CONNECTION_BAD.
-Ordinarily, an OK status will remain so until PQfinish, but a
+The status can be one of a number of values. However, only two of these are
+seen outside of an asynchronous connection procedure -
+CONNECTION_OK or CONNECTION_BAD. A good
+connection to the database has the status CONNECTION_OK. A failed connection
+attempt is signaled by status CONNECTION_BAD.  Ordinarily,
+an OK status will remain so until PQfinish, but a
 communications failure might result in the status changing to
-CONNECTION_BAD prematurely.  In that case the application could
-try to recover by calling PQreset.
+CONNECTION_BAD prematurely.  In that case the application
+could try to recover by calling PQreset.
 
+
+See the entry for PQconnectStart and PQconnectPoll with regards to other status codes
+that might be seen.
 
 
 
@@ -385,6 +621,60 @@ server host, not the local host!
 
 
 
+  
+   PQsetenvStart
+   PQsetenvPoll
+   PQsetenvAbort
+   Perform an environment negotiation.
+
+PGsetenvHandle *PQsetenvStart(PGconn *conn)
+
+
+PostgresPollingStatusType *PQsetenvPoll(PGsetenvHandle handle)
+
+
+void PQsetenvAbort(PGsetenvHandle handle)
+
+   These two routines can be used to re-perform the environment negotiation
+   that occurs during the opening of a connection to a database server. I have
+   no idea why this might be useful (XXX anyone?) but it might prove useful
+   for users to be able to reconfigure their character encodings on-the-fly,
+   for example.
+  
+  
+   These functions will not block, subject to the restrictions applied to
+   PQconnectStart and PQconnectPoll.
+  
+  
+   To begin, call handle=PQsetenvStart(conn), where conn is an open connection
+   to the database server. If handle is NULL, then libpq has been unable to
+   allocate a new PGsetenvHandle structure. Otherwise, a valid handle is
+   returned. This handle is intended to be opaque - you may only use it to
+   call other functions in libpq (PQsetenvPoll, for example).
+  
+  
+    Poll the procedure using PQsetenvPoll, in exactly the same way as you would
+    create a connection using PQconnectPoll.
+  
+  
+    The procedure may be aborted at any time by calling PQsetenvAbort(handle).
+  
+  
+   These functions are not thread-safe.
+  
+
+  
+   PQsetenv
+   Perform an environment negotiation.
+
+int PQsetenv(PGconn *conn)
+
+   This function performs the same duties as PQsetenvStart and PQsetenvPoll, but
+   blocks to do so. It returns 1 on success and 0 on failure.
+  
 
 
 
index 7e2fb4437975807ccfbc8a5d8321c75a613d1b77..85764b1e9d405d0cbfcea057ddadbc39199163bb 100644 (file)
@@ -7,11 +7,13 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.106 1999/11/11 00:10:13 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.107 1999/11/30 03:08:18 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 
+#include 
+#include 
 #include 
 #include 
 #include 
@@ -27,6 +29,7 @@
 #include 
 #include 
 #include 
+#include 
 #endif
 
 #ifndef HAVE_STRDUP
 #include "mb/pg_wchar.h"
 #endif
 
+/* ----------
+ *     pg_setenv_state
+ * A struct used when polling a setenv request. This is referred to externally
+ * using a PGsetenvHandle.
+ * ----------
+ */
+struct pg_setenv_state
+{
+   enum
+   {
+       SETENV_STATE_OPTION_SEND,    /* About to send an Environment Option */
+       SETENV_STATE_OPTION_WAIT,    /* Waiting for above send to complete  */
+#ifdef MULTIBYTE
+       SETENV_STATE_ENCODINGS_SEND, /* About to send an "encodings" query  */
+       SETENV_STATE_ENCODINGS_WAIT, /* Waiting for query to complete       */
+#endif
+       SETENV_STATE_OK,
+       SETENV_STATE_FAILED
+   } state;
+   PGconn *conn;
+   PGresult *res;
+   struct EnvironmentOptions *eo;
+} ;
+
+static int connectDBStart(PGconn *conn);
+static int connectDBComplete(PGconn *conn);
+
 #ifdef USE_SSL
 static SSL_CTX *SSL_context = NULL;
 #endif
 
-static ConnStatusType connectDB(PGconn *conn);
 static PGconn *makeEmptyPGconn(void);
 static void freePGconn(PGconn *conn);
 static void closePGconn(PGconn *conn);
@@ -53,9 +82,6 @@ static char *conninfo_getval(char *keyword);
 static void conninfo_free(void);
 static void defaultNoticeProcessor(void *arg, const char *message);
 
-/* XXX Why is this not static? */
-void       PQsetenv(PGconn *conn);
-
 #define NOTIFYLIST_INITIAL_SIZE 10
 #define NOTIFYLIST_GROWBY 10
 
@@ -98,6 +124,9 @@ static PQconninfoOption PQconninfoOptions[] = {
    {"host", "PGHOST", NULL, NULL,
    "Database-Host", "", 40},
 
+   {"hostaddr", "PGHOSTADDR", NULL, NULL,
+    "Database-Host-IPv4-Address", "", 15}, /* Room for abc.def.ghi.jkl */
+
    {"port", "PGPORT", DEF_PGPORT, NULL,
    "Database-Port", "", 6},
 
@@ -145,27 +174,86 @@ static struct EnvironmentOptions
    }
 };
 
+
+/* ----------------
+ *      Connecting to a Database
+ *
+ * There are now four different ways a user of this API can connect to the
+ * database.  Two are not recommended for use in new code, because of their
+ * lack of extensibility with respect to the passing of options to the
+ * backend.  These are PQsetdb and PQsetdbLogin (the former now being a macro
+ * to the latter).
+ *
+ * If it is desired to connect in a synchronous (blocking) manner, use the
+ * function PQconnectdb.
+ *
+ * To connect in an asychronous (non-blocking) manner, use the functions
+ * PQconnectStart, and PQconnectPoll.
+ *
+ * Internally, the static functions connectDBStart, connectDBComplete
+ * are part of the connection procedure.
+ * 
+ * ----------------
+ */
+
 /* ----------------
  *     PQconnectdb
  *
  * establishes a connection to a postgres backend through the postmaster
  * using connection information in a string.
  *
- * The conninfo string is a list of
+ * The conninfo string is a white-separated list of
  *
  *    option = value
  *
- * definitions. Value might be a single value containing no whitespaces
- * or a single quoted string. If a single quote should appear everywhere
- * in the value, it must be escaped with a backslash like \'
+ * definitions. Value might be a single value containing no whitespaces or
+ * a single quoted string. If a single quote should appear anywhere in
+ * the value, it must be escaped with a backslash like \'
  *
- * Returns a PGconn* which is needed for all subsequent libpq calls
- * if the status field of the connection returned is CONNECTION_BAD,
- * then some fields may be null'ed out instead of having valid values
- * ----------------
- */
+ * Returns a PGconn* which is needed for all subsequent libpq calls, or NULL
+ * if a memory allocation failed.
+ * If the status field of the connection returned is CONNECTION_BAD,
+ * then some fields may be null'ed out instead of having valid values.
+ *
+ * You should call PQfinish (if conn is not NULL) regardless of whether this
+ * call succeeded.
+ *
+ * ---------------- */
+
 PGconn *
 PQconnectdb(const char *conninfo)
+{
+   PGconn *conn = PQconnectStart(conninfo);
+   
+   (void)(!conn || (conn->status == CONNECTION_BAD) ||
+          !connectDBComplete(conn));
+
+   return conn;
+}
+
+/* ----------------
+ *     PQconnectStart
+ *
+ * Begins the establishment of a connection to a postgres backend through the
+ * postmaster using connection information in a string.
+ *
+ * See comment for PQconnectdb for the definition of the string format.
+ *
+ * Returns a PGconn*.  If NULL is returned, a malloc error has occurred, and
+ * you should not attempt to proceed with this connection.  If the status
+ * field of the connection returned is CONNECTION_BAD, an error has
+ * occurred. In this case you should call PQfinish on the result, (perhaps
+ * inspecting the error message first).  Other fields of the structure may not
+ * be valid if that occurs.  If the status field is not CONNECTION_BAD, then
+ * this stage has succeeded - call PQconnectPoll, using select(2) to see when
+ * this is necessary.
+ *
+ * See PQconnectPoll for more info.
+ *
+ * ---------------- */
+
+PGconn *
+PQconnectStart(const char *conninfo)
 {
    PGconn     *conn;
    char       *tmp;
@@ -174,6 +262,7 @@ PQconnectdb(const char *conninfo)
     * Allocate memory for the conn structure
     * ----------
     */
+   
    conn = makeEmptyPGconn();
    if (conn == NULL)
        return (PGconn *) NULL;
@@ -188,6 +277,8 @@ PQconnectdb(const char *conninfo)
        conninfo_free();
        return conn;
    }
+   tmp = conninfo_getval("hostaddr");
+   conn->pghostaddr = tmp ? strdup(tmp) : NULL;
    tmp = conninfo_getval("host");
    conn->pghost = tmp ? strdup(tmp) : NULL;
    tmp = conninfo_getval("port");
@@ -213,7 +304,11 @@ PQconnectdb(const char *conninfo)
     * Connect to the database
     * ----------
     */
-   conn->status = connectDB(conn);
+   if (!connectDBStart(conn))
+   {
+     /* Just in case we failed to set it in connectDBStart */
+       conn->status = CONNECTION_BAD;
+   }
 
    return conn;
 }
@@ -384,10 +479,14 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons
    }
 
    if (error)
+   {
        conn->status = CONNECTION_BAD;
+   }
    else
-       conn->status = connectDB(conn);
-
+   {
+       (void)(!connectDBStart(conn) || !connectDBComplete(conn));
+   }
+       
    return conn;
 }
 
@@ -480,7 +579,9 @@ update_db_info(PGconn *conn)
                if (strcmp(old + offset, "localhost") != 0)
                {
                    printfPQExpBuffer(&conn->errorMessage,
-                                     "connectDB() -- non-tcp access only possible on localhost\n");
+                                     "connectDBStart() -- "
+                                     "non-tcp access only possible on "
+                                     "localhost\n");
                    return 1;
                }
            }
@@ -494,24 +595,84 @@ update_db_info(PGconn *conn)
    return 0;
 }
 
-/*
- * connectDB -
- * make a connection to the backend so it is ready to receive queries.
- * return CONNECTION_OK if successful, CONNECTION_BAD if not.
- *
+
+/* ----------
+ * connectMakeNonblocking -
+ * Make a connection non-blocking.
+ * Returns 1 if successful, 0 if not.
+ * ----------
  */
-static ConnStatusType
-connectDB(PGconn *conn)
+static int
+connectMakeNonblocking(PGconn *conn)
+{
+#ifndef WIN32
+   if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0)
+#else
+   if (ioctlsocket(conn->sock, FIONBIO, &on) != 0)
+#endif
+   {
+       printfPQExpBuffer(&conn->errorMessage,
+                         "connectMakeNonblocking -- fcntl() failed: errno=%d\n%s\n",
+                         errno, strerror(errno));
+       return 0;
+   }
+
+   return 1;
+}
+
+/* ----------
+ * connectNoDelay -
+ * Sets the TCP_NODELAY socket option.
+ * Returns 1 if successful, 0 if not.
+ * ----------
+ */
+static int
+connectNoDelay(PGconn *conn)
+{
+   struct protoent *pe;
+   int         on = 1;
+
+   pe = getprotobyname("TCP");
+   if (pe == NULL)
+   {
+       printfPQExpBuffer(&conn->errorMessage,
+                         "connectNoDelay() -- "
+                         "getprotobyname failed: errno=%d\n%s\n",
+                         errno, strerror(errno));
+       return 0;
+   }
+   if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY,
+#ifdef WIN32
+                  (char *)
+#endif
+                  &on,
+                  sizeof(on)) < 0)
+   {
+       printfPQExpBuffer(&conn->errorMessage,
+                         "connectNoDelay() -- setsockopt failed: errno=%d\n%s\n",
+                         errno, strerror(errno));
+#ifdef WIN32
+       printf("Winsock error: %i\n", WSAGetLastError());
+#endif
+       return 0;
+   }
+
+   return 1;
+}
+
+
+/* ----------
+ * connectDBStart -
+ * Start to make a connection to the backend so it is ready to receive
+ * queries.
+ * Returns 1 if successful, 0 if not.
+ * ----------
+ */
+static int
+connectDBStart(PGconn *conn)
 {
-   PGresult   *res;
-   struct hostent *hp;
-   StartupPacket sp;
-   AuthRequest areq;
-   SOCKET_SIZE_TYPE laddrlen;
    int         portno,
                family;
-   char        beresp;
-   int         on = 1;
 #ifdef USE_SSL
    StartupPacket           np; /* Used to negotiate SSL connection */
    char                    SSLok;
@@ -519,92 +680,126 @@ connectDB(PGconn *conn)
    int                     tried_ssl = 0;      /* Set if SSL negotiation was tried */
 #endif
 
+   if (!conn)
+       return 0;
    /*
     * parse dbName to get all additional info in it, if any
     */
    if (update_db_info(conn) != 0)
        goto connect_errReturn;
 
+   /* Ensure our buffers are empty */
+   conn->inStart = conn->inCursor = conn->inEnd = 0;
+   conn->outCount = 0;
+
    /*
-    * Initialize the startup packet.
+    * Set up the connection to postmaster/backend.
+    * Note that this supports IPv4 and UDP only.
     */
 
-   MemSet((char *) &sp, 0, sizeof(StartupPacket));
-
-   sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LIBPQ);
+   MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr));
 
-   strncpy(sp.user, conn->pguser, SM_USER);
-   strncpy(sp.database, conn->dbName, SM_DATABASE);
-   strncpy(sp.tty, conn->pgtty, SM_TTY);
+   if (conn->pghostaddr != NULL)
+   {
+       /* Using pghostaddr avoids a hostname lookup */
+       /* Note that this supports IPv4 only */
+       struct in_addr addr;
 
-   if (conn->pgoptions)
-       strncpy(sp.options, conn->pgoptions, SM_OPTIONS);
+       if(!inet_aton(conn->pghostaddr, &addr))
+       {
+           printfPQExpBuffer(&conn->errorMessage,
+                             "connectDBStart() -- "
+                             "invalid host address: %s\n", conn->pghostaddr);
+           goto connect_errReturn;
+       }
 
-   /*
-    * Open a connection to postmaster/backend.
-    */
+       family = AF_INET;
 
-   if (conn->pghost != NULL)
+       memmove((char *) &(conn->raddr.in.sin_addr),
+               (char *) &addr, sizeof(addr));
+   }
+   else if (conn->pghost != NULL)
    {
+       /* Using pghost, so we have to look-up the hostname */
+       struct hostent *hp;
+
        hp = gethostbyname(conn->pghost);
        if ((hp == NULL) || (hp->h_addrtype != AF_INET))
        {
            printfPQExpBuffer(&conn->errorMessage,
-                             "connectDB() --  unknown hostname: %s\n",
+                             "connectDBStart() --  unknown hostname: %s\n",
                              conn->pghost);
            goto connect_errReturn;
        }
        family = AF_INET;
+
+       memmove((char *) &(conn->raddr.in.sin_addr),
+               (char *) hp->h_addr,
+               hp->h_length);
    }
    else
    {
-       hp = NULL;
+       /* pghostaddr and pghost are NULL, so use UDP */
        family = AF_UNIX;
    }
 
-   MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr));
+   /* Set family */
    conn->raddr.sa.sa_family = family;
 
+   /* Set port number */
    portno = atoi(conn->pgport);
    if (family == AF_INET)
    {
-       memmove((char *) &(conn->raddr.in.sin_addr),
-               (char *) hp->h_addr,
-               hp->h_length);
        conn->raddr.in.sin_port = htons((unsigned short) (portno));
        conn->raddr_len = sizeof(struct sockaddr_in);
    }
 #if !defined(WIN32) && !defined(__CYGWIN32__)
-   else
-       conn->raddr_len = UNIXSOCK_PATH(conn->raddr.un, portno);
+       else
+           conn->raddr_len = UNIXSOCK_PATH(conn->raddr.un, portno);
 #endif
 
 
-   /* Connect to the server  */
+   /* Open a socket */
    if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0)
    {
        printfPQExpBuffer(&conn->errorMessage,
-                         "connectDB() -- socket() failed: errno=%d\n%s\n",
+                         "connectDBStart() -- "
+                         "socket() failed: errno=%d\n%s\n",
                          errno, strerror(errno));
        goto connect_errReturn;
    }
-   if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0)
+
+   /* ----------
+    * Set the right options. Normally, we need nonblocking I/O, and we don't
+    * want delay of outgoing data for AF_INET sockets.  If we are using SSL,
+    * then we need the blocking I/O (XXX Can this be fixed?).
+    * ---------- */
+
+   if (family == AF_INET)
    {
-       printfPQExpBuffer(&conn->errorMessage,
-                         "connectDB() -- connect() failed: %s\n"
-                         "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n",
-                         strerror(errno),
-                         (family == AF_INET) ? " (with -i)" : "",
-                         conn->pghost ? conn->pghost : "localhost",
-                         (family == AF_INET) ? "TCP/IP port" : "Unix socket",
-                         conn->pgport);
-       goto connect_errReturn;
+       if (!connectNoDelay(conn))
+           goto connect_errReturn;
    }
 
+   /* ----------
+    * Since I have no idea whether this is a valid thing to do under Windows
+    * before a connection is made, and since I have no way of testing it, I
+    * leave the code looking as below.  When someone decides that they want
+    * non-blocking connections under Windows, they can define
+    * WIN32_NON_BLOCKING_CONNECTIONS before compilation.  If it works, then
+    * this code can be cleaned up.
+    *
+    *   Ewan Mellor .
+    * ---------- */
+#if (!defined(WIN32) || defined(WIN32_NON_BLOCKING_CONNECTIONS)) && !defined(USE_SSL)
+   if (!connectMakeNonblocking(conn))
+       goto connect_errReturn;
+#endif 
+
+#ifdef USE_SSL
    /* This needs to be done before we set into nonblocking, since SSL negotiation
     * does not like that mode */
 
-#ifdef USE_SSL
    /* Attempt to negotiate SSL usage */
    if (allow_ssl_try) {
      tried_ssl = 1;
@@ -653,7 +848,7 @@ connectDB(PGconn *conn)
          fprintf(conn->Pfdebug, "Backend reports error, attempting fallback to pre-6.6.\n");
        close(conn->sock);
        allow_ssl_try = 0;
-       return connectDB(conn);
+       return connectDBStart(conn);
      }
      else if (SSLok != 'N') {
        strcpy(conn->errorMessage,
@@ -665,242 +860,812 @@ connectDB(PGconn *conn)
      allow_ssl_try = 1; /* We'll allow an attempt to use SSL next time */
 #endif
 
-   /*
-    * Set the right options. We need nonblocking I/O, and we don't want
-    * delay of outgoing data.
+   /* ----------
+     * Start / make connection.  We are hopefully in non-blocking mode
+    * now, but it is possible that:
+    *   1. Older systems will still block on connect, despite the
+    *      non-blocking flag. (Anyone know if this is true?)
+    *   2. We are running under Windows, and aren't even trying
+    *      to be non-blocking (see above).
+    *   3. We are using SSL.
+    * Thus, we have make arrangements for all eventualities.
+    * ----------
     */
-
-#ifndef WIN32
-   if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0)
-#else
-   if (ioctlsocket(conn->sock, FIONBIO, &on) != 0)
-#endif
-   {
-       printfPQExpBuffer(&conn->errorMessage,
-                         "connectDB() -- fcntl() failed: errno=%d\n%s\n",
-                         errno, strerror(errno));
-       goto connect_errReturn;
-   }
-
-   if (family == AF_INET)
+   if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0)
    {
-       struct protoent *pe;
-
-       pe = getprotobyname("TCP");
-       if (pe == NULL)
+       if (errno == EINPROGRESS)
        {
-           printfPQExpBuffer(&conn->errorMessage,
-                             "connectDB(): getprotobyname failed\n");
-           goto connect_errReturn;
+           /* This is fine - we're in non-blocking mode, and the
+            * connection is in progress. */
+           conn->status = CONNECTION_STARTED;
        }
-       if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY,
-#ifdef WIN32
-                      (char *)
-#endif
-                      &on,
-                      sizeof(on)) < 0)
+       else
        {
+           /* Something's gone wrong */
            printfPQExpBuffer(&conn->errorMessage,
-                             "connectDB() -- setsockopt failed: errno=%d\n%s\n",
-                             errno, strerror(errno));
-#ifdef WIN32
-           printf("Winsock error: %i\n", WSAGetLastError());
-#endif
+                             "connectDBStart() -- connect() failed: %s\n"
+                             "Is the postmaster running%s at '%s' "
+                             "and accepting connections on %s '%s'?\n",
+                             strerror(errno),
+                             (family == AF_INET) ? " (with -i)" : "",
+                             conn->pghost ? conn->pghost : "localhost",
+                             (family == AF_INET) ?
+                             "TCP/IP port" : "Unix socket",
+                             conn->pgport);
            goto connect_errReturn;
        }
    }
-
-   /* Fill in the client address */
-   laddrlen = sizeof(conn->laddr);
-   if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0)
+   else
    {
-       printfPQExpBuffer(&conn->errorMessage,
-                         "connectDB() -- getsockname() failed: errno=%d\n%s\n",
-                         errno, strerror(errno));
-       goto connect_errReturn;
+       /* We're connected already */
+       conn->status = CONNECTION_MADE;
    }
 
-   /* Ensure our buffers are empty */
-   conn->inStart = conn->inCursor = conn->inEnd = 0;
-   conn->outCount = 0;
+   /* This makes the connection non-blocking, for all those cases which forced us
+      not to do it above. */
+#if (defined(WIN32) && !defined(WIN32_NON_BLOCKING_CONNECTIONS)) || defined(USE_SSL)
+   if (!connectMakeNonblocking(conn))
+       goto connect_errReturn;
+#endif 
 
-   /* Send the startup packet. */
+   return 1;
 
-   if (pqPacketSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK)
+connect_errReturn:
+   if (conn->sock >= 0)
    {
-       printfPQExpBuffer(&conn->errorMessage,
-                         "connectDB() --  couldn't send startup packet: errno=%d\n%s\n",
-                         errno, strerror(errno));
-       goto connect_errReturn;
+#ifdef WIN32
+       closesocket(conn->sock);
+#else
+       close(conn->sock);
+#endif
+       conn->sock = -1;
    }
+   conn->status = CONNECTION_BAD;
 
-   /*
-    * Perform the authentication exchange: wait for backend messages and
-    * respond as necessary. We fall out of this loop when done talking to
-    * the postmaster.
-    */
+   return 0;
+}
 
-   for (;;)
-   {
-       /* Wait for some data to arrive (or for the channel to close) */
-       if (pqWait(TRUE, FALSE, conn))
-           goto connect_errReturn;
-       /* Load data, or detect EOF */
-       if (pqReadData(conn) < 0)
-           goto connect_errReturn;
 
-       /*
-        * Scan the message. If we run out of data, loop around to try
-        * again.
-        */
-       conn->inCursor = conn->inStart;
+/* ----------------
+ *     connectDBComplete
+ *
+ * Block and complete a connection.
+ *
+ * Returns 1 on success, 0 on failure.
+ * ----------------
+ */
+static int
+connectDBComplete(PGconn *conn)
+{
+   PostgresPollingStatusType flag;
+   int r = 0, w = 1;
 
-       if (pqGetc(&beresp, conn))
-           continue;           /* no data yet */
+   do
+   {
+       if(pqWait(r, w, conn))
+       {
+           conn->status = CONNECTION_BAD;
+           return 0;
+       }
 
-       /* Handle errors. */
-       if (beresp == 'E')
+   again:
+       switch(flag = PQconnectPoll(conn))
        {
-           if (pqGets(&conn->errorMessage, conn))
-               continue;
-           goto connect_errReturn;
+           case PGRES_POLLING_ACTIVE:
+               goto again;
+
+           case PGRES_POLLING_OK:
+               break;
+               
+           case PGRES_POLLING_READING:
+               r = 1;
+               w = 0;
+               break;
+
+           case PGRES_POLLING_WRITING:
+               r = 0;
+               w = 1;
+               break;
+
+           default:
+               /* Just in case we failed to set it in PQconnectPoll */
+               conn->status = CONNECTION_BAD;
+               return 0;
        }
+   } while (flag != PGRES_POLLING_OK);
+   
+   return 1;
+}
+
+
+/* ----------------
+ *     PQconnectPoll
+ *
+ * Poll an asynchronous connection.
+ *
+ * Returns a PostgresPollingStatusType.
+ * Before calling this function, use select(2) to determine when data arrive.
+ * 
+ * You must call PQfinish whether or not this fails.
+ *
+ * This function and PQconnectStart are intended to allow connections to be
+ * made without blocking the execution of your program on remote I/O. However,
+ * there are a number of caveats:
+ *
+ *   o  If you call PQtrace, ensure that the stream object into which you trace
+        will not block.
+ *   o  If you do not supply an IP address for the remote host (i.e. you 
+ *      supply a host name instead) then this function will block on
+ *      gethostbyname.  You will be fine if using UDP (i.e. by supplying
+ *      neither a host name nor a host address).
+ *   o  If your backend wants to use Kerberos authentication then you must
+ *      supply both a host name and a host address, otherwise this function
+ *      may block on gethostname.
+ *   o  This function will block if compiled with USE_SSL.
+ *
+ * ---------------- */
+PostgresPollingStatusType
+PQconnectPoll(PGconn *conn)
+{
+   PGresult   *res;
 
-       /* Otherwise it should be an authentication request. */
-       if (beresp != 'R')
+   if (conn == NULL)
+       return PGRES_POLLING_FAILED;
+
+   /* Get the new data */
+   switch (conn->status)
+   {
+       /* We really shouldn't have been polled in these two cases, but
+          we can handle it. */
+       case CONNECTION_BAD:
+           return PGRES_POLLING_FAILED;
+       case CONNECTION_OK:
+           return PGRES_POLLING_OK;
+
+       /* These are reading states */
+       case CONNECTION_AWAITING_RESPONSE:
+       case CONNECTION_AUTH_RESPONSE:
+       case CONNECTION_ERROR_RESPONSE:
+       case CONNECTION_AUTH_OK:
        {
-           printfPQExpBuffer(&conn->errorMessage,
-                             "connectDB() -- expected authentication request\n");
-           goto connect_errReturn;
+           /* Load waiting data */
+           int n = pqReadData(conn);
+               
+           if (n < 0)
+               goto error_return;
+           if (n == 0)
+               return PGRES_POLLING_READING;
+
+           break;
        }
 
-       /* Get the type of request. */
-       if (pqGetInt((int *) &areq, 4, conn))
-           continue;
+       /* These are writing states, so we just proceed. */
+       case CONNECTION_STARTED:
+       case CONNECTION_MADE:
+          break;
 
-       /* Get the password salt if there is one. */
-       if (areq == AUTH_REQ_CRYPT)
+       case CONNECTION_SETENV:
+           /* We allow PQsetenvPoll to decide whether to proceed */
+           break;
+
+       default:
+           printfPQExpBuffer(&conn->errorMessage,
+                             "PQconnectPoll() -- unknown connection state - "
+                             "probably indicative of memory corruption!\n");
+           goto error_return;
+   }
+
+
+ keep_going: /* We will come back to here until there is nothing left to
+               parse. */
+   switch(conn->status)
+   {
+       case CONNECTION_STARTED:
        {
-           if (pqGetnchar(conn->salt, sizeof(conn->salt), conn))
-               continue;
+           SOCKET_SIZE_TYPE laddrlen;
+           int optval;
+           socklen_t optlen = sizeof(int);
+
+           /* Write ready, since we've made it here, so the connection
+            * has been made. */
+
+           /* Now check (using getsockopt) that there is not an error
+              state waiting for us on the socket. */
+
+           if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
+                          &optval, &optlen) == -1)
+           {
+               printfPQExpBuffer(&conn->errorMessage,
+                                 "PQconnectPoll() -- getsockopt() failed: "
+                                 "errno=%d\n%s\n",
+                                 errno, strerror(errno));
+               goto error_return;
+           }
+           else if (optval != 0)
+           {
+               printfPQExpBuffer(&conn->errorMessage,
+                                 "PQconnectPoll() -- "
+                                 "socket has error condition %d: %s.\n",
+                                 optval, strerror(optval));
+               goto error_return;
+           }
+
+           /* Fill in the client address */
+           laddrlen = sizeof(conn->laddr);
+           if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0)
+           {
+               printfPQExpBuffer(&conn->errorMessage,
+                                 "PQconnectPoll() -- getsockname() failed: "
+                                 "errno=%d\n%s\n",
+                                 errno, strerror(errno));
+               goto error_return;
+           }
+
+           conn->status = CONNECTION_MADE;
+           return PGRES_POLLING_WRITING;
        }
 
-       /* OK, we successfully read the message; mark data consumed */
-       conn->inStart = conn->inCursor;
+       case CONNECTION_MADE:
+       {
+           StartupPacket sp;
+           
+           /*
+            * Initialize the startup packet.
+            */
 
-       /* Respond to the request if necessary. */
-       /* fe-auth.c has not been fixed to support PQExpBuffers, so: */
-       if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
-                       conn->errorMessage.data) != STATUS_OK)
+           MemSet((char *) &sp, 0, sizeof(StartupPacket));
+   
+           sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LIBPQ);
+           
+           strncpy(sp.user, conn->pguser, SM_USER);
+           strncpy(sp.database, conn->dbName, SM_DATABASE);
+           strncpy(sp.tty, conn->pgtty, SM_TTY);
+
+           if (conn->pgoptions)
+               strncpy(sp.options, conn->pgoptions, SM_OPTIONS);
+
+           /* Send the startup packet. */
+
+           if (pqPacketSend(conn, (char *) &sp,
+                            sizeof(StartupPacket)) != STATUS_OK)
+           {
+               printfPQExpBuffer(&conn->errorMessage,
+                                 "PQconnectPoll() --  "
+                                 "couldn't send startup packet: "
+                                 "errno=%d\n%s\n",
+                                 errno, strerror(errno));
+               goto error_return;
+           }
+
+           conn->status = CONNECTION_AWAITING_RESPONSE;
+           return PGRES_POLLING_READING;
+       }
+
+
+       /*
+        * Handle the authentication exchange: wait for backend messages
+        * and respond as necessary.
+        */
+       case CONNECTION_AWAITING_RESPONSE:
+       {
+           char        beresp;
+
+           /* Scan the message */
+           conn->inCursor = conn->inStart;
+
+           if (pqGetc(&beresp, conn))
+           {
+               /* We'll come back when there are more data */
+               return PGRES_POLLING_READING;
+           }
+
+           /* Handle errors. */
+           if (beresp == 'E')
+           {
+               conn->status = CONNECTION_ERROR_RESPONSE;
+               goto keep_going;
+           }
+
+           /* Otherwise it should be an authentication request. */
+           if (beresp != 'R')
+           {
+               printfPQExpBuffer(&conn->errorMessage,
+                              "PQconnectPoll() -- expected authentication "
+                              "request\n");
+               goto error_return;
+           }
+
+           /* Got an authentication request, so that's OK */
+           conn->status = CONNECTION_AUTH_RESPONSE;
+           goto keep_going;
+       }
+
+       case CONNECTION_AUTH_RESPONSE:
        {
+           AuthRequest areq;
+           
+           /* Get the type of request. */
+           if (pqGetInt((int *) &areq, 4, conn))
+           {
+               /* We'll come back when there are more data */
+               return PGRES_POLLING_READING;
+           }
+
+           /* Get the password salt if there is one. */
+           if (areq == AUTH_REQ_CRYPT)
+           {
+               if (pqGetnchar(conn->salt, sizeof(conn->salt), conn))
+               {
+                   /* We'll come back when there are more data */
+                   return PGRES_POLLING_READING;
+               }
+           }
+
+           /* OK, we successfully read the message; mark data consumed */
+           conn->inStart = conn->inCursor;
+
+           /* Respond to the request if necessary. */
+           /* Note that conn->pghost must be non-NULL if we are going
+            * avoid the Kerberos code doing a hostname look-up. */
+           /* XXX fe-auth.c has not been fixed to support PQExpBuffers, so: */
+           if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
+                           conn->errorMessage.data) != STATUS_OK)
+           {
+               conn->errorMessage.len = strlen(conn->errorMessage.data);
+               goto error_return;
+           }
            conn->errorMessage.len = strlen(conn->errorMessage.data);
-           goto connect_errReturn;
+
+           /* This function has a section near the end that looks like it
+            * should block.  I think that it will be OK though, since the
+            * socket is non-blocking, and thus the data should get out
+            * as quickly as possible.                                    */
+           if (pqFlush(conn))
+               goto error_return;
+
+           if (areq == AUTH_REQ_OK)
+           {
+               /* We are done with authentication exchange */
+               conn->status = CONNECTION_AUTH_OK;
+               /* Set asyncStatus so that PQsetResult will think that what
+                * comes back next is the result of a query.  See below.  */
+               conn->asyncStatus = PGASYNC_BUSY;
+               goto keep_going;
+           }
+
+           conn->status = CONNECTION_AWAITING_RESPONSE;
+           return PGRES_POLLING_READING;
        }
 
-       if (pqFlush(conn))
-           goto connect_errReturn;
+       case CONNECTION_ERROR_RESPONSE:
+           if (pqGets(&conn->errorMessage, conn))
+           {
+               /* We'll come back when there are more data */
+               return PGRES_POLLING_READING;
+           }
+           goto error_return;
 
-       /* Are we done? */
-       if (areq == AUTH_REQ_OK)
-           break;
-   }
+       case CONNECTION_AUTH_OK:
+       {
+           /* ----------
+            * Now we expect to hear from the backend. A ReadyForQuery
+            * message indicates that startup is successful, but we might
+            * also get an Error message indicating failure. (Notice
+            * messages indicating nonfatal warnings are also allowed by
+            * the protocol, as is a BackendKeyData message.) Easiest way
+            * to handle this is to let PQgetResult() read the messages. We
+            * just have to fake it out about the state of the connection.
+            *----------
+            */
 
-   /*
-    * Now we expect to hear from the backend. A ReadyForQuery message
-    * indicates that startup is successful, but we might also get an
-    * Error message indicating failure. (Notice messages indicating
-    * nonfatal warnings are also allowed by the protocol, as is a
-    * BackendKeyData message.) Easiest way to handle this is to let
-    * PQgetResult() read the messages. We just have to fake it out about
-    * the state of the connection.
-    */
+           if (!PQconsumeInput(conn))
+               goto error_return;
 
-   conn->status = CONNECTION_OK;
-   conn->asyncStatus = PGASYNC_BUSY;
-   res = PQgetResult(conn);
-   /* NULL return indicating we have gone to IDLE state is expected */
-   if (res)
-   {
-       if (res->resultStatus != PGRES_FATAL_ERROR)
+           if(PQisBusy(conn))
+               return PGRES_POLLING_READING;
+           
+           res = PQgetResult(conn);
+           /* NULL return indicating we have gone to
+              IDLE state is expected */
+           if (res)
+           {
+               if (res->resultStatus != PGRES_FATAL_ERROR)
+                   printfPQExpBuffer(&conn->errorMessage,
+                                     "PQconnectPoll() -- unexpected message "
+                                     "during startup\n");
+               PQclear(res);
+               goto error_return;
+           }
+
+           /*
+            * Post-connection housekeeping. Send environment variables
+            * to server.
+            */
+
+           if ((conn->setenv_handle = PQsetenvStart(conn)) == NULL)
+               goto error_return;
+
+           conn->status = CONNECTION_SETENV;
+
+           goto keep_going;
+       }
+
+       case CONNECTION_SETENV:
+           /* We pretend that the connection is OK for the duration of
+              theses queries. */
+           conn->status = CONNECTION_OK;
+
+           switch(PQsetenvPoll(conn->setenv_handle))
+           {
+               case PGRES_POLLING_OK: /* Success */
+                   conn->status = CONNECTION_OK;
+                   return PGRES_POLLING_OK;
+
+               case PGRES_POLLING_READING: /* Still going */
+                   conn->status = CONNECTION_SETENV;
+                   return PGRES_POLLING_READING;
+
+               case PGRES_POLLING_WRITING: /* Still going */
+                   conn->status = CONNECTION_SETENV;
+                   return PGRES_POLLING_WRITING;
+
+               default:
+                   conn->status = CONNECTION_SETENV;
+                   goto error_return;
+           }
+           /* Unreachable */
+
+       default:
            printfPQExpBuffer(&conn->errorMessage,
-                             "connectDB() -- unexpected message during startup\n");
-       PQclear(res);
-       goto connect_errReturn;
+                             "PQconnectPoll() -- unknown connection state - "
+                             "probably indicative of memory corruption!\n",
+                             sizeof(conn->errorMessage));
+           goto error_return;
    }
 
-   /*
-    * Post-connection housekeeping. Send environment variables to server
+   /* Unreachable */
+
+error_return:
+   /* ----------
+    * We used to close the socket at this point, but that makes it awkward
+    * for those above us if they wish to remove this socket from their
+    * own records (an fd_set for example).  We'll just have this socket
+    * closed when PQfinish is called (which is compulsory even after an
+    * error, since the connection structure must be freed).
+    * ----------
     */
+   return PGRES_POLLING_FAILED;
+}
 
-   PQsetenv(conn);
 
-   return CONNECTION_OK;
+/* ----------------
+ *     PQsetenvStart
+ *
+ * Starts the process of passing the values of a standard set of environment
+ * variables to the backend.
+ *
+ * ---------------- */
+PGsetenvHandle
+PQsetenvStart(PGconn *conn)
+{
+   struct pg_setenv_state *handle;
 
-connect_errReturn:
-   if (conn->sock >= 0)
+   if ((handle = malloc(sizeof(struct pg_setenv_state))) == NULL)
    {
-#ifdef WIN32
-       closesocket(conn->sock);
-#else
-       close(conn->sock);
-#endif
-       conn->sock = -1;
+       printfPQExpBuffer(&conn->errorMessage,
+                         "PQsetenvStart() -- malloc error - %s\n",
+                         strerror(errno));
+       return NULL;
    }
-   return CONNECTION_BAD;
 
+   handle->conn = conn;
+   handle->res = NULL;
+   handle->eo = EnvironmentOptions;
+
+#ifdef MULTIBYTE
+   handle->state = SETENV_STATE_ENCODINGS_SEND;
+#else
+   handle->state = SETENV_STATE_OPTION_SEND;
+#endif
+   
+   return handle; /* Note that a struct pg_setenv_state * is the same as a
+                     PGsetenvHandle */
 }
 
-void
-PQsetenv(PGconn *conn)
+/* ----------------
+ *     PQsetenvPoll
+ *
+ * Polls the process of passing the values of a standard set of environment
+ * variables to the backend.
+ *
+ * ---------------- */
+PostgresPollingStatusType
+PQsetenvPoll(PGsetenvHandle handle)
 {
-   struct EnvironmentOptions *eo;
-   char        setQuery[100];  /* note length limits in sprintf's below */
-   const char *val;
-   PGresult   *res;
 #ifdef MULTIBYTE
-   char       *envname = "PGCLIENTENCODING";
+   const char envname[] = "PGCLIENTENCODING";
+#endif
 
-   /* Set env. variable PGCLIENTENCODING if it's not set already */
-   val = getenv(envname);
-   if (!val || *val == '\0')
+   if (!handle || handle->state == SETENV_STATE_FAILED)
+       return PGRES_POLLING_FAILED;
+
+   /* Check whether there are any data for us */
+   switch (handle->state)
    {
-       const char *encoding = NULL;
-
-       /* query server encoding */
-       res = PQexec(conn, "select getdatabaseencoding()");
-       if (res && PQresultStatus(res) == PGRES_TUPLES_OK)
-           encoding = PQgetvalue(res, 0, 0);
-       if (!encoding)          /* this should not happen */
-           encoding = pg_encoding_to_char(MULTIBYTE);
-       if (encoding)
+       /* These are reading states */
+#ifdef MULTIBYTE
+       case SETENV_STATE_ENCODINGS_WAIT:
+#endif
+       case SETENV_STATE_OPTION_WAIT:
        {
-           /* set client encoding via environment variable */
-           char       *envbuf;
+           /* Load waiting data */
+           int n = pqReadData(handle->conn);
+               
+           if (n < 0)
+               goto error_return;
+           if (n == 0)
+               return PGRES_POLLING_READING;
 
-           envbuf = (char *) malloc(strlen(envname) + strlen(encoding) + 2);
-           sprintf(envbuf, "%s=%s", envname, encoding);
-           putenv(envbuf);
+           break;
        }
-       PQclear(res);
-   }
+
+       /* These are writing states, so we just proceed. */
+#ifdef MULTIBYTE
+       case SETENV_STATE_ENCODINGS_SEND:
 #endif
+       case SETENV_STATE_OPTION_SEND:
+           break;
+          
+       default:
+           printfPQExpBuffer(&handle->conn->errorMessage,
+                             "PQsetenvPoll() -- unknown state - "
+                             "probably indicative of memory corruption!\n");
+           goto error_return;
+   }
 
-   for (eo = EnvironmentOptions; eo->envName; eo++)
+
+ keep_going: /* We will come back to here until there is nothing left to
+               parse. */
+   switch(handle->state)
    {
-       if ((val = getenv(eo->envName)))
+
+#ifdef MULTIBYTE
+       case SETENV_STATE_ENCODINGS_SEND:
        {
-           if (strcasecmp(val, "default") == 0)
-               sprintf(setQuery, "SET %s = %.60s", eo->pgName, val);
-           else
-               sprintf(setQuery, "SET %s = '%.60s'", eo->pgName, val);
+           const char *env;
+           
+           /* query server encoding */
+           env = getenv(envname);
+           if (!env || *env == '\0')
+           {
+               if (!PQsendQuery(handle->conn,
+                                "select getdatabaseencoding()"))
+                   goto error_return;
+
+               handle->state = SETENV_STATE_ENCODINGS_WAIT;
+               return PGRES_POLLING_READING;
+           }
+       }
+
+       case SETENV_STATE_ENCODINGS_WAIT:
+       {
+           const char *encoding = 0;
+
+           if (!PQconsumeInput(handle->conn))
+               goto error_return;
+
+           if (PQisBusy(handle->conn))
+               return PGRES_POLLING_READING;
+           
+           handle->res = PQgetResult(handle->conn);
+
+           if (handle->res)
+           {
+               if (PQresultStatus(handle->res) != PGRES_TUPLES_OK)
+               {
+                   PQclear(handle->res);
+                   goto error_return;
+               }
+
+               encoding = PQgetvalue(handle->res, 0, 0);
+               if (!encoding)          /* this should not happen */
+                   encoding = pg_encoding_to_char(MULTIBYTE);
+
+               if (encoding)
+               {
+                   /* set client encoding via environment variable */
+                   char       *envbuf;
+
+                   envbuf = (char *) malloc(strlen(envname) + strlen(encoding) + 2);
+                   sprintf(envbuf, "%s=%s", envname, encoding);
+                   putenv(envbuf);
+               }
+               PQclear(handle->res);
+               /* We have to keep going in order to clear up the query */
+               goto keep_going;
+           }
+
+           /* NULL result indicates that the query is finished */
+
+           /* Move on to setting the environment options */
+           handle->state = SETENV_STATE_OPTION_SEND;
+           goto keep_going;
+       }
+#endif
+
+       case SETENV_STATE_OPTION_SEND:
+       {
+           /* Send an Environment Option */
+           char        setQuery[100];  /* note length limits in sprintf's below */
+
+           if (handle->eo->envName)
+           {
+               const char *val;
+
+               if ((val = getenv(handle->eo->envName)))
+               {
+                   if (strcasecmp(val, "default") == 0)
+                       sprintf(setQuery, "SET %s = %.60s",
+                               handle->eo->pgName, val);
+                   else
+                       sprintf(setQuery, "SET %s = '%.60s'",
+                               handle->eo->pgName, val);
 #ifdef CONNECTDEBUG
-           printf("Use environment variable %s to send %s\n", eo->envName, setQuery);
+                   printf("Use environment variable %s to send %s\n",
+                          handle->eo->envName, setQuery);
 #endif
-           res = PQexec(conn, setQuery);
-           PQclear(res);       /* Don't care? */
+                   if (!PQsendQuery(handle->conn, setQuery))
+                       goto error_return;
+
+                   handle->state = SETENV_STATE_OPTION_WAIT;
+               }
+               else
+               {
+                   handle->eo++;
+               }
+           }
+           else
+           {
+               /* No option to send, so we are done. */
+               handle->state = SETENV_STATE_OK;
+           }
+
+           goto keep_going;
        }
+
+       case SETENV_STATE_OPTION_WAIT:
+       {
+           if (!PQconsumeInput(handle->conn))
+               goto error_return;
+
+           if (PQisBusy(handle->conn))
+               return PGRES_POLLING_READING;
+           
+           handle->res = PQgetResult(handle->conn);
+
+           if (handle->res)
+           {
+               if (PQresultStatus(handle->res) != PGRES_COMMAND_OK)
+               {
+                   PQclear(handle->res);
+                   goto error_return;
+               }
+               /* Don't need the result */
+               PQclear(handle->res);
+               /* We have to keep going in order to clear up the query */
+               goto keep_going;
+           }
+
+           /* NULL result indicates that the query is finished */
+
+           /* Send the next option */
+           handle->eo++;
+           handle->state = SETENV_STATE_OPTION_SEND;
+           goto keep_going;
+       }
+
+       case SETENV_STATE_OK:
+           /* Tidy up */
+           free(handle);
+           return PGRES_POLLING_OK;
+
+       default:
+           printfPQExpBuffer(&handle->conn->errorMessage,
+                             "PQsetenvPoll() -- unknown state - "
+                             "probably indicative of memory corruption!\n");
+           goto error_return;
+   }
+
+   /* Unreachable */
+
+ error_return:
+   handle->state = SETENV_STATE_FAILED; /* This may protect us even if we
+                                         * are called after the handle
+                                         * has been freed.             */
+   free(handle);
+   return PGRES_POLLING_FAILED;
+}
+
+
+/* ----------------
+ *     PQsetenvAbort
+ *
+ * Aborts the process of passing the values of a standard set of environment
+ * variables to the backend.
+ *
+ * ---------------- */
+void
+PQsetenvAbort(PGsetenvHandle handle)
+{
+   /* We should not have been called in the FAILED state, but we can cope by
+    * not freeing the handle (it has probably been freed by now anyway). */
+   if (handle->state != SETENV_STATE_FAILED)
+   {
+       handle->state = SETENV_STATE_FAILED;
+       free(handle);
    }
-}  /* PQsetenv() */
+}
+
+
+/* ----------------
+ *     PQsetenv
+ *
+ * Passes the values of a standard set of environment variables to the
+ * backend.
+ *
+ * Returns 1 on success, 0 on failure.
+ *
+ * This function used to return void.  I don't think that there should be
+ * compatibility problems caused by giving it a return value, especially as
+ * this function has not been documented previously.
+ *
+ * ---------------- */
+int
+PQsetenv(PGconn *conn)
+{
+   PostgresPollingStatusType flag;
+   PGsetenvHandle handle;
+   int r = 0, w = 1;
+   
+   if((handle = PQsetenvStart(conn)) == NULL)
+       return 0;
+
+   do
+   {
+       if(pqWait(r, w, conn))
+       {
+           /* XXX This is not a good sign - perhaps we should mark the
+              connection as bad here... */
+           return 0;
+       }
+
+   again:
+       switch(flag = PQsetenvPoll(handle))
+       {
+           case PGRES_POLLING_ACTIVE:
+               goto again;
+
+           case PGRES_POLLING_OK:
+               break;
+               
+           case PGRES_POLLING_READING:
+               r = 1;
+               w = 0;
+               break;
+               
+           case PGRES_POLLING_WRITING:
+               r = 0;
+               w = 1;
+               break;
+               
+           default: /* Failed */
+               return 0;
+       }
+   } while (flag != PGRES_POLLING_OK);
+  
+   return 1;
+}
+
 
 /*
  * makeEmptyPGconn
@@ -973,6 +1738,8 @@ freePGconn(PGconn *conn)
 #endif
    if (conn->pghost)
        free(conn->pghost);
+   if (conn->pghostaddr)
+       free(conn->pghostaddr);
    if (conn->pgport)
        free(conn->pgport);
    if (conn->pgtty)
@@ -1006,6 +1773,12 @@ freePGconn(PGconn *conn)
 static void
 closePGconn(PGconn *conn)
 {
+   if (conn->status == CONNECTION_SETENV)
+   {
+       /* We have to abort the setenv process as well */
+       PQsetenvAbort(conn->setenv_handle);
+   }
+
    if (conn->sock >= 0)
    {
 
@@ -1067,8 +1840,47 @@ PQreset(PGconn *conn)
    if (conn)
    {
        closePGconn(conn);
-       conn->status = connectDB(conn);
+
+       (void)(!connectDBStart(conn) || !connectDBComplete(conn));
    }
+
+   return;
+}
+
+
+/* PQresetStart :
+   resets the connection to the backend
+   closes the existing connection and makes a new one
+   Returns 1 on success, 0 on failure.
+*/
+int
+PQresetStart(PGconn *conn)
+{
+   if (conn)
+   {
+       closePGconn(conn);
+
+       return connectDBStart(conn);
+   }
+
+   return 1;
+}
+
+
+/* PQresetPoll :
+   resets the connection to the backend
+   closes the existing connection and makes a new one
+*/
+
+PostgresPollingStatusType
+PQresetPoll(PGconn *conn)
+{
+   if (conn)
+   {
+       return PQconnectPoll(conn);
+   }
+
+   return PGRES_POLLING_FAILED;
 }
 
 
index d29277bf2004276bfaab4b9515848d347b81ed00..89425e0034c40c1d3de798a72b8260fb89c4a6aa 100644 (file)
@@ -24,7 +24,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.32 1999/11/11 00:10:14 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.33 1999/11/30 03:08:19 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -269,29 +269,69 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
 
 /* --------------------------------------------------------------------- */
 /* pqReadReady: is select() saying the file is ready to read?
+ * Returns -1 on failure, 0 if not ready, 1 if ready.
  */
-static int
+int
 pqReadReady(PGconn *conn)
 {
    fd_set      input_mask;
    struct timeval timeout;
 
-   if (conn->sock < 0)
-       return 0;
+   if (!conn || conn->sock < 0)
+       return -1;
 
    FD_ZERO(&input_mask);
    FD_SET(conn->sock, &input_mask);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
+ retry:
    if (select(conn->sock + 1, &input_mask, (fd_set *) NULL, (fd_set *) NULL,
               &timeout) < 0)
    {
+       if (errno == EINTR)
+           /* Interrupted system call - we'll just try again */
+           goto retry;
+
        printfPQExpBuffer(&conn->errorMessage,
                          "pqReadReady() -- select() failed: errno=%d\n%s\n",
                          errno, strerror(errno));
-       return 0;
+       return -1;
+   }
+
+   return FD_ISSET(conn->sock, &input_mask) ? 1 : 0;
+}
+
+/* --------------------------------------------------------------------- */
+/* pqWriteReady: is select() saying the file is ready to write?
+ * Returns -1 on failure, 0 if not ready, 1 if ready.
+ */
+int
+pqWriteReady(PGconn *conn)
+{
+   fd_set      input_mask;
+   struct timeval timeout;
+
+   if (!conn || conn->sock < 0)
+       return -1;
+
+   FD_ZERO(&input_mask);
+   FD_SET(conn->sock, &input_mask);
+   timeout.tv_sec = 0;
+   timeout.tv_usec = 0;
+ retry:
+   if (select(conn->sock + 1, (fd_set *) NULL, &input_mask, (fd_set *) NULL,
+              &timeout) < 0)
+   {
+       if (errno == EINTR)
+           /* Interrupted system call - we'll just try again */
+           goto retry;
+
+       printfPQExpBuffer(&conn->errorMessage,
+                         "pqWriteReady() -- select() failed: errno=%d\n%s\n",
+                         errno, strerror(errno));
+       return -1;
    }
-   return FD_ISSET(conn->sock, &input_mask);
+   return FD_ISSET(conn->sock, &input_mask) ? 1 : 0;
 }
 
 /* --------------------------------------------------------------------- */
@@ -418,8 +458,17 @@ tryAgain:
     * be taken much, since in normal practice we should not be trying to
     * read data unless the file selected for reading already.
     */
-   if (!pqReadReady(conn))
-       return 0;               /* definitely no data available */
+   switch (pqReadReady(conn))
+   {
+       case 0:
+           /* definitely no data available */
+           return 0;
+       case 1:
+           /* ready for read */
+           break;
+       default:
+           goto definitelyFailed;
+   }
 
    /*
     * Still not sure that it's EOF, because some data could have just
@@ -570,6 +619,10 @@ pqFlush(PGconn *conn)
        if (len > 0)
        {
            /* We didn't send it all, wait till we can send more */
+
+           /* At first glance this looks as though it should block.  I think
+            * that it will be OK though, as long as the socket is
+            * non-blocking. */
            if (pqWait(FALSE, TRUE, conn))
                return EOF;
        }
@@ -599,9 +652,9 @@ pqWait(int forRead, int forWrite, PGconn *conn)
        return EOF;
    }
 
-   /* loop in case select returns EINTR */
-   for (;;)
+   if (forRead || forWrite)
    {
+   retry:
        FD_ZERO(&input_mask);
        FD_ZERO(&output_mask);
        if (forRead)
@@ -612,14 +665,12 @@ pqWait(int forRead, int forWrite, PGconn *conn)
                   (struct timeval *) NULL) < 0)
        {
            if (errno == EINTR)
-               continue;
+               goto retry;
            printfPQExpBuffer(&conn->errorMessage,
                              "pqWait() -- select() failed: errno=%d\n%s\n",
                              errno, strerror(errno));
            return EOF;
        }
-       /* On nonerror return, assume we're done */
-       break;
    }
 
    return 0;
index b1c97e046a60ec93439798182fd063bd3a4279d0..5a25c40121f199cc642f52da0d6361a700286bf5 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-fe.h,v 1.52 1999/11/11 00:10:14 momjian Exp $
+ * $Id: libpq-fe.h,v 1.53 1999/11/30 03:08:19 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,10 +29,40 @@ extern      "C"
 
    typedef enum
    {
+       /* Although you may decide to change this list in some way,
+          values which become unused should never be removed, nor
+           should constants be redefined - that would break 
+           compatibility with existing code.                           */
        CONNECTION_OK,
-       CONNECTION_BAD
+       CONNECTION_BAD,
+       /* Non-blocking mode only below here */
+       /* The existence of these should never be relied upon - they
+          should only be used for user feedback or similar purposes.  */
+       CONNECTION_STARTED,     /* Waiting for connection to be made.  */
+       CONNECTION_MADE,        /* Connection OK; waiting to send.     */
+       CONNECTION_AWAITING_RESPONSE,   /* Waiting for a response
+                                          from the backend.           */
+       CONNECTION_AUTH_RESPONSE,       /* Got an authentication
+                                          response; about to deal
+                                          with it.                    */
+       CONNECTION_ERROR_RESPONSE,      /* Got an error
+                                          response; about to deal 
+                                          with it.                    */
+       CONNECTION_AUTH_OK,             /* Received authentication;
+                                          waiting for ReadyForQuery
+                                          etc.                        */
+       CONNECTION_SETENV               /* Negotiating environment.    */
    } ConnStatusType;
 
+   typedef enum
+   {
+       PGRES_POLLING_FAILED = 0,
+       PGRES_POLLING_READING,     /* These two indicate that one may    */
+       PGRES_POLLING_WRITING,     /* use select before polling again.   */
+       PGRES_POLLING_OK,
+       PGRES_POLLING_ACTIVE       /* Can call poll function immediately.*/
+   } PostgresPollingStatusType;
+
    typedef enum
    {
        PGRES_EMPTY_QUERY = 0,
@@ -67,6 +97,12 @@ extern       "C"
  */
    typedef struct pg_result PGresult;
 
+/* PGsetenvHandle is an opaque handle which is returned by PQsetenvStart and
+ * which should be passed to PQsetenvPoll or PQsetenvAbort in order to refer
+ * to the particular process being performed.
+ */
+   typedef struct pg_setenv_state *PGsetenvHandle;
+
 /* PGnotify represents the occurrence of a NOTIFY message.
  * Ideally this would be an opaque typedef, but it's so simple that it's
  * unlikely to change.
@@ -152,11 +188,15 @@ extern        "C"
 /* === in fe-connect.c === */
 
    /* make a new client connection to the backend */
+   /* Asynchronous (non-blocking) */
+   extern PGconn *PQconnectStart(const char *conninfo);
+   extern PostgresPollingStatusType PQconnectPoll(PGconn *conn);
+   /* Synchronous (blocking) */
    extern PGconn *PQconnectdb(const char *conninfo);
    extern PGconn *PQsetdbLogin(const char *pghost, const char *pgport,
                                const char *pgoptions, const char *pgtty,
-                                           const char *dbName,
-                                    const char *login, const char *pwd);
+                               const char *dbName,
+                               const char *login, const char *pwd);
 #define PQsetdb(M_PGHOST,M_PGPORT,M_PGOPT,M_PGTTY,M_DBNAME)  \
    PQsetdbLogin(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME, NULL, NULL)
 
@@ -170,6 +210,10 @@ extern     "C"
     * close the current connection and restablish a new one with the same
     * parameters
     */
+   /* Asynchronous (non-blocking) */
+   extern int PQresetStart(PGconn *conn);
+   extern PostgresPollingStatusType PQresetPoll(PGconn *conn);
+   /* Synchronous (blocking) */
    extern void PQreset(PGconn *conn);
 
    /* issue a cancel request */
@@ -195,6 +239,15 @@ extern     "C"
    /* Override default notice processor */
    extern PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg);
 
+   /* Passing of environment variables */
+   /* Asynchronous (non-blocking) */
+   extern PGsetenvHandle PQsetenvStart(PGconn *conn);
+   extern PostgresPollingStatusType PQsetenvPoll(PGsetenvHandle handle);
+   extern void PQsetenvAbort(PGsetenvHandle handle);
+
+   /* Synchronous (blocking) */
+   extern int PQsetenv(PGconn *conn);
+
 /* === in fe-exec.c === */
 
    /* Simple synchronous query */
index 7323e9fe065db71d8f429b29c0706bbfdc444a78..64566d6c743f5273e24f8757c81e79d5a109e532 100644 (file)
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.13 1999/11/11 00:10:14 momjian Exp $
+ * $Id: libpq-int.h,v 1.14 1999/11/30 03:08:19 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -168,6 +168,10 @@ struct pg_conn
    /* Saved values of connection options */
    char       *pghost;         /* the machine on which the server is
                                 * running */
+   char       *pghostaddr;     /* the IPv4 address of the machine on
+                                * which the server is running, in
+                                * IPv4 numbers-and-dots notation. Takes
+                                * precedence over above. */
    char       *pgport;         /* the server's communication port */
    char       *pgtty;          /* tty on which the backend messages is
                                 * displayed (NOT ACTUALLY USED???) */
@@ -220,6 +224,9 @@ struct pg_conn
    PGresult   *result;         /* result being constructed */
    PGresAttValue *curTuple;    /* tuple currently being read */
 
+   /* Handle for setenv request.  Used during connection only. */
+   PGsetenvHandle setenv_handle;
+
 #ifdef USE_SSL
         SSL *ssl;
 #endif
@@ -268,6 +275,8 @@ extern int  pqPutInt(int value, size_t bytes, PGconn *conn);
 extern int pqReadData(PGconn *conn);
 extern int pqFlush(PGconn *conn);
 extern int pqWait(int forRead, int forWrite, PGconn *conn);
+extern int pqReadReady(PGconn *conn);
+extern int pqWriteReady(PGconn *conn);
 
 /* bits in a byte */
 #define BYTELEN 8