| /*------------------------------------------------------------------------- |
| * |
| * jsonpath.c |
| * Input/output and supporting routines for jsonpath |
| * |
| * jsonpath expression is a chain of path items. First path item is $, $var, |
| * literal or arithmetic expression. Subsequent path items are accessors |
| * (.key, .*, [subscripts], [*]), filters (? (predicate)) and methods (.type(), |
| * .size() etc). |
| * |
| * For instance, structure of path items for simple expression: |
| * |
| * $.a[*].type() |
| * |
| * is pretty evident: |
| * |
| * $ => .a => [*] => .type() |
| * |
| * Some path items such as arithmetic operations, predicates or array |
| * subscripts may comprise subtrees. For instance, more complex expression |
| * |
| * ($.a + $[1 to 5, 7] ? (@ > 3).double()).type() |
| * |
| * have following structure of path items: |
| * |
| * + => .type() |
| * ___/ \___ |
| * / \ |
| * $ => .a $ => [] => ? => .double() |
| * _||_ | |
| * / \ > |
| * to to / \ |
| * / \ / @ 3 |
| * 1 5 7 |
| * |
| * Binary encoding of jsonpath constitutes a sequence of 4-bytes aligned |
| * variable-length path items connected by links. Every item has a header |
| * consisting of item type (enum JsonPathItemType) and offset of next item |
| * (zero means no next item). After the header, item may have payload |
| * depending on item type. For instance, payload of '.key' accessor item is |
| * length of key name and key name itself. Payload of '>' arithmetic operator |
| * item is offsets of right and left operands. |
| * |
| * So, binary representation of sample expression above is: |
| * (bottom arrows are next links, top lines are argument links) |
| * |
| * _____ |
| * _____ ___/____ \ __ |
| * _ /_ \ _____/__/____ \ \ __ _ /_ \ |
| * / / \ \ / / / \ \ \ / \ / / \ \ |
| * +(LR) $ .a $ [](* to *, * to *) 1 5 7 ?(A) >(LR) @ 3 .double() .type() |
| * | | ^ | ^| ^| ^ ^ |
| * | |__| |__||________________________||___________________| | |
| * |_______________________________________________________________________| |
| * |
| * Copyright (c) 2019-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/jsonpath.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "funcapi.h" |
| #include "lib/stringinfo.h" |
| #include "libpq/pqformat.h" |
| #include "nodes/miscnodes.h" |
| #include "miscadmin.h" |
| #include "utils/builtins.h" |
| #include "utils/json.h" |
| #include "utils/jsonpath.h" |
| |
| |
| static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext); |
| static char *jsonPathToCstring(StringInfo out, JsonPath *in, |
| int estimated_len); |
| static bool flattenJsonPathParseItem(StringInfo buf, int *result, |
| struct Node *escontext, |
| JsonPathParseItem *item, |
| int nestingLevel, bool insideArraySubscript); |
| static void alignStringInfoInt(StringInfo buf); |
| static int32 reserveSpaceForItemPointer(StringInfo buf); |
| static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, |
| bool printBracketes); |
| static int operationPriority(JsonPathItemType op); |
| |
| |
| /**************************** INPUT/OUTPUT ********************************/ |
| |
| /* |
| * jsonpath type input function |
| */ |
| Datum |
| jsonpath_in(PG_FUNCTION_ARGS) |
| { |
| char *in = PG_GETARG_CSTRING(0); |
| int len = strlen(in); |
| |
| return jsonPathFromCstring(in, len, fcinfo->context); |
| } |
| |
| /* |
| * jsonpath type recv function |
| * |
| * The type is sent as text in binary mode, so this is almost the same |
| * as the input function, but it's prefixed with a version number so we |
| * can change the binary format sent in future if necessary. For now, |
| * only version 1 is supported. |
| */ |
| Datum |
| jsonpath_recv(PG_FUNCTION_ARGS) |
| { |
| StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); |
| int version = pq_getmsgint(buf, 1); |
| char *str; |
| int nbytes; |
| |
| if (version == JSONPATH_VERSION) |
| str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); |
| else |
| elog(ERROR, "unsupported jsonpath version number: %d", version); |
| |
| return jsonPathFromCstring(str, nbytes, NULL); |
| } |
| |
| /* |
| * jsonpath type output function |
| */ |
| Datum |
| jsonpath_out(PG_FUNCTION_ARGS) |
| { |
| JsonPath *in = PG_GETARG_JSONPATH_P(0); |
| |
| PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in))); |
| } |
| |
| /* |
| * jsonpath type send function |
| * |
| * Just send jsonpath as a version number, then a string of text |
| */ |
| Datum |
| jsonpath_send(PG_FUNCTION_ARGS) |
| { |
| JsonPath *in = PG_GETARG_JSONPATH_P(0); |
| StringInfoData buf; |
| StringInfoData jtext; |
| int version = JSONPATH_VERSION; |
| |
| initStringInfo(&jtext); |
| (void) jsonPathToCstring(&jtext, in, VARSIZE(in)); |
| |
| pq_begintypsend(&buf); |
| pq_sendint8(&buf, version); |
| pq_sendtext(&buf, jtext.data, jtext.len); |
| pfree(jtext.data); |
| |
| PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); |
| } |
| |
| /* |
| * Converts C-string to a jsonpath value. |
| * |
| * Uses jsonpath parser to turn string into an AST, then |
| * flattenJsonPathParseItem() does second pass turning AST into binary |
| * representation of jsonpath. |
| */ |
| static Datum |
| jsonPathFromCstring(char *in, int len, struct Node *escontext) |
| { |
| JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext); |
| JsonPath *res; |
| StringInfoData buf; |
| |
| if (SOFT_ERROR_OCCURRED(escontext)) |
| return (Datum) 0; |
| |
| if (!jsonpath) |
| ereturn(escontext, (Datum) 0, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", |
| in))); |
| |
| initStringInfo(&buf); |
| enlargeStringInfo(&buf, 4 * len /* estimation */ ); |
| |
| appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); |
| |
| if (!flattenJsonPathParseItem(&buf, NULL, escontext, |
| jsonpath->expr, 0, false)) |
| return (Datum) 0; |
| |
| res = (JsonPath *) buf.data; |
| SET_VARSIZE(res, buf.len); |
| res->header = JSONPATH_VERSION; |
| if (jsonpath->lax) |
| res->header |= JSONPATH_LAX; |
| |
| PG_RETURN_JSONPATH_P(res); |
| } |
| |
| /* |
| * Converts jsonpath value to a C-string. |
| * |
| * If 'out' argument is non-null, the resulting C-string is stored inside the |
| * StringBuffer. The resulting string is always returned. |
| */ |
| static char * |
| jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) |
| { |
| StringInfoData buf; |
| JsonPathItem v; |
| |
| if (!out) |
| { |
| out = &buf; |
| initStringInfo(out); |
| } |
| enlargeStringInfo(out, estimated_len); |
| |
| if (!(in->header & JSONPATH_LAX)) |
| appendStringInfoString(out, "strict "); |
| |
| jspInit(&v, in); |
| printJsonPathItem(out, &v, false, true); |
| |
| return out->data; |
| } |
| |
| /* |
| * Recursive function converting given jsonpath parse item and all its |
| * children into a binary representation. |
| */ |
| static bool |
| flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, |
| JsonPathParseItem *item, int nestingLevel, |
| bool insideArraySubscript) |
| { |
| /* position from beginning of jsonpath data */ |
| int32 pos = buf->len - JSONPATH_HDRSZ; |
| int32 chld; |
| int32 next; |
| int argNestingLevel = 0; |
| |
| check_stack_depth(); |
| CHECK_FOR_INTERRUPTS(); |
| |
| appendStringInfoChar(buf, (char) (item->type)); |
| |
| /* |
| * We align buffer to int32 because a series of int32 values often goes |
| * after the header, and we want to read them directly by dereferencing |
| * int32 pointer (see jspInitByBuffer()). |
| */ |
| alignStringInfoInt(buf); |
| |
| /* |
| * Reserve space for next item pointer. Actual value will be recorded |
| * later, after next and children items processing. |
| */ |
| next = reserveSpaceForItemPointer(buf); |
| |
| switch (item->type) |
| { |
| case jpiString: |
| case jpiVariable: |
| case jpiKey: |
| appendBinaryStringInfo(buf, &item->value.string.len, |
| sizeof(item->value.string.len)); |
| appendBinaryStringInfo(buf, item->value.string.val, |
| item->value.string.len); |
| appendStringInfoChar(buf, '\0'); |
| break; |
| case jpiNumeric: |
| appendBinaryStringInfo(buf, item->value.numeric, |
| VARSIZE(item->value.numeric)); |
| break; |
| case jpiBool: |
| appendBinaryStringInfo(buf, &item->value.boolean, |
| sizeof(item->value.boolean)); |
| break; |
| case jpiAnd: |
| case jpiOr: |
| case jpiEqual: |
| case jpiNotEqual: |
| case jpiLess: |
| case jpiGreater: |
| case jpiLessOrEqual: |
| case jpiGreaterOrEqual: |
| case jpiAdd: |
| case jpiSub: |
| case jpiMul: |
| case jpiDiv: |
| case jpiMod: |
| case jpiStartsWith: |
| { |
| /* |
| * First, reserve place for left/right arg's positions, then |
| * record both args and sets actual position in reserved |
| * places. |
| */ |
| int32 left = reserveSpaceForItemPointer(buf); |
| int32 right = reserveSpaceForItemPointer(buf); |
| |
| if (!item->value.args.left) |
| chld = pos; |
| else if (!flattenJsonPathParseItem(buf, &chld, escontext, |
| item->value.args.left, |
| nestingLevel + argNestingLevel, |
| insideArraySubscript)) |
| return false; |
| *(int32 *) (buf->data + left) = chld - pos; |
| |
| if (!item->value.args.right) |
| chld = pos; |
| else if (!flattenJsonPathParseItem(buf, &chld, escontext, |
| item->value.args.right, |
| nestingLevel + argNestingLevel, |
| insideArraySubscript)) |
| return false; |
| *(int32 *) (buf->data + right) = chld - pos; |
| } |
| break; |
| case jpiLikeRegex: |
| { |
| int32 offs; |
| |
| appendBinaryStringInfo(buf, |
| &item->value.like_regex.flags, |
| sizeof(item->value.like_regex.flags)); |
| offs = reserveSpaceForItemPointer(buf); |
| appendBinaryStringInfo(buf, |
| &item->value.like_regex.patternlen, |
| sizeof(item->value.like_regex.patternlen)); |
| appendBinaryStringInfo(buf, item->value.like_regex.pattern, |
| item->value.like_regex.patternlen); |
| appendStringInfoChar(buf, '\0'); |
| |
| if (!flattenJsonPathParseItem(buf, &chld, escontext, |
| item->value.like_regex.expr, |
| nestingLevel, |
| insideArraySubscript)) |
| return false; |
| *(int32 *) (buf->data + offs) = chld - pos; |
| } |
| break; |
| case jpiFilter: |
| argNestingLevel++; |
| /* FALLTHROUGH */ |
| case jpiIsUnknown: |
| case jpiNot: |
| case jpiPlus: |
| case jpiMinus: |
| case jpiExists: |
| case jpiDatetime: |
| { |
| int32 arg = reserveSpaceForItemPointer(buf); |
| |
| if (!item->value.arg) |
| chld = pos; |
| else if (!flattenJsonPathParseItem(buf, &chld, escontext, |
| item->value.arg, |
| nestingLevel + argNestingLevel, |
| insideArraySubscript)) |
| return false; |
| *(int32 *) (buf->data + arg) = chld - pos; |
| } |
| break; |
| case jpiNull: |
| break; |
| case jpiRoot: |
| break; |
| case jpiAnyArray: |
| case jpiAnyKey: |
| break; |
| case jpiCurrent: |
| if (nestingLevel <= 0) |
| ereturn(escontext, false, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("@ is not allowed in root expressions"))); |
| break; |
| case jpiLast: |
| if (!insideArraySubscript) |
| ereturn(escontext, false, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("LAST is allowed only in array subscripts"))); |
| break; |
| case jpiIndexArray: |
| { |
| int32 nelems = item->value.array.nelems; |
| int offset; |
| int i; |
| |
| appendBinaryStringInfo(buf, &nelems, sizeof(nelems)); |
| |
| offset = buf->len; |
| |
| appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); |
| |
| for (i = 0; i < nelems; i++) |
| { |
| int32 *ppos; |
| int32 topos; |
| int32 frompos; |
| |
| if (!flattenJsonPathParseItem(buf, &frompos, escontext, |
| item->value.array.elems[i].from, |
| nestingLevel, true)) |
| return false; |
| frompos -= pos; |
| |
| if (item->value.array.elems[i].to) |
| { |
| if (!flattenJsonPathParseItem(buf, &topos, escontext, |
| item->value.array.elems[i].to, |
| nestingLevel, true)) |
| return false; |
| topos -= pos; |
| } |
| else |
| topos = 0; |
| |
| ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)]; |
| |
| ppos[0] = frompos; |
| ppos[1] = topos; |
| } |
| } |
| break; |
| case jpiAny: |
| appendBinaryStringInfo(buf, |
| &item->value.anybounds.first, |
| sizeof(item->value.anybounds.first)); |
| appendBinaryStringInfo(buf, |
| &item->value.anybounds.last, |
| sizeof(item->value.anybounds.last)); |
| break; |
| case jpiType: |
| case jpiSize: |
| case jpiAbs: |
| case jpiFloor: |
| case jpiCeiling: |
| case jpiDouble: |
| case jpiKeyValue: |
| break; |
| default: |
| elog(ERROR, "unrecognized jsonpath item type: %d", item->type); |
| } |
| |
| if (item->next) |
| { |
| if (!flattenJsonPathParseItem(buf, &chld, escontext, |
| item->next, nestingLevel, |
| insideArraySubscript)) |
| return false; |
| chld -= pos; |
| *(int32 *) (buf->data + next) = chld; |
| } |
| |
| if (result) |
| *result = pos; |
| return true; |
| } |
| |
| /* |
| * Align StringInfo to int by adding zero padding bytes |
| */ |
| static void |
| alignStringInfoInt(StringInfo buf) |
| { |
| switch (INTALIGN(buf->len) - buf->len) |
| { |
| case 3: |
| appendStringInfoCharMacro(buf, 0); |
| /* FALLTHROUGH */ |
| case 2: |
| appendStringInfoCharMacro(buf, 0); |
| /* FALLTHROUGH */ |
| case 1: |
| appendStringInfoCharMacro(buf, 0); |
| /* FALLTHROUGH */ |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written, |
| * actual value will be recorded at '(int32 *) &buf->data[pos]' later. |
| */ |
| static int32 |
| reserveSpaceForItemPointer(StringInfo buf) |
| { |
| int32 pos = buf->len; |
| int32 ptr = 0; |
| |
| appendBinaryStringInfo(buf, &ptr, sizeof(ptr)); |
| |
| return pos; |
| } |
| |
| /* |
| * Prints text representation of given jsonpath item and all its children. |
| */ |
| static void |
| printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, |
| bool printBracketes) |
| { |
| JsonPathItem elem; |
| int i; |
| |
| check_stack_depth(); |
| CHECK_FOR_INTERRUPTS(); |
| |
| switch (v->type) |
| { |
| case jpiNull: |
| appendStringInfoString(buf, "null"); |
| break; |
| case jpiKey: |
| if (inKey) |
| appendStringInfoChar(buf, '.'); |
| escape_json(buf, jspGetString(v, NULL)); |
| break; |
| case jpiString: |
| escape_json(buf, jspGetString(v, NULL)); |
| break; |
| case jpiVariable: |
| appendStringInfoChar(buf, '$'); |
| escape_json(buf, jspGetString(v, NULL)); |
| break; |
| case jpiNumeric: |
| if (jspHasNext(v)) |
| appendStringInfoChar(buf, '('); |
| appendStringInfoString(buf, |
| DatumGetCString(DirectFunctionCall1(numeric_out, |
| NumericGetDatum(jspGetNumeric(v))))); |
| if (jspHasNext(v)) |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiBool: |
| if (jspGetBool(v)) |
| appendStringInfoString(buf, "true"); |
| else |
| appendStringInfoString(buf, "false"); |
| break; |
| case jpiAnd: |
| case jpiOr: |
| case jpiEqual: |
| case jpiNotEqual: |
| case jpiLess: |
| case jpiGreater: |
| case jpiLessOrEqual: |
| case jpiGreaterOrEqual: |
| case jpiAdd: |
| case jpiSub: |
| case jpiMul: |
| case jpiDiv: |
| case jpiMod: |
| case jpiStartsWith: |
| if (printBracketes) |
| appendStringInfoChar(buf, '('); |
| jspGetLeftArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, |
| operationPriority(elem.type) <= |
| operationPriority(v->type)); |
| appendStringInfoChar(buf, ' '); |
| appendStringInfoString(buf, jspOperationName(v->type)); |
| appendStringInfoChar(buf, ' '); |
| jspGetRightArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, |
| operationPriority(elem.type) <= |
| operationPriority(v->type)); |
| if (printBracketes) |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiLikeRegex: |
| if (printBracketes) |
| appendStringInfoChar(buf, '('); |
| |
| jspInitByBuffer(&elem, v->base, v->content.like_regex.expr); |
| printJsonPathItem(buf, &elem, false, |
| operationPriority(elem.type) <= |
| operationPriority(v->type)); |
| |
| appendStringInfoString(buf, " like_regex "); |
| |
| escape_json(buf, v->content.like_regex.pattern); |
| |
| if (v->content.like_regex.flags) |
| { |
| appendStringInfoString(buf, " flag \""); |
| |
| if (v->content.like_regex.flags & JSP_REGEX_ICASE) |
| appendStringInfoChar(buf, 'i'); |
| if (v->content.like_regex.flags & JSP_REGEX_DOTALL) |
| appendStringInfoChar(buf, 's'); |
| if (v->content.like_regex.flags & JSP_REGEX_MLINE) |
| appendStringInfoChar(buf, 'm'); |
| if (v->content.like_regex.flags & JSP_REGEX_WSPACE) |
| appendStringInfoChar(buf, 'x'); |
| if (v->content.like_regex.flags & JSP_REGEX_QUOTE) |
| appendStringInfoChar(buf, 'q'); |
| |
| appendStringInfoChar(buf, '"'); |
| } |
| |
| if (printBracketes) |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiPlus: |
| case jpiMinus: |
| if (printBracketes) |
| appendStringInfoChar(buf, '('); |
| appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-'); |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, |
| operationPriority(elem.type) <= |
| operationPriority(v->type)); |
| if (printBracketes) |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiFilter: |
| appendStringInfoString(buf, "?("); |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, false); |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiNot: |
| appendStringInfoString(buf, "!("); |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, false); |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiIsUnknown: |
| appendStringInfoChar(buf, '('); |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, false); |
| appendStringInfoString(buf, ") is unknown"); |
| break; |
| case jpiExists: |
| appendStringInfoString(buf, "exists ("); |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, false); |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiCurrent: |
| Assert(!inKey); |
| appendStringInfoChar(buf, '@'); |
| break; |
| case jpiRoot: |
| Assert(!inKey); |
| appendStringInfoChar(buf, '$'); |
| break; |
| case jpiLast: |
| appendStringInfoString(buf, "last"); |
| break; |
| case jpiAnyArray: |
| appendStringInfoString(buf, "[*]"); |
| break; |
| case jpiAnyKey: |
| if (inKey) |
| appendStringInfoChar(buf, '.'); |
| appendStringInfoChar(buf, '*'); |
| break; |
| case jpiIndexArray: |
| appendStringInfoChar(buf, '['); |
| for (i = 0; i < v->content.array.nelems; i++) |
| { |
| JsonPathItem from; |
| JsonPathItem to; |
| bool range = jspGetArraySubscript(v, &from, &to, i); |
| |
| if (i) |
| appendStringInfoChar(buf, ','); |
| |
| printJsonPathItem(buf, &from, false, false); |
| |
| if (range) |
| { |
| appendStringInfoString(buf, " to "); |
| printJsonPathItem(buf, &to, false, false); |
| } |
| } |
| appendStringInfoChar(buf, ']'); |
| break; |
| case jpiAny: |
| if (inKey) |
| appendStringInfoChar(buf, '.'); |
| |
| if (v->content.anybounds.first == 0 && |
| v->content.anybounds.last == PG_UINT32_MAX) |
| appendStringInfoString(buf, "**"); |
| else if (v->content.anybounds.first == v->content.anybounds.last) |
| { |
| if (v->content.anybounds.first == PG_UINT32_MAX) |
| appendStringInfoString(buf, "**{last}"); |
| else |
| appendStringInfo(buf, "**{%u}", |
| v->content.anybounds.first); |
| } |
| else if (v->content.anybounds.first == PG_UINT32_MAX) |
| appendStringInfo(buf, "**{last to %u}", |
| v->content.anybounds.last); |
| else if (v->content.anybounds.last == PG_UINT32_MAX) |
| appendStringInfo(buf, "**{%u to last}", |
| v->content.anybounds.first); |
| else |
| appendStringInfo(buf, "**{%u to %u}", |
| v->content.anybounds.first, |
| v->content.anybounds.last); |
| break; |
| case jpiType: |
| appendStringInfoString(buf, ".type()"); |
| break; |
| case jpiSize: |
| appendStringInfoString(buf, ".size()"); |
| break; |
| case jpiAbs: |
| appendStringInfoString(buf, ".abs()"); |
| break; |
| case jpiFloor: |
| appendStringInfoString(buf, ".floor()"); |
| break; |
| case jpiCeiling: |
| appendStringInfoString(buf, ".ceiling()"); |
| break; |
| case jpiDouble: |
| appendStringInfoString(buf, ".double()"); |
| break; |
| case jpiDatetime: |
| appendStringInfoString(buf, ".datetime("); |
| if (v->content.arg) |
| { |
| jspGetArg(v, &elem); |
| printJsonPathItem(buf, &elem, false, false); |
| } |
| appendStringInfoChar(buf, ')'); |
| break; |
| case jpiKeyValue: |
| appendStringInfoString(buf, ".keyvalue()"); |
| break; |
| default: |
| elog(ERROR, "unrecognized jsonpath item type: %d", v->type); |
| } |
| |
| if (jspGetNext(v, &elem)) |
| printJsonPathItem(buf, &elem, true, true); |
| } |
| |
| const char * |
| jspOperationName(JsonPathItemType type) |
| { |
| switch (type) |
| { |
| case jpiAnd: |
| return "&&"; |
| case jpiOr: |
| return "||"; |
| case jpiEqual: |
| return "=="; |
| case jpiNotEqual: |
| return "!="; |
| case jpiLess: |
| return "<"; |
| case jpiGreater: |
| return ">"; |
| case jpiLessOrEqual: |
| return "<="; |
| case jpiGreaterOrEqual: |
| return ">="; |
| case jpiPlus: |
| case jpiAdd: |
| return "+"; |
| case jpiMinus: |
| case jpiSub: |
| return "-"; |
| case jpiMul: |
| return "*"; |
| case jpiDiv: |
| return "/"; |
| case jpiMod: |
| return "%"; |
| case jpiStartsWith: |
| return "starts with"; |
| case jpiLikeRegex: |
| return "like_regex"; |
| case jpiType: |
| return "type"; |
| case jpiSize: |
| return "size"; |
| case jpiKeyValue: |
| return "keyvalue"; |
| case jpiDouble: |
| return "double"; |
| case jpiAbs: |
| return "abs"; |
| case jpiFloor: |
| return "floor"; |
| case jpiCeiling: |
| return "ceiling"; |
| case jpiDatetime: |
| return "datetime"; |
| default: |
| elog(ERROR, "unrecognized jsonpath item type: %d", type); |
| return NULL; |
| } |
| } |
| |
| static int |
| operationPriority(JsonPathItemType op) |
| { |
| switch (op) |
| { |
| case jpiOr: |
| return 0; |
| case jpiAnd: |
| return 1; |
| case jpiEqual: |
| case jpiNotEqual: |
| case jpiLess: |
| case jpiGreater: |
| case jpiLessOrEqual: |
| case jpiGreaterOrEqual: |
| case jpiStartsWith: |
| return 2; |
| case jpiAdd: |
| case jpiSub: |
| return 3; |
| case jpiMul: |
| case jpiDiv: |
| case jpiMod: |
| return 4; |
| case jpiPlus: |
| case jpiMinus: |
| return 5; |
| default: |
| return 6; |
| } |
| } |
| |
| /******************* Support functions for JsonPath *************************/ |
| |
| /* |
| * Support macros to read stored values |
| */ |
| |
| #define read_byte(v, b, p) do { \ |
| (v) = *(uint8*)((b) + (p)); \ |
| (p) += 1; \ |
| } while(0) \ |
| |
| #define read_int32(v, b, p) do { \ |
| (v) = *(uint32*)((b) + (p)); \ |
| (p) += sizeof(int32); \ |
| } while(0) \ |
| |
| #define read_int32_n(v, b, p, n) do { \ |
| (v) = (void *)((b) + (p)); \ |
| (p) += sizeof(int32) * (n); \ |
| } while(0) \ |
| |
| /* |
| * Read root node and fill root node representation |
| */ |
| void |
| jspInit(JsonPathItem *v, JsonPath *js) |
| { |
| Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION); |
| jspInitByBuffer(v, js->data, 0); |
| } |
| |
| /* |
| * Read node from buffer and fill its representation |
| */ |
| void |
| jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) |
| { |
| v->base = base + pos; |
| |
| read_byte(v->type, base, pos); |
| pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base; |
| read_int32(v->nextPos, base, pos); |
| |
| switch (v->type) |
| { |
| case jpiNull: |
| case jpiRoot: |
| case jpiCurrent: |
| case jpiAnyArray: |
| case jpiAnyKey: |
| case jpiType: |
| case jpiSize: |
| case jpiAbs: |
| case jpiFloor: |
| case jpiCeiling: |
| case jpiDouble: |
| case jpiKeyValue: |
| case jpiLast: |
| break; |
| case jpiKey: |
| case jpiString: |
| case jpiVariable: |
| read_int32(v->content.value.datalen, base, pos); |
| /* FALLTHROUGH */ |
| case jpiNumeric: |
| case jpiBool: |
| v->content.value.data = base + pos; |
| break; |
| case jpiAnd: |
| case jpiOr: |
| case jpiAdd: |
| case jpiSub: |
| case jpiMul: |
| case jpiDiv: |
| case jpiMod: |
| case jpiEqual: |
| case jpiNotEqual: |
| case jpiLess: |
| case jpiGreater: |
| case jpiLessOrEqual: |
| case jpiGreaterOrEqual: |
| case jpiStartsWith: |
| read_int32(v->content.args.left, base, pos); |
| read_int32(v->content.args.right, base, pos); |
| break; |
| case jpiLikeRegex: |
| read_int32(v->content.like_regex.flags, base, pos); |
| read_int32(v->content.like_regex.expr, base, pos); |
| read_int32(v->content.like_regex.patternlen, base, pos); |
| v->content.like_regex.pattern = base + pos; |
| break; |
| case jpiNot: |
| case jpiExists: |
| case jpiIsUnknown: |
| case jpiPlus: |
| case jpiMinus: |
| case jpiFilter: |
| case jpiDatetime: |
| read_int32(v->content.arg, base, pos); |
| break; |
| case jpiIndexArray: |
| read_int32(v->content.array.nelems, base, pos); |
| read_int32_n(v->content.array.elems, base, pos, |
| v->content.array.nelems * 2); |
| break; |
| case jpiAny: |
| read_int32(v->content.anybounds.first, base, pos); |
| read_int32(v->content.anybounds.last, base, pos); |
| break; |
| default: |
| elog(ERROR, "unrecognized jsonpath item type: %d", v->type); |
| } |
| } |
| |
| void |
| jspGetArg(JsonPathItem *v, JsonPathItem *a) |
| { |
| Assert(v->type == jpiFilter || |
| v->type == jpiNot || |
| v->type == jpiIsUnknown || |
| v->type == jpiExists || |
| v->type == jpiPlus || |
| v->type == jpiMinus || |
| v->type == jpiDatetime); |
| |
| jspInitByBuffer(a, v->base, v->content.arg); |
| } |
| |
| bool |
| jspGetNext(JsonPathItem *v, JsonPathItem *a) |
| { |
| if (jspHasNext(v)) |
| { |
| Assert(v->type == jpiString || |
| v->type == jpiNumeric || |
| v->type == jpiBool || |
| v->type == jpiNull || |
| v->type == jpiKey || |
| v->type == jpiAny || |
| v->type == jpiAnyArray || |
| v->type == jpiAnyKey || |
| v->type == jpiIndexArray || |
| v->type == jpiFilter || |
| v->type == jpiCurrent || |
| v->type == jpiExists || |
| v->type == jpiRoot || |
| v->type == jpiVariable || |
| v->type == jpiLast || |
| v->type == jpiAdd || |
| v->type == jpiSub || |
| v->type == jpiMul || |
| v->type == jpiDiv || |
| v->type == jpiMod || |
| v->type == jpiPlus || |
| v->type == jpiMinus || |
| v->type == jpiEqual || |
| v->type == jpiNotEqual || |
| v->type == jpiGreater || |
| v->type == jpiGreaterOrEqual || |
| v->type == jpiLess || |
| v->type == jpiLessOrEqual || |
| v->type == jpiAnd || |
| v->type == jpiOr || |
| v->type == jpiNot || |
| v->type == jpiIsUnknown || |
| v->type == jpiType || |
| v->type == jpiSize || |
| v->type == jpiAbs || |
| v->type == jpiFloor || |
| v->type == jpiCeiling || |
| v->type == jpiDouble || |
| v->type == jpiDatetime || |
| v->type == jpiKeyValue || |
| v->type == jpiStartsWith || |
| v->type == jpiLikeRegex); |
| |
| if (a) |
| jspInitByBuffer(a, v->base, v->nextPos); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void |
| jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) |
| { |
| Assert(v->type == jpiAnd || |
| v->type == jpiOr || |
| v->type == jpiEqual || |
| v->type == jpiNotEqual || |
| v->type == jpiLess || |
| v->type == jpiGreater || |
| v->type == jpiLessOrEqual || |
| v->type == jpiGreaterOrEqual || |
| v->type == jpiAdd || |
| v->type == jpiSub || |
| v->type == jpiMul || |
| v->type == jpiDiv || |
| v->type == jpiMod || |
| v->type == jpiStartsWith); |
| |
| jspInitByBuffer(a, v->base, v->content.args.left); |
| } |
| |
| void |
| jspGetRightArg(JsonPathItem *v, JsonPathItem *a) |
| { |
| Assert(v->type == jpiAnd || |
| v->type == jpiOr || |
| v->type == jpiEqual || |
| v->type == jpiNotEqual || |
| v->type == jpiLess || |
| v->type == jpiGreater || |
| v->type == jpiLessOrEqual || |
| v->type == jpiGreaterOrEqual || |
| v->type == jpiAdd || |
| v->type == jpiSub || |
| v->type == jpiMul || |
| v->type == jpiDiv || |
| v->type == jpiMod || |
| v->type == jpiStartsWith); |
| |
| jspInitByBuffer(a, v->base, v->content.args.right); |
| } |
| |
| bool |
| jspGetBool(JsonPathItem *v) |
| { |
| Assert(v->type == jpiBool); |
| |
| return (bool) *v->content.value.data; |
| } |
| |
| Numeric |
| jspGetNumeric(JsonPathItem *v) |
| { |
| Assert(v->type == jpiNumeric); |
| |
| return (Numeric) v->content.value.data; |
| } |
| |
| char * |
| jspGetString(JsonPathItem *v, int32 *len) |
| { |
| Assert(v->type == jpiKey || |
| v->type == jpiString || |
| v->type == jpiVariable); |
| |
| if (len) |
| *len = v->content.value.datalen; |
| return v->content.value.data; |
| } |
| |
| bool |
| jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, |
| int i) |
| { |
| Assert(v->type == jpiIndexArray); |
| |
| jspInitByBuffer(from, v->base, v->content.array.elems[i].from); |
| |
| if (!v->content.array.elems[i].to) |
| return false; |
| |
| jspInitByBuffer(to, v->base, v->content.array.elems[i].to); |
| |
| return true; |
| } |