Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 02077ef

Browse files
author
Nikita Glukhov
committed
Add custom jsonpath item methods and functions
1 parent fd9c5a8 commit 02077ef

File tree

9 files changed

+506
-13
lines changed

9 files changed

+506
-13
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,42 @@ copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level,
576576
}
577577
break;
578578

579+
case jpiMethod:
580+
case jpiFunction:
581+
{
582+
int32 nargs = item->content.func.nargs;
583+
int offset;
584+
int i;
585+
586+
/* assign cache id */
587+
appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
588+
++cxt->id;
589+
590+
appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs));
591+
offset = buf->len;
592+
appendStringInfoSpaces(buf, sizeof(int32) * nargs);
593+
594+
appendBinaryStringInfo(buf, (char *) &item->content.func.namelen,
595+
sizeof(item->content.func.namelen));
596+
appendBinaryStringInfo(buf, item->content.func.name,
597+
item->content.func.namelen);
598+
appendStringInfoChar(buf, '\0');
599+
600+
for (i = 0; i < nargs; i++)
601+
{
602+
JsonPathItem arg;
603+
int32 argpos;
604+
605+
jspGetFunctionArg(item, i, &arg);
606+
607+
argpos = copyJsonPathItem(cxt, &arg, level, NULL, NULL);
608+
609+
*(int32 *) &buf->data[offset] = argpos - pos;
610+
offset += sizeof(int32);
611+
}
612+
}
613+
break;
614+
579615
default:
580616
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
581617
}
@@ -768,6 +804,39 @@ flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item,
768804
offset += sizeof(int32);
769805
}
770806
break;
807+
case jpiMethod:
808+
case jpiFunction:
809+
{
810+
int32 nargs = list_length(item->value.func.args);
811+
ListCell *lc;
812+
int offset;
813+
814+
/* assign cache id */
815+
appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id));
816+
++cxt->id;
817+
818+
appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs));
819+
offset = buf->len;
820+
appendStringInfoSpaces(buf, sizeof(int32) * nargs);
821+
822+
appendBinaryStringInfo(buf, (char *) &item->value.func.namelen,
823+
sizeof(item->value.func.namelen));
824+
appendBinaryStringInfo(buf, item->value.func.name,
825+
item->value.func.namelen);
826+
appendStringInfoChar(buf, '\0');
827+
828+
foreach(lc, item->value.func.args)
829+
{
830+
int32 argpos =
831+
flattenJsonPathParseItem(cxt, lfirst(lc),
832+
nestingLevel + 1,
833+
insideArraySubscript);
834+
835+
*(int32 *) &buf->data[offset] = argpos - pos;
836+
offset += sizeof(int32);
837+
}
838+
}
839+
break;
771840
case jpiNull:
772841
break;
773842
case jpiRoot:
@@ -1278,6 +1347,31 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
12781347
if (printBracketes || jspHasNext(v))
12791348
appendStringInfoChar(buf, ')');
12801349
break;
1350+
case jpiMethod:
1351+
case jpiFunction:
1352+
if (v->type == jpiMethod)
1353+
{
1354+
jspGetMethodItem(v, &elem);
1355+
printJsonPathItem(buf, &elem, false,
1356+
operationPriority(elem.type) <=
1357+
operationPriority(v->type));
1358+
appendStringInfoChar(buf, '.');
1359+
}
1360+
1361+
escape_json(buf, v->content.func.name);
1362+
appendStringInfoChar(buf, '(');
1363+
1364+
for (i = v->type == jpiMethod ? 1 : 0; i < v->content.func.nargs; i++)
1365+
{
1366+
if (i > (v->type == jpiMethod ? 1 : 0))
1367+
appendBinaryStringInfo(buf, ", ", 2);
1368+
1369+
jspGetFunctionArg(v, i, &elem);
1370+
printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
1371+
}
1372+
1373+
appendStringInfoChar(buf, ')');
1374+
break;
12811375
default:
12821376
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
12831377
}
@@ -1371,11 +1465,13 @@ operationPriority(JsonPathItemType op)
13711465
case jpiDiv:
13721466
case jpiMod:
13731467
return 4;
1468+
case jpiMethod:
1469+
return 5;
13741470
case jpiPlus:
13751471
case jpiMinus:
1376-
return 5;
1377-
default:
13781472
return 6;
1473+
default:
1474+
return 7;
13791475
}
13801476
}
13811477

@@ -1483,6 +1579,15 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
14831579
v->content.lambda.nparams);
14841580
read_int32(v->content.lambda.expr, base, pos);
14851581
break;
1582+
case jpiMethod:
1583+
case jpiFunction:
1584+
read_int32(v->content.func.id, base, pos);
1585+
read_int32(v->content.func.nargs, base, pos);
1586+
read_int32_n(v->content.func.args, base, pos,
1587+
v->content.func.nargs);
1588+
read_int32(v->content.func.namelen, base, pos);
1589+
v->content.func.name = base + pos;
1590+
break;
14861591
case jpiNot:
14871592
case jpiExists:
14881593
case jpiIsUnknown:
@@ -1581,7 +1686,9 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
15811686
v->type == jpiArray ||
15821687
v->type == jpiObject ||
15831688
v->type == jpiLambda ||
1584-
v->type == jpiArgument);
1689+
v->type == jpiArgument ||
1690+
v->type == jpiFunction ||
1691+
v->type == jpiMethod);
15851692

15861693
if (a)
15871694
jspInitByBuffer(a, v->base, v->nextPos);
@@ -1717,6 +1824,24 @@ jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr)
17171824
return expr;
17181825
}
17191826

1827+
JsonPathItem *
1828+
jspGetFunctionArg(JsonPathItem *func, int index, JsonPathItem *arg)
1829+
{
1830+
Assert(func->type == jpiMethod || func->type == jpiFunction);
1831+
Assert(index < func->content.func.nargs);
1832+
1833+
jspInitByBuffer(arg, func->base, func->content.func.args[index]);
1834+
1835+
return arg;
1836+
}
1837+
1838+
JsonPathItem *
1839+
jspGetMethodItem(JsonPathItem *method, JsonPathItem *arg)
1840+
{
1841+
Assert(method->type == jpiMethod);
1842+
return jspGetFunctionArg(method, 0, arg);
1843+
}
1844+
17201845
static void
17211846
checkJsonPathArgsMismatch(JsonPath *jp1, JsonPath *jp2)
17221847
{

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
#include "lib/stringinfo.h"
6767
#include "miscadmin.h"
6868
#include "nodes/nodeFuncs.h"
69+
#include "parser/parse_func.h"
6970
#include "regex/regex.h"
7071
#include "utils/builtins.h"
7172
#include "utils/date.h"
@@ -296,6 +297,9 @@ static int getJsonPathVariableFromJsonx(void *varsJsonb, bool isJsonb,
296297
static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv,
297298
JsonItem *rv, void *p);
298299
static int compareNumeric(Numeric a, Numeric b);
300+
static JsonPathExecResult executeFunction(JsonPathExecContext *cxt,
301+
JsonPathItem *jsp, JsonItem *js,
302+
JsonValueList *result /*, bool needBool */);
299303

300304
static void JsonItemInitNull(JsonItem *item);
301305
static void JsonItemInitBool(JsonItem *item, bool val);
@@ -839,6 +843,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar,
839843

840844
cxt.vars = vars;
841845
cxt.getVar = getVar;
846+
cxt.args = NULL;
842847
cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
843848
cxt.ignoreStructuralErrors = cxt.laxMode;
844849
cxt.root = &jsi;
@@ -1912,6 +1917,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
19121917
elog(ERROR, "jsonpath lambda expression cannot be executed directly");
19131918
break;
19141919

1920+
case jpiMethod:
1921+
case jpiFunction:
1922+
res = executeFunction(cxt, jsp, jb, found);
1923+
break;
1924+
19151925
default:
19161926
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
19171927
}
@@ -2052,6 +2062,130 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
20522062
return executeItem(cxt, jsp, jb, found);
20532063
}
20542064

2065+
typedef struct JsonPathFuncCache
2066+
{
2067+
FmgrInfo finfo;
2068+
JsonPathItem *args;
2069+
void **argscache;
2070+
} JsonPathFuncCache;
2071+
2072+
static JsonPathFuncCache *
2073+
prepareFunctionCache(JsonPathExecContext *cxt, JsonPathItem *jsp)
2074+
{
2075+
MemoryContext oldcontext;
2076+
JsonPathFuncCache *cache = cxt->cache[jsp->content.func.id];
2077+
List *funcname = list_make1(makeString(jsp->content.func.name));
2078+
Oid argtypes[] = {JSONPATH_FCXTOID};
2079+
Oid funcid;
2080+
int32 i;
2081+
2082+
if (cache)
2083+
return cache;
2084+
2085+
funcid = LookupFuncName(funcname,
2086+
sizeof(argtypes) / sizeof(argtypes[0]), argtypes,
2087+
false);
2088+
2089+
if (get_func_rettype(funcid) != INT8OID)
2090+
ereport(ERROR,
2091+
(errcode(ERRCODE_DATATYPE_MISMATCH),
2092+
errmsg("return type of jsonpath item function %s is not %s",
2093+
NameListToString(funcname), format_type_be(INT8OID))));
2094+
2095+
oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt);
2096+
2097+
cache = cxt->cache[jsp->content.func.id] = palloc0(sizeof(*cache));
2098+
2099+
fmgr_info(funcid, &cache->finfo);
2100+
2101+
cache->args = palloc(sizeof(*cache->args) * jsp->content.func.nargs);
2102+
cache->argscache = palloc0(sizeof(*cache->argscache) *
2103+
jsp->content.func.nargs);
2104+
2105+
for (i = 0; i < jsp->content.func.nargs; i++)
2106+
jspGetFunctionArg(jsp, i, &cache->args[i]);
2107+
2108+
MemoryContextSwitchTo(oldcontext);
2109+
2110+
return cache;
2111+
}
2112+
2113+
static JsonPathExecResult
2114+
executeFunction(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb,
2115+
JsonValueList *result /*, bool needBool */)
2116+
{
2117+
JsonPathFuncCache *cache = prepareFunctionCache(cxt, jsp);
2118+
JsonPathFuncContext fcxt;
2119+
JsonValueList tmpres = {0};
2120+
JsonValueListIterator tmpiter;
2121+
JsonPathExecResult res;
2122+
JsonItem *jsi;
2123+
2124+
fcxt.cxt = cxt;
2125+
fcxt.funcname = jsp->content.func.name;
2126+
fcxt.jb = jb;
2127+
fcxt.result = jspHasNext(jsp) ? &tmpres : result;
2128+
fcxt.args = cache->args;
2129+
fcxt.argscache = cache->argscache;
2130+
fcxt.nargs = jsp->content.func.nargs;
2131+
2132+
if (jsp->type == jpiMethod)
2133+
{
2134+
JsonValueList items = {0};
2135+
JsonValueListIterator iter;
2136+
2137+
/* skip first item argument */
2138+
fcxt.args++;
2139+
fcxt.argscache++;
2140+
fcxt.nargs--;
2141+
2142+
res = executeItem(cxt, &cache->args[0], jb, &items);
2143+
2144+
if (jperIsError(res))
2145+
return res;
2146+
2147+
JsonValueListInitIterator(&items, &iter);
2148+
2149+
while ((jsi = JsonValueListNext(&items, &iter)))
2150+
{
2151+
fcxt.item = jsi;
2152+
2153+
res = (JsonPathExecResult)
2154+
DatumGetPointer(FunctionCall2(&cache->finfo,
2155+
PointerGetDatum(&fcxt),
2156+
PointerGetDatum(NULL)));
2157+
if (jperIsError(res))
2158+
return res;
2159+
}
2160+
}
2161+
else
2162+
{
2163+
fcxt.item = NULL;
2164+
2165+
res = (JsonPathExecResult)
2166+
DatumGetPointer(FunctionCall2(&cache->finfo,
2167+
PointerGetDatum(&fcxt),
2168+
PointerGetDatum(NULL)));
2169+
if (jperIsError(res))
2170+
return res;
2171+
}
2172+
2173+
if (!jspHasNext(jsp))
2174+
return res;
2175+
2176+
JsonValueListInitIterator(&tmpres, &tmpiter);
2177+
2178+
while ((jsi = JsonValueListNext(&tmpres, &tmpiter)))
2179+
{
2180+
res = executeNextItem(cxt, jsp, NULL, jsi, result, false /*, needBool FIXME */);
2181+
2182+
if (jperIsError(res))
2183+
return res;
2184+
}
2185+
2186+
return res;
2187+
}
2188+
20552189
/*
20562190
* Same as executeItemOptUnwrapResult(), but with error suppression.
20572191
*/

0 commit comments

Comments
 (0)