📄 fe-exec.c
字号:
/*------------------------------------------------------------------------- * * fe-exec.c * functions related to sending a query down to the backend * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /usr/local/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.81 1999/05/28 01:54:53 tgl Exp $ * *------------------------------------------------------------------------- */#include "libpq-fe.h"#include "libpq-int.h"#include "postgres.h"#ifdef WIN32#include "win32.h"#else#if !defined(NO_UNISTD_H)#include <unistd.h>#endif#endif#include <stdlib.h>#include <string.h>#include <errno.h>#include <ctype.h>/* keep this in same order as ExecStatusType in libpq-fe.h */const char *const pgresStatus[] = { "PGRES_EMPTY_QUERY", "PGRES_COMMAND_OK", "PGRES_TUPLES_OK", "PGRES_COPY_OUT", "PGRES_COPY_IN", "PGRES_BAD_RESPONSE", "PGRES_NONFATAL_ERROR", "PGRES_FATAL_ERROR"};#define DONOTICE(conn,message) \ ((*(conn)->noticeHook) ((conn)->noticeArg, (message)))static int addTuple(PGresult *res, PGresAttValue *tup);static void parseInput(PGconn *conn);static void handleSendFailure(PGconn *conn);static int getRowDescriptions(PGconn *conn);static int getAnotherTuple(PGconn *conn, int binary);static int getNotify(PGconn *conn);static int getNotice(PGconn *conn);/* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free * consumed a sizable part of the application's runtime. And there is * no real need to keep track of the fields separately, since they will * all be freed together when the PGresult is released. So now, we grab * large blocks of storage from malloc and allocate space for query data * within these blocks, using a trivially simple allocator. This reduces * the number of malloc/free calls dramatically, and it also avoids * fragmentation of the malloc storage arena. * The PGresult structure itself is still malloc'd separately. We could * combine it with the first allocation block, but that would waste space * for the common case that no extra storage is actually needed (that is, * the SQL command did not return tuples). * We also malloc the top-level array of tuple pointers separately, because * we need to be able to enlarge it via realloc, and our trivial space * allocator doesn't handle that effectively. (Too bad the FE/BE protocol * doesn't tell us up front how many tuples will be returned.) * All other subsidiary storage for a PGresult is kept in PGresult_data blocks * of size PGRESULT_DATA_BLOCKSIZE. The overhead at the start of each block * is just a link to the next one, if any. Free-space management info is * kept in the owning PGresult. * A query returning a small amount of data will thus require three malloc * calls: one for the PGresult, one for the tuples pointer array, and one * PGresult_data block. * Only the most recently allocated PGresult_data block is a candidate to * have more stuff added to it --- any extra space left over in older blocks * is wasted. We could be smarter and search the whole chain, but the point * here is to be simple and fast. Typical applications do not keep a PGresult * around very long anyway, so some wasted space within one is not a problem. * * Tuning constants for the space allocator are: * PGRESULT_DATA_BLOCKSIZE: size of a standard allocation block, in bytes * PGRESULT_ALIGN_BOUNDARY: assumed alignment requirement for binary data * PGRESULT_SEP_ALLOC_THRESHOLD: objects bigger than this are given separate * blocks, instead of being crammed into a regular allocation block. * Requirements for correct function are: * PGRESULT_ALIGN_BOUNDARY must be a multiple of the alignment requirements * of all machine data types. (Currently this is set from configure * tests, so it should be OK automatically.) * PGRESULT_SEP_ALLOC_THRESHOLD + PGRESULT_BLOCK_OVERHEAD <= * PGRESULT_DATA_BLOCKSIZE * pqResultAlloc assumes an object smaller than the threshold will fit * in a new block. * The amount of space wasted at the end of a block could be as much as * PGRESULT_SEP_ALLOC_THRESHOLD, so it doesn't pay to make that too large. * ---------------- */#ifdef MAX#undef MAX#endif#define MAX(a,b) ((a) > (b) ? (a) : (b))#define PGRESULT_DATA_BLOCKSIZE 2048#define PGRESULT_ALIGN_BOUNDARY MAXIMUM_ALIGNOF /* from configure */#define PGRESULT_BLOCK_OVERHEAD MAX(sizeof(PGresult_data), PGRESULT_ALIGN_BOUNDARY)#define PGRESULT_SEP_ALLOC_THRESHOLD (PGRESULT_DATA_BLOCKSIZE / 2)/* * PQmakeEmptyPGresult * 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. * * Note this is exported --- you wouldn't think an application would need * to build its own PGresults, but this has proven useful in both libpgtcl * and the Perl5 interface, so maybe it's not so unreasonable. */PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status){ PGresult *result; result = (PGresult *) malloc(sizeof(PGresult)); result->conn = conn; /* might be NULL */ result->ntups = 0; result->numAttributes = 0; result->attDescs = NULL; result->tuples = NULL; result->tupArrSize = 0; result->resultStatus = status; result->cmdStatus[0] = '\0'; result->binary = 0; result->errMsg = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; result->spaceLeft = 0; if (conn) /* consider copying conn's errorMessage */ { switch (status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_COPY_OUT: case PGRES_COPY_IN: /* non-error cases */ break; default: pqSetResultError(result, conn->errorMessage); break; } } return result;}/* * pqResultAlloc - * Allocate subsidiary storage for a PGresult. * * nBytes is the amount of space needed for the object. * If isBinary is true, we assume that we need to align the object on * a machine allocation boundary. * If isBinary is false, we assume the object is a char string and can * be allocated on any byte boundary. */void *pqResultAlloc(PGresult *res, int nBytes, int isBinary){ char *space; PGresult_data *block; if (!res) return NULL; if (nBytes <= 0) return res->null_field; /* * If alignment is needed, round up the current position to an * alignment boundary. */ if (isBinary) { int offset = res->curOffset % PGRESULT_ALIGN_BOUNDARY; if (offset) { res->curOffset += PGRESULT_ALIGN_BOUNDARY - offset; res->spaceLeft -= PGRESULT_ALIGN_BOUNDARY - offset; } } /* If there's enough space in the current block, no problem. */ if (nBytes <= res->spaceLeft) { space = res->curBlock->space + res->curOffset; res->curOffset += nBytes; res->spaceLeft -= nBytes; return space; } /* * If the requested object is very large, give it its own block; this * avoids wasting what might be most of the current block to start a * new block. (We'd have to special-case requests bigger than the * block size anyway.) The object is always given binary alignment in * this case. */ if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD) { block = (PGresult_data *) malloc(nBytes + PGRESULT_BLOCK_OVERHEAD); if (!block) return NULL; space = block->space + PGRESULT_BLOCK_OVERHEAD; if (res->curBlock) { /* * Tuck special block below the active block, so that we don't * have to waste the free space in the active block. */ block->next = res->curBlock->next; res->curBlock->next = block; } else { /* Must set up the new block as the first active block. */ block->next = NULL; res->curBlock = block; res->spaceLeft = 0; /* be sure it's marked full */ } return space; } /* Otherwise, start a new block. */ block = (PGresult_data *) malloc(PGRESULT_DATA_BLOCKSIZE); if (!block) return NULL; block->next = res->curBlock; res->curBlock = block; if (isBinary) { /* object needs full alignment */ res->curOffset = PGRESULT_BLOCK_OVERHEAD; res->spaceLeft = PGRESULT_DATA_BLOCKSIZE - PGRESULT_BLOCK_OVERHEAD; } else { /* we can cram it right after the overhead pointer */ res->curOffset = sizeof(PGresult_data); res->spaceLeft = PGRESULT_DATA_BLOCKSIZE - sizeof(PGresult_data); } space = block->space + res->curOffset; res->curOffset += nBytes; res->spaceLeft -= nBytes; return space;}/* * pqResultStrdup - * Like strdup, but the space is subsidiary PGresult space. */char *pqResultStrdup(PGresult *res, const char *str){ char *space = (char *) pqResultAlloc(res, strlen(str) + 1, FALSE); if (space) strcpy(space, str); return space;}/* * pqSetResultError - * assign a new error message to a PGresult */voidpqSetResultError(PGresult *res, const char *msg){ if (!res) return; if (msg && *msg) res->errMsg = pqResultStrdup(res, msg); else res->errMsg = NULL;}/* * PQclear - * free's the memory associated with a PGresult */voidPQclear(PGresult *res){ PGresult_data *block; if (!res) return; /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { res->curBlock = block->next; free(block); } /* Free the top-level tuple pointer array */ if (res->tuples) free(res->tuples); /* Free the PGresult structure itself */ free(res);}/* * Handy subroutine to deallocate any partially constructed async result. */voidpqClearAsyncResult(PGconn *conn){ if (conn->result) PQclear(conn->result); conn->result = NULL; conn->curTuple = NULL;}/* * addTuple * 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 */static intaddTuple(PGresult *res, PGresAttValue *tup){ if (res->ntups >= res->tupArrSize) { /* * Try to grow the array. * * We can use realloc because shallow copying of the structure is * okay. Note that the first time through, res->tuples is NULL. * While ANSI says that realloc() should act like malloc() in that * case, some old C libraries (like SunOS 4.1.x) coredump instead. * On failure realloc is supposed to return NULL without damaging * the existing allocation. Note that the positions beyond * res->ntups are garbage, not necessarily NULL. */ int newSize = (res->tupArrSize > 0) ? res->tupArrSize * 2 : 128; PGresAttValue **newTuples; if (res->tuples == NULL) newTuples = (PGresAttValue **) malloc(newSize * sizeof(PGresAttValue *)); else newTuples = (PGresAttValue **) realloc(res->tuples, newSize * sizeof(PGresAttValue *)); if (!newTuples) return FALSE; /* malloc or realloc failed */ res->tupArrSize = newSize; res->tuples = newTuples; } res->tuples[res->ntups] = tup; res->ntups++; return TRUE;}/* * PQsendQuery * Submit a query, but don't wait for it to finish * * Returns: 1 if successfully submitted * 0 if error (conn->errorMessage is set) */intPQsendQuery(PGconn *conn, const char *query){ if (!conn) return 0; if (!query) { sprintf(conn->errorMessage, "PQsendQuery() -- query pointer is null."); return 0; } /* check to see if the query string is too long */ if (strlen(query) > MAX_MESSAGE_LEN - 2) { sprintf(conn->errorMessage, "PQsendQuery() -- query is too long. " "Maximum length is %d\n", MAX_MESSAGE_LEN - 2); return 0; } /* Don't try to send if we know there's no live connection. */ if (conn->status != CONNECTION_OK) { sprintf(conn->errorMessage, "PQsendQuery() -- There is no connection " "to the backend.\n"); return 0; } /* Can't send while already busy, either. */ if (conn->asyncStatus != PGASYNC_IDLE) { sprintf(conn->errorMessage, "PQsendQuery() -- another query already in progress."); return 0; } /* clear the error string */ conn->errorMessage[0] = '\0'; /* initialize async result-accumulation state */ conn->result = NULL; conn->curTuple = NULL; /* send the query to the backend; */ /* the frontend-backend protocol uses 'Q' to designate queries */ if (pqPutnchar("Q", 1, conn) || pqPuts(query, conn) || pqFlush(conn)) { handleSendFailure(conn); return 0; } /* OK, it's launched! */ conn->asyncStatus = PGASYNC_BUSY; return 1;}/* * handleSendFailure: try to clean up after failure to send command. * * Primarily, what we want to accomplish here is to process an async * NOTICE message that the backend might have sent just before it died. * * NOTE: this routine should only be called in PGASYNC_IDLE state. */static voidhandleSendFailure(PGconn *conn){ /* Preserve the error message emitted by the failing output routine */ char * svErrMsg = strdup(conn->errorMessage); /* * Accept any available input data, ignoring errors. Note that if * pqReadData decides the backend has closed the channel, it will * close our side of the socket --- that's just what we want here. */ while (pqReadData(conn) > 0) /* loop until no more data readable */ ; /* * Parse any available input messages. Since we are in PGASYNC_IDLE * state, only NOTICE and NOTIFY messages will be eaten. */ parseInput(conn); /* Restore error message generated by output routine, if any. */ if (*svErrMsg != '\0') strcpy(conn->errorMessage, svErrMsg); free(svErrMsg);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -