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

Commit 6470455

Browse files
author
Nikita Glukhov
committed
Optimize and fix JSON item coercion to target type in JSON_VALUE
1 parent 5769f6b commit 6470455

File tree

9 files changed

+296
-40
lines changed

9 files changed

+296
-40
lines changed

src/backend/executor/execExpr.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,9 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
20942094
lappend(scratch.d.jsonexpr.args, var);
20952095
}
20962096

2097+
memset(&scratch.d.jsonexpr.scalar, 0,
2098+
sizeof(scratch.d.jsonexpr.scalar));
2099+
20972100
ExprEvalPushStep(state, &scratch);
20982101
}
20992102
break;

src/backend/executor/execExprInterp.c

Lines changed: 165 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@
6565
#include "executor/nodeSubplan.h"
6666
#include "funcapi.h"
6767
#include "miscadmin.h"
68+
#include "nodes/makefuncs.h"
6869
#include "nodes/nodeFuncs.h"
6970
#include "parser/parsetree.h"
71+
#include "parser/parse_expr.h"
7072
#include "pgstat.h"
7173
#include "utils/builtins.h"
7274
#include "utils/date.h"
@@ -3711,6 +3713,132 @@ EvalJsonPathVar(void *cxt, bool *isnull)
37113713
return ecxt->value;
37123714
}
37133715

3716+
/*
3717+
* Prepare SQL/JSON item coercion to the output type. Returned a datum of the
3718+
* corresponding SQL type and a pointer to the coercion state.
3719+
*/
3720+
Datum
3721+
ExecPrepareJsonItemCoercion(JsonbValue *item, JsonReturning *returning,
3722+
struct JsonScalarCoercions *coercions,
3723+
MemoryContext mcxt,
3724+
struct JsonScalarCoercionExprState **pcestate)
3725+
{
3726+
struct JsonScalarCoercionExprState *cestate;
3727+
Datum res;
3728+
Oid typid;
3729+
JsonbValue jbvbuf;
3730+
3731+
if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data))
3732+
item = JsonbExtractScalar(item->val.binary.data, &jbvbuf);
3733+
3734+
/* get coercion state reference and datum of the corresponding SQL type */
3735+
switch (item->type)
3736+
{
3737+
case jbvNull:
3738+
cestate = &coercions->null;
3739+
typid = UNKNOWNOID;
3740+
res = (Datum) 0;
3741+
break;
3742+
3743+
case jbvString:
3744+
cestate = &coercions->string;
3745+
typid = TEXTOID;
3746+
res = PointerGetDatum(
3747+
cstring_to_text_with_len(item->val.string.val,
3748+
item->val.string.len));
3749+
break;
3750+
3751+
case jbvNumeric:
3752+
cestate = &coercions->numeric;
3753+
typid = NUMERICOID;
3754+
res = NumericGetDatum(item->val.numeric);
3755+
break;
3756+
3757+
case jbvBool:
3758+
cestate = &coercions->boolean;
3759+
typid = BOOLOID;
3760+
res = BoolGetDatum(item->val.boolean);
3761+
break;
3762+
3763+
case jbvDatetime:
3764+
res = item->val.datetime.value;
3765+
typid = item->val.datetime.typid;
3766+
switch (item->val.datetime.typid)
3767+
{
3768+
case DATEOID:
3769+
cestate = &coercions->date;
3770+
break;
3771+
case TIMEOID:
3772+
cestate = &coercions->time;
3773+
break;
3774+
case TIMETZOID:
3775+
cestate = &coercions->timetz;
3776+
break;
3777+
case TIMESTAMPOID:
3778+
cestate = &coercions->timestamp;
3779+
break;
3780+
case TIMESTAMPTZOID:
3781+
cestate = &coercions->timestamptz;
3782+
break;
3783+
default:
3784+
elog(ERROR, "unexpected jsonb datetime type oid %d",
3785+
item->val.datetime.typid);
3786+
return (Datum) 0;
3787+
}
3788+
break;
3789+
3790+
case jbvArray:
3791+
case jbvObject:
3792+
case jbvBinary:
3793+
cestate = &coercions->composite;
3794+
res = JsonbPGetDatum(JsonbValueToJsonb(item));
3795+
typid = JSONBOID;
3796+
break;
3797+
3798+
default:
3799+
elog(ERROR, "unexpected jsonb value type %d", item->type);
3800+
return (Datum) 0;
3801+
}
3802+
3803+
/* on-demand initialization of coercion state */
3804+
if (!cestate->initialized)
3805+
{
3806+
MemoryContext oldCxt = MemoryContextSwitchTo(mcxt);
3807+
Node *expr;
3808+
3809+
if (item->type == jbvNull)
3810+
{
3811+
expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
3812+
}
3813+
else
3814+
{
3815+
CaseTestExpr *placeholder = makeNode(CaseTestExpr);
3816+
3817+
placeholder->typeId = typid;
3818+
placeholder->typeMod = -1;
3819+
placeholder->collation = InvalidOid;
3820+
3821+
expr = (Node *) placeholder;
3822+
}
3823+
3824+
cestate->result_expr =
3825+
coerceJsonExpr(NULL, expr, returning,
3826+
&cestate->coerce_via_io,
3827+
&cestate->coerce_via_populate);
3828+
3829+
cestate->result_expr_state =
3830+
ExecInitExpr((Expr *) cestate->result_expr, NULL);
3831+
3832+
MemoryContextSwitchTo(oldCxt);
3833+
3834+
cestate->initialized = true;
3835+
}
3836+
3837+
*pcestate = cestate;
3838+
3839+
return res;
3840+
}
3841+
37143842
/* ----------------------------------------------------------------
37153843
* ExecEvalJson
37163844
* ----------------------------------------------------------------
@@ -3779,8 +3907,42 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
37793907
break;
37803908

37813909
case IS_JSON_VALUE:
3782-
res = JsonbPathValue(item, path, &empty, op->d.jsonexpr.args);
3783-
*op->resnull = !DatumGetPointer(res);
3910+
{
3911+
JsonbValue *jbv = JsonbPathValue(item, path, &empty,
3912+
op->d.jsonexpr.args);
3913+
struct JsonScalarCoercionExprState *cestate;
3914+
3915+
if (!jbv)
3916+
break;
3917+
3918+
*op->resnull = false;
3919+
3920+
res = ExecPrepareJsonItemCoercion(jbv,
3921+
&op->d.jsonexpr.jsexpr->returning,
3922+
&op->d.jsonexpr.scalar,
3923+
econtext->ecxt_per_query_memory,
3924+
&cestate);
3925+
3926+
/* coerce item datum to the output type */
3927+
if (cestate->coerce_via_io ||
3928+
cestate->coerce_via_populate || /* ignored for scalars jsons */
3929+
jexpr->returning.typid == JSONOID ||
3930+
jexpr->returning.typid == JSONBOID)
3931+
{
3932+
/* use coercion from json[b] to the output type */
3933+
res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
3934+
res = ExecEvalJsonExprCoercion(op, econtext,
3935+
res, op->resnull);
3936+
}
3937+
else if (cestate->result_expr_state)
3938+
{
3939+
res = ExecEvalExprPassingCaseValue(cestate->result_expr_state,
3940+
econtext,
3941+
op->resnull,
3942+
res, false);
3943+
}
3944+
/* else no coercion */
3945+
}
37843946
break;
37853947

37863948
case IS_JSON_EXISTS:
@@ -3809,7 +3971,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
38093971
}
38103972

38113973
if (jexpr->op != IS_JSON_EXISTS &&
3812-
(!empty ||
3974+
(!empty ? jexpr->op != IS_JSON_VALUE :
38133975
/* result is already coerced in DEFAULT behavior case */
38143976
jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT))
38153977
res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull);

src/backend/parser/parse_expr.c

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,6 +4466,35 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
44664466
ret->typmod = -1;
44674467
}
44684468

4469+
/*
4470+
* Try to coerce expression to the output type or
4471+
* use json_populate_type() for composite, array and domain types or
4472+
* use coercion via I/O.
4473+
*/
4474+
Node *
4475+
coerceJsonExpr(ParseState *pstate, Node *expr, JsonReturning *returning,
4476+
bool *coerce_via_io, bool *coerce_via_populate)
4477+
{
4478+
Node *res = coerceJsonFuncExpr(pstate, expr, returning, false);
4479+
char typtype;
4480+
4481+
if (res)
4482+
return res == expr ? NULL : res;
4483+
4484+
typtype = get_typtype(returning->typid);
4485+
4486+
if (coerce_via_populate &&
4487+
(returning->typid == RECORDOID ||
4488+
typtype == TYPTYPE_COMPOSITE ||
4489+
typtype == TYPTYPE_DOMAIN ||
4490+
type_is_array(returning->typid)))
4491+
*coerce_via_populate = true;
4492+
else
4493+
*coerce_via_io = true;
4494+
4495+
return NULL;
4496+
}
4497+
44694498
/*
44704499
* Transform a JSON output clause of JSON_VALUE, JSON_QUERY, JSON_EXISTS.
44714500
*/
@@ -4479,13 +4508,22 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
44794508
transformJsonOutput(pstate, func->output, false,
44804509
&jsexpr->returning);
44814510

4511+
/* JSON_VALUE returns text by default */
4512+
if (func->op == IS_JSON_VALUE && !OidIsValid(jsexpr->returning.typid))
4513+
{
4514+
jsexpr->returning.typid = TEXTOID;
4515+
jsexpr->returning.typmod = -1;
4516+
}
4517+
44824518
if (OidIsValid(jsexpr->returning.typid))
44834519
{
44844520
JsonReturning ret;
44854521

4486-
if (func->op == IS_JSON_VALUE)
4522+
if (func->op == IS_JSON_VALUE &&
4523+
jsexpr->returning.typid != JSONOID &&
4524+
jsexpr->returning.typid != JSONBOID)
44874525
{
4488-
/* Forced coercion via I/O for JSON_VALUE */
4526+
/* Forced coercion via I/O for JSON_VALUE for non-JSON types */
44894527
jsexpr->result_expr = NULL;
44904528
jsexpr->coerce_via_io = true;
44914529
return;
@@ -4501,15 +4539,10 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
45014539
Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
45024540
Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
45034541

4504-
jsexpr->result_expr = coerceJsonFuncExpr(pstate,
4505-
placeholder,
4506-
&jsexpr->returning,
4507-
false);
4508-
4509-
if (!jsexpr->result_expr)
4510-
jsexpr->coerce_via_io = true;
4511-
else if (jsexpr->result_expr == placeholder)
4512-
jsexpr->result_expr = NULL;
4542+
jsexpr->result_expr = coerceJsonExpr(pstate, placeholder,
4543+
&jsexpr->returning,
4544+
&jsexpr->coerce_via_io,
4545+
NULL);
45134546
}
45144547
}
45154548
else

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3198,7 +3198,7 @@ JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
31983198
return PointerGetDatum(NULL);
31993199
}
32003200

3201-
Datum
3201+
JsonbValue *
32023202
JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
32033203
{
32043204
JsonbValue *res;
@@ -3214,7 +3214,7 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
32143214
*empty = !count;
32153215

32163216
if (*empty)
3217-
return PointerGetDatum(NULL);
3217+
return NULL;
32183218

32193219
if (count > 1)
32203220
ereport(ERROR,
@@ -3233,7 +3233,7 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
32333233
errmsg("SQL/JSON scalar required")));
32343234

32353235
if (res->type == jbvNull)
3236-
return PointerGetDatum(NULL);
3236+
return NULL;
32373237

3238-
return JsonbPGetDatum(JsonbValueToJsonb(res));
3238+
return res;
32393239
}

src/include/executor/execExpr.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
/* forward reference to avoid circularity */
2020
struct ArrayRefState;
21+
struct JsonbValue;
2122

2223
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
2324
/* expression's interpreter has been initialized */
@@ -584,6 +585,28 @@ typedef struct ExprEvalStep
584585
ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
585586
ExprState *default_on_error; /* ON ERROR DEFAULT expression */
586587
List *args; /* passing arguments */
588+
589+
struct JsonScalarCoercions
590+
{
591+
struct JsonScalarCoercionExprState
592+
{
593+
Node *result_expr; /* coercion expression */
594+
ExprState *result_expr_state; /* coercion expression state */
595+
bool coerce_via_io;
596+
bool coerce_via_populate;
597+
bool initialized;
598+
} string,
599+
numeric,
600+
boolean,
601+
date,
602+
time,
603+
timetz,
604+
timestamp,
605+
timestamptz,
606+
composite,
607+
null;
608+
} scalar; /* states for coercion from SQL/JSON item
609+
* types directly to the output type */
587610
} jsonexpr;
588611

589612
} d;
@@ -674,5 +697,10 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
674697
ExprContext *econtext);
675698
extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
676699
ExprContext *econtext);
700+
extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
701+
JsonReturning *returning,
702+
struct JsonScalarCoercions *coercions,
703+
MemoryContext mcxt,
704+
struct JsonScalarCoercionExprState **pcestate);
677705

678706
#endif /* EXEC_EXPR_H */

src/include/parser/parse_expr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
2323

2424
extern const char *ParseExprKindName(ParseExprKind exprKind);
2525

26+
extern Node *coerceJsonExpr(ParseState *pstate, Node *expr,
27+
JsonReturning *returning,
28+
bool *coerce_via_io, bool *coerce_via_populate);
29+
2630
#endif /* PARSE_EXPR_H */

src/include/utils/jsonpath.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,10 @@ JsonPathExecResult executeJsonPath(JsonPath *path,
314314
JsonValueList *foundJson);
315315

316316
extern bool JsonbPathExists(Datum jb, JsonPath *path, List *vars);
317-
extern Datum JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars);
318317
extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper,
319318
bool *empty, List *vars);
319+
extern JsonbValue *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty,
320+
List *vars);
320321

321322
extern Datum EvalJsonPathVar(void *cxt, bool *isnull);
322323

0 commit comments

Comments
 (0)