diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/commands/async.c | 13 | ||||
-rw-r--r-- | src/backend/tcop/dest.c | 107 | ||||
-rw-r--r-- | src/backend/tcop/fastpath.c | 3 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 12 | ||||
-rw-r--r-- | src/bin/psql/psql.c | 50 | ||||
-rw-r--r-- | src/include/libpq/pqcomm.h | 4 | ||||
-rw-r--r-- | src/include/tcop/dest.h | 7 | ||||
-rw-r--r-- | src/interfaces/libpgtcl/pgtclId.c | 46 | ||||
-rw-r--r-- | src/interfaces/libpq/Makefile.in | 12 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 603 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 2212 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-misc.c | 461 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 310 |
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" |