/*------------------------------------------------------------------------- * * 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-2021, 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 "miscadmin.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/jsonpath.h" static Datum jsonPathFromCstring(char *in, int len); static char *jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len); static int flattenJsonPathParseItem(StringInfo buf, 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); } /* * 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); } /* * 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) { JsonPathParseResult *jsonpath = parsejsonpath(in, len); JsonPath *res; StringInfoData buf; initStringInfo(&buf); enlargeStringInfo(&buf, 4 * len /* estimation */ ); appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); if (!jsonpath) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", in))); flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false); 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)) appendBinaryStringInfo(out, "strict ", 7); 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 int flattenJsonPathParseItem(StringInfo buf, 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, (char *) &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, (char *) item->value.numeric, VARSIZE(item->value.numeric)); break; case jpiBool: appendBinaryStringInfo(buf, (char *) &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); chld = !item->value.args.left ? pos : flattenJsonPathParseItem(buf, item->value.args.left, nestingLevel + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + left) = chld - pos; chld = !item->value.args.right ? pos : flattenJsonPathParseItem(buf, item->value.args.right, nestingLevel + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + right) = chld - pos; } break; case jpiLikeRegex: { int32 offs; appendBinaryStringInfo(buf, (char *) &item->value.like_regex.flags, sizeof(item->value.like_regex.flags)); offs = reserveSpaceForItemPointer(buf); appendBinaryStringInfo(buf, (char *) &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'); chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, nestingLevel, insideArraySubscript); *(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); chld = !item->value.arg ? pos : flattenJsonPathParseItem(buf, item->value.arg, nestingLevel + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + arg) = chld - pos; } break; case jpiNull: break; case jpiRoot: break; case jpiAnyArray: case jpiAnyKey: break; case jpiCurrent: if (nestingLevel <= 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("@ is not allowed in root expressions"))); break; case jpiLast: if (!insideArraySubscript) ereport(ERROR, (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, (char *) &nelems, sizeof(nelems)); offset = buf->len; appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); for (i = 0; i < nelems; i++) { int32 *ppos; int32 topos; int32 frompos = flattenJsonPathParseItem(buf, item->value.array.elems[i].from, nestingLevel, true) - pos; if (item->value.array.elems[i].to) topos = flattenJsonPathParseItem(buf, item->value.array.elems[i].to, nestingLevel, true) - pos; else topos = 0; ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)]; ppos[0] = frompos; ppos[1] = topos; } } break; case jpiAny: appendBinaryStringInfo(buf, (char *) &item->value.anybounds.first, sizeof(item->value.anybounds.first)); appendBinaryStringInfo(buf, (char *) &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) { chld = flattenJsonPathParseItem(buf, item->next, nestingLevel, insideArraySubscript) - pos; *(int32 *) (buf->data + next) = chld; } return pos; } /* * 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, (char *) &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: appendStringInfoString(buf, DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(jspGetNumeric(v))))); break; case jpiBool: if (jspGetBool(v)) appendBinaryStringInfo(buf, "true", 4); else appendBinaryStringInfo(buf, "false", 5); 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)); appendBinaryStringInfo(buf, " like_regex ", 12); escape_json(buf, v->content.like_regex.pattern); if (v->content.like_regex.flags) { appendBinaryStringInfo(buf, " flag \"", 7); 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: appendBinaryStringInfo(buf, "?(", 2); jspGetArg(v, &elem); printJsonPathItem(buf, &elem, false, false); appendStringInfoChar(buf, ')'); break; case jpiNot: appendBinaryStringInfo(buf, "!(", 2); jspGetArg(v, &elem); printJsonPathItem(buf, &elem, false, false); appendStringInfoChar(buf, ')'); break; case jpiIsUnknown: appendStringInfoChar(buf, '('); jspGetArg(v, &elem); printJsonPathItem(buf, &elem, false, false); appendBinaryStringInfo(buf, ") is unknown", 12); break; case jpiExists: appendBinaryStringInfo(buf, "exists (", 8); 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: appendBinaryStringInfo(buf, "last", 4); break; case jpiAnyArray: appendBinaryStringInfo(buf, "[*]", 3); 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) { appendBinaryStringInfo(buf, " to ", 4); 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) appendBinaryStringInfo(buf, "**", 2); 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: appendBinaryStringInfo(buf, ".type()", 7); break; case jpiSize: appendBinaryStringInfo(buf, ".size()", 7); break; case jpiAbs: appendBinaryStringInfo(buf, ".abs()", 6); break; case jpiFloor: appendBinaryStringInfo(buf, ".floor()", 8); break; case jpiCeiling: appendBinaryStringInfo(buf, ".ceiling()", 10); break; case jpiDouble: appendBinaryStringInfo(buf, ".double()", 9); break; case jpiDatetime: appendBinaryStringInfo(buf, ".datetime(", 10); if (v->content.arg) { jspGetArg(v, &elem); printJsonPathItem(buf, &elem, false, false); } appendStringInfoChar(buf, ')'); break; case jpiKeyValue: appendBinaryStringInfo(buf, ".keyvalue()", 11); 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); 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; }