hpqtypes-1.3.1: libpqtypes/src/handler.c
/*
* handler.c
* Type handler management functions, the core of the handler system.
*
* 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"
/* -------------------------
* Built-in handler defaults:
* base_id, nattrs, freeAttDescs, attDescsBuf, attDescs
*/
#define __HANDLER_DEFAULTS__ -1, 0, 0, {{0}}, NULL
/* id, schema, typname, typlen, oid, arroid, typput, typget, defaults */
static PGtypeHandler pg_handlers[] = {
/* character types */
{0, "pg_catalog", "char", 1, CHAROID, 1002, pqt_put_char,
pqt_get_char, __HANDLER_DEFAULTS__},
{1, "pg_catalog", "varchar", -1, VARCHAROID, 1015, pqt_put_text,
pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */
{2, "pg_catalog", "bpchar", -1, BPCHAROID, 1014, pqt_put_text,
pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */
{3, "pg_catalog", "text", -1, TEXTOID, 1009, pqt_put_text,
pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */
/* boolean types */
{4, "pg_catalog", "bool", 1, BOOLOID, 1000, pqt_put_bool,
pqt_get_bool, __HANDLER_DEFAULTS__},
/* numeric types */
{5, "pg_catalog", "int2", 2, INT2OID, 1005, pqt_put_int2,
pqt_get_int2, __HANDLER_DEFAULTS__},
{6, "pg_catalog", "int4", 4, INT4OID, 1007, pqt_put_int4,
pqt_get_int4, __HANDLER_DEFAULTS__},
{7, "pg_catalog", "int8", 8, INT8OID, 1016, pqt_put_int8,
pqt_get_int8, __HANDLER_DEFAULTS__},
{8, "pg_catalog", "float4", 4, FLOAT4OID, 1021, pqt_put_float4,
pqt_get_float4, __HANDLER_DEFAULTS__},
{9, "pg_catalog", "float8", 8, FLOAT8OID, 1022, pqt_put_float8,
pqt_get_float8, __HANDLER_DEFAULTS__},
{10, "pg_catalog", "numeric", -1, NUMERICOID, 1231, pqt_put_numeric,
pqt_get_numeric, __HANDLER_DEFAULTS__},
/* bytea types */
{11, "pg_catalog", "bytea", -1, BYTEAOID, 1001, pqt_put_bytea,
pqt_get_bytea, __HANDLER_DEFAULTS__}, /* supports ptr */
/* geometric types */
{12, "pg_catalog", "point", 16, POINTOID, 1017, pqt_put_point,
pqt_get_point, __HANDLER_DEFAULTS__},
{13, "pg_catalog", "lseg", 32, LSEGOID, 1018, pqt_put_lseg,
pqt_get_lseg, __HANDLER_DEFAULTS__},
{14, "pg_catalog", "box", 32, BOXOID, 1020, pqt_put_box,
pqt_get_box, __HANDLER_DEFAULTS__},
{15, "pg_catalog", "circle", 24, CIRCLEOID, 719, pqt_put_circle,
pqt_get_circle, __HANDLER_DEFAULTS__},
{16, "pg_catalog", "path", -1, PATHOID, 1019, pqt_put_path,
pqt_get_path, __HANDLER_DEFAULTS__},
{17, "pg_catalog", "polygon", -1, POLYGONOID, 1027, pqt_put_polygon,
pqt_get_polygon, __HANDLER_DEFAULTS__},
/* monetary types */
{18, "pg_catalog", "money", 8, CASHOID, 791, pqt_put_money,
pqt_get_money, __HANDLER_DEFAULTS__},
/* network address typess */
{19, "pg_catalog", "inet", -1, INETOID, 1041, pqt_put_inet,
pqt_get_inet, __HANDLER_DEFAULTS__},
{20, "pg_catalog", "cidr", -1, CIDROID, 651, pqt_put_inet,
pqt_get_cidr, __HANDLER_DEFAULTS__},
{21, "pg_catalog", "macaddr", 6, MACADDROID, 1040, pqt_put_macaddr,
pqt_get_macaddr, __HANDLER_DEFAULTS__},
/* date & time types */
{22, "pg_catalog", "time", 8, TIMEOID, 1183, pqt_put_time,
pqt_get_time, __HANDLER_DEFAULTS__},
{23, "pg_catalog", "timetz", 12, TIMETZOID, 1270, pqt_put_timetz,
pqt_get_timetz, __HANDLER_DEFAULTS__},
{24, "pg_catalog", "date", 4, DATEOID, 1182, pqt_put_date,
pqt_get_date, __HANDLER_DEFAULTS__},
{25, "pg_catalog", "timestamp", 8, TIMESTAMPOID, 1115, pqt_put_timestamp,
pqt_get_timestamp, __HANDLER_DEFAULTS__},
{26, "pg_catalog", "timestamptz", 8, TIMESTAMPTZOID, 1185,
pqt_put_timestamptz, pqt_get_timestamptz, __HANDLER_DEFAULTS__},
{27, "pg_catalog", "interval", 16, INTERVALOID, 1187, pqt_put_interval,
pqt_get_interval, __HANDLER_DEFAULTS__},
/* object identifier types */
{28, "pg_catalog", "oid", 4, OIDOID, 1028, pqt_put_int4,
pqt_get_int4, __HANDLER_DEFAULTS__},
/* UUID types */
{29, "pg_catalog", "uuid", 16, UUIDOID, 2951, pqt_put_uuid,
pqt_get_uuid, __HANDLER_DEFAULTS__},
/* Record types (composites) */
{30, "pg_catalog", "record", -1, RECORDOID, 0, pqt_put_record,
pqt_get_record, __HANDLER_DEFAULTS__},
/* pqt types */
{31, "pqt", "str", -1, InvalidOid, 0, pqt_put_str,
NULL, __HANDLER_DEFAULTS__}, /* supports ptr */
{32, "pqt", "null", -1, InvalidOid, 0, pqt_put_null,
NULL, __HANDLER_DEFAULTS__},
/* more character types */
{33, "pg_catalog", "name", -1, NAMEOID, 1003, pqt_put_text,
pqt_get_text, __HANDLER_DEFAULTS__}, /* supports ptr */
/* text passed as bytea to avoid double copy with ByteStringS
* and silent truncation of strings containting NULL characters */
{34, "pg_catalog", "btext", -1, TEXTOID, 1009, pqt_put_bytea,
pqt_get_bytea, __HANDLER_DEFAULTS__},
{35, "pg_catalog", "xml", -1, XMLOID, 143, pqt_put_bytea,
pqt_get_bytea, __HANDLER_DEFAULTS__}, /* supports ptr */
};
static int
expandHandlers(PGtypeData *typeData, PGerror *err);
static PGrecordAttDesc *
initAttDescs(PGerror *err, PGtypeHandler *h, char *attrs);
static char *
parseType(PGerror *err, const char *spec, char *schema, char *typname, int typpos);
static int
getTypeParams(PGconn *conn, PGerror *err, PGregisterType *types, int count,
PGarray *names, PGarray *schemas);
static int
checkTypeLookups(PGresult *res, PGerror *err, PGregisterType *types, int count);
/* If res is NULL, non-blocking send is used, otherwise a blocking
* exec is issued and *res contains the result. Returns zero on
* error and non-zero on success.
*/
static int
performRegisterQuery(PGconn *conn, PGerror *err, int which, PGregisterType *types,
int count, PGresult **res);
/* Called by PQregisterTypes() for each type provided. */
static int
registerSubClass(PGtypeData *connData, PGerror *err, const char *type_name,
PGtypeProc typput, PGtypeProc typget);
int
PQinitTypes(PGconn *conn)
{
return PQregisterEventProc(conn, pqt_eventproc, "pqtypes", NULL);
}
/* Deprecated */
int
PQregisterSubClasses(PGconn *conn, PGerror *err, PGregisterType *types, int count)
{
return PQregisterTypes(conn, err, PQT_SUBCLASS, types, count, 0);
}
/* Deprecated */
int
PQregisterUserDefinedTypes(PGconn *conn, PGerror *err, PGregisterType *types, int count)
{
return PQregisterTypes(conn, err, PQT_USERDEFINED, types, count, 0);
}
/* Deprecated */
int
PQregisterComposites(PGconn *conn, PGerror *err, PGregisterType *types, int count)
{
return PQregisterTypes(conn, err, PQT_COMPOSITE, types, count, 0);
}
int
PQregisterTypes(PGconn *conn, PGerror *err, int which, PGregisterType *types,
int count, int async)
{
int n = FALSE;
if (!conn)
{
PQseterror(err, "PGconn cannot be NULL");
return FALSE;
}
if (!types)
{
PQseterror(err, "PGregisterType[] cannot be NULL");
return FALSE;
}
if (count < 0)
{
PQseterror(err, "PGregisterType[] count cannot be less than zero");
return FALSE;
}
/* nothing to do, silently ignore it */
if (count == 0)
return TRUE;
if (which == PQT_SUBCLASS)
{
int i;
PGtypeData *connData;
if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc)))
{
PQseterror(err, "PGconn is missing event data");
return FALSE;
}
for (i=0; i < count; i++)
{
n = registerSubClass(connData, err, types[i].typname,
types[i].typput, types[i].typget);
if (!n)
return FALSE;
}
}
else
{
PGresult *res = NULL;
n = performRegisterQuery(conn, err, which, types, count, async ? NULL : &res);
/* If not async, register the result and clear it. */
if (n && !async)
{
n = PQregisterResult(conn, err, which, types, count, res);
PQclear(res);
}
}
return n;
}
int
PQregisterResult(PGconn *conn, PGerror *err, int which, PGregisterType *types,
int count, PGresult *res)
{
int i;
PGtypeData *connData;
char typname[PQT_MAXIDLEN + 1];
char typschema[PQT_MAXIDLEN + 1];
/* inherit typput and typget from record type */
PGtypeHandler *h_rec = pqt_gethandler(NULL, 0, "pg_catalog", "record");
if (!conn)
{
PQseterror(err, "PGconn cannot be NULL");
return FALSE;
}
if (!res)
{
PQseterror(err, "PGresult cannot be NULL");
return FALSE;
}
if (which == PQT_SUBCLASS)
{
PQseterror(err, "Cannot call PQregisterResult for a subclass registration.");
return FALSE;
}
if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc)))
{
PQseterror(err, "PGconn is missing event data");
return FALSE;
}
if (!types)
{
PQseterror(err, "PGregisterType[] cannot be NULL");
return FALSE;
}
if (count < 0)
{
PQseterror(err, "PGregisterType[] count cannot be less than zero");
return FALSE;
}
if(!checkTypeLookups(res, err, types, count))
return FALSE;
for (i=0; i < PQntuples(res); i++)
{
int flags;
PGint2 typlen;
PGtypeHandler *h;
if (which == PQT_USERDEFINED && !types[i].typput && !types[i].typget)
{
PQseterror(err, "Must provide a put and/or a get routine: '%s'",
types[i].typname);
return FALSE;
}
/* make sure conn's type handlers array is large enough */
if (!expandHandlers(connData, err))
return FALSE;
/* create the handler */
h = &connData->typhandlers[connData->typhcnt];
memset(h, 0, sizeof(PGtypeHandler));
if (!PQgetf(res, err, i, "%oid %oid %int2", 1, &h->typoid,
2, &h->typoid_array, 3, &typlen))
{
return FALSE;
}
h->id = connData->typhcnt + countof(pg_handlers);
h->typlen = (int) typlen;
h->base_id = -1;
if (which == PQT_USERDEFINED)
{
h->typput = types[i].typput;
h->typget = types[i].typget;
}
else
{
h->typput = h_rec->typput;
h->typget = h_rec->typget;
}
/* parse out type and schema names again */
(void ) pqt_parsetype(err, types[i].typname, typschema, typname, &flags, 1);
pqt_strcpy(h->typschema, sizeof(h->typschema), typschema);
pqt_strcpy(h->typname, sizeof(h->typname), typname);
/* Process composite attributes */
if(which == PQT_COMPOSITE)
{
PGtext attrs;
int nattrs;
PGrecordAttDesc *attDescs;
if (!PQgetf(res, err, i, "%text", 4, &attrs))
{
return FALSE;
}
if (!(attDescs = initAttDescs(err, h, attrs)))
return FALSE;
for (nattrs=0; *attrs; nattrs++)
{
char *p;
char *name;
int len;
/* Attribute Text Encoding:
* "attoid,attlen,atttypmod,name_hex attoid,etc..."
*/
attDescs[nattrs].attoid = (int) strtol(attrs, &attrs, 10);
attDescs[nattrs].attlen = (int) strtol(attrs + 1, &attrs, 10);
attDescs[nattrs].atttypmod = (int) strtol(attrs + 1, &attrs, 10);
/* skip comma before name */
attrs++;
/* attribute name in hex */
if (!(p = strchr(attrs, ' ')))
p = attrs + strlen(attrs); /* last attr, point at NUL */
/* truncate name if it exceeds buffer */
len = (int) (p - attrs);
if (len >= (int) sizeof(attDescs[nattrs].attname))
len = (int) (sizeof(attDescs[nattrs].attname) - 1);
/* hex decode and copy */
for (name = attDescs[nattrs].attname; attrs < p; attrs += 2)
*name++ = (char) (pqt_hex_to_dec(attrs[0]) << 4)
| pqt_hex_to_dec(attrs[1]);
*name = 0;
}
h->nattrs = nattrs;
h->attDescs = attDescs;
}
connData->typhcnt++;
}
return TRUE;
}
int
PQclearTypes(PGconn *conn, PGerror *err)
{
PGtypeData *connData;
if (!conn)
{
PQseterror(err, "PGconn cannot be NULL");
return FALSE;
}
if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc)))
{
PQseterror(err, "PGconn is missing event data");
return FALSE;
}
pqt_cleartypes(connData);
return TRUE;
}
/* Do not call when hcnt is 0. This returns NULL when malloc fails.
* Passing in 0 could only do the same thing (ambiguos), so it is simply
* not handled and may dump core.
*/
PGtypeHandler *
pqt_duphandlers(PGtypeHandler *handlers, int hcnt)
{
int i;
PGtypeHandler *h = (PGtypeHandler *) malloc(hcnt * sizeof(PGtypeHandler));
if (!h)
return NULL;
/* In the most common cases, this is the total cost of the dup.
* Previously, the handler had 4 inner strings that required a deep
* copy. Surprisingly, this had a noticeable overhead. This was
* solved by using fixed length buffers in the type handler struct.
* Also, a fixed length attDescs buffer was added to avoid having
* to perform a deep copy for the common cases, 16 or less attrs.
*/
memcpy(h, handlers, hcnt * sizeof(PGtypeHandler));
/* Possibly deep copy PGrecordAttDesc array. Otherwise, assign it
* to the fixed length buffer.
*/
for (i=0; i < hcnt; i++)
{
if (h[i].nattrs == 0)
continue;
/* There are attributes but the attDescs buffer can be used rather
* than allocating and copying. The data was copied during the
* handlers memcpy prior to this loop.
*/
if (!h[i].freeAttDescs)
{
h[i].attDescs = h[i].attDescsBuf;
continue;
}
/* ------------------------------
* Must allocate and copy.
*/
h[i].attDescs = (PGrecordAttDesc *) malloc(
h[i].nattrs * sizeof(PGrecordAttDesc));
if (!h[i].attDescs)
{
pqt_freehandlers(h, i+1);
return NULL;
}
memcpy(h[i].attDescs, handlers[i].attDescs,
h[i].nattrs * sizeof(PGrecordAttDesc));
}
return h;
}
void
pqt_cleartypes(PGtypeData *typeData)
{
if (typeData)
{
pqt_freehandlers(typeData->typhandlers, typeData->typhcnt);
typeData->typhandlers = NULL;
typeData->typhcnt = 0;
typeData->typhmax = 0;
}
}
void
pqt_freehandlers(PGtypeHandler *handlers, int hcnt)
{
int i;
/* Free attDescs */
for (i=0; i < hcnt; i++)
if (handlers[i].freeAttDescs && handlers[i].attDescs)
free(handlers[i].attDescs);
if (handlers)
free(handlers);
}
PGtypeHandler *
pqt_gethandler(PGtypeHandler *handlers, int hcnt,
const char *schema, const char *typname)
{
int i;
int noschema = !schema || !*schema;
if (!typname || !*typname)
return NULL;
/* user registered types are searched first */
for (i=0; i < hcnt; i++)
{
if ((noschema || strcmp(handlers[i].typschema, schema)==0) &&
strcmp(handlers[i].typname, typname)==0)
{
return &handlers[i];
}
}
/* builtin types searched last */
for (i=0; i < countof(pg_handlers); i++)
{
if ((noschema || strcmp(pg_handlers[i].typschema, schema)==0) &&
strcmp(pg_handlers[i].typname, typname)==0)
{
return &pg_handlers[i];
}
}
return NULL;
}
PGtypeHandler *
pqt_gethandlerbyid(PGtypeHandler *handlers, int hcnt, int id)
{
if (id <= -1)
return NULL;
if (id < countof(pg_handlers))
return &pg_handlers[id];
id -= countof(pg_handlers);
if (id >= hcnt)
return NULL;
return &handlers[id];
}
PGtypeHandler *
pqt_gethandlerbyoid(PGtypeHandler *handlers, int hcnt, Oid oid)
{
int pg_handlers_count = countof(pg_handlers);
for (int i = 0; i < pg_handlers_count; ++i)
{
if (pg_handlers[i].typoid == oid)
return &pg_handlers[i];
}
for (int i = 0; i < hcnt; ++i)
{
if (handlers[i].typoid == oid)
return &handlers[i];
}
return NULL;
}
int
pqt_argssuper(PGtypeArgs *args, ...)
{
int r;
va_list ap;
PGtypeHandler *baseclass;
PGtypeHandler *subclass = args->typhandler;
PGtypeData *resData = NULL;
if (!args->is_put)
{
resData = (PGtypeData *) PQresultInstanceData(
args->get.result, pqt_eventproc);
if (!resData)
return args->errorf(args, "PGresult is missing event data");
}
/* should always work, but play it safe */
baseclass = pqt_gethandlerbyid(
args->is_put ? args->put.param->typhandlers : resData->typhandlers,
args->is_put ? args->put.param->typhcnt : resData->typhcnt,
subclass->base_id);
if (!baseclass)
return args->errorf(args, "type handler has no base type");
args->typhandler = baseclass;
va_copy(ap, args->ap);
va_start(args->ap, args);
r = args->is_put ? baseclass->typput(args) : baseclass->typget(args);
va_copy(args->ap, ap);
args->typhandler = subclass;
return r;
}
/* FQTN standards for Fully Qualified Type Name. Returns a pointer to out.
* Only returns NULL if out is NULL or outl <= 0.
*/
char *
pqt_fqtn(char *out, size_t outl, const char *schema, const char *typname)
{
int r;
int have_schema = schema && *schema;
if (!out || outl<=0)
return NULL;
*out = 0;
if (!typname || !*typname)
return out;
r = pqt_snprintf(out, outl, "%s%s%s", have_schema ? schema : "",
have_schema ? "." : "", typname);
if (r < 0)
{
*out = 0;
return out;
}
return out;
}
/* only checks builin types or pqt types. User registered types must
* be checked by the user's handler functions.
*/
int
pqt_allowsptr(PGtypeHandler *h)
{
/* pg_catalog.[bpchar, varchar, name, text, bytea] */
if (strcmp(h->typschema, "pg_catalog")==0)
{
if (strcmp(h->typname, "bpchar") &&
strcmp(h->typname, "varchar") &&
strcmp(h->typname, "text") &&
strcmp(h->typname, "bytea") &&
strcmp(h->typname, "name"))
{
return FALSE;
}
}
/* pqt.str */
else if (strcmp(h->typschema, "pqt")==0 && strcmp(h->typname, "str"))
{
return FALSE;
}
return TRUE;
}
void
pqt_getfmtinfo(const PGconn *conn, PGtypeFormatInfo *info)
{
const char *value;
memset(info, 0, sizeof(PGtypeFormatInfo));
if ((value = PQparameterStatus(conn, "DateStyle")))
pqt_strcpy(info->datestyle, sizeof(info->datestyle), value);
if ((value = PQparameterStatus(conn, "integer_datetimes")))
info->integer_datetimes = strcmp(value, "on")==0 ? TRUE : FALSE;
info->sversion = PQserverVersion(conn);
info->pversion = PQprotocolVersion(conn);
}
static int registerSubClass(PGtypeData *connData, PGerror *err, const char *type_name,
PGtypeProc typput, PGtypeProc typget)
{
char *s;
PGtypeHandler *h_sub;
PGtypeHandler *h_base;
char sub_typschema[PQT_MAXIDLEN + 1];
char sub_typname[PQT_MAXIDLEN + 1];
char base_typschema[PQT_MAXIDLEN + 1];
char base_typname[PQT_MAXIDLEN + 1];
char sub_fqtn[200];
char base_fqtn[200];
if (!(s = parseType(err, type_name, sub_typschema, sub_typname, 1)))
return FALSE;
if (*s != '=')
{
PQseterror(err, "Missing inheritence operator '=': %s", type_name);
return FALSE;
}
if (!parseType(err, s + 1, base_typschema, base_typname, 1))
return FALSE;
/* lookup the base handler */
h_base = pqt_gethandler(connData->typhandlers, connData->typhcnt,
base_typschema, base_typname);
if (!h_base)
{
PQseterror(err, "typname '%s' does not exist, '%s' cannot sub-class it",
pqt_fqtn(base_fqtn, sizeof(base_fqtn), base_typschema, base_typname),
pqt_fqtn(sub_fqtn, sizeof(sub_fqtn), sub_typschema, sub_typname));
return FALSE;
}
/* cannot sub-class a record type */
if (h_base->typoid == RECORDOID)
{
PQseterror(err, "Cannot sub-class pg_catalog.record '%s'",
pqt_fqtn(sub_fqtn, sizeof(sub_fqtn), sub_typschema, sub_typname));
return FALSE;
}
if (!expandHandlers(connData, err))
return FALSE;
h_sub = &connData->typhandlers[connData->typhcnt];
memset(h_sub, 0, sizeof(PGtypeHandler));
h_sub->id = connData->typhcnt + countof(pg_handlers);
h_sub->typlen = h_base->typlen;
h_sub->typoid = h_base->typoid;
h_sub->typoid_array = h_base->typoid_array;
h_sub->typput = typput;
h_sub->typget = typget;
h_sub->base_id = h_base->id;
pqt_strcpy(h_sub->typschema,
sizeof(h_sub->typschema), sub_typschema);
pqt_strcpy(h_sub->typname,
sizeof(h_sub->typname), sub_typname);
connData->typhcnt++;
return TRUE;
}
static int
expandHandlers(PGtypeData *typeData, PGerror *err)
{
int hmax;
PGtypeHandler *h;
if (typeData->typhcnt < typeData->typhmax)
return TRUE;
hmax = typeData->typhmax ? (typeData->typhmax * 3) / 2 : 8;
h = (PGtypeHandler *) pqt_realloc(
typeData->typhandlers, sizeof(PGtypeHandler) * hmax);
if (!h)
{
PQseterror(err, PQT_OUTOFMEMORY);
return FALSE;
}
typeData->typhandlers = h;
typeData->typhmax = hmax;
return TRUE;
}
static int
checkTypeLookups(PGresult *res, PGerror *err, PGregisterType *types, int count)
{
int i;
int ntups = PQntuples(res);
/* The tuple count must match the requested count. The server omits
* tuples for types it did not find. For those it did find, it returns
* a sequenctial index. The first gap found is our missing type. This
* only reports about the first missing type.
*/
if (ntups == count)
return TRUE;
for (i=0; i < ntups; i++)
{
int idx;
if (!PQgetf(res, err, i, "%int4", 0, &idx))
return FALSE;
/* 'i' should always match idx-1, postgresql arrays are 1-based.
* This is a missing type, first gap in the sequence.
*/
if (i != idx-1)
break;
}
PQseterror(err, "server type lookup failed: could not find '%s'",
types[i].typname);
return FALSE;
}
/* This is part of a performance enhancement for getting arrays
* and/or composites. They require generating PGresults which
* causes pqt_duphandlers() to run. Its amazing how much a simple
* malloc+memcpy costs after around 10000 or so. The common case
* avoids this by using a fixed length PGrecordAttDesc buffer. If
* there are a large number of attributes, the slower path is used.
* Again this is small, maybe 10% of the overall 63% win.
*/
static PGrecordAttDesc *initAttDescs(PGerror *err, PGtypeHandler *h, char *attrs)
{
char *p;
int nattrs = 1;
PGrecordAttDesc *attDescs;
for(p = attrs; *p; nattrs++, ++p)
if(!(p = strchr(p, ' ')))
break;
if (nattrs < (int) (sizeof(h->attDescsBuf) / sizeof(h->attDescsBuf[0])))
{
h->freeAttDescs = 0;
attDescs = h->attDescsBuf;
}
else
{
attDescs = (PGrecordAttDesc *) malloc(nattrs * sizeof(PGrecordAttDesc));
if (!attDescs)
{
PQseterror(err, PQT_OUTOFMEMORY);
return NULL;
}
h->freeAttDescs = 1;
}
return attDescs;
}
/* wraps pqt_parsetype to toggle out illegal flags during a register */
static char *parseType(PGerror *err, const char *spec, char *typschema, char *typname,
int typpos)
{
char *s;
int flags;
if (!(s = pqt_parsetype(err, spec, typschema, typname, &flags, typpos)))
return NULL;
if (flags & TYPFLAG_INVALID)
return NULL;
if (flags & TYPFLAG_ARRAY)
{
PQseterror(err, "Cannot use an array[] during a type handler registration.");
return NULL;
}
if (flags & TYPFLAG_POINTER)
{
PQseterror(err, "Cannot use a type* during a type handler registration.");
return NULL;
}
return s;
}
static int getTypeParams(PGconn *conn, PGerror *err, PGregisterType *types, int count,
PGarray *names, PGarray *schemas)
{
int i;
names->ndims = 0;
schemas->ndims = 0;
if (!(names->param = PQparamCreate(conn, err)))
return FALSE;
if (!(schemas->param = PQparamCreate(conn, err)))
{
PQparamClear(names->param);
return FALSE;
}
for (i=0; i < count; i++)
{
char typname[PQT_MAXIDLEN + 1];
char typschema[PQT_MAXIDLEN + 1];
char *s = parseType(err, types[i].typname, typschema, typname, 1);
if (!s)
{
PQparamClear(names->param);
PQparamClear(schemas->param);
return FALSE;
}
s = *typschema ? typschema : NULL;
if (!PQputf(names->param, err, "%text", typname) ||
!PQputf(schemas->param, err, "%text", s))
{
PQparamClear(names->param);
PQparamClear(schemas->param);
return FALSE;
}
}
return TRUE;
}
/* Lookup types, including composites. Arguments are:
* schemas text[], type_names text[], want_attrs bool
*/
#define LOOKUP_TYPES \
"WITH nspnames AS" \
"(" \
" SELECT * FROM information_schema._pg_expandarray(%text[])" \
")," \
"typnames AS" \
"(" \
" SELECT * FROM information_schema._pg_expandarray(%text[])" \
")," \
"curpath AS" \
"(" \
" SELECT * FROM information_schema._pg_expandarray(current_schemas(true))" \
")," \
"composites AS" \
"(" \
" SELECT n.n AS idx, n.x AS nspname, t.x AS typname" \
" FROM nspnames n LEFT JOIN typnames t ON n.n = t.n" \
" AND n.x IS NOT NULL" \
" WHERE t.x IS NOT NULL" \
" UNION ALL" \
" SELECT n.n AS idx," \
" (" \
" SELECT n.nspname from pg_type nt JOIN pg_namespace n ON " \
"nt.typnamespace = n.oid" \
" JOIN curpath c ON c.x = n.nspname" \
" WHERE nt.typname = t.x" \
" ORDER BY c.n LIMIT 1" \
" ) AS nspname, t.x AS typname" \
" FROM nspnames n LEFT JOIN typnames t ON n.n = t.n" \
" AND n.x IS NULL" \
" WHERE t.x IS NOT NULL" \
")" \
"SELECT idx, t.oid AS typoid, a.oid AS arroid, t.typlen," \
" (" \
" CASE WHEN %bool THEN (" \
" SELECT array_to_string" \
" (" \
" ARRAY" \
" (" \
" SELECT CASE WHEN tt.typtype = 'd' THEN tt.typbasetype " \
"ELSE a.atttypid END ||" \
" ',' || attlen || ',' || atttypmod || ',' || " \
"encode(attname::bytea, 'hex')" \
" FROM pg_type b" \
" JOIN pg_attribute a ON b.typrelid = a.attrelid" \
" JOIN pg_type tt ON a.atttypid = tt.oid" \
" WHERE b.oid = t.oid" \
" AND a.attnum > 0" \
" AND NOT a.attisdropped" \
" ORDER BY a.attnum" \
" ), ' ')" \
" ) ELSE NULL END) AS arr_props" \
" FROM composites c" \
" JOIN pg_type t ON t.typname = c.typname" \
" JOIN pg_namespace n ON t.typnamespace = n.oid AND n.nspname = c.nspname" \
" JOIN pg_type a ON a.oid = t.typarray" \
" ORDER BY idx;"
/* Lookup types for pre 8.4 servers, including composites. Arguments are:
* want_attrs bool, schemas text[], type_names text[]
*/
#define LOOKUP_TYPES_PRE_8_4 \
"SELECT idx, t.oid AS typoid, a.oid AS arroid, t.typlen," \
" (" \
" CASE WHEN %bool THEN (" \
" SELECT array_to_string" \
" (" \
" ARRAY" \
" (" \
" SELECT CASE WHEN tt.typtype = 'd' THEN tt.typbasetype " \
"ELSE a.atttypid END ||" \
" ',' || attlen || ',' || atttypmod || ',' || " \
"encode(attname::bytea, 'hex')" \
" FROM pg_type b" \
" JOIN pg_attribute a ON b.typrelid = a.attrelid" \
" JOIN pg_type tt ON a.atttypid = tt.oid" \
" WHERE b.oid = t.oid" \
" AND a.attnum > 0" \
" AND NOT a.attisdropped" \
" ORDER BY a.attnum" \
" ), ' ')" \
" ) ELSE NULL END) AS arr_props" \
" FROM" \
" (" \
" SELECT n.n AS idx, n.x AS nspname, t.x AS typname" \
" FROM" \
" (" \
" SELECT * FROM information_schema._pg_expandarray(%text[])" \
" ) n LEFT JOIN" \
" (" \
" SELECT * FROM information_schema._pg_expandarray(%text[])" \
" ) t ON n.n = t.n" \
" AND n.x IS NOT NULL" \
" WHERE t.x IS NOT NULL" \
" UNION ALL" \
" SELECT n.n AS idx," \
" (" \
" SELECT n.nspname from pg_type nt JOIN pg_namespace n ON " \
"nt.typnamespace = n.oid" \
" JOIN" \
" (" \
" SELECT * FROM information_schema._pg_expandarray(" \
"current_schemas(true))" \
" ) c ON c.x = n.nspname" \
" WHERE nt.typname = t.x" \
" ORDER BY c.n LIMIT 1" \
" ) AS nspname, t.x AS typname" \
" FROM" \
" (" \
" SELECT * FROM information_schema._pg_expandarray($2)" \
" ) n LEFT JOIN" \
" (" \
" SELECT * FROM information_schema._pg_expandarray($3)" \
" ) t ON n.n = t.n" \
" AND n.x IS NULL" \
" WHERE t.x IS NOT NULL" \
" ) c" \
" JOIN pg_type t ON t.typname = c.typname" \
" JOIN pg_namespace n ON t.typnamespace = n.oid AND n.nspname = c.nspname" \
" JOIN pg_type a ON a.oid = t.typarray" \
" ORDER BY idx"
static PGresult *
execLookupTypes(PGconn *conn, PGerror *err, PGtypeData *data, PGarray *schemas,
PGarray *names, int want_attrs)
{
if(data->fmtinfo.sversion >= 80400)
return PQexecf(conn, err, LOOKUP_TYPES, schemas, names, &want_attrs);
return PQexecf(conn, err, LOOKUP_TYPES_PRE_8_4, &want_attrs, schemas, names);
}
static int
sendLookupTypes(PGconn *conn, PGerror *err, PGtypeData *data, PGarray *schemas,
PGarray *names, int want_attrs)
{
if(data->fmtinfo.sversion >= 80400)
return PQsendf(conn, err, LOOKUP_TYPES, schemas, names, &want_attrs);
return PQsendf(conn, err, LOOKUP_TYPES_PRE_8_4, &want_attrs, schemas, names);
}
static int
performRegisterQuery(PGconn *conn, PGerror *err, int which, PGregisterType *types,
int count, PGresult **res)
{
int n = FALSE;
PGtypeData *connData;
PGarray names;
PGarray schemas;
int want_attrs = which == PQT_COMPOSITE;
if (!(connData = (PGtypeData *) PQinstanceData(conn, pqt_eventproc)))
{
PQseterror(err, "PGconn is missing event data");
return FALSE;
}
if (!getTypeParams(conn, err, types, count, &names, &schemas))
return FALSE;
if (res)
*res = execLookupTypes(conn, err, connData, &schemas, &names, want_attrs);
else
n = sendLookupTypes(conn, err, connData, &schemas, &names, want_attrs);
PQparamClear(names.param);
PQparamClear(schemas.param);
if (res)
return *res ? TRUE : FALSE;
return n;
}