1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-21 00:42:43 +03:00

Update libpq to make new features of FE/BE protocol available to

client applications.  Some editorial work on libpq.sgml, too.
This commit is contained in:
Tom Lane
2003-06-21 21:51:35 +00:00
parent b8d601e735
commit efc3a25bb0
11 changed files with 2523 additions and 831 deletions

View File

@@ -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;