diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2003-05-08 18:16:37 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2003-05-08 18:16:37 +0000 |
commit | c0a8c3ac13f84602a46ba25b9a2fd5427514f61a (patch) | |
tree | f321719251471a05a768117f35010d28076f938a /src/interfaces/libpq/fe-exec.c | |
parent | 5e7a5c9511b65d483639dd3f7dfab7b9e92c3433 (diff) | |
download | postgresql-c0a8c3ac13f84602a46ba25b9a2fd5427514f61a.tar.gz postgresql-c0a8c3ac13f84602a46ba25b9a2fd5427514f61a.zip |
Update 3.0 protocol support to match recent agreements about how to
handle multiple 'formats' for data I/O. Restructure CommandDest and
DestReceiver stuff one more time (it's finally starting to look a bit
clean though). Code now matches latest 3.0 protocol document as far
as message formats go --- but there is no support for binary I/O yet.
Diffstat (limited to 'src/interfaces/libpq/fe-exec.c')
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 670 |
1 files changed, 313 insertions, 357 deletions
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index db513d64ef5..7d0eb39f96c 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.134 2003/04/26 20:22:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.135 2003/05/08 18:16:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -55,250 +55,10 @@ static void parseInput(PGconn *conn); static void handleSendFailure(PGconn *conn); static void handleSyncLoss(PGconn *conn, char id, int msgLength); static int getRowDescriptions(PGconn *conn); -static int getAnotherTuple(PGconn *conn, int binary); +static int getAnotherTuple(PGconn *conn); static int getParameterStatus(PGconn *conn); static int getNotify(PGconn *conn); -/* --------------- - * Escaping arbitrary strings to get valid SQL strings/identifiers. - * - * Replaces "\\" with "\\\\" and "'" with "''". - * length is the length of the buffer pointed to by - * from. The buffer at to must be at least 2*length + 1 characters - * long. A terminating NUL character is written. - * --------------- - */ - -size_t -PQescapeString(char *to, const char *from, size_t length) -{ - const char *source = from; - char *target = to; - unsigned int remaining = length; - - while (remaining > 0) - { - switch (*source) - { - case '\\': - *target = '\\'; - target++; - *target = '\\'; - /* target and remaining are updated below. */ - break; - - case '\'': - *target = '\''; - target++; - *target = '\''; - /* target and remaining are updated below. */ - break; - - default: - *target = *source; - /* target and remaining are updated below. */ - } - source++; - target++; - remaining--; - } - - /* Write the terminating NUL character. */ - *target = '\0'; - - return target - to; -} - -/* - * PQescapeBytea - converts from binary string to the - * minimal encoding necessary to include the string in an SQL - * INSERT statement with a bytea type column as the target. - * - * The following transformations are applied - * '\0' == ASCII 0 == \\000 - * '\'' == ASCII 39 == \' - * '\\' == ASCII 92 == \\\\ - * anything >= 0x80 ---> \\ooo (where ooo is an octal expression) - */ -unsigned char * -PQescapeBytea(const unsigned char *bintext, size_t binlen, size_t *bytealen) -{ - const unsigned char *vp; - unsigned char *rp; - unsigned char *result; - size_t i; - size_t len; - - /* - * empty string has 1 char ('\0') - */ - len = 1; - - vp = bintext; - for (i = binlen; i > 0; i--, vp++) - { - if (*vp == 0 || *vp >= 0x80) - len += 5; /* '5' is for '\\ooo' */ - else if (*vp == '\'') - len += 2; - else if (*vp == '\\') - len += 4; - else - len++; - } - - rp = result = (unsigned char *) malloc(len); - if (rp == NULL) - return NULL; - - vp = bintext; - *bytealen = len; - - for (i = binlen; i > 0; i--, vp++) - { - if (*vp == 0 || *vp >= 0x80) - { - (void) sprintf(rp, "\\\\%03o", *vp); - rp += 5; - } - else if (*vp == '\'') - { - rp[0] = '\\'; - rp[1] = '\''; - rp += 2; - } - else if (*vp == '\\') - { - rp[0] = '\\'; - rp[1] = '\\'; - rp[2] = '\\'; - rp[3] = '\\'; - rp += 4; - } - else - *rp++ = *vp; - } - *rp = '\0'; - - return result; -} - -/* - * PQunescapeBytea - converts the null terminated string representation - * of a bytea, strtext, into binary, filling a buffer. It returns a - * pointer to the buffer which is NULL on error, and the size of the - * buffer in retbuflen. The pointer may subsequently be used as an - * argument to the function free(3). It is the reverse of PQescapeBytea. - * - * The following transformations are reversed: - * '\0' == ASCII 0 == \000 - * '\'' == ASCII 39 == \' - * '\\' == ASCII 92 == \\ - * - * States: - * 0 normal 0->1->2->3->4 - * 1 \ 1->5 - * 2 \0 1->6 - * 3 \00 - * 4 \000 - * 5 \' - * 6 \\ - */ -unsigned char * -PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen) -{ - size_t buflen; - unsigned char *buffer, - *bp; - const unsigned char *sp; - unsigned int state = 0; - - if (strtext == NULL) - return NULL; - buflen = strlen(strtext); /* will shrink, also we discover if - * strtext */ - buffer = (unsigned char *) malloc(buflen); /* isn't NULL terminated */ - if (buffer == NULL) - return NULL; - for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++) - { - switch (state) - { - case 0: - if (*sp == '\\') - state = 1; - *bp = *sp; - break; - case 1: - if (*sp == '\'') /* state=5 */ - { /* replace \' with 39 */ - bp--; - *bp = '\''; - buflen--; - state = 0; - } - else if (*sp == '\\') /* state=6 */ - { /* replace \\ with 92 */ - bp--; - *bp = '\\'; - buflen--; - state = 0; - } - else - { - if (isdigit(*sp)) - state = 2; - else - state = 0; - *bp = *sp; - } - break; - case 2: - if (isdigit(*sp)) - state = 3; - else - state = 0; - *bp = *sp; - break; - case 3: - if (isdigit(*sp)) /* state=4 */ - { - int v; - - bp -= 3; - sscanf(sp - 2, "%03o", &v); - *bp = v; - buflen -= 3; - state = 0; - } - else - { - *bp = *sp; - state = 0; - } - break; - } - } - buffer = realloc(buffer, buflen); - if (buffer == NULL) - return NULL; - - *retbuflen = buflen; - return buffer; -} - - -/* - * PQfreemem - safely frees memory allocated - * - * Needed mostly by Win32, unless multithreaded DLL (/MD in VC6) - * Used for freeing memory from PQescapeByte()a/PQunescapeBytea() - */ -void PQfreemem(void *ptr) -{ - free(ptr); -} - /* ---------------- * Space management for PGresult. @@ -909,7 +669,7 @@ parseInput(PGconn *conn) return; } if (msgLength > 30000 && - !(id == 'T' || id == 'D' || id == 'B' || id == 'd')) + !(id == 'T' || id == 'D' || id == 'd')) { handleSyncLoss(conn, id, msgLength); return; @@ -1074,11 +834,11 @@ parseInput(PGconn *conn) return; } break; - case 'D': /* ASCII data tuple */ + case 'D': /* Data Row */ if (conn->result != NULL) { /* Read another tuple of a normal query response */ - if (getAnotherTuple(conn, FALSE)) + if (getAnotherTuple(conn)) return; } else @@ -1090,30 +850,18 @@ parseInput(PGconn *conn) conn->inCursor += msgLength; } break; - case 'B': /* Binary data tuple */ - if (conn->result != NULL) - { - /* Read another tuple of a normal query response */ - if (getAnotherTuple(conn, TRUE)) - return; - } - else - { - snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n")); - DONOTICE(conn, noticeWorkspace); - /* Discard the unexpected message */ - conn->inCursor += msgLength; - } - break; case 'G': /* Start Copy In */ if (pqGetc(&conn->copy_is_binary, conn)) 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)) 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; @@ -1229,13 +977,15 @@ getRowDescriptions(PGconn *conn) 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(&atttypmod, 4, conn) || + pqGetInt(&format, 2, conn)) { PQclear(result); return EOF; @@ -1247,13 +997,14 @@ getRowDescriptions(PGconn *conn) */ columnid = (int) ((int16) columnid); typlen = (int) ((int16) typlen); + format = (int) ((int16) format); result->attDescs[i].name = pqResultStrdup(result, conn->workBuffer.data); result->attDescs[i].typid = typid; result->attDescs[i].typlen = typlen; result->attDescs[i].atttypmod = atttypmod; - /* XXX todo: save tableid/columnid too */ + /* XXX todo: save tableid/columnid, format too */ } /* Success! */ @@ -1262,7 +1013,7 @@ getRowDescriptions(PGconn *conn) } /* - * parseInput subroutine to read a 'B' or 'D' (row data) message. + * 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. * @@ -1272,23 +1023,14 @@ getRowDescriptions(PGconn *conn) */ static int -getAnotherTuple(PGconn *conn, int binary) +getAnotherTuple(PGconn *conn) { PGresult *result = conn->result; int nfields = result->numAttributes; PGresAttValue *tup; - - /* the backend sends us a bitmap of which attributes are null */ - char std_bitmap[64]; /* used unless it doesn't fit */ - char *bitmap = std_bitmap; - int i; - size_t nbytes; /* the number of bytes in bitmap */ - char bmap; /* One byte of the bitmap */ - int bitmap_index; /* Its index */ - int bitcnt; /* number of bits examined in current byte */ + int tupnfields; /* # fields from tuple */ int vlen; /* length of the current field value */ - - result->binary = binary; + int i; /* Allocate tuple space if first time for this data message */ if (conn->curTuple == NULL) @@ -1301,61 +1043,50 @@ getAnotherTuple(PGconn *conn, int binary) } tup = conn->curTuple; - /* Get the null-value bitmap */ - nbytes = (nfields + BYTELEN - 1) / BYTELEN; - /* malloc() only for unusually large field counts... */ - if (nbytes > sizeof(std_bitmap)) - bitmap = (char *) malloc(nbytes); + /* Get the field count and make sure it's what we expect */ + if (pqGetInt(&tupnfields, 2, conn)) + return EOF; - if (pqGetnchar(bitmap, nbytes, conn)) - goto EOFexit; + if (tupnfields != nfields) + { + /* Replace partially constructed result with an error result */ + pqClearAsyncResult(conn); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unexpected field count in D message\n")); + saveErrorResult(conn); + conn->asyncStatus = PGASYNC_READY; + /* Discard the failed message by pretending we read it */ + return 0; + } /* Scan the fields */ - bitmap_index = 0; - bmap = bitmap[bitmap_index]; - bitcnt = 0; - for (i = 0; i < nfields; i++) { - if (!(bmap & 0200)) + /* get the value length */ + if (pqGetInt(&vlen, 4, conn)) + return EOF; + if (vlen == -1) { - /* if the field value is absent, make it a null string */ + /* null field */ tup[i].value = result->null_field; tup[i].len = NULL_LEN; + continue; } - else + if (vlen < 0) + vlen = 0; + if (tup[i].value == NULL) { - /* get the value length (the first four bytes are for length) */ - if (pqGetInt(&vlen, 4, conn)) - goto EOFexit; - if (binary == 0) - vlen = vlen - 4; - if (vlen < 0) - vlen = 0; + tup[i].value = (char *) pqResultAlloc(result, vlen + 1, false); if (tup[i].value == NULL) - { - tup[i].value = (char *) pqResultAlloc(result, vlen + 1, (bool) binary); - if (tup[i].value == NULL) - goto outOfMemory; - } - tup[i].len = vlen; - /* read in the value */ - if (vlen > 0) - if (pqGetnchar((char *) (tup[i].value), vlen, conn)) - goto EOFexit; - /* we have to terminate this ourselves */ - tup[i].value[vlen] = '\0'; - } - /* advance the bitmap stuff */ - bitcnt++; - if (bitcnt == BYTELEN) - { - bitmap_index++; - bmap = bitmap[bitmap_index]; - bitcnt = 0; + goto outOfMemory; } - else - bmap <<= 1; + tup[i].len = vlen; + /* read in the value */ + if (vlen > 0) + if (pqGetnchar((char *) (tup[i].value), vlen, conn)) + return EOF; + /* we have to terminate this ourselves */ + tup[i].value[vlen] = '\0'; } /* Success! Store the completed tuple in the result */ @@ -1364,8 +1095,6 @@ getAnotherTuple(PGconn *conn, int binary) /* and reset for a new message */ conn->curTuple = NULL; - if (bitmap != std_bitmap) - free(bitmap); return 0; outOfMemory: @@ -1380,13 +1109,8 @@ outOfMemory: libpq_gettext("out of memory\n")); conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); conn->asyncStatus = PGASYNC_READY; - /* Discard the failed message --- good idea? */ - conn->inStart = conn->inEnd; - -EOFexit: - if (bitmap != std_bitmap) - free(bitmap); - return EOF; + /* Discard the failed message by pretending we read it */ + return 0; } @@ -2129,10 +1853,11 @@ PQfn(PGconn *conn, return NULL; } - if (pqPutMsgStart('F', conn) < 0 || /* function call msg */ - pqPuts("", conn) < 0 || /* useless string */ - pqPutInt(fnid, 4, conn) < 0 || /* function id */ - pqPutInt(nargs, 4, conn) < 0) /* # of args */ + if (pqPutMsgStart('F', conn) < 0 || /* function call msg */ + pqPutInt(fnid, 4, conn) < 0 || /* function id */ + pqPutInt(1, 2, conn) < 0 || /* # of format codes */ + pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */ + pqPutInt(nargs, 2, conn) < 0) /* # of args */ { handleSendFailure(conn); return NULL; @@ -2145,10 +1870,12 @@ PQfn(PGconn *conn, handleSendFailure(conn); return NULL; } + if (args[i].len == -1) + continue; /* it's NULL */ if (args[i].isint) { - if (pqPutInt(args[i].u.integer, 4, conn)) + if (pqPutInt(args[i].u.integer, args[i].len, conn)) { handleSendFailure(conn); return NULL; @@ -2164,6 +1891,12 @@ PQfn(PGconn *conn, } } + if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */ + { + handleSendFailure(conn); + return NULL; + } + if (pqPutMsgEnd(conn) < 0 || pqFlush(conn)) { @@ -2204,7 +1937,7 @@ PQfn(PGconn *conn, break; } if (msgLength > 30000 && - !(id == 'T' || id == 'D' || id == 'B' || id == 'd' || id == 'V')) + !(id == 'T' || id == 'D' || id == 'd' || id == 'V')) { handleSyncLoss(conn, id, msgLength); break; @@ -2243,16 +1976,13 @@ PQfn(PGconn *conn, switch (id) { case 'V': /* function result */ - if (pqGetc(&id, conn)) + if (pqGetInt(actual_result_len, 4, conn)) continue; - if (id == 'G') + if (*actual_result_len != -1) { - /* function returned nonempty value */ - if (pqGetInt(actual_result_len, 4, conn)) - continue; if (result_is_int) { - if (pqGetInt(result_buf, 4, conn)) + if (pqGetInt(result_buf, *actual_result_len, conn)) continue; } else @@ -2262,24 +1992,9 @@ PQfn(PGconn *conn, conn)) continue; } - if (pqGetc(&id, conn)) /* get the last '0' */ - continue; - } - if (id == '0') - { - /* correctly finished function result message */ - status = PGRES_COMMAND_OK; - } - else - { - /* The backend violates the protocol. */ - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("protocol error: id=0x%x\n"), - id); - saveErrorResult(conn); - conn->inStart += 5 + msgLength; - return prepareAsyncResult(conn); } + /* correctly finished function result message */ + status = PGRES_COMMAND_OK; break; case 'E': /* error return */ if (pqGetErrorNotice(conn, true)) @@ -2744,6 +2459,18 @@ PQflush(PGconn *conn) return (pqFlush(conn)); } + +/* + * PQfreemem - safely frees memory allocated + * + * Needed mostly by Win32, unless multithreaded DLL (/MD in VC6) + * Used for freeing memory from PQescapeByte()a/PQunescapeBytea() + */ +void PQfreemem(void *ptr) +{ + free(ptr); +} + /* * PQfreeNotify - free's the memory associated with a PGnotify * @@ -2760,3 +2487,232 @@ PQfreeNotify(PGnotify *notify) { PQfreemem(notify); } + + +/* --------------- + * Escaping arbitrary strings to get valid SQL strings/identifiers. + * + * Replaces "\\" with "\\\\" and "'" with "''". + * length is the length of the buffer pointed to by + * from. The buffer at to must be at least 2*length + 1 characters + * long. A terminating NUL character is written. + * --------------- + */ + +size_t +PQescapeString(char *to, const char *from, size_t length) +{ + const char *source = from; + char *target = to; + unsigned int remaining = length; + + while (remaining > 0) + { + switch (*source) + { + case '\\': + *target = '\\'; + target++; + *target = '\\'; + /* target and remaining are updated below. */ + break; + + case '\'': + *target = '\''; + target++; + *target = '\''; + /* target and remaining are updated below. */ + break; + + default: + *target = *source; + /* target and remaining are updated below. */ + } + source++; + target++; + remaining--; + } + + /* Write the terminating NUL character. */ + *target = '\0'; + + return target - to; +} + +/* + * PQescapeBytea - converts from binary string to the + * minimal encoding necessary to include the string in an SQL + * INSERT statement with a bytea type column as the target. + * + * The following transformations are applied + * '\0' == ASCII 0 == \\000 + * '\'' == ASCII 39 == \' + * '\\' == ASCII 92 == \\\\ + * anything >= 0x80 ---> \\ooo (where ooo is an octal expression) + */ +unsigned char * +PQescapeBytea(const unsigned char *bintext, size_t binlen, size_t *bytealen) +{ + const unsigned char *vp; + unsigned char *rp; + unsigned char *result; + size_t i; + size_t len; + + /* + * empty string has 1 char ('\0') + */ + len = 1; + + vp = bintext; + for (i = binlen; i > 0; i--, vp++) + { + if (*vp == 0 || *vp >= 0x80) + len += 5; /* '5' is for '\\ooo' */ + else if (*vp == '\'') + len += 2; + else if (*vp == '\\') + len += 4; + else + len++; + } + + rp = result = (unsigned char *) malloc(len); + if (rp == NULL) + return NULL; + + vp = bintext; + *bytealen = len; + + for (i = binlen; i > 0; i--, vp++) + { + if (*vp == 0 || *vp >= 0x80) + { + (void) sprintf(rp, "\\\\%03o", *vp); + rp += 5; + } + else if (*vp == '\'') + { + rp[0] = '\\'; + rp[1] = '\''; + rp += 2; + } + else if (*vp == '\\') + { + rp[0] = '\\'; + rp[1] = '\\'; + rp[2] = '\\'; + rp[3] = '\\'; + rp += 4; + } + else + *rp++ = *vp; + } + *rp = '\0'; + + return result; +} + +/* + * PQunescapeBytea - converts the null terminated string representation + * of a bytea, strtext, into binary, filling a buffer. It returns a + * pointer to the buffer which is NULL on error, and the size of the + * buffer in retbuflen. The pointer may subsequently be used as an + * argument to the function free(3). It is the reverse of PQescapeBytea. + * + * The following transformations are reversed: + * '\0' == ASCII 0 == \000 + * '\'' == ASCII 39 == \' + * '\\' == ASCII 92 == \\ + * + * States: + * 0 normal 0->1->2->3->4 + * 1 \ 1->5 + * 2 \0 1->6 + * 3 \00 + * 4 \000 + * 5 \' + * 6 \\ + */ +unsigned char * +PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen) +{ + size_t buflen; + unsigned char *buffer, + *bp; + const unsigned char *sp; + unsigned int state = 0; + + if (strtext == NULL) + return NULL; + buflen = strlen(strtext); /* will shrink, also we discover if + * strtext */ + buffer = (unsigned char *) malloc(buflen); /* isn't NULL terminated */ + if (buffer == NULL) + return NULL; + for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++) + { + switch (state) + { + case 0: + if (*sp == '\\') + state = 1; + *bp = *sp; + break; + case 1: + if (*sp == '\'') /* state=5 */ + { /* replace \' with 39 */ + bp--; + *bp = '\''; + buflen--; + state = 0; + } + else if (*sp == '\\') /* state=6 */ + { /* replace \\ with 92 */ + bp--; + *bp = '\\'; + buflen--; + state = 0; + } + else + { + if (isdigit(*sp)) + state = 2; + else + state = 0; + *bp = *sp; + } + break; + case 2: + if (isdigit(*sp)) + state = 3; + else + state = 0; + *bp = *sp; + break; + case 3: + if (isdigit(*sp)) /* state=4 */ + { + int v; + + bp -= 3; + sscanf(sp - 2, "%03o", &v); + *bp = v; + buflen -= 3; + state = 0; + } + else + { + *bp = *sp; + state = 0; + } + break; + } + } + buffer = realloc(buffer, buflen); + if (buffer == NULL) + return NULL; + + *retbuflen = buflen; + return buffer; +} |