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

Commit a5133c2

Browse files
author
Nikita Glukhov
committed
Add jsonpath .reduce() and .fold() item methods
1 parent 45f3910 commit a5133c2

File tree

9 files changed

+333
-12
lines changed

9 files changed

+333
-12
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
3030
/* position from begining of jsonpath data */
3131
int32 pos = buf->len - JSONPATH_HDRSZ;
3232
int32 chld, next;
33+
bool allowCurrentInArg = false;
3334

3435
check_stack_depth();
3536

@@ -62,6 +63,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
6263
appendBinaryStringInfo(buf, (char*)&item->value.boolean,
6364
sizeof(item->value.boolean));
6465
break;
66+
case jpiFold:
67+
case jpiFoldl:
68+
case jpiFoldr:
69+
allowCurrentInArg = true;
70+
/* fall through */
6571
case jpiAnd:
6672
case jpiOr:
6773
case jpiEqual:
@@ -90,11 +96,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
9096
appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right));
9197

9298
chld = flattenJsonPathParseItem(buf, item->value.args.left,
93-
allowCurrent,
99+
allowCurrent ||
100+
allowCurrentInArg,
94101
insideArraySubscript);
95102
*(int32*)(buf->data + left) = chld;
96103
chld = flattenJsonPathParseItem(buf, item->value.args.right,
97-
allowCurrent,
104+
allowCurrent ||
105+
allowCurrentInArg,
98106
insideArraySubscript);
99107
*(int32*)(buf->data + right) = chld;
100108
}
@@ -123,13 +131,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
123131
}
124132
break;
125133
case jpiFilter:
134+
case jpiMap:
135+
case jpiReduce:
136+
allowCurrentInArg = true;
137+
/* fall through */
126138
case jpiIsUnknown:
127139
case jpiNot:
128140
case jpiPlus:
129141
case jpiMinus:
130142
case jpiExists:
131143
case jpiDatetime:
132-
case jpiMap:
133144
case jpiArray:
134145
{
135146
int32 arg = item->value.arg ? buf->len : 0;
@@ -140,9 +151,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
140151
break;
141152

142153
chld = flattenJsonPathParseItem(buf, item->value.arg,
143-
item->type == jpiFilter ||
144-
item->type == jpiMap ||
145-
allowCurrent,
154+
allowCurrent ||
155+
allowCurrentInArg,
146156
insideArraySubscript);
147157
*(int32*)(buf->data + arg) = chld;
148158
}
@@ -659,6 +669,28 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
659669

660670
appendStringInfoChar(buf, '}');
661671
break;
672+
case jpiReduce:
673+
appendBinaryStringInfo(buf, ".reduce(", 8);
674+
jspGetArg(v, &elem);
675+
printJsonPathItem(buf, &elem, false, false);
676+
appendStringInfoChar(buf, ')');
677+
break;
678+
case jpiFold:
679+
case jpiFoldl:
680+
case jpiFoldr:
681+
if (v->type == jpiFold)
682+
appendBinaryStringInfo(buf, ".fold(", 6);
683+
else if (v->type == jpiFoldl)
684+
appendBinaryStringInfo(buf, ".foldl(", 7);
685+
else
686+
appendBinaryStringInfo(buf, ".foldr(", 7);
687+
jspGetLeftArg(v, &elem);
688+
printJsonPathItem(buf, &elem, false, false);
689+
appendBinaryStringInfo(buf, ", ", 2);
690+
jspGetRightArg(v, &elem);
691+
printJsonPathItem(buf, &elem, false, false);
692+
appendStringInfoChar(buf, ')');
693+
break;
662694
default:
663695
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
664696
}
@@ -776,6 +808,9 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
776808
case jpiLessOrEqual:
777809
case jpiGreaterOrEqual:
778810
case jpiStartsWith:
811+
case jpiFold:
812+
case jpiFoldl:
813+
case jpiFoldr:
779814
read_int32(v->content.args.left, base, pos);
780815
read_int32(v->content.args.right, base, pos);
781816
break;
@@ -794,6 +829,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
794829
case jpiDatetime:
795830
case jpiMap:
796831
case jpiArray:
832+
case jpiReduce:
797833
read_int32(v->content.arg, base, pos);
798834
break;
799835
case jpiIndexArray:
@@ -832,7 +868,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
832868
v->type == jpiMinus ||
833869
v->type == jpiDatetime ||
834870
v->type == jpiMap ||
835-
v->type == jpiArray
871+
v->type == jpiArray ||
872+
v->type == jpiReduce
836873
);
837874

838875
jspInitByBuffer(a, v->base, v->content.arg);
@@ -888,7 +925,11 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
888925
v->type == jpiMap ||
889926
v->type == jpiSequence ||
890927
v->type == jpiArray ||
891-
v->type == jpiObject
928+
v->type == jpiObject ||
929+
v->type == jpiReduce ||
930+
v->type == jpiFold ||
931+
v->type == jpiFoldl ||
932+
v->type == jpiFoldr
892933
);
893934

894935
if (a)
@@ -916,7 +957,10 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
916957
v->type == jpiMul ||
917958
v->type == jpiDiv ||
918959
v->type == jpiMod ||
919-
v->type == jpiStartsWith
960+
v->type == jpiStartsWith ||
961+
v->type == jpiFold ||
962+
v->type == jpiFoldl ||
963+
v->type == jpiFoldr
920964
);
921965

922966
jspInitByBuffer(a, v->base, v->content.args.left);
@@ -939,7 +983,10 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
939983
v->type == jpiMul ||
940984
v->type == jpiDiv ||
941985
v->type == jpiMod ||
942-
v->type == jpiStartsWith
986+
v->type == jpiStartsWith ||
987+
v->type == jpiFold ||
988+
v->type == jpiFoldl ||
989+
v->type == jpiFoldr
943990
);
944991

945992
jspInitByBuffer(a, v->base, v->content.args.right);

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt
5656

5757
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
5858

59+
static inline JsonbValue *wrapItem(JsonbValue *jbv);
60+
61+
static Datum returnDATUM(void *arg, bool *isNull);
5962

6063
static inline void
6164
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
@@ -286,6 +289,9 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
286289
JsonbInitBinary(value, jb);
287290
}
288291
break;
292+
case (Oid) -1: /* JsonbValue */
293+
*value = *(JsonbValue *) DatumGetPointer(computedValue);
294+
break;
289295
default:
290296
ereport(ERROR,
291297
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -2263,6 +2269,160 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
22632269
res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
22642270
}
22652271
break;
2272+
case jpiReduce:
2273+
case jpiFold:
2274+
case jpiFoldl:
2275+
case jpiFoldr:
2276+
if (JsonbType(jb) != jbvArray)
2277+
{
2278+
if (cxt->lax)
2279+
{
2280+
if (jsp->type == jpiReduce)
2281+
res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
2282+
else
2283+
res = recursiveExecute(cxt, jsp, wrapItem(jb), found);
2284+
}
2285+
else
2286+
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
2287+
}
2288+
else
2289+
{
2290+
JsonbValue jbv;
2291+
JsonbValue *result = NULL;
2292+
int size = JsonbArraySize(jb);
2293+
int i;
2294+
bool foldr = jsp->type == jpiFoldr;
2295+
2296+
if (jsp->type == jpiReduce)
2297+
jspGetArg(jsp, &elem);
2298+
else
2299+
{
2300+
JsonValueList reslist = { 0 };
2301+
2302+
jspGetRightArg(jsp, &elem);
2303+
res = recursiveExecute(cxt, &elem, jb, &reslist);
2304+
2305+
if (jperIsError(res))
2306+
return res;
2307+
2308+
if (JsonValueListLength(&reslist) != 1)
2309+
return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
2310+
2311+
result = JsonValueListHead(&reslist);
2312+
2313+
jspGetLeftArg(jsp, &elem);
2314+
}
2315+
2316+
if (jsp->type == jpiReduce && size == 1)
2317+
{
2318+
if (jb->type == jbvBinary)
2319+
{
2320+
result = getIthJsonbValueFromContainer(jb->val.binary.data, 0);
2321+
if (!result)
2322+
{
2323+
res = jperNotFound;
2324+
break;
2325+
}
2326+
}
2327+
else
2328+
{
2329+
Assert(jb->type == jbvArray);
2330+
result = &jb->val.array.elems[0];
2331+
}
2332+
}
2333+
else if (size)
2334+
{
2335+
JsonPathVariable *v1 = palloc(sizeof(*v1));
2336+
JsonPathVariable *v2 = palloc(sizeof(*v2));
2337+
JsonbIterator *it = NULL;
2338+
JsonbIteratorToken tok;
2339+
JsonbValue *element;
2340+
2341+
if (jb->type == jbvBinary)
2342+
{
2343+
if (foldr)
2344+
{
2345+
/* unpack array for reverse iteration */
2346+
JsonbParseState *ps = NULL;
2347+
2348+
jb = pushJsonbValue(&ps, WJB_ELEM, jb);
2349+
}
2350+
else
2351+
{
2352+
element = &jbv;
2353+
it = JsonbIteratorInit(jb->val.binary.data);
2354+
tok = JsonbIteratorNext(&it, &jbv, false);
2355+
if (tok != WJB_BEGIN_ARRAY)
2356+
elog(ERROR, "unexpected jsonb token at the array start");
2357+
}
2358+
}
2359+
2360+
v1->cb = returnDATUM;
2361+
v2->cb = returnDATUM;
2362+
v1->varName = cstring_to_text_with_len("1", 1);
2363+
v2->varName = cstring_to_text_with_len("2", 1);
2364+
v1->typid = (Oid) -1; /* raw JsonbValue */
2365+
v2->typid = (Oid) -1;
2366+
v1->typmod = -1;
2367+
v2->typmod = -1;
2368+
2369+
cxt->vars = lcons(v1, lcons(v2, cxt->vars));
2370+
2371+
if (foldr)
2372+
{
2373+
/* swap $1 and $2 for foldr() */
2374+
JsonPathVariable *tmp = v1;
2375+
2376+
v1 = v2;
2377+
v2 = tmp;
2378+
}
2379+
2380+
for (i = 0; i < size; i++)
2381+
{
2382+
JsonValueList reslist = { 0 };
2383+
2384+
if (it)
2385+
{
2386+
tok = JsonbIteratorNext(&it, element, true);
2387+
if (tok != WJB_ELEM)
2388+
break;
2389+
}
2390+
else if (foldr)
2391+
element = &jb->val.array.elems[size - i - 1];
2392+
else
2393+
element = &jb->val.array.elems[i];
2394+
2395+
if (!i && jsp->type == jpiReduce)
2396+
{
2397+
result = copyJsonbValue(element);
2398+
continue;
2399+
}
2400+
2401+
v1->cb_arg = result;
2402+
v2->cb_arg = element;
2403+
2404+
res = recursiveExecute(cxt, &elem, jb, &reslist);
2405+
2406+
if (jperIsError(res))
2407+
return res;
2408+
2409+
if (JsonValueListLength(&reslist) != 1)
2410+
return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
2411+
2412+
result = JsonValueListHead(&reslist);
2413+
}
2414+
2415+
cxt->vars = list_delete_first(list_delete_first(cxt->vars));
2416+
}
2417+
else if (jsp->type == jpiReduce)
2418+
{
2419+
res = jperNotFound;
2420+
break;
2421+
}
2422+
2423+
res = recursiveExecuteNext(cxt, jsp, NULL, result, found, false);
2424+
}
2425+
break;
22662426
default:
22672427
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
22682428
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ makeItemObject(List *fields)
286286
%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
287287
%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
288288
%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
289-
%token <str> KEYVALUE_P MAP_P
289+
%token <str> KEYVALUE_P MAP_P REDUCE_P FOLD_P FOLDL_P FOLDR_P
290290

291291
%type <result> result
292292

@@ -299,7 +299,7 @@ makeItemObject(List *fields)
299299

300300
%type <indexs> index_list
301301

302-
%type <optype> comp_op method
302+
%type <optype> comp_op method fold
303303

304304
%type <boolean> mode
305305

@@ -476,9 +476,19 @@ accessor_op:
476476
{ $$ = makeItemUnary(jpiDatetime, $4); }
477477
| '.' MAP_P '(' expr_or_predicate ')'
478478
{ $$ = makeItemUnary(jpiMap, $4); }
479+
| '.' REDUCE_P '(' expr_or_predicate ')'
480+
{ $$ = makeItemUnary(jpiReduce, $4); }
481+
| '.' fold '(' expr_or_predicate ',' expr_or_predicate ')'
482+
{ $$ = makeItemBinary($2, $4, $6); }
479483
| '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
480484
;
481485

486+
fold:
487+
FOLD_P { $$ = jpiFold; }
488+
| FOLDL_P { $$ = jpiFoldl; }
489+
| FOLDR_P { $$ = jpiFoldr; }
490+
;
491+
482492
opt_datetime_template:
483493
STRING_P { $$ = makeItemString(&$1); }
484494
| /* EMPTY */ { $$ = NULL; }
@@ -515,6 +525,10 @@ key_name:
515525
| LIKE_REGEX_P
516526
| FLAG_P
517527
| MAP_P
528+
| REDUCE_P
529+
| FOLD_P
530+
| FOLDL_P
531+
| FOLDR_P
518532
;
519533

520534
method:

0 commit comments

Comments
 (0)