/* * exec.c * Query execution and data retrieval functions. * * Copyright (c) 2011 eSilo, LLC. All rights reserved. * This is free software; see the source for copying conditions. There is * NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. */ #include "libpqtypes-int.h" /* * Each param value requires an oid, ptr and 2 ints (oid, value, length, * format). This makes the maximum size of a param 20 bytes. A stack size * of 4k would allow for 204 query params. If more params are needed, * heap memory is used. Needing more than 204 param values in a query * is very rare, although possible. */ #define PARAM_STACKSIZE 4096 #define BUILD_ARRAYS(rettype) \ rettype r; \ char *buf = NULL; \ Oid *oids = NULL; \ char **vals = NULL; \ int *lens = NULL; \ int *fmts = NULL; \ int vcnt = 0; \ char stackbuffer[PARAM_STACKSIZE]; \ \ if (!conn) \ { \ PQseterror(err, "PGconn cannot be NULL"); \ return (rettype)(0); \ } \ \ if (param) \ { \ buf = stackbuffer; \ if (!buildArrays(param, err, &buf, &oids, &vals, &lens, &fmts)) \ return (rettype) (0); \ vcnt = param->vcnt; \ } #define RETURN_RESULT \ if (param) \ { \ if (buf && buf != stackbuffer) \ free(buf); \ } \ return r static int buildArrays(PGparam *param, PGerror *err, char **buf, Oid **oids, char ***vals, int **lens, int **fmts); static PGresult * copyExecError(PGconn *conn, PGerror *err, PGresult *r); static const char * getCommand(PGconn *conn, PGerror *err, PGparam *param, const char *command); static int _execvf(PGconn *conn, PGerror *err, const char *cmdspec, va_list ap, PGresult **resp); int PQgetf(const PGresult *res, PGerror *err, int tup_num, const char *format, ...) { int n; va_list ap; va_start(ap, format); n = PQgetvf(res, err, tup_num, format, ap); va_end(ap); return n; } int PQgetvf(const PGresult *res, PGerror *err, int tup_num, const char *format, va_list ap) { int r; PGtypeHandler *h; PGtypeArgs args; int typpos = 0; int flags; Oid ftype; PGtypeData *resData; PGtypeSpec *spec = NULL; char tmp[200]; if (!res) { PQseterror(err, "PGresult cannot be NULL"); return FALSE; } if (!(resData = (PGtypeData *) PQresultInstanceData(res, pqt_eventproc))) { PQseterror(err, "PGresult at %p has no event data", res); return FALSE; } va_copy(args.ap, ap); /* "@name" format, lookup typeSpec in cache */ if(format && *format == '@') { spec = pqt_getspec(resData->typspecs, resData->typspeccnt, format + 1); /* If we didn't find a type spec, this is an error. A format string * with a '@' as its first character is reserved. */ if (!spec) { PQseterror(err, "No such prepared specifier name: '%s'", format + 1); va_end(args.ap); return FALSE; } } while (format && *format) { if (spec) { /* done, no more handlers in cached spec string. */ if (typpos == spec->idcnt) break; h = pqt_gethandlerbyid(resData->typhandlers, resData->typhcnt, spec->idlist[typpos]); /* should be an unusual, or a "will never happen", situation */ if (!h) { va_end(args.ap); PQseterror(err, "Unknown type handler id (column '%s' at position %d", PQfname(res, typpos), typpos+1); return FALSE; } flags = (int) spec->flags[typpos]; typpos++; } else { format = pqt_parse(err, format, resData->typhandlers, resData->typhcnt, NULL, 0, &h, NULL, &typpos, &flags); if (!format) { va_end(args.ap); return FALSE; } if (!h) continue; } if (flags & TYPFLAG_BYNAME) args.get.field_num = PQfnumber(res, va_arg(args.ap, const char *)); else args.get.field_num = va_arg(args.ap, int); /* simplify life for handlers by checking getvalue's return here. */ if (args.get.field_num < 0 || !PQgetvalue(res, tup_num, args.get.field_num)) { PQseterror(err, "Invalid tup_num[%d].field_num[%d] (column '%s' at position %d)", tup_num, args.get.field_num, PQfname(res, typpos-1), typpos); va_end(args.ap); return FALSE; } ftype = PQftype(res, args.get.field_num); if (((flags & TYPFLAG_ARRAY) && ftype != h->typoid_array) || (!(flags & TYPFLAG_ARRAY) && ftype != h->typoid)) { // try to get name of actual type char tname[200]; PGtypeHandler *hh = pqt_gethandlerbyoid(resData->typhandlers, resData->typhcnt, ftype); if (hh != NULL) pqt_fqtn(tname, sizeof(tname), hh->typschema, hh->typname); else strcpy(tname, "???"); Oid oid = (flags & TYPFLAG_ARRAY) ? h->typoid_array : h->typoid; PQseterror(err, "Trying to get type '%s' (%u) but server returned '%s' (%u) (column '%s' at position %d)", pqt_fqtn(tmp, sizeof(tmp), h->typschema, h->typname), oid, tname, ftype, PQfname(res, typpos-1), typpos); va_end(args.ap); return FALSE; } args.is_put = 0; args.get.result = (PGresult *) res; args.format = PQfformat(res, args.get.field_num); args.fmtinfo = &resData->fmtinfo; args.get.tup_num = tup_num; args.is_ptr = (flags & TYPFLAG_POINTER) ? 1 : 0; args.err = err; args.typpos = typpos; args.typhandler = h; args.errorf = pqt_argserrorf; args.super = pqt_argssuper; if (flags & TYPFLAG_ARRAY) r = pqt_get_array(&args); else r = h->typget(&args); if (r == -1) { va_end(args.ap); return FALSE; } } va_end(args.ap); return TRUE; } /* -------------------------------- * Exec and Send functions */ PGresult * PQexecf(PGconn *conn, PGerror *err, const char *cmdspec, ...) { va_list ap; PGresult *res; va_start(ap, cmdspec); res = PQexecvf(conn, err, cmdspec, ap); va_end(ap); return res; } PGresult * PQexecvf(PGconn *conn, PGerror *err, const char *cmdspec, va_list ap) { PGresult *res; (void) _execvf(conn, err, cmdspec, ap, &res); return res; } int PQsendf(PGconn *conn, PGerror *err, const char *cmdspec, ...) { int n; va_list ap; va_start(ap, cmdspec); n = PQsendvf(conn, err, cmdspec, ap); va_end(ap); return n; } int PQsendvf(PGconn *conn, PGerror *err, const char *cmdspec, va_list ap) { return _execvf(conn, err, cmdspec, ap, NULL); } PGresult * PQparamExec(PGconn *conn, PGerror *err, PGparam *param, const char *command, int resultFormat) { BUILD_ARRAYS(PGresult *); command = getCommand(conn, err, param, command); if (!command) { r = NULL; } else { r = PQexecParams(conn, command, vcnt, oids, (const char *const * ) vals, lens, fmts, resultFormat); r = copyExecError(conn, err, r); } RETURN_RESULT; } int PQparamSendQuery(PGconn *conn, PGerror *err, PGparam *param, const char *command, int resultFormat) { BUILD_ARRAYS(int); command = getCommand(conn, err, param, command); if (!command) { r = FALSE; } else { r = PQsendQueryParams(conn, command, vcnt, oids, (const char *const * ) vals, lens, fmts, resultFormat); if (!r) PQseterror(err, "PGconn: %s", PQerrorMessage(conn)); } RETURN_RESULT; } PGresult * PQparamExecPrepared(PGconn *conn, PGerror *err, PGparam *param, const char *stmtName, int resultFormat) { BUILD_ARRAYS(PGresult *); r = PQexecPrepared(conn, stmtName, vcnt, (const char *const * ) vals, lens, fmts, resultFormat); r = copyExecError(conn, err, r); RETURN_RESULT; } int PQparamSendQueryPrepared(PGconn *conn, PGerror *err, PGparam *param, const char *stmtName, int resultFormat) { BUILD_ARRAYS(int); r = PQsendQueryPrepared(conn, stmtName, vcnt, (const char *const * ) vals, lens, fmts, resultFormat); if (!r) PQseterror(err, "PGconn: %s", PQerrorMessage(conn)); RETURN_RESULT; } /* Called by PQexecvf and PQsendvf. When resp is NULL, PQparamSendQuery * is used to execute the command. Otherwise, PQparamExec is used. The * return value is always zero when resp is not NULL. When resp is NULL, * the return value is zero for error and non-zero for success (identical * to PQparamSendQuery). */ static int _execvf(PGconn *conn, PGerror *err, const char *cmdspec, va_list ap, PGresult **resp) { int retval = 0; size_t stmt_len=0; char buffer[8192]; /* could be larger these days but be conservative */ char *stmt = NULL; PGparam *param = NULL; if (resp) *resp = NULL; if(!conn) { PQseterror(err, "PGconn cannot be NULL"); return FALSE; } if(!cmdspec || !*cmdspec) { PQseterror(err, "cmdspec cannot be NULL or an empty string"); return FALSE; } /* No stmt buf required for preapred type specs */ if (*cmdspec != '@') { /* The resulting parameterized command is guarenteed to be smaller * than the cmdspec. When needed, enlarge stmt buf to cmdspec length. */ stmt_len = strlen(cmdspec) + 1; /* stack buffer is too small, use heap */ if (stmt_len > sizeof(buffer)) { if (!(stmt = (char *) malloc(stmt_len))) { PQseterror(err, PQT_OUTOFMEMORY); return FALSE; } } else { stmt = buffer; stmt_len = sizeof(buffer); } } if ((param = PQparamCreate(conn, err))) { if (PQputvf(param, err, stmt, stmt_len, cmdspec, ap)) { const char *s = stmt ? stmt : cmdspec; if (resp) *resp = PQparamExec(conn, err, param, s, 1); else retval = PQparamSendQuery(conn, err, param, s, 1); } PQparamClear(param); } if (stmt && stmt != buffer) free(stmt); return retval; } /* * Assigns param values to param arrays, for use with postgres * parameter API. 'buf' is expected to be PARAM_STACKSIZE bytes. If more * memory is required, memory is allocated and assigned to *buf, which * must be freed by caller. To determine if *buf was allocated, compare * its address to the initially provided stack address. * Returns 1 on success and 0 on error. */ static int buildArrays(PGparam *param, PGerror *err, char **buf, Oid **oids, char ***vals, int **lens, int **fmts) { int n; /* no params to assign */ if (param->vcnt == 0) return 1; /* required memory size for the 4 param arrays */ n = (int) ((sizeof(void *) * param->vcnt) + /* values */ ((sizeof(int) * 2) * param->vcnt) + /* lengths and formats */ (sizeof(Oid) * param->vcnt)); /* oids */ /* required memory is too large for stack buffer, get some heap */ if (n > PARAM_STACKSIZE) { char *p; if (!(p = (char *) malloc(n))) { PQseterror(err, PQT_OUTOFMEMORY); return 0; } *buf = p; } /* give arrays memory from buffer, which could be stack or heap. */ *vals = (char **) *buf; *lens = (int *) (*buf + (sizeof(void *) * param->vcnt)); *fmts = (*lens) + param->vcnt; *oids = (Oid *) ((*fmts) + param->vcnt); /* loop param values and assign value, length, format * and oid to arrays. */ for (n=0; n < param->vcnt; n++) { (*oids)[n] = param->vals[n].oid; (*vals)[n] = param->vals[n].data; (*lens)[n] = param->vals[n].datal; (*fmts)[n] = param->vals[n].format; } return 1; } static PGresult * copyExecError(PGconn *conn, PGerror *err, PGresult *r) { if (!r) { PQseterror(err, "PGconn: %s", PQerrorMessage(conn)); return NULL; } switch (PQresultStatus(r)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: break; default: { PQseterror(err, "PGresult: %s", PQresultErrorMessage(r)); break; } } return r; } /* Using the param is preferred when both conn and param are provided. * The conn is there in case the exec has no parameters, NULL param. */ static const char * getCommand(PGconn *conn, PGerror *err, PGparam *param, const char *command) { PGtypeSpec *spec; int typspeccnt = 0; PGtypeSpec *typspecs = NULL; if (!command) { PQseterror(err, "command to execute cannot be NULL"); return NULL; } if (*command != '@') return command; if (param) { typspecs = param->typspecs; typspeccnt = param->typspeccnt; } /* Try to get instance data from the conn */ if (!typspecs || typspeccnt == 0) { PGtypeData *data = PQinstanceData(conn, pqt_eventproc); if (!data) { PQseterror(err, "PGconn at %p has no event data", conn); return NULL; } typspecs = data->typspecs; typspeccnt = data->typspeccnt; } spec = pqt_getspec(typspecs, typspeccnt, command + 1); /* If we didn't find a type spec, this is an error. A format string * with an '@' as its first character is reserved. */ if (!spec) { PQseterror(err, "No such prepared specifier name: '%s'", command + 1); return NULL; } /* make sure type spec was prepared with a statement */ if (!spec->stmt || !*spec->stmt) { PQseterror(err, "Prepared specifier name '%s' has no statement", command + 1); return NULL; } return (const char *) spec->stmt; }