diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2003-06-21 21:51:35 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2003-06-21 21:51:35 +0000 |
commit | efc3a25bb02ada63158fe7006673518b005261ba (patch) | |
tree | 0f64a370bfcca9f8fa223904179c9e4ba1755779 /src | |
parent | b8d601e7359bdafbe9fede9bdfb9037b19a50272 (diff) | |
download | postgresql-efc3a25bb02ada63158fe7006673518b005261ba.tar.gz postgresql-efc3a25bb02ada63158fe7006673518b005261ba.zip |
Update libpq to make new features of FE/BE protocol available to
client applications. Some editorial work on libpq.sgml, too.
Diffstat (limited to 'src')
-rw-r--r-- | src/interfaces/libpq/blibpqdll.def | 29 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 106 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 782 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-misc.c | 36 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol2.c | 194 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 340 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 106 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 84 | ||||
-rw-r--r-- | src/interfaces/libpq/libpqdll.def | 15 |
9 files changed, 1324 insertions, 368 deletions
diff --git a/src/interfaces/libpq/blibpqdll.def b/src/interfaces/libpq/blibpqdll.def index 8445a29b1b4..ff85e9cdfc8 100644 --- a/src/interfaces/libpq/blibpqdll.def +++ b/src/interfaces/libpq/blibpqdll.def @@ -97,6 +97,20 @@ EXPORTS _pg_utf_mblen @ 93 _PQunescapeBytea @ 94 _PQfreemem @ 95 + _PQtransactionStatus @ 96 + _PQparameterStatus @ 97 + _PQprotocolVersion @ 98 + _PQsetErrorVerbosity @ 99 + _PQsetNoticeReceiver @ 100 + _PQexecParams @ 101 + _PQsendQueryParams @ 102 + _PQputCopyData @ 103 + _PQputCopyEnd @ 104 + _PQgetCopyData @ 105 + _PQresultErrorField @ 106 + _PQftable @ 107 + _PQftablecol @ 108 + _PQfformat @ 109 ; Aliases for MS compatible names PQconnectdb = _PQconnectdb @@ -194,4 +208,17 @@ EXPORTS pg_utf_mblen = _pg_utf_mblen PQunescapeBytea = _PQunescapeBytea PQfreemem = _PQfreemem - + PQtransactionStatus = _PQtransactionStatus + PQparameterStatus = _PQparameterStatus + PQprotocolVersion = _PQprotocolVersion + PQsetErrorVerbosity = _PQsetErrorVerbosity + PQsetNoticeReceiver = _PQsetNoticeReceiver + PQexecParams = _PQexecParams + PQsendQueryParams = _PQsendQueryParams + PQputCopyData = _PQputCopyData + PQputCopyEnd = _PQputCopyEnd + PQgetCopyData = _PQgetCopyData + PQresultErrorField = _PQresultErrorField + PQftable = _PQftable + PQftablecol = _PQftablecol + PQfformat = _PQfformat diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a58caa3f3aa..7008d99601d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.249 2003/06/20 04:09:12 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.250 2003/06/21 21:51:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -176,6 +176,7 @@ static PQconninfoOption *conninfo_parse(const char *conninfo, PQExpBuffer errorMessage); static char *conninfo_getval(PQconninfoOption *connOptions, const char *keyword); +static void defaultNoticeReceiver(void *arg, const PGresult *res); static void defaultNoticeProcessor(void *arg, const char *message); static int parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage); @@ -1804,11 +1805,14 @@ makeEmptyPGconn(void) /* Zero all pointers and booleans */ MemSet((char *) conn, 0, sizeof(PGconn)); - conn->noticeHook = defaultNoticeProcessor; + conn->noticeHooks.noticeRec = defaultNoticeReceiver; + conn->noticeHooks.noticeProc = defaultNoticeProcessor; conn->status = CONNECTION_BAD; conn->asyncStatus = PGASYNC_IDLE; + conn->xactStatus = PQTRANS_IDLE; conn->setenv_state = SETENV_STATE_IDLE; conn->client_encoding = PG_SQL_ASCII; + conn->verbosity = PQERRORS_DEFAULT; conn->notifyList = DLNewList(); conn->sock = -1; #ifdef USE_SSL @@ -1850,7 +1854,6 @@ makeEmptyPGconn(void) /* * freePGconn * - free the PGconn data structure - * */ static void freePGconn(PGconn *conn) @@ -1899,9 +1902,9 @@ freePGconn(PGconn *conn) } /* - closePGconn - - properly close a connection to the backend -*/ + * closePGconn + * - properly close a connection to the backend + */ static void closePGconn(PGconn *conn) { @@ -2662,6 +2665,41 @@ PQstatus(const PGconn *conn) return conn->status; } +PGTransactionStatusType +PQtransactionStatus(const PGconn *conn) +{ + if (!conn || conn->status != CONNECTION_OK) + return PQTRANS_UNKNOWN; + if (conn->asyncStatus != PGASYNC_IDLE) + return PQTRANS_ACTIVE; + return conn->xactStatus; +} + +const char * +PQparameterStatus(const PGconn *conn, const char *paramName) +{ + const pgParameterStatus *pstatus; + + if (!conn || !paramName) + return NULL; + for (pstatus = conn->pstatus; pstatus != NULL; pstatus = pstatus->next) + { + if (strcmp(pstatus->name, paramName) == 0) + return pstatus->value; + } + return NULL; +} + +int +PQprotocolVersion(const PGconn *conn) +{ + if (!conn) + return 0; + if (conn->status == CONNECTION_BAD) + return 0; + return PG_PROTOCOL_MAJOR(conn->pversion); +} + char * PQerrorMessage(const PGconn *conn) { @@ -2731,11 +2769,22 @@ PQsetClientEncoding(PGconn *conn, const char *encoding) return (status); } +PGVerbosity +PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity) +{ + PGVerbosity old; + + if (!conn) + return PQERRORS_DEFAULT; + old = conn->verbosity; + conn->verbosity = verbosity; + return old; +} + void PQtrace(PGconn *conn, FILE *debug_port) { - if (conn == NULL || - conn->status == CONNECTION_BAD) + if (conn == NULL) return; PQuntrace(conn); conn->Pfdebug = debug_port; @@ -2744,7 +2793,6 @@ PQtrace(PGconn *conn, FILE *debug_port) void PQuntrace(PGconn *conn) { - /* note: better allow untrace even when connection bad */ if (conn == NULL) return; if (conn->Pfdebug) @@ -2754,6 +2802,23 @@ PQuntrace(PGconn *conn) } } +PQnoticeReceiver +PQsetNoticeReceiver(PGconn *conn, PQnoticeReceiver proc, void *arg) +{ + PQnoticeReceiver old; + + if (conn == NULL) + return NULL; + + old = conn->noticeHooks.noticeRec; + if (proc) + { + conn->noticeHooks.noticeRec = proc; + conn->noticeHooks.noticeRecArg = arg; + } + return old; +} + PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg) { @@ -2762,22 +2827,35 @@ PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg) if (conn == NULL) return NULL; - old = conn->noticeHook; + old = conn->noticeHooks.noticeProc; if (proc) { - conn->noticeHook = proc; - conn->noticeArg = arg; + conn->noticeHooks.noticeProc = proc; + conn->noticeHooks.noticeProcArg = arg; } return old; } /* - * The default notice/error message processor just prints the + * The default notice message receiver just gets the standard notice text + * and sends it to the notice processor. This two-level setup exists + * mostly for backwards compatibility; perhaps we should deprecate use of + * PQsetNoticeProcessor? + */ +static void +defaultNoticeReceiver(void *arg, const PGresult *res) +{ + (void) arg; /* not used */ + (*res->noticeHooks.noticeProc) (res->noticeHooks.noticeProcArg, + PQresultErrorMessage(res)); +} + +/* + * The default notice message processor just prints the * message on stderr. Applications can override this if they * want the messages to go elsewhere (a window, for example). * Note that simply discarding notices is probably a bad idea. */ - static void defaultNoticeProcessor(void *arg, const char *message) { diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 45db359bde1..4c96bbd3868 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.138 2003/06/12 01:17:19 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.139 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,7 +43,10 @@ char *const pgresStatus[] = { +static bool PQsendQueryStart(PGconn *conn); static void parseInput(PGconn *conn); +static bool PQexecStart(PGconn *conn); +static PGresult *PQexecFinish(PGconn *conn); /* ---------------- @@ -137,16 +140,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) result->cmdStatus[0] = '\0'; result->binary = 0; result->errMsg = NULL; - result->errSeverity = NULL; - result->errCode = NULL; - result->errPrimary = NULL; - result->errDetail = NULL; - result->errHint = NULL; - result->errPosition = NULL; - result->errContext = NULL; - result->errFilename = NULL; - result->errLineno = NULL; - result->errFuncname = NULL; + result->errFields = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; @@ -155,8 +149,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) if (conn) { /* copy connection data we might need for operations on PGresult */ - result->noticeHook = conn->noticeHook; - result->noticeArg = conn->noticeArg; + result->noticeHooks = conn->noticeHooks; result->client_encoding = conn->client_encoding; /* consider copying conn's errorMessage */ @@ -177,9 +170,11 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) else { /* defaults... */ - result->noticeHook = NULL; - result->noticeArg = NULL; - result->client_encoding = 0; /* should be SQL_ASCII */ + result->noticeHooks.noticeRec = NULL; + result->noticeHooks.noticeRecArg = NULL; + result->noticeHooks.noticeProc = NULL; + result->noticeHooks.noticeProcArg = NULL; + result->client_encoding = PG_SQL_ASCII; } return result; @@ -445,6 +440,41 @@ pqPrepareAsyncResult(PGconn *conn) } /* + * pqInternalNotice - helper routine for internally-generated notices + * + * The supplied text is taken as primary message (ie., it should not include + * a trailing newline, and should not be more than one line). + */ +void +pqInternalNotice(const PGNoticeHooks *hooks, const char *msgtext) +{ + PGresult *res; + + if (hooks->noticeRec == NULL) + return; /* nobody home? */ + + /* Make a PGresult to pass to the notice receiver */ + res = PQmakeEmptyPGresult(NULL, PGRES_NONFATAL_ERROR); + res->noticeHooks = *hooks; + /* + * Set up fields of notice. + */ + pqSaveMessageField(res, 'M', msgtext); + pqSaveMessageField(res, 'S', libpq_gettext("NOTICE")); + /* XXX should provide a SQLSTATE too? */ + /* + * Result text is always just the primary message + newline. + */ + res->errMsg = (char *) pqResultAlloc(res, strlen(msgtext) + 2, FALSE); + sprintf(res->errMsg, "%s\n", msgtext); + /* + * Pass to receiver, then free it. + */ + (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); + PQclear(res); +} + +/* * pqAddTuple * add a row pointer to the PGresult structure, growing it if necessary * Returns TRUE if OK, FALSE if not enough memory to add the row @@ -484,6 +514,25 @@ pqAddTuple(PGresult *res, PGresAttValue *tup) return TRUE; } +/* + * pqSaveMessageField - save one field of an error or notice message + */ +void +pqSaveMessageField(PGresult *res, char code, const char *value) +{ + PGMessageField *pfield; + + pfield = (PGMessageField *) + pqResultAlloc(res, + sizeof(PGMessageField) + strlen(value), + TRUE); + if (!pfield) + return; /* out of memory? */ + pfield->code = code; + strcpy(pfield->contents, value); + pfield->next = res->errFields; + res->errFields = pfield; +} /* * pqSaveParameterStatus - remember parameter status sent by backend @@ -543,26 +592,6 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) StrNCpy(conn->sversion, value, sizeof(conn->sversion)); } -/* - * pqGetParameterStatus - fetch parameter value, if available - * - * Returns NULL if info not available - * - * XXX this probably should be exported for client use - */ -const char * -pqGetParameterStatus(PGconn *conn, const char *name) -{ - pgParameterStatus *pstatus; - - for (pstatus = conn->pstatus; pstatus != NULL; pstatus = pstatus->next) - { - if (strcmp(pstatus->name, name) == 0) - return pstatus->value; - } - return NULL; -} - /* * PQsendQuery @@ -574,12 +603,9 @@ pqGetParameterStatus(PGconn *conn, const char *name) int PQsendQuery(PGconn *conn, const char *query) { - if (!conn) + if (!PQsendQueryStart(conn)) return 0; - /* clear the error string */ - resetPQExpBuffer(&conn->errorMessage); - if (!query) { printfPQExpBuffer(&conn->errorMessage, @@ -587,25 +613,6 @@ PQsendQuery(PGconn *conn, const char *query) return 0; } - /* Don't try to send if we know there's no live connection. */ - if (conn->status != CONNECTION_OK) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("no connection to the server\n")); - return 0; - } - /* Can't send while already busy, either. */ - if (conn->asyncStatus != PGASYNC_IDLE) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("another command is already in progress\n")); - return 0; - } - - /* initialize async result-accumulation state */ - conn->result = NULL; - conn->curTuple = NULL; - /* construct the outgoing Query message */ if (pqPutMsgStart('Q', false, conn) < 0 || pqPuts(query, conn) < 0 || @@ -617,7 +624,7 @@ PQsendQuery(PGconn *conn, const char *query) /* * Give the data a push. In nonblock mode, don't complain if we're - * unable to send it all; PQconsumeInput() will do any additional flushing + * unable to send it all; PQgetResult() will do any additional flushing * needed. */ if (pqFlush(conn) < 0) @@ -632,6 +639,194 @@ PQsendQuery(PGconn *conn, const char *query) } /* + * PQsendQueryParams + * Like PQsendQuery, but use 3.0 protocol so we can pass parameters + */ +int +PQsendQueryParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat) +{ + int i; + + if (!PQsendQueryStart(conn)) + return 0; + + /* This isn't gonna work on a 2.0 server */ + if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("function requires at least 3.0 protocol\n")); + return 0; + } + + if (!command) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("command string is a null pointer\n")); + return 0; + } + + /* + * We will send Parse, Bind, Describe Portal, Execute, Sync, using + * unnamed statement and portal. + */ + + /* construct the Parse message */ + if (pqPutMsgStart('P', false, conn) < 0 || + pqPuts("", conn) < 0 || + pqPuts(command, conn) < 0) + goto sendFailed; + if (nParams > 0 && paramTypes) + { + if (pqPutInt(nParams, 2, conn) < 0) + goto sendFailed; + for (i = 0; i < nParams; i++) + { + if (pqPutInt(paramTypes[i], 4, conn) < 0) + goto sendFailed; + } + } + else + { + if (pqPutInt(0, 2, conn) < 0) + goto sendFailed; + } + if (pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Bind message */ + if (pqPutMsgStart('B', false, conn) < 0 || + pqPuts("", conn) < 0 || + pqPuts("", conn) < 0) + goto sendFailed; + if (nParams > 0 && paramFormats) + { + if (pqPutInt(nParams, 2, conn) < 0) + goto sendFailed; + for (i = 0; i < nParams; i++) + { + if (pqPutInt(paramFormats[i], 2, conn) < 0) + goto sendFailed; + } + } + else + { + if (pqPutInt(0, 2, conn) < 0) + goto sendFailed; + } + if (pqPutInt(nParams, 2, conn) < 0) + goto sendFailed; + for (i = 0; i < nParams; i++) + { + if (paramValues && paramValues[i]) + { + int nbytes; + + if (paramFormats && paramFormats[i] != 0) + { + /* binary parameter */ + nbytes = paramLengths[i]; + } + else + { + /* text parameter, do not use paramLengths */ + nbytes = strlen(paramValues[i]); + } + if (pqPutInt(nbytes, 4, conn) < 0 || + pqPutnchar(paramValues[i], nbytes, conn) < 0) + goto sendFailed; + } + else + { + /* take the param as NULL */ + if (pqPutInt(-1, 4, conn) < 0) + goto sendFailed; + } + } + if (pqPutInt(1, 2, conn) < 0 || + pqPutInt(resultFormat, 2, conn)) + goto sendFailed; + if (pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Describe Portal message */ + if (pqPutMsgStart('D', false, conn) < 0 || + pqPutc('P', conn) < 0 || + pqPuts("", conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Execute message */ + if (pqPutMsgStart('E', false, conn) < 0 || + pqPuts("", conn) < 0 || + pqPutInt(0, 4, conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Sync message */ + if (pqPutMsgStart('S', false, conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* + * Give the data a push. In nonblock mode, don't complain if we're + * unable to send it all; PQgetResult() will do any additional flushing + * needed. + */ + if (pqFlush(conn) < 0) + goto sendFailed; + + /* OK, it's launched! */ + conn->asyncStatus = PGASYNC_BUSY; + return 1; + +sendFailed: + pqHandleSendFailure(conn); + return 0; +} + +/* + * Common startup code for PQsendQuery and PQsendQueryParams + */ +static bool +PQsendQueryStart(PGconn *conn) +{ + if (!conn) + return false; + + /* clear the error string */ + resetPQExpBuffer(&conn->errorMessage); + + /* Don't try to send if we know there's no live connection. */ + if (conn->status != CONNECTION_OK) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("no connection to the server\n")); + return false; + } + /* Can't send while already busy, either. */ + if (conn->asyncStatus != PGASYNC_IDLE) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("another command is already in progress\n")); + return false; + } + + /* initialize async result-accumulation state */ + conn->result = NULL; + conn->curTuple = NULL; + + /* ready to send command message */ + return true; +} + +/* * pqHandleSendFailure: try to clean up after failure to send command. * * Primarily, what we want to accomplish here is to process an async @@ -746,8 +941,24 @@ PQgetResult(PGconn *conn) /* If not ready to return something, block until we are. */ while (conn->asyncStatus == PGASYNC_BUSY) { + int flushResult; + + /* + * If data remains unsent, send it. Else we might be waiting + * for the result of a command the backend hasn't even got yet. + */ + while ((flushResult = pqFlush(conn)) > 0) + { + if (pqWait(FALSE, TRUE, conn)) + { + flushResult = -1; + break; + } + } + /* Wait for some more data, and load it. */ - if (pqWait(TRUE, FALSE, conn) || + if (flushResult || + pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) { /* @@ -758,6 +969,7 @@ PQgetResult(PGconn *conn) conn->asyncStatus = PGASYNC_IDLE; return pqPrepareAsyncResult(conn); } + /* Parse it. */ parseInput(conn); } @@ -774,10 +986,16 @@ PQgetResult(PGconn *conn) conn->asyncStatus = PGASYNC_BUSY; break; case PGASYNC_COPY_IN: - res = PQmakeEmptyPGresult(conn, PGRES_COPY_IN); + if (conn->result && conn->result->resultStatus == PGRES_COPY_IN) + res = pqPrepareAsyncResult(conn); + else + res = PQmakeEmptyPGresult(conn, PGRES_COPY_IN); break; case PGASYNC_COPY_OUT: - res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT); + if (conn->result && conn->result->resultStatus == PGRES_COPY_OUT) + res = pqPrepareAsyncResult(conn); + else + res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT); break; default: printfPQExpBuffer(&conn->errorMessage, @@ -806,18 +1024,46 @@ PQgetResult(PGconn *conn) PGresult * PQexec(PGconn *conn, const char *query) { - PGresult *result; - PGresult *lastResult; - bool savedblocking; + if (!PQexecStart(conn)) + return NULL; + if (!PQsendQuery(conn, query)) + return NULL; + return PQexecFinish(conn); +} - /* - * we assume anyone calling PQexec wants blocking behaviour, we force - * the blocking status of the connection to blocking for the duration - * of this function and restore it on return - */ - savedblocking = pqIsnonblocking(conn); - if (PQsetnonblocking(conn, FALSE) == -1) +/* + * PQexecParams + * Like PQexec, but use 3.0 protocol so we can pass parameters + */ +PGresult * +PQexecParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat) +{ + if (!PQexecStart(conn)) + return NULL; + if (!PQsendQueryParams(conn, command, + nParams, paramTypes, paramValues, paramLengths, + paramFormats, resultFormat)) return NULL; + return PQexecFinish(conn); +} + +/* + * Common code for PQexec and PQexecParams: prepare to send command + */ +static bool +PQexecStart(PGconn *conn) +{ + PGresult *result; + + if (!conn) + return false; /* * Silently discard any prior query result that application didn't @@ -832,15 +1078,23 @@ PQexec(PGconn *conn, const char *query) PQclear(result); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("COPY state must be terminated first\n")); - /* restore blocking status */ - goto errout; + return false; } PQclear(result); } - /* OK to send the message */ - if (!PQsendQuery(conn, query)) - goto errout; /* restore blocking status */ + /* OK to send a command */ + return true; +} + +/* + * Common code for PQexec and PQexecParams: wait for command result + */ +static PGresult * +PQexecFinish(PGconn *conn) +{ + PGresult *result; + PGresult *lastResult; /* * For backwards compatibility, return the last result if there are @@ -848,7 +1102,7 @@ PQexec(PGconn *conn, const char *query) * error result. * * We have to stop if we see copy in/out, however. We will resume parsing - * when application calls PQendcopy. + * after application performs the data transfer. */ lastResult = NULL; while ((result = PQgetResult(conn)) != NULL) @@ -874,14 +1128,7 @@ PQexec(PGconn *conn, const char *query) break; } - if (PQsetnonblocking(conn, savedblocking) == -1) - return NULL; return lastResult; - -errout: - if (PQsetnonblocking(conn, savedblocking) == -1) - return NULL; - return NULL; } /* @@ -894,7 +1141,6 @@ errout: * * the CALLER is responsible for FREE'ing the structure returned */ - PGnotify * PQnotifies(PGconn *conn) { @@ -917,6 +1163,156 @@ PQnotifies(PGconn *conn) } /* + * PQputCopyData - send some data to the backend during COPY IN + * + * Returns 1 if successful, 0 if data could not be sent (only possible + * in nonblock mode), or -1 if an error occurs. + */ +int +PQputCopyData(PGconn *conn, const char *buffer, int nbytes) +{ + if (!conn) + return -1; + if (conn->asyncStatus != PGASYNC_COPY_IN) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("no COPY in progress\n")); + return -1; + } + if (nbytes > 0) + { + /* + * Try to flush any previously sent data in preference to growing + * the output buffer. If we can't enlarge the buffer enough to hold + * the data, return 0 in the nonblock case, else hard error. + * (For simplicity, always assume 5 bytes of overhead even in + * protocol 2.0 case.) + */ + if ((conn->outBufSize - conn->outCount - 5) < nbytes) + { + if (pqFlush(conn) < 0) + return -1; + if (pqCheckOutBufferSpace(conn->outCount + 5 + nbytes, conn)) + return pqIsnonblocking(conn) ? 0 : -1; + } + /* Send the data (too simple to delegate to fe-protocol files) */ + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + { + if (pqPutMsgStart('d', false, conn) < 0 || + pqPutnchar(buffer, nbytes, conn) < 0 || + pqPutMsgEnd(conn) < 0) + return -1; + } + else + { + if (pqPutMsgStart(0, false, conn) < 0 || + pqPutnchar(buffer, nbytes, conn) < 0 || + pqPutMsgEnd(conn) < 0) + return -1; + } + } + return 1; +} + +/* + * PQputCopyEnd - send EOF indication to the backend during COPY IN + * + * After calling this, use PQgetResult() to check command completion status. + * + * Returns 1 if successful, 0 if data could not be sent (only possible + * in nonblock mode), or -1 if an error occurs. + */ +int +PQputCopyEnd(PGconn *conn, const char *errormsg) +{ + if (!conn) + return -1; + if (conn->asyncStatus != PGASYNC_COPY_IN) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("no COPY in progress\n")); + return -1; + } + /* + * Send the COPY END indicator. This is simple enough that we don't + * bother delegating it to the fe-protocol files. + */ + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + { + if (errormsg) + { + /* Send COPY FAIL */ + if (pqPutMsgStart('f', false, conn) < 0 || + pqPuts(errormsg, conn) < 0 || + pqPutMsgEnd(conn) < 0) + return -1; + } + else + { + /* Send COPY DONE */ + if (pqPutMsgStart('c', false, conn) < 0 || + pqPutMsgEnd(conn) < 0) + return -1; + } + } + else + { + if (errormsg) + { + /* Ooops, no way to do this in 2.0 */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("function requires at least 3.0 protocol\n")); + return -1; + } + else + { + /* Send old-style end-of-data marker */ + if (pqPutMsgStart(0, false, conn) < 0 || + pqPuts("\\.\n", conn) < 0 || + pqPutMsgEnd(conn) < 0) + return -1; + } + } + + /* Return to active duty */ + conn->asyncStatus = PGASYNC_BUSY; + resetPQExpBuffer(&conn->errorMessage); + + /* Try to flush data */ + if (pqFlush(conn) < 0) + return -1; + + return 1; +} + +/* + * 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 +PQgetCopyData(PGconn *conn, char **buffer, int async) +{ + *buffer = NULL; /* for all failure cases */ + if (!conn) + return -2; + if (conn->asyncStatus != PGASYNC_COPY_OUT) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("no COPY in progress\n")); + return -2; + } + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + return pqGetCopyData3(conn, buffer, async); + else + return pqGetCopyData2(conn, buffer, async); +} + +/* * PQgetline - gets a newline-terminated string from the backend. * * Chiefly here so that applications can use "COPY <rel> to stdout" @@ -1002,11 +1398,12 @@ PQgetlineAsync(PGconn *conn, char *buffer, int bufsize) } /* - * PQputline -- sends a string to the backend. + * PQputline -- sends a string to the backend during COPY IN. * Returns 0 if OK, EOF if not. * - * This exists to support "COPY <rel> from stdin". The backend will ignore - * the string if not doing COPY. + * This is deprecated primarily because the return convention doesn't allow + * caller to tell the difference between a hard error and a nonblock-mode + * send failure. */ int PQputline(PGconn *conn, const char *s) @@ -1021,27 +1418,10 @@ PQputline(PGconn *conn, const char *s) int PQputnbytes(PGconn *conn, const char *buffer, int nbytes) { - if (!conn || conn->sock < 0) + if (PQputCopyData(conn, buffer, nbytes) > 0) + return 0; + else return EOF; - if (nbytes > 0) - { - /* This is too simple to bother with separate subroutines */ - if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) - { - if (pqPutMsgStart('d', false, conn) < 0 || - pqPutnchar(buffer, nbytes, conn) < 0 || - pqPutMsgEnd(conn) < 0) - return EOF; - } - else - { - if (pqPutMsgStart(0, false, conn) < 0 || - pqPutnchar(buffer, nbytes, conn) < 0 || - pqPutMsgEnd(conn) < 0) - return EOF; - } - } - return 0; } /* @@ -1049,6 +1429,11 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes) * After completing the data transfer portion of a copy in/out, * the application must call this routine to finish the command protocol. * + * When using 3.0 protocol this is deprecated; it's cleaner to use PQgetResult + * to get the transfer status. Note however that when using 2.0 protocol, + * recovering from a copy failure often requires a PQreset. PQendcopy will + * take care of that, PQgetResult won't. + * * RETURNS: * 0 on success * 1 on failure @@ -1133,7 +1518,7 @@ ExecStatusType PQresultStatus(const PGresult *res) { if (!res) - return PGRES_NONFATAL_ERROR; + return PGRES_FATAL_ERROR; return res->resultStatus; } @@ -1153,6 +1538,21 @@ PQresultErrorMessage(const PGresult *res) return res->errMsg; } +char * +PQresultErrorField(const PGresult *res, int fieldcode) +{ + PGMessageField *pfield; + + if (!res) + return NULL; + for (pfield = res->errFields; pfield != NULL; pfield = pfield->next) + { + if (pfield->code == fieldcode) + return pfield->contents; + } + return NULL; +} + int PQntuples(const PGresult *res) { @@ -1191,13 +1591,10 @@ check_field_number(const PGresult *res, int field_num) return FALSE; /* no way to display error message... */ if (field_num < 0 || field_num >= res->numAttributes) { - if (res->noticeHook) - { - snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("column number %d is out of range 0..%d\n"), - field_num, res->numAttributes - 1); - PGDONOTICE(res, noticeBuf); - } + snprintf(noticeBuf, sizeof(noticeBuf), + libpq_gettext("column number %d is out of range 0..%d"), + field_num, res->numAttributes - 1); + PGDONOTICE(res, noticeBuf); return FALSE; } return TRUE; @@ -1213,32 +1610,26 @@ check_tuple_field_number(const PGresult *res, return FALSE; /* no way to display error message... */ if (tup_num < 0 || tup_num >= res->ntups) { - if (res->noticeHook) - { - snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("row number %d is out of range 0..%d\n"), - tup_num, res->ntups - 1); - PGDONOTICE(res, noticeBuf); - } + snprintf(noticeBuf, sizeof(noticeBuf), + libpq_gettext("row number %d is out of range 0..%d"), + tup_num, res->ntups - 1); + PGDONOTICE(res, noticeBuf); return FALSE; } if (field_num < 0 || field_num >= res->numAttributes) { - if (res->noticeHook) - { - snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("column number %d is out of range 0..%d\n"), - field_num, res->numAttributes - 1); - PGDONOTICE(res, noticeBuf); - } + snprintf(noticeBuf, sizeof(noticeBuf), + libpq_gettext("column number %d is out of range 0..%d"), + field_num, res->numAttributes - 1); + PGDONOTICE(res, noticeBuf); return FALSE; } return TRUE; } /* - returns NULL if the field_num is invalid -*/ + * returns NULL if the field_num is invalid + */ char * PQfname(const PGresult *res, int field_num) { @@ -1251,8 +1642,8 @@ PQfname(const PGresult *res, int field_num) } /* - returns -1 on a bad field name -*/ + * returns -1 on a bad field name + */ int PQfnumber(const PGresult *res, const char *field_name) { @@ -1291,6 +1682,39 @@ PQfnumber(const PGresult *res, const char *field_name) } Oid +PQftable(const PGresult *res, int field_num) +{ + if (!check_field_number(res, field_num)) + return InvalidOid; + if (res->attDescs) + return res->attDescs[field_num].tableid; + else + return InvalidOid; +} + +int +PQftablecol(const PGresult *res, int field_num) +{ + if (!check_field_number(res, field_num)) + return 0; + if (res->attDescs) + return res->attDescs[field_num].columnid; + else + return 0; +} + +int +PQfformat(const PGresult *res, int field_num) +{ + if (!check_field_number(res, field_num)) + return 0; + if (res->attDescs) + return res->attDescs[field_num].format; + else + return 0; +} + +Oid PQftype(const PGresult *res, int field_num) { if (!check_field_number(res, field_num)) @@ -1332,10 +1756,10 @@ PQcmdStatus(PGresult *res) } /* - PQoidStatus - - if the last command was an INSERT, return the oid string - if not, return "" -*/ + * PQoidStatus - + * if the last command was an INSERT, return the oid string + * if not, return "" + */ char * PQoidStatus(const PGresult *res) { @@ -1360,10 +1784,10 @@ PQoidStatus(const PGresult *res) } /* - PQoidValue - - a perhaps preferable form of the above which just returns - an Oid type -*/ + * PQoidValue - + * a perhaps preferable form of the above which just returns + * an Oid type + */ Oid PQoidValue(const PGresult *res) { @@ -1388,13 +1812,13 @@ PQoidValue(const PGresult *res) /* - PQcmdTuples - - If the last command was an INSERT/UPDATE/DELETE/MOVE/FETCH, return a - string containing the number of inserted/affected tuples. If not, - return "". - - XXX: this should probably return an int -*/ + * PQcmdTuples - + * If the last command was an INSERT/UPDATE/DELETE/MOVE/FETCH, return a + * string containing the number of inserted/affected tuples. If not, + * return "". + * + * XXX: this should probably return an int + */ char * PQcmdTuples(PGresult *res) { @@ -1426,13 +1850,10 @@ PQcmdTuples(PGresult *res) if (*p == 0) { - if (res->noticeHook) - { - snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("could not interpret result from server: %s\n"), - res->cmdStatus); - PGDONOTICE(res, noticeBuf); - } + snprintf(noticeBuf, sizeof(noticeBuf), + libpq_gettext("could not interpret result from server: %s"), + res->cmdStatus); + PGDONOTICE(res, noticeBuf); return ""; } @@ -1440,15 +1861,9 @@ PQcmdTuples(PGresult *res) } /* - PQgetvalue: - return the value of field 'field_num' of row 'tup_num' - - If res is binary, then the value returned is NOT a null-terminated - ASCII string, but the binary representation in the server's native - format. - - if res is not binary, a null-terminated ASCII string is returned. -*/ + * PQgetvalue: + * return the value of field 'field_num' of row 'tup_num' + */ char * PQgetvalue(const PGresult *res, int tup_num, int field_num) { @@ -1458,11 +1873,8 @@ PQgetvalue(const PGresult *res, int tup_num, int field_num) } /* PQgetlength: - returns the length of a field value in bytes. If res is binary, - i.e. a result of a binary portal, then the length returned does - NOT include the size field of the varlena. (The data returned - by PQgetvalue doesn't either.) -*/ + * returns the actual length of a field value in bytes. + */ int PQgetlength(const PGresult *res, int tup_num, int field_num) { @@ -1475,8 +1887,8 @@ PQgetlength(const PGresult *res, int tup_num, int field_num) } /* PQgetisnull: - returns the null status of a field value. -*/ + * returns the null status of a field value. + */ int PQgetisnull(const PGresult *res, int tup_num, int field_num) { @@ -1489,16 +1901,17 @@ PQgetisnull(const PGresult *res, int tup_num, int field_num) } /* PQsetnonblocking: - sets the PGconn's database connection non-blocking if the arg is TRUE - or makes it non-blocking if the arg is FALSE, this will not protect - you from PQexec(), you'll only be safe when using the non-blocking - API - Needs to be called only on a connected database connection. -*/ - + * sets the PGconn's database connection non-blocking if the arg is TRUE + * or makes it non-blocking if the arg is FALSE, this will not protect + * you from PQexec(), you'll only be safe when using the non-blocking API. + * Needs to be called only on a connected database connection. + */ int PQsetnonblocking(PGconn *conn, int arg) { + if (!conn || conn->status == CONNECTION_BAD) + return -1; + arg = (arg == TRUE) ? 1 : 0; /* early out if the socket is already in the state requested */ if (arg == conn->nonblocking) @@ -1520,9 +1933,10 @@ PQsetnonblocking(PGconn *conn, int arg) return (0); } -/* return the blocking status of the database connection, TRUE == nonblocking, - FALSE == blocking -*/ +/* + * return the blocking status of the database connection + * TRUE == nonblocking, FALSE == blocking + */ int PQisnonblocking(const PGconn *conn) { diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index 3856145a4ef..f10c3112d7e 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -23,7 +23,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.96 2003/06/14 17:49:54 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.97 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -197,7 +197,7 @@ pqPutnchar(const char *s, size_t len, PGconn *conn) } /* - * pgGetInt + * pqGetInt * read a 2 or 4 byte integer and convert from network byte order * to local byte order */ @@ -226,7 +226,7 @@ pqGetInt(int *result, size_t bytes, PGconn *conn) break; default: snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("integer of size %lu not supported by pqGetInt\n"), + libpq_gettext("integer of size %lu not supported by pqGetInt"), (unsigned long) bytes); PGDONOTICE(conn, noticeBuf); return EOF; @@ -239,7 +239,7 @@ pqGetInt(int *result, size_t bytes, PGconn *conn) } /* - * pgPutInt + * pqPutInt * write an integer of 2 or 4 bytes, converting from host byte order * to network byte order. */ @@ -264,7 +264,7 @@ pqPutInt(int value, size_t bytes, PGconn *conn) break; default: snprintf(noticeBuf, sizeof(noticeBuf), - libpq_gettext("integer of size %lu not supported by pqPutInt\n"), + libpq_gettext("integer of size %lu not supported by pqPutInt"), (unsigned long) bytes); PGDONOTICE(conn, noticeBuf); return EOF; @@ -282,7 +282,7 @@ pqPutInt(int value, size_t bytes, PGconn *conn) * * Returns 0 on success, EOF if failed to enlarge buffer */ -static int +int pqCheckOutBufferSpace(int bytes_needed, PGconn *conn) { int newsize = conn->outBufSize; @@ -748,7 +748,7 @@ pqSendSome(PGconn *conn, int len) if (sent < 0) { /* - * Anything except EAGAIN or EWOULDBLOCK is trouble. If it's + * Anything except EAGAIN/EWOULDBLOCK/EINTR is trouble. If it's * EPIPE or ECONNRESET, assume we've lost the backend * connection permanently. */ @@ -804,25 +804,17 @@ pqSendSome(PGconn *conn, int len) if (len > 0) { - /* We didn't send it all, wait till we can send more */ - /* - * if the socket is in non-blocking mode we may need to abort - * here and return 1 to indicate that data is still pending. + * We didn't send it all, wait till we can send more. + * + * If the connection is in non-blocking mode we don't wait, + * but return 1 to indicate that data is still pending. */ -#ifdef USE_SSL - /* can't do anything for our SSL users yet */ - if (conn->ssl == NULL) + if (pqIsnonblocking(conn)) { -#endif - if (pqIsnonblocking(conn)) - { - result = 1; - break; - } -#ifdef USE_SSL + result = 1; + break; } -#endif if (pqWait(FALSE, TRUE, conn)) { diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c index 2a7b6b43bf5..6b909334079 100644 --- a/src/interfaces/libpq/fe-protocol2.c +++ b/src/interfaces/libpq/fe-protocol2.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol2.c,v 1.1 2003/06/08 17:43:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol2.c,v 1.2 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,7 @@ static int getRowDescriptions(PGconn *conn); static int getAnotherTuple(PGconn *conn, bool binary); static int pqGetErrorNotice2(PGconn *conn, bool isError); +static void checkXactStatus(PGconn *conn, const char *cmdTag); static int getNotify(PGconn *conn); @@ -312,7 +313,7 @@ pqSetenvPoll(PGconn *conn) val); else { - val = pqGetParameterStatus(conn, "server_encoding"); + val = PQparameterStatus(conn, "server_encoding"); if (val && *val) pqSaveParameterStatus(conn, "client_encoding", val); @@ -424,7 +425,7 @@ pqParseInput2(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; good idea?? */ @@ -447,6 +448,7 @@ pqParseInput2(PGconn *conn) PGRES_COMMAND_OK); strncpy(conn->result->cmdStatus, conn->workBuffer.data, CMDSTATUS_LEN); + checkXactStatus(conn, conn->workBuffer.data); conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ @@ -464,7 +466,7 @@ pqParseInput2(PGconn *conn) if (id != '\0') { snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("unexpected character %c following empty query response (\"I\" message)\n"), + libpq_gettext("unexpected character %c following empty query response (\"I\" message)"), id); PGDONOTICE(conn, noticeWorkspace); } @@ -521,7 +523,7 @@ pqParseInput2(PGconn *conn) else { snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n")); + libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)")); PGDONOTICE(conn, noticeWorkspace); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; @@ -538,7 +540,7 @@ pqParseInput2(PGconn *conn) else { snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n")); + libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)")); PGDONOTICE(conn, noticeWorkspace); /* Discard the unexpected message; good idea?? */ conn->inStart = conn->inEnd; @@ -628,6 +630,9 @@ getRowDescriptions(PGconn *conn) result->attDescs[i].name = pqResultStrdup(result, conn->workBuffer.data); + result->attDescs[i].tableid = 0; + result->attDescs[i].columnid = 0; + result->attDescs[i].format = 0; result->attDescs[i].typid = typid; result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; @@ -674,6 +679,15 @@ getAnotherTuple(PGconn *conn, bool binary) if (conn->curTuple == NULL) goto outOfMemory; MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue)); + /* + * If it's binary, fix the column format indicators. We assume + * the backend will consistently send either B or D, not a mix. + */ + if (binary) + { + for (i = 0; i < nfields; i++) + result->attDescs[i].format = 1; + } } tup = conn->curTuple; @@ -778,6 +792,8 @@ pqGetErrorNotice2(PGconn *conn, bool isError) { PGresult *res; PQExpBufferData workBuf; + char *startp; + char *splitp; /* * Since the message might be pretty long, we create a temporary @@ -801,18 +817,62 @@ pqGetErrorNotice2(PGconn *conn, bool isError) res->errMsg = pqResultStrdup(res, workBuf.data); /* + * Break the message into fields. We can't do very much here, but we + * can split the severity code off, and remove trailing newlines. Also, + * we use the heuristic that the primary message extends only to the + * first newline --- anything after that is detail message. (In some + * cases it'd be better classed as hint, but we can hardly be expected + * to guess that here.) + */ + while (workBuf.len > 0 && workBuf.data[workBuf.len-1] == '\n') + workBuf.data[--workBuf.len] = '\0'; + splitp = strstr(workBuf.data, ": "); + if (splitp) + { + /* what comes before the colon is severity */ + *splitp = '\0'; + pqSaveMessageField(res, 'S', workBuf.data); + startp = splitp + 3; + } + else + { + /* can't find a colon? oh well... */ + startp = workBuf.data; + } + splitp = strchr(startp, '\n'); + if (splitp) + { + /* what comes before the newline is primary message */ + *splitp++ = '\0'; + pqSaveMessageField(res, 'M', startp); + /* the rest is detail; strip any leading whitespace */ + while (*splitp && isspace((unsigned char) *splitp)) + splitp++; + pqSaveMessageField(res, 'D', splitp); + } + else + { + /* single-line message, so all primary */ + pqSaveMessageField(res, 'M', startp); + } + + /* * Either save error as current async result, or just emit the notice. + * Also, if it's an error and we were in a transaction block, assume + * the server has now gone to error-in-transaction state. */ if (isError) { pqClearAsyncResult(conn); conn->result = res; resetPQExpBuffer(&conn->errorMessage); - appendPQExpBufferStr(&conn->errorMessage, workBuf.data); + appendPQExpBufferStr(&conn->errorMessage, res->errMsg); + if (conn->xactStatus == PQTRANS_INTRANS) + conn->xactStatus = PQTRANS_INERROR; } else { - PGDONOTICE(conn, workBuf.data); + (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); PQclear(res); } @@ -820,6 +880,37 @@ pqGetErrorNotice2(PGconn *conn, bool isError) return 0; } +/* + * checkXactStatus - attempt to track transaction-block status of server + * + * This is called each time we receive a command-complete message. By + * watching for messages from BEGIN/COMMIT/ROLLBACK commands, we can do + * a passable job of tracking the server's xact status. BUT: this does + * not work at all on 7.3 servers with AUTOCOMMIT OFF. (Man, was that + * feature ever a mistake.) Caveat user. + * + * The tags known here are all those used as far back as 7.0; is it worth + * adding those from even-older servers? + */ +static void +checkXactStatus(PGconn *conn, const char *cmdTag) +{ + if (strcmp(cmdTag, "BEGIN") == 0) + conn->xactStatus = PQTRANS_INTRANS; + else if (strcmp(cmdTag, "COMMIT") == 0) + conn->xactStatus = PQTRANS_IDLE; + else if (strcmp(cmdTag, "ROLLBACK") == 0) + conn->xactStatus = PQTRANS_IDLE; + else if (strcmp(cmdTag, "START TRANSACTION") == 0) /* 7.3 only */ + conn->xactStatus = PQTRANS_INTRANS; + /* + * Normally we get into INERROR state by detecting an Error message. + * However, if we see one of these tags then we know for sure the + * server is in abort state ... + */ + else if (strcmp(cmdTag, "*ABORT STATE*") == 0) /* pre-7.3 only */ + conn->xactStatus = PQTRANS_INERROR; +} /* * Attempt to read a Notify response message. @@ -832,6 +923,7 @@ static int getNotify(PGconn *conn) { int be_pid; + int nmlen; PGnotify *newNotify; if (pqGetInt(&be_pid, 4, conn)) @@ -844,12 +936,14 @@ getNotify(PGconn *conn) * 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(conn->workBuffer.data); + newNotify = (PGnotify *) malloc(sizeof(PGnotify) + nmlen + 1); if (newNotify) { newNotify->relname = (char *) newNotify + sizeof(PGnotify); strcpy(newNotify->relname, conn->workBuffer.data); + /* fake up an empty-string extra field */ + newNotify->extra = newNotify->relname + nmlen; newNotify->be_pid = be_pid; DLAddTail(conn->notifyList, DLNewElem(newNotify)); } @@ -859,6 +953,84 @@ getNotify(PGconn *conn) /* + * 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 +pqGetCopyData2(PGconn *conn, char **buffer, int async) +{ + bool found; + int msgLength; + + for (;;) + { + /* + * Do we have a complete line of data? + */ + conn->inCursor = conn->inStart; + found = false; + while (conn->inCursor < conn->inEnd) + { + char c = conn->inBuffer[conn->inCursor++]; + + if (c == '\n') + { + found = true; + break; + } + } + if (!found) + goto nodata; + msgLength = conn->inCursor - conn->inStart; + + /* + * If it's the end-of-data marker, consume it, exit COPY_OUT mode, + * and let caller read status with PQgetResult(). + */ + if (msgLength == 3 && + strncmp(&conn->inBuffer[conn->inStart], "\\.\n", 3) == 0) + { + conn->inStart = conn->inCursor; + conn->asyncStatus = PGASYNC_BUSY; + return -1; + } + + /* + * Pass the line back to the caller. + */ + *buffer = (char *) malloc(msgLength + 1); + if (*buffer == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return -2; + } + memcpy(*buffer, &conn->inBuffer[conn->inStart], msgLength); + (*buffer)[msgLength] = '\0'; /* Add terminating null */ + + /* Mark message consumed */ + conn->inStart = conn->inCursor; + + return msgLength; + + 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. * * See fe-exec.c for documentation. @@ -1020,7 +1192,7 @@ pqEndcopy2(PGconn *conn) if (conn->errorMessage.len > 0) PGDONOTICE(conn, conn->errorMessage.data); - PGDONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection\n")); + PGDONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection")); /* * Users doing non-blocking connections need to handle the reset 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; diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index da61b770906..d8ff52d6b0e 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-fe.h,v 1.93 2003/06/08 17:43:00 tgl Exp $ + * $Id: libpq-fe.h,v 1.94 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -88,6 +88,22 @@ typedef enum PGRES_FATAL_ERROR /* query failed */ } ExecStatusType; +typedef enum +{ + PQTRANS_IDLE, /* connection idle */ + PQTRANS_ACTIVE, /* command in progress */ + PQTRANS_INTRANS, /* idle, within transaction block */ + PQTRANS_INERROR, /* idle, within failed transaction */ + PQTRANS_UNKNOWN /* cannot determine status */ +} PGTransactionStatusType; + +typedef enum +{ + PQERRORS_TERSE, /* single-line error messages */ + PQERRORS_DEFAULT, /* recommended style */ + PQERRORS_VERBOSE /* all the facts, ma'am */ +} PGVerbosity; + /* PGconn encapsulates a connection to the backend. * The contents of this struct are not supposed to be known to applications. */ @@ -108,12 +124,13 @@ typedef struct pg_result PGresult; */ typedef struct pgNotify { - char *relname; /* name of relation containing data */ - int be_pid; /* process id of backend */ + char *relname; /* notification condition name */ + int be_pid; /* process ID of server process */ + char *extra; /* notification parameter */ } PGnotify; -/* PQnoticeProcessor is the function type for the notice-message callback. - */ +/* Function types for notice-handling callbacks */ +typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res); typedef void (*PQnoticeProcessor) (void *arg, const char *message); /* Print options for PQprint() */ @@ -227,6 +244,10 @@ extern char *PQport(const PGconn *conn); extern char *PQtty(const PGconn *conn); extern char *PQoptions(const PGconn *conn); extern ConnStatusType PQstatus(const PGconn *conn); +extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn); +extern const char *PQparameterStatus(const PGconn *conn, + const char *paramName); +extern int PQprotocolVersion(const PGconn *conn); extern char *PQerrorMessage(const PGconn *conn); extern int PQsocket(const PGconn *conn); extern int PQbackendPID(const PGconn *conn); @@ -238,42 +259,58 @@ extern int PQsetClientEncoding(PGconn *conn, const char *encoding); extern SSL *PQgetssl(PGconn *conn); #endif +/* Set verbosity for PQerrorMessage and PQresultErrorMessage */ +extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity); /* Enable/disable tracing */ extern void PQtrace(PGconn *conn, FILE *debug_port); extern void PQuntrace(PGconn *conn); -/* Override default notice processor */ +/* Override default notice handling routines */ +extern PQnoticeReceiver PQsetNoticeReceiver(PGconn *conn, + PQnoticeReceiver proc, + void *arg); extern PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg); /* === in fe-exec.c === */ -/* Quoting strings before inclusion in queries. */ -extern size_t PQescapeString(char *to, const char *from, size_t length); -extern unsigned char *PQescapeBytea(const unsigned char *bintext, size_t binlen, - size_t *bytealen); -extern unsigned char *PQunescapeBytea(const unsigned char *strtext, - size_t *retbuflen); -extern void PQfreemem(void *ptr); - - /* Simple synchronous query */ extern PGresult *PQexec(PGconn *conn, const char *query); -extern PGnotify *PQnotifies(PGconn *conn); -/* Exists for backward compatibility. bjm 2003-03-24 */ -#define PQfreeNotify(ptr) PQfreemem(ptr) +extern PGresult *PQexecParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); /* Interface for multiple-result or asynchronous queries */ extern int PQsendQuery(PGconn *conn, const char *query); +extern int PQsendQueryParams(PGconn *conn, + const char *command, + int nParams, + const Oid *paramTypes, + const char * const *paramValues, + const int *paramLengths, + const int *paramFormats, + int resultFormat); extern PGresult *PQgetResult(PGconn *conn); /* Routines for managing an asynchronous query */ extern int PQisBusy(PGconn *conn); extern int PQconsumeInput(PGconn *conn); +/* LISTEN/NOTIFY support */ +extern PGnotify *PQnotifies(PGconn *conn); + /* Routines for copy in/out */ +extern int PQputCopyData(PGconn *conn, const char *buffer, int nbytes); +extern int PQputCopyEnd(PGconn *conn, const char *errormsg); +extern int PQgetCopyData(PGconn *conn, char **buffer, int async); +/* Deprecated routines for copy in/out */ extern int PQgetline(PGconn *conn, char *string, int length); extern int PQputline(PGconn *conn, const char *string); extern int PQgetlineAsync(PGconn *conn, char *buffer, int bufsize); @@ -303,11 +340,15 @@ extern PGresult *PQfn(PGconn *conn, extern ExecStatusType PQresultStatus(const PGresult *res); extern char *PQresStatus(ExecStatusType status); extern char *PQresultErrorMessage(const PGresult *res); +extern char *PQresultErrorField(const PGresult *res, int fieldcode); extern int PQntuples(const PGresult *res); extern int PQnfields(const PGresult *res); extern int PQbinaryTuples(const PGresult *res); extern char *PQfname(const PGresult *res, int field_num); extern int PQfnumber(const PGresult *res, const char *field_name); +extern Oid PQftable(const PGresult *res, int field_num); +extern int PQftablecol(const PGresult *res, int field_num); +extern int PQfformat(const PGresult *res, int field_num); extern Oid PQftype(const PGresult *res, int field_num); extern int PQfsize(const PGresult *res, int field_num); extern int PQfmod(const PGresult *res, int field_num); @@ -322,6 +363,12 @@ extern int PQgetisnull(const PGresult *res, int tup_num, int field_num); /* Delete a PGresult */ extern void PQclear(PGresult *res); +/* For freeing other alloc'd results, such as PGnotify structs */ +extern void PQfreemem(void *ptr); + +/* Exists for backward compatibility. bjm 2003-03-24 */ +#define PQfreeNotify(ptr) PQfreemem(ptr) + /* * Make an empty PGresult with given status (some apps find this * useful). If conn is not NULL and status indicates an error, the @@ -329,26 +376,33 @@ extern void PQclear(PGresult *res); */ extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); + +/* Quoting strings before inclusion in queries. */ +extern size_t PQescapeString(char *to, const char *from, size_t length); +extern unsigned char *PQescapeBytea(const unsigned char *bintext, size_t binlen, + size_t *bytealen); +extern unsigned char *PQunescapeBytea(const unsigned char *strtext, + size_t *retbuflen); + + + /* === in fe-print.c === */ -extern void -PQprint(FILE *fout, /* output stream */ - const PGresult *res, - const PQprintOpt *ps); /* option structure */ +extern void PQprint(FILE *fout, /* output stream */ + const PGresult *res, + const PQprintOpt *ps); /* option structure */ /* * really old printing routines */ -extern void -PQdisplayTuples(const PGresult *res, +extern void PQdisplayTuples(const PGresult *res, FILE *fp, /* where to send the output */ int fillAlign, /* pad the fields with spaces */ const char *fieldSep, /* field separator */ int printHeader, /* display headers? */ int quiet); -extern void -PQprintTuples(const PGresult *res, +extern void PQprintTuples(const PGresult *res, FILE *fout, /* output stream */ int printAttName, /* print attribute names */ int terseOutput, /* delimiter bars */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 019683ac265..f651320e1cb 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.74 2003/06/14 17:49:54 momjian Exp $ + * $Id: libpq-int.h,v 1.75 2003/06/21 21:51:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,6 +20,9 @@ #ifndef LIBPQ_INT_H #define LIBPQ_INT_H +/* We assume libpq-fe.h has already been included. */ +#include "postgres_fe.h" + #include <time.h> #include <sys/types.h> #ifndef WIN32 @@ -28,13 +31,10 @@ #if defined(WIN32) && (!defined(ssize_t)) -typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast +typedef int ssize_t; /* ssize_t doesn't exist in VC (at least * not VC6) */ #endif -/* We assume libpq-fe.h has already been included. */ -#include "postgres_fe.h" - /* include stuff common to fe and be */ #include "getaddrinfo.h" #include "libpq/pqcomm.h" @@ -78,7 +78,10 @@ union pgresult_data typedef struct pgresAttDesc { - char *name; /* type name */ + char *name; /* column name */ + Oid tableid; /* source table, if known */ + int columnid; /* source column, if known */ + int format; /* format code for value (text/binary) */ Oid typid; /* type id */ int typlen; /* type size */ int atttypmod; /* type-specific modifier info */ @@ -91,7 +94,7 @@ typedef struct pgresAttDesc * * The value pointer always points to a null-terminated area; we add a * null (zero) byte after whatever the backend sends us. This is only - * particularly useful for text tuples ... with a binary value, the + * particularly useful for text values ... with a binary value, the * value might have embedded nulls, so the application can't use C string * operators on it. But we add a null anyway for consistency. * Note that the value itself does not contain a length word. @@ -111,6 +114,23 @@ typedef struct pgresAttValue * byte */ } PGresAttValue; +/* Typedef for message-field list entries */ +typedef struct pgMessageField +{ + struct pgMessageField *next; /* list link */ + char code; /* field code */ + char contents[1]; /* field value (VARIABLE LENGTH) */ +} PGMessageField; + +/* Fields needed for notice handling */ +typedef struct +{ + PQnoticeReceiver noticeRec; /* notice message receiver */ + void *noticeRecArg; + PQnoticeProcessor noticeProc; /* notice message processor */ + void *noticeProcArg; +} PGNoticeHooks; + struct pg_result { int ntups; @@ -118,10 +138,10 @@ struct pg_result PGresAttDesc *attDescs; PGresAttValue **tuples; /* each PGresTuple is an array of * PGresAttValue's */ - int tupArrSize; /* size of tuples array allocated */ + int tupArrSize; /* allocated size of tuples array */ ExecStatusType resultStatus; char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the - * last query */ + * query */ int binary; /* binary tuple values if binary == 1, * otherwise text */ @@ -129,35 +149,23 @@ struct pg_result * These fields are copied from the originating PGconn, so that * operations on the PGresult don't have to reference the PGconn. */ - PQnoticeProcessor noticeHook; /* notice/error message processor */ - void *noticeArg; + PGNoticeHooks noticeHooks; int client_encoding; /* encoding id */ /* * Error information (all NULL if not an error result). errMsg is the * "overall" error message returned by PQresultErrorMessage. If we - * got a field-ized error from the server then the additional fields - * may be set. + * have per-field info then it is stored in a linked list. */ char *errMsg; /* error message, or NULL if no error */ - - char *errSeverity; /* severity code */ - char *errCode; /* SQLSTATE code */ - char *errPrimary; /* primary message text */ - char *errDetail; /* detail text */ - char *errHint; /* hint text */ - char *errPosition; /* cursor position */ - char *errContext; /* location information */ - char *errFilename; /* source-code file name */ - char *errLineno; /* source-code line number */ - char *errFuncname; /* source-code function name */ + PGMessageField *errFields; /* message broken into fields */ /* All NULL attributes in the query result point to this null string */ char null_field[1]; /* - * Space management information. Note that attDescs and errMsg, if - * not null, point into allocated blocks. But tuples points to a + * Space management information. Note that attDescs and error stuff, + * if not null, point into allocated blocks. But tuples points to a * separately malloc'd block, so that we can realloc it. */ PGresult_data *curBlock; /* most recently allocated block */ @@ -245,18 +253,18 @@ struct pg_conn /* Optional file to write trace info to */ FILE *Pfdebug; - /* Callback procedure for notice/error message processing */ - PQnoticeProcessor noticeHook; - void *noticeArg; + /* Callback procedures for notice message processing */ + PGNoticeHooks noticeHooks; /* Status indicators */ ConnStatusType status; PGAsyncStatusType asyncStatus; - char xact_status; /* status flag from latest ReadyForQuery */ - char copy_is_binary; /* 1 = copy binary, 0 = copy text */ - int copy_already_done; /* # bytes already returned in COPY OUT */ + PGTransactionStatusType xactStatus; + /* note: xactStatus never changes to ACTIVE */ int nonblocking; /* whether this connection is using a * blocking socket to the backend or not */ + char copy_is_binary; /* 1 = copy binary, 0 = copy text */ + int copy_already_done; /* # bytes already returned in COPY OUT */ Dllist *notifyList; /* Notify msgs not yet handed to * application */ @@ -281,6 +289,7 @@ struct pg_conn char cryptSalt[2]; /* password salt received from backend */ pgParameterStatus *pstatus; /* ParameterStatus data */ int client_encoding; /* encoding id */ + PGVerbosity verbosity; /* error/notice message verbosity */ PGlobjfuncs *lobjfuncs; /* private state for large-object access * fns */ @@ -351,10 +360,12 @@ extern char *pqResultStrdup(PGresult *res, const char *str); extern void pqClearAsyncResult(PGconn *conn); extern void pqSaveErrorResult(PGconn *conn); extern PGresult *pqPrepareAsyncResult(PGconn *conn); +extern void pqInternalNotice(const PGNoticeHooks *hooks, const char *msgtext); extern int pqAddTuple(PGresult *res, PGresAttValue *tup); +extern void pqSaveMessageField(PGresult *res, char code, + const char *value); extern void pqSaveParameterStatus(PGconn *conn, const char *name, const char *value); -extern const char *pqGetParameterStatus(PGconn *conn, const char *name); extern void pqHandleSendFailure(PGconn *conn); /* === in fe-protocol2.c === */ @@ -364,6 +375,7 @@ extern PostgresPollingStatusType pqSetenvPoll(PGconn *conn); extern char *pqBuildStartupPacket2(PGconn *conn, int *packetlen, const PQEnvironmentOption *options); extern void pqParseInput2(PGconn *conn); +extern int pqGetCopyData2(PGconn *conn, char **buffer, int async); extern int pqGetline2(PGconn *conn, char *s, int maxlen); extern int pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize); extern int pqEndcopy2(PGconn *conn); @@ -378,6 +390,7 @@ extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen, const PQEnvironmentOption *options); extern void pqParseInput3(PGconn *conn); extern int pqGetErrorNotice3(PGconn *conn, bool isError); +extern int pqGetCopyData3(PGconn *conn, char **buffer, int async); extern int pqGetline3(PGconn *conn, char *s, int maxlen); extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize); extern int pqEndcopy3(PGconn *conn); @@ -393,6 +406,7 @@ extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid, * for Get, EOF merely means the buffer is exhausted, not that there is * necessarily any error. */ +extern int pqCheckOutBufferSpace(int bytes_needed, PGconn *conn); extern int pqCheckInBufferSpace(int bytes_needed, PGconn *conn); extern int pqGetc(char *result, PGconn *conn); extern int pqPutc(char c, PGconn *conn); @@ -423,10 +437,10 @@ extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len); /* Note: PGDONOTICE macro will work if applied to either PGconn or PGresult */ #define PGDONOTICE(conn,message) \ - ((*(conn)->noticeHook) ((conn)->noticeArg, (message))) + pqInternalNotice(&(conn)->noticeHooks, (message)) /* - * this is so that we can check is a connection is non-blocking internally + * this is so that we can check if a connection is non-blocking internally * without the overhead of a function call */ #define pqIsnonblocking(conn) ((conn)->nonblocking) diff --git a/src/interfaces/libpq/libpqdll.def b/src/interfaces/libpq/libpqdll.def index f8432fc713a..8ff902f5218 100644 --- a/src/interfaces/libpq/libpqdll.def +++ b/src/interfaces/libpq/libpqdll.def @@ -97,4 +97,17 @@ EXPORTS pg_utf_mblen @ 93 PQunescapeBytea @ 94 PQfreemem @ 95 - + PQtransactionStatus @ 96 + PQparameterStatus @ 97 + PQprotocolVersion @ 98 + PQsetErrorVerbosity @ 99 + PQsetNoticeReceiver @ 100 + PQexecParams @ 101 + PQsendQueryParams @ 102 + PQputCopyData @ 103 + PQputCopyEnd @ 104 + PQgetCopyData @ 105 + PQresultErrorField @ 106 + PQftable @ 107 + PQftablecol @ 108 + PQfformat @ 109 |