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

Commit 5767be1

Browse files
author
Nikita Glukhov
committed
Optimize and fix JSON item coercion to target type in JSON_VALUE
1 parent ec155fc commit 5767be1

File tree

9 files changed

+297
-41
lines changed

9 files changed

+297
-41
lines changed

src/backend/executor/execExpr.c

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

2104+
memset(&scratch.d.jsonexpr.scalar, 0,
2105+
sizeof(scratch.d.jsonexpr.scalar));
2106+
21042107
ExprEvalPushStep(state, &scratch);
21052108
}
21062109
break;

src/backend/executor/execExprInterp.c

Lines changed: 165 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@
6363
#include "executor/nodeSubplan.h"
6464
#include "funcapi.h"
6565
#include "miscadmin.h"
66+
#include "nodes/makefuncs.h"
6667
#include "nodes/nodeFuncs.h"
6768
#include "parser/parsetree.h"
69+
#include "parser/parse_expr.h"
6870
#include "pgstat.h"
6971
#include "utils/builtins.h"
7072
#include "utils/date.h"
@@ -3760,6 +3762,132 @@ EvalJsonPathVar(void *cxt, bool *isnull)
37603762
return ecxt->value;
37613763
}
37623764

3765+
/*
3766+
* Prepare SQL/JSON item coercion to the output type. Returned a datum of the
3767+
* corresponding SQL type and a pointer to the coercion state.
3768+
*/
3769+
Datum
3770+
ExecPrepareJsonItemCoercion(JsonbValue *item, JsonReturning *returning,
3771+
struct JsonScalarCoercions *coercions,
3772+
MemoryContext mcxt,
3773+
struct JsonScalarCoercionExprState **pcestate)
3774+
{
3775+
struct JsonScalarCoercionExprState *cestate;
3776+
Datum res;
3777+
Oid typid;
3778+
JsonbValue jbvbuf;
3779+
3780+
if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data))
3781+
item = JsonbExtractScalar(item->val.binary.data, &jbvbuf);
3782+
3783+
/* get coercion state reference and datum of the corresponding SQL type */
3784+
switch (item->type)
3785+
{
3786+
case jbvNull:
3787+
cestate = &coercions->null;
3788+
typid = UNKNOWNOID;
3789+
res = (Datum) 0;
3790+
break;
3791+
3792+
case jbvString:
3793+
cestate = &coercions->string;
3794+
typid = TEXTOID;
3795+
res = PointerGetDatum(
3796+
cstring_to_text_with_len(item->val.string.val,
3797+
item->val.string.len));
3798+
break;
3799+
3800+
case jbvNumeric:
3801+
cestate = &coercions->numeric;
3802+
typid = NUMERICOID;
3803+
res = NumericGetDatum(item->val.numeric);
3804+
break;
3805+
3806+
case jbvBool:
3807+
cestate = &coercions->boolean;
3808+
typid = BOOLOID;
3809+
res = BoolGetDatum(item->val.boolean);
3810+
break;
3811+
3812+
case jbvDatetime:
3813+
res = item->val.datetime.value;
3814+
typid = item->val.datetime.typid;
3815+
switch (item->val.datetime.typid)
3816+
{
3817+
case DATEOID:
3818+
cestate = &coercions->date;
3819+
break;
3820+
case TIMEOID:
3821+
cestate = &coercions->time;
3822+
break;
3823+
case TIMETZOID:
3824+
cestate = &coercions->timetz;
3825+
break;
3826+
case TIMESTAMPOID:
3827+
cestate = &coercions->timestamp;
3828+
break;
3829+
case TIMESTAMPTZOID:
3830+
cestate = &coercions->timestamptz;
3831+
break;
3832+
default:
3833+
elog(ERROR, "unexpected jsonb datetime type oid %d",
3834+
item->val.datetime.typid);
3835+
return (Datum) 0;
3836+
}
3837+
break;
3838+
3839+
case jbvArray:
3840+
case jbvObject:
3841+
case jbvBinary:
3842+
cestate = &coercions->composite;
3843+
res = JsonbPGetDatum(JsonbValueToJsonb(item));
3844+
typid = JSONBOID;
3845+
break;
3846+
3847+
default:
3848+
elog(ERROR, "unexpected jsonb value type %d", item->type);
3849+
return (Datum) 0;
3850+
}
3851+
3852+
/* on-demand initialization of coercion state */
3853+
if (!cestate->initialized)
3854+
{
3855+
MemoryContext oldCxt = MemoryContextSwitchTo(mcxt);
3856+
Node *expr;
3857+
3858+
if (item->type == jbvNull)
3859+
{
3860+
expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid);
3861+
}
3862+
else
3863+
{
3864+
CaseTestExpr *placeholder = makeNode(CaseTestExpr);
3865+
3866+
placeholder->typeId = typid;
3867+
placeholder->typeMod = -1;
3868+
placeholder->collation = InvalidOid;
3869+
3870+
expr = (Node *) placeholder;
3871+
}
3872+
3873+
cestate->result_expr =
3874+
coerceJsonExpr(NULL, expr, returning,
3875+
&cestate->coerce_via_io,
3876+
&cestate->coerce_via_populate);
3877+
3878+
cestate->result_expr_state =
3879+
ExecInitExpr((Expr *) cestate->result_expr, NULL);
3880+
3881+
MemoryContextSwitchTo(oldCxt);
3882+
3883+
cestate->initialized = true;
3884+
}
3885+
3886+
*pcestate = cestate;
3887+
3888+
return res;
3889+
}
3890+
37633891
/* ----------------------------------------------------------------
37643892
* ExecEvalJson
37653893
* ----------------------------------------------------------------
@@ -3828,8 +3956,42 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
38283956
break;
38293957

38303958
case IS_JSON_VALUE:
3831-
res = JsonbPathValue(item, path, &empty, op->d.jsonexpr.args);
3832-
*op->resnull = !DatumGetPointer(res);
3959+
{
3960+
JsonbValue *jbv = JsonbPathValue(item, path, &empty,
3961+
op->d.jsonexpr.args);
3962+
struct JsonScalarCoercionExprState *cestate;
3963+
3964+
if (!jbv)
3965+
break;
3966+
3967+
*op->resnull = false;
3968+
3969+
res = ExecPrepareJsonItemCoercion(jbv,
3970+
&op->d.jsonexpr.jsexpr->returning,
3971+
&op->d.jsonexpr.scalar,
3972+
econtext->ecxt_per_query_memory,
3973+
&cestate);
3974+
3975+
/* coerce item datum to the output type */
3976+
if (cestate->coerce_via_io ||
3977+
cestate->coerce_via_populate || /* ignored for scalars jsons */
3978+
jexpr->returning.typid == JSONOID ||
3979+
jexpr->returning.typid == JSONBOID)
3980+
{
3981+
/* use coercion from json[b] to the output type */
3982+
res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
3983+
res = ExecEvalJsonExprCoercion(op, econtext,
3984+
res, op->resnull);
3985+
}
3986+
else if (cestate->result_expr_state)
3987+
{
3988+
res = ExecEvalExprPassingCaseValue(cestate->result_expr_state,
3989+
econtext,
3990+
op->resnull,
3991+
res, false);
3992+
}
3993+
/* else no coercion */
3994+
}
38333995
break;
38343996

38353997
case IS_JSON_EXISTS:
@@ -3858,7 +4020,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
38584020
}
38594021

38604022
if (jexpr->op != IS_JSON_EXISTS &&
3861-
(!empty ||
4023+
(!empty ? jexpr->op != IS_JSON_VALUE :
38624024
/* result is already coerced in DEFAULT behavior case */
38634025
jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT))
38644026
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 */
@@ -582,6 +583,28 @@ typedef struct ExprEvalStep
582583
ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */
583584
ExprState *default_on_error; /* ON ERROR DEFAULT expression */
584585
List *args; /* passing arguments */
586+
587+
struct JsonScalarCoercions
588+
{
589+
struct JsonScalarCoercionExprState
590+
{
591+
Node *result_expr; /* coercion expression */
592+
ExprState *result_expr_state; /* coercion expression state */
593+
bool coerce_via_io;
594+
bool coerce_via_populate;
595+
bool initialized;
596+
} string,
597+
numeric,
598+
boolean,
599+
date,
600+
time,
601+
timetz,
602+
timestamp,
603+
timestamptz,
604+
composite,
605+
null;
606+
} scalar; /* states for coercion from SQL/JSON item
607+
* types directly to the output type */
585608
} jsonexpr;
586609

587610
} d;
@@ -673,5 +696,10 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
673696
ExprContext *econtext);
674697
extern void ExecEvalJson(ExprState *state, ExprEvalStep *op,
675698
ExprContext *econtext);
699+
extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
700+
JsonReturning *returning,
701+
struct JsonScalarCoercions *coercions,
702+
MemoryContext mcxt,
703+
struct JsonScalarCoercionExprState **pcestate);
676704

677705
#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)