<synopsis>
PGconn *PQconnectdb(const char *conninfo)
</synopsis>
- This routine opens a new database connection using the parameters
- taken from the string <literal>conninfo</literal>. 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 <literal>conninfo</literal>. 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.
</Para>
<Para>
Each parameter setting is in the form <literal>keyword = value</literal>.
<term><literal>host</literal></term>
<ListItem>
<Para>
- 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.
+ </Para>
+ </ListItem>
+ </VarListEntry>
+
+ <VarListEntry>
+ <term><literal>hostaddr</literal></term>
+ <ListItem>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ Without both a host name and host address, libpq will connect using a
+ local Unix domain socket.
</Para>
</ListItem>
</VarListEntry>
The return value is a pointer to an abstract struct
representing the connection to the backend.
</Para>
+ <Para>
+ This function is not thread-safe.
+ </Para>
</ListItem>
<ListItem>
This is the predecessor of <function>PQconnectdb</function> with a fixed number
of parameters but the same functionality.
</Para>
+ <Para>
+ This function is not thread-safe.
+ </Para>
</ListItem>
<ListItem>
</Para>
</ListItem>
+ <ListItem>
+ <Para>
+ <Function>PQconnectStart</Function>
+ <Function>PQconnectPoll</Function>
+ Make a connection to the database server in a non-blocking manner.
+<synopsis>
+PGconn *PQconnectStart(const char *conninfo)
+</synopsis>
+<synopsis>
+PostgresPollingStatusType *PQconnectPoll(PQconn *conn)
+</synopsis>
+ 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.
+ </Para>
+ <Para>
+ The database connection is made using the parameters taken from the string
+ <literal>conninfo</literal>, passed to PQconnectStart. This string is in
+ the same format as described above for PQconnectdb.
+ </Para>
+ <Para>
+ Neither PQconnectStart nor PQconnectPoll will block, as long as a number of
+ restrictions are met:
+ <ItemizedList>
+ <ListItem>
+ <Para>
+ 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.
+ </Para>
+ </ListItem>
+
+ <ListItem>
+ <Para>
+ If you call PQtrace, ensure that the stream object into which you trace
+ will not block.
+ </Para>
+ </ListItem>
+
+ <ListItem>
+ <Para>
+ You ensure for yourself that the socket is in the appropriate state
+ before calling PQconnectPoll, as described below.
+ </Para>
+ </ListItem>
+ </ItemizedList>
+ </Para>
+
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ 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:
+ <ItemizedList>
+ <ListItem>
+ <Para>
+ CONNECTION_STARTED: Waiting for connection to be made.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_MADE: Connection OK; waiting to send.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_AWAITING_RESPONSE: Waiting for a response from the backend.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_AUTH_RESPONSE: Got an authentication response; about to deal
+ with it.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_ERROR_RESPONSE: Got an error response; about to deal with it.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_AUTH_OK: Received authentication; waiting for ReadyForQuery etc.
+ </Para>
+ </ListItem>
+ <ListItem>
+ <Para>
+ CONNECTION_SETENV: Negotiating environment.
+ </Para>
+ </ListItem>
+ </ItemizedList>
+
+ 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:
+<ProgramListing>
+ switch(PQstatus(conn))
+ {
+ case CONNECTION_STARTED:
+ feedback = "Connecting...";
+ break;
+
+ case CONNECTION_MADE:
+ feedback = "Connected to server...";
+ break;
+.
+.
+.
+ default:
+ feedback = "Connecting...";
+ }
+</ProgramListing>
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ PQconnectPoll will currently block if libpq is compiled with USE_SSL
+ defined. This restriction may be removed in the future.
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ <Para>
+ These functions are not thread-safe.
+ </Para>
+ </ListItem>
+
<ListItem>
<Para>
<Function>PQconndefaults</Function> Returns the default connection options.
will depend on environment variables and other context.
Callers must treat the connection options data as read-only.
</Para>
+ <Para>
+ This function is not thread-safe.
+ </Para>
</ListItem>
<ListItem>
</Para>
</ListItem>
+ <ListItem>
+ <Para>
+ <Function>PQresetStart</Function>
+ <Function>PQresetPoll</Function>
+ Reset the communication port with the backend, in a non-blocking manner.
+<synopsis>
+int PQresetStart(PGconn *conn);
+</synopsis>
+<synopsis>
+PostgresPollingStatusType PQresetPoll(PGconn *conn);
+</synopsis>
+ 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.
+ </Para>
+ <Para>
+ 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.
+ </Para>
+ </ListItem>
+
</ItemizedList>
</Para>
<Para>
<Function>PQstatus</Function>
Returns the status of the connection.
- The status can be <literal>CONNECTION_OK</literal> or <literal>CONNECTION_BAD</literal>.
<synopsis>
ConnStatusType PQstatus(const PGconn *conn)
</synopsis>
</Para>
<Para>
-A failed connection attempt is signaled by status <literal>CONNECTION_BAD</literal>.
-Ordinarily, an OK status will remain so until <function>PQfinish</function>, 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 -
+<literal>CONNECTION_OK</literal> or <literal>CONNECTION_BAD</literal>. A good
+connection to the database has the status CONNECTION_OK. A failed connection
+attempt is signaled by status <literal>CONNECTION_BAD</literal>. Ordinarily,
+an OK status will remain so until <function>PQfinish</function>, but a
communications failure might result in the status changing to
-<literal>CONNECTION_BAD</literal> prematurely. In that case the application could
-try to recover by calling <function>PQreset</function>.
+<literal>CONNECTION_BAD</literal> prematurely. In that case the application
+could try to recover by calling <function>PQreset</function>.
</Para>
+<Para>
+See the entry for PQconnectStart and PQconnectPoll with regards to other status codes
+that might be seen.
</ListItem>
<ListItem>
</Para>
</ListItem>
+ <ListItem>
+ <Para>
+ <Function>PQsetenvStart</Function>
+ <Function>PQsetenvPoll</Function>
+ <Function>PQsetenvAbort</Function>
+ Perform an environment negotiation.
+<synopsis>
+PGsetenvHandle *PQsetenvStart(PGconn *conn)
+</synopsis>
+<synopsis>
+PostgresPollingStatusType *PQsetenvPoll(PGsetenvHandle handle)
+</synopsis>
+<synopsis>
+void PQsetenvAbort(PGsetenvHandle handle)
+</synopsis>
+ 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.
+ </Para>
+ <Para>
+ These functions will not block, subject to the restrictions applied to
+ PQconnectStart and PQconnectPoll.
+ </Para>
+ <Para>
+ 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).
+ </Para>
+ <Para>
+ Poll the procedure using PQsetenvPoll, in exactly the same way as you would
+ create a connection using PQconnectPoll.
+ </Para>
+ <Para>
+ The procedure may be aborted at any time by calling PQsetenvAbort(handle).
+ </Para>
+ <Para>
+ These functions are not thread-safe.
+ </Para>
+ </ListItem>
+
+ <ListItem>
+ <Para>
+ <Function>PQsetenv</Function>
+ Perform an environment negotiation.
+<synopsis>
+int PQsetenv(PGconn *conn)
+</synopsis>
+ This function performs the same duties as PQsetenvStart and PQsetenvPoll, but
+ blocks to do so. It returns 1 on success and 0 on failure.
+ </Para>
</ItemizedList>
</Para>
</Sect1>
*
*
* 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 <sys/types.h>
+#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/tcp.h>
+#include <arpa/inet.h>
#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);
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
{"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},
}
};
+
+/* ----------------
+ * 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;
* Allocate memory for the conn structure
* ----------
*/
+
conn = makeEmptyPGconn();
if (conn == NULL)
return (PGconn *) NULL;
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");
* 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;
}
}
if (error)
+ {
conn->status = CONNECTION_BAD;
+ }
else
- conn->status = connectDB(conn);
-
+ {
+ (void)(!connectDBStart(conn) || !connectDBComplete(conn));
+ }
+
return 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;
}
}
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;
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 <eem21@cam.ac.uk>.
+ * ---------- */
+#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;
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,
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
#endif
if (conn->pghost)
free(conn->pghost);
+ if (conn->pghostaddr)
+ free(conn->pghostaddr);
if (conn->pgport)
free(conn->pgport);
if (conn->pgtty)
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)
{
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;
}