From ab5cafa5d3f47439d8f65516bc5b88ad04117621 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Tue, 31 Aug 1999 01:37:37 +0000 Subject: [PATCH] Update frontend libpq to remove limits on query lengths, error/notice message lengths, and number of fields per tuple. Add pqexpbuffer.c/.h, a frontend version of backend's stringinfo module. This is first step in applying Mike Ansley's long-query patches, even though he didn't do any of these particular changes... --- src/interfaces/libpq/Makefile.in | 5 +- src/interfaces/libpq/fe-auth.c | 4 +- src/interfaces/libpq/fe-connect.c | 197 +++++++------- src/interfaces/libpq/fe-exec.c | 398 ++++++++++++++++++----------- src/interfaces/libpq/fe-lobj.c | 77 +++--- src/interfaces/libpq/fe-misc.c | 77 +++--- src/interfaces/libpq/fe-print.c | 117 ++++----- src/interfaces/libpq/libpq-int.h | 29 +-- src/interfaces/libpq/pqexpbuffer.c | 254 ++++++++++++++++++ src/interfaces/libpq/pqexpbuffer.h | 169 ++++++++++++ src/interfaces/libpq/win32.mak | 4 +- 11 files changed, 939 insertions(+), 392 deletions(-) create mode 100644 src/interfaces/libpq/pqexpbuffer.c create mode 100644 src/interfaces/libpq/pqexpbuffer.h diff --git a/src/interfaces/libpq/Makefile.in b/src/interfaces/libpq/Makefile.in index 73f333cae12..ec955dcd4bc 100644 --- a/src/interfaces/libpq/Makefile.in +++ b/src/interfaces/libpq/Makefile.in @@ -6,7 +6,7 @@ # Copyright (c) 1994, Regents of the University of California # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.46 1999/06/30 23:57:25 tgl Exp $ +# $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.47 1999/08/31 01:37:36 tgl Exp $ # #------------------------------------------------------------------------- @@ -28,7 +28,7 @@ CFLAGS+= $(MBFLAGS) endif OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \ - dllist.o pqsignal.o + pqexpbuffer.o dllist.o pqsignal.o ifdef MULTIBYTE OBJS+= common.o wchar.o conv.o big5.o @@ -80,6 +80,7 @@ install-headers: libpq-fe.h libpq-int.h @if [ ! -d $(HEADERDIR) ]; then mkdir $(HEADERDIR); fi $(INSTALL) $(INSTLOPTS) libpq-fe.h $(HEADERDIR)/libpq-fe.h $(INSTALL) $(INSTLOPTS) libpq-int.h $(HEADERDIR)/libpq-int.h + $(INSTALL) $(INSTLOPTS) pqexpbuffer.h $(HEADERDIR)/pqexpbuffer.h .PHONY: clean diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index f72f76b4080..574d78c25d5 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -5,9 +5,11 @@ * * Copyright (c) 1994, Regents of the University of California * + * NOTE: the error message strings returned by this module must not + * exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes). * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.32 1999/07/19 06:25:38 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.33 1999/08/31 01:37:36 tgl Exp $ * *------------------------------------------------------------------------- */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4edb4e8e596..4e86a8db91e 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.101 1999/07/19 06:25:38 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.102 1999/08/31 01:37:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,7 +44,7 @@ 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 int conninfo_parse(const char *conninfo, PQExpBuffer errorMessage); static char *conninfo_getval(char *keyword); static void conninfo_free(void); static void defaultNoticeProcessor(void *arg, const char *message); @@ -178,7 +178,7 @@ PQconnectdb(const char *conninfo) * Parse the conninfo string and save settings in conn structure * ---------- */ - if (conninfo_parse(conninfo, conn->errorMessage) < 0) + if (conninfo_parse(conninfo, &conn->errorMessage) < 0) { conn->status = CONNECTION_BAD; conninfo_free(); @@ -226,9 +226,11 @@ PQconnectdb(const char *conninfo) PQconninfoOption * PQconndefaults(void) { - char errorMessage[ERROR_MSG_LENGTH]; + PQExpBufferData errorBuf; - conninfo_parse("", errorMessage); + initPQExpBuffer(&errorBuf); + conninfo_parse("", &errorBuf); + termPQExpBuffer(&errorBuf); return PQconninfoOptions; } @@ -328,13 +330,17 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons else if ((tmp = getenv("PGUSER")) != NULL) conn->pguser = strdup(tmp); else - conn->pguser = fe_getauthname(conn->errorMessage); + { + /* fe-auth.c has not been fixed to support PQExpBuffers, so: */ + conn->pguser = fe_getauthname(conn->errorMessage.data); + conn->errorMessage.len = strlen(conn->errorMessage.data); + } if (conn->pguser == NULL) { error = TRUE; - sprintf(conn->errorMessage, - "FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n"); + printfPQExpBuffer(&conn->errorMessage, + "FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n"); } if (pwd) @@ -469,8 +475,8 @@ update_db_info(PGconn *conn) conn->pghost = NULL; if (strcmp(old + offset, "localhost") != 0) { - (void) sprintf(conn->errorMessage, - "connectDB() -- non-tcp access only possible on localhost\n"); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- non-tcp access only possible on localhost\n"); return 1; } } @@ -533,9 +539,9 @@ connectDB(PGconn *conn) hp = gethostbyname(conn->pghost); if ((hp == NULL) || (hp->h_addrtype != AF_INET)) { - (void) sprintf(conn->errorMessage, - "connectDB() -- unknown hostname: %s\n", - conn->pghost); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- unknown hostname: %s\n", + conn->pghost); goto connect_errReturn; } family = AF_INET; @@ -567,21 +573,21 @@ connectDB(PGconn *conn) /* Connect to the server */ if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0) { - (void) sprintf(conn->errorMessage, - "connectDB() -- socket() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- socket() failed: errno=%d\n%s\n", + errno, strerror(errno)); goto connect_errReturn; } if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0) { - (void) sprintf(conn->errorMessage, - "connectDB() -- connect() failed: %s\n" - "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n", - strerror(errno), - (family == AF_INET) ? " (with -i)" : "", - conn->pghost ? conn->pghost : "localhost", - (family == AF_INET) ? "TCP/IP port" : "Unix socket", - conn->pgport); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- connect() failed: %s\n" + "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n", + strerror(errno), + (family == AF_INET) ? " (with -i)" : "", + conn->pghost ? conn->pghost : "localhost", + (family == AF_INET) ? "TCP/IP port" : "Unix socket", + conn->pgport); goto connect_errReturn; } @@ -596,9 +602,9 @@ connectDB(PGconn *conn) if (ioctlsocket(conn->sock, FIONBIO, &on) != 0) #endif { - (void) sprintf(conn->errorMessage, - "connectDB() -- fcntl() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- fcntl() failed: errno=%d\n%s\n", + errno, strerror(errno)); goto connect_errReturn; } @@ -609,8 +615,8 @@ connectDB(PGconn *conn) pe = getprotobyname("TCP"); if (pe == NULL) { - (void) sprintf(conn->errorMessage, - "connectDB(): getprotobyname failed\n"); + printfPQExpBuffer(&conn->errorMessage, + "connectDB(): getprotobyname failed\n"); goto connect_errReturn; } if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY, @@ -620,9 +626,9 @@ connectDB(PGconn *conn) &on, sizeof(on)) < 0) { - (void) sprintf(conn->errorMessage, - "connectDB() -- setsockopt failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- setsockopt failed: errno=%d\n%s\n", + errno, strerror(errno)); #ifdef WIN32 printf("Winsock error: %i\n", WSAGetLastError()); #endif @@ -634,9 +640,9 @@ connectDB(PGconn *conn) laddrlen = sizeof(conn->laddr); if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0) { - (void) sprintf(conn->errorMessage, - "connectDB() -- getsockname() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- getsockname() failed: errno=%d\n%s\n", + errno, strerror(errno)); goto connect_errReturn; } @@ -648,9 +654,9 @@ connectDB(PGconn *conn) if (pqPacketSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK) { - sprintf(conn->errorMessage, - "connectDB() -- couldn't send startup packet: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- couldn't send startup packet: errno=%d\n%s\n", + errno, strerror(errno)); goto connect_errReturn; } @@ -681,7 +687,7 @@ connectDB(PGconn *conn) /* Handle errors. */ if (beresp == 'E') { - if (pqGets(conn->errorMessage, sizeof(conn->errorMessage), conn)) + if (pqGets(&conn->errorMessage, conn)) continue; goto connect_errReturn; } @@ -689,8 +695,8 @@ connectDB(PGconn *conn) /* Otherwise it should be an authentication request. */ if (beresp != 'R') { - (void) sprintf(conn->errorMessage, - "connectDB() -- expected authentication request\n"); + printfPQExpBuffer(&conn->errorMessage, + "connectDB() -- expected authentication request\n"); goto connect_errReturn; } @@ -709,9 +715,14 @@ connectDB(PGconn *conn) conn->inStart = conn->inCursor; /* Respond to the request if necessary. */ + /* fe-auth.c has not been fixed to support PQExpBuffers, so: */ if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass, - conn->errorMessage) != STATUS_OK) + conn->errorMessage.data) != STATUS_OK) + { + conn->errorMessage.len = strlen(conn->errorMessage.data); goto connect_errReturn; + } + if (pqFlush(conn)) goto connect_errReturn; @@ -737,36 +748,12 @@ connectDB(PGconn *conn) if (res) { if (res->resultStatus != PGRES_FATAL_ERROR) - sprintf(conn->errorMessage, - "connectDB() -- unexpected message during startup\n"); + printfPQExpBuffer(&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. - */ - -#ifdef NOT_USED - - /* - * 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; - } - PQclear(res); - -#endif - /* * Post-connection housekeeping. Send environment variables to server */ @@ -870,11 +857,27 @@ makeEmptyPGconn(void) conn->asyncStatus = PGASYNC_IDLE; conn->notifyList = DLNewList(); conn->sock = -1; - conn->inBufSize = PQ_BUFFER_SIZE; + /* + * The output buffer size is set to 8K, which is the usual size of pipe + * buffers on Unix systems. That way, when we are sending a large + * amount of data, we avoid incurring extra kernel context swaps for + * partial bufferloads. Note that we currently don't ever enlarge + * the output buffer. + * + * With the same goal of minimizing context swaps, the input buffer will + * be enlarged anytime it has less than 8K free, so we initially allocate + * twice that. + */ + conn->inBufSize = 16 * 1024; conn->inBuffer = (char *) malloc(conn->inBufSize); - conn->outBufSize = PQ_BUFFER_SIZE; + conn->outBufSize = 8 * 1024; conn->outBuffer = (char *) malloc(conn->outBufSize); - if (conn->inBuffer == NULL || conn->outBuffer == NULL) + initPQExpBuffer(&conn->errorMessage); + initPQExpBuffer(&conn->workBuffer); + if (conn->inBuffer == NULL || + conn->outBuffer == NULL || + conn->errorMessage.data == NULL || + conn->workBuffer.data == NULL) { freePGconn(conn); conn = NULL; @@ -922,6 +925,8 @@ freePGconn(PGconn *conn) free(conn->inBuffer); if (conn->outBuffer) free(conn->outBuffer); + termPQExpBuffer(&conn->errorMessage); + termPQExpBuffer(&conn->workBuffer); free(conn); } @@ -1002,16 +1007,24 @@ PQreset(PGconn *conn) * PQrequestCancel: 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). + * dispatched, FALSE if not (in which case conn->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. * + * XXX it was a bad idea to have the error message returned in + * conn->errorMessage, since it could overwrite a message already there. + * Would be better to return it in a char array passed by the caller. + * * CAUTION: we want this routine to be safely callable from a signal handler * (for example, an application might want to call it in a SIGINT handler). * This means we cannot use any C library routine that might be non-reentrant. * malloc/free are often non-reentrant, and anything that might call them is * just as dangerous. We avoid sprintf here for that reason. Building up * error messages with strcpy/strcat is tedious but should be quite safe. + * + * NOTE: this routine must not generate any error message longer than + * INITIAL_EXPBUFFER_SIZE (currently 256), since we dare not try to + * expand conn->errorMessage! */ int @@ -1030,8 +1043,9 @@ PQrequestCancel(PGconn *conn) if (conn->sock < 0) { - strcpy(conn->errorMessage, + strcpy(conn->errorMessage.data, "PQrequestCancel() -- connection is not open\n"); + conn->errorMessage.len = strlen(conn->errorMessage.data); return FALSE; } @@ -1041,12 +1055,14 @@ PQrequestCancel(PGconn *conn) */ if ((tmpsock = socket(conn->raddr.sa.sa_family, SOCK_STREAM, 0)) < 0) { - strcpy(conn->errorMessage, "PQrequestCancel() -- socket() failed: "); + strcpy(conn->errorMessage.data, + "PQrequestCancel() -- socket() failed: "); goto cancel_errReturn; } if (connect(tmpsock, &conn->raddr.sa, conn->raddr_len) < 0) { - strcpy(conn->errorMessage, "PQrequestCancel() -- connect() failed: "); + strcpy(conn->errorMessage.data, + "PQrequestCancel() -- connect() failed: "); goto cancel_errReturn; } @@ -1063,7 +1079,8 @@ PQrequestCancel(PGconn *conn) if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp)) { - strcpy(conn->errorMessage, "PQrequestCancel() -- send() failed: "); + strcpy(conn->errorMessage.data, + "PQrequestCancel() -- send() failed: "); goto cancel_errReturn; } @@ -1077,8 +1094,9 @@ PQrequestCancel(PGconn *conn) return TRUE; cancel_errReturn: - strcat(conn->errorMessage, strerror(errno)); - strcat(conn->errorMessage, "\n"); + strcat(conn->errorMessage.data, strerror(errno)); + strcat(conn->errorMessage.data, "\n"); + conn->errorMessage.len = strlen(conn->errorMessage.data); if (tmpsock >= 0) { #ifdef WIN32 @@ -1123,7 +1141,7 @@ pqPacketSend(PGconn *conn, const char *buf, size_t len) * ---------------- */ static int -conninfo_parse(const char *conninfo, char *errorMessage) +conninfo_parse(const char *conninfo, PQExpBuffer errorMessage) { char *pname; char *pval; @@ -1132,13 +1150,13 @@ conninfo_parse(const char *conninfo, char *errorMessage) char *cp; char *cp2; PQconninfoOption *option; - char errortmp[ERROR_MSG_LENGTH]; + char errortmp[INITIAL_EXPBUFFER_SIZE]; conninfo_free(); if ((buf = strdup(conninfo)) == NULL) { - strcpy(errorMessage, + printfPQExpBuffer(errorMessage, "FATAL: cannot allocate memory for copy of conninfo string\n"); return -1; } @@ -1176,9 +1194,9 @@ conninfo_parse(const char *conninfo, char *errorMessage) /* Check that there is a following '=' */ if (*cp != '=') { - sprintf(errorMessage, - "ERROR: PQconnectdb() - Missing '=' after '%s' in conninfo\n", - pname); + printfPQExpBuffer(errorMessage, + "ERROR: PQconnectdb() - Missing '=' after '%s' in conninfo\n", + pname); free(buf); return -1; } @@ -1223,7 +1241,7 @@ conninfo_parse(const char *conninfo, char *errorMessage) { if (*cp == '\0') { - sprintf(errorMessage, + printfPQExpBuffer(errorMessage, "ERROR: PQconnectdb() - unterminated quoted string in conninfo\n"); free(buf); return -1; @@ -1257,9 +1275,9 @@ conninfo_parse(const char *conninfo, char *errorMessage) } if (option->keyword == NULL) { - sprintf(errorMessage, - "ERROR: PQconnectdb() - unknown option '%s'\n", - pname); + printfPQExpBuffer(errorMessage, + "ERROR: PQconnectdb() - unknown option '%s'\n", + pname); free(buf); return -1; } @@ -1314,6 +1332,7 @@ conninfo_parse(const char *conninfo, char *errorMessage) if (!strcmp(option->keyword, "user")) { option->val = fe_getauthname(errortmp); + /* note any error message is thrown away */ continue; } @@ -1436,7 +1455,7 @@ PQerrorMessage(PGconn *conn) if (!conn) return noConn; - return conn->errorMessage; + return conn->errorMessage.data; } int diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 01613e0db84..24fe9860ebb 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.84 1999/07/19 06:25:39 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.85 1999/08/31 01:37:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,9 @@ const char *const pgresStatus[] = { ((*(conn)->noticeHook) ((conn)->noticeArg, (message))) +static void pqCatenateResultError(PGresult *res, const char *msg); +static void saveErrorResult(PGconn *conn); +static PGresult *prepareAsyncResult(PGconn *conn); static int addTuple(PGresult *res, PGresAttValue *tup); static void parseInput(PGconn *conn); static void handleSendFailure(PGconn *conn); @@ -158,7 +161,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) /* non-error cases */ break; default: - pqSetResultError(result, conn->errorMessage); + pqSetResultError(result, conn->errorMessage.data); break; } } @@ -298,6 +301,25 @@ pqSetResultError(PGresult *res, const char *msg) res->errMsg = NULL; } +/* + * pqCatenateResultError - + * concatenate a new error message to the one already in a PGresult + */ +static void +pqCatenateResultError(PGresult *res, const char *msg) +{ + PQExpBufferData errorBuf; + + if (!res || !msg) + return; + initPQExpBuffer(&errorBuf); + if (res->errMsg) + appendPQExpBufferStr(&errorBuf, res->errMsg); + appendPQExpBufferStr(&errorBuf, msg); + pqSetResultError(res, errorBuf.data); + termPQExpBuffer(&errorBuf); +} + /* * PQclear - * free's the memory associated with a PGresult @@ -338,6 +360,72 @@ pqClearAsyncResult(PGconn *conn) conn->curTuple = NULL; } +/* + * This subroutine deletes any existing async result, sets conn->result + * to a PGresult with status PGRES_FATAL_ERROR, and stores the current + * contents of conn->errorMessage into that result. It differs from a + * plain call on PQmakeEmptyPGresult() in that if there is already an + * async result with status PGRES_FATAL_ERROR, the current error message + * is APPENDED to the old error message instead of replacing it. This + * behavior lets us report multiple error conditions properly, if necessary. + * (An example where this is needed is when the backend sends an 'E' message + * and immediately closes the connection --- we want to report both the + * backend error and the connection closure error.) + */ +static void +saveErrorResult(PGconn *conn) +{ + /* If no old async result, just let PQmakeEmptyPGresult make one. + * Likewise if old result is not an error message. + */ + if (conn->result == NULL || + conn->result->resultStatus != PGRES_FATAL_ERROR || + conn->result->errMsg == NULL) + { + pqClearAsyncResult(conn); + conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + } + else + { + /* Else, concatenate error message to existing async result. */ + pqCatenateResultError(conn->result, conn->errorMessage.data); + } +} + +/* + * This subroutine prepares an async result object for return to the caller. + * If there is not already an async result object, build an error object + * using whatever is in conn->errorMessage. In any case, clear the async + * result storage and make sure PQerrorMessage will agree with the result's + * error string. + */ +static PGresult * +prepareAsyncResult(PGconn *conn) +{ + PGresult *res; + + /* + * conn->result is the PGresult to return. If it is NULL + * (which probably shouldn't happen) we assume there is an + * appropriate error message in conn->errorMessage. + */ + res = conn->result; + conn->result = NULL; /* handing over ownership to caller */ + conn->curTuple = NULL; /* just in case */ + if (!res) + res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + else + { + /* + * Make sure PQerrorMessage agrees with result; it could + * be different if we have concatenated messages. + */ + resetPQExpBuffer(&conn->errorMessage); + appendPQExpBufferStr(&conn->errorMessage, + PQresultErrorMessage(res)); + } + return res; +} /* * addTuple @@ -394,37 +482,33 @@ PQsendQuery(PGconn *conn, const char *query) { if (!conn) return 0; + + /* clear the error string */ + resetPQExpBuffer(&conn->errorMessage); + if (!query) { - sprintf(conn->errorMessage, "PQsendQuery() -- query pointer is null."); - return 0; - } - /* check to see if the query string is too long */ - if (strlen(query) > MAX_MESSAGE_LEN - 2) - { - sprintf(conn->errorMessage, "PQsendQuery() -- query is too long. " - "Maximum length is %d\n", MAX_MESSAGE_LEN - 2); + printfPQExpBuffer(&conn->errorMessage, + "PQsendQuery() -- query pointer is null.\n"); return 0; } /* 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"); + printfPQExpBuffer(&conn->errorMessage, + "PQsendQuery() -- There is no connection " + "to the backend.\n"); return 0; } /* Can't send while already busy, either. */ if (conn->asyncStatus != PGASYNC_IDLE) { - sprintf(conn->errorMessage, - "PQsendQuery() -- another query already in progress."); + printfPQExpBuffer(&conn->errorMessage, + "PQsendQuery() -- another query already in progress.\n"); return 0; } - /* clear the error string */ - conn->errorMessage[0] = '\0'; - /* initialize async result-accumulation state */ conn->result = NULL; conn->curTuple = NULL; @@ -456,9 +540,6 @@ PQsendQuery(PGconn *conn, const char *query) static void handleSendFailure(PGconn *conn) { - /* Preserve the error message emitted by the failing output routine */ - char * svErrMsg = strdup(conn->errorMessage); - /* * Accept any available input data, ignoring errors. Note that if * pqReadData decides the backend has closed the channel, it will @@ -472,11 +553,6 @@ handleSendFailure(PGconn *conn) * state, only NOTICE and NOTIFY messages will be eaten. */ parseInput(conn); - - /* Restore error message generated by output routine, if any. */ - if (*svErrMsg != '\0') - strcpy(conn->errorMessage, svErrMsg); - free(svErrMsg); } /* @@ -514,6 +590,7 @@ static void parseInput(PGconn *conn) { char id; + char noticeWorkspace[128]; /* * Loop to parse successive complete messages available in the buffer. @@ -565,10 +642,10 @@ parseInput(PGconn *conn) { if (conn->asyncStatus == PGASYNC_IDLE) { - sprintf(conn->errorMessage, - "Backend message type 0x%02x arrived while idle\n", + sprintf(noticeWorkspace, + "Backend message type 0x%02x arrived while idle\n", id); - DONOTICE(conn, conn->errorMessage); + DONOTICE(conn, noticeWorkspace); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; } @@ -577,21 +654,20 @@ parseInput(PGconn *conn) switch (id) { case 'C': /* command complete */ + if (pqGets(&conn->workBuffer, conn)) + return; if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, - PGRES_COMMAND_OK); - if (pqGets(conn->result->cmdStatus, CMDSTATUS_LEN, conn)) - return; + PGRES_COMMAND_OK); + strncpy(conn->result->cmdStatus, conn->workBuffer.data, + CMDSTATUS_LEN); conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ - if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) + if (pqGets(& conn->errorMessage, conn)) return; - /* delete any partially constructed result */ - pqClearAsyncResult(conn); - /* and build an error result holding the error message */ - conn->result = PQmakeEmptyPGresult(conn, - PGRES_FATAL_ERROR); + /* build an error result holding the error message */ + saveErrorResult(conn); conn->asyncStatus = PGASYNC_READY; break; case 'Z': /* backend is ready for new query */ @@ -603,9 +679,10 @@ parseInput(PGconn *conn) return; if (id != '\0') { - sprintf(conn->errorMessage, - "unexpected character %c following 'I'\n", id); - DONOTICE(conn, conn->errorMessage); + sprintf(noticeWorkspace, + "unexpected character %c following 'I'\n", + id); + DONOTICE(conn, noticeWorkspace); } if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, @@ -625,7 +702,7 @@ parseInput(PGconn *conn) return; break; case 'P': /* synchronous (normal) portal */ - if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) + if (pqGets(&conn->workBuffer, conn)) return; /* We pretty much ignore this message type... */ break; @@ -660,9 +737,9 @@ parseInput(PGconn *conn) } else { - sprintf(conn->errorMessage, - "Backend sent D message without prior T\n"); - DONOTICE(conn, conn->errorMessage); + sprintf(noticeWorkspace, + "Backend sent D message without prior T\n"); + DONOTICE(conn, noticeWorkspace); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; return; @@ -677,9 +754,9 @@ parseInput(PGconn *conn) } else { - sprintf(conn->errorMessage, - "Backend sent B message without prior T\n"); - DONOTICE(conn, conn->errorMessage); + sprintf(noticeWorkspace, + "Backend sent B message without prior T\n"); + DONOTICE(conn, noticeWorkspace); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; return; @@ -692,18 +769,15 @@ parseInput(PGconn *conn) conn->asyncStatus = PGASYNC_COPY_OUT; break; default: - sprintf(conn->errorMessage, - "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); + printfPQExpBuffer(&conn->errorMessage, + "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); + /* build an error result holding the error message */ + saveErrorResult(conn); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; - /* delete any partially constructed result */ - pqClearAsyncResult(conn); - /* and build an error result holding the error message */ - conn->result = PQmakeEmptyPGresult(conn, - PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; return; } /* switch on protocol character */ @@ -753,12 +827,11 @@ getRowDescriptions(PGconn *conn) /* get type info */ for (i = 0; i < nfields; i++) { - char typName[MAX_MESSAGE_LEN]; int typid; int typlen; int atttypmod; - if (pqGets(typName, MAX_MESSAGE_LEN, conn) || + if (pqGets(&conn->workBuffer, conn) || pqGetInt(&typid, 4, conn) || pqGetInt(&typlen, 2, conn) || pqGetInt(&atttypmod, 4, conn)) @@ -777,7 +850,8 @@ getRowDescriptions(PGconn *conn) */ if (typlen == 0xFFFF) typlen = -1; - result->attDescs[i].name = pqResultStrdup(result, typName); + result->attDescs[i].name = pqResultStrdup(result, + conn->workBuffer.data); result->attDescs[i].typid = typid; result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; @@ -804,8 +878,9 @@ getAnotherTuple(PGconn *conn, int binary) PGresult *result = conn->result; int nfields = result->numAttributes; PGresAttValue *tup; - char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap - * of which attributes are null */ + /* the backend sends us a bitmap of which attributes are null */ + char std_bitmap[64]; /* used unless it doesn't fit */ + char *bitmap = std_bitmap; int i; int nbytes; /* the number of bytes in bitmap */ char bmap; /* One byte of the bitmap */ @@ -828,21 +903,12 @@ getAnotherTuple(PGconn *conn, int binary) /* Get the null-value bitmap */ nbytes = (nfields + BYTELEN - 1) / BYTELEN; - if (nbytes >= MAX_FIELDS) - { - /* Replace partially constructed result with an error result */ - pqClearAsyncResult(conn); - sprintf(conn->errorMessage, - "getAnotherTuple() -- null-values bitmap is too large\n"); - conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); - conn->asyncStatus = PGASYNC_READY; - /* Discard the broken message */ - conn->inStart = conn->inEnd; - return EOF; - } + /* malloc() only for unusually large field counts... */ + if (nbytes > sizeof(std_bitmap)) + bitmap = (char *) malloc(nbytes); if (pqGetnchar(bitmap, nbytes, conn)) - return EOF; + goto EOFexit; /* Scan the fields */ bitmap_index = 0; @@ -861,7 +927,7 @@ getAnotherTuple(PGconn *conn, int binary) { /* get the value length (the first four bytes are for length) */ if (pqGetInt(&vlen, 4, conn)) - return EOF; + goto EOFexit; if (binary == 0) vlen = vlen - 4; if (vlen < 0) @@ -876,7 +942,7 @@ getAnotherTuple(PGconn *conn, int binary) /* read in the value */ if (vlen > 0) if (pqGetnchar((char *) (tup[i].value), vlen, conn)) - return EOF; + goto EOFexit; /* we have to terminate this ourselves */ tup[i].value[vlen] = '\0'; } @@ -897,17 +963,27 @@ getAnotherTuple(PGconn *conn, int binary) goto outOfMemory; /* and reset for a new message */ conn->curTuple = NULL; + + if (bitmap != std_bitmap) + free(bitmap); return 0; outOfMemory: /* Replace partially constructed result with an error result */ + /* we do NOT use saveErrorResult() here, because of the likelihood + * that there's not enough memory to concatenate messages... + */ pqClearAsyncResult(conn); - sprintf(conn->errorMessage, - "getAnotherTuple() -- out of memory for result\n"); + printfPQExpBuffer(&conn->errorMessage, + "getAnotherTuple() -- out of memory for result\n"); conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; /* Discard the failed message --- good idea? */ conn->inStart = conn->inEnd; + +EOFexit: + if (bitmap != std_bitmap) + free(bitmap); return EOF; } @@ -955,10 +1031,12 @@ PQgetResult(PGconn *conn) if (pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) { - pqClearAsyncResult(conn); + /* conn->errorMessage has been set by pqWait or pqReadData. + * We want to append it to any already-received error message. + */ + saveErrorResult(conn); conn->asyncStatus = PGASYNC_IDLE; - /* conn->errorMessage has been set by pqWait or pqReadData. */ - return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + return prepareAsyncResult(conn); } /* Parse it. */ parseInput(conn); @@ -971,28 +1049,7 @@ PQgetResult(PGconn *conn) res = NULL; /* query is complete */ break; case PGASYNC_READY: - - /* - * conn->result is the PGresult to return. If it is NULL - * (which probably shouldn't happen) we assume there is an - * appropriate error message in conn->errorMessage. - */ - res = conn->result; - conn->result = NULL;/* handing over ownership to caller */ - conn->curTuple = NULL; /* just in case */ - if (!res) - res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); - else - { - - /* - * Make sure PQerrorMessage agrees with result; it could - * be that we have done other operations that changed - * errorMessage since the result's error message was - * saved. - */ - strcpy(conn->errorMessage, PQresultErrorMessage(res)); - } + res = prepareAsyncResult(conn); /* Set the state back to BUSY, allowing parsing to proceed. */ conn->asyncStatus = PGASYNC_BUSY; break; @@ -1003,9 +1060,9 @@ PQgetResult(PGconn *conn) res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT); break; default: - sprintf(conn->errorMessage, - "PQgetResult: Unexpected asyncStatus %d\n", - (int) conn->asyncStatus); + printfPQExpBuffer(&conn->errorMessage, + "PQgetResult: Unexpected asyncStatus %d\n", + (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } @@ -1043,7 +1100,7 @@ PQexec(PGconn *conn, const char *query) result->resultStatus == PGRES_COPY_OUT) { PQclear(result); - sprintf(conn->errorMessage, + printfPQExpBuffer(&conn->errorMessage, "PQexec: you gotta get out of a COPY state yourself.\n"); return NULL; } @@ -1056,14 +1113,30 @@ PQexec(PGconn *conn, const char *query) /* * For backwards compatibility, return the last result if there are - * more than one. We have to stop if we see copy in/out, however. We - * will resume parsing when application calls PQendcopy. + * more than one --- but merge error messages if we get more than one + * error result. + * + * We have to stop if we see copy in/out, however. + * We will resume parsing when application calls PQendcopy. */ lastResult = NULL; while ((result = PQgetResult(conn)) != NULL) { if (lastResult) - PQclear(lastResult); + { + if (lastResult->resultStatus == PGRES_FATAL_ERROR && + result->resultStatus == PGRES_FATAL_ERROR) + { + pqCatenateResultError(lastResult, result->errMsg); + PQclear(result); + result = lastResult; + /* Make sure PQerrorMessage agrees with catenated result */ + resetPQExpBuffer(&conn->errorMessage); + appendPQExpBufferStr(&conn->errorMessage, result->errMsg); + } + else + PQclear(lastResult); + } lastResult = result; if (result->resultStatus == PGRES_COPY_IN || result->resultStatus == PGRES_COPY_OUT) @@ -1083,9 +1156,20 @@ PQexec(PGconn *conn, const char *query) static int getNotice(PGconn *conn) { - if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) + /* Since the Notice might be pretty long, we create a temporary + * PQExpBuffer rather than using conn->workBuffer. workBuffer is + * intended for stuff that is expected to be short. + */ + PQExpBufferData noticeBuf; + + initPQExpBuffer(¬iceBuf); + if (pqGets(¬iceBuf, conn)) + { + termPQExpBuffer(¬iceBuf); return EOF; - DONOTICE(conn, conn->errorMessage); + } + DONOTICE(conn, noticeBuf.data); + termPQExpBuffer(¬iceBuf); return 0; } @@ -1099,15 +1183,16 @@ getNotice(PGconn *conn) static int getNotify(PGconn *conn) { - PGnotify tempNotify; + int be_pid; PGnotify *newNotify; - if (pqGetInt(&(tempNotify.be_pid), 4, conn)) + if (pqGetInt(&be_pid, 4, conn)) return EOF; - if (pqGets(tempNotify.relname, NAMEDATALEN, conn)) + if (pqGets(&conn->workBuffer, conn)) return EOF; newNotify = (PGnotify *) malloc(sizeof(PGnotify)); - memcpy(newNotify, &tempNotify, sizeof(PGnotify)); + strncpy(newNotify->relname, conn->workBuffer.data, NAMEDATALEN); + newNotify->be_pid = be_pid; DLAddTail(conn->notifyList, DLNewElem(newNotify)); return 0; } @@ -1342,8 +1427,8 @@ PQendcopy(PGconn *conn) 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."); + printfPQExpBuffer(&conn->errorMessage, + "PQendcopy() -- I don't think there's a copy in progress.\n"); return 1; } @@ -1351,7 +1436,7 @@ PQendcopy(PGconn *conn) /* Return to active duty */ conn->asyncStatus = PGASYNC_BUSY; - conn->errorMessage[0] = '\0'; + resetPQExpBuffer(&conn->errorMessage); /* Wait for the completion response */ result = PQgetResult(conn); @@ -1370,8 +1455,8 @@ PQendcopy(PGconn *conn) */ PQclear(result); - if (conn->errorMessage[0]) - DONOTICE(conn, conn->errorMessage); + if (conn->errorMessage.len > 0) + DONOTICE(conn, conn->errorMessage.data); DONOTICE(conn, "PQendcopy: resetting connection\n"); @@ -1423,15 +1508,17 @@ PQfn(PGconn *conn, if (!conn) return NULL; - if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE) + /* clear the error string */ + resetPQExpBuffer(&conn->errorMessage); + + if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE || + conn->result != NULL) { - sprintf(conn->errorMessage, "PQfn() -- connection in wrong state\n"); + printfPQExpBuffer(&conn->errorMessage, + "PQfn() -- connection in wrong state\n"); return NULL; } - /* clear the error string */ - conn->errorMessage[0] = '\0'; - if (pqPuts("F ", conn) || /* function */ pqPutInt(fnid, 4, conn) || /* function id */ pqPutInt(nargs, 4, conn)) /* # of args */ @@ -1529,15 +1616,19 @@ PQfn(PGconn *conn, else { /* The backend violates the protocol. */ - sprintf(conn->errorMessage, - "FATAL: PQfn: protocol error: id=%x\n", id); + printfPQExpBuffer(&conn->errorMessage, + "FATAL: PQfn: protocol error: id=0x%x\n", + id); + saveErrorResult(conn); conn->inStart = conn->inCursor; - return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + return prepareAsyncResult(conn); } break; case 'E': /* error return */ - if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn)) + if (pqGets(&conn->errorMessage, conn)) continue; + /* build an error result holding the error message */ + saveErrorResult(conn); status = PGRES_FATAL_ERROR; break; case 'A': /* notify message */ @@ -1553,21 +1644,30 @@ PQfn(PGconn *conn, case 'Z': /* backend is ready for new query */ /* consume the message and exit */ conn->inStart = conn->inCursor; + /* if we saved a result object (probably an error), use it */ + if (conn->result) + return prepareAsyncResult(conn); return PQmakeEmptyPGresult(conn, status); default: /* The backend violates the protocol. */ - sprintf(conn->errorMessage, - "FATAL: PQfn: protocol error: id=%x\n", id); + printfPQExpBuffer(&conn->errorMessage, + "FATAL: PQfn: protocol error: id=0x%x\n", + id); + saveErrorResult(conn); conn->inStart = conn->inCursor; - return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + return prepareAsyncResult(conn); } /* Completed this message, keep going */ conn->inStart = conn->inCursor; needInput = false; } - /* we fall out of the loop only upon failing to read data */ - return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); + /* We fall out of the loop only upon failing to read data. + * conn->errorMessage has been set by pqWait or pqReadData. + * We want to append it to any already-received error message. + */ + saveErrorResult(conn); + return prepareAsyncResult(conn); } @@ -1630,16 +1730,18 @@ PQbinaryTuples(PGresult *res) static int check_field_number(const char *routineName, PGresult *res, int field_num) { + char noticeBuf[128]; + if (!res) return FALSE; /* no way to display error message... */ if (field_num < 0 || field_num >= res->numAttributes) { if (res->conn) { - sprintf(res->conn->errorMessage, + sprintf(noticeBuf, "%s: ERROR! field number %d is out of range 0..%d\n", routineName, field_num, res->numAttributes - 1); - DONOTICE(res->conn, res->conn->errorMessage); + DONOTICE(res->conn, noticeBuf); } return FALSE; } @@ -1650,16 +1752,18 @@ static int check_tuple_field_number(const char *routineName, PGresult *res, int tup_num, int field_num) { + char noticeBuf[128]; + if (!res) return FALSE; /* no way to display error message... */ if (tup_num < 0 || tup_num >= res->ntups) { if (res->conn) { - sprintf(res->conn->errorMessage, + sprintf(noticeBuf, "%s: ERROR! tuple number %d is out of range 0..%d\n", routineName, tup_num, res->ntups - 1); - DONOTICE(res->conn, res->conn->errorMessage); + DONOTICE(res->conn, noticeBuf); } return FALSE; } @@ -1667,10 +1771,10 @@ check_tuple_field_number(const char *routineName, PGresult *res, { if (res->conn) { - sprintf(res->conn->errorMessage, + sprintf(noticeBuf, "%s: ERROR! field number %d is out of range 0..%d\n", routineName, field_num, res->numAttributes - 1); - DONOTICE(res->conn, res->conn->errorMessage); + DONOTICE(res->conn, noticeBuf); } return FALSE; } @@ -1830,6 +1934,8 @@ PQoidStatus(PGresult *res) const char * PQcmdTuples(PGresult *res) { + char noticeBuf[128]; + if (!res) return ""; @@ -1843,10 +1949,10 @@ PQcmdTuples(PGresult *res) { if (res->conn) { - sprintf(res->conn->errorMessage, + sprintf(noticeBuf, "PQcmdTuples (%s) -- bad input from server\n", res->cmdStatus); - DONOTICE(res->conn, res->conn->errorMessage); + DONOTICE(res->conn, noticeBuf); } return ""; } @@ -1859,9 +1965,9 @@ PQcmdTuples(PGresult *res) { if (res->conn) { - sprintf(res->conn->errorMessage, + sprintf(noticeBuf, "PQcmdTuples (INSERT) -- there's no # of tuples\n"); - DONOTICE(res->conn, res->conn->errorMessage); + DONOTICE(res->conn, noticeBuf); } return ""; } diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index a105d0344b0..8793e24cf89 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.24 1999/07/19 06:25:39 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.25 1999/08/31 01:37:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -397,8 +397,9 @@ lo_import(PGconn *conn, char *filename) #endif if (fd < 0) { /* error */ - sprintf(conn->errorMessage, - "lo_import: can't open unix file\"%s\"\n", filename); + printfPQExpBuffer(&conn->errorMessage, + "lo_import: can't open unix file\"%s\"\n", + filename); return InvalidOid; } @@ -408,16 +409,18 @@ lo_import(PGconn *conn, char *filename) lobjOid = lo_creat(conn, INV_READ | INV_WRITE); if (lobjOid == InvalidOid) { - sprintf(conn->errorMessage, - "lo_import: can't create inv object for \"%s\"", filename); + printfPQExpBuffer(&conn->errorMessage, + "lo_import: can't create inv object for \"%s\"", + filename); return InvalidOid; } lobj = lo_open(conn, lobjOid, INV_WRITE); if (lobj == -1) { - sprintf(conn->errorMessage, - "lo_import: could not open inv object oid %u", lobjOid); + printfPQExpBuffer(&conn->errorMessage, + "lo_import: could not open inv object oid %u", + lobjOid); return InvalidOid; } @@ -429,8 +432,9 @@ lo_import(PGconn *conn, char *filename) tmp = lo_write(conn, lobj, buf, nbytes); if (tmp < nbytes) { - sprintf(conn->errorMessage, - "lo_import: error while reading \"%s\"", filename); + printfPQExpBuffer(&conn->errorMessage, + "lo_import: error while reading \"%s\"", + filename); return InvalidOid; } } @@ -461,8 +465,8 @@ lo_export(PGconn *conn, Oid lobjId, char *filename) lobj = lo_open(conn, lobjId, INV_READ); if (lobj == -1) { - sprintf(conn->errorMessage, - "lo_export: can't open inv object %u", lobjId); + printfPQExpBuffer(&conn->errorMessage, + "lo_export: can't open inv object %u", lobjId); return -1; } @@ -476,8 +480,9 @@ lo_export(PGconn *conn, Oid lobjId, char *filename) #endif if (fd < 0) { /* error */ - sprintf(conn->errorMessage, - "lo_export: can't open unix file\"%s\"", filename); + printfPQExpBuffer(&conn->errorMessage, + "lo_export: can't open unix file\"%s\"", + filename); return 0; } @@ -489,9 +494,9 @@ lo_export(PGconn *conn, Oid lobjId, char *filename) tmp = write(fd, buf, nbytes); if (tmp < nbytes) { - sprintf(conn->errorMessage, - "lo_export: error while writing \"%s\"", - filename); + printfPQExpBuffer(&conn->errorMessage, + "lo_export: error while writing \"%s\"", + filename); return -1; } } @@ -527,8 +532,8 @@ lo_initialize(PGconn *conn) lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); if (lobjfuncs == (PGlobjfuncs *) NULL) { - strcpy(conn->errorMessage, - "FATAL: malloc() failed in lo_initialize()\n"); + printfPQExpBuffer(&conn->errorMessage, + "FATAL: malloc() failed in lo_initialize()\n"); return -1; } MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs)); @@ -556,8 +561,8 @@ lo_initialize(PGconn *conn) { free(lobjfuncs); PQclear(res); - strcpy(conn->errorMessage, - "ERROR: SELECT didn't return data in lo_initialize()\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: SELECT didn't return data in lo_initialize()\n"); return -1; } @@ -596,57 +601,57 @@ lo_initialize(PGconn *conn) */ if (lobjfuncs->fn_lo_open == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_open\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_open\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_close == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_close\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_close\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_creat == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_creat\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_creat\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_unlink == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_unlink\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_unlink\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_lseek == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_lseek\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_lseek\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_tell == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lo_tell\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lo_tell\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_read == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function loread\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function loread\n"); free(lobjfuncs); return -1; } if (lobjfuncs->fn_lo_write == 0) { - strcpy(conn->errorMessage, - "ERROR: Cannot determine OID for function lowrite\n"); + printfPQExpBuffer(&conn->errorMessage, + "ERROR: Cannot determine OID for function lowrite\n"); free(lobjfuncs); return -1; } diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index 63558ec33c7..84149d12773 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -24,7 +24,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.28 1999/07/19 06:25:40 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.29 1999/08/31 01:37:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -106,12 +106,12 @@ pqPutBytes(const char *s, int nbytes, PGconn *conn) /* --------------------------------------------------------------------- */ /* 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, + and store it in an expansible PQExpBuffer. + If we run out of memory, all of the string is still read, but the excess characters are silently discarded. */ int -pqGets(char *s, int maxlen, PGconn *conn) +pqGets(PQExpBuffer buf, PGconn *conn) { /* Copy conn data to locals for faster search loop */ char *inBuffer = conn->inBuffer; @@ -126,18 +126,15 @@ pqGets(char *s, int maxlen, PGconn *conn) 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'; - } + + resetPQExpBuffer(buf); + appendBinaryPQExpBuffer(buf, inBuffer + conn->inCursor, slen); conn->inCursor = ++inCursor; if (conn->Pfdebug) - fprintf(conn->Pfdebug, "From backend> \"%s\"\n", s); + fprintf(conn->Pfdebug, "From backend> \"%s\"\n", + buf->data); return 0; } @@ -202,6 +199,7 @@ pqGetInt(int *result, int bytes, PGconn *conn) { uint16 tmp2; uint32 tmp4; + char noticeBuf[64]; switch (bytes) { @@ -220,9 +218,9 @@ pqGetInt(int *result, int bytes, PGconn *conn) *result = (int) ntohl(tmp4); break; default: - sprintf(conn->errorMessage, + sprintf(noticeBuf, "pqGetInt: int size %d not supported\n", bytes); - DONOTICE(conn, conn->errorMessage); + DONOTICE(conn, noticeBuf); return EOF; } @@ -242,6 +240,7 @@ pqPutInt(int value, int bytes, PGconn *conn) { uint16 tmp2; uint32 tmp4; + char noticeBuf[64]; switch (bytes) { @@ -256,9 +255,9 @@ pqPutInt(int value, int bytes, PGconn *conn) return EOF; break; default: - sprintf(conn->errorMessage, + sprintf(noticeBuf, "pqPutInt: int size %d not supported\n", bytes); - DONOTICE(conn, conn->errorMessage); + DONOTICE(conn, noticeBuf); return EOF; } @@ -287,9 +286,9 @@ pqReadReady(PGconn *conn) 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)); + printfPQExpBuffer(&conn->errorMessage, + "pqReadReady() -- select() failed: errno=%d\n%s\n", + errno, strerror(errno)); return 0; } return FD_ISSET(conn->sock, &input_mask); @@ -312,7 +311,8 @@ pqReadData(PGconn *conn) if (conn->sock < 0) { - strcpy(conn->errorMessage, "pqReadData() -- connection not open\n"); + printfPQExpBuffer(&conn->errorMessage, + "pqReadData() -- connection not open\n"); return -1; } @@ -333,9 +333,10 @@ pqReadData(PGconn *conn) * 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. + * here should be large enough for a TCP packet or Unix pipe + * bufferload. 8K is the usual pipe buffer size, so... */ - if (conn->inBufSize - conn->inEnd < 2000) + if (conn->inBufSize - conn->inEnd < 8192) { int newSize = conn->inBufSize * 2; char *newBuf = (char *) realloc(conn->inBuffer, newSize); @@ -369,9 +370,9 @@ tryAgain: if (errno == ECONNRESET) goto definitelyFailed; #endif - sprintf(conn->errorMessage, - "pqReadData() -- read() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "pqReadData() -- read() failed: errno=%d\n%s\n", + errno, strerror(errno)); return -1; } if (nread > 0) @@ -417,9 +418,9 @@ tryAgain2: if (errno == ECONNRESET) goto definitelyFailed; #endif - sprintf(conn->errorMessage, - "pqReadData() -- read() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "pqReadData() -- read() failed: errno=%d\n%s\n", + errno, strerror(errno)); return -1; } if (nread > 0) @@ -433,7 +434,7 @@ tryAgain2: * This means the connection has been closed. Cope. */ definitelyFailed: - sprintf(conn->errorMessage, + printfPQExpBuffer(&conn->errorMessage, "pqReadData() -- backend closed the channel unexpectedly.\n" "\tThis probably means the backend terminated abnormally\n" "\tbefore or while processing the request.\n"); @@ -459,7 +460,8 @@ pqFlush(PGconn *conn) if (conn->sock < 0) { - strcpy(conn->errorMessage, "pqFlush() -- connection not open\n"); + printfPQExpBuffer(&conn->errorMessage, + "pqFlush() -- connection not open\n"); return EOF; } @@ -499,7 +501,7 @@ pqFlush(PGconn *conn) #ifdef ECONNRESET case ECONNRESET: #endif - sprintf(conn->errorMessage, + printfPQExpBuffer(&conn->errorMessage, "pqFlush() -- backend closed the channel unexpectedly.\n" "\tThis probably means the backend terminated abnormally" " before or while processing the request.\n"); @@ -513,8 +515,8 @@ pqFlush(PGconn *conn) return EOF; default: - sprintf(conn->errorMessage, - "pqFlush() -- couldn't send data: errno=%d\n%s\n", + printfPQExpBuffer(&conn->errorMessage, + "pqFlush() -- couldn't send data: errno=%d\n%s\n", errno, strerror(errno)); /* We don't assume it's a fatal error... */ return EOF; @@ -552,7 +554,8 @@ pqWait(int forRead, int forWrite, PGconn *conn) if (conn->sock < 0) { - strcpy(conn->errorMessage, "pqWait() -- connection not open\n"); + printfPQExpBuffer(&conn->errorMessage, + "pqWait() -- connection not open\n"); return EOF; } @@ -570,9 +573,9 @@ pqWait(int forRead, int forWrite, PGconn *conn) { if (errno == EINTR) continue; - sprintf(conn->errorMessage, - "pqWait() -- select() failed: errno=%d\n%s\n", - errno, strerror(errno)); + printfPQExpBuffer(&conn->errorMessage, + "pqWait() -- select() failed: errno=%d\n%s\n", + errno, strerror(errno)); return EOF; } /* On nonerror return, assume we're done */ diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c index 9151463fece..02f3455dcbc 100644 --- a/src/interfaces/libpq/fe-print.c +++ b/src/interfaces/libpq/fe-print.c @@ -9,7 +9,7 @@ * didn't really belong there. * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-print.c,v 1.26 1999/07/19 06:25:40 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-print.c,v 1.27 1999/08/31 01:37:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -51,7 +51,7 @@ static struct winsize static void do_field(PQprintOpt *po, PGresult *res, - const int i, const int j, char *buf, const int fs_len, + const int i, const int j, const int fs_len, char **fields, const int nFields, char **fieldNames, unsigned char *fieldNotNum, int *fieldMax, @@ -103,7 +103,6 @@ PQprint(FILE *fout, int usePipe = 0; pqsigfunc oldsigpipehandler = NULL; char *pagerenv; - char buf[MAX_QUERY_SIZE + 1]; nTups = PQntuples(res); if (!(fieldNames = (char **) calloc(nFields, sizeof(char *)))) @@ -254,7 +253,7 @@ PQprint(FILE *fout, fprintf(fout, "-- RECORD %d --\n", i); } for (j = 0; j < nFields; j++) - do_field(po, res, i, j, buf, fs_len, fields, nFields, + do_field(po, res, i, j, fs_len, fields, nFields, fieldNames, fieldNotNum, fieldMax, fieldMaxLen, fout); if (po->html3 && po->expanded) @@ -332,7 +331,7 @@ PQdisplayTuples(PGresult *res, j; int nFields; int nTuples; - int fLength[MAX_FIELDS]; + int *fLength = NULL; if (fieldSep == NULL) fieldSep = DEFAULT_FIELD_SEP; @@ -344,19 +343,19 @@ PQdisplayTuples(PGresult *res, if (fp == NULL) fp = stdout; - /* 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 */ + /* Figure the field lengths to align to */ /* will be somewhat time consuming for very large results */ if (fillAlign) { - for (i = 0; i < nTuples; i++) + fLength = (int *) malloc(nFields * sizeof(int)); + for (j = 0; j < nFields; j++) { - for (j = 0; j < nFields; j++) + fLength[j] = strlen(PQfname(res, j)); + for (i = 0; i < nTuples; i++) { - if (PQgetlength(res, i, j) > fLength[j]) - fLength[j] = PQgetlength(res, i, j); + int flen = PQgetlength(res, i, j); + if (flen > fLength[j]) + fLength[j] = flen; } } } @@ -401,6 +400,9 @@ PQdisplayTuples(PGresult *res, (PQntuples(res) == 1) ? "" : "s"); fflush(fp); + + if (fLength) + free(fLength); } @@ -522,7 +524,7 @@ PQmblen(unsigned char *s) static void do_field(PQprintOpt *po, PGresult *res, - const int i, const int j, char *buf, const int fs_len, + const int i, const int j, const int fs_len, char **fields, const int nFields, char **fieldNames, unsigned char *fieldNotNum, int *fieldMax, @@ -530,8 +532,7 @@ do_field(PQprintOpt *po, PGresult *res, { char *pval, - *p, - *o; + *p; int plen; bool skipit; @@ -553,62 +554,49 @@ do_field(PQprintOpt *po, PGresult *res, if (!skipit) { - char ch = 0; + if (po->align && ! fieldNotNum[j]) + { + /* Detect whether field contains non-numeric data */ + char ch = '0'; #ifdef MULTIBYTE - int len; - - for (p = pval, o = buf; *p; - len = PQmblen(p), memcpy(o, p, len), - o += len, p += len) + for (p = pval; *p; p += PQmblen(p)) #else - for (p = pval, o = buf; *p; *(o++) = *(p++)) + for (p = pval; *p; p++) #endif - { - ch = *p; - + { + ch = *p; + if (! ((ch >= '0' && ch <= '9') || + ch == '.' || + ch == 'E' || + ch == 'e' || + ch == ' ' || + ch == '-')) + { + fieldNotNum[j] = 1; + break; + } + } /* - * Consensus on pgsql-interfaces (as of Aug 1998) seems to be - * that the print functions ought not insert backslashes. If - * you like them, you can re-enable this next bit. + * Above loop will believe E in first column is numeric; also, we + * insist on a digit in the last column for a numeric. This test + * is still not bulletproof but it handles most cases. */ -#ifdef GRATUITOUS_BACKSLASHES - if ((fs_len == 1 && (ch == *(po->fieldSep))) || - ch == '\\' || ch == '\n') - *(o++) = '\\'; -#endif - if (po->align && - !((ch >= '0' && ch <= '9') || - ch == '.' || - ch == 'E' || - ch == 'e' || - ch == ' ' || - ch == '-')) + if (*pval == 'E' || *pval == 'e' || + !(ch >= '0' && ch <= '9')) fieldNotNum[j] = 1; } - *o = '\0'; - - /* - * Above loop will believe E in first column is numeric; also, we - * insist on a digit in the last column for a numeric. This test - * is still not bulletproof but it handles most cases. - */ - if (po->align && - (*pval == 'E' || *pval == 'e' || - !(ch >= '0' && ch <= '9'))) - fieldNotNum[j] = 1; + if (!po->expanded && (po->align || po->html3)) { - int n = strlen(buf); - - if (n > fieldMax[j]) - fieldMax[j] = n; - if (!(fields[i * nFields + j] = (char *) malloc(n + 1))) + if (plen > fieldMax[j]) + fieldMax[j] = plen; + if (!(fields[i * nFields + j] = (char *) malloc(plen + 1))) { perror("malloc"); exit(1); } - strcpy(fields[i * nFields + j], buf); + strcpy(fields[i * nFields + j], pval); } else { @@ -620,23 +608,26 @@ do_field(PQprintOpt *po, PGresult *res, "<td align=%s>%s</td></tr>\n", fieldNames[j], fieldNotNum[j] ? "left" : "right", - buf); + pval); else { if (po->align) fprintf(fout, "%-*s%s %s\n", - fieldMaxLen - fs_len, fieldNames[j], po->fieldSep, - buf); + fieldMaxLen - fs_len, fieldNames[j], + po->fieldSep, + pval); else - fprintf(fout, "%s%s%s\n", fieldNames[j], po->fieldSep, buf); + fprintf(fout, + "%s%s%s\n", + fieldNames[j], po->fieldSep, pval); } } else { if (!po->html3) { - fputs(buf, fout); + fputs(pval, fout); efield: if ((j + 1) < nFields) fputs(po->fieldSep, fout); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index f3b788dd930..d22d403e016 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -11,7 +11,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.10 1999/07/13 20:00:37 momjian Exp $ + * $Id: libpq-int.h,v 1.11 1999/08/31 01:37:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,12 +21,12 @@ /* We assume libpq-fe.h has already been included. */ -/* ---------------- - * include stuff common to fe and be - * ---------------- - */ +/* include stuff common to fe and be */ #include "libpq/pqcomm.h" #include "lib/dllist.h" +/* include stuff found in fe only */ +#include "pqexpbuffer.h" + /* libpq supports this version of the frontend/backend protocol. * @@ -45,8 +45,6 @@ * POSTGRES backend dependent Constants. */ -/* ERROR_MSG_LENGTH should really be the same as ELOG_MAXLEN in utils/elog.h*/ -#define ERROR_MSG_LENGTH 4096 #define CMDSTATUS_LEN 40 /* @@ -115,7 +113,7 @@ struct pg_result int tupArrSize; /* size of tuples array allocated */ ExecStatusType resultStatus; char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the - * last insert query */ + * last query */ int binary; /* binary tuple values if binary == 1, * otherwise ASCII */ PGconn *conn; /* connection we did the query on, if any */ @@ -217,8 +215,11 @@ struct pg_conn PGresult *result; /* result being constructed */ PGresAttValue *curTuple; /* tuple currently being read */ - /* Message space. Placed last for code-size reasons. */ - char errorMessage[ERROR_MSG_LENGTH]; + /* Buffer for current error message */ + PQExpBufferData errorMessage; /* expansible string */ + + /* Buffer for receiving various parts of messages */ + PQExpBufferData workBuffer; /* expansible string */ }; /* ---------------- @@ -249,7 +250,7 @@ extern void pqClearAsyncResult(PGconn *conn); * necessarily any error. */ extern int pqGetc(char *result, PGconn *conn); -extern int pqGets(char *s, int maxlen, PGconn *conn); +extern int pqGets(PQExpBuffer buf, 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); @@ -259,12 +260,6 @@ extern int pqReadData(PGconn *conn); extern int pqFlush(PGconn *conn); extern int pqWait(int forRead, int forWrite, PGconn *conn); -/* max length of message to send */ -#define MAX_MESSAGE_LEN MAX_QUERY_SIZE - -/* maximum number of fields in a tuple */ -#define MAX_FIELDS 512 - /* bits in a byte */ #define BYTELEN 8 diff --git a/src/interfaces/libpq/pqexpbuffer.c b/src/interfaces/libpq/pqexpbuffer.c new file mode 100644 index 00000000000..ddaca7e67b5 --- /dev/null +++ b/src/interfaces/libpq/pqexpbuffer.c @@ -0,0 +1,254 @@ +/*------------------------------------------------------------------------- + * + * pqexpbuffer.c + * + * PQExpBuffer provides an indefinitely-extensible string data type. + * It can be used to buffer either ordinary C strings (null-terminated text) + * or arbitrary binary data. All storage is allocated with malloc(). + * + * This module is essentially the same as the backend's StringInfo data type, + * but it is intended for use in frontend libpq and client applications. + * Thus, it does not rely on palloc(), elog(), nor vsnprintf(). + * + * Copyright (c) 1994, Regents of the University of California + * + * $Header: /cvsroot/pgsql/src/interfaces/libpq/pqexpbuffer.c,v 1.1 1999/08/31 01:37:37 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "pqexpbuffer.h" + +/* + * createPQExpBuffer + * + * Create an empty 'PQExpBufferData' & return a pointer to it. + */ +PQExpBuffer +createPQExpBuffer(void) +{ + PQExpBuffer res; + + res = (PQExpBuffer) malloc(sizeof(PQExpBufferData)); + if (res != NULL) + initPQExpBuffer(res); + + return res; +} + +/* + * initPQExpBuffer + * + * Initialize a PQExpBufferData struct (with previously undefined contents) + * to describe an empty string. + */ +void +initPQExpBuffer(PQExpBuffer str) +{ + str->data = (char *) malloc(INITIAL_EXPBUFFER_SIZE); + if (str->data == NULL) + { + str->maxlen = 0; + str->len = 0; + } + else + { + str->maxlen = INITIAL_EXPBUFFER_SIZE; + str->len = 0; + str->data[0] = '\0'; + } +} + +/*------------------------ + * destroyPQExpBuffer(str); + * free()s both the data buffer and the PQExpBufferData. + * This is the inverse of createPQExpBuffer(). + */ +void +destroyPQExpBuffer(PQExpBuffer str) +{ + if (str) + { + termPQExpBuffer(str); + free(str); + } +} + +/*------------------------ + * termPQExpBuffer(str) + * free()s the data buffer but not the PQExpBufferData itself. + * This is the inverse of initPQExpBuffer(). + */ +void +termPQExpBuffer(PQExpBuffer str) +{ + if (str->data) + { + free(str->data); + str->data = NULL; + } +} + +/*------------------------ + * resetPQExpBuffer + * Reset a PQExpBuffer to empty + */ +void +resetPQExpBuffer(PQExpBuffer str) +{ + if (str) + { + str->len = 0; + if (str->data) + str->data[0] = '\0'; + } +} + +/*------------------------ + * enlargePQExpBuffer + * Make sure there is enough space for 'needed' more bytes in the buffer + * ('needed' does not include the terminating null). + * + * Returns 1 if OK, 0 if failed to enlarge buffer. + */ +int +enlargePQExpBuffer(PQExpBuffer str, int needed) +{ + int newlen; + char *newdata; + + needed += str->len + 1; /* total space required now */ + if (needed <= str->maxlen) + return 1; /* got enough space already */ + + /* + * We don't want to allocate just a little more space with each + * append; for efficiency, double the buffer size each time it + * overflows. Actually, we might need to more than double it if + * 'needed' is big... + */ + newlen = str->maxlen ? (2 * str->maxlen) : 64; + while (needed > newlen) + newlen = 2 * newlen; + + newdata = (char *) realloc(str->data, newlen); + if (newdata != NULL) + { + str->data = newdata; + str->maxlen = newlen; + return 1; + } + return 0; +} + +/*------------------------ + * printfPQExpBuffer + * Format text data under the control of fmt (an sprintf-like format string) + * and insert it into str. More space is allocated to str if necessary. + * This is a convenience routine that does the same thing as + * resetPQExpBuffer() followed by appendPQExpBuffer(). + * + * CAUTION: the frontend version of this routine WILL FAIL if the result of + * the sprintf formatting operation exceeds 1KB of data (but the size of the + * pre-existing string in the buffer doesn't matter). We could make it + * support larger strings, but that requires vsnprintf() which is not + * universally available. Currently there is no need for long strings to be + * formatted in the frontend. We could support it, if necessary, by + * conditionally including a vsnprintf emulation. + */ +void +printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) +{ + va_list args; + char buffer[1024]; + + va_start(args, fmt); + vsprintf(buffer, fmt, args); + va_end(args); + + resetPQExpBuffer(str); + appendPQExpBufferStr(str, buffer); +} + +/*------------------------ + * appendPQExpBuffer + * + * Format text data under the control of fmt (an sprintf-like format string) + * and append it to whatever is already in str. More space is allocated + * to str if necessary. This is sort of like a combination of sprintf and + * strcat. + * + * CAUTION: the frontend version of this routine WILL FAIL if the result of + * the sprintf formatting operation exceeds 1KB of data (but the size of the + * pre-existing string in the buffer doesn't matter). We could make it + * support larger strings, but that requires vsnprintf() which is not + * universally available. Currently there is no need for long strings to be + * formatted in the frontend. We could support it, if necessary, by + * conditionally including a vsnprintf emulation. + */ +void +appendPQExpBuffer(PQExpBuffer str, const char *fmt,...) +{ + va_list args; + char buffer[1024]; + + va_start(args, fmt); + vsprintf(buffer, fmt, args); + va_end(args); + + appendPQExpBufferStr(str, buffer); +} + +/*------------------------ + * appendPQExpBufferStr + * Append the given string to a PQExpBuffer, allocating more space + * if necessary. + */ +void +appendPQExpBufferStr(PQExpBuffer str, const char *data) +{ + appendBinaryPQExpBuffer(str, data, strlen(data)); +} + +/*------------------------ + * appendPQExpBufferChar + * Append a single byte to str. + * Like appendPQExpBuffer(str, "%c", ch) but much faster. + */ +void +appendPQExpBufferChar(PQExpBuffer str, char ch) +{ + /* Make more room if needed */ + if (! enlargePQExpBuffer(str, 1)) + return; + + /* OK, append the character */ + str->data[str->len] = ch; + str->len++; + str->data[str->len] = '\0'; +} + +/* + * appendBinaryPQExpBuffer + * + * Append arbitrary binary data to a PQExpBuffer, allocating more space + * if necessary. + */ +void +appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, int datalen) +{ + /* Make more room if needed */ + if (! enlargePQExpBuffer(str, datalen)) + return; + + /* OK, append the data */ + memcpy(str->data + str->len, data, datalen); + str->len += datalen; + + /* + * Keep a trailing null in place, even though it's probably useless + * for binary data... + */ + str->data[str->len] = '\0'; +} diff --git a/src/interfaces/libpq/pqexpbuffer.h b/src/interfaces/libpq/pqexpbuffer.h new file mode 100644 index 00000000000..5c2d2f122ce --- /dev/null +++ b/src/interfaces/libpq/pqexpbuffer.h @@ -0,0 +1,169 @@ +/*------------------------------------------------------------------------- + * + * pqexpbuffer.h + * Declarations/definitions for "PQExpBuffer" functions. + * + * PQExpBuffer provides an indefinitely-extensible string data type. + * It can be used to buffer either ordinary C strings (null-terminated text) + * or arbitrary binary data. All storage is allocated with malloc(). + * + * This module is essentially the same as the backend's StringInfo data type, + * but it is intended for use in frontend libpq and client applications. + * Thus, it does not rely on palloc(), elog(), nor vsnprintf(). + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: pqexpbuffer.h,v 1.1 1999/08/31 01:37:37 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PQEXPBUFFER_H +#define PQEXPBUFFER_H + +/*------------------------- + * PQExpBufferData holds information about an extensible string. + * data is the current buffer for the string (allocated with malloc). + * len is the current string length. There is guaranteed to be + * a terminating '\0' at data[len], although this is not very + * useful when the string holds binary data rather than text. + * maxlen is the allocated size in bytes of 'data', i.e. the maximum + * string size (including the terminating '\0' char) that we can + * currently store in 'data' without having to reallocate + * more space. We must always have maxlen > len. + *------------------------- + */ +typedef struct PQExpBufferData +{ + char *data; + int len; + int maxlen; +} PQExpBufferData; + +typedef PQExpBufferData *PQExpBuffer; + +/*------------------------ + * Initial size of the data buffer in a PQExpBuffer. + * NB: this must be large enough to hold error messages that might + * be returned by PQrequestCancel() or any routine in fe-auth.c. + *------------------------ + */ +#define INITIAL_EXPBUFFER_SIZE 256 + +/*------------------------ + * There are two ways to create a PQExpBuffer object initially: + * + * PQExpBuffer stringptr = createPQExpBuffer(); + * Both the PQExpBufferData and the data buffer are malloc'd. + * + * PQExpBufferData string; + * initPQExpBuffer(&string); + * The data buffer is malloc'd but the PQExpBufferData is presupplied. + * This is appropriate if the PQExpBufferData is a field of another + * struct. + *------------------------- + */ + +/*------------------------ + * createPQExpBuffer + * Create an empty 'PQExpBufferData' & return a pointer to it. + */ +extern PQExpBuffer createPQExpBuffer(void); + +/*------------------------ + * initPQExpBuffer + * Initialize a PQExpBufferData struct (with previously undefined contents) + * to describe an empty string. + */ +extern void initPQExpBuffer(PQExpBuffer str); + +/*------------------------ + * To destroy a PQExpBuffer, use either: + * + * destroyPQExpBuffer(str); + * free()s both the data buffer and the PQExpBufferData. + * This is the inverse of createPQExpBuffer(). + * + * termPQExpBuffer(str) + * free()s the data buffer but not the PQExpBufferData itself. + * This is the inverse of initPQExpBuffer(). + * + * NOTE: some routines build up a string using PQExpBuffer, and then + * release the PQExpBufferData but return the data string itself to their + * caller. At that point the data string looks like a plain malloc'd + * string. + */ +extern void destroyPQExpBuffer(PQExpBuffer str); +extern void termPQExpBuffer(PQExpBuffer str); + +/*------------------------ + * resetPQExpBuffer + * Reset a PQExpBuffer to empty + */ +extern void resetPQExpBuffer(PQExpBuffer str); + +/*------------------------ + * enlargePQExpBuffer + * Make sure there is enough space for 'needed' more bytes in the buffer + * ('needed' does not include the terminating null). + * + * Returns 1 if OK, 0 if failed to enlarge buffer. + */ +extern int enlargePQExpBuffer(PQExpBuffer str, int needed); + +/*------------------------ + * printfPQExpBuffer + * Format text data under the control of fmt (an sprintf-like format string) + * and insert it into str. More space is allocated to str if necessary. + * This is a convenience routine that does the same thing as + * resetPQExpBuffer() followed by appendPQExpBuffer(). + * + * CAUTION: the frontend version of this routine WILL FAIL if the result of + * the sprintf formatting operation exceeds 1KB of data (but the size of the + * pre-existing string in the buffer doesn't matter). We could make it + * support larger strings, but that requires vsnprintf() which is not + * universally available. Currently there is no need for long strings to be + * formatted in the frontend. We could support it, if necessary, by + * conditionally including a vsnprintf emulation. + */ +extern void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...); + +/*------------------------ + * appendPQExpBuffer + * Format text data under the control of fmt (an sprintf-like format string) + * and append it to whatever is already in str. More space is allocated + * to str if necessary. This is sort of like a combination of sprintf and + * strcat. + * + * CAUTION: the frontend version of this routine WILL FAIL if the result of + * the sprintf formatting operation exceeds 1KB of data (but the size of the + * pre-existing string in the buffer doesn't matter). We could make it + * support larger strings, but that requires vsnprintf() which is not + * universally available. Currently there is no need for long strings to be + * formatted in the frontend. We could support it, if necessary, by + * conditionally including a vsnprintf emulation. + */ +extern void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...); + +/*------------------------ + * appendPQExpBufferStr + * Append the given string to a PQExpBuffer, allocating more space + * if necessary. + */ +extern void appendPQExpBufferStr(PQExpBuffer str, const char *data); + +/*------------------------ + * appendPQExpBufferChar + * Append a single byte to str. + * Like appendPQExpBuffer(str, "%c", ch) but much faster. + */ +extern void appendPQExpBufferChar(PQExpBuffer str, char ch); + +/*------------------------ + * appendBinaryPQExpBuffer + * Append arbitrary binary data to a PQExpBuffer, allocating more space + * if necessary. + */ +extern void appendBinaryPQExpBuffer(PQExpBuffer str, + const char *data, int datalen); + +#endif /* PQEXPBUFFER_H */ diff --git a/src/interfaces/libpq/win32.mak b/src/interfaces/libpq/win32.mak index 1d351df7e8f..eda99d815ba 100644 --- a/src/interfaces/libpq/win32.mak +++ b/src/interfaces/libpq/win32.mak @@ -29,6 +29,7 @@ CLEAN : -@erase "$(INTDIR)\fe-lobj.obj" -@erase "$(INTDIR)\fe-misc.obj" -@erase "$(INTDIR)\fe-print.obj" + -@erase "$(INTDIR)\pqexpbuffer.obj" -@erase "$(OUTDIR)\libpqdll.obj" -@erase "$(OUTDIR)\libpq.lib" -@erase "$(OUTDIR)\libpq.dll" @@ -70,7 +71,8 @@ LIB32_OBJS= \ "$(INTDIR)\fe-exec.obj" \ "$(INTDIR)\fe-lobj.obj" \ "$(INTDIR)\fe-misc.obj" \ - "$(INTDIR)\fe-print.obj" + "$(INTDIR)\fe-print.obj" \ + "$(INTDIR)\pqexpbuffer.obj" !IFDEF MULTIBYTE LIB32_OBJS = $(LIB32_OBJS) "$(INTDIR)\common.obj" "$(INTDIR)\wchar.obj" "$(INTDIR)\conv.obj" "$(INTDIR)\big5.obj" -- GitLab