diff options
Diffstat (limited to 'src/interfaces/libpq/fe-exec.c')
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 1061 |
1 files changed, 1061 insertions, 0 deletions
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c new file mode 100644 index 00000000000..78854ed73fe --- /dev/null +++ b/src/interfaces/libpq/fe-exec.c @@ -0,0 +1,1061 @@ +/*------------------------------------------------------------------------- + * + * fe-exec.c-- + * functions related to sending a query down to the backend + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.1.1.1 1996/07/09 06:22:17 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "postgres.h" +#include "libpq/pqcomm.h" +#include "libpq-fe.h" +#include <signal.h> + +/* the tuples array in a PGresGroup has to grow to accommodate the tuples */ +/* returned. Each time, we grow by this much: */ +#define TUPARR_GROW_BY 100 + +/* keep this in same order as ExecStatusType in pgtclCmds.h */ +char* pgresStatus[] = { + "PGRES_EMPTY_QUERY", + "PGRES_COMMAND_OK", + "PGRES_TUPLES_OK", + "PGRES_BAD_RESPONSE", + "PGRES_NONFATAL_ERROR", + "PGRES_FATAL_ERROR" +}; + + +static PGresult* makePGresult(PGconn *conn, char *pname); +static void addTuple(PGresult *res, PGresAttValue *tup); +static PGresAttValue* getTuple(PGconn *conn, PGresult *res, int binary); +static PGresult* makeEmptyPGresult(PGconn *conn, ExecStatusType status); +static void fill(int length, int max, char filler, FILE *fp); + +/* + * PQclear - + * free's the memory associated with a PGresult + * + */ +void +PQclear(PGresult* res) +{ + int i,j; + + if (!res) + return; + + /* free all the tuples */ + for (i=0;i<res->ntups;i++) { + for (j=0;j<res->numAttributes;j++) { + if (res->tuples[i][j].value) + free(res->tuples[i][j].value); + } + free(res->tuples[i]); + } + free(res->tuples); + + /* free all the attributes */ + for (i=0;i<res->numAttributes;i++) { + if (res->attDescs[i].name) + free(res->attDescs[i].name); + } + free(res->attDescs); + + /* free the structure itself */ + free(res); +} + +/* + * PGresult - + * returns a newly allocated, initialized PGresult + * + */ + +static PGresult* +makeEmptyPGresult(PGconn *conn, ExecStatusType status) +{ + PGresult *result; + + result = (PGresult*)malloc(sizeof(PGresult)); + + result->conn = conn; + 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; + return result; +} + +/* + * getTuple - + * get the next tuple from the stream + * + * the CALLER is responsible from freeing the PGresAttValue returned + */ + +static PGresAttValue* +getTuple(PGconn *conn, PGresult* result, int binary) +{ + char bitmap[MAX_FIELDS]; /* the backend sends us a bitmap of */ + /* which attributes are null */ + int bitmap_index = 0; + int i; + int nbytes; /* the number of bytes in bitmap */ + char bmap; /* One byte of the bitmap */ + int bitcnt = 0; /* number of bits examined in current byte */ + int vlen; /* length of the current field value */ + FILE *Pfin = conn->Pfin; + FILE *Pfdebug = conn->Pfdebug; + + PGresAttValue* tup; + + int nfields = result->numAttributes; + + result->binary = binary; + + tup = (PGresAttValue*) malloc(nfields * sizeof(PGresAttValue)); + + nbytes = nfields / BYTELEN; + if ( (nfields % BYTELEN) > 0) + nbytes++; + + if (pqGetnchar(bitmap, nbytes, Pfin, Pfdebug) == 1){ + sprintf(conn->errorMessage, + "Error reading null-values bitmap from tuple data stream\n"); + return NULL; + } + + bmap = bitmap[bitmap_index]; + + for (i=0;i<nfields;i++) { + if (!(bmap & 0200)) { + /* if the field value is absent, make it '\0' */ + /* XXX this makes it impossible to distinguish NULL + attributes from "". Is that OK? */ + tup[i].value = (char*)malloc(1); + tup[i].value[0] = '\0'; + tup[i].len = 0; + } + else { + /* get the value length (the first four bytes are for length) */ + pqGetInt(&vlen, VARHDRSZ, Pfin, Pfdebug); + if (binary == 0) { + vlen = vlen - VARHDRSZ; + } + if (vlen < 0) + vlen = 0; + tup[i].len = vlen; + tup[i].value = (char*) malloc(vlen + 1); + /* read in the value; */ + if (vlen > 0) + pqGetnchar((char*)(tup[i].value), vlen, Pfin, Pfdebug); + tup[i].value[vlen] = '\0'; + } + /* get the appropriate bitmap */ + bitcnt++; + if (bitcnt == BYTELEN) { + bitmap_index++; + bmap = bitmap[bitmap_index]; + bitcnt = 0; + } else + bmap <<= 1; + } + + return tup; +} + + +/* + * addTuple + * add a tuple to the PGresult structure, growing it if necessary + * to accommodate + * + */ +static void +addTuple(PGresult* res, PGresAttValue* tup) +{ + if (res->ntups == res->tupArrSize) { + /* grow the array */ + res->tupArrSize += TUPARR_GROW_BY; + + if (res->ntups == 0) + res->tuples = (PGresAttValue**) + malloc(res->tupArrSize * sizeof(PGresAttValue*)); + else + /* we can use realloc because shallow copying of the structure is okay */ + res->tuples = (PGresAttValue**) + realloc(res->tuples, res->tupArrSize * sizeof(PGresAttValue*)); + } + + res->tuples[res->ntups] = tup; + res->ntups++; +} + +/* + * PGresult + * fill out the PGresult structure with result tuples from the backend + * this is called after query has been successfully run and we have + * a portal name + * + * ASSUMPTION: we assume only *1* tuple group is returned from the backend + * + * the CALLER is reponsible for free'ing the new PGresult allocated here + * + */ + +static PGresult* +makePGresult(PGconn* conn, char* pname) +{ + PGresult* result; + int id; + int nfields; + int i; + int done = 0; + + PGresAttValue* newTup; + + FILE* Pfin = conn->Pfin; + FILE* Pfdebug = conn->Pfdebug; + + result = makeEmptyPGresult(conn, PGRES_TUPLES_OK); + + /* makePGresult() should only be called when the */ + /* id of the stream is 'T' to start with */ + + /* the next two bytes are the number of fields */ + if (pqGetInt(&nfields, 2, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "could not get the number of fields from the 'T' message\n"); + goto makePGresult_badResponse_return; + } + else + result->numAttributes = nfields; + + /* allocate space for the attribute descriptors */ + if (nfields > 0) { + result->attDescs = (PGresAttDesc*) malloc(nfields * sizeof(PGresAttDesc)); + } + + /* get type info */ + for (i=0;i<nfields;i++) { + char typName[MAX_MESSAGE_LEN]; + int adtid; + int adtsize; + + if ( pqGets(typName, MAX_MESSAGE_LEN, Pfin, Pfdebug) || + pqGetInt(&adtid, 4, Pfin, Pfdebug) || + pqGetInt(&adtsize, 2, Pfin, Pfdebug)) { + sprintf(conn->errorMessage, + "error reading type information from the 'T' message\n"); + goto makePGresult_badResponse_return; + } + result->attDescs[i].name = malloc(strlen(typName)+1); + strcpy(result->attDescs[i].name,typName); + result->attDescs[i].adtid = adtid; + result->attDescs[i].adtsize = adtsize; /* casting from int to int2 here */ + } + + id = pqGetc(Pfin,Pfdebug); + + /* process the data stream until we're finished */ + while(!done) { + switch (id) { + case 'T': /* a new tuple group */ + sprintf(conn->errorMessage, + "makePGresult() -- is not equipped to handle multiple tuple groups.\n"); + goto makePGresult_badResponse_return; + case 'B': /* a tuple in binary format */ + case 'D': /* a tuple in ASCII format */ + newTup = getTuple(conn, result, (id == 'B')); + if (newTup == NULL) + goto makePGresult_badResponse_return; + addTuple(result,newTup); + break; +/* case 'A': + sprintf(conn->errorMessage, "Asynchronous portals not supported"); + result->resultStatus = PGRES_NONFATAL_ERROR; + return result; + break; +*/ + case 'C': /* end of portal tuple stream */ + { + char command[MAX_MESSAGE_LEN]; + pqGets(command,MAX_MESSAGE_LEN, Pfin, Pfdebug); /* read the command tag */ + done = 1; + } + break; + case 'E': /* errors */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Error return detected from backend, but error message cannot be read"); + } + result->resultStatus = PGRES_FATAL_ERROR; + return result; + break; + case 'N': /* notices from the backend */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Notice return detected from backend, but error message cannot be read"); + } else + /* XXXX send Notices to stderr for now */ + fprintf(stderr, "%s\n", conn->errorMessage); + break; + default: /* uh-oh + this should never happen but frequently does when the + backend dumps core */ + sprintf(conn->errorMessage,"FATAL: unexpected results from the backend, it probably dumped core."); + fprintf(stderr, conn->errorMessage); + result->resultStatus = PGRES_FATAL_ERROR; + return result; + break; + } + if (!done) + id = getc(Pfin); + } /* while (1) */ + + result->resultStatus = PGRES_TUPLES_OK; + return result; + +makePGresult_badResponse_return: + result->resultStatus = PGRES_BAD_RESPONSE; + return result; + +} + + + +/* + * PQexec + * send a query to the backend and package up the result in a Pgresult + * + * if the query failed, return NULL, conn->errorMessage is set to + * a relevant message + * if query is successful, a new PGresult is returned + * the use is responsible for freeing that structure when done with it + * + */ + +PGresult* +PQexec(PGconn* conn, char* query) +{ + PGresult *result; + int id, clear; + char buffer[MAX_MESSAGE_LEN]; + char cmdStatus[MAX_MESSAGE_LEN]; + char pname[MAX_MESSAGE_LEN]; /* portal name */ + PGnotify *newNotify; + FILE *Pfin = conn->Pfin; + FILE *Pfout = conn->Pfout; + FILE* Pfdebug = conn->Pfdebug; + + pname[0]='\0'; + + /*clear the error string */ + conn->errorMessage[0] = '\0'; + + /* check to see if the query string is too long */ + if (strlen(query) > MAX_MESSAGE_LEN) { + sprintf(conn->errorMessage, "PQexec() -- query is too long. Maximum length is %d\n", MAX_MESSAGE_LEN -2 ); + return NULL; + } + + /* the frontend-backend protocol uses 'Q' to designate queries */ + sprintf(buffer,"Q%s",query); + + /* send the query to the backend; */ + if (pqPuts(buffer,Pfout, Pfdebug) == 1) { + (void) sprintf(conn->errorMessage, + "PQexec() -- while sending query: %s\n-- fprintf to Pfout failed: errno=%d\n%s\n", + query, errno,strerror(errno)); + return NULL; + } + + /* loop forever because multiple messages, especially NOTICES, + can come back from the backend + NOTICES are output directly to stderr + */ + + while (1) { + + /* read the result id */ + id = pqGetc(Pfin,Pfdebug); + if (id == EOF) { + /* hmm, no response from the backend-end, that's bad */ + (void) sprintf(conn->errorMessage, + "PQexec() -- No response from backend\n"); + return (PGresult*)NULL; + } + + switch (id) { + case 'A': + newNotify = (PGnotify*)malloc(sizeof(PGnotify)); + pqGetInt(&(newNotify->be_pid), 4, Pfin, Pfdebug); + pqGets(newNotify->relname, NAMEDATALEN, Pfin, Pfdebug); + DLAddTail(conn->notifyList, DLNewElem(newNotify)); + /* async messages are piggy'ed back on other messages, + so we stay in the while loop for other messages */ + break; + case 'C': /* portal query command, no tuples returned */ + if (pqGets(cmdStatus, MAX_MESSAGE_LEN, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "PQexec() -- query command completed, but return message from backend cannot be read"); + return (PGresult*)NULL; + } + else { + /* + // since backend may produce more than one result for some commands + // need to poll until clear + // send an empty query down, and keep reading out of the pipe + // until an 'I' is received. + */ + clear = 0; + + pqPuts("Q ",Pfout,Pfdebug); /* send an empty query */ + while (!clear) + { + if (pqGets(buffer,ERROR_MSG_LENGTH,Pfin,Pfdebug) == 1) + clear = 1; + clear = (buffer[0] == 'I'); + } + result = makeEmptyPGresult(conn,PGRES_COMMAND_OK); + strncpy(result->cmdStatus,cmdStatus, CMDSTATUS_LEN-1); + return result; + } + break; + case 'E': /* error return */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + (void) sprintf(conn->errorMessage, + "PQexec() -- error return detected from backend, but error message cannot be read"); + } + return (PGresult*)NULL; + break; + case 'I': /* empty query */ + /* read the throw away the closing '\0' */ + { + int c; + if ((c = pqGetc(Pfin,Pfdebug)) != '\0') { + fprintf(stderr,"error!, unexpected character %c following 'I'\n", c); + } + result = makeEmptyPGresult(conn, PGRES_EMPTY_QUERY); + return result; + } + break; + case 'N': /* notices from the backend */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "PQexec() -- error return detected from backend, but error message cannot be read"); + return (PGresult*)NULL; + } + else + fprintf(stderr,"%s", conn->errorMessage); + break; + case 'P': /* synchronous (normal) portal */ + pqGets(pname,MAX_MESSAGE_LEN,Pfin, Pfdebug); /* read in the portal name*/ + break; + case 'T': /* actual tuple results: */ + return makePGresult(conn, pname); + break; + case 'D': /* copy command began successfully */ + return makeEmptyPGresult(conn,PGRES_COPY_IN); + break; + case 'B': /* copy command began successfully */ + return makeEmptyPGresult(conn,PGRES_COPY_OUT); + break; + default: + sprintf(conn->errorMessage, + "unknown protocol character %c read from backend\n", + id); + return (PGresult*)NULL; + } /* switch */ +} /* while (1)*/ + +} + +/* + * PQnotifies + * returns a PGnotify* structure of the latest async notification + * that has not yet been handled + * + * returns NULL, if there is currently + * no unhandled async notification from the backend + * + * the CALLER is responsible for FREE'ing the structure returned + */ + +PGnotify* +PQnotifies(PGconn *conn) +{ + Dlelem *e; + if (conn->status != CONNECTION_OK) + return NULL; + /* RemHead returns NULL if list is empy */ + e = DLRemHead(conn->notifyList); + if (e) + return (PGnotify*)DLE_VAL(e); + else + return NULL; +} + +/* + * PQgetline - gets a newline-terminated string from the backend. + * + * Chiefly here so that applications can use "COPY <rel> to stdout" + * and read the output string. Returns a null-terminated string in s. + * + * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips + * the terminating \n (like gets(3)). + * + * RETURNS: + * EOF if it is detected or invalid arguments are given + * 0 if EOL is reached (i.e., \n has been read) + * (this is required for backward-compatibility -- this + * routine used to always return EOF or 0, assuming that + * the line ended within maxlen bytes.) + * 1 in other cases + */ +int +PQgetline(PGconn *conn, char *s, int maxlen) +{ + int c = '\0'; + + if (!conn->Pfin || !s || maxlen <= 1) + return(EOF); + + for (; maxlen > 1 && + (c = pqGetc(conn->Pfin, conn->Pfdebug)) != '\n' && + c != EOF; + --maxlen) { + *s++ = c; + } + *s = '\0'; + + if (c == EOF) { + return(EOF); /* error -- reached EOF before \n */ + } else if (c == '\n') { + return(0); /* done with this line */ + } + return(1); /* returning a full buffer */ +} + + +/* + * PQputline -- sends a string to the backend. + * + * Chiefly here so that applications can use "COPY <rel> from stdin". + * + */ +void +PQputline(PGconn *conn, char *s) +{ + if (conn->Pfout) { + (void) fputs(s, conn->Pfout); + fflush(conn->Pfout); + } +} + +/* + * PQendcopy + * called while waiting for the backend to respond with success/failure + * to a "copy". + * + * RETURNS: + * 0 on failure + * 1 on success + */ +int +PQendcopy(PGconn *conn) +{ + char id; + FILE *Pfin = conn->Pfin; + FILE* Pfdebug = conn->Pfdebug; + + if ( (id = pqGetc(Pfin,Pfdebug)) > 0) + return(0); + switch (id) { + case 'Z': /* backend finished the copy */ + return(1); + case 'E': + case 'N': + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Error return detected from backend, but error message cannot be read"); + } + return(0); + break; + default: + (void) sprintf(conn->errorMessage, + "FATAL: PQendcopy: protocol error: id=%x\n", + id); + fputs(conn->errorMessage, stderr); + fprintf(stderr,"resetting connection\n"); + PQreset(conn); + return(0); + } +} + +/* simply send out max-length number of filler characters to fp */ +static void +fill (int length, int max, char filler, FILE *fp) +{ + int count; + char filltmp[2]; + + filltmp[0] = filler; + filltmp[1] = 0; + count = max - length; + while (count-- >= 0) + { + fprintf(fp, "%s", filltmp); + } + } + + +/* + * PQdisplayTuples() + * + * a better version of PQprintTuples() + * that can optionally do padding of fields with spaces and use different + * field separators + */ +void +PQdisplayTuples(PGresult *res, + FILE *fp, /* where to send the output */ + int fillAlign, /* pad the fields with spaces */ + char *fieldSep, /* field separator */ + int printHeader, /* display headers? */ + int quiet + ) +{ +#define DEFAULT_FIELD_SEP " " + + char *pager; + int i, j; + int nFields; + int nTuples; + int fLength[MAX_FIELDS]; + int usePipe = 0; + + if (fieldSep == NULL) + fieldSep == DEFAULT_FIELD_SEP; + + if (fp == NULL) + fp = stdout; + if (fp == stdout) { + /* try to pipe to the pager program if possible */ + pager=getenv("PAGER"); + if (pager != NULL) { + fp = popen(pager, "w"); + if (fp) { + usePipe = 1; + signal(SIGPIPE, SIG_IGN); + } else + fp = stdout; + } + } + + /* Get some useful info about the results */ + nFields = PQnfields(res); + nTuples = PQntuples(res); + + /* Zero the initial field lengths */ + for (j=0 ; j < nFields; j++) { + fLength[j] = strlen(PQfname(res,j)); + } + /* Find the max length of each field in the result */ + /* will be somewhat time consuming for very large results */ + if (fillAlign) { + for (i=0; i < nTuples; i++) { + for (j=0 ; j < nFields; j++) { + if (PQgetlength(res,i,j) > fLength[j]) + fLength[j] = PQgetlength(res,i,j); + } + } + } + + if (printHeader) { + /* first, print out the attribute names */ + for (i=0; i < nFields; i++) { + fputs(PQfname(res,i), fp); + if (fillAlign) + fill (strlen (PQfname(res,i)), fLength[i], ' ', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + + /* Underline the attribute names */ + for (i=0; i < nFields; i++) { + if (fillAlign) + fill (0, fLength[i], '-', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + } + + /* next, print out the instances */ + for (i=0; i < nTuples; i++) { + for (j=0 ; j < nFields; j++) { + fprintf(fp, "%s", PQgetvalue(res,i,j)); + if (fillAlign) + fill (strlen (PQgetvalue(res,i,j)), fLength[j], ' ', fp); + fputs(fieldSep,fp); + } + fprintf(fp, "\n"); + } + + if (!quiet) + fprintf (fp, "\nQuery returned %d row%s.\n",PQntuples(res), + (PQntuples(res) == 1) ? "" : "s"); + + fflush(fp); + if (usePipe) { + pclose(fp); + signal(SIGPIPE, SIG_DFL); + } +} + + + +/* + * PQprintTuples() + * + * This is the routine that prints out the tuples that + * are returned from the backend. + * Right now all columns are of fixed length, + * this should be changed to allow wrap around for + * tuples values that are wider. + */ +void +PQprintTuples(PGresult *res, + FILE* fout, /* output stream */ + int PrintAttNames,/* print attribute names or not*/ + int TerseOutput, /* delimiter bars or not?*/ + int colWidth /* width of column, if 0, use variable width */ + ) +{ + int nFields; + int nTups; + int i,j; + char formatString[80]; + + char *tborder = NULL; + + nFields = PQnfields(res); + nTups = PQntuples(res); + + if (colWidth > 0) { + sprintf(formatString,"%%s %%-%ds",colWidth); + } else + sprintf(formatString,"%%s %%s"); + + if ( nFields > 0 ) { /* only print tuples with at least 1 field. */ + + if (!TerseOutput) + { + int width; + width = nFields * 14; + tborder = malloc (width+1); + for (i = 0; i <= width; i++) + tborder[i] = '-'; + tborder[i] = '\0'; + fprintf(fout,"%s\n",tborder); + } + + for (i=0; i < nFields; i++) { + if (PrintAttNames) { + fprintf(fout,formatString, + TerseOutput ? "" : "|", + PQfname(res, i)); + } + } + + if (PrintAttNames) { + if (TerseOutput) + fprintf(fout,"\n"); + else + fprintf(fout, "|\n%s\n",tborder); + } + + for (i = 0; i < nTups; i++) { + for (j = 0; j < nFields; j++) { + char *pval = PQgetvalue(res,i,j); + fprintf(fout, formatString, + TerseOutput ? "" : "|", + pval ? pval : ""); + } + if (TerseOutput) + fprintf(fout,"\n"); + else + fprintf(fout, "|\n%s\n",tborder); + } + } +} + + +/* ---------------- + * PQfn - Send a function call to the POSTGRES backend. + * + * conn : backend connection + * fnid : function id + * result_buf : pointer to result buffer (&int if integer) + * result_len : length of return value. + * actual_result_len: actual length returned. (differs from result_len + * for varlena structures.) + * result_type : If the result is an integer, this must be 1, + * otherwise this should be 0 + * args : pointer to a NULL terminated arg array. + * (length, if integer, and result-pointer) + * nargs : # of arguments in args array. + * + * RETURNS + * NULL on failure. PQerrormsg will be set. + * "G" if there is a return value. + * "V" if there is no return value. + * ---------------- + */ + +PGresult* +PQfn(PGconn *conn, + int fnid, + int *result_buf, + int *actual_result_len, + int result_is_int, + PQArgBlock *args, + int nargs) +{ + FILE *Pfin = conn->Pfin; + FILE *Pfout = conn->Pfout; + FILE* Pfdebug = conn->Pfdebug; + int id; + int i; + + /* clear the error string */ + conn->errorMessage[0] = '\0'; + + pqPuts("F ",Pfout,Pfdebug); /* function */ + pqPutInt(fnid, 4, Pfout, Pfdebug); /* function id */ + pqPutInt(nargs, 4, Pfout, Pfdebug); /* # of args */ + + for (i = 0; i < nargs; ++i) { /* len.int4 + contents */ + pqPutInt(args[i].len, 4, Pfout, Pfdebug); + if (args[i].isint) { + pqPutInt(args[i].u.integer, 4, Pfout, Pfdebug); + } else { + pqPutnchar((char *)args[i].u.ptr, args[i].len, Pfout, Pfdebug); + } + } + pqFlush(Pfout, Pfdebug); + + id = pqGetc(Pfin, Pfdebug); + if (id != 'V') { + if (id == 'E') { + pqGets(conn->errorMessage,ERROR_MSG_LENGTH,Pfin,Pfdebug); + } else + sprintf(conn->errorMessage, + "PQfn: expected a 'V' from the backend. Got '%c' instead", + id); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + } + + id = pqGetc(Pfin, Pfdebug); + for (;;) { + int c; + switch (id) { + case 'G': /* function returned properly */ + pqGetInt(actual_result_len,4,Pfin,Pfdebug); + if (result_is_int) { + pqGetInt(result_buf,4,Pfin,Pfdebug); + } else { + pqGetnchar((char *) result_buf, *actual_result_len, + Pfin, Pfdebug); + } + c = pqGetc(Pfin, Pfdebug); /* get the last '0'*/ + return makeEmptyPGresult(conn,PGRES_COMMAND_OK); + case 'E': + sprintf(conn->errorMessage, + "PQfn: returned an error"); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + case 'N': + /* print notice and go back to processing return values */ + if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, Pfin, Pfdebug) == 1) { + sprintf(conn->errorMessage, + "Notice return detected from backend, but error message cannot be read"); + } else + fprintf(stderr, "%s\n", conn->errorMessage); + /* keep iterating */ + break; + case '0': /* no return value */ + return makeEmptyPGresult(conn,PGRES_COMMAND_OK); + default: + /* The backend violates the protocol. */ + sprintf(conn->errorMessage, + "FATAL: PQfn: protocol error: id=%x\n", id); + return makeEmptyPGresult(conn,PGRES_FATAL_ERROR); + } + } +} + + + + +/* ====== accessor funcs for PGresult ======== */ + +ExecStatusType +PQresultStatus(PGresult* res) +{ + return res->resultStatus; +} + +int +PQntuples(PGresult *res) +{ + return res->ntups; +} + +int +PQnfields(PGresult *res) +{ + return res->numAttributes; +} + +/* + returns NULL if the field_num is invalid +*/ +char* +PQfname(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQfname: ERROR! name of field %d(of %d) is not available", + field_num, res->numAttributes -1); + return NULL; + } + if (res->attDescs) { + return res->attDescs[field_num].name; + } else + return NULL; +} + +/* + returns -1 on a bad field name +*/ +int +PQfnumber(PGresult *res, char* field_name) +{ + int i; + + if (field_name == NULL || + field_name[0] == '\0' || + res->attDescs == NULL) + return -1; + + for (i=0;i<res->numAttributes;i++) { + if ( strcmp(field_name, res->attDescs[i].name) == 0 ) + return i; + } + return -1; + +} + +Oid +PQftype(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQftype: ERROR! type of field %d(of %d) is not available", + field_num, res->numAttributes -1); + } + if (res->attDescs) { + return res->attDescs[field_num].adtid; + } else + return InvalidOid; +} + +int2 +PQfsize(PGresult *res, int field_num) +{ + if (field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQfsize: ERROR! size of field %d(of %d) is not available", + field_num, res->numAttributes -1); + } + if (res->attDescs) { + return res->attDescs[field_num].adtsize; + } else + return 0; +} + +char* PQcmdStatus(PGresult *res) { + return res->cmdStatus; +} + +/* + PQoidStatus - + if the last command was an INSERT, return the oid string + if not, return "" +*/ +char* PQoidStatus(PGresult *res) { + if (!res->cmdStatus) + return ""; + + if (strncmp(res->cmdStatus, "INSERT",6) == 0) { + return res->cmdStatus+7; + } else + return ""; +} + +/* + PQgetvalue: + return the attribute value of field 'field_num' of + tuple '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. +*/ +char* +PQgetvalue(PGresult *res, int tup_num, int field_num) +{ + if (tup_num > (res->ntups - 1) || + field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQgetvalue: ERROR! field %d(of %d) of tuple %d(of %d) is not available", + field_num, res->numAttributes - 1, tup_num, res->ntups); + } + + + return res->tuples[tup_num][field_num].value; +} + +/* 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. +*/ +int +PQgetlength(PGresult *res, int tup_num, int field_num) +{ + if (tup_num > (res->ntups - 1 )|| + field_num > (res->numAttributes - 1)) { + fprintf(stderr, + "PQgetlength: ERROR! field %d(of %d) of tuple %d(of %d) is not available", + field_num, res->numAttributes - 1, tup_num, res->ntups); + } + + return res->tuples[tup_num][field_num].len; + } |