Skip to content

Commit 9fb0b79

Browse files
author
Nikita Glukhov
committed
Add JSON_TRANSFORM()
1 parent 740222f commit 9fb0b79

File tree

22 files changed

+2239
-67
lines changed

22 files changed

+2239
-67
lines changed

src/backend/catalog/system_functions.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,15 @@ LANGUAGE INTERNAL
579579
STRICT STABLE PARALLEL SAFE
580580
AS 'jsonb_path_query_first_tz';
581581

582+
CREATE OR REPLACE FUNCTION
583+
jsonb_path_set(target jsonb, path jsonpath, newval jsonb,
584+
vars jsonb DEFAULT '{}',
585+
silent boolean DEFAULT false)
586+
RETURNS jsonb
587+
LANGUAGE INTERNAL
588+
IMMUTABLE PARALLEL SAFE
589+
AS 'jsonb_path_set';
590+
582591
-- default normalization form is NFC, per SQL standard
583592
CREATE OR REPLACE FUNCTION
584593
"normalize"(text, text DEFAULT 'NFC')

src/backend/executor/execExpr.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,101 @@ ExecInitExprRec(Expr *node, ExprState *state,
26682668
break;
26692669
}
26702670

2671+
case T_JsonTransformExpr:
2672+
{
2673+
JsonTransformExpr *jexpr = castNode(JsonTransformExpr, node);
2674+
ListCell *argexprlc;
2675+
ListCell *argnamelc;
2676+
ListCell *oplc;
2677+
int i = 0;
2678+
2679+
scratch.opcode = EEOP_JSONTRANSFORM;
2680+
scratch.d.jsontransform.jsexpr = jexpr;
2681+
2682+
scratch.d.jsontransform.formatted_expr =
2683+
palloc(sizeof(*scratch.d.jsontransform.formatted_expr));
2684+
2685+
ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
2686+
&scratch.d.jsontransform.formatted_expr->value,
2687+
&scratch.d.jsontransform.formatted_expr->isnull);
2688+
2689+
scratch.d.jsontransform.ops =
2690+
palloc(sizeof(*scratch.d.jsontransform.ops) * list_length(jexpr->ops));
2691+
2692+
foreach(oplc, jexpr->ops)
2693+
{
2694+
JsonTransformOp *op = lfirst_node(JsonTransformOp, oplc);
2695+
2696+
if (op->expr)
2697+
{
2698+
ExecInitExprRec((Expr *) op->expr, state,
2699+
&scratch.d.jsontransform.ops[i].expr.value,
2700+
&scratch.d.jsontransform.ops[i].expr.isnull);
2701+
2702+
scratch.d.jsontransform.ops[i].expr_typid = exprType(op->expr);
2703+
scratch.d.jsontransform.ops[i].expr_typmod = exprTypmod(op->expr);
2704+
}
2705+
else
2706+
{
2707+
memset(&scratch.d.jsontransform.ops[i], 0,
2708+
sizeof(scratch.d.jsontransform.ops[i]));
2709+
scratch.d.jsontransform.ops[i].expr.isnull = true;
2710+
}
2711+
2712+
ExecInitExprRec((Expr *) op->pathspec, state,
2713+
&scratch.d.jsontransform.ops[i].pathspec.value,
2714+
&scratch.d.jsontransform.ops[i].pathspec.isnull);
2715+
2716+
i++;
2717+
}
2718+
2719+
scratch.d.jsontransform.res_expr =
2720+
palloc(sizeof(*scratch.d.jsontransform.res_expr));
2721+
2722+
scratch.d.jsontransform.result_expr = jexpr->result_coercion
2723+
? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
2724+
state->parent,
2725+
&scratch.d.jsontransform.res_expr->value,
2726+
&scratch.d.jsontransform.res_expr->isnull)
2727+
: NULL;
2728+
2729+
if (jexpr->result_coercion && jexpr->result_coercion->via_io)
2730+
{
2731+
Oid typinput;
2732+
2733+
/* lookup the result type's input function */
2734+
getTypeInputInfo(jexpr->returning->typid, &typinput,
2735+
&scratch.d.jsontransform.input.typioparam);
2736+
fmgr_info(typinput, &scratch.d.jsontransform.input.func);
2737+
}
2738+
2739+
scratch.d.jsontransform.args = NIL;
2740+
2741+
forboth(argexprlc, jexpr->passing_values,
2742+
argnamelc, jexpr->passing_names)
2743+
{
2744+
Expr *argexpr = (Expr *) lfirst(argexprlc);
2745+
String *argname = lfirst_node(String, argnamelc);
2746+
JsonPathVariableEvalContext *var = palloc(sizeof(*var));
2747+
2748+
var->name = pstrdup(argname->sval);
2749+
var->typid = exprType((Node *) argexpr);
2750+
var->typmod = exprTypmod((Node *) argexpr);
2751+
var->estate = ExecInitExpr(argexpr, state->parent);
2752+
var->econtext = NULL;
2753+
var->mcxt = NULL;
2754+
var->evaluated = false;
2755+
var->value = (Datum) 0;
2756+
var->isnull = true;
2757+
2758+
scratch.d.jsontransform.args =
2759+
lappend(scratch.d.jsontransform.args, var);
2760+
}
2761+
2762+
ExprEvalPushStep(state, &scratch);
2763+
break;
2764+
}
2765+
26712766
default:
26722767
elog(ERROR, "unrecognized node type: %d",
26732768
(int) nodeTag(node));

src/backend/executor/execExprInterp.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
491491
&&CASE_EEOP_JSON_CONSTRUCTOR,
492492
&&CASE_EEOP_IS_JSON,
493493
&&CASE_EEOP_JSONEXPR,
494+
&&CASE_EEOP_JSONTRANSFORM,
494495
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
495496
&&CASE_EEOP_AGG_DESERIALIZE,
496497
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@@ -1824,6 +1825,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
18241825
EEO_NEXT();
18251826
}
18261827

1828+
EEO_CASE(EEOP_JSONTRANSFORM)
1829+
{
1830+
/* too complex for an inline implementation */
1831+
ExecEvalJsonTransform(state, op, econtext);
1832+
EEO_NEXT();
1833+
}
1834+
18271835
EEO_CASE(EEOP_LAST)
18281836
{
18291837
/* unreachable */
@@ -5143,3 +5151,111 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
51435151

51445152
*op->resvalue = res;
51455153
}
5154+
5155+
/*
5156+
* Evaluate a coercion of a JSON item to the target type.
5157+
*/
5158+
static Datum
5159+
ExecEvalJsonTransformCoercion(ExprEvalStep *op, ExprContext *econtext,
5160+
Datum res, bool *isNull)
5161+
{
5162+
JsonTransformExpr *jexpr = op->d.jsontransform.jsexpr;
5163+
JsonCoercion *coercion = jexpr->result_coercion;
5164+
5165+
if (coercion)
5166+
{
5167+
if (coercion->via_io)
5168+
{
5169+
/* strip quotes and call typinput function */
5170+
char *str = *isNull ? NULL : JsonbUnquote(DatumGetJsonbP(res));
5171+
5172+
return InputFunctionCall(&op->d.jsontransform.input.func, str,
5173+
op->d.jsontransform.input.typioparam,
5174+
jexpr->returning->typmod);
5175+
}
5176+
5177+
if (coercion->via_populate)
5178+
return json_populate_type(res, JSONBOID,
5179+
jexpr->returning->typid,
5180+
jexpr->returning->typmod,
5181+
&op->d.jsontransform.cache,
5182+
econtext->ecxt_per_query_memory,
5183+
isNull);
5184+
}
5185+
5186+
if (op->d.jsontransform.result_expr)
5187+
{
5188+
op->d.jsontransform.res_expr->value = res;
5189+
op->d.jsontransform.res_expr->isnull = *isNull;
5190+
5191+
res = ExecEvalExpr(op->d.jsontransform.result_expr, econtext, isNull);
5192+
}
5193+
5194+
return res;
5195+
}
5196+
5197+
void
5198+
ExecEvalJsonTransform(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
5199+
{
5200+
JsonTransformExpr *jexpr = op->d.jsontransform.jsexpr;
5201+
Datum res = (Datum) 0;
5202+
ListCell *lc;
5203+
int i = 0;
5204+
5205+
*op->resnull = true; /* until we get a result */
5206+
*op->resvalue = (Datum) 0;
5207+
5208+
if (op->d.jsontransform.formatted_expr->isnull)
5209+
goto check_null;
5210+
5211+
res = op->d.jsontransform.formatted_expr->value;
5212+
5213+
/* reset JSON path variable contexts */
5214+
foreach(lc, op->d.jsontransform.args)
5215+
{
5216+
JsonPathVariableEvalContext *var = lfirst(lc);
5217+
5218+
var->econtext = econtext;
5219+
var->evaluated = false;
5220+
}
5221+
5222+
foreach(lc, jexpr->ops)
5223+
{
5224+
JsonPath *path;
5225+
JsonTransformOp *oper = lfirst_node(JsonTransformOp, lc);
5226+
5227+
if (op->d.jsontransform.ops[i].pathspec.isnull)
5228+
goto check_null;
5229+
5230+
if (oper->on_null == JSTB_ERROR &&
5231+
op->d.jsontransform.ops[i].expr.isnull)
5232+
ereport(ERROR,
5233+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5234+
errmsg("JSON_TRANSFORM called with NULL input")));
5235+
5236+
path = DatumGetJsonPathP(op->d.jsontransform.ops[i].pathspec.value);
5237+
res = JsonPathTransform(res, path,
5238+
op->d.jsontransform.ops[i].expr.value,
5239+
op->d.jsontransform.ops[i].expr.isnull,
5240+
op->d.jsontransform.ops[i].expr_typid,
5241+
op->d.jsontransform.ops[i].expr_typmod,
5242+
op->d.jsontransform.args,
5243+
oper->op_type, oper->on_existing,
5244+
oper->on_missing, oper->on_null);
5245+
5246+
*op->resnull = res == (Datum) 0;
5247+
5248+
if (*op->resnull)
5249+
goto check_null;
5250+
5251+
i++;
5252+
}
5253+
5254+
*op->resvalue = ExecEvalJsonTransformCoercion(op, econtext, res, op->resnull);
5255+
return;
5256+
5257+
check_null:
5258+
/* execute domain checks for NULLs */
5259+
(void) ExecEvalJsonTransformCoercion(op, econtext, res, op->resnull);
5260+
Assert(*op->resnull);
5261+
}

src/backend/nodes/makefuncs.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,29 @@ makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
941941

942942
return (Node *) n;
943943
}
944+
945+
/*
946+
* makeJsonTransformOp -
947+
* creates a JsonTransformOp node
948+
*/
949+
Node *
950+
makeJsonTransformOp(JsonTransformOpType op_type,
951+
Node *pathspec, Node *expr,
952+
JsonTransformBehavior on_existing,
953+
JsonTransformBehavior on_missing,
954+
JsonTransformBehavior on_null,
955+
int location)
956+
{
957+
JsonTransformOp *n = makeNode(JsonTransformOp);
958+
959+
n->pathspec = pathspec;
960+
n->expr = expr;
961+
n->op_type = op_type;
962+
n->on_existing = on_existing;
963+
n->on_missing = on_missing;
964+
n->on_null = on_null;
965+
n->location = location;
966+
967+
return (Node *) n;
968+
}
969+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy