aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-protocol3.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/libpq/fe-protocol3.c')
-rw-r--r--src/interfaces/libpq/fe-protocol3.c340
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;