libpqtypes - spec.c

Home Page

  1 
  2 /*
  3  * spec.c
  4  *   Type Specifier parser and compiler.
  5  *
  6  * Copyright (c) 2009 eSilo, LLC. All rights reserved.
  7  * This is free software; see the source for copying conditions.  There is
  8  * NO warranty; not even for MERCHANTABILITY or  FITNESS FOR A  PARTICULAR
  9  * PURPOSE.
 10  */
 11 
 12 #include "libpqtypes-int.h"
 13 
 14 /* For use with pqt_parse */
 15 #define CHKSTMTBUF(nbytes_add) do{ \
 16   if ((*stmtPos + (nbytes_add)) >= stmtBufLen) \
 17   { \
 18     PQseterror("statement buffer is too small"); \
 19     return FALSE; \
 20   } \
 21 }while (0)
 22 
 23 /* For use with PQspecPrepare */
 24 #define FREESTMTBUF do{ \
 25   if (stmtBuf && stmtBuf != buffer) \
 26     free(stmtBuf); \
 27 }while(0)
 28 
 29 static char *
 30 skipQuotes(char *s);
 31 
 32 static char *
 33 parseId(char *id, char **start, int *len, int *flags, int typpos);
 34 
 35 static int
 36 expandSpecs(PGtypeData *typeData);
 37 
 38 int
 39 PQspecPrepare(PGconn *conn, const char *name, const char *format, int is_stmt)
 40 {
 41   int flags;
 42   int typpos = 0;
 43   int idmax = 0;
 44   size_t stmtPos = 0;
 45   PGtypeHandler *h;
 46   PGtypeData *typeData;
 47   PGtypeSpec *spec;
 48   size_t stmtBufLen = 0;
 49   char *stmtBuf = NULL;
 50   char buffer[8192];
 51 
 52   if (!conn)
 53   {
 54     PQseterror("PGConn cannot be NULL");
 55     return FALSE;
 56   }
 57 
 58   if (!name || !*name)
 59   {
 60     PQseterror("Prepared specifier name cannot be NULL or an empty string");
 61     return FALSE;
 62   }
 63 
 64   if (format && !*format)
 65   {
 66     PQseterror("Specifier format string cannot be empty");
 67     return FALSE;
 68   }
 69 
 70   if (!isalnum(*name) && *name != '_')
 71   {
 72     PQseterror("Prepared specifier name must begin with an alpha, "
 73       "number or underscore.");
 74     return FALSE;
 75   }
 76 
 77   typeData = PQinstanceData(conn, pqt_eventproc);
 78   if (!typeData)
 79   {
 80     PQseterror("No type data exists for PGconn at %p", conn);
 81     return FALSE;
 82   }
 83 
 84   /* This is a removal request */
 85   if (!format)
 86   {
 87     int i;
 88 
 89     for (i=0; i < typeData->typspeccnt; i++)
 90     {
 91       if (strcmp(typeData->typspecs[i].name, name) == 0)
 92       {
 93         /* clear it */
 94         pqt_clearspec(&typeData->typspecs[i]);
 95 
 96         /* remove from list, not needed if its the last element */
 97         if (i != typeData->typspeccnt - 1)
 98           memmove(typeData->typspecs + i, typeData->typspecs + i + 1,
 99             (typeData->typspeccnt - i - 1) * sizeof(PGtypeSpec));
100 
101         typeData->typspeccnt--;
102         break;
103       }
104     }
105 
106     /* always return TRUE, an error is not useful here */
107     return TRUE;
108   }
109 
110   /* Already exists case */
111   spec = pqt_getspec(typeData->typspecs, typeData->typspeccnt, name);
112   if (spec)
113   {
114     PQseterror("Prepared spec already exists '%s'", name);
115     return FALSE;
116   }
117 
118   /* Make sure specs array is large enough */
119   if (!expandSpecs(typeData))
120     return FALSE;
121 
122   spec = &typeData->typspecs[typeData->typspeccnt];
123 
124   /* cache statement along with prepared type spec */
125   if (is_stmt)
126   {
127     stmtBufLen = strlen(format) + 1;
128 
129     /* no room in stack, use heap */
130     if (stmtBufLen > sizeof(buffer))
131     {
132       stmtBuf = (char *) malloc(stmtBufLen);
133       if (!stmtBuf)
134       {
135         PQseterror(PQT_OUTOFMEMORY);
136         return FALSE;
137       }
138     }
139     else
140     {
141       stmtBuf = buffer;
142       stmtBufLen = sizeof(buffer);
143     }
144   }
145 
146   while (format && *format)
147   {
148     format = pqt_parse(format, typeData->typhandlers, typeData->typhcnt,
149       stmtBuf, stmtBufLen, &h, &stmtPos, &typpos, &flags);
150 
151     if (!format)
152     {
153       pqt_clearspec(spec);
154       FREESTMTBUF;
155       return FALSE;
156     }
157 
158     /* skipped harmless chars in format, like quoted sections. */
159     if(!h)
160       continue;
161 
162     if (!spec->idlist || spec->idcnt == idmax)
163     {
164       int c = idmax ? idmax * 2 : 8;
165       void *p = pqt_realloc(spec->idlist, c * sizeof(int));
166 
167       if (!p)
168       {
169         PQseterror(PQT_OUTOFMEMORY);
170         pqt_clearspec(spec);
171         FREESTMTBUF;
172         return FALSE;
173       }
174 
175       spec->idlist = (int *) p;
176 
177       p = pqt_realloc(spec->flags, c * sizeof(char));
178       if (!p)
179       {
180         PQseterror(PQT_OUTOFMEMORY);
181         pqt_clearspec(spec);
182         FREESTMTBUF;
183         return FALSE;
184       }
185 
186       spec->flags = (unsigned char *) p;
187       idmax = c;
188     }
189 
190     /* Parallel arrays, every handler needs type flags */
191     spec->idlist[spec->idcnt] = h->id;
192     spec->flags[spec->idcnt++] = (unsigned char) flags;
193   }
194 
195   /* terminate stmtBuf, guarenteed to have room for NUL */
196   if (stmtBuf)
197     stmtBuf[stmtPos] = 0;
198 
199   /* copy name string */
200   spec->name = strdup(name);
201   if (!spec->name)
202   {
203     pqt_clearspec(spec);
204     PQseterror(PQT_OUTOFMEMORY);
205     FREESTMTBUF;
206     return FALSE;
207   }
208 
209   /* copy the parameterized stmt string */
210   if (stmtBuf)
211   {
212     spec->stmt = strdup(stmtBuf);
213     if (!spec->stmt)
214     {
215       pqt_clearspec(spec);
216       PQseterror(PQT_OUTOFMEMORY);
217       FREESTMTBUF;
218       return FALSE;
219     }
220   }
221 
222   FREESTMTBUF;
223 
224   /* Success, increment type spec count */
225   typeData->typspeccnt++;
226   return TRUE;
227 }
228 
229 char *pqt_parse(const char *format, PGtypeHandler *h, int hcnt,
230   char *stmtBuf, size_t stmtBufLen, PGtypeHandler **out, size_t *stmtPos,
231   int *typpos, int *flags)
232 {
233   int specMark;
234   char *s = skipQuotes((char *) format);
235   char typname[PQT_MAXIDLEN + 1];
236   char schema[PQT_MAXIDLEN + 1];
237   char tmp[200];
238 
239   *out = NULL;
240 
241   if (!s)
242     return NULL;
243 
244   /* found quotes to skip */
245   if (s != format)
246   {
247     if (stmtBuf)
248     {
249       size_t n = s - format;
250       CHKSTMTBUF(n);
251       memcpy(stmtBuf + *stmtPos, format, n);
252       (*stmtPos) += n;
253     }
254 
255     return s;
256   }
257 
258   specMark = *format;
259   if (specMark != '%' && specMark != '#')
260   {
261     if (stmtBuf)
262     {
263       CHKSTMTBUF(1);
264       stmtBuf[*stmtPos] = *format;
265       (*stmtPos)++;
266     }
267 
268     format++;
269     return (char *) format;
270   }
271 
272   /* spec skips % or # */
273   if (!(s = pqt_parsetype(format + 1, schema, typname, flags, *typpos + 1)))
274     return NULL;
275 
276   if (*flags & TYPFLAG_INVALID)
277   {
278     if (stmtBuf)
279     {
280       CHKSTMTBUF(1);
281       stmtBuf[*stmtPos] = *format++;
282       (*stmtPos)++;
283       PQseterror(NULL); /* set by pqt_parsetype */
284       return (char *) format;
285     }
286 
287     return NULL;
288   }
289 
290   (*typpos)++;
291 
292   if (!(*out = pqt_gethandler(h, hcnt, schema, typname)))
293   {
294     PQseterror("Uknown type '%s' (position %d)",
295       pqt_fqtn(tmp, sizeof(tmp), schema, typname), *typpos);
296     return NULL;
297   }
298 
299   if (stmtBuf)
300   {
301     int n = pqt_snprintf(tmp, sizeof(tmp), "$%d", *typpos);
302     CHKSTMTBUF(n);
303     memcpy(stmtBuf + *stmtPos, tmp, n);
304     (*stmtPos) += n;
305   }
306 
307   if (!(*out)->typput)
308   {
309     PGtypeHandler *o = pqt_gethandlerbyid(h, hcnt, h->base_id);
310     if (!o || !o->typput)
311     {
312       PQseterror(
313         "Type '%s' doesn't support put operations (position %d)",
314         pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema, (*out)->typname), *typpos);
315 
316       *out = NULL;
317       return NULL;
318     }
319 
320     *out = o;
321   }
322 
323   if ((*flags & TYPFLAG_POINTER) && !pqt_allowsptr(*out))
324   {
325     PQseterror(
326       "Type '%s' doesn't support putting pointers (position %d)",
327       pqt_fqtn(tmp, sizeof(tmp), (*out)->typschema, (*out)->typname), *typpos);
328 
329     *out = NULL;
330     return NULL;
331   }
332 
333   if (specMark == '#')
334     (*flags) |= TYPFLAG_BYNAME;
335 
336   return s;
337 }
338 
339 void
340 pqt_clearspec(PGtypeSpec *spec)
341 {
342   if (spec->name)
343     free(spec->name);
344 
345   if (spec->stmt)
346     free(spec->stmt);
347 
348   if (spec->idlist)
349     free(spec->idlist);
350 
351   if (spec->flags)
352     free(spec->flags);
353 
354   memset(spec, 0, sizeof(PGtypeSpec));
355 }
356 
357 PGtypeSpec *pqt_dupspecs(PGtypeSpec *specs, int count)
358 {
359   int i;
360   PGtypeSpec *new_specs = (PGtypeSpec *) malloc(count * sizeof(PGtypeSpec));
361 
362   if (!new_specs)
363     return NULL;
364 
365   memset(new_specs, 0, count * sizeof(PGtypeSpec));
366 
367   for (i=0; i < count; i++)
368   {
369     PGtypeSpec *s = &specs[i];
370     PGtypeSpec *news = &new_specs[i];
371 
372     news->idcnt = s->idcnt;
373 
374     news->name = strdup(s->name);
375     if (!news->name)
376     {
377       pqt_freespecs(new_specs, i+1);
378       return NULL;
379     }
380 
381     if(s->stmt)
382     {
383       news->stmt = strdup(s->stmt);
384       if (!news->stmt)
385       {
386         pqt_freespecs(new_specs, i+1);
387         return NULL;
388       }
389     }
390 
391     news->idlist = (int *) malloc(s->idcnt * sizeof(int));
392     if (!news->idlist)
393     {
394       pqt_freespecs(new_specs, i+1);
395       return NULL;
396     }
397 
398     memcpy(news->idlist, s->idlist, s->idcnt * sizeof(int));
399 
400     news->flags = (unsigned char *) malloc(s->idcnt * sizeof(char));
401     if (!news->flags)
402     {
403       pqt_freespecs(new_specs, i+1);
404       return NULL;
405     }
406 
407     memcpy(news->flags, s->flags, s->idcnt * sizeof(char));
408   }
409 
410   return new_specs;
411 }
412 
413 void pqt_freespecs(PGtypeSpec *specs, int count)
414 {
415   int i;
416 
417   for (i=0; i < count; i++)
418     pqt_clearspec(&specs[i]);
419 
420   if (specs)
421     free(specs);
422 }
423 
424 PGtypeSpec *pqt_getspec(PGtypeSpec *specs, int count, const char *name)
425 {
426   int i;
427 
428   for (i=0; i < count; i++)
429     if (strcmp(specs[i].name, name) == 0)
430       return &specs[i];
431 
432   return NULL;
433 }
434 
435 /* Parse a type identifer name (schema qualified or not) from spec. spec
436  * must point to the first char after the % sign, which maybe a double quote.
437  *
438  * spec - pointer to typname, just after the '%' or '#'
439  * schema - buffer to receive schema (PQT_MAXIDLEN bytes)
440  * typname - buffer to receive typname (PQT_MAXIDLEN bytes)
441  * flags - a pointer to an int that is set one or more TYPFLAG_xxx
442  * typpos - 1-based position of spec in specifier string (0 for unknown)
443  */
444 char *
445 pqt_parsetype(const char *spec, char *schema, char *typname,
446   int *flags, int typpos)
447 {
448   int i;
449   char *start;
450   int len=0;
451   char *s = (char *)spec;
452 
453   if (!(s = parseId(s, &start, &len, flags, typpos)))
454     return NULL;
455 
456   /* not a valid specifer, false positive like "(x % y) = 0" */
457   if (*flags & TYPFLAG_INVALID)
458     return s;
459 
460   *schema = 0;
461   if (*s == '.')
462   {
463     memcpy(schema, start, len);
464     schema[len] = 0;
465     if (*flags & TYPFLAG_CASEFOLD)
466       for (i=0; i < len; i++)
467         schema[i] = pqt_tolower(schema[i]);
468 
469     /* now get typname */
470     if (!(s = parseId(++s, &start, &len, flags, typpos)))
471       return NULL;
472 
473     if (*flags & TYPFLAG_INVALID)
474       return s;
475   }
476 
477   memcpy(typname, start, len);
478   typname[len] = 0;
479   if (*flags & TYPFLAG_CASEFOLD)
480     for (i=0; i < len; i++)
481       schema[i] = pqt_tolower(schema[i]);
482 
483   return s;
484 }
485 
486 static char *
487 parseId(char *id, char **start, int *len, int *flags, int typpos)
488 {
489   char *p = id;
490 
491   *flags = 0;
492   *start = NULL;
493   *len = 0;
494 
495   if (*p == '"')
496     p++;
497 
498   /* check first character */
499   if (!isalpha(*p) && *p != '_')
500   {
501     *flags |= TYPFLAG_INVALID;
502     PQseterror(
503       "Invalid first character for identifier '%c' (pos:%d)", *p, typpos);
504     return p;
505   }
506 
507   if (*id == '"')
508   {
509     id++;
510     if (!(p = strchr(id, '"')))
511     {
512       *flags |= TYPFLAG_INVALID;
513       PQseterror("Unterminated double quote '%s' (pos:%d)",
514         id-1, typpos);
515       return p;
516     }
517 
518     *len = (int) (p - id);
519     *start = id;
520     p++;
521   }
522   else
523   {
524     for (p=id+1; isalnum(*p) || *p=='_'; p++) ;
525 
526     *len = (int) (p - id);
527     *start = id;
528     *flags |= TYPFLAG_CASEFOLD;
529   }
530 
531   /* range check */
532   if (*len == 0 || *len > PQT_MAXIDLEN)
533   {
534     *flags |= TYPFLAG_INVALID;
535     PQseterror("Identifier out of range %d (pos:%d), range is 1 to %d",
536       *len, typpos, PQT_MAXIDLEN);
537     return p;
538   }
539 
540   /* direct pointer request */
541   if (*p == '*')
542   {
543     p++;
544     *flags |= TYPFLAG_POINTER;
545   }
546 
547   /* Is this an array?  Ex. %int4[] or %"a b"[] */
548   if (p[0] == '[' && p[1] == ']')
549   {
550     if (*flags & TYPFLAG_POINTER)
551     {
552       PQseterror(
553         "'*' specifer flag cannot be used with arrays[] '%s' (pos:%d)",
554         id, typpos);
555       return NULL;
556     }
557 
558     *flags |= TYPFLAG_ARRAY;
559     p += 2;
560   }
561 
562   return p;
563 }
564 
565 static int
566 expandSpecs(PGtypeData *typeData)
567 {
568   int n;
569   PGtypeSpec *specs;
570 
571   if (typeData->typspeccnt < typeData->typspecmax)
572     return TRUE;
573 
574   n = typeData->typspecmax ? (typeData->typspecmax * 3) / 2 : 8;
575 
576   specs = (PGtypeSpec *) pqt_realloc(
577     typeData->typspecs, sizeof(PGtypeSpec) * n);
578 
579   if (!specs)
580   {
581     PQseterror(PQT_OUTOFMEMORY);
582     return FALSE;
583   }
584 
585   memset(specs + typeData->typspeccnt, 0,
586     (n - typeData->typspeccnt) * sizeof(PGtypeSpec));
587 
588   typeData->typspecs = specs;
589   typeData->typspecmax = n;
590   return TRUE;
591 }
592 
593 /* skip quoted strings.  Doesn't need to account for E'' syntax. The
594  * E is copied over prior to the quoted string.
595  *
596  * Returns a pointer to the next character after the closing quote or
597  * NULL if there was an error.
598  */
599 static char *
600 skipQuotes(char *s)
601 {
602   char *end;
603 
604   if (*s != '\'')
605     return s;
606 
607   end = s;
608   while (*++end)
609   {
610     /* If we see a backslash, skip an extra char.  No need to dig any
611      * further since this method works with \digits and \hex.
612      */
613     if (*end == '\\')
614       end++;
615     else if (*end == '\'')
616       break;
617   }
618 
619   /* unterminated quote */
620   if (!*end)
621   {
622     PQseterror("unterminated single quoted string");
623     return NULL;
624   }
625 
626   return ++end; /* skip ending quote */
627 }