"PGRES_PIPELINE_ABORTED"
};
+/* We return this if we're unable to make a PGresult at all */
+static const PGresult OOM_result = {
+ .resultStatus = PGRES_FATAL_ERROR,
+ .client_encoding = PG_SQL_ASCII,
+ .errMsg = "out of memory\n",
+};
+
/*
* static state needed by PQescapeString and PQescapeBytea; initialize to
* values that result in backward-compatible behavior
* returns a newly allocated, initialized PGresult with given status.
* If conn is not NULL and status indicates an error, the conn's
* errorMessage is copied. Also, any PGEvents are copied from the conn.
+ *
+ * Note: the logic to copy the conn's errorMessage is now vestigial;
+ * no internal caller uses it. However, that behavior is documented for
+ * outside callers, so we'd better keep it.
*/
PGresult *
PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
/* non-error cases */
break;
default:
- pqSetResultError(result, &conn->errorMessage);
+ /* we intentionally do not use or modify errorReported here */
+ pqSetResultError(result, &conn->errorMessage, 0);
break;
}
{
int i;
+ /* Fail if argument is NULL or OOM_result */
+ if (!res || (const PGresult *) res == &OOM_result)
+ return false;
+
/* If attrs already exist, they cannot be overwritten. */
- if (!res || res->numAttributes > 0)
+ if (res->numAttributes > 0)
return false;
/* ignore no-op request */
PGresAttValue *attval;
const char *errmsg = NULL;
- /* Note that this check also protects us against null "res" */
+ /* Fail if argument is NULL or OOM_result */
+ if (!res || (const PGresult *) res == &OOM_result)
+ return false;
+
+ /* Invalid field_num? */
if (!check_field_number(res, field_num))
return false;
void *
PQresultAlloc(PGresult *res, size_t nBytes)
{
+ /* Fail if argument is NULL or OOM_result */
+ if (!res || (const PGresult *) res == &OOM_result)
+ return NULL;
+
return pqResultAlloc(res, nBytes, true);
}
/*
* pqSetResultError -
* assign a new error message to a PGresult
+ *
+ * Copy text from errorMessage buffer beginning at given offset
+ * (it's caller's responsibility that offset is valid)
*/
void
-pqSetResultError(PGresult *res, PQExpBuffer errorMessage)
+pqSetResultError(PGresult *res, PQExpBuffer errorMessage, int offset)
{
char *msg;
* at a constant "out of memory" string.
*/
if (!PQExpBufferBroken(errorMessage))
- msg = pqResultStrdup(res, errorMessage->data);
+ msg = pqResultStrdup(res, errorMessage->data + offset);
else
msg = NULL;
if (msg)
PGresult_data *block;
int i;
+ /* As a convenience, do nothing for a NULL pointer */
if (!res)
return;
+ /* Also, do nothing if the argument is OOM_result */
+ if ((const PGresult *) res == &OOM_result)
+ return;
+ /* Close down any events we may have */
for (i = 0; i < res->nEvents; i++)
{
/* only send DESTROY to successfully-initialized event procs */
if (conn->result)
PQclear(conn->result);
conn->result = NULL;
+ conn->error_result = false;
if (conn->next_result)
PQclear(conn->next_result);
conn->next_result = 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.
+ * pqSaveErrorResult -
+ * remember that we have an error condition
+ *
+ * In much of libpq, reporting an error just requires appending text to
+ * conn->errorMessage and returning a failure code to one's caller.
+ * Where returning a failure code is impractical, instead call this
+ * function to remember that an error needs to be reported.
+ *
+ * (It might seem that appending text to conn->errorMessage should be
+ * sufficient, but we can't rely on that working under out-of-memory
+ * conditions. The OOM hazard is also why we don't try to make a new
+ * PGresult right here.)
*/
void
pqSaveErrorResult(PGconn *conn)
{
+ /* Drop any pending result ... */
pqClearAsyncResult(conn);
- conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ /* ... and set flag to remember to make an error result later */
+ conn->error_result = true;
}
/*
+ * pqSaveWriteError -
+ * report a write failure
+ *
* As above, after appending conn->write_err_msg to whatever other error we
* have. This is used when we've detected a write failure and have exhausted
* our chances of reporting something else instead.
}
/*
- * This subroutine prepares an async result object for return to the caller.
+ * pqPrepareAsyncResult -
+ * prepare the current 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.
+ * result storage, and update our notion of how much error text has been
+ * returned to the application.
*/
PGresult *
pqPrepareAsyncResult(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;
- if (!res)
- res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ if (res)
+ {
+ /*
+ * If the pre-existing result is an ERROR (presumably something
+ * received from the server), assume that it represents whatever is in
+ * conn->errorMessage, and advance errorReported.
+ */
+ if (res->resultStatus == PGRES_FATAL_ERROR)
+ conn->errorReported = conn->errorMessage.len;
+ }
+ else
+ {
+ /*
+ * We get here after internal-to-libpq errors. We should probably
+ * always have error_result = true, but if we don't, gin up some error
+ * text.
+ */
+ if (!conn->error_result)
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("no error text available\n"));
+
+ /* Paranoia: be sure errorReported offset is sane */
+ if (conn->errorReported < 0 ||
+ conn->errorReported >= conn->errorMessage.len)
+ conn->errorReported = 0;
+
+ /*
+ * Make a PGresult struct for the error. We temporarily lie about the
+ * result status, so that PQmakeEmptyPGresult doesn't uselessly copy
+ * all of conn->errorMessage.
+ */
+ res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
+ if (res)
+ {
+ /*
+ * Report whatever new error text we have, and advance
+ * errorReported.
+ */
+ res->resultStatus = PGRES_FATAL_ERROR;
+ pqSetResultError(res, &conn->errorMessage, conn->errorReported);
+ conn->errorReported = conn->errorMessage.len;
+ }
+ else
+ {
+ /*
+ * Ouch, not enough memory for a PGresult. Fortunately, we have a
+ * card up our sleeve: we can use the static OOM_result. Casting
+ * away const here is a bit ugly, but it seems best to declare
+ * OOM_result as const, in hopes it will be allocated in read-only
+ * storage.
+ */
+ res = unconstify(PGresult *, &OOM_result);
+
+ /*
+ * Don't advance errorReported. Perhaps we'll be able to report
+ * the text later.
+ */
+ }
+ }
/*
* Replace conn->result with next_result, if any. In the normal case
* it was before we created the current single-row result.
*/
conn->result = conn->next_result;
+ conn->error_result = false; /* next_result is never an error */
conn->next_result = NULL;
return res;
*/
if (conn->asyncStatus == PGASYNC_IDLE)
{
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
pqPipelineProcessQueue(conn);
}
break;
return false;
/*
- * If this is the beginning of a query cycle, reset the error buffer.
+ * If this is the beginning of a query cycle, reset the error state.
*/
if (newQuery)
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
/* Don't try to send if we know there's no live connection. */
if (conn->status != CONNECTION_OK)
/* reset single-row processing mode */
conn->singleRowMode = false;
-
}
+
/* ready to send command message */
return true;
}
(conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE &&
conn->cmd_queue_head->queryclass != PGQUERY_EXTENDED))
return 0;
- if (conn->result)
+ if (conn->result || conn->error_result)
return 0;
/* OK, set flag */
pqWait(true, false, conn) ||
pqReadData(conn) < 0)
{
- /*
- * conn->errorMessage has been set by pqWait or pqReadData. We
- * want to append it to any already-received error message.
- */
+ /* Report the error saved by pqWait or pqReadData */
pqSaveErrorResult(conn);
conn->asyncStatus = PGASYNC_IDLE;
return pqPrepareAsyncResult(conn);
* is the start of the results of the next query, clear any
* prior error message.
*/
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
pqPipelineProcessQueue(conn);
}
break;
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("unexpected asyncStatus: %d\n"),
(int) conn->asyncStatus);
- res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+ pqSaveErrorResult(conn);
+ conn->asyncStatus = PGASYNC_IDLE; /* try to restore valid state */
+ res = pqPrepareAsyncResult(conn);
break;
}
}
/*
- * Since this is the beginning of a query cycle, reset the error buffer.
+ * Since this is the beginning of a query cycle, reset the error state.
*/
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
/*
* Silently discard any prior query result that application didn't eat.
return NULL;
/*
- * Since this is the beginning of a query cycle, reset the error buffer.
+ * Since this is the beginning of a query cycle, reset the error state.
*/
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
{
}
if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
- conn->result != NULL)
+ conn->result || conn->error_result)
{
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("connection in wrong state\n"));
* behavior. this is ok because either they are making a transition _from_
* or _to_ blocking mode, either way we can block them.
*
- * Clear errorMessage in case pqFlush adds to it.
+ * Clear error state in case pqFlush adds to it.
*/
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
/* if we are going from blocking to non-blocking flush here */
if (pqFlush(conn))
return 0;
}
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
return PQescapeStringInternal(conn, to, from, length, error,
conn->client_encoding,
if (!conn)
return NULL;
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
/* Scan the string for characters that must be escaped. */
for (s = str; (s - str) < len && *s != '\0'; ++s)
if (!conn)
return NULL;
- resetPQExpBuffer(&conn->errorMessage);
+ pqClearConnErrorState(conn);
return PQescapeByteaInternal(conn, from, from_length, to_length,
conn->std_strings,
return;
break;
case 'T': /* Row Description */
- if (conn->result != NULL &&
- conn->result->resultStatus == PGRES_FATAL_ERROR)
+ if (conn->error_result ||
+ (conn->result != NULL &&
+ conn->result->resultStatus == PGRES_FATAL_ERROR))
{
/*
* We've already choked for some reason. Just discard
if (getAnotherTuple(conn, msgLength))
return;
}
- else if (conn->result != NULL &&
- conn->result->resultStatus == PGRES_FATAL_ERROR)
+ else if (conn->error_result ||
+ (conn->result != NULL &&
+ conn->result->resultStatus == PGRES_FATAL_ERROR))
{
/*
* We've already choked for some reason. Just discard
*/
if (isError)
{
- if (res)
- pqSetResultError(res, &workBuf);
pqClearAsyncResult(conn); /* redundant, but be safe */
- conn->result = res;
+ if (res)
+ {
+ pqSetResultError(res, &workBuf, 0);
+ conn->result = res;
+ }
+ else
+ {
+ /* Fall back to using the internal-error processing paths */
+ conn->error_result = true;
+ }
+
if (PQExpBufferDataBroken(workBuf))
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("out of memory\n"));
continue;
/* consume the message and exit */
conn->inStart += 5 + msgLength;
- /* if we saved a result object (probably an error), use it */
- if (conn->result)
- return pqPrepareAsyncResult(conn);
- return PQmakeEmptyPGresult(conn, status);
+
+ /*
+ * If we already have a result object (probably an error), use
+ * that. Otherwise, if we saw a function result message,
+ * report COMMAND_OK. Otherwise, the backend violated the
+ * protocol, so complain.
+ */
+ if (!(conn->result || conn->error_result))
+ {
+ if (status == PGRES_COMMAND_OK)
+ {
+ conn->result = PQmakeEmptyPGresult(conn, status);
+ if (!conn->result)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ pqSaveErrorResult(conn);
+ }
+ }
+ else
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("protocol error: no function result\n"));
+ pqSaveErrorResult(conn);
+ }
+ }
+ return pqPrepareAsyncResult(conn);
case 'S': /* parameter status */
if (getParameterStatus(conn))
continue;
PGdataValue *rowBuf; /* array for passing values to rowProcessor */
int rowBufLen; /* number of entries allocated in rowBuf */
- /* Status for asynchronous result construction */
+ /*
+ * Status for asynchronous result construction. If result isn't NULL, it
+ * is a result being constructed or ready to return. If result is NULL
+ * and error_result is true, then we need to return a PGRES_FATAL_ERROR
+ * result, but haven't yet constructed it; text for the error has been
+ * appended to conn->errorMessage. (Delaying construction simplifies
+ * dealing with out-of-memory cases.) If next_result isn't NULL, it is a
+ * PGresult that will replace "result" after we return that one.
+ */
PGresult *result; /* result being constructed */
+ bool error_result; /* do we need to make an ERROR result? */
PGresult *next_result; /* next result (used in single-row mode) */
/* Assorted state for SASL, SSL, GSS, etc */
* Buffer for current error message. This is cleared at the start of any
* connection attempt or query cycle; after that, all code should append
* messages to it, never overwrite.
+ *
+ * In some situations we might report an error more than once in a query
+ * cycle. If so, errorMessage accumulates text from all the errors, and
+ * errorReported tracks how much we've already reported, so that the
+ * individual error PGresult objects don't contain duplicative text.
*/
PQExpBufferData errorMessage; /* expansible string */
+ int errorReported; /* # bytes of string already reported */
/* Buffer for receiving various parts of messages */
PQExpBufferData workBuffer; /* expansible string */
/* === in fe-exec.c === */
-extern void pqSetResultError(PGresult *res, PQExpBuffer errorMessage);
+extern void pqSetResultError(PGresult *res, PQExpBuffer errorMessage, int offset);
extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
extern char *pqResultStrdup(PGresult *res, const char *str);
extern void pqClearAsyncResult(PGconn *conn);
/* === miscellaneous macros === */
+/*
+ * Reset the conn's error-reporting state.
+ */
+#define pqClearConnErrorState(conn) \
+ (resetPQExpBuffer(&(conn)->errorMessage), \
+ (conn)->errorReported = 0)
+
/*
* this is so that we can check if a connection is non-blocking internally
* without the overhead of a function call