v6-0003-Simplify-JsonExpr-execution.patch
text/x-patch
Filename: v6-0003-Simplify-JsonExpr-execution.patch
Type: text/x-patch
Part: 2
Message:
Re: SQL/JSON features for v15
Patch
Format: format-patch
Series: patch v6-0003
Subject: Simplify JsonExpr execution
| File | + | − |
|---|---|---|
| src/backend/executor/execExpr.c | 570 | 105 |
| src/backend/executor/execExprInterp.c | 190 | 415 |
| src/backend/jit/llvm/llvmjit_expr.c | 135 | 2 |
| src/backend/jit/llvm/llvmjit_types.c | 3 | 0 |
| src/backend/optimizer/util/clauses.c | 1 | 1 |
| src/backend/parser/parse_expr.c | 17 | 1 |
| src/backend/utils/adt/jsonpath_exec.c | 68 | 0 |
| src/include/executor/execExpr.h | 94 | 56 |
| src/include/nodes/primnodes.h | 6 | 3 |
| src/include/utils/jsonb.h | 2 | 0 |
| src/include/utils/jsonpath.h | 0 | 3 |
| src/test/regress/expected/jsonb_sqljson.out | 17 | 0 |
| src/test/regress/sql/jsonb_sqljson.sql | 7 | 0 |
From 842c002642c06d2dd371e195544236ae9ad2e76e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 16 Aug 2022 03:17:16 +0300
Subject: [PATCH v6 3/3] Simplify JsonExpr execution
---
src/backend/executor/execExpr.c | 675 +++++++++++++++++---
src/backend/executor/execExprInterp.c | 605 ++++++------------
src/backend/jit/llvm/llvmjit_expr.c | 137 +++-
src/backend/jit/llvm/llvmjit_types.c | 3 +
src/backend/optimizer/util/clauses.c | 2 +-
src/backend/parser/parse_expr.c | 18 +-
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 +
13 files changed, 1110 insertions(+), 586 deletions(-)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 96196d50876..9370727d902 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,112 +2562,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
case T_JsonExpr:
{
JsonExpr *jexpr = castNode(JsonExpr, node);
- JsonExprState *jsestate = palloc0(sizeof(JsonExprState));
- ListCell *argexprlc;
- ListCell *argnamelc;
-
- scratch.opcode = EEOP_JSONEXPR;
- scratch.d.jsonexpr.jsestate = jsestate;
-
- jsestate->jsexpr = jexpr;
-
- jsestate->formatted_expr =
- palloc(sizeof(*jsestate->formatted_expr));
-
- ExecInitExprRec((Expr *) jexpr->formatted_expr, state,
- &jsestate->formatted_expr->value,
- &jsestate->formatted_expr->isnull);
-
- jsestate->pathspec =
- palloc(sizeof(*jsestate->pathspec));
-
- ExecInitExprRec((Expr *) jexpr->path_spec, state,
- &jsestate->pathspec->value,
- &jsestate->pathspec->isnull);
-
- jsestate->res_expr =
- palloc(sizeof(*jsestate->res_expr));
-
- jsestate->result_expr = jexpr->result_coercion
- ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr,
- state->parent,
- &jsestate->res_expr->value,
- &jsestate->res_expr->isnull)
- : NULL;
-
- jsestate->default_on_empty = !jexpr->on_empty ? NULL :
- ExecInitExpr((Expr *) jexpr->on_empty->default_expr,
- state->parent);
-
- jsestate->default_on_error =
- ExecInitExpr((Expr *) jexpr->on_error->default_expr,
- state->parent);
-
- if (jexpr->omit_quotes ||
- (jexpr->result_coercion && jexpr->result_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;
+ ExecInitJsonExpr(&scratch, jexpr, state, resv, resnull);
+ break;
+ }
- if (jexpr->coercions)
- {
- JsonCoercion **coercion;
- struct JsonCoercionState *cstate;
- Datum *caseval;
- bool *casenull;
-
- jsestate->coercions =
- palloc(sizeof(*jsestate->coercions));
- jsestate->coercion_expr =
- palloc(sizeof(*jsestate->coercion_expr));
-
- caseval = &jsestate->coercion_expr->value;
- casenull = &jsestate->coercion_expr->isnull;
-
- for (cstate = &jsestate->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;
- }
- }
+ case T_JsonItemCoercions:
+ {
+ elog(ERROR, "JsonItemCoercions must be initialized manually");
+ break;
+ }
- ExprEvalPushStep(state, &scratch);
+ case T_JsonCoercion:
+ {
+ elog(ERROR, "JsonCoercion must be initialized manually");
break;
}
@@ -4259,3 +4168,559 @@ 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 bool
+ExecExprNeedsSubTransaction(Node *expr)
+{
+ /* No need for sub-tranactions for Const expressions */
+ if (IsA(expr, Const))
+ return false;
+
+ /* TODO check other expression types */
+ return true;
+}
+
+bool
+ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr)
+{
+ if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ return false; /* no error handling */
+
+ if (jsexpr->result_coercion &&
+ (jsexpr->result_coercion->expr ||
+ jsexpr->result_coercion->via_io ||
+ jsexpr->result_coercion->via_populate))
+ return true;
+
+ if (jsexpr->on_empty &&
+ jsexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT &&
+ ExecExprNeedsSubTransaction(jsexpr->on_empty->default_expr))
+ return true;
+
+ if (jsexpr->op == JSON_EXISTS_OP)
+ return false;
+
+ if (jsexpr->op == JSON_VALUE_OP && jsexpr->coercions)
+ return true; /* may call item coercions */
+
+ if (jsexpr->op == JSON_QUERY_OP && jsexpr->omit_quotes)
+ return true; /* may call coercion via I/O */
+
+ return false;
+}
+
+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->expr &&
+ !coercion->via_io &&
+ !coercion->via_populate)
+ return -1; /* no coercion is needed */
+ }
+
+ /* Check whether expression really needs sub-transactions */
+ if (need_subtrans)
+ need_subtrans = ExecExprNeedsSubTransaction(expr);
+
+ /* Emit EEOP_JSONEXPR_SUBTRANS step */
+ if (need_subtrans)
+ {
+ cxt->adjust_subtrans =
+ lappend_int(cxt->adjust_subtrans, state->steps_len);
+
+ 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->expr)
+ /* Push step(s) to compute coercion->expr */
+ ExecInitJsonCoercionExpr(state, (Expr *) coercion->expr,
+ cxt->resv, cxt->resnull);
+ else if (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->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 JsonItemCoercionsSteps *
+ExecInitJsonItemCoercions(ExecInitJsonContext *cxt,
+ JsonItemCoercions *coercions,
+ bool need_subtrans)
+{
+ JsonItemCoercionsSteps *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)
+ {
+ Assert(!(*coercion)->via_io && !(*coercion)->via_populate);
+
+ if ((*coercion)->expr)
+ *step = ExecInitJsonCoercion(cxt, (Node *) *coercion,
+ need_subtrans);
+ else
+ *step = 0; /* No coercion is needed */
+ }
+ 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;
+ 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 special output
+ * parameter, else append it to jump_done list.
+ */
+ if (cxt->jsestate->jsexpr->result_coercion)
+ *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 = palloc0(sizeof(JsonExprState));
+ JsonExprPreEvalState *pre_eval = &jsestate->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;
+
+ cxt.state = state;
+ cxt.scratch = scratch;
+ cxt.jsestate = jsestate;
+ cxt.resv = resv;
+ cxt.resnull = resnull;
+ cxt.adjust_jumps = NIL;
+ cxt.adjust_subtrans = NIL;
+
+ jsestate->jsexpr = jexpr;
+
+ /*
+ * 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->via_io))
+ {
+ JsonCoercion io_coercion = {0};
+
+ io_coercion.type = T_JsonCoercion;
+ io_coercion.via_io = true;
+
+ 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);
+
+ 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;
+
+ /* 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 = 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 = 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 ||
+ ExecJsonExprNeedsSubTransaction(jexpr));
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 662d47f8cbb..15278b296cd 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 */
- ExecEvalJson(state, op, econtext);
+ EEO_JUMP(ExecEvalJson(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();
}
@@ -4799,550 +4824,300 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
}
/*
- * Evaluate a JSON error/empty behavior result.
+ * 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
-ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
- ExprState *default_estate, bool *is_null)
+ExecPrepareJsonItemCoercion(JsonbValue *item, ExprEvalStep *op,
+ JsonItemCoercionsSteps *steps, int *jump)
{
- *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;
- }
-}
-
-/*
- * Evaluate a coercion of a JSON item to the target type.
- */
-static Datum
-ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
- Datum res, bool *isNull, void *p, bool *error)
-{
- ExprState *estate = p;
- JsonExprState *jsestate;
-
- if (estate) /* coerce using specified expression */
- return ExecEvalExpr(estate, econtext, isNull);
-
- jsestate = op->d.jsonexpr.jsestate;
-
- if (jsestate->jsexpr->op != JSON_EXISTS_OP)
- {
- JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
- JsonExpr *jexpr = jsestate->jsexpr;
- Jsonb *jb = *isNull ? NULL : 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,
- jexpr->returning->typmod);
- }
- else if (coercion && coercion->via_populate)
- return json_populate_type(res, JSONBOID,
- jexpr->returning->typid,
- jexpr->returning->typmod,
- &jsestate->cache,
- econtext->ecxt_per_query_memory,
- isNull);
- }
-
- if (jsestate->result_expr)
- {
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *isNull;
-
- res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
- }
-
- return res;
-}
-
-/*
- * 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);
+ JsonbValue buf;
- if (!strncmp(var->name, varName, varNameLen))
- break;
-
- var = NULL;
- id++;
- }
-
- if (!var)
- return -1;
-
- if (!var->evaluated)
+ /*
+ * Special case for json and jsonb types for which we don't have
+ * casts like numeric::jsonb etc.
+ */
+ if (!steps)
{
- MemoryContext oldcxt = var->mcxt ?
- MemoryContextSwitchTo(var->mcxt) : NULL;
-
- var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
- var->evaluated = true;
+ Jsonb *jb = JsonbValueToJsonb(item);
- if (oldcxt)
- MemoryContextSwitchTo(oldcxt);
- }
+ /*
+ * Use result coercion from json[b] to the output
+ * type when casting to json[b] types or their
+ * domains.
+ */
- if (var->isnull)
- {
- val->type = jbvNull;
- return 0;
+ *jump = op->d.jsonexpr.jump_coercion_subtrans;
+ return JsonbPGetDatum(jb);;
}
- JsonItemFromDatum(var->value, var->typid, var->typmod, val);
-
- *baseObject = *val;
- return id;
-}
-
-/*
- * Prepare SQL/JSON item coercion to the output type. Returned a datum of the
- * corresponding SQL type and a pointer to the coercion state.
- */
-Datum
-ExecPrepareJsonItemCoercion(JsonbValue *item,
- JsonReturning *returning,
- struct JsonCoercionsState *coercions,
- struct JsonCoercionState **pcoercion)
-{
- struct JsonCoercionState *coercion;
- Datum res;
- JsonbValue buf;
-
+ /* 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:
- coercion = &coercions->null;
- res = (Datum) 0;
- break;
+ *jump = steps->null;
+ return (Datum) 0;
case jbvString:
- coercion = &coercions->string;
- res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
- item->val.string.len));
- break;
+ *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;
- res = NumericGetDatum(item->val.numeric);
- break;
+ *jump = steps->numeric;
+ return NumericGetDatum(item->val.numeric);
case jbvBool:
- coercion = &coercions->boolean;
- res = BoolGetDatum(item->val.boolean);
- break;
+ *jump = steps->boolean;
+ return BoolGetDatum(item->val.boolean);
case jbvDatetime:
- res = item->val.datetime.value;
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",
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;
+ *jump = steps->composite;
+ return *jump < 0 ? (Datum) 0 :
+ 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)
-{
- 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)
+/*
+ * Evaluate a SQL/JSON function.
+ */
+int
+ExecEvalJson(ExprState *state, ExprEvalStep *op)
{
- ExecEvalJsonExprContext *cxt = pcxt;
- JsonPath *path = cxt->path;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+ JsonExprPreEvalState *pre_eval = &jsestate->pre_eval;
JsonExpr *jexpr = jsestate->jsexpr;
- ExprState *estate = NULL;
- 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;
+
+ *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)
- {
- *resnull = true;
- return (Datum) 0;
- }
- *resnull = !DatumGetPointer(res);
+ res = JsonPathQuery(item, path, jexpr->wrapper, &empty,
+ p_error, pre_eval->args);
+
+ if (error)
+ return op->d.jsonexpr.jump_onerror;
+
+ *op->resnull = !DatumGetPointer(res);
+ *op->resvalue = res;
+
+ /* Force coercion via I/O if OMIT QUOTES is used */
+ if (jexpr->omit_quotes && !empty && !*op->resnull &&
+ JB_ROOT_IS_SCALAR(DatumGetJsonbP(res)))
+ return op->d.jsonexpr.jump_coercion_via_io;
+
break;
case JSON_VALUE_OP:
{
- struct JsonCoercionState *jcstate;
- 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 */
break;
Assert(!empty);
+ Assert(jbv->type != jbvNull);
- *resnull = false;
+ /*
+ * Prepare coercion from SQL/JSON item type to the
+ * output SQL type.
+ */
+ res = ExecPrepareJsonItemCoercion(jbv, op,
+ jsestate->item_coercions,
+ &jump_item_coercion);
- /* coerce scalar item to the output type */
- if (!jsestate->coercions)
- {
- /*
- * Use result coercion from json[b] to the output
- * type when casting to json[b] types or their
- * domains.
- */
- res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
- break;
- }
+ *op->resvalue = res;
+ *op->resnull = false;
- /* Use coercion from SQL/JSON item type to the output type */
- res = ExecPrepareJsonItemCoercion(jbv,
- jsestate->jsexpr->returning,
- jsestate->coercions,
- &jcstate);
+ /* Jump to zero here means jump to the end. */
+ if (jump_item_coercion == 0)
+ return op->d.jsonexpr.jump_done;
- if (jcstate->coercion &&
- (jcstate->coercion->via_io ||
- jcstate->coercion->via_populate))
+ /*
+ * Error out if no cast exists to coerce SQL/JSON item
+ * to the the output type.
+ */
+ if (jump_item_coercion < 0)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
+ *op->resnull = true;
+
+ if (catch_errors)
+ return op->d.jsonexpr.jump_onerror;
- /*
- * 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;
- break;
+ /* 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);
+ *op->resnull = error;
+ *op->resvalue = BoolGetDatum(exists);
- if (!jsestate->result_expr)
- return res;
+ if (error)
+ return op->d.jsonexpr.jump_onerror;
- /* coerce using result expression */
- estate = jsestate->result_expr;
- jsestate->res_expr->value = res;
- jsestate->res_expr->isnull = *resnull;
break;
}
case JSON_TABLE_OP:
- *resnull = false;
- return item;
+ *op->resvalue = item;
+ *op->resnull = false;
+ return op->d.jsonexpr.jump_done;
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
- return (Datum) 0;
+ return op->d.jsonexpr.jump_done;
}
+ /* Execute ON EMPTY behavior if no SQL/JSON items were found */
if (empty)
{
Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
{
- if (error)
- {
- *error = true;
- return (Datum) 0;
- }
+ /* Throw an error or jump to ON ERROR handler */
+ if (catch_errors)
+ return op->d.jsonexpr.jump_onerror;
ereport(ERROR,
(errcode(ERRCODE_NO_SQL_JSON_ITEM),
errmsg("no SQL/JSON item")));
}
- 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.
- */
- estate = jsestate->default_on_empty;
- else
- /* Execute ON EMPTY behavior */
- res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
- jsestate->default_on_empty,
- resnull);
+ /* Evaluate ON EMPTY behavior */
+ return op->d.jsonexpr.jump_onempty;
}
- return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
- res, resnull, estate, error,
- cxt->coercionInSubtrans);
+ /* Evaluate coercion expression inside subtransaction */
+ return op->d.jsonexpr.jump_coercion_subtrans;
}
+/* Skip calling ExecEvalJson() on a JsonExpr? */
bool
-ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
- bool consider_conditional_coercions)
+ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op)
{
- if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
- return false;
-
- if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
- return false;
+ JsonExprState *jsestate = op->d.jsonexpr_skip.jsestate;
- if (consider_conditional_coercions)
+ /*
+ * 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->resvalue = (Datum) 0;
+ *op->resnull = true;
return true;
+ }
+ /*
+ * Go evaluate the PASSING args if any and subsequently JSON path
+ * itself.
+ */
return false;
}
-/* ----------------------------------------------------------------
- * ExecEvalJson
- * ----------------------------------------------------------------
- */
+/* Apply I/O coercion to a JSON item */
void
-ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op)
{
- ExecEvalJsonExprContext cxt;
- JsonExprState *jsestate = op->d.jsonexpr.jsestate;
- JsonExpr *jexpr = jsestate->jsexpr;
- Datum item;
- Datum res = (Datum) 0;
- 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)
- {
- /* execute domain checks for NULLs */
- (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
- NULL, NULL);
+ /* Strip quotes and call typinput function */
+ char *str = NULL;
- Assert(*op->resnull);
- return;
- }
-
- item = jsestate->formatted_expr->value;
- path = DatumGetJsonPathP(jsestate->pathspec->value);
-
- /* 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);
}
- needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, false);
-
- 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);
-
- if (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(op, econtext, res,
- op->resnull,
- NULL, 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 614ed58f1cb..5245fe765c5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2465,8 +2465,141 @@ llvm_compile_expr_step(ExprState *state, int start_opno)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
- case EEOP_JSONEXPR:
- build_EvalXFunc(b, mod, "ExecEvalJson",
+ case EEOP_JSONEXPR_PATH:
+ {
+ LLVMValueRef v_ret;
+ LLVMBasicBlockRef *b_coercions;
+ JsonItemCoercionsSteps *jcstate =
+ op->d.jsonexpr.jsestate->item_coercions;
+ int n_jumps = 6;
+ int n_coercions = !jcstate ? 0 :
+ (int) (&jcstate->composite - &jcstate->null) + 1;
+ int *jumps;
+
+ /*
+ * ExecEvalJson() returns number of the step we
+ * need to jump.
+ */
+ v_ret = build_EvalXFunc(b, mod, "ExecEvalJson",
+ v_state, op);
+
+ /*
+ * Will create a block for each possible jump to
+ * check whether to evaluate the coercion's
+ * expression if there's one or to skip to the end
+ * if not.
+ */
+ b_coercions = palloc((n_jumps + n_coercions) * sizeof(*b_coercions));
+ jumps = palloc((n_jumps + n_coercions) * sizeof(*jumps));
+
+ jumps[0] = op->d.jsonexpr.jump_done;
+ jumps[1] = op->d.jsonexpr.jump_coercion;
+ jumps[2] = op->d.jsonexpr.jump_coercion_subtrans;
+ jumps[3] = op->d.jsonexpr.jump_coercion_via_io;
+ jumps[4] = op->d.jsonexpr.jump_onempty;
+ jumps[5] = op->d.jsonexpr.jump_onerror;
+
+ b_coercions[0] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_coercion", opno);
+ b_coercions[1] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_coercion_subtrans", opno);
+ b_coercions[2] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_coercion_io", opno);
+ b_coercions[3] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_onempty", opno);
+ b_coercions[4] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_onerror", opno);
+
+ for (int i = 0; i < n_coercions; i++)
+ jumps[i + n_jumps] = (&jcstate->null)[i];
+
+ for (int i = 0; i < n_coercions + 1; i++)
+ b_coercions[i + n_jumps - 1] =
+ l_bb_before_v(opblocks[opno + 1],
+ "op.%d.jsonexpr_jump_item.%d",
+ opno, i);
+
+ /*
+ * Add conditional branches for individual
+ * coercion's expressions.
+ *
+ * Jump to coercion step if the returned address is
+ * the same as jumps[i], else jump to the next
+ * block, that checks next jump address.
+ */
+ for (int i = 0; i < n_jumps + n_coercions; i++)
+ {
+ if (i > 0)
+ LLVMPositionBuilderAtEnd(b, b_coercions[i - 1]);
+
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b,
+ LLVMIntEQ,
+ v_ret,
+ l_int32_const(jumps[i]),
+ ""),
+ opblocks[jumps[i] >= 0 ? jumps[i] : jumps[0]],
+ b_coercions[i]);
+ }
+
+ /*
+ * A placeholder block that the last coercion's
+ * block might jump to, which unconditionally jumps
+ * to end of coercions.
+ */
+ LLVMPositionBuilderAtEnd(b, b_coercions[n_jumps + n_coercions - 1]);
+ LLVMBuildBr(b, opblocks[op->d.jsonexpr.jump_done]);
+
+ pfree(b_coercions);
+ pfree(jumps);
+ 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 a9ddba6e4cd..d47d065dc59 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -136,6 +136,9 @@ void *referenced_functions[] =
ExecEvalSubtrans,
ExecEvalJsonConstructor,
ExecEvalJsonIsPredicate,
+ ExecEvalJsonExprSkip,
+ ExecEvalJsonExprIOCoerce,
+ ExecEvalJsonExprPopulate,
ExecEvalJson,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1d868bb87b..e9eee8c1a0c 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, true))
+ if (ExecJsonExprNeedsSubTransaction(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 31fb5555659..41f4acbc262 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4329,6 +4329,7 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
const JsonReturning *returning)
{
Node *expr;
+ JsonCoercion *coercion;
if (typid == UNKNOWNOID)
{
@@ -4345,7 +4346,16 @@ initJsonItemCoercion(ParseState *pstate, Oid typid,
expr = (Node *) placeholder;
}
- return coerceJsonExpr(pstate, expr, returning);
+ coercion = coerceJsonExpr(pstate, expr, returning);
+
+ /*
+ * Coercion via I/O means here that the cast to the target type
+ * simply does not exist.
+ */
+ if (coercion->via_io || coercion->via_populate)
+ return NULL;
+
+ return coercion;
}
static JsonItemCoercions *
@@ -4373,6 +4383,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);
@@ -4453,6 +4468,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);
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 10c7e64aab3..c3fc77cf515 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 14064dd9521..432876fb01f 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -245,7 +245,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,
@@ -702,12 +705,48 @@ 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;
} 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;
@@ -767,60 +806,67 @@ typedef struct JsonConstructorExprState
int nargs;
} JsonConstructorExprState;
+/*
+ * Information computed before evaluating a JsonExpr expression.
+ */
+typedef struct JsonExprPreEvalState
+{
+ /* 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 JsonItemCoercionsSteps
+{
+ /* 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;
+} JsonItemCoercionsSteps;
+
/* 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 */
- *res_expr, /* result item */
- *coercion_expr, /* input for JSON item coercion */
- *pathspec; /* path specification value */
+ JsonExprPreEvalState pre_eval;
- 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() */
+ /*
+ * Either of the following two is used by ExecEvalJsonCoercion()
+ * to apply coercion to the final result if needed.
+ */
/*
- * This is used only in JSON_VALUE.
- *
- * States for coercion from SQL/JSON item types directly to the
- * output type.
- *
- * "coercions == NULL" means output type is json[b] or its domain
- * and we use "result_expr" for coercion.
+ * In JSON_VALUE() "item_coercions == NULL" means that output type
+ * is json[b] or its domain and we use "result_coercion".
*/
- struct JsonCoercionsState
- {
- struct JsonCoercionState
- {
- JsonCoercion *coercion; /* coercion expression */
- ExprState *estate; /* coercion expression state */
- } null,
- string,
- numeric ,
- boolean,
- date,
- time,
- timetz,
- timestamp,
- timestamptz,
- composite;
- } *coercions;
+ JsonItemCoercionsSteps *item_coercions;
} JsonExprState;
/* functions in execExpr.c */
extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+extern bool ExecJsonExprNeedsSubTransaction(JsonExpr *jsexpr);
/* functions in execExprInterp.c */
extern void ExecReadyInterpretedExpr(ExprState *state);
@@ -881,19 +927,11 @@ extern bool ExecEvalSubtrans(ExprState *state, ExprEvalStep *op,
ExecEvalSubroutine func);
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,
- bool consider_conditional_coercions);
-extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
- ExprContext *econtext, bool *isnull,
- Datum caseval_datum,
- bool caseval_isnull);
-
+extern bool ExecEvalJsonExprSkip(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprIOCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalJsonExprPopulate(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern int ExecEvalJson(ExprState *state, ExprEvalStep *op);
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 3aa96bb6855..6afb4228730 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1566,8 +1566,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
{
@@ -1581,7 +1584,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