aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/execExprInterp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/execExprInterp.c')
-rw-r--r--src/backend/executor/execExprInterp.c338
1 files changed, 337 insertions, 1 deletions
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a25ab7570fe..24a3990a30a 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -72,8 +72,8 @@
#include "utils/datum.h"
#include "utils/expandedrecord.h"
#include "utils/json.h"
-#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
+#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -180,6 +180,7 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate
AggStatePerGroup pergroup,
ExprContext *aggcontext,
int setno);
+static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull);
/*
* ScalarArrayOpExprHashEntry
@@ -481,6 +482,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_XMLEXPR,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
+ &&CASE_EEOP_JSONEXPR_PATH,
+ &&CASE_EEOP_JSONEXPR_COERCION,
+ &&CASE_EEOP_JSONEXPR_COERCION_FINISH,
&&CASE_EEOP_AGGREF,
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
@@ -1554,6 +1558,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_JSONEXPR_PATH)
+ {
+ /* too complex for an inline implementation */
+ EEO_JUMP(ExecEvalJsonExprPath(state, op, econtext));
+ }
+
+ EEO_CASE(EEOP_JSONEXPR_COERCION)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonCoercion(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JSONEXPR_COERCION_FINISH)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonCoercionFinish(state, op);
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_AGGREF)
{
/*
@@ -4222,6 +4248,316 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
*op->resvalue = BoolGetDatum(res);
}
+/*
+ * Evaluate a jsonpath against a document, both of which must have been
+ * evaluated and their values saved in op->d.jsonexpr.jsestate.
+ *
+ * If an error occurs during JsonPath* evaluation or when coercing its result
+ * to the RETURNING type, JsonExprState.error is set to true, provided the
+ * ON ERROR behavior is not ERROR. Similarly, if JsonPath{Query|Value}() found
+ * no matching items, JsonExprState.empty is set to true, provided the ON EMPTY
+ * behavior is not ERROR. That is to signal to the subsequent steps that check
+ * those flags to return the ON ERROR / ON EMPTY expression.
+ *
+ * Return value is the step address to be performed next. It will be one of
+ * jump_error, jump_empty, jump_eval_coercion, or jump_end, all given in
+ * op->d.jsonexpr.jsestate.
+ */
+int
+ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+ JsonExpr *jsexpr = jsestate->jsexpr;
+ Datum item;
+ JsonPath *path;
+ bool throw_error = jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
+ bool error = false,
+ empty = false;
+ int jump_eval_coercion = jsestate->jump_eval_coercion;
+ char *val_string = NULL;
+
+ item = jsestate->formatted_expr.value;
+ path = DatumGetJsonPathP(jsestate->pathspec.value);
+
+ /* Set error/empty to false. */
+ memset(&jsestate->error, 0, sizeof(NullableDatum));
+ memset(&jsestate->empty, 0, sizeof(NullableDatum));
+
+ /*
+ * Also reset ErrorSaveContext contents for the next row. Since we don't
+ * set details_wanted, we don't need to also reset error_data, which would
+ * be NULL anyway.
+ */
+ Assert(!jsestate->escontext.details_wanted &&
+ jsestate->escontext.error_data == NULL);
+ jsestate->escontext.error_occurred = false;
+
+ switch (jsexpr->op)
+ {
+ case JSON_EXISTS_OP:
+ {
+ bool exists = JsonPathExists(item, path,
+ !throw_error ? &error : NULL,
+ jsestate->args);
+
+ if (!error)
+ {
+ *op->resvalue = BoolGetDatum(exists);
+ *op->resnull = false;
+ }
+ }
+ break;
+
+ case JSON_QUERY_OP:
+ *op->resvalue = JsonPathQuery(item, path, jsexpr->wrapper, &empty,
+ !throw_error ? &error : NULL,
+ jsestate->args);
+
+ *op->resnull = (DatumGetPointer(*op->resvalue) == NULL);
+
+ /* Handle OMIT QUOTES. */
+ if (!*op->resnull && jsexpr->omit_quotes)
+ {
+ val_string = JsonbUnquote(DatumGetJsonbP(*op->resvalue));
+
+ /*
+ * Pass the string as a text value to the cast expression if
+ * one present. If not, use the input function call below to
+ * do the coercion.
+ */
+ if (jump_eval_coercion >= 0)
+ *op->resvalue =
+ DirectFunctionCall1(textin,
+ PointerGetDatum(val_string));
+ }
+ break;
+
+ case JSON_VALUE_OP:
+ {
+ JsonbValue *jbv = JsonPathValue(item, path, &empty,
+ !throw_error ? &error : NULL,
+ jsestate->args);
+
+ if (jbv == NULL)
+ {
+ /* Will be coerced with coercion_expr, if any. */
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ }
+ else if (!error && !empty)
+ {
+ if (jsexpr->returning->typid == JSONOID ||
+ jsexpr->returning->typid == JSONBOID)
+ {
+ val_string = DatumGetCString(DirectFunctionCall1(jsonb_out,
+ JsonbPGetDatum(JsonbValueToJsonb(jbv))));
+ }
+ else
+ {
+ val_string = ExecGetJsonValueItemString(jbv, op->resnull);
+
+ /*
+ * Pass the string as a text value to the cast
+ * expression if one present. If not, use the input
+ * function call below to do the coercion.
+ */
+ *op->resvalue = PointerGetDatum(val_string);
+ if (jump_eval_coercion >= 0)
+ *op->resvalue = DirectFunctionCall1(textin, *op->resvalue);
+ }
+ }
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON expression op %d",
+ (int) jsexpr->op);
+ return false;
+ }
+
+ /*
+ * Coerce the result value to the RETURNING type by calling its input
+ * function.
+ */
+ if (!*op->resnull && jsexpr->use_io_coercion)
+ {
+ FunctionCallInfo fcinfo;
+
+ Assert(jump_eval_coercion == -1);
+ fcinfo = jsestate->input_fcinfo;
+ Assert(fcinfo != NULL);
+ Assert(val_string != NULL);
+ fcinfo->args[0].value = PointerGetDatum(val_string);
+ fcinfo->args[0].isnull = *op->resnull;
+
+ /*
+ * Second and third arguments are already set up in
+ * ExecInitJsonExpr().
+ */
+
+ fcinfo->isnull = false;
+ *op->resvalue = FunctionCallInvoke(fcinfo);
+ if (SOFT_ERROR_OCCURRED(&jsestate->escontext))
+ error = true;
+ }
+
+ /* Handle ON EMPTY. */
+ if (empty)
+ {
+ if (jsexpr->on_empty)
+ {
+ if (jsexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
+ ereport(ERROR,
+ errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item"));
+ else
+ jsestate->empty.value = BoolGetDatum(true);
+
+ Assert(jsestate->jump_empty >= 0);
+ return jsestate->jump_empty;
+ }
+ else if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
+ ereport(ERROR,
+ errcode(ERRCODE_NO_SQL_JSON_ITEM),
+ errmsg("no SQL/JSON item"));
+ else
+ jsestate->error.value = BoolGetDatum(true);
+
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ Assert(!throw_error && jsestate->jump_error >= 0);
+ return jsestate->jump_error;
+ }
+
+ /*
+ * ON ERROR. Wouldn't get here if the behavior is ERROR, because they
+ * would have already been thrown.
+ */
+ if (error)
+ {
+ Assert(!throw_error && jsestate->jump_error >= 0);
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ jsestate->error.value = BoolGetDatum(true);
+ return jsestate->jump_error;
+ }
+
+ return jump_eval_coercion >= 0 ? jump_eval_coercion : jsestate->jump_end;
+}
+
+/*
+ * Convert the given JsonbValue to its C string representation
+ *
+ * *resnull is set if the JsonbValue is a jbvNull.
+ */
+static char *
+ExecGetJsonValueItemString(JsonbValue *item, bool *resnull)
+{
+ *resnull = false;
+
+ /* get coercion state reference and datum of the corresponding SQL type */
+ switch (item->type)
+ {
+ case jbvNull:
+ *resnull = true;
+ return NULL;
+
+ case jbvString:
+ {
+ char *str = palloc(item->val.string.len + 1);
+
+ memcpy(str, item->val.string.val, item->val.string.len);
+ str[item->val.string.len] = '\0';
+ return str;
+ }
+
+ case jbvNumeric:
+ return DatumGetCString(DirectFunctionCall1(numeric_out,
+ NumericGetDatum(item->val.numeric)));
+
+ case jbvBool:
+ return DatumGetCString(DirectFunctionCall1(boolout,
+ BoolGetDatum(item->val.boolean)));
+
+ case jbvDatetime:
+ switch (item->val.datetime.typid)
+ {
+ case DATEOID:
+ return DatumGetCString(DirectFunctionCall1(date_out,
+ item->val.datetime.value));
+ case TIMEOID:
+ return DatumGetCString(DirectFunctionCall1(time_out,
+ item->val.datetime.value));
+ case TIMETZOID:
+ return DatumGetCString(DirectFunctionCall1(timetz_out,
+ item->val.datetime.value));
+ case TIMESTAMPOID:
+ return DatumGetCString(DirectFunctionCall1(timestamp_out,
+ item->val.datetime.value));
+ case TIMESTAMPTZOID:
+ return DatumGetCString(DirectFunctionCall1(timestamptz_out,
+ item->val.datetime.value));
+ default:
+ elog(ERROR, "unexpected jsonb datetime type oid %u",
+ item->val.datetime.typid);
+ }
+ break;
+
+ case jbvArray:
+ case jbvObject:
+ case jbvBinary:
+ return DatumGetCString(DirectFunctionCall1(jsonb_out,
+ JsonbPGetDatum(JsonbValueToJsonb(item))));
+
+ default:
+ elog(ERROR, "unexpected jsonb value type %d", item->type);
+ }
+
+ Assert(false);
+ *resnull = true;
+ return NULL;
+}
+
+/*
+ * Coerce a jsonb value produced by ExecEvalJsonExprPath() or an ON ERROR /
+ * ON EMPTY behavior expression to the target type.
+ *
+ * Any soft errors that occur here will be checked by
+ * EEOP_JSONEXPR_COERCION_FINISH that will run after this.
+ */
+void
+ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ErrorSaveContext *escontext = op->d.jsonexpr_coercion.escontext;
+
+ *op->resvalue = json_populate_type(*op->resvalue, JSONBOID,
+ op->d.jsonexpr_coercion.targettype,
+ op->d.jsonexpr_coercion.targettypmod,
+ &op->d.jsonexpr_coercion.json_populate_type_cache,
+ econtext->ecxt_per_query_memory,
+ op->resnull, (Node *) escontext);
+}
+
+/*
+ * Checks if an error occurred either when evaluating JsonExpr.coercion_expr or
+ * in ExecEvalJsonCoercion(). If so, this sets JsonExprState.error to trigger
+ * the ON ERROR handling steps.
+ */
+void
+ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op)
+{
+ JsonExprState *jsestate = op->d.jsonexpr.jsestate;
+
+ if (SOFT_ERROR_OCCURRED(&jsestate->escontext))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ jsestate->error.value = BoolGetDatum(true);
+ }
+}
/*
* ExecEvalGroupingFunc