pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
To: Jonathan S. Katz <jkatz@postgresql.org>
To: Andres Freund <andres@anarazel.de>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Cc: Andrew Dunstan <andrew@dunslane.net>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: John Naylor <john.naylor@enterprisedb.com>
Cc: Amit Langote <amitlangote09@gmail.com>
Subject: Re: SQL/JSON features for v15
Date: Thu, 18 Aug 2022 06:45:56 +0300
Message-ID: <7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru> (raw)
In-Reply-To: <8d0ffc54-469a-ffab-68a3-8b547e24a08d@postgresql.org>
References: <abd9b83b-aa66-f230-3d6d-734817f0995d@postgresql.org>
	<20220815223853.itrlpqgpqheg6r6a@awork3.anarazel.de>
	<225d341b-f310-eed0-ceff-b8ec61669d9f@postgrespro.ru>
	<20220816021425.5sa6gx67zmpou3bc@awork3.anarazel.de>
	<8d0ffc54-469a-ffab-68a3-8b547e24a08d@postgresql.org>

Hi,

On 17.08.2022 04:45, Jonathan S. Katz wrote:
>
> On 8/15/22 10:14 PM, Andres Freund wrote:
>
>> I pushed a few cleanups to 
>> https://github.com/anarazel/postgres/commits/json
>> while I was hacking on this (ignore that it's based on the meson 
>> tree, that's
>> just faster for me). Some of them might not be applicable anymore, 
>> but it
>> might still make sense for you to look at.
>
> With RMT hat on, this appears to be making progress. A few questions / 
> comments for the group:
>
> 1. Nikita: Did you have a chance to review Andres's changes as well?

Yes, I have reviewed Andres's changes, they all are ok.

Then I started to do on the top of it other fixes that help to avoid
subtransactions when they are not needed. And it ended in the new
refactoring of coercion code.  Also I moved here from v6-0003 fix of
ExecEvalJsonNeedSubtransaction() which considers more cases.



On 16.08.2022 05:14, Andres Freund wrote:
>> But for JIT I still had to construct additional ExprState with a
>> function compiled from subexpression steps.

> Why did you have to do this?

I simply did not dare to implement compilation of recursively-callable
function with additional parameter stepno.  In the v8 patch I did it
by adding a switch with all possible jump addresses of EEOP_SUBTRANS
steps in the beginning of the function.  And it really seems to work
faster, but needs more exploration.  See patch 0003, where both
variants preserved using #ifdef.


The desciprion of the v7 patches:

0001 Simplify JsonExpr execution
  Andres's changes + mine:
   - Added JsonCoercionType enum, fields like via_io replaced with it
   - Emit only context item steps in JSON_TABLE_OP case
   - Skip coercion of NULLs to non-domain types (is it correct?)

0002 Fix returning of json[b] domains in JSON_VALUE:
   simply rebase of v6 onto 0001

0003 Add EEOP_SUBTRANS executor step
   v6 + new recursive JIT

0004 Split JsonExpr execution into steps
   simply rebase of v6 + used LLMBuildSwitch() in EEOP_JSONEXPR

-- 
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company

Attachments: download all (4 files, .tar.gz)

v7-0001-Simplify-JsonExpr-execution.patch (text/x-patch, 40.1K) download From 99cf2d1c6cea1889120da5e845b17ccf5db10332 Mon Sep 17 00:00:00 2001 From: Andres Freund <andres@anarazel.de> Date: Fri, 12 Aug 2022 11:07:06 -0700 Subject: [PATCH v7 1/4] Simplify JsonExpr execution json: rename ExecEvalJson() to ExecEvalJsonExpr() It was confusing that EEOP_JSONEXPR was evaluated by ExecEvalJson() but that a ExecEvalJsonExpr() also existed. The latter is now renamed to ExecEvalJsonExprInternal(), but something better might be possible. json: make ExecPrepareJsonItemCoercion static json: Remove unused 'returning' argument to ExecPrepareJsonItemCoercion()'s json: wip: catching errors via macro, cleanup singatures. wip: no need for subtrans in ExecEvalJsonExpr()??? Remove JSE_OPT_SUBTRANS's parameter coercionInSubtrans Refactor JsonExpr coercions More correctly implement ExecEvalJsonNeedsSubTransaction() Emit only context item for JSON_TABLE Skip coercion of NULLs for non-domain types --- src/backend/executor/execExpr.c | 109 +++-- src/backend/executor/execExprInterp.c | 559 ++++++++++++++------------ src/backend/jit/llvm/llvmjit_expr.c | 2 +- src/backend/jit/llvm/llvmjit_types.c | 2 +- src/backend/nodes/nodeFuncs.c | 10 +- src/backend/optimizer/util/clauses.c | 2 +- src/backend/parser/parse_expr.c | 62 ++- src/include/executor/execExpr.h | 55 ++- src/include/nodes/primnodes.h | 18 +- 9 files changed, 472 insertions(+), 347 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index d0a57c7aaee..1c9782de15f 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2561,49 +2561,72 @@ ExecInitExprRec(Expr *node, ExprState *state, case T_JsonExpr: { JsonExpr *jexpr = castNode(JsonExpr, node); - JsonExprState *jsestate = palloc0(sizeof(JsonExprState)); + JsonExprState *jsestate; + JsonCoercion *result_coercion = jexpr->result_coercion; ListCell *argexprlc; ListCell *argnamelc; - scratch.opcode = EEOP_JSONEXPR; - scratch.d.jsonexpr.jsestate = jsestate; + /* JSON_TABLE preudo-function returns context item as a result */ + if (jexpr->op == JSON_TABLE_OP) + { + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + resv, resnull); + break; + } + jsestate = palloc0(sizeof(JsonExprState)); jsestate->jsexpr = jexpr; - jsestate->formatted_expr = - palloc(sizeof(*jsestate->formatted_expr)); + scratch.opcode = EEOP_JSONEXPR; + scratch.d.jsonexpr.jsestate = jsestate; ExecInitExprRec((Expr *) jexpr->formatted_expr, state, - &jsestate->formatted_expr->value, - &jsestate->formatted_expr->isnull); - - jsestate->pathspec = - palloc(sizeof(*jsestate->pathspec)); + &jsestate->formatted_expr.value, + &jsestate->formatted_expr.isnull); ExecInitExprRec((Expr *) jexpr->path_spec, state, - &jsestate->pathspec->value, - &jsestate->pathspec->isnull); + &jsestate->pathspec.value, + &jsestate->pathspec.isnull); - jsestate->res_expr = - palloc(sizeof(*jsestate->res_expr)); + if (result_coercion) + { + jsestate->result_coercion.type = result_coercion->ctype; - jsestate->result_expr = jexpr->result_coercion - ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr, - state->parent, - &jsestate->res_expr->value, - &jsestate->res_expr->isnull) - : NULL; + if (result_coercion->expr) + jsestate->result_coercion.estate = + ExecInitExprWithCaseValue((Expr *) result_coercion->expr, + state->parent, + &jsestate->coercion_expr.value, + &jsestate->coercion_expr.isnull); - jsestate->default_on_empty = !jexpr->on_empty ? NULL : - ExecInitExpr((Expr *) jexpr->on_empty->default_expr, - state->parent); + /* Skip coercion of NULLs for non-domain types */ + jsestate->skip_null_coercion = + getBaseType(jexpr->returning->typid) == jexpr->returning->typid; + } + else + jsestate->result_coercion.type = JSON_COERCION_NONE; + + /* Additional coercion of JSON_QUERY(OMIT QUOTES) */ + jsestate->io_coercion.type = JSON_COERCION_VIA_IO; + + if (jexpr->on_empty) + { + jsestate->default_on_empty = + ExecInitExpr((Expr *) jexpr->on_empty->default_expr, + state->parent); + + jsestate->default_on_empty_can_throw = + ExecEvalExprCanThrowErrors(jexpr->on_empty->default_expr); + } jsestate->default_on_error = ExecInitExpr((Expr *) jexpr->on_error->default_expr, state->parent); + + /* Initialize type input info if I/O coercion can be used */ if (jexpr->omit_quotes || - (jexpr->result_coercion && jexpr->result_coercion->via_io)) + jsestate->result_coercion.type == JSON_COERCION_VIA_IO) { Oid typinput; @@ -2641,30 +2664,46 @@ ExecInitExprRec(Expr *node, ExprState *state, if (jexpr->coercions) { JsonCoercion **coercion; - struct JsonCoercionState *cstate; + JsonCoercionState *cstate; Datum *caseval; bool *casenull; - jsestate->coercion_expr = - palloc(sizeof(*jsestate->coercion_expr)); + jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions)); - caseval = &jsestate->coercion_expr->value; - casenull = &jsestate->coercion_expr->isnull; + caseval = &jsestate->coercion_expr.value; + casenull = &jsestate->coercion_expr.isnull; - for (cstate = &jsestate->coercions.null, + for (cstate = &jsestate->item_coercions->null, coercion = &jexpr->coercions->null; coercion <= &jexpr->coercions->composite; coercion++, cstate++) { - cstate->coercion = *coercion; - cstate->estate = *coercion ? - ExecInitExprWithCaseValue((Expr *) (*coercion)->expr, - state->parent, - caseval, casenull) : NULL; + if (!*coercion) + /* Missing coercion here means missing cast */ + cstate->type = JSON_COERCION_ERROR; + else if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR) + { + Assert((*coercion)->expr); + cstate->estate = + ExecInitExprWithCaseValue((Expr *) (*coercion)->expr, + state->parent, + caseval, + casenull); + cstate->type = JSON_COERCION_VIA_EXPR; + } + else if ((*coercion)->ctype == JSON_COERCION_NONE) + cstate->type = JSON_COERCION_NONE; + else + { + /* There should not be other coercion types */ + Assert(0); + cstate->type = JSON_COERCION_ERROR; + } } } ExprEvalPushStep(state, &scratch); + break; } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 636794ca6f1..bf141a66066 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -1844,7 +1844,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_CASE(EEOP_JSONEXPR) { /* too complex for an inline implementation */ - ExecEvalJson(state, op, econtext); + ExecEvalJsonExpr(state, op, econtext); EEO_NEXT(); } @@ -4738,56 +4738,159 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, } } + +typedef struct EvalJsonSubtransState +{ + MemoryContext oldcontext; + ResourceOwner oldowner; +} EvalJsonSubtransState; + +static void +ExecEvalJsonStartSubtrans(EvalJsonSubtransState *state) +{ + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and + * execute the corresponding ON ERROR behavior then. + */ + state->oldcontext = CurrentMemoryContext; + state->oldowner = CurrentResourceOwner; + + BeginInternalSubTransaction(NULL); + + /* Want to execute expressions inside function's memory context */ + /* + * AFIXME: I don't think that's OK, there might be lots of leaked memory + * etc. + */ + MemoryContextSwitchTo(state->oldcontext); +} + +static void +ExecEvalJsonEndSubtrans(EvalJsonSubtransState *state) +{ + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(state->oldcontext); + CurrentResourceOwner = state->oldowner; +} + +static void +ExecEvalJsonCatchError(EvalJsonSubtransState *state) +{ + ErrorData *edata; + int ecategory; + + /* Save error info in oldcontext */ + MemoryContextSwitchTo(state->oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(state->oldcontext); + CurrentResourceOwner = state->oldowner; + + ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode); + + if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data + * errors */ + ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */ + { + ReThrowError(edata); + } +} + /* - * Evaluate a coercion of a JSON item to the target type. + * If `error` is not NULL (we need to catch errors), execute the + * statement passed in the third argument inside a subtransaction, + * otherwise do it outside. In the subtrans case an error classified as + * being catchable by ExecEvalJsonCatchError() will set `error` to true. */ +#define JSE_OPT_SUBTRANS(error, jexpr, res, ...) \ + do { \ + bool *error_ = (error); \ + \ + if (!error_) \ + { \ + res = __VA_ARGS__; \ + } \ + else \ + { \ + EvalJsonSubtransState substate = {0}; \ + \ + /* Check correctness of ExecEvalJsonNeedsSubTransaction() */ \ + AssertMacro(ExecEvalJsonNeedsSubTransaction(jexpr)); \ + \ + ExecEvalJsonStartSubtrans(&substate); \ + \ + PG_TRY(); \ + { \ + res = __VA_ARGS__; \ + ExecEvalJsonEndSubtrans(&substate); \ + } \ + PG_CATCH(); \ + { \ + ExecEvalJsonCatchError(&substate); \ + \ + res = (Datum) 0; \ + *error_ = true; \ + } \ + PG_END_TRY(); \ + } \ + } while (0) + +/* Execute one of possible coercions or report an error if there is no cast */ static Datum -ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, - Datum res, bool *isNull, void *p, bool *error) +ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext, + JsonCoercionState *coercion, + Datum res, bool *resnull, bool *error) { - ExprState *estate = p; - JsonExprState *jsestate; + switch (coercion->type) + { + case JSON_COERCION_NONE: + return (Datum) res; - if (estate) /* coerce using specified expression */ - return ExecEvalExpr(estate, econtext, isNull); + case JSON_COERCION_VIA_EXPR: + jsestate->coercion_expr.value = res; + jsestate->coercion_expr.isnull = *resnull; + return ExecEvalExpr(coercion->estate, econtext, resnull); - jsestate = op->d.jsonexpr.jsestate; + case JSON_COERCION_VIA_IO: + { + /* Strip quotes from jsonb strings and call typinput() */ + char *str; - if (jsestate->jsexpr->op != JSON_EXISTS_OP) - { - JsonCoercion *coercion = jsestate->jsexpr->result_coercion; - JsonExpr *jexpr = jsestate->jsexpr; - Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res); + if (*resnull) + str = NULL; + else + str = JsonbUnquote(DatumGetJsonbP(res)); - if ((coercion && coercion->via_io) || - (jexpr->omit_quotes && !*isNull && - JB_ROOT_IS_SCALAR(jb))) - { - /* strip quotes and call typinput function */ - char *str = *isNull ? NULL : JsonbUnquote(jb); + return InputFunctionCall(&jsestate->input.func, str, + jsestate->input.typioparam, + jsestate->jsexpr->returning->typmod); + } - return InputFunctionCall(&jsestate->input.func, str, - jsestate->input.typioparam, - jexpr->returning->typmod); - } - else if (coercion && coercion->via_populate) + case JSON_COERCION_VIA_POPULATE: return json_populate_type(res, JSONBOID, - jexpr->returning->typid, - jexpr->returning->typmod, + jsestate->jsexpr->returning->typid, + jsestate->jsexpr->returning->typmod, &jsestate->cache, econtext->ecxt_per_query_memory, - isNull); - } + resnull); - if (jsestate->result_expr) - { - jsestate->res_expr->value = res; - jsestate->res_expr->isnull = *isNull; + default: + Assert(coercion->type == JSON_COERCION_ERROR); - res = ExecEvalExpr(jsestate->result_expr, econtext, isNull); - } + /* Report an error if there is no cast to the output type */ + if (!error) + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), + errmsg("SQL/JSON item cannot be cast to target type"))); - return res; + *error = true; + *resnull = true; + return (Datum) 0; + } } /* @@ -4844,19 +4947,48 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen, } /* - * Prepare SQL/JSON item coercion to the output type. Returned a datum of the - * corresponding SQL type and a pointer to the coercion state. + * Check whether we need to override default coercion in + * JSON_QUERY(OMIT QUOTES) case. */ -Datum -ExecPrepareJsonItemCoercion(JsonbValue *item, - JsonReturning *returning, - struct JsonCoercionsState *coercions, - struct JsonCoercionState **pcoercion) +static bool +ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull) { - struct JsonCoercionState *coercion; - Datum res; + if (jsexpr->omit_quotes && !isnull) + { + Jsonb *jb = DatumGetJsonbP(res); + + /* Coerce non-null scalar items via I/O in OMIT QUOTES case */ + if (JB_ROOT_IS_SCALAR(jb)) + return true; + } + + return false; +} + +/* + * Prepare SQL/JSON item coercion to the JSON_VALUE output type. + * Returned a coercion status, a datum of the corresponding SQL type + * and a pointer to selected coercion state. + */ +static Datum +ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item, + JsonCoercionState **coercion, bool *resnull) +{ + struct JsonItemsCoercionStates *coercions = jsestate->item_coercions; JsonbValue buf; + *resnull = false; + + if (!coercions) + { + /* + * Special case for json[b] output types. Simply use + * default coercion from jsonb to the output type. + */ + return JsonbPGetDatum(JsonbValueToJsonb(item)); + } + + /* Use coercion from SQL/JSON item type to the output type */ if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) { @@ -4871,174 +5003,96 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, switch (item->type) { case jbvNull: - coercion = &coercions->null; - res = (Datum) 0; - break; + Assert(0); /* must be handled by the caller */ + *coercion = &coercions->null; + *resnull = true; + return (Datum) 0; case jbvString: - coercion = &coercions->string; - res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val, - item->val.string.len)); - break; + *coercion = &coercions->string; + return PointerGetDatum(cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); case jbvNumeric: - coercion = &coercions->numeric; - res = NumericGetDatum(item->val.numeric); - break; + *coercion = &coercions->numeric; + return NumericGetDatum(item->val.numeric); case jbvBool: - coercion = &coercions->boolean; - res = BoolGetDatum(item->val.boolean); - break; + *coercion = &coercions->boolean; + return BoolGetDatum(item->val.boolean); case jbvDatetime: - res = item->val.datetime.value; switch (item->val.datetime.typid) { case DATEOID: - coercion = &coercions->date; + *coercion = &coercions->date; break; case TIMEOID: - coercion = &coercions->time; + *coercion = &coercions->time; break; case TIMETZOID: - coercion = &coercions->timetz; + *coercion = &coercions->timetz; break; case TIMESTAMPOID: - coercion = &coercions->timestamp; + *coercion = &coercions->timestamp; break; case TIMESTAMPTZOID: - coercion = &coercions->timestamptz; + *coercion = &coercions->timestamptz; break; default: elog(ERROR, "unexpected jsonb datetime type oid %u", item->val.datetime.typid); return (Datum) 0; } - break; + + return item->val.datetime.value; case jbvArray: case jbvObject: case jbvBinary: - coercion = &coercions->composite; - res = JsonbPGetDatum(JsonbValueToJsonb(item)); - break; + Assert(0); /* non-scalars must be rejected by JsonPathValue() */ + *coercion = &coercions->composite; + return JsonbPGetDatum(JsonbValueToJsonb(item)); default: elog(ERROR, "unexpected jsonb value type %d", item->type); return (Datum) 0; } - - *pcoercion = coercion; - - return res; } -typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext, - Datum item, bool *resnull, void *p, bool *error); - static Datum -ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op, - ExprContext *econtext, - Datum res, bool *resnull, - void *p, bool *error, bool subtrans) +ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext, + JsonPath *path, Datum item, bool *resnull, + bool *error) { - MemoryContext oldcontext; - ResourceOwner oldowner; - - if (!subtrans) - /* No need to use subtransactions. */ - return func(op, econtext, res, resnull, p, error); - - /* - * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and - * execute the corresponding ON ERROR behavior then. - */ - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - Assert(error); - - BeginInternalSubTransaction(NULL); - /* Want to execute expressions inside function's memory context */ - MemoryContextSwitchTo(oldcontext); - - PG_TRY(); - { - res = func(op, econtext, res, resnull, p, error); - - /* Commit the inner transaction, return to outer xact context */ - ReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(oldcontext); - CurrentResourceOwner = oldowner; - } - PG_CATCH(); - { - ErrorData *edata; - int ecategory; - - /* Save error info in oldcontext */ - MemoryContextSwitchTo(oldcontext); - edata = CopyErrorData(); - FlushErrorState(); - - /* Abort the inner transaction */ - RollbackAndReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(oldcontext); - CurrentResourceOwner = oldowner; - - ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode); - - if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data - * errors */ - ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */ - ReThrowError(edata); - - res = (Datum) 0; - *error = true; - } - PG_END_TRY(); - - return res; -} - - -typedef struct -{ - JsonPath *path; - bool *error; - bool coercionInSubtrans; -} ExecEvalJsonExprContext; - -static Datum -ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, - Datum item, bool *resnull, void *pcxt, - bool *error) -{ - ExecEvalJsonExprContext *cxt = pcxt; - JsonPath *path = cxt->path; - JsonExprState *jsestate = op->d.jsonexpr.jsestate; JsonExpr *jexpr = jsestate->jsexpr; - ExprState *estate = NULL; + JsonCoercionState *coercion = &jsestate->result_coercion; bool empty = false; Datum res = (Datum) 0; + *resnull = true; + switch (jexpr->op) { case JSON_QUERY_OP: res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error, jsestate->args); if (error && *error) - { - *resnull = true; return (Datum) 0; - } + + if (empty) + break; + *resnull = !DatumGetPointer(res); + + /* Override default coercion in OMIT QUOTES case */ + if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull)) + coercion = &jsestate->io_coercion; + break; case JSON_VALUE_OP: { - struct JsonCoercionState *jcstate; JsonbValue *jbv = JsonPathValue(item, path, &empty, error, jsestate->args); @@ -5049,49 +5103,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, break; Assert(!empty); + Assert(jbv->type != jbvNull); - *resnull = false; - - /* coerce scalar item to the output type */ - if (jexpr->returning->typid == JSONOID || - jexpr->returning->typid == JSONBOID) - { - /* Use result coercion from json[b] to the output type */ - res = JsonbPGetDatum(JsonbValueToJsonb(jbv)); - break; - } - - /* Use coercion from SQL/JSON item type to the output type */ - res = ExecPrepareJsonItemCoercion(jbv, - jsestate->jsexpr->returning, - &jsestate->coercions, - &jcstate); - - if (jcstate->coercion && - (jcstate->coercion->via_io || - jcstate->coercion->via_populate)) - { - if (error) - { - *error = true; - return (Datum) 0; - } - - /* - * Coercion via I/O means here that the cast to the target - * type simply does not exist. - */ - ereport(ERROR, - (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), - errmsg("SQL/JSON item cannot be cast to target type"))); - } - else if (!jcstate->estate) - return res; /* no coercion */ - - /* coerce using specific expression */ - estate = jcstate->estate; - jsestate->coercion_expr->value = res; - jsestate->coercion_expr->isnull = *resnull; + res = ExecPrepareJsonValueCoercion(jsestate, jbv, + &coercion, resnull); break; } @@ -5103,21 +5118,9 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, *resnull = error && *error; res = BoolGetDatum(exists); - - if (!jsestate->result_expr) - return res; - - /* coerce using result expression */ - estate = jsestate->result_expr; - jsestate->res_expr->value = res; - jsestate->res_expr->isnull = *resnull; - break; + break; /* always use result coercion */ } - case JSON_TABLE_OP: - *resnull = false; - return item; - default: elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); return (Datum) 0; @@ -5141,73 +5144,115 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, } if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT) - + { /* - * Execute DEFAULT expression as a coercion expression, because - * its result is already coerced to the target type. + * Execute DEFAULT expression in a subtransation if needed. + * Coercion is not needed here, because expression is + * already coerced to the target type by the parser. */ - estate = jsestate->default_on_empty; - else - /* Execute ON EMPTY behavior */ - res = ExecEvalJsonBehavior(econtext, jexpr->on_empty, - jsestate->default_on_empty, - resnull); + JSE_OPT_SUBTRANS(jsestate->default_on_empty_can_throw ? error : NULL, + jexpr, res, + ExecEvalExpr(jsestate->default_on_empty, + econtext, resnull)); + return res; + } + + /* Execute ON EMPTY behavior, coercion is default */ + res = ExecEvalJsonBehavior(econtext, jexpr->on_empty, + jsestate->default_on_empty, + resnull); } - return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext, - res, resnull, estate, error, - cxt->coercionInSubtrans); + /* + * Execute resulting coercion in a subtransaction if `error` != NULL. + * In case of cast errors, there is no need to use subtransaction. + */ + if (coercion->type != JSON_COERCION_NONE) + { + /* Skip coercion of NULLs for non-domain types */ + if (*resnull && jsestate->skip_null_coercion) + return (Datum) 0; + + JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL, + jexpr, res, + ExecEvalJsonExprCoercion(jsestate, econtext, + coercion, res, + resnull, error)); + } + + return res; } bool -ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, - struct JsonCoercionsState *coercions) +ExecEvalExprCanThrowErrors(Node *expr) { - if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + if (!expr) return false; - if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion) + if (IsA(expr, Const)) return false; - if (!coercions) - return true; + /* TODO consider more cases */ + return true; +} - return false; +/* Can SQL/JSON expression start subtransactions for error handling? */ +bool +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) +{ + if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + return false; /* no error handling needed */ + + if (jsexpr->result_coercion && + jsexpr->result_coercion->ctype != JSON_COERCION_NONE) + return true; /* may execute non-empty result coercion */ + + if (jsexpr->on_empty && + jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT && + ExecEvalExprCanThrowErrors(jsexpr->on_empty->default_expr)) + return true; /* may execute arbitrary unsafe expression */ + + if (jsexpr->coercions) + return true; /* JSON_VALUE may execute item coercions */ + + if (jsexpr->omit_quotes) + return true; /* JSON_QUERY may execute coercion via I/O */ + + return false; /* subtransactios should not be used */ } /* ---------------------------------------------------------------- - * ExecEvalJson + * ExecEvalJsonExpr * ---------------------------------------------------------------- */ void -ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { - ExecEvalJsonExprContext cxt; JsonExprState *jsestate = op->d.jsonexpr.jsestate; JsonExpr *jexpr = jsestate->jsexpr; Datum item; - Datum res = (Datum) 0; + Datum res; JsonPath *path; ListCell *lc; bool error = false; - bool needSubtrans; bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR; - *op->resnull = true; /* until we get a result */ - *op->resvalue = (Datum) 0; - - if (jsestate->formatted_expr->isnull || jsestate->pathspec->isnull) + if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull) { + *op->resnull = true; + *op->resvalue = (Datum) 0; + /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, - NULL, NULL); + (void) ExecEvalJsonExprCoercion(jsestate, econtext, + &jsestate->result_coercion, + *op->resvalue, op->resnull, NULL); Assert(*op->resnull); return; } - item = jsestate->formatted_expr->value; - path = DatumGetJsonPathP(jsestate->pathspec->value); + item = jsestate->formatted_expr.value; + path = DatumGetJsonPathP(jsestate->pathspec.value); /* reset JSON path variable contexts */ foreach(lc, jsestate->args) @@ -5218,19 +5263,13 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) var->evaluated = false; } - needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions); - - cxt.path = path; - cxt.error = throwErrors ? NULL : &error; - cxt.coercionInSubtrans = !needSubtrans && !throwErrors; - Assert(!needSubtrans || cxt.error); - - res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item, - op->resnull, &cxt, cxt.error, - needSubtrans); - + res = ExecEvalJsonExprInternal(jsestate, econtext, + path, item, op->resnull, + throwErrors ? NULL : &error); if (error) { + Assert(jexpr->on_error->btype != JSON_BEHAVIOR_ERROR); + /* Execute ON ERROR behavior */ res = ExecEvalJsonBehavior(econtext, jexpr->on_error, jsestate->default_on_error, @@ -5238,9 +5277,9 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) /* result is already coerced in DEFAULT behavior case */ if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT) - res = ExecEvalJsonExprCoercion(op, econtext, res, - op->resnull, - NULL, NULL); + res = ExecEvalJsonExprCoercion(jsestate, econtext, + &jsestate->result_coercion, + res, op->resnull, NULL); } *op->resvalue = res; diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index bd3965143da..fd72630f5e6 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2408,7 +2408,7 @@ llvm_compile_expr(ExprState *state) break; case EEOP_JSONEXPR: - build_EvalXFunc(b, mod, "ExecEvalJson", + build_EvalXFunc(b, mod, "ExecEvalJsonExpr", v_state, op, v_econtext); LLVMBuildBr(b, opblocks[opno + 1]); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 373471ad27f..37fe64654b6 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -135,7 +135,7 @@ void *referenced_functions[] = ExecEvalXmlExpr, ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, - ExecEvalJson, + ExecEvalJsonExpr, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index c334daae392..b7fdd0af9d8 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1014,9 +1014,10 @@ exprCollation(const Node *expr) if (!coercion) coll = InvalidOid; - else if (coercion->expr) + else if (coercion->ctype == JSON_COERCION_VIA_EXPR) coll = exprCollation(coercion->expr); - else if (coercion->via_io || coercion->via_populate) + else if (coercion->ctype == JSON_COERCION_VIA_IO || + coercion->ctype == JSON_COERCION_VIA_POPULATE) coll = coercion->collation; else coll = InvalidOid; @@ -1259,9 +1260,10 @@ exprSetCollation(Node *expr, Oid collation) if (!coercion) Assert(!OidIsValid(collation)); - else if (coercion->expr) + else if (coercion->ctype == JSON_COERCION_VIA_EXPR) exprSetCollation(coercion->expr, collation); - else if (coercion->via_io || coercion->via_populate) + else if (coercion->ctype == JSON_COERCION_VIA_IO || + coercion->ctype == JSON_COERCION_VIA_POPULATE) coercion->collation = collation; else Assert(!OidIsValid(collation)); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 533df86ff77..980c34cf49d 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -901,7 +901,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) { JsonExpr *jsexpr = (JsonExpr *) node; - if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL)) + if (ExecEvalJsonNeedsSubTransaction(jsexpr)) { context->max_hazard = PROPARALLEL_UNSAFE; return true; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index fabb5f72076..3b82c0f3328 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4172,10 +4172,11 @@ assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format, /* * Try to coerce expression to the output type or * use json_populate_type() for composite, array and domain types or - * use coercion via I/O. + * use coercion via I/O, if allowed. */ static JsonCoercion * -coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning) +coerceJsonExpr(ParseState *pstate, Node *expr, + const JsonReturning *returning, bool allow_io_coercion) { char typtype; JsonCoercion *coercion = makeNode(JsonCoercion); @@ -4185,20 +4186,28 @@ coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning) if (coercion->expr) { if (coercion->expr == expr) + { coercion->expr = NULL; + coercion->ctype = JSON_COERCION_NONE; + } + else + coercion->ctype = JSON_COERCION_VIA_EXPR; return coercion; } + if (!allow_io_coercion) + return NULL; + typtype = get_typtype(returning->typid); if (returning->typid == RECORDOID || typtype == TYPTYPE_COMPOSITE || typtype == TYPTYPE_DOMAIN || type_is_array(returning->typid)) - coercion->via_populate = true; + coercion->ctype = JSON_COERCION_VIA_POPULATE; else - coercion->via_io = true; + coercion->ctype = JSON_COERCION_VIA_IO; return coercion; } @@ -4231,8 +4240,7 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, { /* Forced coercion via I/O for JSON_VALUE for non-JSON types */ jsexpr->result_coercion = makeNode(JsonCoercion); - jsexpr->result_coercion->expr = NULL; - jsexpr->result_coercion->via_io = true; + jsexpr->result_coercion->ctype = JSON_COERCION_VIA_IO; return; } @@ -4247,7 +4255,8 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod); jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder, - jsexpr->returning); + jsexpr->returning, + true); } } else @@ -4318,13 +4327,15 @@ initJsonItemCoercion(ParseState *pstate, Oid typid, expr = (Node *) placeholder; } - return coerceJsonExpr(pstate, expr, returning); + /* Allow only ordinary casts here */ + return coerceJsonExpr(pstate, expr, returning, false); } -static void -initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions, - const JsonReturning *returning, Oid contextItemTypeId) +static JsonItemCoercions * +initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning, + Oid contextItemTypeId) { + JsonItemCoercions *coercions = makeNode(JsonItemCoercions); struct { JsonCoercion **coercion; @@ -4347,6 +4358,8 @@ initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions, for (p = coercionTypids; p->coercion; p++) *p->coercion = initJsonItemCoercion(pstate, p->typid, returning); + + return coercions; } /* @@ -4377,9 +4390,12 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) coerceDefaultJsonExpr(pstate, jsexpr, jsexpr->on_error->default_expr); - jsexpr->coercions = makeNode(JsonItemCoercions); - initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning, - exprType(contextItemExpr)); + if (jsexpr->returning->typid != JSONOID && + jsexpr->returning->typid != JSONBOID) + jsexpr->coercions = + initJsonItemCoercions(pstate, jsexpr->returning, + exprType(contextItemExpr)); + break; @@ -4409,6 +4425,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT; jsexpr->returning->format->encoding = JS_ENC_DEFAULT; + /* Coerce intermediate boolean result to the output type if needed */ if (!OidIsValid(jsexpr->returning->typid)) { jsexpr->returning->typid = BOOLOID; @@ -4418,21 +4435,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) { CaseTestExpr *placeholder = makeNode(CaseTestExpr); int location = exprLocation((Node *) jsexpr); + Node *coercion_expr; placeholder->typeId = BOOLOID; placeholder->typeMod = -1; placeholder->collation = InvalidOid; - jsexpr->result_coercion = makeNode(JsonCoercion); - jsexpr->result_coercion->expr = - coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID, + coercion_expr = + coerce_to_target_type(pstate, (Node *) placeholder, + BOOLOID, jsexpr->returning->typid, jsexpr->returning->typmod, COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, location); - if (!jsexpr->result_coercion->expr) + if (!coercion_expr) ereport(ERROR, (errcode(ERRCODE_CANNOT_COERCE), errmsg("cannot cast type %s to %s", @@ -4440,8 +4458,12 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) format_type_be(jsexpr->returning->typid)), parser_coercion_errposition(pstate, location, (Node *) jsexpr))); - if (jsexpr->result_coercion->expr == (Node *) placeholder) - jsexpr->result_coercion->expr = NULL; + if (coercion_expr != (Node *) placeholder) + { + jsexpr->result_coercion = makeNode(JsonCoercion); + jsexpr->result_coercion->expr = coercion_expr; + jsexpr->result_coercion->ctype = JSON_COERCION_VIA_EXPR; + } } break; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index c8ef917ffe0..7dd96b8d5ba 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -757,6 +757,12 @@ typedef struct JsonConstructorExprState int nargs; } JsonConstructorExprState; +typedef struct JsonCoercionState +{ + ExprState *estate; /* coercion expression state, if any */ + JsonCoercionType type; +} JsonCoercionState; + /* EEOP_JSONEXPR state, too big to inline */ typedef struct JsonExprState { @@ -769,10 +775,9 @@ typedef struct JsonExprState } input; /* I/O info for output type */ NullableDatum - *formatted_expr, /* formatted context item value */ - *res_expr, /* result item */ - *coercion_expr, /* input for JSON item coercion */ - *pathspec; /* path specification value */ + formatted_expr, /* formatted context item value */ + coercion_expr, /* input for JSON item coercion */ + pathspec; /* path specification value */ ExprState *result_expr; /* coerced to output type */ ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */ @@ -781,15 +786,16 @@ typedef struct JsonExprState void *cache; /* cache for json_populate_type() */ - struct JsonCoercionsState + /* + * States for coercion of SQL/JSON items produced in JSON_VALUE + * directly to the output type. + */ + struct JsonItemsCoercionStates { - struct JsonCoercionState - { - JsonCoercion *coercion; /* coercion expression */ - ExprState *estate; /* coercion expression state */ - } null, + JsonCoercionState + null, string, - numeric , + numeric, boolean, date, time, @@ -797,8 +803,19 @@ typedef struct JsonExprState timestamp, timestamptz, composite; - } coercions; /* states for coercion from SQL/JSON item - * types directly to the output type */ + } *item_coercions; + + /* Default output coercion to use, can be overriden in JSON_QUERY */ + JsonCoercionState result_coercion; + + /* Special I/O coercion state for JSON_QUERY OMIT QUOTES */ + JsonCoercionState io_coercion; + + /* Can expression in DEFAULT ON EMPTY throw errors? */ + bool default_on_empty_can_throw; + + /* Can we skip coercion of NULLs (non-domain output type)? */ + bool skip_null_coercion; } JsonExprState; /* functions in execExpr.c */ @@ -860,14 +877,10 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, - ExprContext *econtext); -extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, - JsonReturning *returning, - struct JsonCoercionsState *coercions, - struct JsonCoercionState **pjcstate); -extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, - struct JsonCoercionsState *); +extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); +extern bool ExecEvalExprCanThrowErrors(Node *expr); extern Datum ExecEvalExprPassingCaseValue(ExprState *estate, ExprContext *econtext, bool *isnull, Datum caseval_datum, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 3aa96bb6855..0a40d59be26 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1551,6 +1551,15 @@ typedef struct JsonBehavior Node *default_expr; /* default expression, if any */ } JsonBehavior; +typedef enum JsonCoercionType +{ + JSON_COERCION_NONE = 0, /* coercion is not needed */ + JSON_COERCION_VIA_EXPR = 1, /* coerce using expression */ + JSON_COERCION_VIA_IO = 2, /* coerce using type input function */ + JSON_COERCION_VIA_POPULATE = 3, /* coerce using json_populate_type() */ + JSON_COERCION_ERROR = 4 /* no noercion is available */ +} JsonCoercionType; + /* * JsonCoercion - * coercion from SQL/JSON item types to SQL types @@ -1558,10 +1567,11 @@ typedef struct JsonBehavior typedef struct JsonCoercion { NodeTag type; - Node *expr; /* resulting expression coerced to target type */ - bool via_populate; /* coerce result using json_populate_type()? */ - bool via_io; /* coerce result using type input function? */ - Oid collation; /* collation for coercion via I/O or populate */ + JsonCoercionType ctype; /* coercion type */ + Node *expr; /* resulting expression coerced to the + * target if type == JSON_COERCION_VIA_EXPR, + * NULL otherwise */ + Oid collation; /* collation for coercion via I/O or populate */ } JsonCoercion; /* -- 2.25.1
v7-0002-Fix-returning-of-json-b-domains-in-JSON_VALUE.patch (text/x-patch, 17.5K) download From 8db33b187c1a032d2080b557327b8d7bc2cf2e5d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov <n.gluhov@postgrespro.ru> Date: Mon, 15 Aug 2022 20:18:11 +0300 Subject: [PATCH v7 2/4] Fix returning of json[b] domains in JSON_VALUE() --- src/backend/parser/parse_expr.c | 52 ++++-- src/include/executor/execExpr.h | 3 + src/test/regress/expected/jsonb_sqljson.out | 181 +++++++++++++++++++- src/test/regress/sql/jsonb_sqljson.sql | 63 ++++++- 4 files changed, 287 insertions(+), 12 deletions(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 3b82c0f3328..aff6fc1a1c4 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4212,12 +4212,29 @@ coerceJsonExpr(ParseState *pstate, Node *expr, return coercion; } +/* Is it a json/jsonb type or its domain? */ +static bool +isJsonType(Oid typid) +{ + Oid basetypid; + + if (typid == JSONOID || typid == JSONBOID) + return true; + + basetypid = getBaseType(typid); + + if (basetypid == JSONOID || basetypid == JSONBOID) + return true; + + return false; +} + /* * Transform a JSON output clause of JSON_VALUE and JSON_QUERY. */ static void transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, - JsonExpr *jsexpr) + JsonExpr *jsexpr, bool *returning_json) { Node *expr = jsexpr->formatted_expr; @@ -4234,11 +4251,19 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, { JsonReturning ret; - if (func->op == JSON_VALUE_OP && - jsexpr->returning->typid != JSONOID && - jsexpr->returning->typid != JSONBOID) + *returning_json = isJsonType(jsexpr->returning->typid); + + if (func->op == JSON_VALUE_OP && !*returning_json) { - /* Forced coercion via I/O for JSON_VALUE for non-JSON types */ + /* + * In JSON_VALUE result_coercion can only be used when + * returning JSON types and for NULL values (resulting + * item is JSON null or NULL behavior is used). + * So there is no need to find a cast from json[b] to + * output type when it is a non-JSON type, we only need to + * check domains for NOT NULL, what can be done using I/O + * coercion. + */ jsexpr->result_coercion = makeNode(JsonCoercion); jsexpr->result_coercion->ctype = JSON_COERCION_VIA_IO; return; @@ -4260,8 +4285,11 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, } } else + { assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format, jsexpr->returning); + *returning_json = true; + } } /* @@ -4371,13 +4399,14 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) JsonExpr *jsexpr = transformJsonExprCommon(pstate, func); const char *func_name = NULL; Node *contextItemExpr = jsexpr->formatted_expr; + bool returning_json; switch (func->op) { case JSON_VALUE_OP: func_name = "JSON_VALUE"; - transformJsonFuncExprOutput(pstate, func, jsexpr); + transformJsonFuncExprOutput(pstate, func, jsexpr, &returning_json); jsexpr->returning->format->format_type = JS_FORMAT_DEFAULT; jsexpr->returning->format->encoding = JS_ENC_DEFAULT; @@ -4390,19 +4419,22 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) coerceDefaultJsonExpr(pstate, jsexpr, jsexpr->on_error->default_expr); - if (jsexpr->returning->typid != JSONOID && - jsexpr->returning->typid != JSONBOID) + /* + * Don't initialize item coercions if returning one of JSON + * types or their domains. Coercion will be done using + * "result_coercion". + */ + if (!returning_json) jsexpr->coercions = initJsonItemCoercions(pstate, jsexpr->returning, exprType(contextItemExpr)); - break; case JSON_QUERY_OP: func_name = "JSON_QUERY"; - transformJsonFuncExprOutput(pstate, func, jsexpr); + transformJsonFuncExprOutput(pstate, func, jsexpr, &returning_json); jsexpr->on_empty->default_expr = coerceDefaultJsonExpr(pstate, jsexpr, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 7dd96b8d5ba..51e8d93178e 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -789,6 +789,9 @@ typedef struct JsonExprState /* * States for coercion of SQL/JSON items produced in JSON_VALUE * directly to the output type. + * + * `item_coercions == NULL` means output type is json[b] or its + * domain and we use `result_coercion`. */ struct JsonItemsCoercionStates { diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index ef496110af3..ee5e597909c 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -349,14 +349,193 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; 03-01-2017 (1 row) --- Test NULL checks execution in domain types +-- Test for domain types CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +CREATE DOMAIN sqljsonb_json_not_null AS json NOT NULL CHECK (VALUE::text <> '0'); +CREATE DOMAIN sqljsonb_jsonb_not_null AS jsonb NOT NULL CHECK (VALUE <> '0'); +-- Test casting to json[b] domains (items casted as is, strings are not unquoted) +SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_not_null); + json_value +------------ + "1" +(1 row) + +SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null); + json_value +------------ + "1" +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not_null); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_not_null); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_not_null); + json_value +------------ + true +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb_not_null); + json_value +------------ + true +(1 row) + +-- Test NULL checks execution in domain types SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); ERROR: domain sqljsonb_int_not_null does not allow null values SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null NULL ON ERROR); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null NULL ON ERROR); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null); +ERROR: domain sqljsonb_json_not_null does not allow null values +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null ERROR ON ERROR); +ERROR: value for domain sqljsonb_json_not_null violates check constraint "sqljsonb_json_not_null_check" +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null); +ERROR: domain sqljsonb_jsonb_not_null does not allow null values +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null ERROR ON ERROR); +ERROR: value for domain sqljsonb_jsonb_not_null violates check constraint "sqljsonb_jsonb_not_null_check" +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); + json_value +------------ + 2 +(1 row) + +-- Test returning of non-scalar items SELECT JSON_VALUE(jsonb '[]', '$'); json_value ------------ diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index fff25374808..d70104b70ef 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -82,12 +82,73 @@ SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; --- Test NULL checks execution in domain types +-- Test for domain types CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +CREATE DOMAIN sqljsonb_json_not_null AS json NOT NULL CHECK (VALUE::text <> '0'); +CREATE DOMAIN sqljsonb_jsonb_not_null AS jsonb NOT NULL CHECK (VALUE <> '0'); + +-- Test casting to json[b] domains (items casted as is, strings are not unquoted) +SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb '"1"', '$' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb '1', '$' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING sqljsonb_jsonb_not_null); + +-- Test NULL checks execution in domain types SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON EMPTY DEFAULT 2 ON ERROR); SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON EMPTY); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_json_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON EMPTY); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null); +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_json_not_null DEFAULT '2' ON ERROR); + +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null); +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '0', '$' RETURNING sqljsonb_jsonb_not_null DEFAULT '2' ON ERROR); + +-- Test returning of non-scalar items SELECT JSON_VALUE(jsonb '[]', '$'); SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); SELECT JSON_VALUE(jsonb '{}', '$'); -- 2.25.1
v7-0003-Add-EEOP_SUBTRANS-executor-step.patch (text/x-patch, 16.6K) download From ea071133208844d5843a4803075c153ede07e409 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov <n.gluhov@postgrespro.ru> Date: Mon, 15 Aug 2022 15:04:17 +0300 Subject: [PATCH v7 3/4] Add EEOP_SUBTRANS executor step --- src/backend/executor/execExprInterp.c | 102 +++++++++++- src/backend/jit/llvm/llvmjit_expr.c | 218 ++++++++++++++++++++++++-- src/backend/jit/llvm/llvmjit_types.c | 2 + src/include/executor/execExpr.h | 15 ++ src/include/nodes/execnodes.h | 5 + 5 files changed, 330 insertions(+), 12 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index bf141a66066..66898198f33 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -158,6 +158,8 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, bool *changed); static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, ExprContext *econtext, bool checkisnull); +static void ExecEvalSubtransInterp(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); /* fast-path evaluation functions */ static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); @@ -398,7 +400,8 @@ ExecReadyInterpretedExpr(ExprState *state) * (Only applies when EEO_USE_COMPUTED_GOTO is defined.) */ static Datum -ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) +ExecInterpExprStep(ExprState *state, int stepno, + ExprContext *econtext, bool *isnull) { ExprEvalStep *op; TupleTableSlot *resultslot; @@ -488,6 +491,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, + &&CASE_EEOP_SUBTRANS, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, &&CASE_EEOP_JSONEXPR, @@ -519,7 +523,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) #endif /* EEO_USE_COMPUTED_GOTO */ /* setup state */ - op = state->steps; + op = &state->steps[stepno]; resultslot = state->resultslot; innerslot = econtext->ecxt_innertuple; outerslot = econtext->ecxt_outertuple; @@ -1827,6 +1831,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_SUBTRANS) + { + /* too complex for an inline implementation */ + if (ExecEvalSubtrans(state, op, econtext, + ExecEvalSubtransInterp)) + EEO_JUMP(op->d.subtrans.jump_done); + else + EEO_JUMP(op->d.subtrans.jump_error); + } + EEO_CASE(EEOP_JSON_CONSTRUCTOR) { /* too complex for an inline implementation */ @@ -1861,6 +1875,12 @@ out: return state->resvalue; } +static Datum +ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) +{ + return ExecInterpExprStep(state, 0, econtext, isnull); +} + /* * Expression evaluation callback that performs extra checks before executing * the expression. Declared extern so other methods of execution can use it @@ -4620,6 +4640,84 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans, MemoryContextSwitchTo(oldContext); } +/* + * Execute subexpression steps using recursive interpreter call. + * "op" points EEOP_SUBTRANS step. + */ +static void +ExecEvalSubtransInterp(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + /* Recursively call interpreter for next step ignoring result */ + bool isnull; + + (void) ExecInterpExprStep(state, op - state->steps + 1, econtext, + &isnull); +} + +/* + * Execute EEOP_SUBTRANS, calling "eval" function inside a + * subtransaction. This function executes sequence of subexpression + * steps that begins right after current step and ends with EEOP_DONE. + */ +bool +ExecEvalSubtrans(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, ExecEvalSubroutine eval) +{ + MemoryContext oldcontext = CurrentMemoryContext; + ResourceOwner oldowner = CurrentResourceOwner; + bool error; + + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and + * execute the corresponding ON ERROR behavior then. + */ + BeginInternalSubTransaction(NULL); + /* Want to execute expressions inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + + PG_TRY(); + { + eval(state, op, econtext); + + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + error = false; + } + PG_CATCH(); + { + ErrorData *edata; + int ecategory; + + /* Save error info in oldcontext */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode); + + if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data errors */ + ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */ + ReThrowError(edata); + + *op->resvalue = (Datum) 0; + *op->resnull = true; + + error = true; + } + PG_END_TRY(); + + return !error; +} + /* * Evaluate a JSON constructor expression. */ diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index fd72630f5e6..6568228b996 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -45,14 +45,20 @@ #include "utils/typcache.h" #include "utils/xml.h" +#define LLVMJIT_SUBTRANS_RECURSIVE + typedef struct CompiledExprState { LLVMJitContext *context; const char *funcname; } CompiledExprState; - static Datum ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull); +static void ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +#ifdef LLVMJIT_SUBTRANS_RECURSIVE +static Datum ExecRunCompileExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull); +#endif static LLVMValueRef BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b, LLVMModuleRef mod, FunctionCallInfo fcinfo, @@ -70,15 +76,15 @@ static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod); lengthof(((LLVMValueRef[]){__VA_ARGS__})), \ ((LLVMValueRef[]){__VA_ARGS__})) - /* - * JIT compile expression. + * JIT compile expression staring from the specified step. */ -bool -llvm_compile_expr(ExprState *state) +static bool +llvm_compile_expr_step(ExprState *state, int start_opno) { PlanState *parent = state->parent; char *funcname; + const char *functypename = "TypeExprStateEvalFunc"; LLVMJitContext *context = NULL; @@ -123,6 +129,12 @@ llvm_compile_expr(ExprState *state) instr_time starttime; instr_time endtime; +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + LLVMValueRef v_opno; + int num_recursive_opnos = 1; + bool recursive = false; +#endif + llvm_enter_fatal_on_oom(); /* @@ -148,9 +160,17 @@ llvm_compile_expr(ExprState *state) funcname = llvm_expand_funcname(context, "evalexpr"); +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + if (state->flags & EEO_FLAG_HAVE_SUBTRANS) + { + recursive = true; + functypename = "TypeExprStateEvalStepFunc"; + } +#endif + /* create function */ eval_fn = LLVMAddFunction(mod, funcname, - llvm_pg_var_func_type("TypeExprStateEvalFunc")); + llvm_pg_var_func_type(functypename)); LLVMSetLinkage(eval_fn, LLVMExternalLinkage); LLVMSetVisibility(eval_fn, LLVMDefaultVisibility); llvm_copy_attributes(AttributeTemplate, eval_fn); @@ -161,6 +181,10 @@ llvm_compile_expr(ExprState *state) v_state = LLVMGetParam(eval_fn, 0); v_econtext = LLVMGetParam(eval_fn, 1); v_isnullp = LLVMGetParam(eval_fn, 2); +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + if (recursive) + v_opno = LLVMGetParam(eval_fn, 3); +#endif LLVMPositionBuilderAtEnd(b, entry); @@ -224,13 +248,58 @@ llvm_compile_expr(ExprState *state) /* allocate blocks for each op upfront, so we can do jumps easily */ opblocks = palloc(sizeof(LLVMBasicBlockRef) * state->steps_len); - for (int opno = 0; opno < state->steps_len; opno++) + for (int opno = start_opno; opno < state->steps_len; opno++) + { opblocks[opno] = l_bb_append_v(eval_fn, "b.op.%d.start", opno); +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + if (recursive) + { + /* count recursive opnos */ + ExprEvalStep *op = &state->steps[opno]; + + if (ExecEvalStepOp(state, op) == EEOP_SUBTRANS) + num_recursive_opnos++; + + } +#endif + } + +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + if (recursive) + { + /* + * Emit switch with jump to each recursive opno. + * + * switch (opno) { + * case N: + * goto b.op.N.start; + * ... + * } + */ + LLVMValueRef v_switch = + LLVMBuildSwitch(b, v_opno, opblocks[start_opno], + num_recursive_opnos); + + LLVMAddCase(v_switch, l_int32_const(start_opno), + opblocks[start_opno]); + + for (int opno = start_opno; opno < state->steps_len; opno++) + { + ExprEvalStep *op = &state->steps[opno]; + + if (ExecEvalStepOp(state, op) == EEOP_SUBTRANS) + /* SUBTRANS expression starts in the next step */ + LLVMAddCase(v_switch, l_int32_const(opno + 1), + opblocks[opno + 1]); + } + } + else +#endif /* jump from entry to first block */ - LLVMBuildBr(b, opblocks[0]); + LLVMBuildBr(b, opblocks[start_opno]); - for (int opno = 0; opno < state->steps_len; opno++) + for (int opno = start_opno; opno < state->steps_len; opno++) { ExprEvalStep *op; ExprEvalOp opcode; @@ -2395,6 +2464,72 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_SUBTRANS: + { + LLVMValueRef v_ret; + LLVMTypeRef v_functype; + +#ifndef LLVMJIT_SUBTRANS_RECURSIVE + ExprState *substate; + + /* Create subexpression ExprState */ + substate = makeNode(ExprState); + substate->parent = state->parent; + substate->steps = state->steps; + substate->steps_len = op->d.subtrans.jump_next; + + /* + * Compile it starting from the first subexpression + * step, which is the next step. + */ + llvm_compile_expr_step(substate, opno + 1); + + /* + * Save subexpression state in the private area of + * the step. + */ + op->d.subtrans.private_data = substate; +#endif + + Assert(state->flags & EEO_FLAG_HAVE_SUBTRANS); + + /* + * Call ExecEvalJsonExprSubtrans() passing + * ExecEvalSubtransCompiled(), which will execute + * subexpression getting ExprState from the private + * area. + */ + v_functype = llvm_pg_var_func_type("TypeExecEvalSubroutine"); + + v_ret = build_EvalXFunc(b, mod, "ExecEvalSubtrans", + v_state, op, v_econtext, + l_ptr_const(ExecEvalSubtransCompiled, + LLVMPointerType(v_functype, 0))); + v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, ""); + + /* + * Jump to "error" or "done" steps depending of + * returned boolean value (TRUE means "done"). + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_sbool_const(0), + ""), + opblocks[op->d.subtrans.jump_error], + opblocks[op->d.subtrans.jump_done]); + +#ifndef LLVMJIT_SUBTRANS_RECURSIVE + /* + * Skip subexpression steps, which are compiled + * separately. + */ + opno = op->d.subtrans.jump_next - 1; +#endif + break; + } + case EEOP_JSON_CONSTRUCTOR: build_EvalXFunc(b, mod, "ExecEvalJsonConstructor", v_state, op, v_econtext); @@ -2428,12 +2563,16 @@ llvm_compile_expr(ExprState *state) * remapping overhead. */ { - CompiledExprState *cstate = palloc0(sizeof(CompiledExprState)); cstate->context = context; cstate->funcname = funcname; +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + if (recursive) + state->evalfunc = ExecRunCompileExprRecursive; + else +#endif state->evalfunc = ExecRunCompiledExpr; state->evalfunc_private = cstate; } @@ -2447,6 +2586,15 @@ llvm_compile_expr(ExprState *state) return true; } +/* + * JIT compile expression staring from the first step. + */ +bool +llvm_compile_expr(ExprState *state) +{ + return llvm_compile_expr_step(state, 0); +} + /* * Run compiled expression. * @@ -2475,6 +2623,56 @@ ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull) return func(state, econtext, isNull); } +#ifdef LLVMJIT_SUBTRANS_RECURSIVE +static Datum +ExecRunCompiledExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull) +{ + ExprStateEvalStepFunc func = state->evalfunc_private; + + return func(state, econtext, isNull, 0); +} + +static Datum +ExecRunCompileExprRecursive(ExprState *state, ExprContext *econtext, bool *isNull) +{ + CompiledExprState *cstate = state->evalfunc_private; + ExprStateEvalStepFunc func; + + CheckExprStillValid(state, econtext); + + llvm_enter_fatal_on_oom(); + func = (ExprStateEvalStepFunc) llvm_get_function(cstate->context, + cstate->funcname); + llvm_leave_fatal_on_oom(); + Assert(func); + + /* remove indirection via this function for future calls */ + state->evalfunc = ExecRunCompiledExprRecursive; + state->evalfunc_private = func; + + return func(state, econtext, isNull, 0); +} +#endif + +static void +ExecEvalSubtransCompiled(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + bool isnull; +#ifdef LLVMJIT_SUBTRANS_RECURSIVE + /* Recursively execute next opno */ + ExprStateEvalStepFunc func = state->evalfunc_private; + int opno = op - state->steps + 1; + + (void) func(state, econtext, &isnull, opno); +#else + /* Execute subexpression steps using compiled ExprState */ + ExprState *substate = op->d.subtrans.private_data; + + (void) ExecEvalExpr(substate, econtext, &isnull); +#endif +} + static LLVMValueRef BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b, LLVMModuleRef mod, FunctionCallInfo fcinfo, diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 37fe64654b6..bb96d46048f 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -49,6 +49,7 @@ PGFunction TypePGFunction; size_t TypeSizeT; bool TypeStorageBool; ExprStateEvalFunc TypeExprStateEvalFunc; +ExprStateEvalStepFunc TypeExprStateEvalStepFunc; ExecEvalSubroutine TypeExecEvalSubroutine; ExecEvalBoolSubroutine TypeExecEvalBoolSubroutine; @@ -133,6 +134,7 @@ void *referenced_functions[] = ExecEvalSysVar, ExecEvalWholeRowVar, ExecEvalXmlExpr, + ExecEvalSubtrans, ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, ExecEvalJsonExpr, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 51e8d93178e..a51232b84de 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -30,6 +30,8 @@ struct JsonConstructorExprState; #define EEO_FLAG_INTERPRETER_INITIALIZED (1 << 1) /* jump-threading is in use */ #define EEO_FLAG_DIRECT_THREADED (1 << 2) +/* expression contains EEOP_SUBTRANS steps */ +#define EEO_FLAG_HAVE_SUBTRANS (1 << 3) /* Typical API for out-of-line evaluation subroutines */ typedef void (*ExecEvalSubroutine) (ExprState *state, @@ -242,6 +244,7 @@ typedef enum ExprEvalOp EEOP_GROUPING_FUNC, EEOP_WINDOW_FUNC, EEOP_SUBPLAN, + EEOP_SUBTRANS, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, EEOP_JSONEXPR, @@ -680,6 +683,15 @@ typedef struct ExprEvalStep int setoff; } agg_trans; + /* for EEOP_SUBTRANS */ + struct + { + void *private_data; + int jump_done; + int jump_error; + int jump_next; + } subtrans; + /* for EEOP_JSON_CONSTRUCTOR */ struct { @@ -878,6 +890,9 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); +extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, + ExecEvalSubroutine func); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 01b1727fc09..d6976b92828 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -70,6 +70,11 @@ typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression, struct ExprContext *econtext, bool *isNull); +typedef Datum (*ExprStateEvalStepFunc) (struct ExprState *expression, + struct ExprContext *econtext, + bool *isNull, + int stepno); + /* Bits in ExprState->flags (see also execExpr.h for private flag bits): */ /* expression is for use with ExecQual() */ #define EEO_FLAG_IS_QUAL (1 << 0) -- 2.25.1
v7-0004-Split-JsonExpr-execution-into-steps.patch (text/x-patch, 64.0K) download From 5bbb775e1fb6b69d47fceae7c2c4060a309fb719 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov <n.gluhov@postgrespro.ru> Date: Fri, 12 Aug 2022 19:35:56 +0300 Subject: [PATCH v7 4/4] Split JsonExpr execution into steps Add test for errors in ON EMPTY Add test for errors in ON EMPTY Some improvements to JsonExpr evaluation * Store a pointer to the returning type input function FmgrInfo, not the struct itself, in JsonExprState * Evaluate various JsonExpr sub-expressions using parent ExprState These include PASSING args, ON ERROR, ON EMPTY default expressions. PASSING args are simple, because they all must be evaluated before the JSON path evaluation can occur anyway, so pushing those expressions to be uncondionally evaluated before the step to evaluate JsonExpr proper suffices. Evaluation of the ON ERROR and the ON EMPTY expressions is conditional on what JSON item pops out of the path evaluation (ExecEvalJson()), so needs more logic to gate the steps for their expressions that are pushed into the parent ExprState, rather than be imlemented using separate ExprStates. To that end, this moves the ON ERROR, ON EMPTY behavior decision logic out of ExecEvalJson() and ExecEvalJsonExpr() into a new function ExecEvalJsonExprBehavior(). * Final result coercion is now also computed as a separate step that gets pushed into the parent ExprState. Though given that any coercion evaluation errors must be handled the same way as the errors of JSON item evaluation, that is, to be handled according the ON ERROR behavior specification, a sub-transaction must be used around coercion evaluation, so the ExprState to use for the same must have to be a different one from the JsonExpr's. However, instead of using one ExprState for each JsonCoercion member of JsonItemCoercions, only one is used for the whole JsonItemCoercions. The individual JsonCoercion members (or their Exprs) are handled by steps pushed into that ExprState, of which only the one that applies to a given JSON item is chosen at the runtime and others "jumped" over. * Move the logic of deciding whether to use a sub-transaction for JSON path evaluation and subsequent coercion to ExecInitExprRec() instead of recomputing the same thing on every evaluation. Catch errors in JsonExpr ON EMPTY behavior Remove ExecEvalJsonExprContext Remove local variable Refactor JSonExpr execution: remove ItemCoercion step, use new Subtrans step Remove JsonExprBehavior step Use context for ExecInitJsonExpr() Simplify json item coercions Replace JsonExprCoerce steps with JsonExprIoCoerce and JsonExprPopulate steps Move and rename ExecEvalJsonNeedsSubTransaction(), consider more cases in it Inline ExecEvalJsonExpr() into ExecEvalJson() Move local variables Use LLVMBuildSwitch() in EEOP_JSONEXPR --- src/backend/executor/execExpr.c | 691 ++++++++++++++++---- src/backend/executor/execExprInterp.c | 630 +++++++----------- src/backend/jit/llvm/llvmjit_expr.c | 122 +++- src/backend/jit/llvm/llvmjit_types.c | 3 + src/backend/parser/parse_expr.c | 6 + src/backend/utils/adt/jsonpath_exec.c | 68 ++ src/include/executor/execExpr.h | 150 +++-- src/include/nodes/primnodes.h | 9 +- src/include/utils/jsonb.h | 2 + src/include/utils/jsonpath.h | 3 - src/test/regress/expected/jsonb_sqljson.out | 17 + src/test/regress/sql/jsonb_sqljson.sql | 7 + 12 files changed, 1085 insertions(+), 623 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 1c9782de15f..1aa843698ab 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -86,7 +86,8 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, FunctionCallInfo fcinfo, AggStatePerTrans pertrans, int transno, int setno, int setoff, bool ishash, bool nullcheck); - +static void ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull); static ExprState * ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params, @@ -178,7 +179,7 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) * ExecInitExprWithCaseValue: prepare an expression tree for execution * * This is the same as ExecInitExpr, except that a pointer to the value for - * CasTestExpr is passed here. + * CaseTestExpr is passed here. */ ExprState * ExecInitExprWithCaseValue(Expr *node, PlanState *parent, @@ -2561,149 +2562,8 @@ ExecInitExprRec(Expr *node, ExprState *state, case T_JsonExpr: { JsonExpr *jexpr = castNode(JsonExpr, node); - JsonExprState *jsestate; - JsonCoercion *result_coercion = jexpr->result_coercion; - ListCell *argexprlc; - ListCell *argnamelc; - - /* JSON_TABLE preudo-function returns context item as a result */ - if (jexpr->op == JSON_TABLE_OP) - { - ExecInitExprRec((Expr *) jexpr->formatted_expr, state, - resv, resnull); - break; - } - - jsestate = palloc0(sizeof(JsonExprState)); - jsestate->jsexpr = jexpr; - - scratch.opcode = EEOP_JSONEXPR; - scratch.d.jsonexpr.jsestate = jsestate; - - ExecInitExprRec((Expr *) jexpr->formatted_expr, state, - &jsestate->formatted_expr.value, - &jsestate->formatted_expr.isnull); - - ExecInitExprRec((Expr *) jexpr->path_spec, state, - &jsestate->pathspec.value, - &jsestate->pathspec.isnull); - - if (result_coercion) - { - jsestate->result_coercion.type = result_coercion->ctype; - - if (result_coercion->expr) - jsestate->result_coercion.estate = - ExecInitExprWithCaseValue((Expr *) result_coercion->expr, - state->parent, - &jsestate->coercion_expr.value, - &jsestate->coercion_expr.isnull); - - /* Skip coercion of NULLs for non-domain types */ - jsestate->skip_null_coercion = - getBaseType(jexpr->returning->typid) == jexpr->returning->typid; - } - else - jsestate->result_coercion.type = JSON_COERCION_NONE; - - /* Additional coercion of JSON_QUERY(OMIT QUOTES) */ - jsestate->io_coercion.type = JSON_COERCION_VIA_IO; - - if (jexpr->on_empty) - { - jsestate->default_on_empty = - ExecInitExpr((Expr *) jexpr->on_empty->default_expr, - state->parent); - - jsestate->default_on_empty_can_throw = - ExecEvalExprCanThrowErrors(jexpr->on_empty->default_expr); - } - - jsestate->default_on_error = - ExecInitExpr((Expr *) jexpr->on_error->default_expr, - state->parent); - - - /* Initialize type input info if I/O coercion can be used */ - if (jexpr->omit_quotes || - jsestate->result_coercion.type == JSON_COERCION_VIA_IO) - { - Oid typinput; - - /* lookup the result type's input function */ - getTypeInputInfo(jexpr->returning->typid, &typinput, - &jsestate->input.typioparam); - fmgr_info(typinput, &jsestate->input.func); - } - - jsestate->args = NIL; - - forboth(argexprlc, jexpr->passing_values, - argnamelc, jexpr->passing_names) - { - Expr *argexpr = (Expr *) lfirst(argexprlc); - String *argname = lfirst_node(String, argnamelc); - JsonPathVariableEvalContext *var = palloc(sizeof(*var)); - - var->name = pstrdup(argname->sval); - var->typid = exprType((Node *) argexpr); - var->typmod = exprTypmod((Node *) argexpr); - var->estate = ExecInitExpr(argexpr, state->parent); - var->econtext = NULL; - var->mcxt = NULL; - var->evaluated = false; - var->value = (Datum) 0; - var->isnull = true; - - jsestate->args = - lappend(jsestate->args, var); - } - - jsestate->cache = NULL; - - if (jexpr->coercions) - { - JsonCoercion **coercion; - JsonCoercionState *cstate; - Datum *caseval; - bool *casenull; - - jsestate->item_coercions = palloc0(sizeof(*jsestate->item_coercions)); - - caseval = &jsestate->coercion_expr.value; - casenull = &jsestate->coercion_expr.isnull; - - for (cstate = &jsestate->item_coercions->null, - coercion = &jexpr->coercions->null; - coercion <= &jexpr->coercions->composite; - coercion++, cstate++) - { - if (!*coercion) - /* Missing coercion here means missing cast */ - cstate->type = JSON_COERCION_ERROR; - else if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR) - { - Assert((*coercion)->expr); - cstate->estate = - ExecInitExprWithCaseValue((Expr *) (*coercion)->expr, - state->parent, - caseval, - casenull); - cstate->type = JSON_COERCION_VIA_EXPR; - } - else if ((*coercion)->ctype == JSON_COERCION_NONE) - cstate->type = JSON_COERCION_NONE; - else - { - /* There should not be other coercion types */ - Assert(0); - cstate->type = JSON_COERCION_ERROR; - } - } - } - - ExprEvalPushStep(state, &scratch); + ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull); break; } @@ -4296,3 +4156,546 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } + +static void +ExecInitJsonCoercionExpr(ExprState *state, Expr *coercion, + Datum *resv, bool *resnull) +{ + Datum *save_innermost_caseval; + bool *save_innermost_isnull; + + /* Push step(s) to compute expr using CaseTestValue. */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_isnull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec(coercion, state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_isnull; +} + +/* Context for passing common fields used for JsonExpr initialization */ +typedef struct ExecInitJsonContext +{ + ExprState *state; + ExprEvalStep *scratch; + JsonExprState *jsestate; + Datum *resv; + bool *resnull; + List *adjust_jumps; /* list of step numbers of jump_done steps */ + List *adjust_subtrans; /* list of step numbers of subtrans steps */ +} ExecInitJsonContext; + +static int +ExecInitJsonCoercion(ExecInitJsonContext *cxt, Node *expr, + bool need_subtrans) +{ + ExprState *state = cxt->state; + ExprEvalStep *scratch = cxt->scratch; + int step_off = state->steps_len; + + if (IsA(expr, JsonCoercion)) + { + JsonCoercion *coercion = (JsonCoercion *) expr; + + if (coercion->ctype == JSON_COERCION_NONE) + return -1; /* no coercion is needed */ + } + + /* Check whether expression really needs sub-transactions */ + if (need_subtrans) + need_subtrans = ExecEvalExprCanThrowErrors(expr); + + /* Emit EEOP_JSONEXPR_SUBTRANS step */ + if (need_subtrans) + { + cxt->adjust_subtrans = + lappend_int(cxt->adjust_subtrans, state->steps_len); + + state->flags |= EEO_FLAG_HAVE_SUBTRANS; + scratch->opcode = EEOP_SUBTRANS; + scratch->d.subtrans.jump_next = -1; /* computed later */ + scratch->d.subtrans.jump_done = -1; /* computed later */ + scratch->d.subtrans.jump_error = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + } + + /* Emit expression step, if needed. */ + if (IsA(expr, JsonCoercion)) + { + JsonCoercion *coercion = (JsonCoercion *) expr; + + if (coercion->ctype == JSON_COERCION_VIA_EXPR) + /* Push step(s) to compute coercion->expr */ + ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr, + cxt->resv, cxt->resnull); + else if (coercion->ctype == JSON_COERCION_VIA_POPULATE) + { + /* Push step for coercion using json_populate_type() */ + scratch->opcode = EEOP_JSONEXPR_POPULATE; + scratch->d.jsonexpr_populate.jsexpr = cxt->jsestate->jsexpr; + scratch->d.jsonexpr_populate.cache = NULL; + ExprEvalPushStep(state, scratch); + } + else if (coercion->ctype == JSON_COERCION_VIA_IO) + { + /* Push step for coercion via I/O */ + FmgrInfo *finfo; + Oid typinput; + Oid typioparam; + JsonReturning *returning = cxt->jsestate->jsexpr->returning; + + /* lookup the result type's input function */ + getTypeInputInfo(returning->typid, &typinput, + &typioparam); + finfo = palloc0(sizeof(FmgrInfo)); + fmgr_info(typinput, finfo); + + scratch->opcode = EEOP_JSONEXPR_IOCOERCE; + scratch->d.jsonexpr_iocoerce.finfo = finfo; + scratch->d.jsonexpr_iocoerce.typioparam = typioparam; + scratch->d.jsonexpr_iocoerce.typmod = returning->typmod; + ExprEvalPushStep(state, scratch); + } + else + { + Assert(0); /* No-coercion case must be checked at the beginning */ + } + } + else + /* Push step(s) to compute expr */ + ExecInitExprRec((Expr *) expr, state, cxt->resv, cxt->resnull); + + if (need_subtrans) + { + /* Step for returning back to EEOP_JSONEXPR_SUBTRANS step. */ + scratch->opcode = EEOP_DONE; + ExprEvalPushStep(state, scratch); + + state->steps[step_off].d.subtrans.jump_next = state->steps_len; + } + else + { + /* + * Emit JUMP step to jump to end of JsonExpr code, because + * evaluating the coercion or default expression gives the + * final result and there's nothing more to do. + */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + + /* + * Don't know address for that jump yet, compute once the whole + * JsonExpr is built. So remember JUMP step address to set + * the actual jump target addreess. + */ + cxt->adjust_jumps = + lappend_int(cxt->adjust_jumps, state->steps_len - 1); + } + + return step_off; +} + +/* + * Push steps to evaluate a JsonItemCoercions, which contains the state + * for evaluating all possible coercions that may need to be applied to + * a JSON item coming from evaluating the parent JsonExpr. + */ +static JsonItemCoercionsState * +ExecInitJsonItemCoercions(ExecInitJsonContext *cxt, + JsonItemCoercions *coercions, + bool need_subtrans) +{ + JsonItemCoercionsState *jcstate = palloc0(sizeof(*jcstate)); + JsonCoercion **coercion; + int *step; + + /* Push the steps of individual coercion's expression, if needed. */ + for (step = &jcstate->null, + coercion = &coercions->null; + coercion <= &coercions->composite; + coercion++, step++) + { + if (*coercion) + { + if ((*coercion)->ctype == JSON_COERCION_VIA_EXPR) + *step = ExecInitJsonCoercion(cxt, (Node *) *coercion, + need_subtrans); + else if ((*coercion)->ctype == JSON_COERCION_NONE) + *step = 0; /* No coercion is needed */ + else + { + Assert(0); /* There should not be other coercion types */ + *step = -1; + } + } + else + *step = -1; /* This means there is no cast to output type */ + } + + return jcstate; +} + +/* + * Evaluate a JSON error/empty behavior result. + */ +static Datum +ExecJsonBehavior(JsonBehavior *behavior, bool *is_null) +{ + *is_null = false; + + switch (behavior->btype) + { + case JSON_BEHAVIOR_EMPTY_ARRAY: + return JsonbPGetDatum(JsonbMakeEmptyArray()); + + case JSON_BEHAVIOR_EMPTY_OBJECT: + return JsonbPGetDatum(JsonbMakeEmptyObject()); + + case JSON_BEHAVIOR_TRUE: + return BoolGetDatum(true); + + case JSON_BEHAVIOR_FALSE: + return BoolGetDatum(false); + + case JSON_BEHAVIOR_NULL: + case JSON_BEHAVIOR_UNKNOWN: + case JSON_BEHAVIOR_EMPTY: + *is_null = true; + return (Datum) 0; + + case JSON_BEHAVIOR_DEFAULT: + /* Always handled in the caller. */ + Assert(false); + return (Datum) 0; + + default: + elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); + return (Datum) 0; + } +} + +/* + * Push steps to evaluate ON ERROR/EMPTY behavior: it is either DEFAULT + * expression or constant with optional coercion. + * + * Returned first step number, or -1 if no steps. + */ +static int +ExecInitJsonBehavior(ExecInitJsonContext *cxt, JsonBehavior *behavior, + bool need_subtrans, int *jump_coercion_step_off) +{ + if (!behavior || behavior->btype == JSON_BEHAVIOR_ERROR) + return -1; + + if (behavior->default_expr) + return ExecInitJsonCoercion(cxt, behavior->default_expr, + need_subtrans); + else + { + ExprState *state = cxt->state; + ExprEvalStep *scratch = cxt->scratch; + JsonCoercion *coercion = cxt->jsestate->jsexpr->result_coercion; + bool isnull; + Datum val = ExecJsonBehavior(behavior, &isnull); + int step_off = state->steps_len; + + /* Step to emit resulting constant */ + scratch->opcode = EEOP_CONST; + scratch->d.constval.value = val; + scratch->d.constval.isnull = isnull; + ExprEvalPushStep(state, scratch); + + /* Step to the coercion or the end of expression */ + scratch->opcode = EEOP_JUMP; + scratch->d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, scratch); + + /* + * If it is a jump to coercion, return it through the special + * output parameter, else append it to jump_done list. + */ + if (coercion && coercion->ctype != JSON_COERCION_NONE) + *jump_coercion_step_off = state->steps_len - 1; + else + cxt->adjust_jumps = + lappend_int(cxt->adjust_jumps, state->steps_len - 1); + + return step_off; + } +} + +/* + * Push steps to evaluate a JsonExpr and its various subsidiary + * expressions. + */ +static void +ExecInitJsonExpr(ExprEvalStep *scratch, JsonExpr *jexpr, ExprState *state, + Datum *resv, bool *resnull) +{ + ExecInitJsonContext cxt; + JsonExprState *jsestate; + JsonExprPreEvalState *pre_eval; + ExprEvalStep *as; + ListCell *argexprlc; + ListCell *argnamelc; + ListCell *lc; + int skip_step_off; + int execpath_step_off; + int onempty_step_off; + int onerror_step_off; + int onempty_jump_coercion_step_off = -1; + int onerror_jump_coercion_step_off = -1; + int coercion_step_off = -1; + int coercion_subtrans_step_off = -1; + int io_coercion_step_off = -1; + int done_off; + bool need_subtrans; + bool skip_null_coercion; + + /* JSON_TABLE preudo-function returns context item as a result */ + if (jexpr->op == JSON_TABLE_OP) + { + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, resv, resnull); + return; + } + + jsestate = palloc0(sizeof(JsonExprState)); + jsestate->jsexpr = jexpr; + pre_eval = &jsestate->pre_eval; + + cxt.state = state; + cxt.scratch = scratch; + cxt.jsestate = jsestate; + cxt.resv = resv; + cxt.resnull = resnull; + cxt.adjust_jumps = NIL; + cxt.adjust_subtrans = NIL; + + /* + * Set if coercion or DEFAULT ON EMPTY expression, which runs + * separately from path evaluation and whose errors must be caught + * and handled per ON ERROR behavior, must use a sub-transaction. + */ + need_subtrans = jexpr->on_error->btype != JSON_BEHAVIOR_ERROR; + + /* + * Add steps to compute formatted_expr, pathspec, and PASSING arg + * expressions as things that must be evaluated *before* the actual + * JSON path expression. + */ + + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + &pre_eval->formatted_expr.value, + &pre_eval->formatted_expr.isnull); + + ExecInitExprRec((Expr *) jexpr->path_spec, state, + &pre_eval->pathspec.value, + &pre_eval->pathspec.isnull); + + /* + * Before pushing steps for PASSING args, push a step to decide + * whether to skip evaluating the args and the JSON path expression + * depending on whether either of formatted_expr and pathspec is + * NULL. + */ + scratch->opcode = EEOP_JSONEXPR_SKIP; + scratch->d.jsonexpr_skip.jsestate = jsestate; + skip_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* PASSING args. */ + jsestate->pre_eval.args = NIL; + forboth(argexprlc, jexpr->passing_values, + argnamelc, jexpr->passing_names) + { + Expr *argexpr = (Expr *) lfirst(argexprlc); + String *argname = lfirst_node(String, argnamelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->name = pstrdup(argname->sval); + var->typid = exprType((Node *) argexpr); + var->typmod = exprTypmod((Node *) argexpr); + + /* + * A separate ExprState is not necessary for these expressions + * when being evaluated for a JsonExpr, like in this case, + * because they will evaluated as the steps of the JsonExpr. + */ + var->estate = NULL; + var->econtext = NULL; + var->mcxt = NULL; + + /* + * Mark these as always evaluated because they must have been + * evaluated before JSON path evaluation begins, because we + * haven't pushed the step for the latter yet. + */ + var->evaluated = true; + + ExecInitExprRec((Expr *) argexpr, state, &var->value, &var->isnull); + + pre_eval->args = lappend(pre_eval->args, var); + } + + /* Step for the actual JSON path evaluation. */ + scratch->opcode = EEOP_JSONEXPR_PATH; + scratch->d.jsonexpr.jsestate = jsestate; + execpath_step_off = state->steps_len; + ExprEvalPushStep(state, scratch); + + /* + * Push steps to control the evaluation of expressions based + * on the result of JSON path evaluation. + */ + + /* Step(s) to evaluate ON EMPTY behavior */ + onempty_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_empty, + need_subtrans, + &onempty_jump_coercion_step_off); + + /* Step(s) to evaluate ON ERROR behavior */ + onerror_step_off = ExecInitJsonBehavior(&cxt, jexpr->on_error, + false, /* sub-transactions are not needed here */ + &onerror_jump_coercion_step_off); + + Assert(!need_subtrans || onerror_step_off >= 0); + + /* + * Initialize coercion expression. + */ + if (jexpr->result_coercion) + { + /* + * Don't use a sub-transaction for coercing the ON ERROR + * expression. + */ + coercion_step_off = + ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, false); + + /* + * Generate also coercion expression wrapped into a + * sub-transaction, if needed. + */ + if (need_subtrans) + coercion_subtrans_step_off = + ExecInitJsonCoercion(&cxt, (Node *) jexpr->result_coercion, true); + } + + /* + * Emit I/O coercion if OMIT QUOTES is used and it was not already + * emitted. + */ + if (jexpr->omit_quotes && + (!jexpr->result_coercion || jexpr->result_coercion->ctype != JSON_COERCION_VIA_IO)) + { + JsonCoercion io_coercion = {0}; + + io_coercion.type = T_JsonCoercion; + io_coercion.ctype = JSON_COERCION_VIA_IO; + + io_coercion_step_off = + ExecInitJsonCoercion(&cxt, (Node *) &io_coercion, need_subtrans); + } + + /* + * Initialize "SQL/JSON item type => SQL type" coercion expressions. + */ + if (jexpr->coercions) + jsestate->item_coercions = + ExecInitJsonItemCoercions(&cxt, jexpr->coercions, need_subtrans); + + /* Skip coercion of NULLs for non-domain types */ + skip_null_coercion = + getBaseType(jexpr->returning->typid) == jexpr->returning->typid; + + done_off = state->steps_len; + + /* Redirect missing steps to the end step */ + if (onerror_step_off < 0) + onerror_step_off = done_off; + + if (onempty_step_off < 0) + onempty_step_off = done_off; + + if (coercion_step_off < 0) + coercion_step_off = done_off; + + if (coercion_subtrans_step_off < 0) + coercion_subtrans_step_off = coercion_step_off; + + if (io_coercion_step_off < 0) + io_coercion_step_off = done_off; + + /* + * Adjust jump target addresses in various post-eval steps now that + * we have all the steps in place. + */ + + /* EEOP_JSONEXPR_PATH */ + as = &state->steps[execpath_step_off]; + as->d.jsonexpr.jump_done = done_off; + as->d.jsonexpr.jump_onerror = onerror_step_off; + as->d.jsonexpr.jump_onempty = onempty_step_off; + as->d.jsonexpr.jump_coercion = coercion_step_off; + as->d.jsonexpr.jump_coercion_subtrans = coercion_subtrans_step_off; + as->d.jsonexpr.jump_coercion_via_io = io_coercion_step_off; + as->d.jsonexpr.jump_coercion_null = + skip_null_coercion ? done_off : coercion_step_off; + as->d.jsonexpr.jump_coercion_null_subtrans = + skip_null_coercion ? done_off : coercion_subtrans_step_off; + + /* EEOP_JSONEXPR_SKIP */ + as = &state->steps[skip_step_off]; + as->d.jsonexpr_skip.jump_coercion = coercion_step_off; + + /* Adjust jump to ON EMPTY coercion, if any */ + if (onempty_jump_coercion_step_off >= 0) + { + as = &state->steps[onempty_jump_coercion_step_off]; + as->d.jump.jumpdone = + jexpr->on_empty->btype == JSON_BEHAVIOR_NULL && + skip_null_coercion ? done_off : coercion_subtrans_step_off; + } + + /* Adjust jump to ON ERROR coercion, if any */ + if (onerror_jump_coercion_step_off >= 0) + { + as = &state->steps[onerror_jump_coercion_step_off]; + as->d.jump.jumpdone = + jexpr->on_error->btype == JSON_BEHAVIOR_NULL && + skip_null_coercion ? done_off : coercion_step_off; + } + + /* + * EEOP_JUMP steps added after default expressions and coercions + * should jump to the expression end. + */ + foreach(lc, cxt.adjust_jumps) + { + as = &state->steps[lfirst_int(lc)]; + as->d.jump.jumpdone = done_off; + } + + /* + * Adjust EEOP_SUBTRANS jumps to point to the expression end and to + * ON ERROR handler. + */ + foreach(lc, cxt.adjust_subtrans) + { + as = &state->steps[lfirst_int(lc)]; + as->d.subtrans.jump_done = done_off; + as->d.subtrans.jump_error = onerror_step_off; + } + + /* + * Verify that no subtransaction nodes were generated if + * ExecJsonExprNeedsSubTransaction() returns false. + */ + Assert(cxt.adjust_subtrans == NIL || + ExecEvalJsonNeedsSubTransaction(jexpr)); +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 66898198f33..af0049abd33 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -494,7 +494,10 @@ ExecInterpExprStep(ExprState *state, int stepno, &&CASE_EEOP_SUBTRANS, &&CASE_EEOP_JSON_CONSTRUCTOR, &&CASE_EEOP_IS_JSON, - &&CASE_EEOP_JSONEXPR, + &&CASE_EEOP_JSONEXPR_SKIP, + &&CASE_EEOP_JSONEXPR_PATH, + &&CASE_EEOP_JSONEXPR_IOCOERCE, + &&CASE_EEOP_JSONEXPR_POPULATE, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1855,10 +1858,32 @@ ExecInterpExprStep(ExprState *state, int stepno, EEO_NEXT(); } - EEO_CASE(EEOP_JSONEXPR) + EEO_CASE(EEOP_JSONEXPR_PATH) { /* too complex for an inline implementation */ - ExecEvalJsonExpr(state, op, econtext); + EEO_JUMP(ExecEvalJsonExpr(state, op)); + } + + EEO_CASE(EEOP_JSONEXPR_SKIP) + { + /* too complex for an inline implementation */ + if (ExecEvalJsonExprSkip(state, op)) + EEO_JUMP(op->d.jsonexpr_skip.jump_coercion); + else + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR_IOCOERCE) + { + /* too complex for an inline implementation */ + ExecEvalJsonExprIOCoerce(state, op); + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR_POPULATE) + { + /* too complex for an inline implementation */ + ExecEvalJsonExprPopulate(state, op, econtext); EEO_NEXT(); } @@ -4798,252 +4823,6 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, *op->resnull = isnull; } -/* - * Evaluate a JSON error/empty behavior result. - */ -static Datum -ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, - ExprState *default_estate, bool *is_null) -{ - *is_null = false; - - switch (behavior->btype) - { - case JSON_BEHAVIOR_EMPTY_ARRAY: - return JsonbPGetDatum(JsonbMakeEmptyArray()); - - case JSON_BEHAVIOR_EMPTY_OBJECT: - return JsonbPGetDatum(JsonbMakeEmptyObject()); - - case JSON_BEHAVIOR_TRUE: - return BoolGetDatum(true); - - case JSON_BEHAVIOR_FALSE: - return BoolGetDatum(false); - - case JSON_BEHAVIOR_NULL: - case JSON_BEHAVIOR_UNKNOWN: - case JSON_BEHAVIOR_EMPTY: - *is_null = true; - return (Datum) 0; - - case JSON_BEHAVIOR_DEFAULT: - return ExecEvalExpr(default_estate, econtext, is_null); - - default: - elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); - return (Datum) 0; - } -} - - -typedef struct EvalJsonSubtransState -{ - MemoryContext oldcontext; - ResourceOwner oldowner; -} EvalJsonSubtransState; - -static void -ExecEvalJsonStartSubtrans(EvalJsonSubtransState *state) -{ - /* - * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and - * execute the corresponding ON ERROR behavior then. - */ - state->oldcontext = CurrentMemoryContext; - state->oldowner = CurrentResourceOwner; - - BeginInternalSubTransaction(NULL); - - /* Want to execute expressions inside function's memory context */ - /* - * AFIXME: I don't think that's OK, there might be lots of leaked memory - * etc. - */ - MemoryContextSwitchTo(state->oldcontext); -} - -static void -ExecEvalJsonEndSubtrans(EvalJsonSubtransState *state) -{ - /* Commit the inner transaction, return to outer xact context */ - ReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(state->oldcontext); - CurrentResourceOwner = state->oldowner; -} - -static void -ExecEvalJsonCatchError(EvalJsonSubtransState *state) -{ - ErrorData *edata; - int ecategory; - - /* Save error info in oldcontext */ - MemoryContextSwitchTo(state->oldcontext); - edata = CopyErrorData(); - FlushErrorState(); - - /* Abort the inner transaction */ - RollbackAndReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(state->oldcontext); - CurrentResourceOwner = state->oldowner; - - ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode); - - if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data - * errors */ - ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */ - { - ReThrowError(edata); - } -} - -/* - * If `error` is not NULL (we need to catch errors), execute the - * statement passed in the third argument inside a subtransaction, - * otherwise do it outside. In the subtrans case an error classified as - * being catchable by ExecEvalJsonCatchError() will set `error` to true. - */ -#define JSE_OPT_SUBTRANS(error, jexpr, res, ...) \ - do { \ - bool *error_ = (error); \ - \ - if (!error_) \ - { \ - res = __VA_ARGS__; \ - } \ - else \ - { \ - EvalJsonSubtransState substate = {0}; \ - \ - /* Check correctness of ExecEvalJsonNeedsSubTransaction() */ \ - AssertMacro(ExecEvalJsonNeedsSubTransaction(jexpr)); \ - \ - ExecEvalJsonStartSubtrans(&substate); \ - \ - PG_TRY(); \ - { \ - res = __VA_ARGS__; \ - ExecEvalJsonEndSubtrans(&substate); \ - } \ - PG_CATCH(); \ - { \ - ExecEvalJsonCatchError(&substate); \ - \ - res = (Datum) 0; \ - *error_ = true; \ - } \ - PG_END_TRY(); \ - } \ - } while (0) - -/* Execute one of possible coercions or report an error if there is no cast */ -static Datum -ExecEvalJsonExprCoercion(JsonExprState *jsestate, ExprContext *econtext, - JsonCoercionState *coercion, - Datum res, bool *resnull, bool *error) -{ - switch (coercion->type) - { - case JSON_COERCION_NONE: - return (Datum) res; - - case JSON_COERCION_VIA_EXPR: - jsestate->coercion_expr.value = res; - jsestate->coercion_expr.isnull = *resnull; - return ExecEvalExpr(coercion->estate, econtext, resnull); - - case JSON_COERCION_VIA_IO: - { - /* Strip quotes from jsonb strings and call typinput() */ - char *str; - - if (*resnull) - str = NULL; - else - str = JsonbUnquote(DatumGetJsonbP(res)); - - return InputFunctionCall(&jsestate->input.func, str, - jsestate->input.typioparam, - jsestate->jsexpr->returning->typmod); - } - - case JSON_COERCION_VIA_POPULATE: - return json_populate_type(res, JSONBOID, - jsestate->jsexpr->returning->typid, - jsestate->jsexpr->returning->typmod, - &jsestate->cache, - econtext->ecxt_per_query_memory, - resnull); - - default: - Assert(coercion->type == JSON_COERCION_ERROR); - - /* Report an error if there is no cast to the output type */ - if (!error) - ereport(ERROR, - (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), - errmsg("SQL/JSON item cannot be cast to target type"))); - - *error = true; - *resnull = true; - return (Datum) 0; - } -} - -/* - * Evaluate a JSON path variable caching computed value. - */ -int -EvalJsonPathVar(void *cxt, char *varName, int varNameLen, - JsonbValue *val, JsonbValue *baseObject) -{ - JsonPathVariableEvalContext *var = NULL; - List *vars = cxt; - ListCell *lc; - int id = 1; - - if (!varName) - return list_length(vars); - - foreach(lc, vars) - { - var = lfirst(lc); - - if (!strncmp(var->name, varName, varNameLen)) - break; - - var = NULL; - id++; - } - - if (!var) - return -1; - - if (!var->evaluated) - { - MemoryContext oldcxt = var->mcxt ? - MemoryContextSwitchTo(var->mcxt) : NULL; - - var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); - var->evaluated = true; - - if (oldcxt) - MemoryContextSwitchTo(oldcxt); - } - - if (var->isnull) - { - val->type = jbvNull; - return 0; - } - - JsonItemFromDatum(var->value, var->typid, var->typmod, val); - - *baseObject = *val; - return id; -} - /* * Check whether we need to override default coercion in * JSON_QUERY(OMIT QUOTES) case. @@ -5064,78 +4843,84 @@ ExecJsonQueryNeedsIOCoercion(JsonExpr *jsexpr, Datum res, bool isnull) } /* - * Prepare SQL/JSON item coercion to the JSON_VALUE output type. - * Returned a coercion status, a datum of the corresponding SQL type - * and a pointer to selected coercion state. + * Prepare SQL/JSON item coercion to the output type. Returned a datum + * of the corresponding SQL type and pointers to the coercion and its + * state. */ static Datum -ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item, - JsonCoercionState **coercion, bool *resnull) +ExecPrepareJsonValueCoercion(JsonbValue *item, ExprEvalStep *op, + JsonItemCoercionsState *steps, int *jump) { - struct JsonItemsCoercionStates *coercions = jsestate->item_coercions; - JsonbValue buf; + JsonbValue buf; - *resnull = false; - - if (!coercions) + /* + * Special case for json and jsonb types for which we don't have + * casts like numeric::jsonb etc. + */ + if (!steps) { + Jsonb *jb = JsonbValueToJsonb(item); + /* - * Special case for json[b] output types. Simply use - * default coercion from jsonb to the output type. + * Use result coercion from json[b] to the output + * type when casting to json[b] types or their + * domains. */ - return JsonbPGetDatum(JsonbValueToJsonb(item)); + + *jump = op->d.jsonexpr.jump_coercion_subtrans; + return JsonbPGetDatum(jb);; } - /* Use coercion from SQL/JSON item type to the output type */ + /* Extract scalar from binary scalar presudo-array */ if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) { - bool res PG_USED_FOR_ASSERTS_ONLY; + bool is_scalar PG_USED_FOR_ASSERTS_ONLY; + + is_scalar = JsonbExtractScalar(item->val.binary.data, &buf); + Assert(is_scalar); - res = JsonbExtractScalar(item->val.binary.data, &buf); item = &buf; - Assert(res); } /* get coercion state reference and datum of the corresponding SQL type */ switch (item->type) { case jbvNull: - Assert(0); /* must be handled by the caller */ - *coercion = &coercions->null; - *resnull = true; + *jump = steps->null; return (Datum) 0; case jbvString: - *coercion = &coercions->string; - return PointerGetDatum(cstring_to_text_with_len(item->val.string.val, - item->val.string.len)); + *jump = steps->string; + return *jump < 0 ? (Datum) 0 : + PointerGetDatum(cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); case jbvNumeric: - *coercion = &coercions->numeric; + *jump = steps->numeric; return NumericGetDatum(item->val.numeric); case jbvBool: - *coercion = &coercions->boolean; + *jump = steps->boolean; return BoolGetDatum(item->val.boolean); case jbvDatetime: switch (item->val.datetime.typid) { case DATEOID: - *coercion = &coercions->date; + *jump = steps->date; break; case TIMEOID: - *coercion = &coercions->time; + *jump = steps->time; break; case TIMETZOID: - *coercion = &coercions->timetz; + *jump = steps->timetz; break; case TIMESTAMPOID: - *coercion = &coercions->timestamp; + *jump = steps->timestamp; break; case TIMESTAMPTZOID: - *coercion = &coercions->timestamptz; + *jump = steps->timestamptz; break; default: elog(ERROR, "unexpected jsonb datetime type oid %u", @@ -5149,8 +4934,9 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item, case jbvObject: case jbvBinary: Assert(0); /* non-scalars must be rejected by JsonPathValue() */ - *coercion = &coercions->composite; - return JsonbPGetDatum(JsonbValueToJsonb(item)); + *jump = steps->composite; + return *jump < 0 ? (Datum) 0 : + JsonbPGetDatum(JsonbValueToJsonb(item)); default: elog(ERROR, "unexpected jsonb value type %d", item->type); @@ -5158,129 +4944,159 @@ ExecPrepareJsonValueCoercion(JsonExprState *jsestate, JsonbValue *item, } } -static Datum -ExecEvalJsonExprInternal(JsonExprState *jsestate, ExprContext *econtext, - JsonPath *path, Datum item, bool *resnull, - bool *error) +/* + * Evaluate a SQL/JSON function. + */ +int +ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op) { + JsonExprState *jsestate = op->d.jsonexpr.jsestate; + JsonExprPreEvalState *pre_eval = &jsestate->pre_eval; JsonExpr *jexpr = jsestate->jsexpr; - JsonCoercionState *coercion = &jsestate->result_coercion; - bool empty = false; + Datum item = pre_eval->formatted_expr.value; + JsonPath *path = DatumGetJsonPathP(pre_eval->pathspec.value); + bool catch_errors = + jexpr->on_error->btype != JSON_BEHAVIOR_ERROR; Datum res = (Datum) 0; + bool empty = false; + bool error = false; + bool *p_error = catch_errors ? &error : NULL; - *resnull = true; + *op->resvalue = (Datum) 0; + *op->resnull = true; /* until we get a result */ switch (jexpr->op) { case JSON_QUERY_OP: - res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error, - jsestate->args); - if (error && *error) - return (Datum) 0; + res = JsonPathQuery(item, path, jexpr->wrapper, &empty, + p_error, pre_eval->args); + + if (error) + return op->d.jsonexpr.jump_onerror; if (empty) break; - *resnull = !DatumGetPointer(res); + *op->resnull = !DatumGetPointer(res); + *op->resvalue = res; /* Override default coercion in OMIT QUOTES case */ - if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *resnull)) - coercion = &jsestate->io_coercion; + if (ExecJsonQueryNeedsIOCoercion(jexpr, res, *op->resnull)) + return op->d.jsonexpr.jump_coercion_via_io; - break; + /* Maybe skip coercion of NULLs for non-domain types */ + if (*op->resnull) + return op->d.jsonexpr.jump_coercion_null_subtrans; + + /* Evaluate coercion expression inside subtransaction */ + return op->d.jsonexpr.jump_coercion_subtrans; case JSON_VALUE_OP: { - JsonbValue *jbv = JsonPathValue(item, path, &empty, error, - jsestate->args); + int jump_item_coercion; + JsonbValue *jbv = JsonPathValue(item, path, &empty, + p_error, pre_eval->args); - if (error && *error) - return (Datum) 0; + if (error) + return op->d.jsonexpr.jump_onerror; - if (!jbv) /* NULL or empty */ + if (empty) break; + /* + * Execute coercion for NULL inside subtransaction, it + * can be skiped for non-domain types. + */ + if (!jbv) + return op->d.jsonexpr.jump_coercion_null_subtrans; + + Assert(!empty); Assert(jbv->type != jbvNull); - res = ExecPrepareJsonValueCoercion(jsestate, jbv, - &coercion, resnull); - break; + /* + * Prepare coercion from SQL/JSON item type to the + * output SQL type. + */ + res = ExecPrepareJsonValueCoercion(jbv, op, + jsestate->item_coercions, + &jump_item_coercion); + + *op->resvalue = res; + *op->resnull = false; + + /* Jump to zero here means jump to the end. */ + if (jump_item_coercion == 0) + return op->d.jsonexpr.jump_done; + + /* + * Error out if no cast exists to coerce SQL/JSON item + * to the the output type. + */ + if (jump_item_coercion < 0) + { + *op->resnull = true; + + if (catch_errors) + return op->d.jsonexpr.jump_onerror; + + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE), + errmsg("SQL/JSON item cannot be cast to target type"))); + } + + /* Coerce using a specific coercion expression */ + return jump_item_coercion; } case JSON_EXISTS_OP: { bool exists = JsonPathExists(item, path, - jsestate->args, - error); + pre_eval->args, + p_error); - *resnull = error && *error; - res = BoolGetDatum(exists); - break; /* always use result coercion */ - } + *op->resnull = error; + *op->resvalue = BoolGetDatum(exists); - default: - elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); - return (Datum) 0; - } + if (error) + return op->d.jsonexpr.jump_onerror; - if (empty) - { - Assert(jexpr->on_empty); /* it is not JSON_EXISTS */ + /* + * Execute coercion of NULL inside subtransaction, it + * can be skiped for non-domain types. + */ + if (*op->resnull) + return op->d.jsonexpr.jump_coercion_null_subtrans; - if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) - { - if (error) - { - *error = true; - return (Datum) 0; + /* Evaluate coercion inside subtransaction, if any */ + return op->d.jsonexpr.jump_coercion_subtrans; } - ereport(ERROR, - (errcode(ERRCODE_NO_SQL_JSON_ITEM), - errmsg("no SQL/JSON item"))); - } - - if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT) - { - /* - * Execute DEFAULT expression in a subtransation if needed. - * Coercion is not needed here, because expression is - * already coerced to the target type by the parser. - */ - JSE_OPT_SUBTRANS(jsestate->default_on_empty_can_throw ? error : NULL, - jexpr, res, - ExecEvalExpr(jsestate->default_on_empty, - econtext, resnull)); - return res; - } - - /* Execute ON EMPTY behavior, coercion is default */ - res = ExecEvalJsonBehavior(econtext, jexpr->on_empty, - jsestate->default_on_empty, - resnull); + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); + return op->d.jsonexpr.jump_done; } - /* - * Execute resulting coercion in a subtransaction if `error` != NULL. - * In case of cast errors, there is no need to use subtransaction. - */ - if (coercion->type != JSON_COERCION_NONE) + /* Execute ON EMPTY behavior if no SQL/JSON items were found */ + Assert(empty); + Assert(jexpr->on_empty); /* it is not JSON_EXISTS */ + + if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) { - /* Skip coercion of NULLs for non-domain types */ - if (*resnull && jsestate->skip_null_coercion) - return (Datum) 0; + /* Throw an error or jump to ON ERROR handler */ + if (catch_errors) + return op->d.jsonexpr.jump_onerror; - JSE_OPT_SUBTRANS(coercion->type != JSON_COERCION_ERROR ? error : NULL, - jexpr, res, - ExecEvalJsonExprCoercion(jsestate, econtext, - coercion, res, - resnull, error)); + ereport(ERROR, + (errcode(ERRCODE_NO_SQL_JSON_ITEM), + errmsg("no SQL/JSON item"))); } - return res; + /* Evaluate ON EMPTY behavior */ + return op->d.jsonexpr.jump_onempty; } +/* Skip calling ExecEvalJson() on a JsonExpr? */ bool ExecEvalExprCanThrowErrors(Node *expr) { @@ -5319,66 +5135,60 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) return false; /* subtransactios should not be used */ } -/* ---------------------------------------------------------------- - * ExecEvalJsonExpr - * ---------------------------------------------------------------- - */ -void -ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +bool +ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op) { - JsonExprState *jsestate = op->d.jsonexpr.jsestate; - JsonExpr *jexpr = jsestate->jsexpr; - Datum item; - Datum res; - JsonPath *path; - ListCell *lc; - bool error = false; - bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR; + JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate; - if (jsestate->formatted_expr.isnull || jsestate->pathspec.isnull) + /* + * Skip if either of the input expressions has turned out to be + * NULL, though do execute domain checks for NULLs, which are + * handled by the coercion step. + */ + if (jsestate->pre_eval.formatted_expr.isnull || + jsestate->pre_eval.pathspec.isnull) { - *op->resnull = true; *op->resvalue = (Datum) 0; - - /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(jsestate, econtext, - &jsestate->result_coercion, - *op->resvalue, op->resnull, NULL); - - Assert(*op->resnull); - return; + *op->resnull = true; + return true; } - item = jsestate->formatted_expr.value; - path = DatumGetJsonPathP(jsestate->pathspec.value); + /* + * Go evaluate the PASSING args if any and subsequently JSON path + * itself. + */ + return false; +} + +/* Apply I/O coercion to a JSON item */ +void +ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op) +{ + /* Strip quotes and call typinput function */ + char *str = NULL; - /* reset JSON path variable contexts */ - foreach(lc, jsestate->args) + if (!*op->resnull) { - JsonPathVariableEvalContext *var = lfirst(lc); + Jsonb *jb = DatumGetJsonbP(*op->resvalue); - var->econtext = econtext; - var->evaluated = false; + str = JsonbUnquote(jb); } - res = ExecEvalJsonExprInternal(jsestate, econtext, - path, item, op->resnull, - throwErrors ? NULL : &error); - if (error) - { - Assert(jexpr->on_error->btype != JSON_BEHAVIOR_ERROR); - - /* Execute ON ERROR behavior */ - res = ExecEvalJsonBehavior(econtext, jexpr->on_error, - jsestate->default_on_error, - op->resnull); - - /* result is already coerced in DEFAULT behavior case */ - if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT) - res = ExecEvalJsonExprCoercion(jsestate, econtext, - &jsestate->result_coercion, - res, op->resnull, NULL); - } + *op->resvalue = InputFunctionCall(op->d.jsonexpr_iocoerce.finfo, str, + op->d.jsonexpr_iocoerce.typioparam, + op->d.jsonexpr_iocoerce.typmod); +} - *op->resvalue = res; +/* Apply json_populate_type() to a JSON item */ +void +ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + *op->resvalue = + json_populate_type(*op->resvalue, JSONBOID, + op->d.jsonexpr_populate.jsexpr->returning->typid, + op->d.jsonexpr_populate.jsexpr->returning->typmod, + &op->d.jsonexpr_populate.cache, + econtext->ecxt_per_query_memory, + op->resnull); } diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 6568228b996..70d84d95175 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2542,8 +2542,126 @@ llvm_compile_expr_step(ExprState *state, int start_opno) LLVMBuildBr(b, opblocks[opno + 1]); break; - case EEOP_JSONEXPR: - build_EvalXFunc(b, mod, "ExecEvalJsonExpr", + case EEOP_JSONEXPR_PATH: + { + LLVMValueRef v_ret; + LLVMValueRef v_switch; + JsonItemCoercionsState *jcstate = + op->d.jsonexpr.jsestate->item_coercions; + int jumps[6 + sizeof(*jcstate) / sizeof(jcstate->null)]; + int njumps = 0; + + /* + * ExecEvalJson() returns number of the step we + * need to jump. + */ + v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExpr", + v_state, op); + + /* + * Collect all unique jumps. All jumps can be + * equal to jump_done. + */ + jumps[njumps++] = op->d.jsonexpr.jump_done; + + if (op->d.jsonexpr.jump_coercion != op->d.jsonexpr.jump_done) + jumps[njumps++] = op->d.jsonexpr.jump_coercion; + + /* + * jump_coercion_subtrans and jump_coercion can be + * the same if there are no subtransactions. + */ + if (op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_done && + op->d.jsonexpr.jump_coercion_subtrans != op->d.jsonexpr.jump_coercion) + jumps[njumps++] = op->d.jsonexpr.jump_coercion_subtrans; + + /* + * jump_coercion_via_io and jump_coercion_subtrans + * can be the same whn only I/O coercion to output + * type is available. jump_coercion_via_io can + * also be negative. + */ + if (op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_done && + op->d.jsonexpr.jump_coercion_via_io != op->d.jsonexpr.jump_coercion_subtrans && + op->d.jsonexpr.jump_coercion_via_io >= 0) + jumps[njumps++] = op->d.jsonexpr.jump_coercion_via_io; + + if (op->d.jsonexpr.jump_onempty != op->d.jsonexpr.jump_done) + jumps[njumps++] = op->d.jsonexpr.jump_onempty; + + if (op->d.jsonexpr.jump_onerror != op->d.jsonexpr.jump_done) + jumps[njumps++] = op->d.jsonexpr.jump_onerror; + + /* + * jump_coercion_null and jump_coercion_null_subtrans + * are always duplicate. + */ + + /* Collect non-zero jumps to item coercions, if any */ + if (jcstate) + { + for (int *p_jump = &jcstate->null; + p_jump <= &jcstate->composite; + p_jump++) + { + if (*p_jump > 0) + jumps[njumps++] = *p_jump; + } + } + + /* Emit switch for all possible jumps */ + v_switch = LLVMBuildSwitch(b, v_ret, + opblocks[op->d.jsonexpr.jump_done], + njumps); + + for (int i = 0; i < njumps; i++) + LLVMAddCase(v_switch, + l_int32_const(jumps[i]), + opblocks[jumps[i]]); + + break; + } + break; + + case EEOP_JSONEXPR_SKIP: + { + LLVMValueRef v_ret; + + /* + * Call ExecEvalJsonExprSkip() to decide if JSON + * path evaluation can be skipped. This returns + * boolean "skip" flag. + */ + v_ret = build_EvalXFunc(b, mod, "ExecEvalJsonExprSkip", + v_state, op); + v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, ""); + + /* + * Jump to coercion step if true was returned, + * which signifies skipping of JSON path evaluation, + * else to the next step which must point to the + * steps to evaluate PASSING args, if any, or to + * the JSON path evaluation. + */ + LLVMBuildCondBr(b, + LLVMBuildICmp(b, + LLVMIntEQ, + v_ret, + l_sbool_const(0), + ""), + opblocks[opno + 1], + opblocks[op->d.jsonexpr_skip.jump_coercion]); + break; + } + + case EEOP_JSONEXPR_IOCOERCE: + build_EvalXFunc(b, mod, "ExecEvalJsonExprIOCoerce", + v_state, op); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_JSONEXPR_POPULATE: + build_EvalXFunc(b, mod, "ExecEvalJsonExprPopulate", v_state, op, v_econtext); LLVMBuildBr(b, opblocks[opno + 1]); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index bb96d46048f..d7653288dd8 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -138,6 +138,9 @@ void *referenced_functions[] = ExecEvalJsonConstructor, ExecEvalJsonIsPredicate, ExecEvalJsonExpr, + ExecEvalJsonExprSkip, + ExecEvalJsonExprIOCoerce, + ExecEvalJsonExprPopulate, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index aff6fc1a1c4..cad3c56a38f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4384,6 +4384,11 @@ initJsonItemCoercions(ParseState *pstate, const JsonReturning *returning, {NULL, InvalidOid} }; + /* When returning JSON types, no need to initialize coercions */ + /* XXX domain types on json/jsonb */ + if (returning->typid == JSONBOID || returning->typid == JSONOID) + return NULL; + for (p = coercionTypids; p->coercion; p++) *p->coercion = initJsonItemCoercion(pstate, p->typid, returning); @@ -4465,6 +4470,7 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) } else if (jsexpr->returning->typid != BOOLOID) { + /* We need to handle RETURNING int etc. */ CaseTestExpr *placeholder = makeNode(CaseTestExpr); int location = exprLocation((Node *) jsexpr); Node *coercion_expr; diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 5b6a4805721..3390038ff4c 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -286,6 +286,8 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList *found, JsonPathBool res); static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value); +static int EvalJsonPathVar(void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); static void getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, JsonbValue *value); static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, @@ -2172,6 +2174,72 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, } } +/* + * Evaluate a JSON path variable caching computed value. + */ +int +EvalJsonPathVar(void *cxt, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject) +{ + JsonPathVariableEvalContext *var = NULL; + List *vars = cxt; + ListCell *lc; + int id = 1; + + if (!varName) + return list_length(vars); + + foreach(lc, vars) + { + var = lfirst(lc); + + if (!strncmp(var->name, varName, varNameLen)) + break; + + var = NULL; + id++; + } + + if (!var) + return -1; + + /* + * When belonging to a JsonExpr, path variables are computed with the + * JsonExpr's ExprState (var->estate is NULL), so don't need to be computed + * here. In some other cases, such as when the path variables belonging + * to a JsonTable instead, those variables must be evaluated on their own, + * without the enclosing JsonExpr itself needing to be evaluated, so must + * be handled here. + */ + if (var->estate && !var->evaluated) + { + MemoryContext oldcxt = var->mcxt ? + MemoryContextSwitchTo(var->mcxt) : NULL; + + Assert(var->econtext != NULL); + var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); + var->evaluated = true; + + if (oldcxt) + MemoryContextSwitchTo(oldcxt); + } + else + { + Assert(var->evaluated); + } + + if (var->isnull) + { + val->type = jbvNull; + return 0; + } + + JsonItemFromDatum(var->value, var->typid, var->typmod, val); + + *baseObject = *val; + return id; +} + /* * Get the value of variable passed to jsonpath executor */ diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index a51232b84de..3aa5cddbb8e 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -247,7 +247,10 @@ typedef enum ExprEvalOp EEOP_SUBTRANS, EEOP_JSON_CONSTRUCTOR, EEOP_IS_JSON, - EEOP_JSONEXPR, + EEOP_JSONEXPR_SKIP, + EEOP_JSONEXPR_PATH, + EEOP_JSONEXPR_IOCOERCE, + EEOP_JSONEXPR_POPULATE, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -704,12 +707,50 @@ typedef struct ExprEvalStep JsonIsPredicate *pred; /* original expression node */ } is_json; - /* for EEOP_JSONEXPR */ + /* for EEOP_JSONEXPR_PATH */ struct { struct JsonExprState *jsestate; + + /* See ExecEvalJsonExpr() */ + int jump_done; + int jump_onerror; + int jump_onempty; + int jump_coercion; + int jump_coercion_subtrans; + int jump_coercion_via_io; + int jump_coercion_null; + int jump_coercion_null_subtrans; } jsonexpr; + /* for EEOP_JSONEXPR_SKIP */ + struct + { + /* Same as jsonexpr.jsestate */ + struct JsonExprState *jsestate; + + /* See ExecEvalJsonExprSkip() */ + int jump_coercion; + } jsonexpr_skip; + + /* for EEOP_JSONEXPR_IOCOERCE */ + struct + { + /* I/O info for output type */ + FmgrInfo *finfo; /* typinput function for output type */ + Oid typioparam; + int32 typmod; + } jsonexpr_iocoerce; + + /* for EEOP_JSONEXPR_POPULATE */ + struct + { + struct JsonExpr *jsexpr; + + /* Cache for json_populate_type() */ + void *cache; + } jsonexpr_populate; + } d; } ExprEvalStep; @@ -769,68 +810,57 @@ typedef struct JsonConstructorExprState int nargs; } JsonConstructorExprState; -typedef struct JsonCoercionState +/* + * Information computed before evaluating a JsonExpr expression. + */ +typedef struct JsonExprPreEvalState { - ExprState *estate; /* coercion expression state, if any */ - JsonCoercionType type; -} JsonCoercionState; + /* value/isnull for JsonExpr.formatted_expr */ + NullableDatum formatted_expr; + + /* value/isnull for JsonExpr.pathspec */ + NullableDatum pathspec; + + /* JsonPathVariableEvalContext entries for JsonExpr.passing_values */ + List *args; +} JsonExprPreEvalState; + +/* + * State for evaluating the coercion for a given JSON item using one of + * the following coercions. + * + * Note that while ExecInitExprRec() for JsonItemCoercions will + * initialize ExprEvalSteps for all of the members that need it, only + * one will get run during a given evaluation of the enclosing JsonExpr + * depending on the type of the result JSON item. + */ +typedef struct JsonItemCoercionsState +{ + /* Number of ExprEvalStep to compute this coercion's expression */ + int null; + int string; + int numeric; + int boolean; + int date; + int time; + int timetz; + int timestamp; + int timestamptz; + int composite; +} JsonItemCoercionsState; /* EEOP_JSONEXPR state, too big to inline */ typedef struct JsonExprState { JsonExpr *jsexpr; /* original expression node */ - struct - { - FmgrInfo func; /* typinput function for output type */ - Oid typioparam; - } input; /* I/O info for output type */ - - NullableDatum - formatted_expr, /* formatted context item value */ - coercion_expr, /* input for JSON item coercion */ - pathspec; /* path specification value */ - - ExprState *result_expr; /* coerced to output type */ - ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */ - ExprState *default_on_error; /* ON ERROR DEFAULT expression */ - List *args; /* passing arguments */ - - void *cache; /* cache for json_populate_type() */ + JsonExprPreEvalState pre_eval; /* - * States for coercion of SQL/JSON items produced in JSON_VALUE - * directly to the output type. - * - * `item_coercions == NULL` means output type is json[b] or its - * domain and we use `result_coercion`. + * Used only in JSON_VALUE(). `item_coercions == NULL` means that + * output type is json[b] or its domain and we use `result_coercion`. */ - struct JsonItemsCoercionStates - { - JsonCoercionState - null, - string, - numeric, - boolean, - date, - time, - timetz, - timestamp, - timestamptz, - composite; - } *item_coercions; - - /* Default output coercion to use, can be overriden in JSON_QUERY */ - JsonCoercionState result_coercion; - - /* Special I/O coercion state for JSON_QUERY OMIT QUOTES */ - JsonCoercionState io_coercion; - - /* Can expression in DEFAULT ON EMPTY throw errors? */ - bool default_on_empty_can_throw; - - /* Can we skip coercion of NULLs (non-domain output type)? */ - bool skip_null_coercion; + JsonItemCoercionsState *item_coercions; } JsonExprState; /* functions in execExpr.c */ @@ -895,15 +925,13 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op, ExecEvalSubroutine func); extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern void ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, - ExprContext *econtext); extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); extern bool ExecEvalExprCanThrowErrors(Node *expr); -extern Datum ExecEvalExprPassingCaseValue(ExprState *estate, - ExprContext *econtext, bool *isnull, - Datum caseval_datum, - bool caseval_isnull); - +extern int ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op); +extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup, ExprContext *aggcontext); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 0a40d59be26..0824a6186d1 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1576,8 +1576,11 @@ typedef struct JsonCoercion /* * JsonItemCoercions - - * expressions for coercion from SQL/JSON item types directly to the - * output SQL type + * expressions for coercion from SQL/JSON item types directly to + * the output SQL type. + * + * "JsonCoercion == NULL" means no cast is available. + * "JsonCoercion.expr == NULL" means no coercion is needed. */ typedef struct JsonItemCoercions { @@ -1591,7 +1594,7 @@ typedef struct JsonItemCoercions JsonCoercion *timetz; JsonCoercion *timestamp; JsonCoercion *timestamptz; - JsonCoercion *composite; /* arrays and objects */ + JsonCoercion *composite; } JsonItemCoercions; /* diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index bae466b5234..6bdd9f51219 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -69,8 +69,10 @@ typedef enum /* Convenience macros */ #define DatumGetJsonbP(d) ((Jsonb *) PG_DETOAST_DATUM(d)) +#define DatumGetJsonbValueP(d) ((JsonbValue *) DatumGetPointer(d)) #define DatumGetJsonbPCopy(d) ((Jsonb *) PG_DETOAST_DATUM_COPY(d)) #define JsonbPGetDatum(p) PointerGetDatum(p) +#define JsonbValuePGetDatum(p) PointerGetDatum(p) #define PG_GETARG_JSONB_P(x) DatumGetJsonbP(PG_GETARG_DATUM(x)) #define PG_GETARG_JSONB_P_COPY(x) DatumGetJsonbPCopy(PG_GETARG_DATUM(x)) #define PG_RETURN_JSONB_P(x) PG_RETURN_POINTER(x) diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 8e79b8dc9f0..fa1d3eae6cf 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -280,9 +280,6 @@ extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars); -extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen, - JsonbValue *val, JsonbValue *baseObject); - extern PGDLLIMPORT const TableFuncRoutine JsonbTableRoutine; #endif diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index ee5e597909c..c84fb03df37 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -693,6 +693,17 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 + "2018-02-21T02:34:56+00:00" (1 row) +-- test errors in DEFAULT ON EMPTY expressions +SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int + DEFAULT 1 / x ON EMPTY + DEFAULT 2 ON ERROR) +FROM (VALUES (1::int), (0)) x(x); + json_value +------------ + 1 + 2 +(2 rows) + -- JSON_QUERY SELECT JSON_QUERY(js, '$'), @@ -794,6 +805,12 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERR \x616161 (1 row) +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR); + json_query +------------ + [] +(1 row) + -- QUOTES behavior should not be specified when WITH WRAPPER used: -- Should fail SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index d70104b70ef..eed163e7089 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -194,6 +194,12 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 + SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); +-- test errors in DEFAULT ON EMPTY expressions +SELECT JSON_VALUE('1'::jsonb, '$.x' RETURNING int + DEFAULT 1 / x ON EMPTY + DEFAULT 2 ON ERROR) +FROM (VALUES (1::int), (0)) x(x); + -- JSON_QUERY SELECT @@ -239,6 +245,7 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES EMPTY ARRAY ON ERROR); -- QUOTES behavior should not be specified when WITH WRAPPER used: -- Should fail -- 2.25.1

view thread (95+ messages)  latest in thread

Message-ID: <7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru>
Permalink:  ../7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru/
Also on:    postgresql.org/message-id/7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: n.gluhov@postgrespro.ru, jkatz@postgresql.org, andres@anarazel.de, pgsql-hackers@lists.postgresql.org, andrew@dunslane.net, tgl@sss.pgh.pa.us, michael@paquier.xyz, john.naylor@enterprisedb.com, amitlangote09@gmail.com
  Subject: Re: SQL/JSON features for v15
  In-Reply-To: <7d83684b-7932-9f29-400b-0beedfafcdd4@postgrespro.ru>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox