aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/async.c13
-rw-r--r--src/backend/tcop/dest.c107
-rw-r--r--src/backend/tcop/fastpath.c3
-rw-r--r--src/backend/tcop/postgres.c12
-rw-r--r--src/bin/psql/psql.c50
-rw-r--r--src/include/libpq/pqcomm.h4
-rw-r--r--src/include/tcop/dest.h7
-rw-r--r--src/interfaces/libpgtcl/pgtclId.c46
-rw-r--r--src/interfaces/libpq/Makefile.in12
-rw-r--r--src/interfaces/libpq/fe-connect.c603
-rw-r--r--src/interfaces/libpq/fe-exec.c2212
-rw-r--r--src/interfaces/libpq/fe-misc.c461
-rw-r--r--src/interfaces/libpq/libpq-fe.h310
13 files changed, 1969 insertions, 1871 deletions
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 551c3bad3fd..fcf02b2e4bf 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.31 1998/04/27 04:05:08 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.32 1998/05/06 23:49:52 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,8 +20,7 @@
* end of commit),
* 2.a If the process is the same as the backend process that issued
* notification (we are notifying something that we are listening),
- * signal the corresponding frontend over the comm channel using the
- * out-of-band channel.
+ * signal the corresponding frontend over the comm channel.
* 2.b For all other listening processes, we send kill(2) to wake up
* the listening backend.
* 3. Upon receiving a kill(2) signal from another backend process notifying
@@ -30,7 +29,7 @@
* 3.a We are sleeping, wake up and signal our frontend.
* 3.b We are in middle of another transaction, wait until the end of
* of the current transaction and signal our frontend.
- * 4. Each frontend receives this notification and prcesses accordingly.
+ * 4. Each frontend receives this notification and processes accordingly.
*
* -- jw, 12/28/93
*
@@ -547,12 +546,6 @@ Async_UnlistenOnExit(int code, /* from exitpg */
* Results:
* XXX
*
- * Side effects:
- *
- * We make use of the out-of-band channel to transmit the
- * notification to the front end. The actual data transfer takes
- * place at the front end's request.
- *
* --------------------------------------------------------------
*/
GlobalMemory notifyContext = NULL;
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 94d9eaad627..74da434f364 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.17 1998/02/26 04:36:24 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.18 1998/05/06 23:49:59 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -15,7 +15,8 @@
* INTERFACE ROUTINES
* BeginCommand - prepare destination for tuples of the given type
* EndCommand - tell destination that no more tuples will arrive
- * NullCommand - tell dest that the last of a query sequence was processed
+ * NullCommand - tell dest that an empty query string was recognized
+ * ReadyForQuery - tell dest that we are ready for a new query
*
* NOTES
* These routines do the appropriate work before and after
@@ -115,16 +116,10 @@ EndCommand(char *commandTag, CommandDest dest)
sprintf(buf, "%s%s", commandTag, CommandInfo);
CommandInfo[0] = 0;
pq_putstr(buf);
- pq_flush();
break;
case Local:
case Debug:
- break;
- case CopyEnd:
- pq_putnchar("Z", 1);
- pq_flush();
- break;
case None:
default:
break;
@@ -139,28 +134,37 @@ EndCommand(char *commandTag, CommandDest dest)
*
* COPY rel FROM stdin
*
+ * NOTE: the message code letters are changed at protocol version 2.0
+ * to eliminate possible confusion with data tuple messages.
*/
void
SendCopyBegin(void)
{
- pq_putnchar("B", 1);
-/* pq_putint(0, 4); */
- pq_flush();
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+ pq_putnchar("H", 1); /* new way */
+ else
+ pq_putnchar("B", 1); /* old way */
}
void
ReceiveCopyBegin(void)
{
- pq_putnchar("D", 1);
-/* pq_putint(0, 4); */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+ pq_putnchar("G", 1); /* new way */
+ else
+ pq_putnchar("D", 1); /* old way */
+ /* We *must* flush here to ensure FE knows it can send. */
pq_flush();
}
/* ----------------
- * NullCommand - tell dest that the last of a query sequence was processed
+ * NullCommand - tell dest that an empty query string was recognized
*
- * Necessary to implement the hacky FE/BE interface to handle
- * multiple-return queries.
+ * In FE/BE protocol version 1.0, this hack is necessary to support
+ * libpq's crufty way of determining whether a multiple-command
+ * query string is done. In protocol 2.0 it's probably not really
+ * necessary to distinguish empty queries anymore, but we still do it
+ * for backwards compatibility with 1.0.
* ----------------
*/
void
@@ -168,46 +172,46 @@ NullCommand(CommandDest dest)
{
switch (dest)
{
- case RemoteInternal:
- case Remote:
+ case RemoteInternal:
+ case Remote:
{
-#if 0
-
- /*
- * Do any asynchronous notification. If front end wants
- * to poll, it can send null queries to call this
- * function.
- */
- PQNotifyList *nPtr;
- MemoryContext orig;
-
- if (notifyContext == NULL)
- {
- notifyContext = CreateGlobalMemory("notify");
- }
- orig = MemoryContextSwitchTo((MemoryContext) notifyContext);
-
- for (nPtr = PQnotifies();
- nPtr != NULL;
- nPtr = (PQNotifyList *) SLGetSucc(&nPtr->Node))
- {
- pq_putnchar("A", 1);
- pq_putint(0, sizeof(int4));
- pq_putstr(nPtr->relname);
- pq_putint(nPtr->be_pid, sizeof(nPtr->be_pid));
- PQremoveNotify(nPtr);
- }
- pq_flush();
- PQcleanNotify();/* garbage collect */
- MemoryContextSwitchTo(orig);
-#endif
/* ----------------
- * tell the fe that the last of the queries has finished
+ * tell the fe that we saw an empty query string
* ----------------
*/
-/* pq_putnchar("I", 1); */
pq_putstr("I");
- /* pq_putint(0, 4); */
+ }
+ break;
+
+ case Local:
+ case Debug:
+ case None:
+ default:
+ break;
+ }
+}
+
+/* ----------------
+ * ReadyForQuery - tell dest that we are ready for a new query
+ *
+ * The ReadyForQuery message is sent in protocol versions 2.0 and up
+ * so that the FE can tell when we are done processing a query string.
+ *
+ * Note that by flushing the stdio buffer here, we can avoid doing it
+ * most other places and thus reduce the number of separate packets sent.
+ * ----------------
+ */
+void
+ReadyForQuery(CommandDest dest)
+{
+ switch (dest)
+ {
+ case RemoteInternal:
+ case Remote:
+ {
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+ pq_putnchar("Z", 1);
+ /* Flush output at end of cycle in any case. */
pq_flush();
}
break;
@@ -264,7 +268,6 @@ BeginCommand(char *pname,
* send fe info on tuples we're about to send
* ----------------
*/
- pq_flush();
pq_putnchar("P", 1);/* new portal.. */
pq_putstr(pname); /* portal name */
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 9a59c43db56..7ec50debf8a 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.16 1998/04/26 04:07:22 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.17 1998/05/06 23:50:10 momjian Exp $
*
* NOTES
* This cruft is the server side of PQfn.
@@ -113,7 +113,6 @@ SendFunctionResult(Oid fid, /* function id */
}
pq_putnchar("0", 1);
- pq_flush();
}
/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index efba35f2404..87e1bf9fe56 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.67 1998/02/26 04:36:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.68 1998/05/06 23:50:19 momjian Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -1302,7 +1302,7 @@ PostgresMain(int argc, char *argv[])
if (IsUnderPostmaster == false)
{
puts("\nPOSTGRES backend interactive interface");
- puts("$Revision: 1.67 $ $Date: 1998/02/26 04:36:31 $");
+ puts("$Revision: 1.68 $ $Date: 1998/05/06 23:50:19 $");
}
/* ----------------
@@ -1317,6 +1317,12 @@ PostgresMain(int argc, char *argv[])
for (;;)
{
/* ----------------
+ * (0) tell the frontend we're ready for a new query.
+ * ----------------
+ */
+ ReadyForQuery(Remote);
+
+ /* ----------------
* (1) read a command.
* ----------------
*/
@@ -1391,8 +1397,8 @@ PostgresMain(int argc, char *argv[])
* ----------------
*/
case 'X':
- IsEmptyQuery = true;
pq_close();
+ exitpg(0);
break;
default:
diff --git a/src/bin/psql/psql.c b/src/bin/psql/psql.c
index ad2b93e93dd..1864475dd3e 100644
--- a/src/bin/psql/psql.c
+++ b/src/bin/psql/psql.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.139 1998/05/04 02:02:01 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.140 1998/05/06 23:50:23 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -284,6 +284,38 @@ PSQLexec(PsqlSettings *pset, char *query)
}
/*
+ * Code to support command cancellation.
+ * If interactive, we enable a SIGINT signal catcher that sends
+ * a cancel request to the backend.
+ * Note that sending the cancel directly from the signal handler
+ * is safe only because the cancel is sent as an OOB message.
+ * If it were inline data, then we'd risk inserting it into the
+ * middle of a normal data message by doing this.
+ * (It's probably not too cool to write on stderr, for that matter...
+ * but for debugging purposes we'll risk that.)
+ */
+
+static PGconn * cancelConn = NULL; /* connection to try cancel on */
+
+static void
+handle_sigint (SIGNAL_ARGS)
+{
+ if (cancelConn == NULL)
+ exit(1); /* accept signal if no connection */
+ /* Try to send cancel request */
+ if (PQrequestCancel(cancelConn))
+ {
+ fprintf(stderr, "\nCANCEL request sent\n");
+ }
+ else
+ {
+ fprintf(stderr, "\nCannot send cancel request:\n%s\n",
+ PQerrorMessage(cancelConn));
+ }
+}
+
+
+/*
* listAllDbs
*
* list all the databases in the system returns 0 if all went well
@@ -1099,8 +1131,7 @@ SendQuery(bool *success_p, PsqlSettings *pset, const char *query,
exit(2); /* we are out'ta here */
}
/* check for asynchronous returns */
- notify = PQnotifies(pset->db);
- if (notify)
+ while ((notify = PQnotifies(pset->db)) != NULL)
{
fprintf(stderr,
"ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
@@ -1416,6 +1447,7 @@ do_connect(const char *new_dbname,
}
else
{
+ cancelConn = pset->db; /* redirect sigint's loving attentions */
PQfinish(olddb);
free(pset->prompt);
pset->prompt = malloc(strlen(PQdb(pset->db)) + 10);
@@ -2462,11 +2494,18 @@ main(int argc, char **argv)
settings.opt.fieldSep = strdup(DEFAULT_FIELD_SEP);
settings.opt.pager = 1;
if (!isatty(0) || !isatty(1))
+ {
+ /* Noninteractive defaults */
settings.notty = 1;
-#ifdef USE_READLINE
+ }
else
+ {
+ /* Interactive defaults */
+ pqsignal(SIGINT, handle_sigint); /* control-C => cancel */
+#ifdef USE_READLINE
settings.useReadline = 1;
#endif
+ }
#ifdef PSQL_ALWAYS_GET_PASSWORDS
settings.getPassword = 1;
#else
@@ -2580,6 +2619,9 @@ main(int argc, char **argv)
PQfinish(settings.db);
exit(1);
}
+
+ cancelConn = settings.db; /* enable SIGINT to send cancel */
+
if (listDatabases)
{
exit(listAllDbs(&settings));
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 59a70b655c9..867e91c060f 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
- * $Id: pqcomm.h,v 1.24 1998/03/02 05:42:15 scrappy Exp $
+ * $Id: pqcomm.h,v 1.25 1998/05/06 23:50:32 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -66,7 +66,7 @@ typedef union SockAddr
/* The earliest and latest frontend/backend protocol version supported. */
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(0,0)
-#define PG_PROTOCOL_LATEST PG_PROTOCOL(1,0)
+#define PG_PROTOCOL_LATEST PG_PROTOCOL(2,0)
/*
* All packets sent to the postmaster start with the length. This is omitted
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 3f3dd71b123..ef1dbbc77f7 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -26,7 +26,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
- * $Id: dest.h,v 1.13 1998/02/26 04:43:39 momjian Exp $
+ * $Id: dest.h,v 1.14 1998/05/06 23:50:49 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -46,10 +46,6 @@ typedef enum
Debug, /* results go to debugging output */
Local, /* results go in local portal buffer */
Remote, /* results sent to frontend process */
- CopyBegin, /* results sent to frontend process but
- * are strings */
- CopyEnd, /* results sent to frontend process but
- * are strings */
RemoteInternal, /* results sent to frontend process in
* internal (binary) form */
SPI /* results sent to SPI manager */
@@ -70,6 +66,7 @@ extern void EndCommand(char *commandTag, CommandDest dest);
extern void SendCopyBegin(void);
extern void ReceiveCopyBegin(void);
extern void NullCommand(CommandDest dest);
+extern void ReadyForQuery(CommandDest dest);
extern void
BeginCommand(char *pname, int operation, TupleDesc attinfo,
bool isIntoRel, bool isIntoPortal, char *tag,
diff --git a/src/interfaces/libpgtcl/pgtclId.c b/src/interfaces/libpgtcl/pgtclId.c
index 971b04039b4..1707e9b7750 100644
--- a/src/interfaces/libpgtcl/pgtclId.c
+++ b/src/interfaces/libpgtcl/pgtclId.c
@@ -12,7 +12,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.8 1998/03/15 08:03:00 scrappy Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.9 1998/05/06 23:51:00 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -48,7 +48,7 @@ int PgInputProc(DRIVER_INPUT_PROTO)
{
Pg_ConnectionId *connid;
PGconn *conn;
- int c;
+ char c;
int avail;
connid = (Pg_ConnectionId *)cData;
@@ -64,13 +64,24 @@ int PgInputProc(DRIVER_INPUT_PROTO)
return PgEndCopy(connid, errorCodePtr);
}
+ /* Try to load any newly arrived data */
+ errno = 0;
+
+ if (pqReadData(conn) < 0) {
+ *errorCodePtr = errno ? errno : EIO;
+ return -1;
+ }
+
+ /* Move data from libpq's buffer to tcl's */
+
+ conn->inCursor = conn->inStart;
+
avail = bufSize;
while (avail > 0 &&
- (c = pqGetc(conn->Pfin, conn->Pfdebug)) != EOF) {
- /* fprintf(stderr, "%d: got char %c\n", bufSize-avail, c); */
+ pqGetc(&c, conn) == 0) {
*buf++ = c;
--avail;
- if (c == '\n' && bufSize-avail > 3) {
+ if (c == '\n' && bufSize-avail >= 3) {
if ((bufSize-avail == 3 || buf[-4] == '\n') &&
buf[-3] == '\\' && buf[-2] == '.') {
avail += 3;
@@ -79,6 +90,8 @@ int PgInputProc(DRIVER_INPUT_PROTO)
}
}
}
+ /* Accept the data permanently */
+ conn->inStart = conn->inCursor;
/* fprintf(stderr, "returning %d chars\n", bufSize - avail); */
return bufSize - avail;
}
@@ -100,16 +113,15 @@ int PgOutputProc(DRIVER_OUTPUT_PROTO)
return -1;
}
- /*
- fprintf(stderr, "PgOutputProc called: bufSize=%d: atend:%d <", bufSize,
- strncmp(buf, "\\.\n", 3));
- fwrite(buf, 1, bufSize, stderr);
- fputs(">\n", stderr);
- */
- fwrite(buf, 1, bufSize, conn->Pfout);
- if (bufSize > 2 && strncmp(&buf[bufSize-3], "\\.\n", 3) == 0) {
- /* fprintf(stderr,"checking closure\n"); */
- fflush(conn->Pfout);
+ errno = 0;
+
+ if (pqPutnchar(buf, bufSize, conn)) {
+ *errorCodePtr = errno ? errno : EIO;
+ return -1;
+ }
+
+ if (bufSize >= 3 && strncmp(&buf[bufSize-3], "\\.\n", 3) == 0) {
+ (void) pqFlush(conn);
if (PgEndCopy(connid, errorCodePtr) == -1)
return -1;
}
@@ -156,10 +168,10 @@ PgSetConnectionId(Tcl_Interp *interp, PGconn *conn)
for (i = 0; i < RES_START; i++) connid->results[i] = NULL;
Tcl_InitHashTable(&connid->notify_hash, TCL_STRING_KEYS);
- sprintf(connid->id, "pgsql%d", fileno(conn->Pfout));
+ sprintf(connid->id, "pgsql%d", PQsocket(conn));
#if TCL_MAJOR_VERSION == 7 && TCL_MINOR_VERSION == 5
- conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, conn->Pfin, conn->Pfout, (ClientData)connid);
+ conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, NULL, NULL, (ClientData)connid);
#else
conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, (ClientData)connid,
TCL_READABLE | TCL_WRITABLE);
diff --git a/src/interfaces/libpq/Makefile.in b/src/interfaces/libpq/Makefile.in
index 779bc7de6bf..025b0b9363d 100644
--- a/src/interfaces/libpq/Makefile.in
+++ b/src/interfaces/libpq/Makefile.in
@@ -7,7 +7,7 @@
#
#
# IDENTIFICATION
-# $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.16 1998/04/27 14:55:46 scrappy Exp $
+# $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.17 1998/05/06 23:51:06 momjian Exp $
#
#-------------------------------------------------------------------------
@@ -25,14 +25,13 @@ ifdef KRBVERS
CFLAGS+= $(KRBFLAGS)
endif
-OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-lobj.o \
- dllist.o pqsignal.o pqcomprim.o
+OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+ dllist.o pqsignal.o
# Shared library stuff
shlib :=
install-shlib-dep :=
ifeq ($(PORTNAME), linux)
- LINUX_ELF=@LINUX_ELF@
ifdef LINUX_ELF
install-shlib-dep := install-shlib
shlib := libpq.so.$(SO_MAJOR_VERSION).$(SO_MINOR_VERSION)
@@ -84,9 +83,6 @@ fe-lobj.o: $(SRCDIR)/backend/fmgr.h
dllist.c: $(SRCDIR)/backend/lib/dllist.c
-ln -s $(SRCDIR)/backend/lib/dllist.c .
-pqcomprim.c: $(SRCDIR)/backend/libpq/pqcomprim.c
- -ln -s $(SRCDIR)/backend/libpq/pqcomprim.c .
-
# The following rules cause dependencies in the backend directory to
# get made if they don't exist, but don't cause them to get remade if they
# are out of date.
@@ -183,7 +179,7 @@ depend dep:
.PHONY: clean
clean:
- rm -f libpq.a $(shlib) $(OBJS) c.h dllist.c pqcomprim.c libpq.so
+ rm -f libpq.a $(shlib) $(OBJS) c.h dllist.c libpq.so
ifeq (depend,$(wildcard depend))
include depend
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 136a5fb602b..288f159c740 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.65 1998/04/21 04:00:06 scrappy Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.66 1998/05/06 23:51:11 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -41,14 +41,14 @@
#endif
-/* use a local version instead of the one found in pqpacket.c */
static ConnStatusType connectDB(PGconn *conn);
-
+static PGconn *makeEmptyPGconn(void);
static void freePGconn(PGconn *conn);
static void closePGconn(PGconn *conn);
static int conninfo_parse(const char *conninfo, char *errorMessage);
static char *conninfo_getval(char *keyword);
static void conninfo_free(void);
+/* XXX Why is this not static? */
void PQsetenv(PGconn *conn);
#define NOTIFYLIST_INITIAL_SIZE 10
@@ -162,44 +162,30 @@ PGconn *
PQconnectdb(const char *conninfo)
{
PGconn *conn;
- char errorMessage[ERROR_MSG_LENGTH];
char *tmp;
/* ----------
* Allocate memory for the conn structure
* ----------
*/
- conn = (PGconn *) malloc(sizeof(PGconn));
+ conn = makeEmptyPGconn();
if (conn == NULL)
{
fprintf(stderr,
- "FATAL: PQsetdb() -- unable to allocate memory for a PGconn");
+ "FATAL: PQconnectdb() -- unable to allocate memory for a PGconn");
return (PGconn *) NULL;
}
- MemSet((char *) conn, 0, sizeof(PGconn));
/* ----------
- * Parse the conninfo string and get the fallback resources
+ * Parse the conninfo string and save settings in conn structure
* ----------
*/
- if (conninfo_parse(conninfo, errorMessage) < 0)
+ if (conninfo_parse(conninfo, conn->errorMessage) < 0)
{
conn->status = CONNECTION_BAD;
- strcpy(conn->errorMessage, errorMessage);
conninfo_free();
return conn;
}
-
- /* ----------
- * Setup the conn structure
- * ----------
- */
- conn->lobjfuncs = (PGlobjfuncs *) NULL;
- conn->Pfout = NULL;
- conn->Pfin = NULL;
- conn->Pfdebug = NULL;
- conn->notifyList = DLNewList();
-
tmp = conninfo_getval("host");
conn->pghost = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("port");
@@ -208,12 +194,12 @@ PQconnectdb(const char *conninfo)
conn->pgtty = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("options");
conn->pgoptions = tmp ? strdup(tmp) : NULL;
+ tmp = conninfo_getval("dbname");
+ conn->dbName = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("user");
conn->pguser = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("password");
conn->pgpass = tmp ? strdup(tmp) : NULL;
- tmp = conninfo_getval("dbname");
- conn->dbName = tmp ? strdup(tmp) : NULL;
/* ----------
* Free the connection info - all is in conn now
@@ -226,24 +212,6 @@ PQconnectdb(const char *conninfo)
* ----------
*/
conn->status = connectDB(conn);
- if (conn->status == CONNECTION_OK)
- {
- PGresult *res;
-
- /*
- * Send a blank query to make sure everything works; in
- * particular, that the database exists.
- */
- res = PQexec(conn, " ");
- if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY)
- {
- /* PQexec has put error message in conn->errorMessage */
- closePGconn(conn);
- }
- PQclear(res);
- }
-
- PQsetenv(conn);
return conn;
}
@@ -311,150 +279,119 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons
{
PGconn *conn;
char *tmp;
- char errorMessage[ERROR_MSG_LENGTH];
-
/* An error message from some service we call. */
- bool error;
-
+ bool error = FALSE;
/* We encountered an error that prevents successful completion */
int i;
- conn = (PGconn *) malloc(sizeof(PGconn));
-
+ conn = makeEmptyPGconn();
if (conn == NULL)
+ {
fprintf(stderr,
- "FATAL: PQsetdb() -- unable to allocate memory for a PGconn");
+ "FATAL: PQsetdbLogin() -- unable to allocate memory for a PGconn");
+ return (PGconn *) NULL;
+ }
+
+ if ((pghost == NULL) || pghost[0] == '\0')
+ {
+ if ((tmp = getenv("PGHOST")) != NULL)
+ conn->pghost = strdup(tmp);
+ }
else
+ conn->pghost = strdup(pghost);
+
+ if ((pgport == NULL) || pgport[0] == '\0')
{
- conn->lobjfuncs = (PGlobjfuncs *) NULL;
- conn->Pfout = NULL;
- conn->Pfin = NULL;
- conn->Pfdebug = NULL;
- conn->notifyList = DLNewList();
+ if ((tmp = getenv("PGPORT")) == NULL)
+ tmp = DEF_PGPORT;
+ conn->pgport = strdup(tmp);
+ }
+ else
+ conn->pgport = strdup(pgport);
- if ((pghost == NULL) || pghost[0] == '\0')
- {
- conn->pghost = NULL;
- if ((tmp = getenv("PGHOST")) != NULL)
- conn->pghost = strdup(tmp);
- }
- else
- conn->pghost = strdup(pghost);
+ if ((pgtty == NULL) || pgtty[0] == '\0')
+ {
+ if ((tmp = getenv("PGTTY")) == NULL)
+ tmp = DefaultTty;
+ conn->pgtty = strdup(tmp);
+ }
+ else
+ conn->pgtty = strdup(pgtty);
- if ((pgport == NULL) || pgport[0] == '\0')
- {
- if ((tmp = getenv("PGPORT")) == NULL)
- tmp = DEF_PGPORT;
- conn->pgport = strdup(tmp);
- }
- else
- conn->pgport = strdup(pgport);
+ if ((pgoptions == NULL) || pgoptions[0] == '\0')
+ {
+ if ((tmp = getenv("PGOPTIONS")) == NULL)
+ tmp = DefaultOption;
+ conn->pgoptions = strdup(tmp);
+ }
+ else
+ conn->pgoptions = strdup(pgoptions);
- if ((pgtty == NULL) || pgtty[0] == '\0')
- {
- if ((tmp = getenv("PGTTY")) == NULL)
- tmp = DefaultTty;
- conn->pgtty = strdup(tmp);
- }
- else
- conn->pgtty = strdup(pgtty);
+ if (login)
+ {
+ conn->pguser = strdup(login);
+ }
+ else if ((tmp = getenv("PGUSER")) != NULL)
+ {
+ conn->pguser = strdup(tmp);
+ }
+ else
+ {
+ conn->pguser = fe_getauthname(conn->errorMessage);
+ }
- if ((pgoptions == NULL) || pgoptions[0] == '\0')
- {
- if ((tmp = getenv("PGOPTIONS")) == NULL)
- tmp = DefaultOption;
- conn->pgoptions = strdup(tmp);
- }
- else
- conn->pgoptions = strdup(pgoptions);
+ if (conn->pguser == NULL)
+ {
+ error = TRUE;
+ sprintf(conn->errorMessage,
+ "FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n");
+ }
- if (login)
- {
- error = FALSE;
- conn->pguser = strdup(login);
- }
- else if ((tmp = getenv("PGUSER")) != NULL)
- {
- error = FALSE;
- conn->pguser = strdup(tmp);
- }
- else
- {
- tmp = fe_getauthname(errorMessage);
- if (tmp == 0)
- {
- error = TRUE;
- sprintf(conn->errorMessage,
- "FATAL: PQsetdb: Unable to determine a Postgres username!\n");
- }
- else
- {
- error = FALSE;
- conn->pguser = tmp;
- }
- }
+ if (pwd)
+ {
+ conn->pgpass = strdup(pwd);
+ }
+ else if ((tmp = getenv("PGPASSWORD")) != NULL)
+ {
+ conn->pgpass = strdup(tmp);
+ }
+ else
+ {
+ conn->pgpass = strdup(DefaultPassword);
+ }
- if (pwd)
- {
- conn->pgpass = strdup(pwd);
- }
- else if ((tmp = getenv("PGPASSWORD")) != NULL)
- {
- conn->pgpass = strdup(tmp);
- }
- else
- conn->pgpass = strdup(DefaultPassword);
+ if ((dbName == NULL) || dbName[0] == '\0')
+ {
+ if ((tmp = getenv("PGDATABASE")) != NULL)
+ conn->dbName = strdup(tmp);
+ else if (conn->pguser)
+ conn->dbName = strdup(conn->pguser);
+ }
+ else
+ conn->dbName = strdup(dbName);
- if (!error)
+ if (conn->dbName)
+ {
+ /*
+ * if the database name is surrounded by double-quotes, then
+ * don't convert case
+ */
+ if (*conn->dbName == '"')
{
- if ((((tmp = (char *) dbName) != NULL) && (dbName[0] != '\0'))
- || ((tmp = getenv("PGDATABASE"))))
- conn->dbName = strdup(tmp);
- else
- conn->dbName = strdup(conn->pguser);
-
- /*
- * if the database name is surrounded by double-quotes, then
- * don't convert case
- */
- if (*conn->dbName == '"')
- {
- strcpy(conn->dbName, conn->dbName + 1);
- *(conn->dbName + strlen(conn->dbName) - 1) = '\0';
- }
- else
- for (i = 0; conn->dbName[i]; i++)
- if (isupper(conn->dbName[i]))
- conn->dbName[i] = tolower(conn->dbName[i]);
+ strcpy(conn->dbName, conn->dbName + 1);
+ conn->dbName[strlen(conn->dbName) - 1] = '\0';
}
else
- conn->dbName = NULL;
-
- if (error)
- conn->status = CONNECTION_BAD;
- else
- {
- conn->status = connectDB(conn);
- /* Puts message in conn->errorMessage */
- if (conn->status == CONNECTION_OK)
- {
- PGresult *res;
-
- /*
- * Send a blank query to make sure everything works; in
- * particular, that the database exists.
- */
- res = PQexec(conn, " ");
- if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY)
- {
- /* PQexec has put error message in conn->errorMessage */
- closePGconn(conn);
- }
- PQclear(res);
- }
- PQsetenv(conn);
- }
+ for (i = 0; conn->dbName[i]; i++)
+ if (isupper(conn->dbName[i]))
+ conn->dbName[i] = tolower(conn->dbName[i]);
}
+
+ if (error)
+ conn->status = CONNECTION_BAD;
+ else
+ conn->status = connectDB(conn);
+
return conn;
}
@@ -468,6 +405,7 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons
static ConnStatusType
connectDB(PGconn *conn)
{
+ PGresult *res;
struct hostent *hp;
StartupPacket sp;
@@ -476,6 +414,7 @@ connectDB(PGconn *conn)
int portno,
family,
len;
+ char beresp;
/*
* Initialize the startup packet.
@@ -506,16 +445,17 @@ connectDB(PGconn *conn)
conn->pghost);
goto connect_errReturn;
}
+ family = AF_INET;
}
- else
+ else {
hp = NULL;
+ family = AF_UNIX;
+ }
-#if FALSE
- MemSet((char *) &port->raddr, 0, sizeof(port->raddr));
-#endif
- portno = atoi(conn->pgport);
- family = (conn->pghost != NULL) ? AF_INET : AF_UNIX;
+ MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr));
conn->raddr.sa.sa_family = family;
+
+ portno = atoi(conn->pgport);
if (family == AF_INET)
{
memmove((char *) &(conn->raddr.in.sin_addr),
@@ -528,7 +468,8 @@ connectDB(PGconn *conn)
{
len = UNIXSOCK_PATH(conn->raddr.un, portno);
}
- /* connect to the server */
+
+ /* Connect to the server */
if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0)
{
(void) sprintf(conn->errorMessage,
@@ -545,6 +486,20 @@ connectDB(PGconn *conn)
conn->pgport);
goto connect_errReturn;
}
+
+ /*
+ * Set the right options.
+ * We need nonblocking I/O, and we don't want delay of outgoing data.
+ */
+
+ if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0)
+ {
+ (void) sprintf(conn->errorMessage,
+ "connectDB() -- fcntl() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ goto connect_errReturn;
+ }
+
if (family == AF_INET)
{
struct protoent *pe;
@@ -561,109 +516,155 @@ connectDB(PGconn *conn)
&on, sizeof(on)) < 0)
{
(void) sprintf(conn->errorMessage,
- "connectDB(): setsockopt failed\n");
+ "connectDB() -- setsockopt failed: errno=%d\n%s\n",
+ errno, strerror(errno));
goto connect_errReturn;
}
}
- /* fill in the client address */
+ /* Fill in the client address */
if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0)
{
(void) sprintf(conn->errorMessage,
- "connectDB() -- getsockname() failed: errno=%d\n%s\n",
+ "connectDB() -- getsockname() failed: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
- /* set up the socket file descriptors */
- conn->Pfout = fdopen(conn->sock, "w");
- conn->Pfin = fdopen(dup(conn->sock), "r");
- if ((conn->Pfout == NULL) || (conn->Pfin == NULL))
- {
- (void) sprintf(conn->errorMessage,
- "connectDB() -- fdopen() failed: errno=%d\n%s\n",
- errno, strerror(errno));
- goto connect_errReturn;
- }
+ /* Ensure our buffers are empty */
+ conn->inStart = conn->inCursor = conn->inEnd = 0;
+ conn->outCount = 0;
/* Send the startup packet. */
if (packetSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK)
{
sprintf(conn->errorMessage,
- "connectDB() -- couldn't send complete packet: errno=%d\n%s\n", errno, strerror(errno));
+ "connectDB() -- couldn't send startup packet: errno=%d\n%s\n",
+ errno, strerror(errno));
goto connect_errReturn;
}
/*
- * Get the response from the backend, either an error message or an
- * authentication request.
+ * Perform the authentication exchange:
+ * wait for backend messages and respond as necessary.
+ * We fall out of this loop when done talking to the postmaster.
*/
- do
+ for (;;)
{
- int beresp;
-
- if ((beresp = pqGetc(conn->Pfin, conn->Pfdebug)) == EOF)
- {
- (void) sprintf(conn->errorMessage,
- "connectDB() -- error getting authentication request\n");
-
+ /* 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;
- /* Handle errors. */
+ if (pqGetc(&beresp, conn))
+ continue; /* no data yet */
+ /* Handle errors. */
if (beresp == 'E')
{
- pqGets(conn->errorMessage, sizeof(conn->errorMessage),
- conn->Pfin, conn->Pfdebug);
-
+ if (pqGets(conn->errorMessage, sizeof(conn->errorMessage), conn))
+ continue;
goto connect_errReturn;
}
- /* Check it was an authentication request. */
-
+ /* Otherwise it should be an authentication request. */
if (beresp != 'R')
{
(void) sprintf(conn->errorMessage,
- "connectDB() -- expected authentication request\n");
-
+ "connectDB() -- expected authentication request\n");
goto connect_errReturn;
}
/* Get the type of request. */
+ if (pqGetInt((int *) &areq, 4, conn))
+ continue;
- if (pqGetInt((int *) &areq, 4, conn->Pfin, conn->Pfdebug))
+ /* Get the password salt if there is one. */
+ if (areq == AUTH_REQ_CRYPT)
{
- (void) sprintf(conn->errorMessage,
- "connectDB() -- error getting authentication request type\n");
+ if (pqGetnchar(conn->salt, sizeof(conn->salt), conn))
+ continue;
+ }
+ /* OK, we successfully read the message; mark data consumed */
+ conn->inStart = conn->inCursor;
+
+ /* Respond to the request if necessary. */
+ if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
+ conn->errorMessage) != STATUS_OK)
+ goto connect_errReturn;
+ if (pqFlush(conn))
goto connect_errReturn;
- }
- /* Get the password salt if there is one. */
+ /* Are we done? */
+ if (areq == AUTH_REQ_OK)
+ break;
+ }
- if (areq == AUTH_REQ_CRYPT &&
- pqGetnchar(conn->salt, sizeof(conn->salt),
- conn->Pfin, conn->Pfdebug))
- {
- (void) sprintf(conn->errorMessage,
- "connectDB() -- error getting password salt\n");
+ /*
+ * 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.)
+ * 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.
+ */
- goto connect_errReturn;
- }
+ 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)
+ sprintf(conn->errorMessage,
+ "connectDB() -- unexpected message during startup\n");
+ PQclear(res);
+ goto connect_errReturn;
+ }
+ /* Given the new protocol that sends a ReadyForQuery message
+ * after successful backend startup, it should no longer be
+ * necessary to send an empty query to test for startup.
+ */
- /* Respond to the request. */
+#if 0
- if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
- conn->errorMessage) != STATUS_OK)
- goto connect_errReturn;
+ /*
+ * Send a blank query to make sure everything works; in
+ * particular, that the database exists.
+ */
+ res = PQexec(conn, " ");
+ if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY)
+ {
+ /* PQexec has put error message in conn->errorMessage */
+ closePGconn(conn);
+ PQclear(res);
+ goto connect_errReturn;
}
- while (areq != AUTH_REQ_OK);
+ PQclear(res);
- /* free the password so it's not hanging out in memory forever */
+#endif
+
+ /* Post-connection housekeeping.
+ * Send environment variables to server
+ */
+
+ PQsetenv(conn);
+
+ /* Free the password so it's not hanging out in memory forever */
+ /* XXX Is this *really* a good idea? The security gain is marginal
+ * if not totally illusory, and it breaks PQreset() for databases
+ * that use passwords.
+ */
if (conn->pgpass != NULL)
{
free(conn->pgpass);
@@ -673,6 +674,11 @@ connectDB(PGconn *conn)
return CONNECTION_OK;
connect_errReturn:
+ if (conn->sock >= 0)
+ {
+ close(conn->sock);
+ conn->sock = -1;
+ }
return CONNECTION_BAD;
}
@@ -705,6 +711,36 @@ PQsetenv(PGconn *conn)
} /* PQsetenv() */
/*
+ * makeEmptyPGconn
+ * - create a PGconn data structure with (as yet) no interesting data
+ */
+static PGconn *
+makeEmptyPGconn(void)
+{
+ PGconn *conn = (PGconn *) malloc(sizeof(PGconn));
+ if (conn == NULL)
+ return conn;
+
+ /* Zero all pointers */
+ MemSet((char *) conn, 0, sizeof(PGconn));
+
+ conn->status = CONNECTION_BAD;
+ conn->asyncStatus = PGASYNC_IDLE;
+ conn->notifyList = DLNewList();
+ conn->sock = -1;
+ conn->inBufSize = 8192;
+ conn->inBuffer = (char *) malloc(conn->inBufSize);
+ conn->outBufSize = 8192;
+ conn->outBuffer = (char *) malloc(conn->outBufSize);
+ if (conn->inBuffer == NULL || conn->outBuffer == NULL)
+ {
+ freePGconn(conn);
+ conn = NULL;
+ }
+ return conn;
+}
+
+/*
* freePGconn
* - free the PGconn data structure
*
@@ -714,22 +750,32 @@ freePGconn(PGconn *conn)
{
if (!conn)
return;
+ PQclearAsyncResult(conn); /* deallocate result and curTuple */
+ if (conn->sock >= 0)
+ close(conn->sock); /* shouldn't happen, but... */
if (conn->pghost)
free(conn->pghost);
+ if (conn->pgport)
+ free(conn->pgport);
if (conn->pgtty)
free(conn->pgtty);
if (conn->pgoptions)
free(conn->pgoptions);
- if (conn->pgport)
- free(conn->pgport);
if (conn->dbName)
free(conn->dbName);
if (conn->pguser)
free(conn->pguser);
if (conn->pgpass)
free(conn->pgpass);
+ /* Note that conn->Pfdebug is not ours to close or free */
if (conn->notifyList)
DLFreeList(conn->notifyList);
+ if (conn->lobjfuncs)
+ free(conn->lobjfuncs);
+ if (conn->inBuffer)
+ free(conn->inBuffer);
+ if (conn->outBuffer)
+ free(conn->outBuffer);
free(conn);
}
@@ -740,42 +786,54 @@ freePGconn(PGconn *conn)
static void
closePGconn(PGconn *conn)
{
-/* GH: What to do for !USE_POSIX_SIGNALS ? */
+ if (conn->sock >= 0)
+ {
+ /*
+ * Try to send close message.
+ * If connection is already gone, that's cool. No reason for kernel
+ * to kill us when we try to write to it. So ignore SIGPIPE signals.
+ */
#if defined(USE_POSIX_SIGNALS)
- struct sigaction ignore_action;
+ struct sigaction ignore_action;
+ struct sigaction oldaction;
- /*
- * This is used as a constant, but not declared as such because the
- * sigaction structure is defined differently on different systems
- */
- struct sigaction oldaction;
+ ignore_action.sa_handler = SIG_IGN;
+ sigemptyset(&ignore_action.sa_mask);
+ ignore_action.sa_flags = 0;
+ sigaction(SIGPIPE, (struct sigaction *) & ignore_action, &oldaction);
- /*
- * If connection is already gone, that's cool. No reason for kernel
- * to kill us when we try to write to it. So ignore SIGPIPE signals.
- */
- ignore_action.sa_handler = SIG_IGN;
- sigemptyset(&ignore_action.sa_mask);
- ignore_action.sa_flags = 0;
- sigaction(SIGPIPE, (struct sigaction *) & ignore_action, &oldaction);
-
- fputs("X\0", conn->Pfout);
- fflush(conn->Pfout);
- sigaction(SIGPIPE, &oldaction, NULL);
+ (void) pqPuts("X", conn);
+ (void) pqFlush(conn);
+
+ sigaction(SIGPIPE, &oldaction, NULL);
#else
- signal(SIGPIPE, SIG_IGN);
- fputs("X\0", conn->Pfout);
- fflush(conn->Pfout);
- signal(SIGPIPE, SIG_DFL);
+ void (*oldsignal)(int);
+
+ oldsignal = signal(SIGPIPE, SIG_IGN);
+
+ (void) pqPuts("X", conn);
+ (void) pqFlush(conn);
+
+ signal(SIGPIPE, oldsignal);
#endif
- if (conn->Pfout)
- fclose(conn->Pfout);
- if (conn->Pfin)
- fclose(conn->Pfin);
- if (conn->Pfdebug)
- fclose(conn->Pfdebug);
+ }
+
+ /*
+ * Close the connection, reset all transient state, flush I/O buffers.
+ */
+ if (conn->sock >= 0)
+ close(conn->sock);
+ conn->sock = -1;
conn->status = CONNECTION_BAD; /* Well, not really _bad_ - just
* absent */
+ conn->asyncStatus = PGASYNC_IDLE;
+ PQclearAsyncResult(conn); /* deallocate result and curTuple */
+ if (conn->lobjfuncs)
+ free(conn->lobjfuncs);
+ conn->lobjfuncs = NULL;
+ conn->inStart = conn->inCursor = conn->inEnd = 0;
+ conn->outCount = 0;
+
}
/*
@@ -793,8 +851,7 @@ PQfinish(PGconn *conn)
}
else
{
- if (conn->status == CONNECTION_OK)
- closePGconn(conn);
+ closePGconn(conn);
freePGconn(conn);
}
}
@@ -818,12 +875,8 @@ PQreset(PGconn *conn)
}
/*
- * PacketSend()
- *
- this is just like PacketSend(), defined in backend/libpq/pqpacket.c
- but we define it here to avoid linking in all of libpq.a
-
- * packetSend -- send a single-packet message.
+ * PacketSend() -- send a single-packet message.
+ * this is like PacketSend(), defined in backend/libpq/pqpacket.c
*
* RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
* SIDE_EFFECTS: may block.
@@ -833,15 +886,16 @@ packetSend(PGconn *conn, const char *buf, size_t len)
{
/* Send the total packet size. */
- if (pqPutInt(4 + len, 4, conn->Pfout, conn->Pfdebug))
+ if (pqPutInt(4 + len, 4, conn))
return STATUS_ERROR;
/* Send the packet itself. */
- if (pqPutnchar(buf, len, conn->Pfout, conn->Pfdebug))
+ if (pqPutnchar(buf, len, conn))
return STATUS_ERROR;
- pqFlush(conn->Pfout, conn->Pfdebug);
+ if (pqFlush(conn))
+ return STATUS_ERROR;
return STATUS_OK;
}
@@ -1203,6 +1257,17 @@ PQerrorMessage(PGconn *conn)
return conn->errorMessage;
}
+int
+PQsocket(PGconn *conn)
+{
+ if (!conn)
+ {
+ fprintf(stderr, "PQsocket() -- pointer to PGconn is null\n");
+ return -1;
+ }
+ return conn->sock;
+}
+
void
PQtrace(PGconn *conn, FILE *debug_port)
{
@@ -1218,8 +1283,8 @@ PQtrace(PGconn *conn, FILE *debug_port)
void
PQuntrace(PGconn *conn)
{
- if (conn == NULL ||
- conn->status == CONNECTION_BAD)
+ /* note: better allow untrace even when connection bad */
+ if (conn == NULL)
{
return;
}
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 4297abcfd99..aac0ea0e97b 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -7,46 +7,28 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.49 1998/04/29 02:04:01 scrappy Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.50 1998/05/06 23:51:13 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdlib.h>
-#include <unistd.h>
#include <stdio.h>
-#include <signal.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
+#if !defined(NO_UNISTD_H)
+#include <unistd.h>
+#endif
#include "postgres.h"
#include "libpq/pqcomm.h"
-#include "libpq/pqsignal.h"
#include "libpq-fe.h"
-#include <sys/ioctl.h>
-#ifndef HAVE_TERMIOS_H
-#include <sys/termios.h>
-#else
-#include <termios.h>
-#endif
-
-
-#ifdef TIOCGWINSZ
-struct winsize screen_size;
-#else
-struct winsize
-{
- int ws_row;
- int ws_col;
-} screen_size;
-
-#endif
/* the rows array in a PGresGroup has to grow to accommodate the rows */
/* returned. Each time, we grow by this much: */
#define TUPARR_GROW_BY 100
-/* keep this in same order as ExecStatusType in pgtclCmds.h */
+/* keep this in same order as ExecStatusType in libpq-fe.h */
const char *pgresStatus[] = {
"PGRES_EMPTY_QUERY",
"PGRES_COMMAND_OK",
@@ -59,56 +41,15 @@ const char *pgresStatus[] = {
};
-static PGresult *makePGresult(PGconn *conn, char *pname);
-static void addTuple(PGresult *res, PGresAttValue *tup);
-static PGresAttValue *getTuple(PGconn *conn, PGresult *res, int binary);
static PGresult *makeEmptyPGresult(PGconn *conn, ExecStatusType status);
-static void fill(int length, int max, char filler, FILE *fp);
-static char *
-do_header(FILE *fout, PQprintOpt *po, const int nFields,
- int fieldMax[], char *fieldNames[], unsigned char fieldNotNum[],
- const int fs_len, PGresult *res);
-
-/*
- * PQclear -
- * free's the memory associated with a PGresult
- *
- */
-void
-PQclear(PGresult *res)
-{
- int i,
- j;
-
- if (!res)
- return;
-
- /* free all the rows */
- for (i = 0; i < res->ntups; i++)
- {
- for (j = 0; j < res->numAttributes; j++)
- {
- if (res->tuples[i][j].value)
- free(res->tuples[i][j].value);
- }
- if (res->tuples[i])
- free(res->tuples[i]);
- }
- if (res->tuples)
- free(res->tuples);
-
- /* free all the attributes */
- for (i = 0; i < res->numAttributes; i++)
- {
- if (res->attDescs[i].name)
- free(res->attDescs[i].name);
- }
- if (res->attDescs)
- free(res->attDescs);
+static void freeTuple(PGresAttValue *tuple, int numAttributes);
+static void addTuple(PGresult *res, PGresAttValue *tup);
+static void parseInput(PGconn *conn);
+static int getRowDescriptions(PGconn *conn);
+static int getAnotherTuple(PGconn *conn, int binary);
+static int getNotify(PGconn *conn);
+static int getNotice(PGconn *conn);
- /* free the structure itself */
- free(res);
-}
/*
* PGresult -
@@ -136,1347 +77,933 @@ makeEmptyPGresult(PGconn *conn, ExecStatusType status)
}
/*
- * getTuple -
- * get the next row from the stream
+ * PQclear -
+ * free's the memory associated with a PGresult
*
- * the CALLER is responsible from freeing the PGresAttValue returned
*/
-
-static PGresAttValue *
-getTuple(PGconn *conn, PGresult *result, int binary)
+void
+PQclear(PGresult *res)
{
- char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap
- * of */
-
- /* which attributes are null */
- int bitmap_index = 0;
int i;
- int nbytes; /* the number of bytes in bitmap */
- char bmap; /* One byte of the bitmap */
- int bitcnt = 0; /* number of bits examined in current byte */
- int vlen; /* length of the current field value */
- FILE *pfin = conn->Pfin;
- FILE *pfdebug = conn->Pfdebug;
-
- PGresAttValue *tup;
- int nfields = result->numAttributes;
-
- result->binary = binary;
-
- tup = (PGresAttValue *) malloc(nfields * sizeof(PGresAttValue));
-
- nbytes = nfields / BYTELEN;
- if ((nfields % BYTELEN) > 0)
- nbytes++;
+ if (!res)
+ return;
- if (nbytes >= MAX_FIELDS || pqGetnchar(bitmap, nbytes, pfin, pfdebug) == 1)
+ /* free all the rows */
+ if (res->tuples)
{
- sprintf(conn->errorMessage,
- "Error reading null-values bitmap from row data stream\n");
- return NULL;
+ for (i = 0; i < res->ntups; i++)
+ freeTuple(res->tuples[i], res->numAttributes);
+ free(res->tuples);
}
- bmap = bitmap[bitmap_index];
-
- for (i = 0; i < nfields; i++)
+ /* free all the attributes */
+ if (res->attDescs)
{
- if (!(bmap & 0200))
+ for (i = 0; i < res->numAttributes; i++)
{
- /* if the field value is absent, make it '\0' */
- tup[i].value = (char *) malloc(1);
- tup[i].value[0] = '\0';
- tup[i].len = NULL_LEN;
+ if (res->attDescs[i].name)
+ free(res->attDescs[i].name);
}
- else
- {
- /* get the value length (the first four bytes are for length) */
- pqGetInt(&vlen, 4, pfin, pfdebug);
- if (binary == 0)
- {
- vlen = vlen - 4;
- }
- if (vlen < 0)
- vlen = 0;
- tup[i].len = vlen;
- tup[i].value = (char *) malloc(vlen + 1);
- /* read in the value; */
- if (vlen > 0)
- pqGetnchar((char *) (tup[i].value), vlen, pfin, pfdebug);
- tup[i].value[vlen] = '\0';
- }
- /* get the appropriate bitmap */
- bitcnt++;
- if (bitcnt == BYTELEN)
+ free(res->attDescs);
+ }
+
+ /* free the structure itself */
+ free(res);
+}
+
+/*
+ * Free a single tuple structure.
+ */
+
+static void
+freeTuple(PGresAttValue *tuple, int numAttributes)
+{
+ int i;
+
+ if (tuple)
+ {
+ for (i = 0; i < numAttributes; i++)
{
- bitmap_index++;
- bmap = bitmap[bitmap_index];
- bitcnt = 0;
+ if (tuple[i].value)
+ free(tuple[i].value);
}
- else
- bmap <<= 1;
+ free(tuple);
}
+}
+
+/*
+ * Handy subroutine to deallocate any partially constructed async result.
+ */
- return tup;
+void
+PQclearAsyncResult(PGconn *conn)
+{
+ /* Get rid of incomplete result and any not-yet-added tuple */
+ if (conn->result)
+ {
+ if (conn->curTuple)
+ freeTuple(conn->curTuple, conn->result->numAttributes);
+ PQclear(conn->result);
+ }
+ conn->result = NULL;
+ conn->curTuple = NULL;
}
/*
* addTuple
* add a row to the PGresult structure, growing it if necessary
- * to accommodate
- *
*/
static void
addTuple(PGresult *res, PGresAttValue *tup)
{
- if (res->ntups == res->tupArrSize)
+ if (res->ntups >= res->tupArrSize)
{
/* grow the array */
res->tupArrSize += TUPARR_GROW_BY;
-
- if (res->ntups == 0)
- res->tuples = (PGresAttValue **)
- malloc(res->tupArrSize * sizeof(PGresAttValue *));
- else
-
- /*
- * we can use realloc because shallow copying of the structure
- * is okay
- */
- res->tuples = (PGresAttValue **)
- realloc(res->tuples, res->tupArrSize * sizeof(PGresAttValue *));
+ /*
+ * we can use realloc because shallow copying of the structure
+ * is okay. Note that the first time through, res->tuples is NULL.
+ * realloc is supposed to do the right thing in that case.
+ * Also note that the positions beyond res->ntups are garbage,
+ * not necessarily NULL.
+ */
+ res->tuples = (PGresAttValue **)
+ realloc(res->tuples, res->tupArrSize * sizeof(PGresAttValue *));
}
-
res->tuples[res->ntups] = tup;
res->ntups++;
}
+
/*
- * PGresult
- * fill out the PGresult structure with result rows from the backend
- * this is called after query has been successfully run and we have
- * a portal name
- *
- * ASSUMPTION: we assume only *1* row group is returned from the backend
- *
- * the CALLER is reponsible for free'ing the new PGresult allocated here
+ * PQsendQuery
+ * Submit a query, but don't wait for it to finish
*
+ * Returns: 1 if successfully submitted
+ * 0 if error (conn->errorMessage is set)
*/
-static PGresult *
-makePGresult(PGconn *conn, char *pname)
+int
+PQsendQuery(PGconn *conn, const char *query)
{
- PGresult *result;
- int id;
- int nfields;
- int i;
- int done = 0;
-
- PGresAttValue *newTup;
-
- FILE *pfin = conn->Pfin;
- FILE *pfdebug = conn->Pfdebug;
-
- result = makeEmptyPGresult(conn, PGRES_TUPLES_OK);
-
- /* makePGresult() should only be called when the */
- /* id of the stream is 'T' to start with */
-
- /* the next two bytes are the number of fields */
- if (pqGetInt(&nfields, 2, pfin, pfdebug) == 1)
+ if (!conn)
+ return 0;
+ if (!query)
{
- sprintf(conn->errorMessage,
- "could not get the number of fields from the 'T' message\n");
- goto makePGresult_badResponse_return;
+ sprintf(conn->errorMessage, "PQsendQuery() -- query pointer is null.");
+ return 0;
}
- else
- result->numAttributes = nfields;
-
- /* allocate space for the attribute descriptors */
- if (nfields > 0)
+ if (conn->asyncStatus != PGASYNC_IDLE)
{
- result->attDescs = (PGresAttDesc *) malloc(nfields * sizeof(PGresAttDesc));
+ sprintf(conn->errorMessage,
+ "PQsendQuery() -- another query already in progress.");
+ return 0;
}
- /* get type info */
- for (i = 0; i < nfields; i++)
- {
- char typName[MAX_MESSAGE_LEN];
- int adtid;
- int adtsize;
-
- if (pqGets(typName, MAX_MESSAGE_LEN, pfin, pfdebug) ||
- pqGetInt(&adtid, 4, pfin, pfdebug) ||
- pqGetInt(&adtsize, 2, pfin, pfdebug))
- {
- sprintf(conn->errorMessage,
- "error reading type information from the 'T' message\n");
- goto makePGresult_badResponse_return;
- }
- result->attDescs[i].name = malloc(strlen(typName) + 1);
- strcpy(result->attDescs[i].name, typName);
- result->attDescs[i].adtid = adtid;
- result->attDescs[i].adtsize = adtsize; /* casting from int to
- * int2 here */
- }
+ /* clear the error string */
+ conn->errorMessage[0] = '\0';
- id = pqGetc(pfin, pfdebug);
+ /* initialize async result-accumulation state */
+ conn->result = NULL;
+ conn->curTuple = NULL;
+ conn->asyncErrorMessage[0] = '\0';
- /* process the data stream until we're finished */
- while (!done)
+ /* check to see if the query string is too long */
+ if (strlen(query) > MAX_MESSAGE_LEN-2)
{
- switch (id)
- {
- case 'T': /* a new row group */
- sprintf(conn->errorMessage,
- "makePGresult() -- "
- "is not equipped to handle multiple row groups.\n");
- goto makePGresult_badResponse_return;
- case 'B': /* a row in binary format */
- case 'D': /* a row in ASCII format */
- newTup = getTuple(conn, result, (id == 'B'));
- if (newTup == NULL)
- goto makePGresult_badResponse_return;
- addTuple(result, newTup);
- break;
- case 'C': /* end of portal row stream */
- {
- char command[MAX_MESSAGE_LEN];
-
- pqGets(command, MAX_MESSAGE_LEN, pfin, pfdebug); /* read command tag */
- done = 1;
- }
- break;
- case 'E': /* errors */
- if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, pfin, pfdebug) == 1)
- {
- sprintf(conn->errorMessage,
- "Error return detected from backend, "
- "but error message cannot be read");
- }
- result->resultStatus = PGRES_FATAL_ERROR;
- return result;
- break;
- case 'N': /* notices from the backend */
- if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, pfin, pfdebug) == 1)
- {
- sprintf(conn->errorMessage,
- "Notice return detected from backend, "
- "but error message cannot be read");
- }
- else
- /* XXXX send Notices to stderr for now */
- fprintf(stderr, "%s\n", conn->errorMessage);
- break;
- default: /* uh-oh this should never happen but
- * frequently does when the backend dumps
- * core */
- sprintf(conn->errorMessage,
- "FATAL: unrecognized data from the backend. "
- "It probably dumped core.\n");
- fprintf(stderr, conn->errorMessage);
- result->resultStatus = PGRES_FATAL_ERROR;
- return result;
- break;
- }
- if (!done)
- id = getc(pfin);
- } /* while (1) */
+ sprintf(conn->errorMessage, "PQsendQuery() -- query is too long. "
+ "Maximum length is %d\n", MAX_MESSAGE_LEN - 2);
+ return 0;
+ }
- result->resultStatus = PGRES_TUPLES_OK;
- return result;
+ /* Don't try to send if we know there's no live connection. */
+ if (conn->status != CONNECTION_OK)
+ {
+ sprintf(conn->errorMessage, "PQsendQuery() -- There is no connection "
+ "to the backend.\n");
+ return 0;
+ }
-makePGresult_badResponse_return:
- result->resultStatus = PGRES_BAD_RESPONSE;
- return result;
+ /* send the query to the backend; */
+ /* the frontend-backend protocol uses 'Q' to designate queries */
+ if (pqPutnchar("Q", 1, conn))
+ return 0;
+ if (pqPuts(query, conn))
+ return 0;
+ if (pqFlush(conn))
+ return 0;
+ /* OK, it's launched! */
+ conn->asyncStatus = PGASYNC_BUSY;
+ return 1;
}
/*
- * Assuming that we just sent a query to the backend, read the backend's
- * response from stream <pfin> and respond accordingly.
- *
- * If <pfdebug> is non-null, write to that stream whatever we receive
- * (it's a debugging trace).
- *
- * Return as <result> a pointer to a proper final PGresult structure,
- * newly allocated, for the query based on the response we get. If the
- * response we get indicates that the query didn't execute, return a
- * null pointer and don't allocate any space, but also place a text
- * string explaining the problem at <*reason>.
+ * Consume any available input from the backend
*/
-static void
-process_response_from_backend(FILE *pfin, FILE *pfout, FILE *pfdebug,
- PGconn *conn,
- PGresult **result_p, char *const reason)
+void
+PQconsumeInput(PGconn *conn)
{
+ if (!conn)
+ return;
- int id;
-
- /*
- * The protocol character received from the backend. The protocol
- * character is the first character in the backend's response to our
- * query. It defines the nature of the response.
+ /* Load more data, if available.
+ * We do this no matter what state we are in, since we are probably
+ * getting called because the application wants to get rid
+ * of a read-select condition.
+ * Note that we will NOT block waiting for more input.
*/
- PGnotify *newNotify;
- bool done;
+ if (pqReadData(conn) < 0)
+ {
+ strcpy(conn->asyncErrorMessage, conn->errorMessage);
+ }
+ /* Parsing of the data waits till later. */
+}
- /* We're all done with the query and ready to return the result. */
- int emptiesSent;
- /*
- * Number of empty queries we have sent in order to flush out multiple
- * responses, less the number of corresponding responses we have
- * received.
- */
- int errors;
+/*
+ * parseInput: if appropriate, parse input data from backend
+ * until input is exhausted or a stopping state is reached.
+ * Note that this function will NOT attempt to read more data from the backend.
+ */
- /*
- * If an error is received, we must still drain out the empty queries
- * sent. So we need another flag.
- */
- char cmdStatus[MAX_MESSAGE_LEN];
- char pname[MAX_MESSAGE_LEN]; /* portal name */
+static void
+parseInput(PGconn *conn)
+{
+ char id;
- /*
- * loop because multiple messages, especially NOTICES, can come back
- * from the backend. NOTICES are output directly to stderr
+ /*
+ * Loop to parse successive complete messages available in the buffer.
*/
-
- emptiesSent = 0; /* No empty queries sent yet */
- errors = 0; /* No errors received yet */
- pname[0] = '\0';
-
- done = false; /* initial value */
- while (!done)
+ for (;;)
{
- /* read the result id */
- id = pqGetc(pfin, pfdebug);
- if (id == EOF)
+ /*
+ * Quit if in COPY_OUT state: we expect raw data from the server until
+ * PQendcopy is called. Don't try to parse it according to the normal
+ * protocol. (This is bogus. The data lines ought to be part of the
+ * protocol and have identifying leading characters.)
+ */
+ if (conn->asyncStatus == PGASYNC_COPY_OUT)
+ return;
+ /*
+ * OK to try to read a message type code.
+ */
+ conn->inCursor = conn->inStart;
+ if (pqGetc(&id, conn))
+ return;
+ /*
+ * NOTIFY messages can happen in any state besides COPY OUT;
+ * always process them right away.
+ */
+ if (id == 'A')
{
- /* hmm, no response from the backend-end, that's bad */
- (void) sprintf(reason, "PQexec() -- Request was sent to backend"
- ", but backend closed the channel before responding."
- "\n\tThis probably means the backend terminated abnormally"
- " before or while processing the request.\n");
- conn->status = CONNECTION_BAD; /* No more connection to
- * backend */
- *result_p = (PGresult *) NULL;
- done = true;
+ /* Notify responses can happen at any time */
+ if (getNotify(conn))
+ return;
}
else
{
+ /*
+ * Other messages should only be processed while in BUSY state.
+ * (In particular, in READY state we hold off further parsing
+ * until the application collects the current PGresult.)
+ * If the state is IDLE then we got trouble.
+ */
+ if (conn->asyncStatus != PGASYNC_BUSY)
+ {
+ if (conn->asyncStatus == PGASYNC_IDLE)
+ {
+ fprintf(stderr,
+ "Backend message type 0x%02x arrived while idle\n",
+ id);
+ /* Discard the unexpected message; good idea?? */
+ conn->inStart = conn->inEnd;
+ }
+ return;
+ }
switch (id)
{
- case 'A':
- newNotify = (PGnotify *) malloc(sizeof(PGnotify));
- pqGetInt(&(newNotify->be_pid), 4, pfin, pfdebug);
- pqGets(newNotify->relname, NAMEDATALEN, pfin, pfdebug);
- DLAddTail(conn->notifyList, DLNewElem(newNotify));
-
- /*
- * async messages are piggy'ed back on other messages,
- * so we stay in the while loop for other messages
+ case 'C': /* command complete */
+ if (conn->result == NULL)
+ conn->result = makeEmptyPGresult(conn,
+ PGRES_COMMAND_OK);
+ if (pqGets(conn->result->cmdStatus, CMDSTATUS_LEN, conn))
+ return;
+ conn->asyncStatus = PGASYNC_READY;
+ break;
+ case 'E': /* error return */
+ if (pqGets(conn->asyncErrorMessage,ERROR_MSG_LENGTH,conn))
+ return;
+ /* delete any partially constructed result */
+ PQclearAsyncResult(conn);
+ /* we leave result NULL while setting asyncStatus=READY;
+ * this signals an error condition to PQgetResult.
*/
+ conn->asyncStatus = PGASYNC_READY;
+ break;
+ case 'Z': /* backend is ready for new query */
+ conn->asyncStatus = PGASYNC_IDLE;
+ break;
+ case 'I': /* empty query */
+ /* read and throw away the closing '\0' */
+ if (pqGetc(&id, conn))
+ return;
+ if (id != '\0')
+ fprintf(stderr,
+ "unexpected character %c following 'I'\n", id);
+ if (conn->result == NULL)
+ conn->result = makeEmptyPGresult(conn,
+ PGRES_EMPTY_QUERY);
+ conn->asyncStatus = PGASYNC_READY;
break;
- case 'C': /* portal query command, no rows returned */
- if (pqGets(cmdStatus, MAX_MESSAGE_LEN, pfin, pfdebug) == 1)
+ case 'N': /* notices from the backend */
+ if (getNotice(conn))
+ return;
+ break;
+ case 'P': /* synchronous (normal) portal */
+ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+ return;
+ /* We pretty much ignore this message type... */
+ break;
+ case 'T': /* row descriptions (start of query results) */
+ if (conn->result == NULL)
{
- sprintf(reason,
- "PQexec() -- query command completed, "
- "but return message from backend cannot be read.");
- *result_p = (PGresult *) NULL;
- done = true;
+ /* First 'T' in a query sequence */
+ if (getRowDescriptions(conn))
+ return;
}
else
{
-
- /*
- * since backend may produce more than one result
- * for some commands need to poll until clear send
- * an empty query down, and keep reading out of
- * the pipe until an 'I' is received.
+ /* A new 'T' message is treated as the start of
+ * another PGresult. (It is not clear that this
+ * is really possible with the current backend.)
+ * We stop parsing until the application accepts
+ * the current result.
*/
- pqPuts("Q ", pfout, pfdebug); /* send an empty query */
-
- /*
- * Increment a flag and process messages in the
- * usual way because there may be async
- * notifications pending. DZ - 31-8-1996
- */
- emptiesSent++;
+ conn->asyncStatus = PGASYNC_READY;
+ return;
}
break;
- case 'E': /* error return */
- if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, pfin, pfdebug) == 1)
+ case 'D': /* ASCII data tuple */
+ if (conn->result != NULL)
{
- (void) sprintf(reason,
- "PQexec() -- error return detected from backend, "
- "but attempt to read the error message failed.");
+ /* Read another tuple of a normal query response */
+ if (getAnotherTuple(conn, FALSE))
+ return;
}
- *result_p = (PGresult *) NULL;
- errors++;
- if (emptiesSent == 0)
+ else
{
- done = true;
- }
- break;
- case 'I':
- { /* empty query */
- /* read and throw away the closing '\0' */
- int c;
-
- if ((c = pqGetc(pfin, pfdebug)) != '\0')
- {
- fprintf(stderr, "error!, unexpected character %c following 'I'\n", c);
- }
- if (emptiesSent)
- {
- if (--emptiesSent == 0)
- { /* is this the last one? */
-
- /*
- * If this is the result of a portal query
- * command set the command status and
- * message accordingly. DZ - 31-8-1996
- */
- if (!errors)
- {
- *result_p = makeEmptyPGresult(conn, PGRES_COMMAND_OK);
- strncpy((*result_p)->cmdStatus, cmdStatus, CMDSTATUS_LEN - 1);
- }
- else
- {
- *result_p = (PGresult *) NULL;
- }
- done = true;
- }
- }
- else
- {
- if (!errors)
- {
- *result_p = makeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
- }
- else
- {
- *result_p = (PGresult *) NULL;
- }
- done = true;
- }
+ fprintf(stderr,
+ "Backend sent D message without prior T\n");
+ /* Discard the unexpected message; good idea?? */
+ conn->inStart = conn->inEnd;
+ return;
}
break;
- case 'N': /* notices from the backend */
- if (pqGets(reason, ERROR_MSG_LENGTH, pfin, pfdebug) == 1)
+ case 'B': /* Binary data tuple */
+ if (conn->result != NULL)
{
- sprintf(reason,
- "PQexec() -- Notice detected from backend, "
- "but attempt to read the notice failed.");
- *result_p = (PGresult *) NULL;
- done = true;
+ /* Read another tuple of a normal query response */
+ if (getAnotherTuple(conn, TRUE))
+ return;
}
else
-
- /*
- * Should we really be doing this? These notices
- * are not important enough for us to presume to
- * put them on stderr. Maybe the caller should
- * decide whether to put them on stderr or not.
- * BJH 96.12.27
- */
- fprintf(stderr, "%s", reason);
- break;
- case 'P': /* synchronous (normal) portal */
- pqGets(pname, MAX_MESSAGE_LEN, pfin, pfdebug); /* read in portal name */
- break;
- case 'T': /* actual row results: */
- *result_p = makePGresult(conn, pname);
- done = true;
+ {
+ fprintf(stderr,
+ "Backend sent B message without prior T\n");
+ /* Discard the unexpected message; good idea?? */
+ conn->inStart = conn->inEnd;
+ return;
+ }
break;
- case 'D': /* copy command began successfully */
- *result_p = makeEmptyPGresult(conn, PGRES_COPY_IN);
- done = true;
+ case 'G': /* Start Copy In */
+ conn->asyncStatus = PGASYNC_COPY_IN;
break;
- case 'B': /* copy command began successfully */
- *result_p = makeEmptyPGresult(conn, PGRES_COPY_OUT);
- done = true;
+ case 'H': /* Start Copy Out */
+ conn->asyncStatus = PGASYNC_COPY_OUT;
break;
default:
- sprintf(reason,
- "unknown protocol character '%c' read from backend. "
- "(The protocol character is the first character the "
+ sprintf(conn->asyncErrorMessage,
+ "unknown protocol character '%c' read from backend. "
+ "(The protocol character is the first character the "
"backend sends in response to a query it receives).\n",
id);
- *result_p = (PGresult *) NULL;
- done = true;
+ /* Discard the unexpected message; good idea?? */
+ conn->inStart = conn->inEnd;
+ /* delete any partially constructed result */
+ PQclearAsyncResult(conn);
+ conn->asyncStatus = PGASYNC_READY;
+ return;
} /* switch on protocol character */
- } /* if character was received */
- } /* while not done */
+ }
+ /* Successfully consumed this message */
+ conn->inStart = conn->inCursor;
+ }
}
-
/*
- * PQexec
- * send a query to the backend and package up the result in a Pgresult
- *
- * if the query failed, return NULL, conn->errorMessage is set to
- * a relevant message
- * if query is successful, a new PGresult is returned
- * the use is responsible for freeing that structure when done with it
+ * parseInput subroutine to read a 'T' (row descriptions) message.
+ * We build a PGresult structure containing the attribute data.
+ * Returns: 0 if completed message, EOF if not enough data yet.
*
+ * Note that if we run out of data, we have to release the partially
+ * constructed PGresult, and rebuild it again next time. Fortunately,
+ * that shouldn't happen often, since 'T' messages usually fit in a packet.
*/
-PGresult *
-PQexec(PGconn *conn, const char *query)
+static int
+getRowDescriptions(PGconn *conn)
{
PGresult *result;
- char buffer[MAX_MESSAGE_LEN];
-
- if (!conn)
- return NULL;
- if (!query)
- {
- sprintf(conn->errorMessage, "PQexec() -- query pointer is null.");
- return NULL;
- }
+ int nfields;
+ int i;
- /* clear the error string */
- conn->errorMessage[0] = '\0';
+ result = makeEmptyPGresult(conn, PGRES_TUPLES_OK);
- /* check to see if the query string is too long */
- if (strlen(query) > MAX_MESSAGE_LEN)
+ /* parseInput already read the 'T' label. */
+ /* the next two bytes are the number of fields */
+ if (pqGetInt(&(result->numAttributes), 2, conn))
{
- sprintf(conn->errorMessage, "PQexec() -- query is too long. "
- "Maximum length is %d\n", MAX_MESSAGE_LEN - 2);
- return NULL;
+ PQclear(result);
+ return EOF;
}
+ nfields = result->numAttributes;
- /* Don't try to send if we know there's no live connection. */
- if (conn->status != CONNECTION_OK)
+ /* allocate space for the attribute descriptors */
+ if (nfields > 0)
{
- sprintf(conn->errorMessage, "PQexec() -- There is no connection "
- "to the backend.\n");
- return NULL;
+ result->attDescs = (PGresAttDesc *)
+ malloc(nfields * sizeof(PGresAttDesc));
+ MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc));
}
- /* the frontend-backend protocol uses 'Q' to designate queries */
- sprintf(buffer, "Q%s", query);
-
- /* send the query to the backend; */
- if (pqPuts(buffer, conn->Pfout, conn->Pfdebug) == 1)
+ /* get type info */
+ for (i = 0; i < nfields; i++)
{
- (void) sprintf(conn->errorMessage,
- "PQexec() -- while sending query: %s\n"
- "-- fprintf to Pfout failed: errno=%d\n%s\n",
- query, errno, strerror(errno));
- return NULL;
+ char typName[MAX_MESSAGE_LEN];
+ int adtid;
+ int adtsize;
+ int adtmod = -1;
+
+ if (pqGets(typName, MAX_MESSAGE_LEN, conn) ||
+ pqGetInt(&adtid, 4, conn) ||
+ pqGetInt(&adtsize, 2, conn)
+#if 0 /* backend support not there yet */
+ || pqGetInt(&adtmod, 2, conn)
+#endif
+)
+ {
+ PQclear(result);
+ return EOF;
+ }
+ result->attDescs[i].name = strdup(typName);
+ result->attDescs[i].adtid = adtid;
+ result->attDescs[i].adtsize = (short) adtsize;
+ result->attDescs[i].adtmod = (short) adtmod;
}
- process_response_from_backend(conn->Pfin, conn->Pfout, conn->Pfdebug, conn,
- &result, conn->errorMessage);
- return (result);
+ /* Success! */
+ conn->result = result;
+ return 0;
}
/*
- * PQnotifies
- * returns a PGnotify* structure of the latest async notification
- * that has not yet been handled
- *
- * returns NULL, if there is currently
- * no unhandled async notification from the backend
+ * parseInput subroutine to read a 'B' or 'D' (row data) message.
+ * We add another tuple to the existing PGresult structure.
+ * Returns: 0 if completed message, EOF if not enough data yet.
*
- * the CALLER is responsible for FREE'ing the structure returned
+ * Note that if we run out of data, we have to suspend and reprocess
+ * the message after more data is received. We keep a partially constructed
+ * tuple in conn->curTuple, and avoid reallocating already-allocated storage.
*/
-PGnotify *
-PQnotifies(PGconn *conn)
+static int
+getAnotherTuple(PGconn *conn, int binary)
{
- Dlelem *e;
+ int nfields = conn->result->numAttributes;
+ PGresAttValue *tup;
+ char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap
+ * of which attributes are null */
+ int i;
+ int nbytes; /* the number of bytes in bitmap */
+ char bmap; /* One byte of the bitmap */
+ int bitmap_index; /* Its index */
+ int bitcnt; /* number of bits examined in current byte */
+ int vlen; /* length of the current field value */
- if (!conn)
- return NULL;
+ conn->result->binary = binary;
- if (conn->status != CONNECTION_OK)
- return NULL;
- /* RemHead returns NULL if list is empy */
- e = DLRemHead(conn->notifyList);
- if (e)
- return (PGnotify *) DLE_VAL(e);
- else
- return NULL;
-}
+ /* Allocate tuple space if first time for this data message */
+ if (conn->curTuple == NULL)
+ {
+ conn->curTuple = (PGresAttValue *)
+ malloc(nfields * sizeof(PGresAttValue));
+ MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue));
+ }
+ tup = conn->curTuple;
-/*
- * PQgetline - gets a newline-terminated string from the backend.
- *
- * Chiefly here so that applications can use "COPY <rel> to stdout"
- * and read the output string. Returns a null-terminated string in s.
- *
- * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips
- * the terminating \n (like gets(3)).
- *
- * RETURNS:
- * EOF if it is detected or invalid arguments are given
- * 0 if EOL is reached (i.e., \n has been read)
- * (this is required for backward-compatibility -- this
- * routine used to always return EOF or 0, assuming that
- * the line ended within maxlen bytes.)
- * 1 in other cases
- */
-int
-PQgetline(PGconn *conn, char *s, int maxlen)
-{
- int c = '\0';
+ /* Get the null-value bitmap */
+ nbytes = (nfields + BYTELEN-1) / BYTELEN;
+ if (nbytes >= MAX_FIELDS)
+ {
+ sprintf(conn->asyncErrorMessage,
+ "getAnotherTuple() -- null-values bitmap is too large\n");
+ PQclearAsyncResult(conn);
+ conn->asyncStatus = PGASYNC_READY;
+ /* Discard the broken message */
+ conn->inStart = conn->inEnd;
+ return EOF;
+ }
- if (!conn)
+ if (pqGetnchar(bitmap, nbytes, conn))
return EOF;
- if (!conn->Pfin || !s || maxlen <= 1)
- return (EOF);
+ /* Scan the fields */
+ bitmap_index = 0;
+ bmap = bitmap[bitmap_index];
+ bitcnt = 0;
- for (; maxlen > 1 &&
- (c = pqGetc(conn->Pfin, conn->Pfdebug)) != '\n' &&
- c != EOF;
- --maxlen)
+ for (i = 0; i < nfields; i++)
{
- *s++ = c;
+ if (!(bmap & 0200))
+ {
+ /* if the field value is absent, make it a null string */
+ if (tup[i].value == NULL)
+ tup[i].value = strdup("");
+ tup[i].len = NULL_LEN;
+ }
+ else
+ {
+ /* get the value length (the first four bytes are for length) */
+ if (pqGetInt(&vlen, 4, conn))
+ return EOF;
+ if (binary == 0)
+ {
+ vlen = vlen - 4;
+ }
+ if (vlen < 0)
+ vlen = 0;
+ if (tup[i].value == NULL)
+ tup[i].value = (char *) malloc(vlen + 1);
+ tup[i].len = vlen;
+ /* read in the value */
+ if (vlen > 0)
+ if (pqGetnchar((char *) (tup[i].value), vlen, conn))
+ return EOF;
+ tup[i].value[vlen] = '\0';
+ }
+ /* advance the bitmap stuff */
+ bitcnt++;
+ if (bitcnt == BYTELEN)
+ {
+ bitmap_index++;
+ bmap = bitmap[bitmap_index];
+ bitcnt = 0;
+ }
+ else
+ bmap <<= 1;
}
- *s = '\0';
- if (c == EOF)
- {
- return (EOF); /* error -- reached EOF before \n */
- }
- else if (c == '\n')
- {
- return (0); /* done with this line */
- }
- return (1); /* returning a full buffer */
+ /* Success! Store the completed tuple in the result */
+ addTuple(conn->result, tup);
+ /* and reset for a new message */
+ conn->curTuple = NULL;
+ return 0;
}
+
/*
- * PQputline -- sends a string to the backend.
- *
- * Chiefly here so that applications can use "COPY <rel> from stdin".
- *
+ * PQisBusy
+ * Return TRUE if PQgetResult would block waiting for input.
*/
-void
-PQputline(PGconn *conn, const char *s)
+
+int
+PQisBusy(PGconn *conn)
{
- if (conn && (conn->Pfout))
- {
- (void) fputs(s, conn->Pfout);
- fflush(conn->Pfout);
- }
+ if (!conn)
+ return FALSE;
+
+ /* Parse any available data, if our state permits. */
+ parseInput(conn);
+
+ /* PQgetResult will return immediately in all states except BUSY. */
+ return (conn->asyncStatus == PGASYNC_BUSY);
}
+
/*
- * PQendcopy
- * called while waiting for the backend to respond with success/failure
- * to a "copy".
- *
- * RETURNS:
- * 0 on success
- * 1 on failure
+ * PQgetResult
+ * Get the next PGresult produced by a query.
+ * Returns NULL if and only if no query work remains.
*/
-int
-PQendcopy(PGconn *conn)
+
+PGresult *
+PQgetResult(PGconn *conn)
{
- FILE *pfin,
- *pfdebug;
- bool valid = true;
+ PGresult *res;
if (!conn)
- return (int) NULL;
+ return NULL;
- pfin = conn->Pfin;
- pfdebug = conn->Pfdebug;
+ /* Parse any available data, if our state permits. */
+ parseInput(conn);
- if (pqGetc(pfin, pfdebug) == 'C')
+ /* If not ready to return something, block until we are. */
+ while (conn->asyncStatus == PGASYNC_BUSY)
{
- char command[MAX_MESSAGE_LEN];
-
- pqGets(command, MAX_MESSAGE_LEN, pfin, pfdebug); /* read command tag */
+ /* Wait for some more data, and load it. */
+ if (pqWait(TRUE, FALSE, conn) ||
+ pqReadData(conn) < 0)
+ {
+ PQclearAsyncResult(conn);
+ conn->asyncStatus = PGASYNC_IDLE;
+ /* conn->errorMessage has been set by pqWait or pqReadData. */
+ return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ }
+ /* Parse it. */
+ parseInput(conn);
}
- else
- valid = false;
- if (valid)
- return (0);
- else
+ /* Return the appropriate thing. */
+ switch (conn->asyncStatus)
{
- sprintf(conn->errorMessage,
- "Error return detected from backend, "
- "but attempt to read the message failed.");
- fprintf(stderr, "resetting connection\n");
- PQreset(conn);
- return (1);
+ case PGASYNC_IDLE:
+ res = NULL; /* query is complete */
+ break;
+ case PGASYNC_READY:
+ /*
+ * conn->result is the PGresult to return, or possibly NULL
+ * indicating an error.
+ * conn->asyncErrorMessage holds the errorMessage to return.
+ * (We keep it stashed there so that other user calls can't
+ * overwrite it prematurely.)
+ */
+ res = conn->result;
+ conn->result = NULL; /* handing over ownership to caller */
+ conn->curTuple = NULL; /* just in case */
+ if (!res)
+ res = makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ strcpy(conn->errorMessage, conn->asyncErrorMessage);
+ /* Set the state back to BUSY, allowing parsing to proceed. */
+ conn->asyncStatus = PGASYNC_BUSY;
+ break;
+ case PGASYNC_COPY_IN:
+ res = makeEmptyPGresult(conn, PGRES_COPY_IN);
+ break;
+ case PGASYNC_COPY_OUT:
+ res = makeEmptyPGresult(conn, PGRES_COPY_OUT);
+ break;
+ default:
+ sprintf(conn->errorMessage,
+ "PQgetResult: Unexpected asyncStatus %d\n",
+ (int) conn->asyncStatus);
+ res = makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ break;
}
-}
-/* simply send out max-length number of filler characters to fp */
-static void
-fill(int length, int max, char filler, FILE *fp)
-{
- int count;
- char filltmp[2];
-
- filltmp[0] = filler;
- filltmp[1] = 0;
- count = max - length;
- while (count-- >= 0)
- {
- fprintf(fp, "%s", filltmp);
- }
+ return res;
}
+
/*
- * PQdisplayTuples()
- * kept for backward compatibility
+ * PQexec
+ * send a query to the backend and package up the result in a PGresult
+ *
+ * if the query failed, return NULL, conn->errorMessage is set to
+ * a relevant message
+ * if query is successful, a new PGresult is returned
+ * the user is responsible for freeing that structure when done with it
+ *
*/
-void
-PQdisplayTuples(PGresult *res,
- FILE *fp, /* where to send the output */
- int fillAlign, /* pad the fields with spaces */
- const char *fieldSep, /* field separator */
- int printHeader,/* display headers? */
- int quiet
-)
-{
-#define DEFAULT_FIELD_SEP " "
-
- int i,
- j;
- int nFields;
- int nTuples;
- int fLength[MAX_FIELDS];
- if (fieldSep == NULL)
- fieldSep = DEFAULT_FIELD_SEP;
-
- /* Get some useful info about the results */
- nFields = PQnfields(res);
- nTuples = PQntuples(res);
-
- if (fp == NULL)
- fp = stdout;
+PGresult *
+PQexec(PGconn *conn, const char *query)
+{
+ PGresult *result;
+ PGresult *lastResult;
- /* Zero the initial field lengths */
- for (j = 0; j < nFields; j++)
- {
- fLength[j] = strlen(PQfname(res, j));
- }
- /* Find the max length of each field in the result */
- /* will be somewhat time consuming for very large results */
- if (fillAlign)
+ /* Silently discard any prior query result that application didn't eat.
+ * This is probably poor design, but it's here for backward compatibility.
+ */
+ while ((result = PQgetResult(conn)) != NULL)
{
- for (i = 0; i < nTuples; i++)
+ if (result->resultStatus == PGRES_COPY_IN ||
+ result->resultStatus == PGRES_COPY_OUT)
{
- for (j = 0; j < nFields; j++)
- {
- if (PQgetlength(res, i, j) > fLength[j])
- fLength[j] = PQgetlength(res, i, j);
- }
+ PQclear(result);
+ sprintf(conn->errorMessage,
+ "PQexec: you gotta get out of a COPY state yourself.\n");
+ return NULL;
}
+ PQclear(result);
}
- if (printHeader)
- {
- /* first, print out the attribute names */
- for (i = 0; i < nFields; i++)
- {
- fputs(PQfname(res, i), fp);
- if (fillAlign)
- fill(strlen(PQfname(res, i)), fLength[i], ' ', fp);
- fputs(fieldSep, fp);
- }
- fprintf(fp, "\n");
-
- /* Underline the attribute names */
- for (i = 0; i < nFields; i++)
- {
- if (fillAlign)
- fill(0, fLength[i], '-', fp);
- fputs(fieldSep, fp);
- }
- fprintf(fp, "\n");
- }
+ /* OK to send the message */
+ if (! PQsendQuery(conn, query))
+ return NULL;
- /* next, print out the instances */
- for (i = 0; i < nTuples; i++)
+ /* For backwards compatibility, return the last result if there are
+ * more than one.
+ */
+ lastResult = NULL;
+ while ((result = PQgetResult(conn)) != NULL)
{
- for (j = 0; j < nFields; j++)
- {
- fprintf(fp, "%s", PQgetvalue(res, i, j));
- if (fillAlign)
- fill(strlen(PQgetvalue(res, i, j)), fLength[j], ' ', fp);
- fputs(fieldSep, fp);
- }
- fprintf(fp, "\n");
+ if (lastResult)
+ PQclear(lastResult);
+ lastResult = result;
}
-
- if (!quiet)
- fprintf(fp, "\nQuery returned %d row%s.\n", PQntuples(res),
- (PQntuples(res) == 1) ? "" : "s");
-
- fflush(fp);
+ return lastResult;
}
-
/*
- * PQprintTuples()
- *
- * kept for backward compatibility
+ * Attempt to request cancellation of the current operation.
*
+ * The return value is TRUE if the cancel request was successfully
+ * dispatched, FALSE if not (in which case errorMessage is set).
+ * Note: successful dispatch is no guarantee that there will be any effect at
+ * the backend. The application must read the operation result as usual.
*/
-void
-PQprintTuples(PGresult *res,
- FILE *fout, /* output stream */
- int PrintAttNames,/* print attribute names or not */
- int TerseOutput, /* delimiter bars or not? */
- int colWidth /* width of column, if 0, use variable
- * width */
-)
-{
- int nFields;
- int nTups;
- int i,
- j;
- char formatString[80];
- char *tborder = NULL;
+int
+PQrequestCancel(PGconn *conn)
+{
+ char msg[1];
- nFields = PQnfields(res);
- nTups = PQntuples(res);
+ if (!conn)
+ return FALSE;
- if (colWidth > 0)
+ if (conn->sock < 0)
{
- sprintf(formatString, "%%s %%-%ds", colWidth);
+ sprintf(conn->errorMessage,
+ "PQrequestCancel() -- connection is not open\n");
+ return FALSE;
}
- else
- sprintf(formatString, "%%s %%s");
-
- if (nFields > 0)
- { /* only print rows with at least 1 field. */
- if (!TerseOutput)
- {
- int width;
-
- width = nFields * 14;
- tborder = malloc(width + 1);
- for (i = 0; i <= width; i++)
- tborder[i] = '-';
- tborder[i] = '\0';
- fprintf(fout, "%s\n", tborder);
- }
+ msg[0] = '\0';
- for (i = 0; i < nFields; i++)
- {
- if (PrintAttNames)
- {
- fprintf(fout, formatString,
- TerseOutput ? "" : "|",
- PQfname(res, i));
- }
- }
-
- if (PrintAttNames)
- {
- if (TerseOutput)
- fprintf(fout, "\n");
- else
- fprintf(fout, "|\n%s\n", tborder);
- }
-
- for (i = 0; i < nTups; i++)
- {
- for (j = 0; j < nFields; j++)
- {
- char *pval = PQgetvalue(res, i, j);
-
- fprintf(fout, formatString,
- TerseOutput ? "" : "|",
- pval ? pval : "");
- }
- if (TerseOutput)
- fprintf(fout, "\n");
- else
- fprintf(fout, "|\n%s\n", tborder);
- }
+ if (send(conn->sock, msg, 1, MSG_OOB) < 0)
+ {
+ sprintf(conn->errorMessage,
+ "PQrequestCancel() -- couldn't send OOB data: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return FALSE;
}
+
+ return TRUE;
}
+/*
+ * Attempt to read a Notice response message.
+ * This is possible in several places, so we break it out as a subroutine.
+ * Entry: 'N' flag character has already been consumed.
+ * Exit: returns 0 if successfully consumed Notice message.
+ * returns EOF if not enough data.
+ */
+static int
+getNotice(PGconn *conn)
+{
+ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+ return EOF;
+ /*
+ * Should we really be doing this? These notices
+ * are not important enough for us to presume to
+ * put them on stderr. Maybe the caller should
+ * decide whether to put them on stderr or not.
+ * BJH 96.12.27
+ */
+ fprintf(stderr, "%s", conn->errorMessage);
+ return 0;
+}
-static void
-do_field(PQprintOpt *po, PGresult *res,
- const int i, const int j, char *buf, const int fs_len,
- char *fields[],
- const int nFields, char *fieldNames[],
- unsigned char fieldNotNum[], int fieldMax[],
- const int fieldMaxLen, FILE *fout
-)
+/*
+ * Attempt to read a Notify response message.
+ * This is possible in several places, so we break it out as a subroutine.
+ * Entry: 'A' flag character has already been consumed.
+ * Exit: returns 0 if successfully consumed Notify message.
+ * returns EOF if not enough data.
+ */
+static int
+getNotify(PGconn *conn)
{
+ PGnotify tempNotify;
+ PGnotify *newNotify;
- char *pval,
- *p,
- *o;
- int plen;
- bool skipit;
+ if (pqGetInt(&(tempNotify.be_pid), 4, conn))
+ return EOF;
+ if (pqGets(tempNotify.relname, NAMEDATALEN, conn))
+ return EOF;
+ newNotify = (PGnotify *) malloc(sizeof(PGnotify));
+ memcpy(newNotify, &tempNotify, sizeof(PGnotify));
+ DLAddTail(conn->notifyList, DLNewElem(newNotify));
+ return 0;
+}
- plen = PQgetlength(res, i, j);
- pval = PQgetvalue(res, i, j);
+/*
+ * PQnotifies
+ * returns a PGnotify* structure of the latest async notification
+ * that has not yet been handled
+ *
+ * returns NULL, if there is currently
+ * no unhandled async notification from the backend
+ *
+ * the CALLER is responsible for FREE'ing the structure returned
+ */
- if (plen < 1 || !pval || !*pval)
- {
- if (po->align || po->expanded)
- skipit = true;
- else
- {
- skipit = false;
- goto efield;
- }
- }
- else
- skipit = false;
+PGnotify *
+PQnotifies(PGconn *conn)
+{
+ Dlelem *e;
+ PGnotify *event;
- if (!skipit)
- {
- for (p = pval, o = buf; *p; *(o++) = *(p++))
- {
- if ((fs_len == 1 && (*p == *(po->fieldSep))) || *p == '\\' || *p == '\n')
- *(o++) = '\\';
- if (po->align && (*pval == 'E' || *pval == 'e' ||
- !((*p >= '0' && *p <= '9') ||
- *p == '.' ||
- *p == 'E' ||
- *p == 'e' ||
- *p == ' ' ||
- *p == '-')))
- fieldNotNum[j] = 1;
- }
- *o = '\0';
- if (!po->expanded && (po->align || po->html3))
- {
- int n = strlen(buf);
+ if (!conn)
+ return NULL;
- if (n > fieldMax[j])
- fieldMax[j] = n;
- if (!(fields[i * nFields + j] = (char *) malloc(n + 1)))
- {
- perror("malloc");
- exit(1);
- }
- strcpy(fields[i * nFields + j], buf);
- }
- else
- {
- if (po->expanded)
- {
- if (po->html3)
- fprintf(fout,
- "<tr><td align=left><b>%s</b></td>"
- "<td align=%s>%s</td></tr>\n",
- fieldNames[j],
- fieldNotNum[j] ? "left" : "right",
- buf);
- else
- {
- if (po->align)
- fprintf(fout,
- "%-*s%s %s\n",
- fieldMaxLen - fs_len, fieldNames[j], po->fieldSep,
- buf);
- else
- fprintf(fout, "%s%s%s\n", fieldNames[j], po->fieldSep, buf);
- }
- }
- else
- {
- if (!po->html3)
- {
- fputs(buf, fout);
- efield:
- if ((j + 1) < nFields)
- fputs(po->fieldSep, fout);
- else
- fputc('\n', fout);
- }
- }
- }
- }
-}
+ /* Parse any available data to see if we can extract NOTIFY messages. */
+ parseInput(conn);
+ /* RemHead returns NULL if list is empty */
+ e = DLRemHead(conn->notifyList);
+ if (!e)
+ return NULL;
+ event = (PGnotify *) DLE_VAL(e);
+ DLFreeElem(e);
+ return event;
+}
-static char *
-do_header(FILE *fout, PQprintOpt *po, const int nFields, int fieldMax[],
- char *fieldNames[], unsigned char fieldNotNum[],
- const int fs_len, PGresult *res)
+/*
+ * PQgetline - gets a newline-terminated string from the backend.
+ *
+ * Chiefly here so that applications can use "COPY <rel> to stdout"
+ * and read the output string. Returns a null-terminated string in s.
+ *
+ * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips
+ * the terminating \n (like gets(3)).
+ *
+ * RETURNS:
+ * EOF if it is detected or invalid arguments are given
+ * 0 if EOL is reached (i.e., \n has been read)
+ * (this is required for backward-compatibility -- this
+ * routine used to always return EOF or 0, assuming that
+ * the line ended within maxlen bytes.)
+ * 1 in other cases (i.e., the buffer was filled before \n is reached)
+ */
+int
+PQgetline(PGconn *conn, char *s, int maxlen)
{
+ int result = 1; /* return value if buffer overflows */
- int j; /* for loop index */
- char *border = NULL;
+ if (!s || maxlen <= 0)
+ return EOF;
- if (po->html3)
- fputs("<tr>", fout);
- else
+ if (!conn || conn->sock < 0)
{
- int j; /* for loop index */
- int tot = 0;
- int n = 0;
- char *p = NULL;
-
- for (; n < nFields; n++)
- tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
- if (po->standard)
- tot += fs_len * 2 + 2;
- border = malloc(tot + 1);
- if (!border)
- {
- perror("malloc");
- exit(1);
- }
- p = border;
- if (po->standard)
- {
- char *fs = po->fieldSep;
+ *s = '\0';
+ return EOF;
+ }
- while (*fs++)
- *p++ = '+';
- }
- for (j = 0; j < nFields; j++)
+ /* Since this is a purely synchronous routine, we don't bother to
+ * maintain conn->inCursor; there is no need to back up.
+ */
+ while (maxlen > 1)
+ {
+ if (conn->inStart < conn->inEnd)
{
- int len;
-
- for (len = fieldMax[j] + (po->standard ? 2 : 0); len--; *p++ = '-');
- if (po->standard || (j + 1) < nFields)
+ char c = conn->inBuffer[conn->inStart++];
+ if (c == '\n')
{
- char *fs = po->fieldSep;
-
- while (*fs++)
- *p++ = '+';
+ result = 0; /* success exit */
+ break;
}
- }
- *p = '\0';
- if (po->standard)
- fprintf(fout, "%s\n", border);
- }
- if (po->standard)
- fputs(po->fieldSep, fout);
- for (j = 0; j < nFields; j++)
- {
- char *s = PQfname(res, j);
-
- if (po->html3)
- {
- fprintf(fout, "<th align=%s>%s</th>",
- fieldNotNum[j] ? "left" : "right", fieldNames[j]);
+ *s++ = c;
+ maxlen--;
}
else
{
- int n = strlen(s);
-
- if (n > fieldMax[j])
- fieldMax[j] = n;
- if (po->standard)
- fprintf(fout,
- fieldNotNum[j] ? " %-*s " : " %*s ",
- fieldMax[j], s);
- else
- fprintf(fout, fieldNotNum[j] ? "%-*s" : "%*s", fieldMax[j], s);
- if (po->standard || (j + 1) < nFields)
- fputs(po->fieldSep, fout);
+ /* need to load more data */
+ if (pqWait(TRUE, FALSE, conn) ||
+ pqReadData(conn) < 0)
+ {
+ result = EOF;
+ break;
+ }
}
}
- if (po->html3)
- fputs("</tr>\n", fout);
- else
- fprintf(fout, "\n%s\n", border);
- return border;
-}
+ *s = '\0';
+ return result;
+}
-static void
-output_row(FILE *fout, PQprintOpt *po, const int nFields, char *fields[],
- unsigned char fieldNotNum[], int fieldMax[], char *border,
- const int row_index)
+/*
+ * PQputline -- sends a string to the backend.
+ *
+ * Chiefly here so that applications can use "COPY <rel> from stdin".
+ */
+void
+PQputline(PGconn *conn, const char *s)
{
-
- int field_index; /* for loop index */
-
- if (po->html3)
- fputs("<tr>", fout);
- else if (po->standard)
- fputs(po->fieldSep, fout);
- for (field_index = 0; field_index < nFields; field_index++)
+ if (conn && conn->sock >= 0)
{
- char *p = fields[row_index * nFields + field_index];
-
- if (po->html3)
- fprintf(fout, "<td align=%s>%s</td>",
- fieldNotNum[field_index] ? "left" : "right", p ? p : "");
- else
- {
- fprintf(fout,
- fieldNotNum[field_index] ?
- (po->standard ? " %-*s " : "%-*s") :
- (po->standard ? " %*s " : "%*s"),
- fieldMax[field_index],
- p ? p : "");
- if (po->standard || field_index + 1 < nFields)
- fputs(po->fieldSep, fout);
- }
- if (p)
- free(p);
+ (void) pqPuts(s, conn);
}
- if (po->html3)
- fputs("</tr>", fout);
- else if (po->standard)
- fprintf(fout, "\n%s", border);
- fputc('\n', fout);
}
-
-
-
/*
- * PQprint()
- *
- * Format results of a query for printing.
- *
- * PQprintOpt is a typedef (structure) that containes
- * various flags and options. consult libpq-fe.h for
- * details
+ * PQendcopy
+ * After completing the data transfer portion of a copy in/out,
+ * the application must call this routine to finish the command protocol.
*
- * Obsoletes PQprintTuples.
+ * RETURNS:
+ * 0 on success
+ * 1 on failure
*/
-
-void
-PQprint(FILE *fout,
- PGresult *res,
- PQprintOpt *po
-)
+int
+PQendcopy(PGconn *conn)
{
- int nFields;
-
- nFields = PQnfields(res);
-
- if (nFields > 0)
- { /* only print rows with at least 1 field. */
- int i,
- j;
- int nTups;
- int *fieldMax = NULL; /* in case we don't use them */
- unsigned char *fieldNotNum = NULL;
- char *border = NULL;
- char **fields = NULL;
- char **fieldNames;
- int fieldMaxLen = 0;
- int numFieldName;
- int fs_len = strlen(po->fieldSep);
- int total_line_length = 0;
- int usePipe = 0;
- char *pagerenv;
- char buf[8192 * 2 + 1];
-
- nTups = PQntuples(res);
- if (!(fieldNames = (char **) calloc(nFields, sizeof(char *))))
- {
- perror("calloc");
- exit(1);
- }
- if (!(fieldNotNum = (unsigned char *) calloc(nFields, 1)))
- {
- perror("calloc");
- exit(1);
- }
- if (!(fieldMax = (int *) calloc(nFields, sizeof(int))))
- {
- perror("calloc");
- exit(1);
- }
- for (numFieldName = 0;
- po->fieldName && po->fieldName[numFieldName];
- numFieldName++)
- ;
- for (j = 0; j < nFields; j++)
- {
- int len;
- char *s =
- (j < numFieldName && po->fieldName[j][0]) ?
- po->fieldName[j] : PQfname(res, j);
-
- fieldNames[j] = s;
- len = s ? strlen(s) : 0;
- fieldMax[j] = len;
- len += fs_len;
- if (len > fieldMaxLen)
- fieldMaxLen = len;
- total_line_length += len;
- }
+ PGresult *result;
- total_line_length += nFields * strlen(po->fieldSep) + 1;
+ if (!conn)
+ return 0;
- if (fout == NULL)
- fout = stdout;
- if (po->pager && fout == stdout &&
- isatty(fileno(stdin)) &&
- isatty(fileno(stdout)))
- {
- /* try to pipe to the pager program if possible */
-#ifdef TIOCGWINSZ
- if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
- screen_size.ws_col == 0 ||
- screen_size.ws_row == 0)
- {
-#endif
- screen_size.ws_row = 24;
- screen_size.ws_col = 80;
-#ifdef TIOCGWINSZ
- }
-#endif
- pagerenv = getenv("PAGER");
- if (pagerenv != NULL &&
- pagerenv[0] != '\0' &&
- !po->html3 &&
- ((po->expanded &&
- nTups * (nFields + 1) >= screen_size.ws_row) ||
- (!po->expanded &&
- nTups * (total_line_length / screen_size.ws_col + 1) *
- (1 + (po->standard != 0)) >=
- screen_size.ws_row -
- (po->header != 0) *
- (total_line_length / screen_size.ws_col + 1) * 2
- - (po->header != 0) * 2 /* row count and newline */
- )))
- {
- fout = popen(pagerenv, "w");
- if (fout)
- {
- usePipe = 1;
- pqsignal(SIGPIPE, SIG_IGN);
- }
- else
- fout = stdout;
- }
- }
+ if (conn->asyncStatus != PGASYNC_COPY_IN &&
+ conn->asyncStatus != PGASYNC_COPY_OUT)
+ {
+ sprintf(conn->errorMessage,
+ "PQendcopy() -- I don't think there's a copy in progress.");
+ return 1;
+ }
- if (!po->expanded && (po->align || po->html3))
- {
- if (!(fields = (char **) calloc(nFields * (nTups + 1), sizeof(char *))))
- {
- perror("calloc");
- exit(1);
- }
- }
- else if (po->header && !po->html3)
- {
- if (po->expanded)
- {
- if (po->align)
- fprintf(fout, "%-*s%s Value\n",
- fieldMaxLen - fs_len, "Field", po->fieldSep);
- else
- fprintf(fout, "%s%sValue\n", "Field", po->fieldSep);
- }
- else
- {
- int len = 0;
+ (void) pqFlush(conn); /* make sure no data is waiting to be sent */
- for (j = 0; j < nFields; j++)
- {
- char *s = fieldNames[j];
+ /* Return to active duty */
+ conn->asyncStatus = PGASYNC_BUSY;
- fputs(s, fout);
- len += strlen(s) + fs_len;
- if ((j + 1) < nFields)
- fputs(po->fieldSep, fout);
- }
- fputc('\n', fout);
- for (len -= fs_len; len--; fputc('-', fout));
- fputc('\n', fout);
- }
- }
- if (po->expanded && po->html3)
- {
- if (po->caption)
- fprintf(fout, "<centre><h2>%s</h2></centre>\n", po->caption);
- else
- fprintf(fout,
- "<centre><h2>"
- "Query retrieved %d rows * %d fields"
- "</h2></centre>\n",
- nTups, nFields);
- }
- for (i = 0; i < nTups; i++)
- {
- if (po->expanded)
- {
- if (po->html3)
- fprintf(fout,
- "<table %s><caption align=high>%d</caption>\n",
- po->tableOpt ? po->tableOpt : "", i);
- else
- fprintf(fout, "-- RECORD %d --\n", i);
- }
- for (j = 0; j < nFields; j++)
- do_field(po, res, i, j, buf, fs_len, fields, nFields,
- fieldNames, fieldNotNum,
- fieldMax, fieldMaxLen, fout);
- if (po->html3 && po->expanded)
- fputs("</table>\n", fout);
- }
- if (!po->expanded && (po->align || po->html3))
- {
- if (po->html3)
- {
- if (po->header)
- {
- if (po->caption)
- fprintf(fout,
- "<table %s><caption align=high>%s</caption>\n",
- po->tableOpt ? po->tableOpt : "",
- po->caption);
- else
- fprintf(fout,
- "<table %s><caption align=high>"
- "Retrieved %d rows * %d fields"
- "</caption>\n",
- po->tableOpt ? po->tableOpt : "", nTups, nFields);
- }
- else
- fprintf(fout, "<table %s>", po->tableOpt ? po->tableOpt : "");
- }
- if (po->header)
- border = do_header(fout, po, nFields, fieldMax, fieldNames,
- fieldNotNum, fs_len, res);
- for (i = 0; i < nTups; i++)
- output_row(fout, po, nFields, fields,
- fieldNotNum, fieldMax, border, i);
- free(fields);
- if (border)
- free(border);
- }
- if (po->header && !po->html3)
- fprintf(fout, "(%d row%s)\n\n", PQntuples(res),
- (PQntuples(res) == 1) ? "" : "s");
- free(fieldMax);
- free(fieldNotNum);
- free(fieldNames);
- if (usePipe)
- {
- pclose(fout);
- pqsignal(SIGPIPE, SIG_DFL);
- }
- if (po->html3 && !po->expanded)
- fputs("</table>\n", fout);
+ /* Wait for the completion response */
+ result = PQgetResult(conn);
+
+ /* Expecting a successful result */
+ if (result->resultStatus == PGRES_COMMAND_OK)
+ {
+ PQclear(result);
+ return 0;
}
+
+ /* Trouble.
+ * The worst case is that we've lost sync with the backend entirely
+ * due to application screwup of the copy in/out protocol.
+ * To recover, reset the connection (talk about using a sledgehammer...)
+ */
+ PQclear(result);
+ fprintf(stderr, "PQendcopy: resetting connection\n");
+ PQreset(conn);
+
+ return 1;
}
@@ -1491,14 +1018,15 @@ PQprint(FILE *fout,
* for varlena structures.)
* result_type : If the result is an integer, this must be 1,
* otherwise this should be 0
- * args : pointer to a NULL terminated arg array.
- * (length, if integer, and result-pointer)
+ * args : pointer to an array of function arguments.
+ * (each has length, if integer, and value/pointer)
* nargs : # of arguments in args array.
*
* RETURNS
- * NULL on failure. PQerrormsg will be set.
- * "G" if there is a return value.
- * "V" if there is no return value.
+ * PGresult with status = PGRES_COMMAND_OK if successful.
+ * *actual_result_len is > 0 if there is a return value, 0 if not.
+ * PGresult with status = PGRES_FATAL_ERROR if backend returns an error.
+ * NULL on communications failure. conn->errorMessage will be set.
* ----------------
*/
@@ -1511,116 +1039,147 @@ PQfn(PGconn *conn,
PQArgBlock *args,
int nargs)
{
- FILE *pfin,
- *pfout,
- *pfdebug;
- int id;
+ bool needInput = false;
+ ExecStatusType status = PGRES_FATAL_ERROR;
+ char id;
int i;
+ *actual_result_len = 0;
+
if (!conn)
return NULL;
- pfin = conn->Pfin;
- pfout = conn->Pfout;
- pfdebug = conn->Pfdebug;
+ if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE)
+ {
+ sprintf(conn->errorMessage, "PQfn() -- connection in wrong state\n");
+ return NULL;
+ }
/* clear the error string */
conn->errorMessage[0] = '\0';
- pqPuts("F ", pfout, pfdebug); /* function */
- pqPutInt(fnid, 4, pfout, pfdebug); /* function id */
- pqPutInt(nargs, 4, pfout, pfdebug); /* # of args */
+ if (pqPuts("F ", conn)) /* function */
+ return NULL;
+ if (pqPutInt(fnid, 4, conn)) /* function id */
+ return NULL;
+ if (pqPutInt(nargs, 4, conn)) /* # of args */
+ return NULL;
for (i = 0; i < nargs; ++i)
{ /* len.int4 + contents */
- pqPutInt(args[i].len, 4, pfout, pfdebug);
+ if (pqPutInt(args[i].len, 4, conn))
+ return NULL;
+
if (args[i].isint)
{
- pqPutInt(args[i].u.integer, 4, pfout, pfdebug);
+ if (pqPutInt(args[i].u.integer, 4, conn))
+ return NULL;
}
else
{
- pqPutnchar((char *) args[i].u.ptr, args[i].len, pfout, pfdebug);
+ if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
+ return NULL;
}
}
- pqFlush(pfout, pfdebug);
+ if (pqFlush(conn))
+ return NULL;
- while ((id = pqGetc(pfin, pfdebug)) != 'V')
+ for (;;)
{
- if (id == 'E')
+ if (needInput)
{
- pqGets(conn->errorMessage, ERROR_MSG_LENGTH, pfin, pfdebug);
- }
- else if (id == 'N')
- {
- /* print notice and go back to processing return
- values */
- if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH,
- pfin, pfdebug) == 1)
- {
- sprintf(conn->errorMessage,
- "Notice return detected from backend, but "
- "message cannot be read");
- }
- else
- fprintf(stderr, "%s\n", conn->errorMessage);
- continue;
+ /* Wait for some data to arrive (or for the channel to close) */
+ if (pqWait(TRUE, FALSE, conn) ||
+ pqReadData(conn) < 0)
+ break;
}
- else
- sprintf(conn->errorMessage,
- "PQfn: expected a 'V' from the backend. Got '%c' instead",
- id);
- return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
- }
+ /* Scan the message.
+ * If we run out of data, loop around to try again.
+ */
+ conn->inCursor = conn->inStart;
+ needInput = true;
- id = pqGetc(pfin, pfdebug);
- for (;;)
- {
- int c;
+ if (pqGetc(&id, conn))
+ continue;
+ /* We should see V or E response to the command,
+ * but might get N and/or A notices first.
+ * We also need to swallow the final Z before returning.
+ */
switch (id)
{
- case 'G': /* function returned properly */
- pqGetInt(actual_result_len, 4, pfin, pfdebug);
- if (result_is_int)
+ case 'V': /* function result */
+ if (pqGetc(&id, conn))
+ continue;
+ if (id == 'G')
{
- pqGetInt(result_buf, 4, pfin, pfdebug);
+ /* function returned nonempty value */
+ if (pqGetInt(actual_result_len, 4, conn))
+ continue;
+ if (result_is_int)
+ {
+ if (pqGetInt(result_buf, 4, conn))
+ continue;
+ }
+ else
+ {
+ if (pqGetnchar((char *) result_buf,
+ *actual_result_len,
+ conn))
+ continue;
+ }
+ if (pqGetc(&id, conn)) /* get the last '0' */
+ continue;
}
- else
+ if (id == '0')
{
- pqGetnchar((char *) result_buf, *actual_result_len,
- pfin, pfdebug);
+ /* correctly finished function result message */
+ status = PGRES_COMMAND_OK;
}
- c = pqGetc(pfin, pfdebug); /* get the last '0' */
- return makeEmptyPGresult(conn, PGRES_COMMAND_OK);
- case 'E':
- sprintf(conn->errorMessage,
- "PQfn: returned an error");
- return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
- case 'N':
- /* print notice and go back to processing return values */
- if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, pfin, pfdebug)
- == 1)
- {
+ else {
+ /* The backend violates the protocol. */
sprintf(conn->errorMessage,
- "Notice return detected from backend, but message "
- "cannot be read");
+ "FATAL: PQfn: protocol error: id=%x\n", id);
+ conn->inStart = conn->inCursor;
+ return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
}
- else
- fprintf(stderr, "%s\n", conn->errorMessage);
- /* keep iterating */
break;
- case '0': /* no return value */
- return makeEmptyPGresult(conn, PGRES_COMMAND_OK);
+ case 'E': /* error return */
+ if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+ continue;
+ status = PGRES_FATAL_ERROR;
+ break;
+ case 'A': /* notify message */
+ /* handle notify and go back to processing return values */
+ if (getNotify(conn))
+ continue;
+ break;
+ case 'N': /* notice */
+ /* handle notice and go back to processing return values */
+ if (getNotice(conn))
+ continue;
+ break;
+ case 'Z': /* backend is ready for new query */
+ /* consume the message and exit */
+ conn->inStart = conn->inCursor;
+ return makeEmptyPGresult(conn, status);
default:
/* The backend violates the protocol. */
sprintf(conn->errorMessage,
"FATAL: PQfn: protocol error: id=%x\n", id);
+ conn->inStart = conn->inCursor;
return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
}
+ /* Completed this message, keep going */
+ conn->inStart = conn->inCursor;
+ needInput = false;
}
+
+ /* we fall out of the loop only upon failing to read data */
+ return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
}
+
/* ====== accessor funcs for PGresult ======== */
ExecStatusType
@@ -1628,7 +1187,7 @@ PQresultStatus(PGresult *res)
{
if (!res)
{
- fprintf(stderr, "PQresultStatus() -- pointer to PQresult is null");
+ fprintf(stderr, "PQresultStatus() -- pointer to PQresult is null\n");
return PGRES_NONFATAL_ERROR;
}
@@ -1640,8 +1199,8 @@ PQntuples(PGresult *res)
{
if (!res)
{
- fprintf(stderr, "PQntuples() -- pointer to PQresult is null");
- return (int) NULL;
+ fprintf(stderr, "PQntuples() -- pointer to PQresult is null\n");
+ return 0;
}
return res->ntups;
}
@@ -1651,8 +1210,8 @@ PQnfields(PGresult *res)
{
if (!res)
{
- fprintf(stderr, "PQnfields() -- pointer to PQresult is null");
- return (int) NULL;
+ fprintf(stderr, "PQnfields() -- pointer to PQresult is null\n");
+ return 0;
}
return res->numAttributes;
}
@@ -1665,14 +1224,14 @@ PQfname(PGresult *res, int field_num)
{
if (!res)
{
- fprintf(stderr, "PQfname() -- pointer to PQresult is null");
+ fprintf(stderr, "PQfname() -- pointer to PQresult is null\n");
return NULL;
}
- if (field_num > (res->numAttributes - 1))
+ if (field_num < 0 || field_num >= res->numAttributes)
{
fprintf(stderr,
- "PQfname: ERROR! name of field %d(of %d) is not available",
+ "PQfname: ERROR! field number %d is out of range 0..%d\n",
field_num, res->numAttributes - 1);
return NULL;
}
@@ -1695,7 +1254,7 @@ PQfnumber(PGresult *res, const char *field_name)
if (!res)
{
- fprintf(stderr, "PQfnumber() -- pointer to PQresult is null");
+ fprintf(stderr, "PQfnumber() -- pointer to PQresult is null\n");
return -1;
}
@@ -1732,15 +1291,16 @@ PQftype(PGresult *res, int field_num)
{
if (!res)
{
- fprintf(stderr, "PQftype() -- pointer to PQresult is null");
+ fprintf(stderr, "PQftype() -- pointer to PQresult is null\n");
return InvalidOid;
}
- if (field_num > (res->numAttributes - 1))
+ if (field_num < 0 || field_num >= res->numAttributes)
{
fprintf(stderr,
- "PQftype: ERROR! type of field %d(of %d) is not available",
+ "PQftype: ERROR! field number %d is out of range 0..%d\n",
field_num, res->numAttributes - 1);
+ return InvalidOid;
}
if (res->attDescs)
{
@@ -1750,20 +1310,21 @@ PQftype(PGresult *res, int field_num)
return InvalidOid;
}
-int2
+short
PQfsize(PGresult *res, int field_num)
{
if (!res)
{
- fprintf(stderr, "PQfsize() -- pointer to PQresult is null");
- return (int2) NULL;
+ fprintf(stderr, "PQfsize() -- pointer to PQresult is null\n");
+ return 0;
}
- if (field_num > (res->numAttributes - 1))
+ if (field_num < 0 || field_num >= res->numAttributes)
{
fprintf(stderr,
- "PQfsize: ERROR! size of field %d(of %d) is not available",
+ "PQfsize: ERROR! field number %d is out of range 0..%d\n",
field_num, res->numAttributes - 1);
+ return 0;
}
if (res->attDescs)
{
@@ -1773,12 +1334,36 @@ PQfsize(PGresult *res, int field_num)
return 0;
}
+short
+PQfmod(PGresult *res, int field_num)
+{
+ if (!res)
+ {
+ fprintf(stderr, "PQfmod() -- pointer to PQresult is null\n");
+ return 0;
+ }
+
+ if (field_num < 0 || field_num >= res->numAttributes)
+ {
+ fprintf(stderr,
+ "PQfmod: ERROR! field number %d is out of range 0..%d\n",
+ field_num, res->numAttributes - 1);
+ return 0;
+ }
+ if (res->attDescs)
+ {
+ return res->attDescs[field_num].adtmod;
+ }
+ else
+ return 0;
+}
+
char *
PQcmdStatus(PGresult *res)
{
if (!res)
{
- fprintf(stderr, "PQcmdStatus() -- pointer to PQresult is null");
+ fprintf(stderr, "PQcmdStatus() -- pointer to PQresult is null\n");
return NULL;
}
return res->cmdStatus;
@@ -1789,21 +1374,20 @@ PQcmdStatus(PGresult *res)
if the last command was an INSERT, return the oid string
if not, return ""
*/
-static char oidStatus[32] = {0};
const char *
PQoidStatus(PGresult *res)
{
+ static char oidStatus[32] = {0};
+
if (!res)
{
- fprintf(stderr, "PQoidStatus () -- pointer to PQresult is null");
+ fprintf(stderr, "PQoidStatus () -- pointer to PQresult is null\n");
return NULL;
}
oidStatus[0] = 0;
- if (!res->cmdStatus)
- return oidStatus;
- if (strncmp(res->cmdStatus, "INSERT", 6) == 0)
+ if (strncmp(res->cmdStatus, "INSERT ", 7) == 0)
{
char *p = res->cmdStatus + 7;
char *e;
@@ -1825,13 +1409,10 @@ PQcmdTuples(PGresult *res)
{
if (!res)
{
- fprintf(stderr, "PQcmdTuples () -- pointer to PQresult is null");
+ fprintf(stderr, "PQcmdTuples () -- pointer to PQresult is null\n");
return NULL;
}
- if (!res->cmdStatus)
- return "";
-
if (strncmp(res->cmdStatus, "INSERT", 6) == 0 ||
strncmp(res->cmdStatus, "DELETE", 6) == 0 ||
strncmp(res->cmdStatus, "UPDATE", 6) == 0)
@@ -1840,7 +1421,7 @@ PQcmdTuples(PGresult *res)
if (*p == 0)
{
- fprintf(stderr, "PQcmdTuples (%s) -- short input from server",
+ fprintf(stderr, "PQcmdTuples (%s) -- bad input from server\n",
res->cmdStatus);
return NULL;
}
@@ -1851,7 +1432,7 @@ PQcmdTuples(PGresult *res)
p++; /* INSERT: skip oid */
if (*p == 0)
{
- fprintf(stderr, "PQcmdTuples (INSERT) -- there's no # of tuples");
+ fprintf(stderr, "PQcmdTuples (INSERT) -- there's no # of tuples\n");
return NULL;
}
p++;
@@ -1878,7 +1459,7 @@ PQgetvalue(PGresult *res, int tup_num, int field_num)
fprintf(stderr, "PQgetvalue: pointer to PQresult is null\n");
return NULL;
}
- else if (tup_num > (res->ntups - 1))
+ if (tup_num < 0 || tup_num >= res->ntups)
{
fprintf(stderr,
"PQgetvalue: There is no row %d in the query results. "
@@ -1886,7 +1467,7 @@ PQgetvalue(PGresult *res, int tup_num, int field_num)
tup_num, res->ntups - 1);
return NULL;
}
- else if (field_num > (res->numAttributes - 1))
+ if (field_num < 0 || field_num >= res->numAttributes)
{
fprintf(stderr,
"PQgetvalue: There is no field %d in the query results. "
@@ -1910,17 +1491,25 @@ PQgetlength(PGresult *res, int tup_num, int field_num)
{
if (!res)
{
- fprintf(stderr, "PQgetlength() -- pointer to PQresult is null");
- return (int) NULL;
+ fprintf(stderr, "PQgetlength() -- pointer to PQresult is null\n");
+ return 0;
}
- if (tup_num > (res->ntups - 1) ||
- field_num > (res->numAttributes - 1))
+ if (tup_num < 0 || tup_num >= res->ntups)
{
fprintf(stderr,
- "PQgetlength: ERROR! field %d(of %d) of row %d(of %d) "
- "is not available",
- field_num, res->numAttributes - 1, tup_num, res->ntups);
+ "PQgetlength: There is no row %d in the query results. "
+ "The highest numbered row is %d.\n",
+ tup_num, res->ntups - 1);
+ return 0;
+ }
+ if (field_num < 0 || field_num >= res->numAttributes)
+ {
+ fprintf(stderr,
+ "PQgetlength: There is no field %d in the query results. "
+ "The highest numbered field is %d.\n",
+ field_num, res->numAttributes - 1);
+ return 0;
}
if (res->tuples[tup_num][field_num].len != NULL_LEN)
@@ -1937,17 +1526,24 @@ PQgetisnull(PGresult *res, int tup_num, int field_num)
{
if (!res)
{
- fprintf(stderr, "PQgetisnull() -- pointer to PQresult is null");
- return (int) NULL;
+ fprintf(stderr, "PQgetisnull() -- pointer to PQresult is null\n");
+ return 1; /* pretend it is null */
}
-
- if (tup_num > (res->ntups - 1) ||
- field_num > (res->numAttributes - 1))
+ if (tup_num < 0 || tup_num >= res->ntups)
{
fprintf(stderr,
- "PQgetisnull: ERROR! field %d(of %d) of row %d(of %d) "
- "is not available",
- field_num, res->numAttributes - 1, tup_num, res->ntups);
+ "PQgetisnull: There is no row %d in the query results. "
+ "The highest numbered row is %d.\n",
+ tup_num, res->ntups - 1);
+ return 1; /* pretend it is null */
+ }
+ if (field_num < 0 || field_num >= res->numAttributes)
+ {
+ fprintf(stderr,
+ "PQgetisnull: There is no field %d in the query results. "
+ "The highest numbered field is %d.\n",
+ field_num, res->numAttributes - 1);
+ return 1; /* pretend it is null */
}
if (res->tuples[tup_num][field_num].len == NULL_LEN)
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 8a16950cdcd..d7fc71dd134 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -5,177 +5,496 @@
*
* DESCRIPTION
* miscellaneous useful functions
- * these routines are analogous to the ones in libpq/pqcomm.c
+ *
+ * The communication routines here are analogous to the ones in
+ * backend/libpq/pqcomm.c and backend/libpq/pqcomprim.c, but operate
+ * in the considerably different environment of the frontend libpq.
+ * In particular, we work with a bare nonblock-mode socket, rather than
+ * a stdio stream, so that we can avoid unwanted blocking of the application.
+ *
+ * XXX: MOVE DEBUG PRINTOUT TO HIGHER LEVEL. As is, block and restart
+ * will cause repeat printouts.
+ *
+ * We must speak the same transmitted data representations as the backend
+ * routines. Note that this module supports *only* network byte order
+ * for transmitted ints, whereas the backend modules (as of this writing)
+ * still handle either network or little-endian byte order.
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.10 1998/02/26 04:45:09 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.11 1998/05/06 23:51:14 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#if !defined(NO_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <sys/types.h> /* for fd_set stuff */
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
#include "postgres.h"
-
#include "libpq-fe.h"
/* --------------------------------------------------------------------- */
/* pqGetc:
- get a character from stream f
+ get a character from the connection
- if debug is set, also echo the character fetched
+ All these routines return 0 on success, EOF on error.
+ Note that for the Get routines, EOF only means there is not enough
+ data in the buffer, not that there is necessarily a hard error.
*/
int
-pqGetc(FILE *fin, FILE *debug)
+pqGetc(char *result, PGconn *conn)
{
- int c;
+ if (conn->inCursor >= conn->inEnd)
+ return EOF;
- c = getc(fin);
+ *result = conn->inBuffer[conn->inCursor++];
- if (debug && c != EOF)
- fprintf(debug, "From backend> %c\n", c);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend> %c\n", *result);
- return c;
+ return 0;
}
+
/* --------------------------------------------------------------------- */
-/* pqPutnchar:
- send a string of exactly len length into stream f
+/* pqPutBytes: local routine to write N bytes to the connection,
+ with buffering
+ */
+static int
+pqPutBytes(const char *s, int nbytes, PGconn *conn)
+{
+ int avail = conn->outBufSize - conn->outCount;
+
+ while (nbytes > avail)
+ {
+ memcpy(conn->outBuffer + conn->outCount, s, avail);
+ conn->outCount += avail;
+ s += avail;
+ nbytes -= avail;
+ if (pqFlush(conn))
+ return EOF;
+ avail = conn->outBufSize;
+ }
- returns 1 if there was an error, 0 otherwise.
+ memcpy(conn->outBuffer + conn->outCount, s, nbytes);
+ conn->outCount += nbytes;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/* pqGets:
+ get a null-terminated string from the connection,
+ and store it in a buffer of size maxlen bytes.
+ If the incoming string is >= maxlen bytes, all of it is read,
+ but the excess characters are silently discarded.
*/
int
-pqPutnchar(const char *s, int len, FILE *f, FILE *debug)
+pqGets(char *s, int maxlen, PGconn *conn)
+{
+ /* Copy conn data to locals for faster search loop */
+ char *inBuffer = conn->inBuffer;
+ int inCursor = conn->inCursor;
+ int inEnd = conn->inEnd;
+ int slen;
+
+ while (inCursor < inEnd && inBuffer[inCursor])
+ inCursor++;
+
+ if (inCursor >= inEnd)
+ return EOF;
+
+ slen = inCursor - conn->inCursor;
+ if (slen < maxlen)
+ strcpy(s, inBuffer + conn->inCursor);
+ else
+ {
+ strncpy(s, inBuffer + conn->inCursor, maxlen-1);
+ s[maxlen-1] = '\0';
+ }
+
+ conn->inCursor = ++inCursor;
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend> \"%s\"\n", s);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+int
+pqPuts(const char *s, PGconn *conn)
{
- if (debug)
- fprintf(debug, "To backend> %s\n", s);
+ if (pqPutBytes(s, strlen(s)+1, conn))
+ return EOF;
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> %s\n", s);
- return (pqPutNBytes(s, len, f) == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
/* pqGetnchar:
get a string of exactly len bytes in buffer s (which must be 1 byte
- longer) from stream f and terminate it with a '\0'.
+ longer) and terminate it with a '\0'.
*/
int
-pqGetnchar(char *s, int len, FILE *f, FILE *debug)
+pqGetnchar(char *s, int len, PGconn *conn)
{
- int status;
+ if (len < 0 || len > conn->inEnd - conn->inCursor)
+ return EOF;
+
+ memcpy(s, conn->inBuffer + conn->inCursor, len);
+ s[len] = '\0';
- status = pqGetNBytes(s, len, f);
+ conn->inCursor += len;
- if (debug)
- fprintf(debug, "From backend (%d)> %s\n", len, s);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend (%d)> %s\n", len, s);
- return (status == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pqGets:
- get a string of up to length len from stream f
+/* pqPutnchar:
+ send a string of exactly len bytes
+ The buffer should have a terminating null, but it's not sent.
*/
int
-pqGets(char *s, int len, FILE *f, FILE *debug)
+pqPutnchar(const char *s, int len, PGconn *conn)
{
- int status;
-
- status = pqGetString(s, len, f);
+ if (pqPutBytes(s, len, conn))
+ return EOF;
- if (debug)
- fprintf(debug, "From backend> \"%s\"\n", s);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> %s\n", s);
- return (status == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pgPutInt
- send an integer of 2 or 4 bytes to the file stream, compensate
- for host endianness.
- returns 0 if successful, 1 otherwise
+/* pgGetInt
+ read a 2 or 4 byte integer and convert from network byte order
+ to local byte order
*/
int
-pqPutInt(const int integer, int bytes, FILE *f, FILE *debug)
+pqGetInt(int *result, int bytes, PGconn *conn)
{
- int retval = 0;
+ uint16 tmp2;
+ uint32 tmp4;
switch (bytes)
{
case 2:
- retval = pqPutShort(integer, f);
+ if (conn->inCursor + 2 > conn->inEnd)
+ return EOF;
+ memcpy(&tmp2, conn->inBuffer + conn->inCursor, 2);
+ conn->inCursor += 2;
+ *result = (int) ntohs(tmp2);
break;
case 4:
- retval = pqPutLong(integer, f);
+ if (conn->inCursor + 4 > conn->inEnd)
+ return EOF;
+ memcpy(&tmp4, conn->inBuffer + conn->inCursor, 4);
+ conn->inCursor += 4;
+ *result = (int) ntohl(tmp4);
break;
default:
fprintf(stderr, "** int size %d not supported\n", bytes);
- retval = 1;
+ return EOF;
}
- if (debug)
- fprintf(debug, "To backend (%d#)> %d\n", bytes, integer);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend (#%d)> %d\n", bytes, *result);
- return retval;
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pgGetInt
- read a 2 or 4 byte integer from the stream and swab it around
- to compensate for different endianness
- returns 0 if successful
+/* pgPutInt
+ send an integer of 2 or 4 bytes, converting from host byte order
+ to network byte order.
*/
int
-pqGetInt(int *result, int bytes, FILE *f, FILE *debug)
+pqPutInt(int value, int bytes, PGconn *conn)
{
- int retval = 0;
+ uint16 tmp2;
+ uint32 tmp4;
switch (bytes)
{
case 2:
- retval = pqGetShort(result, f);
+ tmp2 = htons((uint16) value);
+ if (pqPutBytes((const char*) &tmp2, 2, conn))
+ return EOF;
break;
case 4:
- retval = pqGetLong(result, f);
+ tmp4 = htonl((uint32) value);
+ if (pqPutBytes((const char*) &tmp4, 4, conn))
+ return EOF;
break;
default:
fprintf(stderr, "** int size %d not supported\n", bytes);
- retval = 1;
+ return EOF;
}
- if (debug)
- fprintf(debug, "From backend (#%d)> %d\n", bytes, *result);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend (%d#)> %d\n", bytes, value);
- return retval;
+ return 0;
}
/* --------------------------------------------------------------------- */
+/* pqReadReady: is select() saying the file is ready to read?
+ */
+static int
+pqReadReady(PGconn *conn)
+{
+ fd_set input_mask;
+ struct timeval timeout;
+
+ if (conn->sock < 0)
+ return 0;
+
+ FD_ZERO(&input_mask);
+ FD_SET(conn->sock, &input_mask);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(conn->sock+1, &input_mask, (fd_set *) NULL, (fd_set *) NULL,
+ &timeout) < 0)
+ {
+ sprintf(conn->errorMessage,
+ "pqReadReady() -- select() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return 0;
+ }
+ return FD_ISSET(conn->sock, &input_mask);
+}
+
+/* --------------------------------------------------------------------- */
+/* pqReadData: read more data, if any is available
+ * Possible return values:
+ * 1: successfully loaded at least one more byte
+ * 0: no data is presently available, but no error detected
+ * -1: error detected (including EOF = connection closure);
+ * conn->errorMessage set
+ * NOTE: callers must not assume that pointers or indexes into conn->inBuffer
+ * remain valid across this call!
+ */
int
-pqPuts(const char *s, FILE *f, FILE *debug)
+pqReadData(PGconn *conn)
{
- if (pqPutString(s, f) == EOF)
- return 1;
+ int nread;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqReadData() -- connection not open\n");
+ return -1;
+ }
- fflush(f);
+ /* Left-justify any data in the buffer to make room */
+ if (conn->inStart < conn->inEnd)
+ {
+ memmove(conn->inBuffer, conn->inBuffer + conn->inStart,
+ conn->inEnd - conn->inStart);
+ conn->inEnd -= conn->inStart;
+ conn->inCursor -= conn->inStart;
+ conn->inStart = 0;
+ }
+ else
+ {
+ conn->inStart = conn->inCursor = conn->inEnd = 0;
+ }
+ /* If the buffer is fairly full, enlarge it.
+ * We need to be able to enlarge the buffer in case a single message
+ * exceeds the initial buffer size. We enlarge before filling the
+ * buffer entirely so as to avoid asking the kernel for a partial packet.
+ * The magic constant here should be at least one TCP packet.
+ */
+ if (conn->inBufSize - conn->inEnd < 2000)
+ {
+ int newSize = conn->inBufSize * 2;
+ char * newBuf = (char *) realloc(conn->inBuffer, newSize);
+ if (newBuf)
+ {
+ conn->inBuffer = newBuf;
+ conn->inBufSize = newSize;
+ }
+ }
- if (debug)
- fprintf(debug, "To backend> %s\n", s);
+ /* OK, try to read some data */
+tryAgain:
+ nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
+ conn->inBufSize - conn->inEnd, 0);
+ if (nread < 0)
+ {
+ if (errno == EINTR)
+ goto tryAgain;
+ sprintf(conn->errorMessage,
+ "pqReadData() -- read() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+ if (nread > 0)
+ {
+ conn->inEnd += nread;
+ return 1;
+ }
- return 0;
+ /* A return value of 0 could mean just that no data is now available,
+ * or it could mean EOF --- that is, the server has closed the connection.
+ * Since we have the socket in nonblock mode, the only way to tell the
+ * difference is to see if select() is saying that the file is ready.
+ * Grumble. Fortunately, we don't expect this path to 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 */
+
+ /* Still not sure that it's EOF,
+ * because some data could have just arrived.
+ */
+tryAgain2:
+ nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
+ conn->inBufSize - conn->inEnd, 0);
+ if (nread < 0)
+ {
+ if (errno == EINTR)
+ goto tryAgain2;
+ sprintf(conn->errorMessage,
+ "pqReadData() -- read() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+ if (nread > 0)
+ {
+ conn->inEnd += nread;
+ return 1;
+ }
+
+ /* OK, we are getting a zero read even though select() says ready.
+ * This means the connection has been closed. Cope.
+ */
+ sprintf(conn->errorMessage,
+ "pqReadData() -- backend closed the channel unexpectedly.\n"
+ "\tThis probably means the backend terminated abnormally"
+ " before or while processing the request.\n");
+ conn->status = CONNECTION_BAD; /* No more connection to
+ * backend */
+ close(conn->sock);
+ conn->sock = -1;
+
+ return -1;
}
/* --------------------------------------------------------------------- */
-void
-pqFlush(FILE *f, FILE *debug)
+/* pqFlush: send any data waiting in the output buffer
+ */
+int
+pqFlush(PGconn *conn)
{
- if (f)
- fflush(f);
+ char * ptr = conn->outBuffer;
+ int len = conn->outCount;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqFlush() -- connection not open\n");
+ return EOF;
+ }
- if (debug)
- fflush(debug);
+ while (len > 0)
+ {
+ int sent = send(conn->sock, ptr, len, 0);
+ if (sent < 0)
+ {
+ /* Anything except EAGAIN or EWOULDBLOCK is trouble */
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+ break;
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+ break;
+#endif
+ default:
+ sprintf(conn->errorMessage,
+ "pqFlush() -- couldn't send data: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return EOF;
+ }
+ }
+ else
+ {
+ ptr += sent;
+ len -= sent;
+ }
+ if (len > 0)
+ {
+ /* We didn't send it all, wait till we can send more */
+ if (pqWait(FALSE, TRUE, conn))
+ return EOF;
+ }
+ }
+
+ conn->outCount = 0;
+
+ if (conn->Pfdebug)
+ fflush(conn->Pfdebug);
+
+ return 0;
}
/* --------------------------------------------------------------------- */
+/* pqWait: wait until we can read or write the connection socket
+ */
+int
+pqWait(int forRead, int forWrite, PGconn *conn)
+{
+ fd_set input_mask;
+ fd_set output_mask;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqWait() -- connection not open\n");
+ return EOF;
+ }
+
+ /* loop in case select returns EINTR */
+ for (;;) {
+ FD_ZERO(&input_mask);
+ FD_ZERO(&output_mask);
+ if (forRead)
+ FD_SET(conn->sock, &input_mask);
+ if (forWrite)
+ FD_SET(conn->sock, &output_mask);
+ if (select(conn->sock+1, &input_mask, &output_mask, (fd_set *) NULL,
+ (struct timeval *) NULL) < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ sprintf(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;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 4fe8347a046..7b932762761 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
- * $Id: libpq-fe.h,v 1.28 1998/03/20 04:02:57 momjian Exp $
+ * $Id: libpq-fe.h,v 1.29 1998/05/06 23:51:16 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,6 +28,8 @@ extern "C"
#include "libpq/pqcomm.h"
#include "lib/dllist.h"
+/* Application-visible enum types */
+
typedef enum
{
CONNECTION_OK,
@@ -41,14 +43,13 @@ extern "C"
/* anything was executed properly by the backend */
PGRES_TUPLES_OK, /* a query command that returns tuples */
/* was executed properly by the backend, PGresult */
- /* contains the resulttuples */
- PGRES_COPY_OUT,
- PGRES_COPY_IN,
+ /* contains the result tuples */
+ PGRES_COPY_OUT, /* Copy Out data transfer in progress */
+ PGRES_COPY_IN, /* Copy In data transfer in progress */
PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from
* the backend */
PGRES_NONFATAL_ERROR,
PGRES_FATAL_ERROR
-
} ExecStatusType;
/* string descriptions of the ExecStatusTypes */
@@ -63,29 +64,21 @@ extern "C"
#define COMMAND_LENGTH 20
#define REMARK_LENGTH 80
#define PORTAL_NAME_LENGTH 16
+#define CMDSTATUS_LEN 40
-/* ----------------
- * PQArgBlock --
- * Information (pointer to array of this structure) required
- * for the PQfn() call.
- * ----------------
+/* PGresult and the subsidiary types PGresAttDesc, PGresAttValue
+ * represent the result of a query (or more precisely, of a single SQL
+ * command --- a query string given to PQexec can contain multiple commands).
+ * Note we assume that a single command can return at most one tuple group,
+ * hence there is no need for multiple descriptor sets.
*/
- typedef struct
- {
- int len;
- int isint;
- union
- {
- int *ptr; /* can't use void (dec compiler barfs) */
- int integer;
- } u;
- } PQArgBlock;
typedef struct pgresAttDesc
{
char *name; /* type name */
Oid adtid; /* type id */
short adtsize; /* type size */
+ short adtmod; /* type-specific modifier info */
} PGresAttDesc;
/* use char* for Attribute values,
@@ -102,6 +95,25 @@ extern "C"
char *value; /* actual value */
} PGresAttValue;
+ struct pg_conn; /* forward reference */
+
+ typedef struct pg_result
+ {
+ int ntups;
+ int numAttributes;
+ PGresAttDesc *attDescs;
+ PGresAttValue **tuples; /* each PGresTuple is an array of
+ * PGresAttValue's */
+ int tupArrSize; /* size of tuples array allocated */
+ ExecStatusType resultStatus;
+ char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the
+ * last insert query */
+ int binary; /* binary tuple values if binary == 1,
+ * otherwise ASCII */
+ struct pg_conn *conn; /* connection we did the query on */
+ } PGresult;
+
+/* PGnotify represents the occurrence of a NOTIFY message */
typedef struct pgNotify
{
char relname[NAMEDATALEN]; /* name of relation
@@ -109,6 +121,18 @@ extern "C"
int be_pid; /* process id of backend */
} PGnotify;
+/* PGAsyncStatusType is private to libpq, really shouldn't be seen by users */
+ typedef enum
+ {
+ PGASYNC_IDLE, /* nothing's happening, dude */
+ PGASYNC_BUSY, /* query in progress */
+ PGASYNC_READY, /* result ready for PQgetResult */
+ PGASYNC_COPY_IN, /* Copy In data transfer in progress */
+ PGASYNC_COPY_OUT /* Copy Out data transfer in progress */
+ } PGAsyncStatusType;
+
+/* large-object-access data ... allocated only if large-object code is used.
+ * Really shouldn't be visible to users */
typedef struct pgLobjfuncs
{
Oid fn_lo_open; /* OID of backend function lo_open */
@@ -122,54 +146,62 @@ extern "C"
Oid fn_lo_write;/* OID of backend function LOwrite */
} PGlobjfuncs;
-/* PGconn encapsulates a connection to the backend */
+/* PGconn encapsulates a connection to the backend.
+ * XXX contents of this struct really shouldn't be visible to applications
+ */
typedef struct pg_conn
{
+ /* Saved values of connection options */
char *pghost; /* the machine on which the server is
* running */
+ char *pgport; /* the server's communication port */
char *pgtty; /* tty on which the backend messages is
- * displayed */
- char *pgport; /* the communication port with the backend */
+ * displayed (NOT ACTUALLY USED???) */
char *pgoptions; /* options to start the backend with */
char *dbName; /* database name */
- ConnStatusType status;
- char errorMessage[ERROR_MSG_LENGTH];
- /* pipes for be/fe communication */
- FILE *Pfin;
- FILE *Pfout;
+ char *pguser; /* Postgres username and password, if any */
+ char *pgpass;
+
+ /* Optional file to write trace info to */
FILE *Pfdebug;
- int sock; /* The socket */
+
+ /* Status indicators */
+ ConnStatusType status;
+ PGAsyncStatusType asyncStatus;
+ Dllist *notifyList; /* Notify msgs not yet handed to application */
+
+ /* Connection data */
+ int sock; /* Unix FD for socket, -1 if not connected */
SockAddr laddr; /* Local address */
SockAddr raddr; /* Remote address */
- char salt[2];
- int asyncNotifyWaiting;
- Dllist *notifyList;
- char *pguser; /* Postgres username of user who is
- * connected */
- char *pgpass;
- PGlobjfuncs *lobjfuncs; /* Backend function OID's for large object
- * access */
- } PGconn;
-
-#define CMDSTATUS_LEN 40
-/* PGresult encapsulates the result of a query */
-/* unlike the old libpq, we assume that queries only return in one group */
- typedef struct pg_result
- {
- int ntups;
- int numAttributes;
- PGresAttDesc *attDescs;
- PGresAttValue **tuples; /* each PGresTuple is an array of
- * PGresAttValue's */
- int tupArrSize; /* size of tuples array allocated */
- ExecStatusType resultStatus;
- char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the
- * last insert query */
- int binary; /* binary tuple values if binary == 1,
- * otherwise ASCII */
- PGconn *conn;
- } PGresult;
+ /* Miscellaneous stuff */
+ char salt[2]; /* password salt received from backend */
+ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */
+
+ /* Buffer for data received from backend and not yet processed */
+ char *inBuffer; /* currently allocated buffer */
+ int inBufSize; /* allocated size of buffer */
+ int inStart; /* offset to first unconsumed data in buffer */
+ int inCursor; /* next byte to tentatively consume */
+ int inEnd; /* offset to first position after avail data */
+
+ /* Buffer for data not yet sent to backend */
+ char *outBuffer; /* currently allocated buffer */
+ int outBufSize; /* allocated size of buffer */
+ int outCount; /* number of chars waiting in buffer */
+
+ /* Status for asynchronous result construction */
+ PGresult *result; /* result being constructed */
+ PGresAttValue *curTuple; /* tuple currently being read */
+
+ /* Message space. Placed last for code-size reasons.
+ * errorMessage is the message last returned to the application.
+ * When asyncStatus=READY, asyncErrorMessage is the pending message
+ * that will be put in errorMessage by PQgetResult. */
+ char errorMessage[ERROR_MSG_LENGTH];
+ char asyncErrorMessage[ERROR_MSG_LENGTH];
+ } PGconn;
typedef char pqbool;
@@ -179,7 +211,9 @@ extern "C"
* defined. Pqbool, on the other hand, is unlikely to be used.
*/
- struct _PQprintOpt
+/* Print options for PQprint() */
+
+ typedef struct _PQprintOpt
{
pqbool header; /* print output field headings and row
* count */
@@ -193,15 +227,28 @@ extern "C"
char *caption; /* HTML <caption> */
char **fieldName; /* null terminated array of repalcement
* field names */
- };
+ } PQprintOpt;
- typedef struct _PQprintOpt PQprintOpt;
+/* ----------------
+ * PQArgBlock -- structure for PQfn() arguments
+ * ----------------
+ */
+ typedef struct
+ {
+ int len;
+ int isint;
+ union
+ {
+ int *ptr; /* can't use void (dec compiler barfs) */
+ int integer;
+ } u;
+ } PQArgBlock;
/* ----------------
* Structure for the conninfo parameter definitions of PQconnectdb()
* ----------------
*/
- struct _PQconninfoOption
+ typedef struct _PQconninfoOption
{
char *keyword; /* The keyword of the option */
char *environ; /* Fallback environment variable name */
@@ -215,9 +262,7 @@ extern "C"
/* "D" Debug options - don't */
/* create a field by default */
int dispsize; /* Field size in characters for dialog */
- };
-
- typedef struct _PQconninfoOption PQconninfoOption;
+ } PQconninfoOption;
/* === in fe-connect.c === */
/* make a new client connection to the backend */
@@ -235,6 +280,7 @@ extern "C"
*/
extern void PQreset(PGconn *conn);
+ /* Accessor functions for PGconn objects */
extern char *PQdb(PGconn *conn);
extern char *PQuser(PGconn *conn);
extern char *PQhost(PGconn *conn);
@@ -243,14 +289,38 @@ extern "C"
extern char *PQtty(PGconn *conn);
extern ConnStatusType PQstatus(PGconn *conn);
extern char *PQerrorMessage(PGconn *conn);
+ extern int PQsocket(PGconn *conn);
+
+ /* Enable/disable tracing */
extern void PQtrace(PGconn *conn, FILE *debug_port);
extern void PQuntrace(PGconn *conn);
/* === in fe-exec.c === */
+ /* Simple synchronous query */
extern PGresult *PQexec(PGconn *conn, const char *query);
+ extern PGnotify *PQnotifies(PGconn *conn);
+ /* Interface for multiple-result or asynchronous queries */
+ extern int PQsendQuery(PGconn *conn, const char *query);
+ extern PGresult *PQgetResult(PGconn *conn);
+ /* Routines for managing an asychronous query */
+ extern int PQisBusy(PGconn *conn);
+ extern void PQconsumeInput(PGconn *conn);
+ extern int PQrequestCancel(PGconn *conn);
+ /* Routines for copy in/out */
extern int PQgetline(PGconn *conn, char *string, int length);
- extern int PQendcopy(PGconn *conn);
extern void PQputline(PGconn *conn, const char *string);
+ extern int PQendcopy(PGconn *conn);
+ /* Not really meant for application use: */
+ extern PGresult *PQfn(PGconn *conn,
+ int fnid,
+ int *result_buf,
+ int *result_len,
+ int result_is_int,
+ PQArgBlock *args,
+ int nargs);
+ extern void PQclearAsyncResult(PGconn *conn);
+
+ /* Accessor functions for PGresult objects */
extern ExecStatusType PQresultStatus(PGresult *res);
extern int PQntuples(PGresult *res);
extern int PQnfields(PGresult *res);
@@ -258,84 +328,84 @@ extern "C"
extern int PQfnumber(PGresult *res, const char *field_name);
extern Oid PQftype(PGresult *res, int field_num);
extern short PQfsize(PGresult *res, int field_num);
+ extern short PQfmod(PGresult *res, int field_num);
extern char *PQcmdStatus(PGresult *res);
extern const char *PQoidStatus(PGresult *res);
extern const char *PQcmdTuples(PGresult *res);
extern char *PQgetvalue(PGresult *res, int tup_num, int field_num);
extern int PQgetlength(PGresult *res, int tup_num, int field_num);
extern int PQgetisnull(PGresult *res, int tup_num, int field_num);
+ /* Delete a PGresult */
extern void PQclear(PGresult *res);
-/* PQdisplayTuples() is a better version of PQprintTuples() */
+
+/* === in fe-print.c === */
+ extern void PQprint(FILE *fout, /* output stream */
+ PGresult *res,
+ PQprintOpt *ps /* option structure */
+ );
+ /* PQdisplayTuples() is a better version of PQprintTuples(),
+ * but both are obsoleted by PQprint().
+ */
extern void PQdisplayTuples(PGresult *res,
- FILE *fp, /* where to send the
- * output */
- int fillAlign, /* pad the fields with
- * spaces */
- const char *fieldSep, /* field separator */
- int printHeader, /* display headers? */
- int quiet);
+ FILE *fp, /* where to send the
+ * output */
+ int fillAlign, /* pad the fields with
+ * spaces */
+ const char *fieldSep, /* field separator */
+ int printHeader, /* display headers? */
+ int quiet);
extern void PQprintTuples(PGresult *res,
- FILE *fout, /* output stream */
- int printAttName, /* print attribute names
- * or not */
- int terseOutput, /* delimiter bars or
- * not? */
- int width /* width of column, if
- * 0, use variable width */
- );
- extern void PQprint(FILE *fout, /* output stream */
- PGresult *res,
- PQprintOpt *ps /* option structure */
- );
- extern PGnotify *PQnotifies(PGconn *conn);
- extern PGresult *PQfn(PGconn *conn,
- int fnid,
- int *result_buf,
- int *result_len,
- int result_is_int,
- PQArgBlock *args,
- int nargs);
+ FILE *fout, /* output stream */
+ int printAttName, /* print attribute names
+ * or not */
+ int terseOutput, /* delimiter bars or
+ * not? */
+ int width /* width of column, if
+ * 0, use variable width */
+ );
+
/* === in fe-auth.c === */
extern MsgType fe_getauthsvc(char *PQerrormsg);
extern void fe_setauthsvc(const char *name, char *PQerrormsg);
extern char *fe_getauthname(char *PQerrormsg);
/* === in fe-misc.c === */
-/* pqGets and pqPuts gets and sends strings to the file stream
- returns 0 if successful
- if debug is non-null, debugging output is sent to that stream
-*/
- extern int pqGets(char *s, int maxlen, FILE *stream, FILE *debug);
- extern int pqGetnchar(char *s, int maxlen, FILE *stream, FILE *debug);
- extern int pqPutnchar(const char *s, int maxlen, FILE *stream, FILE *debug);
- extern int pqPuts(const char *s, FILE *stream, FILE *debug);
- extern int pqGetc(FILE *stream, FILE *debug);
-/* get a n-byte integer from the stream into result */
-/* returns 0 if successful */
- extern int pqGetInt(int *result, int bytes, FILE *stream, FILE *debug);
-/* put a n-byte integer into the stream */
-/* returns 0 if successful */
- extern int pqPutInt(const int n, int bytes, FILE *stream, FILE *debug);
- extern void pqFlush(FILE *stream, FILE *debug);
+ /* "Get" and "Put" routines return 0 if successful, EOF if not.
+ * Note that for Get, EOF merely means the buffer is exhausted,
+ * not that there is necessarily any error.
+ */
+ extern int pqGetc(char *result, PGconn *conn);
+ extern int pqGets(char *s, int maxlen, PGconn *conn);
+ extern int pqPuts(const char *s, PGconn *conn);
+ extern int pqGetnchar(char *s, int len, PGconn *conn);
+ extern int pqPutnchar(const char *s, int len, PGconn *conn);
+ extern int pqGetInt(int *result, int bytes, PGconn *conn);
+ extern int pqPutInt(int value, int bytes, PGconn *conn);
+ extern int pqReadData(PGconn *conn);
+ extern int pqFlush(PGconn *conn);
+ extern int pqWait(int forRead, int forWrite, PGconn *conn);
/* === in fe-lobj.c === */
- int lo_open(PGconn *conn, Oid lobjId, int mode);
- int lo_close(PGconn *conn, int fd);
- int lo_read(PGconn *conn, int fd, char *buf, int len);
- int lo_write(PGconn *conn, int fd, char *buf, int len);
- int lo_lseek(PGconn *conn, int fd, int offset, int whence);
- Oid lo_creat(PGconn *conn, int mode);
- int lo_tell(PGconn *conn, int fd);
- int lo_unlink(PGconn *conn, Oid lobjId);
- Oid lo_import(PGconn *conn, char *filename);
- int lo_export(PGconn *conn, Oid lobjId, char *filename);
+ extern int lo_open(PGconn *conn, Oid lobjId, int mode);
+ extern int lo_close(PGconn *conn, int fd);
+ extern int lo_read(PGconn *conn, int fd, char *buf, int len);
+ extern int lo_write(PGconn *conn, int fd, char *buf, int len);
+ extern int lo_lseek(PGconn *conn, int fd, int offset, int whence);
+ extern Oid lo_creat(PGconn *conn, int mode);
+ extern int lo_tell(PGconn *conn, int fd);
+ extern int lo_unlink(PGconn *conn, Oid lobjId);
+ extern Oid lo_import(PGconn *conn, char *filename);
+ extern int lo_export(PGconn *conn, Oid lobjId, char *filename);
+
/* max length of message to send */
#define MAX_MESSAGE_LEN 8193
/* maximum number of fields in a tuple */
-#define BYTELEN 8
#define MAX_FIELDS 512
+/* bits in a byte */
+#define BYTELEN 8
+
/* fall back options if they are not specified by arguments or defined
by environment variables */
#define DefaultHost "localhost"