v7-0001-Simplify-JsonExpr-execution.patch
text/x-patch
Filename: v7-0001-Simplify-JsonExpr-execution.patch
Type: text/x-patch
Part: 0
Message:
Re: SQL/JSON features for v15
Patch
Format: format-patch
Series: patch v7-0001
Subject: Simplify JsonExpr execution
| File | + | − |
|---|---|---|
| src/backend/executor/execExpr.c | 74 | 35 |
| src/backend/executor/execExprInterp.c | 299 | 260 |
| src/backend/jit/llvm/llvmjit_expr.c | 1 | 1 |
| src/backend/jit/llvm/llvmjit_types.c | 1 | 1 |
| src/backend/nodes/nodeFuncs.c | 6 | 4 |
| src/backend/optimizer/util/clauses.c | 1 | 1 |
| src/backend/parser/parse_expr.c | 42 | 20 |
| src/include/executor/execExpr.h | 34 | 21 |
| src/include/nodes/primnodes.h | 14 | 4 |
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