hpqtypes-1.2.2: libpqtypes/src/exec.c
/*
* 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;
}