hpqtypes-1.2.2: libpqtypes/src/spec.c
/*
* spec.c
* Type Specifier parser and compiler.
*
* 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"
/* For use with pqt_parse */
#define CHKSTMTBUF(nbytes_add) do{ \
if ((*stmtPos + (nbytes_add)) >= stmtBufLen) \
{ \
PQseterror(err, "statement buffer is too small"); \
return FALSE; \
} \
}while (0)
/* For use with PQspecPrepare */
#define FREESTMTBUF do{ \
if (stmtBuf && stmtBuf != buffer) \
free(stmtBuf); \
}while(0)
static char *
skipQuotes(char *s, PGerror *err);
static char *
parseId(PGerror *err, char *id, char **start, int *len, int *flags, int typpos);
static int
expandSpecs(PGtypeData *typeData, PGerror *err);
int
PQspecPrepare(PGconn *conn, PGerror *err, const char *name,
const char *format, int is_stmt)
{
int flags;
int typpos = 0;
int idmax = 0;
size_t stmtPos = 0;
PGtypeHandler *h;
PGtypeData *typeData;
PGtypeSpec *spec;
size_t stmtBufLen = 0;
char *stmtBuf = NULL;
char buffer[8192];
if (!conn)
{
PQseterror(err, "PGConn cannot be NULL");
return FALSE;
}
if (!name || !*name)
{
PQseterror(err, "Prepared specifier name cannot be NULL or an empty string");
return FALSE;
}
if (format && !*format)
{
PQseterror(err, "Specifier format string cannot be empty");
return FALSE;
}
if (!isalnum(*name) && *name != '_')
{
PQseterror(err, "Prepared specifier name must begin with an alpha, "
"number or underscore.");
return FALSE;
}
typeData = PQinstanceData(conn, pqt_eventproc);
if (!typeData)
{
PQseterror(err, "No type data exists for PGconn at %p", conn);
return FALSE;
}
/* This is a removal request */
if (!format)
{
int i;
for (i=0; i < typeData->typspeccnt; i++)
{
if (strcmp(typeData->typspecs[i].name, name) == 0)
{
/* clear it */
pqt_clearspec(&typeData->typspecs[i]);
/* remove from list, not needed if its the last element */
if (i != typeData->typspeccnt - 1)
memmove(typeData->typspecs + i, typeData->typspecs + i + 1,
(typeData->typspeccnt - i - 1) * sizeof(PGtypeSpec));
typeData->typspeccnt--;
break;
}
}
/* always return TRUE, an error is not useful here */
return TRUE;
}
/* Already exists case */
spec = pqt_getspec(typeData->typspecs, typeData->typspeccnt, name);
if (spec)
{
PQseterror(err, "Prepared spec already exists '%s'", name);
return FALSE;
}
/* Make sure specs array is large enough */
if (!expandSpecs(typeData, err))
return FALSE;
spec = &typeData->typspecs[typeData->typspeccnt];
/* cache statement along with prepared type spec */
if (is_stmt)
{
stmtBufLen = strlen(format) + 1;
/* no room in stack, use heap */
if (stmtBufLen > sizeof(buffer))
{
stmtBuf = (char *) malloc(stmtBufLen);
if (!stmtBuf)
{
PQseterror(err, PQT_OUTOFMEMORY);
return FALSE;
}
}
else
{
stmtBuf = buffer;
stmtBufLen = sizeof(buffer);
}
}
while (format && *format)
{
format = pqt_parse(err, format, typeData->typhandlers, typeData->typhcnt,
stmtBuf, stmtBufLen, &h, &stmtPos, &typpos, &flags);
if (!format)
{
pqt_clearspec(spec);
FREESTMTBUF;
return FALSE;
}
/* skipped harmless chars in format, like quoted sections. */
if(!h)
continue;
if (!spec->idlist || spec->idcnt == idmax)
{
int c = idmax ? idmax * 2 : 8;
void *p = pqt_realloc(spec->idlist, c * sizeof(int));
if (!p)
{
PQseterror(err, PQT_OUTOFMEMORY);
pqt_clearspec(spec);
FREESTMTBUF;
return FALSE;
}
spec->idlist = (int *) p;
p = pqt_realloc(spec->flags, c * sizeof(char));
if (!p)
{
PQseterror(err, PQT_OUTOFMEMORY);
pqt_clearspec(spec);
FREESTMTBUF;
return FALSE;
}
spec->flags = (unsigned char *) p;
idmax = c;
}
/* Parallel arrays, every handler needs type flags */
spec->idlist[spec->idcnt] = h->id;
spec->flags[spec->idcnt++] = (unsigned char) flags;
}
/* terminate stmtBuf, guarenteed to have room for NUL */
if (stmtBuf)
stmtBuf[stmtPos] = 0;
/* copy name string */
spec->name = strdup(name);
if (!spec->name)
{
pqt_clearspec(spec);
PQseterror(err, PQT_OUTOFMEMORY);
FREESTMTBUF;
return FALSE;
}
/* copy the parameterized stmt string */
if (stmtBuf)
{
spec->stmt = strdup(stmtBuf);
if (!spec->stmt)
{
pqt_clearspec(spec);
PQseterror(err, PQT_OUTOFMEMORY);
FREESTMTBUF;
return FALSE;
}
}
FREESTMTBUF;
/* Success, increment type spec count */
typeData->typspeccnt++;
return TRUE;
}
int
PQclearSpecs(PGconn *conn, PGerror *err)
{
PGtypeData *typeData;
if (!conn)
{
PQseterror(err, "PGConn cannot be NULL");
return FALSE;
}
typeData = PQinstanceData(conn, pqt_eventproc);
if (!typeData)
{
PQseterror(err, "No type data exists for PGconn at %p", conn);
return FALSE;
}
pqt_freespecs(typeData->typspecs, typeData->typspeccnt);
typeData->typspecs = NULL;
typeData->typspeccnt = 0;
typeData->typspecmax = 0;
return TRUE;
}
char *pqt_parse(PGerror *err, const char *format, PGtypeHandler *h, int hcnt,
char *stmtBuf, size_t stmtBufLen, PGtypeHandler **out, size_t *stmtPos,
int *typpos, int *flags)
{
int specMark;
char *s = skipQuotes((char *) format, err);
char typname[PQT_MAXIDLEN + 1];
char schema[PQT_MAXIDLEN + 1];
char tmp[200];
*out = NULL;
if (!s)
return NULL;
/* found quotes to skip */
if (s != format)
{
if (stmtBuf)
{
size_t n = s - format;
CHKSTMTBUF(n);
memcpy(stmtBuf + *stmtPos, format, n);
(*stmtPos) += n;
}
return s;
}
specMark = *format;
if (specMark != '%' && specMark != '#')
{
if (stmtBuf)
{
CHKSTMTBUF(1);
stmtBuf[*stmtPos] = *format;
(*stmtPos)++;
}
format++;
return (char *) format;
}
/* spec skips % or # */
if (!(s = pqt_parsetype(err, format + 1, schema, typname, flags, *typpos + 1)))
return NULL;
if (*flags & TYPFLAG_INVALID)
{
if (stmtBuf)
{
CHKSTMTBUF(1);
stmtBuf[*stmtPos] = *format++;
(*stmtPos)++;
return (char *) format;
}
return NULL;
}
(*typpos)++;
if (!(*out = pqt_gethandler(h, hcnt, schema, typname)))
{
PQseterror(err, "Unknown type '%s' (position %d)",
pqt_fqtn(tmp, sizeof(tmp), schema, typname), *typpos);
return NULL;
}
if (stmtBuf)
{
int n = pqt_snprintf(tmp, sizeof(tmp), "$%d", *typpos);
CHKSTMTBUF(n);
memcpy(stmtBuf + *stmtPos, tmp, n);
(*stmtPos) += n;
}
if (!(*out)->typput)
{
PGtypeHandler *o = pqt_gethandlerbyid(h, hcnt, h->base_id);
if (!o || !o->typput)
{
PQseterror(err,
"Type '%s' doesn't support put operations (position %d)",
pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema,
(*out)->typname), *typpos);
*out = NULL;
return NULL;
}
*out = o;
}
if ((*flags & TYPFLAG_POINTER) && !pqt_allowsptr(*out))
{
PQseterror(err,
"Type '%s' doesn't support putting pointers (position %d)",
pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema,
(*out)->typname), *typpos);
*out = NULL;
return NULL;
}
if (specMark == '#')
(*flags) |= TYPFLAG_BYNAME;
return s;
}
void
pqt_clearspec(PGtypeSpec *spec)
{
if (spec->name)
free(spec->name);
if (spec->stmt)
free(spec->stmt);
if (spec->idlist)
free(spec->idlist);
if (spec->flags)
free(spec->flags);
memset(spec, 0, sizeof(PGtypeSpec));
}
PGtypeSpec *pqt_dupspecs(PGtypeSpec *specs, int count)
{
int i;
PGtypeSpec *new_specs = (PGtypeSpec *) malloc(count * sizeof(PGtypeSpec));
if (!new_specs)
return NULL;
memset(new_specs, 0, count * sizeof(PGtypeSpec));
for (i=0; i < count; i++)
{
PGtypeSpec *s = &specs[i];
PGtypeSpec *news = &new_specs[i];
news->idcnt = s->idcnt;
news->name = strdup(s->name);
if (!news->name)
{
pqt_freespecs(new_specs, i+1);
return NULL;
}
if(s->stmt)
{
news->stmt = strdup(s->stmt);
if (!news->stmt)
{
pqt_freespecs(new_specs, i+1);
return NULL;
}
}
news->idlist = (int *) malloc(s->idcnt * sizeof(int));
if (!news->idlist)
{
pqt_freespecs(new_specs, i+1);
return NULL;
}
memcpy(news->idlist, s->idlist, s->idcnt * sizeof(int));
news->flags = (unsigned char *) malloc(s->idcnt * sizeof(char));
if (!news->flags)
{
pqt_freespecs(new_specs, i+1);
return NULL;
}
memcpy(news->flags, s->flags, s->idcnt * sizeof(char));
}
return new_specs;
}
void pqt_freespecs(PGtypeSpec *specs, int count)
{
int i;
for (i=0; i < count; i++)
pqt_clearspec(&specs[i]);
if (specs)
free(specs);
}
PGtypeSpec *pqt_getspec(PGtypeSpec *specs, int count, const char *name)
{
int i;
for (i=0; i < count; i++)
if (strcmp(specs[i].name, name) == 0)
return &specs[i];
return NULL;
}
/* Parse a type identifer name (schema qualified or not) from spec. spec
* must point to the first char after the % sign, which maybe a
* double quote.
*
* spec - pointer to typname, just after the '%' or '#'
* schema - buffer to receive schema (PQT_MAXIDLEN bytes)
* typname - buffer to receive typname (PQT_MAXIDLEN bytes)
* flags - a pointer to an int that is set one or more TYPFLAG_xxx
* typpos - 1-based position of spec in specifier string (0 for unknown)
*/
char *
pqt_parsetype(PGerror *err, const char *spec, char *schema, char *typname,
int *flags, int typpos)
{
int i;
char *start;
int len=0;
char *s = (char *)spec;
if (!(s = parseId(err, s, &start, &len, flags, typpos)))
return NULL;
/* not a valid specifer, false positive like "(x % y) = 0" */
if (*flags & TYPFLAG_INVALID)
return s;
*schema = 0;
if (*s == '.')
{
memcpy(schema, start, len);
schema[len] = 0;
if (*flags & TYPFLAG_CASEFOLD)
for (i=0; i < len; i++)
schema[i] = pqt_tolower(schema[i]);
/* now get typname */
if (!(s = parseId(err, ++s, &start, &len, flags, typpos)))
return NULL;
if (*flags & TYPFLAG_INVALID)
return s;
}
memcpy(typname, start, len);
typname[len] = 0;
if (*flags & TYPFLAG_CASEFOLD)
for (i=0; i < len; i++)
typname[i] = pqt_tolower(typname[i]);
return s;
}
static char *
parseId(PGerror *err, char *id, char **start, int *len, int *flags, int typpos)
{
char *p = id;
*flags = 0;
*start = NULL;
*len = 0;
if (*p == '"')
p++;
/* check first character */
if (!isalpha(*p) && *p != '_')
{
*flags |= TYPFLAG_INVALID;
PQseterror(err,
"Invalid first character for identifier '%c' (pos:%d)", *p, typpos);
return p;
}
if (*id == '"')
{
id++;
if (!(p = strchr(id, '"')))
{
*flags |= TYPFLAG_INVALID;
PQseterror(err, "Unterminated double quote '%s' (pos:%d)",
id-1, typpos);
return p;
}
*len = (int) (p - id);
*start = id;
p++;
}
else
{
for (p=id+1; isalnum(*p) || *p=='_'; p++) ;
*len = (int) (p - id);
*start = id;
*flags |= TYPFLAG_CASEFOLD;
}
/* range check */
if (*len == 0 || *len > PQT_MAXIDLEN)
{
*flags |= TYPFLAG_INVALID;
PQseterror(err, "Identifier out of range %d (pos:%d), range is 1 to %d",
*len, typpos, PQT_MAXIDLEN);
return p;
}
/* direct pointer request */
if (*p == '*')
{
p++;
*flags |= TYPFLAG_POINTER;
}
/* Is this an array? Ex. %int4[] or %"a b"[] */
if (p[0] == '[' && p[1] == ']')
{
if (*flags & TYPFLAG_POINTER)
{
PQseterror(err,
"'*' specifer flag cannot be used with arrays[] '%s' (pos:%d)",
id, typpos);
return NULL;
}
*flags |= TYPFLAG_ARRAY;
p += 2;
}
return p;
}
static int
expandSpecs(PGtypeData *typeData, PGerror *err)
{
int n;
PGtypeSpec *specs;
if (typeData->typspeccnt < typeData->typspecmax)
return TRUE;
n = typeData->typspecmax ? (typeData->typspecmax * 3) / 2 : 8;
specs = (PGtypeSpec *) pqt_realloc(
typeData->typspecs, sizeof(PGtypeSpec) * n);
if (!specs)
{
PQseterror(err, PQT_OUTOFMEMORY);
return FALSE;
}
memset(specs + typeData->typspeccnt, 0,
(n - typeData->typspeccnt) * sizeof(PGtypeSpec));
typeData->typspecs = specs;
typeData->typspecmax = n;
return TRUE;
}
/* skip quoted strings. Doesn't need to account for E'' syntax. The
* E is copied over prior to the quoted string.
*
* Returns a pointer to the next character after the closing quote or
* NULL if there was an error.
*/
static char *
skipQuotes(char *s, PGerror *err)
{
char *end;
if (*s != '\'')
return s;
end = s;
while (*++end)
{
/* If we see a backslash, skip an extra char. No need to dig any
* further since this method works with \digits and \hex.
*/
if (*end == '\\')
end++;
else if (*end == '\'')
break;
}
/* unterminated quote */
if (!*end)
{
PQseterror(err, "unterminated single quoted string");
return NULL;
}
return ++end; /* skip ending quote */
}