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

Commit e528afd

Browse files
author
Nikita Glukhov
committed
Optimize and fix JSON item coercion to target type in JSON_VALUE
1 parent 606e446 commit e528afd

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
@@ -2096,6 +2096,9 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
20962096
lappend(scratch.d.jsonexpr.args, var);
20972097
}
20982098

2099+
memset(&scratch.d.jsonexpr.scalar, 0,
2100+
sizeof(scratch.d.jsonexpr.scalar));
2101+
20992102
ExprEvalPushStep(state, &scratch);
21002103
}
21012104
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
@@ -4427,6 +4427,35 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format,
44274427
ret->typmod = -1;
44284428
}
44294429

4430+
/*
4431+
* Try to coerce expression to the output type or
4432+
* use json_populate_type() for composite, array and domain types or
4433+
* use coercion via I/O.
4434+
*/
4435+
Node *
4436+
coerceJsonExpr(ParseState *pstate, Node *expr, JsonReturning *returning,
4437+
bool *coerce_via_io, bool *coerce_via_populate)
4438+
{
4439+
Node *res = coerceJsonFuncExpr(pstate, expr, returning, false);
4440+
char typtype;
4441+
4442+
if (res)
4443+
return res == expr ? NULL : res;
4444+
4445+
typtype = get_typtype(returning->typid);
4446+
4447+
if (coerce_via_populate &&
4448+
(returning->typid == RECORDOID ||
4449+
typtype == TYPTYPE_COMPOSITE ||
4450+
typtype == TYPTYPE_DOMAIN ||
4451+
type_is_array(returning->typid)))
4452+
*coerce_via_populate = true;
4453+
else
4454+
*coerce_via_io = true;
4455+
4456+
return NULL;
4457+
}
4458+
44304459
/*
44314460
* Transform a JSON output clause of JSON_VALUE, JSON_QUERY, JSON_EXISTS.
44324461
*/
@@ -4441,13 +4470,22 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
44414470

44424471
jsexpr->returning = func->output->returning;
44434472

4473+
/* JSON_VALUE returns text by default */
4474+
if (func->op == IS_JSON_VALUE && !OidIsValid(jsexpr->returning.typid))
4475+
{
4476+
jsexpr->returning.typid = TEXTOID;
4477+
jsexpr->returning.typmod = -1;
4478+
}
4479+
44444480
if (OidIsValid(jsexpr->returning.typid))
44454481
{
44464482
JsonReturning ret;
44474483

4448-
if (func->op == IS_JSON_VALUE)
4484+
if (func->op == IS_JSON_VALUE &&
4485+
jsexpr->returning.typid != JSONOID &&
4486+
jsexpr->returning.typid != JSONBOID)
44494487
{
4450-
/* Forced coercion via I/O for JSON_VALUE */
4488+
/* Forced coercion via I/O for JSON_VALUE for non-JSON types */
44514489
jsexpr->result_expr = NULL;
44524490
jsexpr->coerce_via_io = true;
44534491
return;
@@ -4463,15 +4501,10 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
44634501
Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid);
44644502
Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod);
44654503

4466-
jsexpr->result_expr = coerceJsonFuncExpr(pstate,
4467-
placeholder,
4468-
&jsexpr->returning,
4469-
false);
4470-
4471-
if (!jsexpr->result_expr)
4472-
jsexpr->coerce_via_io = true;
4473-
else if (jsexpr->result_expr == placeholder)
4474-
jsexpr->result_expr = NULL;
4504+
jsexpr->result_expr = coerceJsonExpr(pstate, placeholder,
4505+
&jsexpr->returning,
4506+
&jsexpr->coerce_via_io,
4507+
NULL);
44754508
}
44764509
}
44774510
else

src/backend/utils/adt/jsonpath_exec.c

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

3179-
Datum
3179+
JsonbValue *
31803180
JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
31813181
{
31823182
JsonbValue *res;
@@ -3192,7 +3192,7 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
31923192
*empty = !count;
31933193

31943194
if (*empty)
3195-
return PointerGetDatum(NULL);
3195+
return NULL;
31963196

31973197
if (count > 1)
31983198
ereport(ERROR,
@@ -3211,7 +3211,7 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars)
32113211
errmsg("SQL/JSON scalar required")));
32123212

32133213
if (res->type == jbvNull)
3214-
return PointerGetDatum(NULL);
3214+
return NULL;
32153215

3216-
return JsonbPGetDatum(JsonbValueToJsonb(res));
3216+
return res;
32173217
}

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)