/*------------------------------------------------------------------------- * * jsonbsubs.c * Subscripting support functions for jsonb. * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/adt/jsonbsubs.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "executor/execExpr.h" #include "nodes/nodeFuncs.h" #include "nodes/subscripting.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "utils/builtins.h" #include "utils/jsonb.h" /* SubscriptingRefState.workspace for jsonb subscripting execution */ typedef struct JsonbSubWorkspace { bool expectArray; /* jsonb root is expected to be an array */ Oid *indexOid; /* OID of coerced subscript expression, could * be only integer or text */ Datum *index; /* Subscript values in Datum format */ } JsonbSubWorkspace; /* * Finish parse analysis of a SubscriptingRef expression for a jsonb. * * Transform the subscript expressions, coerce them to text, * and determine the result type of the SubscriptingRef node. */ static void jsonb_subscript_transform(SubscriptingRef *sbsref, List *indirection, ParseState *pstate, bool isSlice, bool isAssignment) { List *upperIndexpr = NIL; ListCell *idx; /* * Transform and convert the subscript expressions. Jsonb subscripting * does not support slices, look only and the upper index. */ foreach(idx, indirection) { A_Indices *ai = lfirst_node(A_Indices, idx); Node *subExpr; if (isSlice) { Node *expr = ai->uidx ? ai->uidx : ai->lidx; ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("jsonb subscript does not support slices"), parser_errposition(pstate, exprLocation(expr)))); } if (ai->uidx) { Oid subExprType = InvalidOid, targetType = UNKNOWNOID; subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); subExprType = exprType(subExpr); if (subExprType != UNKNOWNOID) { Oid targets[2] = {INT4OID, TEXTOID}; /* * Jsonb can handle multiple subscript types, but cases when a * subscript could be coerced to multiple target types must be * avoided, similar to overloaded functions. It could be * possibly extend with jsonpath in the future. */ for (int i = 0; i < 2; i++) { if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT)) { /* * One type has already succeeded, it means there are * two coercion targets possible, failure. */ if (targetType != UNKNOWNOID) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscript type %s is not supported", format_type_be(subExprType)), errhint("jsonb subscript must be coercible to only one type, integer or text."), parser_errposition(pstate, exprLocation(subExpr)))); targetType = targets[i]; } } /* * No suitable types were found, failure. */ if (targetType == UNKNOWNOID) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscript type %s is not supported", format_type_be(subExprType)), errhint("jsonb subscript must be coercible to either integer or text."), parser_errposition(pstate, exprLocation(subExpr)))); } else targetType = TEXTOID; /* * We known from can_coerce_type that coercion will succeed, so * coerce_type could be used. Note the implicit coercion context, * which is required to handle subscripts of different types, * similar to overloaded functions. */ subExpr = coerce_type(pstate, subExpr, subExprType, targetType, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); if (subExpr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("jsonb subscript must have text type"), parser_errposition(pstate, exprLocation(subExpr)))); } else { /* * Slice with omitted upper bound. Should not happen as we already * errored out on slice earlier, but handle this just in case. */ Assert(isSlice && ai->is_slice); ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("jsonb subscript does not support slices"), parser_errposition(pstate, exprLocation(ai->uidx)))); } upperIndexpr = lappend(upperIndexpr, subExpr); } /* store the transformed lists into the SubscriptRef node */ sbsref->refupperindexpr = upperIndexpr; sbsref->reflowerindexpr = NIL; /* Determine the result type of the subscripting operation; always jsonb */ sbsref->refrestype = JSONBOID; sbsref->reftypmod = -1; } /* * During execution, process the subscripts in a SubscriptingRef expression. * * The subscript expressions are already evaluated in Datum form in the * SubscriptingRefState's arrays. Check and convert them as necessary. * * If any subscript is NULL, we throw error in assignment cases, or in fetch * cases set result to NULL and return false (instructing caller to skip the * rest of the SubscriptingRef sequence). */ static bool jsonb_subscript_check_subscripts(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; /* * In case if the first subscript is an integer, the source jsonb is * expected to be an array. This information is not used directly, all * such cases are handled within corresponding jsonb assign functions. But * if the source jsonb is NULL the expected type will be used to construct * an empty source. */ if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] && !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID) workspace->expectArray = true; /* Process upper subscripts */ for (int i = 0; i < sbsrefstate->numupper; i++) { if (sbsrefstate->upperprovided[i]) { /* If any index expr yields NULL, result is NULL or error */ if (sbsrefstate->upperindexnull[i]) { if (sbsrefstate->isassignment) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("jsonb subscript in assignment must not be null"))); *op->resnull = true; return false; } /* * For jsonb fetch and assign functions we need to provide path in * text format. Convert if it's not already text. */ if (workspace->indexOid[i] == INT4OID) { Datum datum = sbsrefstate->upperindex[i]; char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum)); workspace->index[i] = CStringGetTextDatum(cs); } else workspace->index[i] = sbsrefstate->upperindex[i]; } } return true; } /* * Evaluate SubscriptingRef fetch for a jsonb element. * * Source container is in step's result variable (it's known not NULL, since * we set fetch_strict to true). */ static void jsonb_subscript_fetch(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; Jsonb *jsonbSource; /* Should not get here if source jsonb (or any subscript) is null */ Assert(!(*op->resnull)); jsonbSource = DatumGetJsonbP(*op->resvalue); *op->resvalue = jsonb_get_element(jsonbSource, workspace->index, sbsrefstate->numupper, op->resnull, false); } /* * Evaluate SubscriptingRef assignment for a jsonb element assignment. * * Input container (possibly null) is in result area, replacement value is in * SubscriptingRefState's replacevalue/replacenull. */ static void jsonb_subscript_assign(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; Jsonb *jsonbSource; JsonbValue replacevalue; if (sbsrefstate->replacenull) replacevalue.type = jbvNull; else JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue), &replacevalue); /* * In case if the input container is null, set up an empty jsonb and * proceed with the assignment. */ if (*op->resnull) { JsonbValue newSource; /* * To avoid any surprising results, set up an empty jsonb array in * case of an array is expected (i.e. the first subscript is integer), * otherwise jsonb object. */ if (workspace->expectArray) { newSource.type = jbvArray; newSource.val.array.nElems = 0; newSource.val.array.rawScalar = false; } else { newSource.type = jbvObject; newSource.val.object.nPairs = 0; } jsonbSource = JsonbValueToJsonb(&newSource); *op->resnull = false; } else jsonbSource = DatumGetJsonbP(*op->resvalue); *op->resvalue = jsonb_set_element(jsonbSource, workspace->index, sbsrefstate->numupper, &replacevalue); /* The result is never NULL, so no need to change *op->resnull */ } /* * Compute old jsonb element value for a SubscriptingRef assignment * expression. Will only be called if the new-value subexpression * contains SubscriptingRef or FieldStore. This is the same as the * regular fetch case, except that we have to handle a null jsonb, * and the value should be stored into the SubscriptingRefState's * prevvalue/prevnull fields. */ static void jsonb_subscript_fetch_old(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; if (*op->resnull) { /* whole jsonb is null, so any element is too */ sbsrefstate->prevvalue = (Datum) 0; sbsrefstate->prevnull = true; } else { Jsonb *jsonbSource = DatumGetJsonbP(*op->resvalue); sbsrefstate->prevvalue = jsonb_get_element(jsonbSource, sbsrefstate->upperindex, sbsrefstate->numupper, &sbsrefstate->prevnull, false); } } /* * Set up execution state for a jsonb subscript operation. Opposite to the * arrays subscription, there is no limit for number of subscripts as jsonb * type itself doesn't have nesting limits. */ static void jsonb_exec_setup(const SubscriptingRef *sbsref, SubscriptingRefState *sbsrefstate, SubscriptExecSteps *methods) { JsonbSubWorkspace *workspace; ListCell *lc; int nupper = sbsref->refupperindexpr->length; char *ptr; /* Allocate type-specific workspace with space for per-subscript data */ workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) + nupper * (sizeof(Datum) + sizeof(Oid))); workspace->expectArray = false; ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace)); /* * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might * misalign the indexOid pointer */ workspace->index = (Datum *) ptr; ptr += nupper * sizeof(Datum); workspace->indexOid = (Oid *) ptr; sbsrefstate->workspace = workspace; /* Collect subscript data types necessary at execution time */ foreach(lc, sbsref->refupperindexpr) { Node *expr = lfirst(lc); int i = foreach_current_index(lc); workspace->indexOid[i] = exprType(expr); } /* * Pass back pointers to appropriate step execution functions. */ methods->sbs_check_subscripts = jsonb_subscript_check_subscripts; methods->sbs_fetch = jsonb_subscript_fetch; methods->sbs_assign = jsonb_subscript_assign; methods->sbs_fetch_old = jsonb_subscript_fetch_old; } /* * jsonb_subscript_handler * Subscripting handler for jsonb. * */ Datum jsonb_subscript_handler(PG_FUNCTION_ARGS) { static const SubscriptRoutines sbsroutines = { .transform = jsonb_subscript_transform, .exec_setup = jsonb_exec_setup, .fetch_strict = true, /* fetch returns NULL for NULL inputs */ .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ .store_leakproof = false /* ... but assignment throws error */ }; PG_RETURN_POINTER(&sbsroutines); }