📄 fe-protocol3.c
字号:
/*------------------------------------------------------------------------- * * fe-protocol3.c * functions that are specific to frontend/backend protocol version 3 * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.9.2.1 2003/12/28 17:44:05 tgl Exp $ * *------------------------------------------------------------------------- */#include "postgres_fe.h"#include <errno.h>#include <ctype.h>#include <fcntl.h>#include "libpq-fe.h"#include "libpq-int.h"#include "mb/pg_wchar.h"#ifdef WIN32#include "win32.h"#else#include <unistd.h>#include <netinet/in.h>#ifdef HAVE_NETINET_TCP_H#include <netinet/tcp.h>#endif#include <arpa/inet.h>#endif/* * This macro lists the backend message types that could be "long" (more * than a couple of kilobytes). */#define VALID_LONG_MESSAGE_TYPE(id) \ ((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \ (id) == 'E' || (id) == 'N' || (id) == 'A')static void handleSyncLoss(PGconn *conn, char id, int msgLength);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);/* * parseInput: if appropriate, parse input data from backend * until input is exhausted or a stopping state is reached. * Note that this function will NOT attempt to read more data from the backend. */voidpqParseInput3(PGconn *conn){ char id; int msgLength; int avail; /* * Loop to parse successive complete messages available in the buffer. */ for (;;) { /* * Try to read a message. First get the type code and length. * Return if not enough data. */ conn->inCursor = conn->inStart; if (pqGetc(&id, conn)) return; if (pqGetInt(&msgLength, 4, conn)) return; /* * Try to validate message type/length here. A length less than 4 * is definitely broken. Large lengths should only be believed * for a few message types. */ if (msgLength < 4) { handleSyncLoss(conn, id, msgLength); return; } if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id)) { handleSyncLoss(conn, id, msgLength); return; } /* * Can't process if message body isn't all here yet. */ msgLength -= 4; avail = conn->inEnd - conn->inCursor; if (avail < msgLength) { /* * Before returning, enlarge the input buffer if needed to * hold the whole message. This is better than leaving it to * pqReadData because we can avoid multiple cycles of * realloc() when the message is large; also, we can implement * a reasonable recovery strategy if we are unable to make the * buffer big enough. */ if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn)) { /* * XXX add some better recovery code... plan is to skip * over the message using its length, then report an * error. For the moment, just treat this like loss of * sync (which indeed it might be!) */ handleSyncLoss(conn, id, msgLength); } return; } /* * NOTIFY and NOTICE messages can happen in any state; always * process them right away. * * Most other messages should only be processed while in BUSY state. * (In particular, in READY state we hold off further parsing * until the application collects the current PGresult.) * * However, if the state is IDLE then we got trouble; we need to deal * with the unexpected message somehow. * * ParameterStatus ('S') messages are a special case: in IDLE state * we must process 'em (this case could happen if a new value was * adopted from config file due to SIGHUP), but otherwise we hold * off until BUSY state. */ if (id == 'A') { if (getNotify(conn)) return; } else if (id == 'N') { if (pqGetErrorNotice3(conn, false)) return; } else if (conn->asyncStatus != PGASYNC_BUSY) { /* If not IDLE state, just wait ... */ if (conn->asyncStatus != PGASYNC_IDLE) return; /* * Unexpected message in IDLE state; need to recover somehow. * ERROR messages are displayed using the notice processor; * ParameterStatus is handled normally; anything else is just * dropped on the floor after displaying a suitable warning * notice. (An ERROR is very possibly the backend telling us * why it is about to close the connection, so we don't want * to just discard it...) */ if (id == 'E') { if (pqGetErrorNotice3(conn, false /* treat as notice */ )) return; } else if (id == 'S') { if (getParameterStatus(conn)) return; } else { pqInternalNotice(&conn->noticeHooks, "message type 0x%02x arrived from server while idle", id); /* Discard the unexpected message */ conn->inCursor += msgLength; } } else { /* * In BUSY state, we can process everything. */ switch (id) { case 'C': /* command complete */ if (pqGets(&conn->workBuffer, conn)) return; if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); strncpy(conn->result->cmdStatus, conn->workBuffer.data, CMDSTATUS_LEN); conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ if (pqGetErrorNotice3(conn, true)) return; conn->asyncStatus = PGASYNC_READY; break; case 'Z': /* backend is ready for new query */ if (getReadyForQuery(conn)) return; conn->asyncStatus = PGASYNC_IDLE; break; case 'I': /* empty query */ if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(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; break; case 'K': /* secret key data from the backend */ /* * This is expected only during backend startup, but * it's just as easy to handle it as part of the main * loop. Save the data and continue processing. */ if (pqGetInt(&(conn->be_pid), 4, conn)) return; if (pqGetInt(&(conn->be_key), 4, conn)) return; break; case 'T': /* Row Description */ if (conn->result == NULL) { /* First 'T' in a query sequence */ if (getRowDescriptions(conn)) return; } else { /* * A new 'T' message is treated as the start of * another PGresult. (It is not clear that this * is really possible with the current backend.) * We stop parsing until the application accepts * the current result. */ conn->asyncStatus = PGASYNC_READY; return; } break; case 'n': /* No Data */ /* * NoData indicates that we will not be seeing a * RowDescription message because the statement or * portal inquired about doesn't return rows. * Set up a COMMAND_OK result, instead of TUPLES_OK. */ if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); break; case 'D': /* Data Row */ if (conn->result != NULL && conn->result->resultStatus == PGRES_TUPLES_OK) { /* Read another tuple of a normal query response */ if (getAnotherTuple(conn, msgLength)) return; } else if (conn->result != NULL && conn->result->resultStatus == PGRES_FATAL_ERROR) { /* * We've already choked for some reason. Just * discard tuples till we get to the end of the * query. */ conn->inCursor += msgLength; } else { /* Set up to report error at end of query */ printfPQExpBuffer(&conn->errorMessage, libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n")); pqSaveErrorResult(conn); /* Discard the unexpected message */ conn->inCursor += msgLength; } break; case 'G': /* Start Copy In */ if (getCopyStart(conn, PGRES_COPY_IN)) return; conn->asyncStatus = PGASYNC_COPY_IN; break; case 'H': /* Start Copy Out */ if (getCopyStart(conn, PGRES_COPY_OUT)) return; conn->asyncStatus = PGASYNC_COPY_OUT; conn->copy_already_done = 0; break; case 'd': /* Copy Data */ /* * If we see Copy Data, just silently drop it. This * would only occur if application exits COPY OUT mode * too early. */ conn->inCursor += msgLength; break; case 'c': /* Copy Done */ /* * If we see Copy Done, just silently drop it. This * is the normal case during PQendcopy. We will keep * swallowing data, expecting to see command-complete * for the COPY command. */ break; default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "unexpected response from server; first received character was \"%c\"\n"), id); /* build an error result holding the error message */ pqSaveErrorResult(conn); /* not sure if we will see more, so go to ready state */ conn->asyncStatus = PGASYNC_READY; /* Discard the unexpected message */ conn->inCursor += msgLength; break; } /* switch on protocol character */ } /* Successfully consumed this message */ if (conn->inCursor == conn->inStart + 5 + msgLength) { /* Normal case: parsing agrees with specified length */ conn->inStart = conn->inCursor; } else { /* Trouble --- report it */ printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"), id); /* build an error result holding the error message */ pqSaveErrorResult(conn); conn->asyncStatus = PGASYNC_READY; /* trust the specified message length as what to skip */ conn->inStart += 5 + msgLength; } }}/* * handleSyncLoss: clean up after loss of message-boundary sync * * There isn't really a lot we can do here except abandon the connection. */static voidhandleSyncLoss(PGconn *conn, char id, int msgLength){ printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "lost synchronization with server: got message type \"%c\", length %d\n"), id, msgLength); /* build an error result holding the error message */ pqSaveErrorResult(conn); conn->asyncStatus = PGASYNC_READY; /* drop out of GetResult wait loop */ pqsecure_close(conn); closesocket(conn->sock); conn->sock = -1; conn->status = CONNECTION_BAD; /* No more connection to backend */}/* * parseInput subroutine to read a 'T' (row descriptions) message. * We build a PGresult structure containing the attribute data. * Returns: 0 if completed message, EOF if not enough data yet. * * Note that if we run out of data, we have to release the partially * constructed PGresult, and rebuild it again next time. Fortunately, * that shouldn't happen often, since 'T' messages usually fit in a packet. */static intgetRowDescriptions(PGconn *conn){ PGresult *result; int nfields; int i; result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); /* parseInput already read the 'T' label and message length. */ /* 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)); } /* 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++) { int tableid; int columnid; int typid; int typlen; int atttypmod; int format; if (pqGets(&conn->workBuffer, conn) || pqGetInt(&tableid, 4, conn) || pqGetInt(&columnid, 2, conn) || pqGetInt(&typid, 4, conn) || pqGetInt(&typlen, 2, conn) || pqGetInt(&atttypmod, 4, conn) || 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. */ columnid = (int) ((int16) columnid); typlen = (int) ((int16) typlen); format = (int) ((int16) format); 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; if (format != 1) result->binary = 0; } /* Success! */ conn->result = result; return 0;}/* * parseInput subroutine to read a 'D' (row data) message. * We add another tuple to the existing PGresult structure. * Returns: 0 if completed message, EOF if error or not enough data yet. * * Note that if we run out of data, we have to suspend and reprocess * the message after more data is received. We keep a partially constructed * tuple in conn->curTuple, and avoid reallocating already-allocated storage. */static intgetAnotherTuple(PGconn *conn, int msgLength){ PGresult *result = conn->result; int nfields = result->numAttributes; PGresAttValue *tup; int tupnfields; /* # fields from tuple */ int vlen; /* length of the current field value */ int i; /* Allocate tuple space if first time for this data message */ if (conn->curTuple == NULL)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -