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 } |