1 /*------------------------------------------------------------------------- 2 * 3 * jsonpath.c 4 * Input/output and supporting routines for jsonpath 5 * 6 * jsonpath expression is a chain of path items. First path item is $, $var, 7 * literal or arithmetic expression. Subsequent path items are accessors 8 * (.key, .*, [subscripts], [*]), filters (? (predicate)) and methods (.type(), 9 * .size() etc). 10 * 11 * For instance, structure of path items for simple expression: 12 * 13 * $.a[*].type() 14 * 15 * is pretty evident: 16 * 17 * $ => .a => [*] => .type() 18 * 19 * Some path items such as arithmetic operations, predicates or array 20 * subscripts may comprise subtrees. For instance, more complex expression 21 * 22 * ($.a + $[1 to 5, 7] ? (@ > 3).double()).type() 23 * 24 * have following structure of path items: 25 * 26 * + => .type() 27 * ___/ \___ 28 * / \ 29 * $ => .a $ => [] => ? => .double() 30 * _||_ | 31 * / \ > 32 * to to / \ 33 * / \ / @ 3 34 * 1 5 7 35 * 36 * Binary encoding of jsonpath constitutes a sequence of 4-bytes aligned 37 * variable-length path items connected by links. Every item has a header 38 * consisting of item type (enum JsonPathItemType) and offset of next item 39 * (zero means no next item). After the header, item may have payload 40 * depending on item type. For instance, payload of '.key' accessor item is 41 * length of key name and key name itself. Payload of '>' arithmetic operator 42 * item is offsets of right and left operands. 43 * 44 * So, binary representation of sample expression above is: 45 * (bottom arrows are next links, top lines are argument links) 46 * 47 * _____ 48 * _____ ___/____ \ __ 49 * _ /_ \ _____/__/____ \ \ __ _ /_ \ 50 * / / \ \ / / / \ \ \ / \ / / \ \ 51 * +(LR) $ .a $ [](* to *, * to *) 1 5 7 ?(A) >(LR) @ 3 .double() .type() 52 * | | ^ | ^| ^| ^ ^ 53 * | |__| |__||________________________||___________________| | 54 * |_______________________________________________________________________| 55 * 56 * Copyright (c) 2019-2020, PostgreSQL Global Development Group 57 * 58 * IDENTIFICATION 59 * src/backend/utils/adt/jsonpath.c 60 * 61 *------------------------------------------------------------------------- 62 */ 63 64 #include "postgres.h" 65 66 #include "funcapi.h" 67 #include "lib/stringinfo.h" 68 #include "libpq/pqformat.h" 69 #include "miscadmin.h" 70 #include "utils/builtins.h" 71 #include "utils/json.h" 72 #include "utils/jsonpath.h" 73 74 75 static Datum jsonPathFromCstring(char *in, int len); 76 static char *jsonPathToCstring(StringInfo out, JsonPath *in, 77 int estimated_len); 78 static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, 79 int nestingLevel, bool insideArraySubscript); 80 static void alignStringInfoInt(StringInfo buf); 81 static int32 reserveSpaceForItemPointer(StringInfo buf); 82 static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, 83 bool printBracketes); 84 static int operationPriority(JsonPathItemType op); 85 86 87 /**************************** INPUT/OUTPUT ********************************/ 88 89 /* 90 * jsonpath type input function 91 */ 92 Datum 93 jsonpath_in(PG_FUNCTION_ARGS) 94 { 95 char *in = PG_GETARG_CSTRING(0); 96 int len = strlen(in); 97 98 return jsonPathFromCstring(in, len); 99 } 100 101 /* 102 * jsonpath type recv function 103 * 104 * The type is sent as text in binary mode, so this is almost the same 105 * as the input function, but it's prefixed with a version number so we 106 * can change the binary format sent in future if necessary. For now, 107 * only version 1 is supported. 108 */ 109 Datum 110 jsonpath_recv(PG_FUNCTION_ARGS) 111 { 112 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); 113 int version = pq_getmsgint(buf, 1); 114 char *str; 115 int nbytes; 116 117 if (version == JSONPATH_VERSION) 118 str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); 119 else 120 elog(ERROR, "unsupported jsonpath version number: %d", version); 121 122 return jsonPathFromCstring(str, nbytes); 123 } 124 125 /* 126 * jsonpath type output function 127 */ 128 Datum 129 jsonpath_out(PG_FUNCTION_ARGS) 130 { 131 JsonPath *in = PG_GETARG_JSONPATH_P(0); 132 133 PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in))); 134 } 135 136 /* 137 * jsonpath type send function 138 * 139 * Just send jsonpath as a version number, then a string of text 140 */ 141 Datum 142 jsonpath_send(PG_FUNCTION_ARGS) 143 { 144 JsonPath *in = PG_GETARG_JSONPATH_P(0); 145 StringInfoData buf; 146 StringInfoData jtext; 147 int version = JSONPATH_VERSION; 148 149 initStringInfo(&jtext); 150 (void) jsonPathToCstring(&jtext, in, VARSIZE(in)); 151 152 pq_begintypsend(&buf); 153 pq_sendint8(&buf, version); 154 pq_sendtext(&buf, jtext.data, jtext.len); 155 pfree(jtext.data); 156 157 PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); 158 } 159 160 /* 161 * Converts C-string to a jsonpath value. 162 * 163 * Uses jsonpath parser to turn string into an AST, then 164 * flattenJsonPathParseItem() does second pass turning AST into binary 165 * representation of jsonpath. 166 */ 167 static Datum 168 jsonPathFromCstring(char *in, int len) 169 { 170 JsonPathParseResult *jsonpath = parsejsonpath(in, len); 171 JsonPath *res; 172 StringInfoData buf; 173 174 initStringInfo(&buf); 175 enlargeStringInfo(&buf, 4 * len /* estimation */ ); 176 177 appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); 178 179 if (!jsonpath) 180 ereport(ERROR, 181 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 182 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", 183 in))); 184 185 flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false); 186 187 res = (JsonPath *) buf.data; 188 SET_VARSIZE(res, buf.len); 189 res->header = JSONPATH_VERSION; 190 if (jsonpath->lax) 191 res->header |= JSONPATH_LAX; 192 193 PG_RETURN_JSONPATH_P(res); 194 } 195 196 /* 197 * Converts jsonpath value to a C-string. 198 * 199 * If 'out' argument is non-null, the resulting C-string is stored inside the 200 * StringBuffer. The resulting string is always returned. 201 */ 202 static char * 203 jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) 204 { 205 StringInfoData buf; 206 JsonPathItem v; 207 208 if (!out) 209 { 210 out = &buf; 211 initStringInfo(out); 212 } 213 enlargeStringInfo(out, estimated_len); 214 215 if (!(in->header & JSONPATH_LAX)) 216 appendBinaryStringInfo(out, "strict ", 7); 217 218 jspInit(&v, in); 219 printJsonPathItem(out, &v, false, true); 220 221 return out->data; 222 } 223 224 /* 225 * Recursive function converting given jsonpath parse item and all its 226 * children into a binary representation. 227 */ 228 static int 229 flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, 230 int nestingLevel, bool insideArraySubscript) 231 { 232 /* position from beginning of jsonpath data */ 233 int32 pos = buf->len - JSONPATH_HDRSZ; 234 int32 chld; 235 int32 next; 236 int argNestingLevel = 0; 237 238 check_stack_depth(); 239 CHECK_FOR_INTERRUPTS(); 240 241 appendStringInfoChar(buf, (char) (item->type)); 242 243 /* 244 * We align buffer to int32 because a series of int32 values often goes 245 * after the header, and we want to read them directly by dereferencing 246 * int32 pointer (see jspInitByBuffer()). 247 */ 248 alignStringInfoInt(buf); 249 250 /* 251 * Reserve space for next item pointer. Actual value will be recorded 252 * later, after next and children items processing. 253 */ 254 next = reserveSpaceForItemPointer(buf); 255 256 switch (item->type) 257 { 258 case jpiString: 259 case jpiVariable: 260 case jpiKey: 261 appendBinaryStringInfo(buf, (char *) &item->value.string.len, 262 sizeof(item->value.string.len)); 263 appendBinaryStringInfo(buf, item->value.string.val, 264 item->value.string.len); 265 appendStringInfoChar(buf, '\0'); 266 break; 267 case jpiNumeric: 268 appendBinaryStringInfo(buf, (char *) item->value.numeric, 269 VARSIZE(item->value.numeric)); 270 break; 271 case jpiBool: 272 appendBinaryStringInfo(buf, (char *) &item->value.boolean, 273 sizeof(item->value.boolean)); 274 break; 275 case jpiAnd: 276 case jpiOr: 277 case jpiEqual: 278 case jpiNotEqual: 279 case jpiLess: 280 case jpiGreater: 281 case jpiLessOrEqual: 282 case jpiGreaterOrEqual: 283 case jpiAdd: 284 case jpiSub: 285 case jpiMul: 286 case jpiDiv: 287 case jpiMod: 288 case jpiStartsWith: 289 { 290 /* 291 * First, reserve place for left/right arg's positions, then 292 * record both args and sets actual position in reserved 293 * places. 294 */ 295 int32 left = reserveSpaceForItemPointer(buf); 296 int32 right = reserveSpaceForItemPointer(buf); 297 298 chld = !item->value.args.left ? pos : 299 flattenJsonPathParseItem(buf, item->value.args.left, 300 nestingLevel + argNestingLevel, 301 insideArraySubscript); 302 *(int32 *) (buf->data + left) = chld - pos; 303 304 chld = !item->value.args.right ? pos : 305 flattenJsonPathParseItem(buf, item->value.args.right, 306 nestingLevel + argNestingLevel, 307 insideArraySubscript); 308 *(int32 *) (buf->data + right) = chld - pos; 309 } 310 break; 311 case jpiLikeRegex: 312 { 313 int32 offs; 314 315 appendBinaryStringInfo(buf, 316 (char *) &item->value.like_regex.flags, 317 sizeof(item->value.like_regex.flags)); 318 offs = reserveSpaceForItemPointer(buf); 319 appendBinaryStringInfo(buf, 320 (char *) &item->value.like_regex.patternlen, 321 sizeof(item->value.like_regex.patternlen)); 322 appendBinaryStringInfo(buf, item->value.like_regex.pattern, 323 item->value.like_regex.patternlen); 324 appendStringInfoChar(buf, '\0'); 325 326 chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, 327 nestingLevel, 328 insideArraySubscript); 329 *(int32 *) (buf->data + offs) = chld - pos; 330 } 331 break; 332 case jpiFilter: 333 argNestingLevel++; 334 /* FALLTHROUGH */ 335 case jpiIsUnknown: 336 case jpiNot: 337 case jpiPlus: 338 case jpiMinus: 339 case jpiExists: 340 case jpiDatetime: 341 { 342 int32 arg = reserveSpaceForItemPointer(buf); 343 344 chld = !item->value.arg ? pos : 345 flattenJsonPathParseItem(buf, item->value.arg, 346 nestingLevel + argNestingLevel, 347 insideArraySubscript); 348 *(int32 *) (buf->data + arg) = chld - pos; 349 } 350 break; 351 case jpiNull: 352 break; 353 case jpiRoot: 354 break; 355 case jpiAnyArray: 356 case jpiAnyKey: 357 break; 358 case jpiCurrent: 359 if (nestingLevel <= 0) 360 ereport(ERROR, 361 (errcode(ERRCODE_SYNTAX_ERROR), 362 errmsg("@ is not allowed in root expressions"))); 363 break; 364 case jpiLast: 365 if (!insideArraySubscript) 366 ereport(ERROR, 367 (errcode(ERRCODE_SYNTAX_ERROR), 368 errmsg("LAST is allowed only in array subscripts"))); 369 break; 370 case jpiIndexArray: 371 { 372 int32 nelems = item->value.array.nelems; 373 int offset; 374 int i; 375 376 appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); 377 378 offset = buf->len; 379 380 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); 381 382 for (i = 0; i < nelems; i++) 383 { 384 int32 *ppos; 385 int32 topos; 386 int32 frompos = 387 flattenJsonPathParseItem(buf, 388 item->value.array.elems[i].from, 389 nestingLevel, true) - pos; 390 391 if (item->value.array.elems[i].to) 392 topos = flattenJsonPathParseItem(buf, 393 item->value.array.elems[i].to, 394 nestingLevel, true) - pos; 395 else 396 topos = 0; 397 398 ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)]; 399 400 ppos[0] = frompos; 401 ppos[1] = topos; 402 } 403 } 404 break; 405 case jpiAny: 406 appendBinaryStringInfo(buf, 407 (char *) &item->value.anybounds.first, 408 sizeof(item->value.anybounds.first)); 409 appendBinaryStringInfo(buf, 410 (char *) &item->value.anybounds.last, 411 sizeof(item->value.anybounds.last)); 412 break; 413 case jpiType: 414 case jpiSize: 415 case jpiAbs: 416 case jpiFloor: 417 case jpiCeiling: 418 case jpiDouble: 419 case jpiKeyValue: 420 break; 421 default: 422 elog(ERROR, "unrecognized jsonpath item type: %d", item->type); 423 } 424 425 if (item->next) 426 { 427 chld = flattenJsonPathParseItem(buf, item->next, nestingLevel, 428 insideArraySubscript) - pos; 429 *(int32 *) (buf->data + next) = chld; 430 } 431 432 return pos; 433 } 434 435 /* 436 * Align StringInfo to int by adding zero padding bytes 437 */ 438 static void 439 alignStringInfoInt(StringInfo buf) 440 { 441 switch (INTALIGN(buf->len) - buf->len) 442 { 443 case 3: 444 appendStringInfoCharMacro(buf, 0); 445 /* FALLTHROUGH */ 446 case 2: 447 appendStringInfoCharMacro(buf, 0); 448 /* FALLTHROUGH */ 449 case 1: 450 appendStringInfoCharMacro(buf, 0); 451 /* FALLTHROUGH */ 452 default: 453 break; 454 } 455 } 456 457 /* 458 * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written, 459 * actual value will be recorded at '(int32 *) &buf->data[pos]' later. 460 */ 461 static int32 462 reserveSpaceForItemPointer(StringInfo buf) 463 { 464 int32 pos = buf->len; 465 int32 ptr = 0; 466 467 appendBinaryStringInfo(buf, (char *) &ptr, sizeof(ptr)); 468 469 return pos; 470 } 471 472 /* 473 * Prints text representation of given jsonpath item and all its children. 474 */ 475 static void 476 printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, 477 bool printBracketes) 478 { 479 JsonPathItem elem; 480 int i; 481 482 check_stack_depth(); 483 CHECK_FOR_INTERRUPTS(); 484 485 switch (v->type) 486 { 487 case jpiNull: 488 appendStringInfoString(buf, "null"); 489 break; 490 case jpiKey: 491 if (inKey) 492 appendStringInfoChar(buf, '.'); 493 escape_json(buf, jspGetString(v, NULL)); 494 break; 495 case jpiString: 496 escape_json(buf, jspGetString(v, NULL)); 497 break; 498 case jpiVariable: 499 appendStringInfoChar(buf, '$'); 500 escape_json(buf, jspGetString(v, NULL)); 501 break; 502 case jpiNumeric: 503 appendStringInfoString(buf, 504 DatumGetCString(DirectFunctionCall1(numeric_out, 505 NumericGetDatum(jspGetNumeric(v))))); 506 break; 507 case jpiBool: 508 if (jspGetBool(v)) 509 appendBinaryStringInfo(buf, "true", 4); 510 else 511 appendBinaryStringInfo(buf, "false", 5); 512 break; 513 case jpiAnd: 514 case jpiOr: 515 case jpiEqual: 516 case jpiNotEqual: 517 case jpiLess: 518 case jpiGreater: 519 case jpiLessOrEqual: 520 case jpiGreaterOrEqual: 521 case jpiAdd: 522 case jpiSub: 523 case jpiMul: 524 case jpiDiv: 525 case jpiMod: 526 case jpiStartsWith: 527 if (printBracketes) 528 appendStringInfoChar(buf, '('); 529 jspGetLeftArg(v, &elem); 530 printJsonPathItem(buf, &elem, false, 531 operationPriority(elem.type) <= 532 operationPriority(v->type)); 533 appendStringInfoChar(buf, ' '); 534 appendStringInfoString(buf, jspOperationName(v->type)); 535 appendStringInfoChar(buf, ' '); 536 jspGetRightArg(v, &elem); 537 printJsonPathItem(buf, &elem, false, 538 operationPriority(elem.type) <= 539 operationPriority(v->type)); 540 if (printBracketes) 541 appendStringInfoChar(buf, ')'); 542 break; 543 case jpiLikeRegex: 544 if (printBracketes) 545 appendStringInfoChar(buf, '('); 546 547 jspInitByBuffer(&elem, v->base, v->content.like_regex.expr); 548 printJsonPathItem(buf, &elem, false, 549 operationPriority(elem.type) <= 550 operationPriority(v->type)); 551 552 appendBinaryStringInfo(buf, " like_regex ", 12); 553 554 escape_json(buf, v->content.like_regex.pattern); 555 556 if (v->content.like_regex.flags) 557 { 558 appendBinaryStringInfo(buf, " flag \"", 7); 559 560 if (v->content.like_regex.flags & JSP_REGEX_ICASE) 561 appendStringInfoChar(buf, 'i'); 562 if (v->content.like_regex.flags & JSP_REGEX_DOTALL) 563 appendStringInfoChar(buf, 's'); 564 if (v->content.like_regex.flags & JSP_REGEX_MLINE) 565 appendStringInfoChar(buf, 'm'); 566 if (v->content.like_regex.flags & JSP_REGEX_WSPACE) 567 appendStringInfoChar(buf, 'x'); 568 if (v->content.like_regex.flags & JSP_REGEX_QUOTE) 569 appendStringInfoChar(buf, 'q'); 570 571 appendStringInfoChar(buf, '"'); 572 } 573 574 if (printBracketes) 575 appendStringInfoChar(buf, ')'); 576 break; 577 case jpiPlus: 578 case jpiMinus: 579 if (printBracketes) 580 appendStringInfoChar(buf, '('); 581 appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-'); 582 jspGetArg(v, &elem); 583 printJsonPathItem(buf, &elem, false, 584 operationPriority(elem.type) <= 585 operationPriority(v->type)); 586 if (printBracketes) 587 appendStringInfoChar(buf, ')'); 588 break; 589 case jpiFilter: 590 appendBinaryStringInfo(buf, "?(", 2); 591 jspGetArg(v, &elem); 592 printJsonPathItem(buf, &elem, false, false); 593 appendStringInfoChar(buf, ')'); 594 break; 595 case jpiNot: 596 appendBinaryStringInfo(buf, "!(", 2); 597 jspGetArg(v, &elem); 598 printJsonPathItem(buf, &elem, false, false); 599 appendStringInfoChar(buf, ')'); 600 break; 601 case jpiIsUnknown: 602 appendStringInfoChar(buf, '('); 603 jspGetArg(v, &elem); 604 printJsonPathItem(buf, &elem, false, false); 605 appendBinaryStringInfo(buf, ") is unknown", 12); 606 break; 607 case jpiExists: 608 appendBinaryStringInfo(buf, "exists (", 8); 609 jspGetArg(v, &elem); 610 printJsonPathItem(buf, &elem, false, false); 611 appendStringInfoChar(buf, ')'); 612 break; 613 case jpiCurrent: 614 Assert(!inKey); 615 appendStringInfoChar(buf, '@'); 616 break; 617 case jpiRoot: 618 Assert(!inKey); 619 appendStringInfoChar(buf, '$'); 620 break; 621 case jpiLast: 622 appendBinaryStringInfo(buf, "last", 4); 623 break; 624 case jpiAnyArray: 625 appendBinaryStringInfo(buf, "[*]", 3); 626 break; 627 case jpiAnyKey: 628 if (inKey) 629 appendStringInfoChar(buf, '.'); 630 appendStringInfoChar(buf, '*'); 631 break; 632 case jpiIndexArray: 633 appendStringInfoChar(buf, '['); 634 for (i = 0; i < v->content.array.nelems; i++) 635 { 636 JsonPathItem from; 637 JsonPathItem to; 638 bool range = jspGetArraySubscript(v, &from, &to, i); 639 640 if (i) 641 appendStringInfoChar(buf, ','); 642 643 printJsonPathItem(buf, &from, false, false); 644 645 if (range) 646 { 647 appendBinaryStringInfo(buf, " to ", 4); 648 printJsonPathItem(buf, &to, false, false); 649 } 650 } 651 appendStringInfoChar(buf, ']'); 652 break; 653 case jpiAny: 654 if (inKey) 655 appendStringInfoChar(buf, '.'); 656 657 if (v->content.anybounds.first == 0 && 658 v->content.anybounds.last == PG_UINT32_MAX) 659 appendBinaryStringInfo(buf, "**", 2); 660 else if (v->content.anybounds.first == v->content.anybounds.last) 661 { 662 if (v->content.anybounds.first == PG_UINT32_MAX) 663 appendStringInfo(buf, "**{last}"); 664 else 665 appendStringInfo(buf, "**{%u}", 666 v->content.anybounds.first); 667 } 668 else if (v->content.anybounds.first == PG_UINT32_MAX) 669 appendStringInfo(buf, "**{last to %u}", 670 v->content.anybounds.last); 671 else if (v->content.anybounds.last == PG_UINT32_MAX) 672 appendStringInfo(buf, "**{%u to last}", 673 v->content.anybounds.first); 674 else 675 appendStringInfo(buf, "**{%u to %u}", 676 v->content.anybounds.first, 677 v->content.anybounds.last); 678 break; 679 case jpiType: 680 appendBinaryStringInfo(buf, ".type()", 7); 681 break; 682 case jpiSize: 683 appendBinaryStringInfo(buf, ".size()", 7); 684 break; 685 case jpiAbs: 686 appendBinaryStringInfo(buf, ".abs()", 6); 687 break; 688 case jpiFloor: 689 appendBinaryStringInfo(buf, ".floor()", 8); 690 break; 691 case jpiCeiling: 692 appendBinaryStringInfo(buf, ".ceiling()", 10); 693 break; 694 case jpiDouble: 695 appendBinaryStringInfo(buf, ".double()", 9); 696 break; 697 case jpiDatetime: 698 appendBinaryStringInfo(buf, ".datetime(", 10); 699 if (v->content.arg) 700 { 701 jspGetArg(v, &elem); 702 printJsonPathItem(buf, &elem, false, false); 703 } 704 appendStringInfoChar(buf, ')'); 705 break; 706 case jpiKeyValue: 707 appendBinaryStringInfo(buf, ".keyvalue()", 11); 708 break; 709 default: 710 elog(ERROR, "unrecognized jsonpath item type: %d", v->type); 711 } 712 713 if (jspGetNext(v, &elem)) 714 printJsonPathItem(buf, &elem, true, true); 715 } 716 717 const char * 718 jspOperationName(JsonPathItemType type) 719 { 720 switch (type) 721 { 722 case jpiAnd: 723 return "&&"; 724 case jpiOr: 725 return "||"; 726 case jpiEqual: 727 return "=="; 728 case jpiNotEqual: 729 return "!="; 730 case jpiLess: 731 return "<"; 732 case jpiGreater: 733 return ">"; 734 case jpiLessOrEqual: 735 return "<="; 736 case jpiGreaterOrEqual: 737 return ">="; 738 case jpiPlus: 739 case jpiAdd: 740 return "+"; 741 case jpiMinus: 742 case jpiSub: 743 return "-"; 744 case jpiMul: 745 return "*"; 746 case jpiDiv: 747 return "/"; 748 case jpiMod: 749 return "%"; 750 case jpiStartsWith: 751 return "starts with"; 752 case jpiLikeRegex: 753 return "like_regex"; 754 case jpiType: 755 return "type"; 756 case jpiSize: 757 return "size"; 758 case jpiKeyValue: 759 return "keyvalue"; 760 case jpiDouble: 761 return "double"; 762 case jpiAbs: 763 return "abs"; 764 case jpiFloor: 765 return "floor"; 766 case jpiCeiling: 767 return "ceiling"; 768 case jpiDatetime: 769 return "datetime"; 770 default: 771 elog(ERROR, "unrecognized jsonpath item type: %d", type); 772 return NULL; 773 } 774 } 775 776 static int 777 operationPriority(JsonPathItemType op) 778 { 779 switch (op) 780 { 781 case jpiOr: 782 return 0; 783 case jpiAnd: 784 return 1; 785 case jpiEqual: 786 case jpiNotEqual: 787 case jpiLess: 788 case jpiGreater: 789 case jpiLessOrEqual: 790 case jpiGreaterOrEqual: 791 case jpiStartsWith: 792 return 2; 793 case jpiAdd: 794 case jpiSub: 795 return 3; 796 case jpiMul: 797 case jpiDiv: 798 case jpiMod: 799 return 4; 800 case jpiPlus: 801 case jpiMinus: 802 return 5; 803 default: 804 return 6; 805 } 806 } 807 808 /******************* Support functions for JsonPath *************************/ 809 810 /* 811 * Support macros to read stored values 812 */ 813 814 #define read_byte(v, b, p) do { \ 815 (v) = *(uint8*)((b) + (p)); \ 816 (p) += 1; \ 817 } while(0) \ 818 819 #define read_int32(v, b, p) do { \ 820 (v) = *(uint32*)((b) + (p)); \ 821 (p) += sizeof(int32); \ 822 } while(0) \ 823 824 #define read_int32_n(v, b, p, n) do { \ 825 (v) = (void *)((b) + (p)); \ 826 (p) += sizeof(int32) * (n); \ 827 } while(0) \ 828 829 /* 830 * Read root node and fill root node representation 831 */ 832 void 833 jspInit(JsonPathItem *v, JsonPath *js) 834 { 835 Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION); 836 jspInitByBuffer(v, js->data, 0); 837 } 838 839 /* 840 * Read node from buffer and fill its representation 841 */ 842 void 843 jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) 844 { 845 v->base = base + pos; 846 847 read_byte(v->type, base, pos); 848 pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base; 849 read_int32(v->nextPos, base, pos); 850 851 switch (v->type) 852 { 853 case jpiNull: 854 case jpiRoot: 855 case jpiCurrent: 856 case jpiAnyArray: 857 case jpiAnyKey: 858 case jpiType: 859 case jpiSize: 860 case jpiAbs: 861 case jpiFloor: 862 case jpiCeiling: 863 case jpiDouble: 864 case jpiKeyValue: 865 case jpiLast: 866 break; 867 case jpiKey: 868 case jpiString: 869 case jpiVariable: 870 read_int32(v->content.value.datalen, base, pos); 871 /* FALLTHROUGH */ 872 case jpiNumeric: 873 case jpiBool: 874 v->content.value.data = base + pos; 875 break; 876 case jpiAnd: 877 case jpiOr: 878 case jpiAdd: 879 case jpiSub: 880 case jpiMul: 881 case jpiDiv: 882 case jpiMod: 883 case jpiEqual: 884 case jpiNotEqual: 885 case jpiLess: 886 case jpiGreater: 887 case jpiLessOrEqual: 888 case jpiGreaterOrEqual: 889 case jpiStartsWith: 890 read_int32(v->content.args.left, base, pos); 891 read_int32(v->content.args.right, base, pos); 892 break; 893 case jpiLikeRegex: 894 read_int32(v->content.like_regex.flags, base, pos); 895 read_int32(v->content.like_regex.expr, base, pos); 896 read_int32(v->content.like_regex.patternlen, base, pos); 897 v->content.like_regex.pattern = base + pos; 898 break; 899 case jpiNot: 900 case jpiExists: 901 case jpiIsUnknown: 902 case jpiPlus: 903 case jpiMinus: 904 case jpiFilter: 905 case jpiDatetime: 906 read_int32(v->content.arg, base, pos); 907 break; 908 case jpiIndexArray: 909 read_int32(v->content.array.nelems, base, pos); 910 read_int32_n(v->content.array.elems, base, pos, 911 v->content.array.nelems * 2); 912 break; 913 case jpiAny: 914 read_int32(v->content.anybounds.first, base, pos); 915 read_int32(v->content.anybounds.last, base, pos); 916 break; 917 default: 918 elog(ERROR, "unrecognized jsonpath item type: %d", v->type); 919 } 920 } 921 922 void 923 jspGetArg(JsonPathItem *v, JsonPathItem *a) 924 { 925 Assert(v->type == jpiFilter || 926 v->type == jpiNot || 927 v->type == jpiIsUnknown || 928 v->type == jpiExists || 929 v->type == jpiPlus || 930 v->type == jpiMinus || 931 v->type == jpiDatetime); 932 933 jspInitByBuffer(a, v->base, v->content.arg); 934 } 935 936 bool 937 jspGetNext(JsonPathItem *v, JsonPathItem *a) 938 { 939 if (jspHasNext(v)) 940 { 941 Assert(v->type == jpiString || 942 v->type == jpiNumeric || 943 v->type == jpiBool || 944 v->type == jpiNull || 945 v->type == jpiKey || 946 v->type == jpiAny || 947 v->type == jpiAnyArray || 948 v->type == jpiAnyKey || 949 v->type == jpiIndexArray || 950 v->type == jpiFilter || 951 v->type == jpiCurrent || 952 v->type == jpiExists || 953 v->type == jpiRoot || 954 v->type == jpiVariable || 955 v->type == jpiLast || 956 v->type == jpiAdd || 957 v->type == jpiSub || 958 v->type == jpiMul || 959 v->type == jpiDiv || 960 v->type == jpiMod || 961 v->type == jpiPlus || 962 v->type == jpiMinus || 963 v->type == jpiEqual || 964 v->type == jpiNotEqual || 965 v->type == jpiGreater || 966 v->type == jpiGreaterOrEqual || 967 v->type == jpiLess || 968 v->type == jpiLessOrEqual || 969 v->type == jpiAnd || 970 v->type == jpiOr || 971 v->type == jpiNot || 972 v->type == jpiIsUnknown || 973 v->type == jpiType || 974 v->type == jpiSize || 975 v->type == jpiAbs || 976 v->type == jpiFloor || 977 v->type == jpiCeiling || 978 v->type == jpiDouble || 979 v->type == jpiDatetime || 980 v->type == jpiKeyValue || 981 v->type == jpiStartsWith); 982 983 if (a) 984 jspInitByBuffer(a, v->base, v->nextPos); 985 return true; 986 } 987 988 return false; 989 } 990 991 void 992 jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) 993 { 994 Assert(v->type == jpiAnd || 995 v->type == jpiOr || 996 v->type == jpiEqual || 997 v->type == jpiNotEqual || 998 v->type == jpiLess || 999 v->type == jpiGreater || 1000 v->type == jpiLessOrEqual || 1001 v->type == jpiGreaterOrEqual || 1002 v->type == jpiAdd || 1003 v->type == jpiSub || 1004 v->type == jpiMul || 1005 v->type == jpiDiv || 1006 v->type == jpiMod || 1007 v->type == jpiStartsWith); 1008 1009 jspInitByBuffer(a, v->base, v->content.args.left); 1010 } 1011 1012 void 1013 jspGetRightArg(JsonPathItem *v, JsonPathItem *a) 1014 { 1015 Assert(v->type == jpiAnd || 1016 v->type == jpiOr || 1017 v->type == jpiEqual || 1018 v->type == jpiNotEqual || 1019 v->type == jpiLess || 1020 v->type == jpiGreater || 1021 v->type == jpiLessOrEqual || 1022 v->type == jpiGreaterOrEqual || 1023 v->type == jpiAdd || 1024 v->type == jpiSub || 1025 v->type == jpiMul || 1026 v->type == jpiDiv || 1027 v->type == jpiMod || 1028 v->type == jpiStartsWith); 1029 1030 jspInitByBuffer(a, v->base, v->content.args.right); 1031 } 1032 1033 bool 1034 jspGetBool(JsonPathItem *v) 1035 { 1036 Assert(v->type == jpiBool); 1037 1038 return (bool) *v->content.value.data; 1039 } 1040 1041 Numeric 1042 jspGetNumeric(JsonPathItem *v) 1043 { 1044 Assert(v->type == jpiNumeric); 1045 1046 return (Numeric) v->content.value.data; 1047 } 1048 1049 char * 1050 jspGetString(JsonPathItem *v, int32 *len) 1051 { 1052 Assert(v->type == jpiKey || 1053 v->type == jpiString || 1054 v->type == jpiVariable); 1055 1056 if (len) 1057 *len = v->content.value.datalen; 1058 return v->content.value.data; 1059 } 1060 1061 bool 1062 jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, 1063 int i) 1064 { 1065 Assert(v->type == jpiIndexArray); 1066 1067 jspInitByBuffer(from, v->base, v->content.array.elems[i].from); 1068 1069 if (!v->content.array.elems[i].to) 1070 return false; 1071 1072 jspInitByBuffer(to, v->base, v->content.array.elems[i].to); 1073 1074 return true; 1075 } 1076