libpqtypes - spec.c

Home Page

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