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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user