diff options
Diffstat (limited to 'src/interfaces/libpq/fe-protocol3.c')
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 340 |
1 files changed, 266 insertions, 74 deletions
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 2fbfa01566e..05543f8e76d 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.1 2003/06/08 17:43:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.2 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,6 +40,8 @@ static int getRowDescriptions(PGconn *conn); static int getAnotherTuple(PGconn *conn, int msgLength); static int getParameterStatus(PGconn *conn); static int getNotify(PGconn *conn); +static int getCopyStart(PGconn *conn, ExecStatusType copytype); +static int getReadyForQuery(PGconn *conn); static int build_startup_packet(const PGconn *conn, char *packet, const PQEnvironmentOption *options); @@ -171,7 +173,7 @@ pqParseInput3(PGconn *conn) else { snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("message type 0x%02x arrived from server while idle\n"), + libpq_gettext("message type 0x%02x arrived from server while idle"), id); PGDONOTICE(conn, noticeWorkspace); /* Discard the unexpected message */ @@ -201,7 +203,7 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; break; case 'Z': /* backend is ready for new query */ - if (pqGetc(&conn->xact_status, conn)) + if (getReadyForQuery(conn)) return; conn->asyncStatus = PGASYNC_IDLE; break; @@ -211,6 +213,11 @@ pqParseInput3(PGconn *conn) PGRES_EMPTY_QUERY); conn->asyncStatus = PGASYNC_READY; break; + case '1': /* Parse Complete */ + case '2': /* Bind Complete */ + case '3': /* Close Complete */ + /* Nothing to do for these message types */ + break; case 'S': /* parameter status */ if (getParameterStatus(conn)) return; @@ -276,17 +283,13 @@ pqParseInput3(PGconn *conn) } break; case 'G': /* Start Copy In */ - if (pqGetc(&conn->copy_is_binary, conn)) + if (getCopyStart(conn, PGRES_COPY_IN)) return; - /* XXX we currently ignore the rest of the message */ - conn->inCursor = conn->inStart + 5 + msgLength; conn->asyncStatus = PGASYNC_COPY_IN; break; case 'H': /* Start Copy Out */ - if (pqGetc(&conn->copy_is_binary, conn)) + if (getCopyStart(conn, PGRES_COPY_OUT)) return; - /* XXX we currently ignore the rest of the message */ - conn->inCursor = conn->inStart + 5 + msgLength; conn->asyncStatus = PGASYNC_COPY_OUT; conn->copy_already_done = 0; break; @@ -398,6 +401,9 @@ getRowDescriptions(PGconn *conn) MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc)); } + /* result->binary is true only if ALL columns are binary */ + result->binary = (nfields > 0) ? 1 : 0; + /* get type info */ for (i = 0; i < nfields; i++) { @@ -430,10 +436,15 @@ getRowDescriptions(PGconn *conn) result->attDescs[i].name = pqResultStrdup(result, conn->workBuffer.data); + result->attDescs[i].tableid = tableid; + result->attDescs[i].columnid = columnid; + result->attDescs[i].format = format; result->attDescs[i].typid = typid; result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; - /* XXX todo: save tableid/columnid, format too */ + + if (format != 1) + result->binary = 0; } /* Success! */ @@ -503,7 +514,9 @@ getAnotherTuple(PGconn *conn, int msgLength) vlen = 0; if (tup[i].value == NULL) { - tup[i].value = (char *) pqResultAlloc(result, vlen + 1, false); + bool isbinary = (result->attDescs[i].format != 0); + + tup[i].value = (char *) pqResultAlloc(result, vlen + 1, isbinary); if (tup[i].value == NULL) goto outOfMemory; } @@ -553,6 +566,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError) PGresult *res; PQExpBufferData workBuf; char id; + const char *val; /* * Make a PGresult to hold the accumulated fields. We temporarily @@ -580,68 +594,63 @@ pqGetErrorNotice3(PGconn *conn, bool isError) break; /* terminator found */ if (pqGets(&workBuf, conn)) goto fail; - switch (id) - { - case 'S': - res->errSeverity = pqResultStrdup(res, workBuf.data); - break; - case 'C': - res->errCode = pqResultStrdup(res, workBuf.data); - break; - case 'M': - res->errPrimary = pqResultStrdup(res, workBuf.data); - break; - case 'D': - res->errDetail = pqResultStrdup(res, workBuf.data); - break; - case 'H': - res->errHint = pqResultStrdup(res, workBuf.data); - break; - case 'P': - res->errPosition = pqResultStrdup(res, workBuf.data); - break; - case 'W': - res->errContext = pqResultStrdup(res, workBuf.data); - break; - case 'F': - res->errFilename = pqResultStrdup(res, workBuf.data); - break; - case 'L': - res->errLineno = pqResultStrdup(res, workBuf.data); - break; - case 'R': - res->errFuncname = pqResultStrdup(res, workBuf.data); - break; - default: - /* silently ignore any other field type */ - break; - } + pqSaveMessageField(res, id, workBuf.data); } /* * Now build the "overall" error message for PQresultErrorMessage. - * - * XXX this should be configurable somehow. */ resetPQExpBuffer(&workBuf); - if (res->errSeverity) - appendPQExpBuffer(&workBuf, "%s: ", res->errSeverity); - if (res->errPrimary) - appendPQExpBufferStr(&workBuf, res->errPrimary); - /* translator: %s represents a digit string */ - if (res->errPosition) - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), - res->errPosition); + val = PQresultErrorField(res, 'S'); /* Severity */ + if (val) + appendPQExpBuffer(&workBuf, "%s: ", val); + if (conn->verbosity == PQERRORS_VERBOSE) + { + val = PQresultErrorField(res, 'C'); /* SQLSTATE Code */ + if (val) + appendPQExpBuffer(&workBuf, "%s: ", val); + } + val = PQresultErrorField(res, 'M'); /* Primary message */ + if (val) + appendPQExpBufferStr(&workBuf, val); + val = PQresultErrorField(res, 'P'); /* Position */ + if (val) + { + /* translator: %s represents a digit string */ + appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), val); + } appendPQExpBufferChar(&workBuf, '\n'); - if (res->errDetail) - appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), - res->errDetail); - if (res->errHint) - appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), - res->errHint); - if (res->errContext) - appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), - res->errContext); + if (conn->verbosity != PQERRORS_TERSE) + { + val = PQresultErrorField(res, 'D'); /* Detail */ + if (val) + appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val); + val = PQresultErrorField(res, 'H'); /* Hint */ + if (val) + appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val); + val = PQresultErrorField(res, 'W'); /* Where */ + if (val) + appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), val); + } + if (conn->verbosity == PQERRORS_VERBOSE) + { + const char *valf; + const char *vall; + + valf = PQresultErrorField(res, 'F'); /* File */ + vall = PQresultErrorField(res, 'L'); /* Line */ + val = PQresultErrorField(res, 'R'); /* Routine */ + if (val || valf || vall) + { + appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: ")); + if (val) + appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val); + if (valf && vall) /* unlikely we'd have just one */ + appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"), + valf, vall); + appendPQExpBufferChar(&workBuf, '\n'); + } + } /* * Either save error as current async result, or just emit the notice. @@ -656,7 +665,9 @@ pqGetErrorNotice3(PGconn *conn, bool isError) } else { - PGDONOTICE(conn, workBuf.data); + /* We can cheat a little here and not copy the message. */ + res->errMsg = workBuf.data; + (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); PQclear(res); } @@ -710,35 +721,216 @@ static int getNotify(PGconn *conn) { int be_pid; + char *svname; + int nmlen; + int extralen; PGnotify *newNotify; if (pqGetInt(&be_pid, 4, conn)) return EOF; if (pqGets(&conn->workBuffer, conn)) return EOF; + /* must save name while getting extra string */ + svname = strdup(conn->workBuffer.data); + if (!svname) + return EOF; + if (pqGets(&conn->workBuffer, conn)) + { + free(svname); + return EOF; + } /* - * Store the relation name right after the PQnotify structure so it + * Store the strings right after the PQnotify structure so it * can all be freed at once. We don't use NAMEDATALEN because we * don't want to tie this interface to a specific server name length. */ - newNotify = (PGnotify *) malloc(sizeof(PGnotify) + - strlen(conn->workBuffer.data) +1); + nmlen = strlen(svname); + extralen = strlen(conn->workBuffer.data); + newNotify = (PGnotify *) malloc(sizeof(PGnotify) + nmlen + extralen + 2); if (newNotify) { newNotify->relname = (char *) newNotify + sizeof(PGnotify); - strcpy(newNotify->relname, conn->workBuffer.data); + strcpy(newNotify->relname, svname); + newNotify->extra = newNotify->relname + nmlen + 1; + strcpy(newNotify->extra, conn->workBuffer.data); newNotify->be_pid = be_pid; DLAddTail(conn->notifyList, DLNewElem(newNotify)); } - /* Swallow extra string (not presently used) */ - if (pqGets(&conn->workBuffer, conn)) + free(svname); + return 0; +} + +/* + * getCopyStart - process CopyInResponse or CopyOutResponse message + * + * parseInput already read the message type and length. + */ +static int +getCopyStart(PGconn *conn, ExecStatusType copytype) +{ + PGresult *result; + int nfields; + int i; + + result = PQmakeEmptyPGresult(conn, copytype); + + if (pqGetc(&conn->copy_is_binary, conn)) + { + PQclear(result); return EOF; + } + result->binary = conn->copy_is_binary; + /* the next two bytes are the number of fields */ + if (pqGetInt(&(result->numAttributes), 2, conn)) + { + PQclear(result); + return EOF; + } + nfields = result->numAttributes; + + /* allocate space for the attribute descriptors */ + if (nfields > 0) + { + result->attDescs = (PGresAttDesc *) + pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE); + MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc)); + } + + for (i = 0; i < nfields; i++) + { + int format; + if (pqGetInt(&format, 2, conn)) + { + PQclear(result); + return EOF; + } + + /* + * Since pqGetInt treats 2-byte integers as unsigned, we need to + * coerce these results to signed form. + */ + format = (int) ((int16) format); + + result->attDescs[i].format = format; + } + + /* Success! */ + conn->result = result; return 0; } +/* + * getReadyForQuery - process ReadyForQuery message + */ +static int +getReadyForQuery(PGconn *conn) +{ + char xact_status; + + if (pqGetc(&xact_status, conn)) + return EOF; + switch (xact_status) + { + case 'I': + conn->xactStatus = PQTRANS_IDLE; + break; + case 'T': + conn->xactStatus = PQTRANS_INTRANS; + break; + case 'E': + conn->xactStatus = PQTRANS_INERROR; + break; + default: + conn->xactStatus = PQTRANS_UNKNOWN; + break; + } + + return 0; +} + +/* + * PQgetCopyData - read a row of data from the backend during COPY OUT + * + * If successful, sets *buffer to point to a malloc'd row of data, and + * returns row length (always > 0) as result. + * Returns 0 if no row available yet (only possible if async is true), + * -1 if end of copy (consult PQgetResult), or -2 if error (consult + * PQerrorMessage). + */ +int +pqGetCopyData3(PGconn *conn, char **buffer, int async) +{ + char id; + int msgLength; + int avail; + + for (;;) + { + /* + * Do we have the next input message? To make life simpler for async + * callers, we keep returning 0 until the next message is fully + * available, even if it is not Copy Data. + */ + conn->inCursor = conn->inStart; + if (pqGetc(&id, conn)) + goto nodata; + if (pqGetInt(&msgLength, 4, conn)) + goto nodata; + avail = conn->inEnd - conn->inCursor; + if (avail < msgLength - 4) + goto nodata; + + /* + * If it's anything except Copy Data, exit COPY_OUT mode and let + * caller read status with PQgetResult(). The normal case is that + * it's Copy Done, but we let parseInput read that. + */ + if (id != 'd') + { + conn->asyncStatus = PGASYNC_BUSY; + return -1; + } + + /* + * Drop zero-length messages (shouldn't happen anyway). Otherwise + * pass the data back to the caller. + */ + msgLength -= 4; + if (msgLength > 0) + { + *buffer = (char *) malloc(msgLength + 1); + if (*buffer == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return -2; + } + memcpy(*buffer, &conn->inBuffer[conn->inCursor], msgLength); + (*buffer)[msgLength] = '\0'; /* Add terminating null */ + + /* Mark message consumed */ + conn->inStart = conn->inCursor + msgLength; + + return msgLength; + } + + /* Empty, so drop it and loop around for another */ + conn->inStart = conn->inCursor; + continue; + + nodata: + /* Don't block if async read requested */ + if (async) + return 0; + /* Need to load more data */ + if (pqWait(TRUE, FALSE, conn) || + pqReadData(conn) < 0) + return -2; + } +} /* * PQgetline - gets a newline-terminated string from the backend. @@ -1108,7 +1300,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid, continue; break; case 'Z': /* backend is ready for new query */ - if (pqGetc(&conn->xact_status, conn)) + if (getReadyForQuery(conn)) continue; /* consume the message and exit */ conn->inStart += 5 + msgLength; |