Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * jsonbsubs.c
4 : * Subscripting support functions for jsonb.
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/utils/adt/jsonbsubs.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "catalog/pg_type_d.h"
18 : #include "executor/execExpr.h"
19 : #include "nodes/nodeFuncs.h"
20 : #include "nodes/subscripting.h"
21 : #include "parser/parse_coerce.h"
22 : #include "parser/parse_expr.h"
23 : #include "utils/builtins.h"
24 : #include "utils/jsonb.h"
25 :
26 :
27 : /* SubscriptingRefState.workspace for jsonb subscripting execution */
28 : typedef struct JsonbSubWorkspace
29 : {
30 : bool expectArray; /* jsonb root is expected to be an array */
31 : Oid *indexOid; /* OID of coerced subscript expression, could
32 : * be only integer or text */
33 : Datum *index; /* Subscript values in Datum format */
34 : } JsonbSubWorkspace;
35 :
36 :
37 : /*
38 : * Finish parse analysis of a SubscriptingRef expression for a jsonb.
39 : *
40 : * Transform the subscript expressions, coerce them to text,
41 : * and determine the result type of the SubscriptingRef node.
42 : */
43 : static void
44 280 : jsonb_subscript_transform(SubscriptingRef *sbsref,
45 : List *indirection,
46 : ParseState *pstate,
47 : bool isSlice,
48 : bool isAssignment)
49 : {
50 280 : List *upperIndexpr = NIL;
51 : ListCell *idx;
52 :
53 : /*
54 : * Transform and convert the subscript expressions. Jsonb subscripting
55 : * does not support slices, look only at the upper index.
56 : */
57 756 : foreach(idx, indirection)
58 : {
59 500 : A_Indices *ai = lfirst_node(A_Indices, idx);
60 : Node *subExpr;
61 :
62 500 : if (isSlice)
63 : {
64 20 : Node *expr = ai->uidx ? ai->uidx : ai->lidx;
65 :
66 20 : ereport(ERROR,
67 : (errcode(ERRCODE_DATATYPE_MISMATCH),
68 : errmsg("jsonb subscript does not support slices"),
69 : parser_errposition(pstate, exprLocation(expr))));
70 : }
71 :
72 480 : if (ai->uidx)
73 : {
74 480 : Oid subExprType = InvalidOid,
75 480 : targetType = UNKNOWNOID;
76 :
77 480 : subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
78 480 : subExprType = exprType(subExpr);
79 :
80 480 : if (subExprType != UNKNOWNOID)
81 : {
82 208 : Oid targets[2] = {INT4OID, TEXTOID};
83 :
84 : /*
85 : * Jsonb can handle multiple subscript types, but cases when a
86 : * subscript could be coerced to multiple target types must be
87 : * avoided, similar to overloaded functions. It could be
88 : * possibly extend with jsonpath in the future.
89 : */
90 624 : for (int i = 0; i < 2; i++)
91 : {
92 416 : if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
93 : {
94 : /*
95 : * One type has already succeeded, it means there are
96 : * two coercion targets possible, failure.
97 : */
98 204 : if (targetType != UNKNOWNOID)
99 0 : ereport(ERROR,
100 : (errcode(ERRCODE_DATATYPE_MISMATCH),
101 : errmsg("subscript type %s is not supported", format_type_be(subExprType)),
102 : errhint("jsonb subscript must be coercible to only one type, integer or text."),
103 : parser_errposition(pstate, exprLocation(subExpr))));
104 :
105 204 : targetType = targets[i];
106 : }
107 : }
108 :
109 : /*
110 : * No suitable types were found, failure.
111 : */
112 208 : if (targetType == UNKNOWNOID)
113 4 : ereport(ERROR,
114 : (errcode(ERRCODE_DATATYPE_MISMATCH),
115 : errmsg("subscript type %s is not supported", format_type_be(subExprType)),
116 : errhint("jsonb subscript must be coercible to either integer or text."),
117 : parser_errposition(pstate, exprLocation(subExpr))));
118 : }
119 : else
120 272 : targetType = TEXTOID;
121 :
122 : /*
123 : * We known from can_coerce_type that coercion will succeed, so
124 : * coerce_type could be used. Note the implicit coercion context,
125 : * which is required to handle subscripts of different types,
126 : * similar to overloaded functions.
127 : */
128 476 : subExpr = coerce_type(pstate,
129 : subExpr, subExprType,
130 : targetType, -1,
131 : COERCION_IMPLICIT,
132 : COERCE_IMPLICIT_CAST,
133 : -1);
134 476 : if (subExpr == NULL)
135 0 : ereport(ERROR,
136 : (errcode(ERRCODE_DATATYPE_MISMATCH),
137 : errmsg("jsonb subscript must have text type"),
138 : parser_errposition(pstate, exprLocation(subExpr))));
139 : }
140 : else
141 : {
142 : /*
143 : * Slice with omitted upper bound. Should not happen as we already
144 : * errored out on slice earlier, but handle this just in case.
145 : */
146 : Assert(isSlice && ai->is_slice);
147 0 : ereport(ERROR,
148 : (errcode(ERRCODE_DATATYPE_MISMATCH),
149 : errmsg("jsonb subscript does not support slices"),
150 : parser_errposition(pstate, exprLocation(ai->uidx))));
151 : }
152 :
153 476 : upperIndexpr = lappend(upperIndexpr, subExpr);
154 : }
155 :
156 : /* store the transformed lists into the SubscriptingRef node */
157 256 : sbsref->refupperindexpr = upperIndexpr;
158 256 : sbsref->reflowerindexpr = NIL;
159 :
160 : /* Determine the result type of the subscripting operation; always jsonb */
161 256 : sbsref->refrestype = JSONBOID;
162 256 : sbsref->reftypmod = -1;
163 256 : }
164 :
165 : /*
166 : * During execution, process the subscripts in a SubscriptingRef expression.
167 : *
168 : * The subscript expressions are already evaluated in Datum form in the
169 : * SubscriptingRefState's arrays. Check and convert them as necessary.
170 : *
171 : * If any subscript is NULL, we throw error in assignment cases, or in fetch
172 : * cases set result to NULL and return false (instructing caller to skip the
173 : * rest of the SubscriptingRef sequence).
174 : */
175 : static bool
176 328 : jsonb_subscript_check_subscripts(ExprState *state,
177 : ExprEvalStep *op,
178 : ExprContext *econtext)
179 : {
180 328 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
181 328 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
182 :
183 : /*
184 : * In case if the first subscript is an integer, the source jsonb is
185 : * expected to be an array. This information is not used directly, all
186 : * such cases are handled within corresponding jsonb assign functions. But
187 : * if the source jsonb is NULL the expected type will be used to construct
188 : * an empty source.
189 : */
190 328 : if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
191 328 : !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
192 96 : workspace->expectArray = true;
193 :
194 : /* Process upper subscripts */
195 877 : for (int i = 0; i < sbsrefstate->numupper; i++)
196 : {
197 563 : if (sbsrefstate->upperprovided[i])
198 : {
199 : /* If any index expr yields NULL, result is NULL or error */
200 563 : if (sbsrefstate->upperindexnull[i])
201 : {
202 14 : if (sbsrefstate->isassignment)
203 4 : ereport(ERROR,
204 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
205 : errmsg("jsonb subscript in assignment must not be null")));
206 10 : *op->resnull = true;
207 10 : return false;
208 : }
209 :
210 : /*
211 : * For jsonb fetch and assign functions we need to provide path in
212 : * text format. Convert if it's not already text.
213 : */
214 549 : if (workspace->indexOid[i] == INT4OID)
215 : {
216 213 : Datum datum = sbsrefstate->upperindex[i];
217 213 : char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
218 :
219 213 : workspace->index[i] = CStringGetTextDatum(cs);
220 : }
221 : else
222 336 : workspace->index[i] = sbsrefstate->upperindex[i];
223 : }
224 : }
225 :
226 314 : return true;
227 : }
228 :
229 : /*
230 : * Evaluate SubscriptingRef fetch for a jsonb element.
231 : *
232 : * Source container is in step's result variable (it's known not NULL, since
233 : * we set fetch_strict to true).
234 : */
235 : static void
236 150 : jsonb_subscript_fetch(ExprState *state,
237 : ExprEvalStep *op,
238 : ExprContext *econtext)
239 : {
240 150 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
241 150 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
242 : Jsonb *jsonbSource;
243 :
244 : /* Should not get here if source jsonb (or any subscript) is null */
245 : Assert(!(*op->resnull));
246 :
247 150 : jsonbSource = DatumGetJsonbP(*op->resvalue);
248 300 : *op->resvalue = jsonb_get_element(jsonbSource,
249 150 : workspace->index,
250 : sbsrefstate->numupper,
251 : op->resnull,
252 : false);
253 150 : }
254 :
255 : /*
256 : * Evaluate SubscriptingRef assignment for a jsonb element assignment.
257 : *
258 : * Input container (possibly null) is in result area, replacement value is in
259 : * SubscriptingRefState's replacevalue/replacenull.
260 : */
261 : static void
262 164 : jsonb_subscript_assign(ExprState *state,
263 : ExprEvalStep *op,
264 : ExprContext *econtext)
265 : {
266 164 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
267 164 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
268 : Jsonb *jsonbSource;
269 : JsonbValue replacevalue;
270 :
271 164 : if (sbsrefstate->replacenull)
272 8 : replacevalue.type = jbvNull;
273 : else
274 156 : JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue),
275 : &replacevalue);
276 :
277 : /*
278 : * In case if the input container is null, set up an empty jsonb and
279 : * proceed with the assignment.
280 : */
281 164 : if (*op->resnull)
282 : {
283 : JsonbValue newSource;
284 :
285 : /*
286 : * To avoid any surprising results, set up an empty jsonb array in
287 : * case of an array is expected (i.e. the first subscript is integer),
288 : * otherwise jsonb object.
289 : */
290 8 : if (workspace->expectArray)
291 : {
292 4 : newSource.type = jbvArray;
293 4 : newSource.val.array.nElems = 0;
294 4 : newSource.val.array.rawScalar = false;
295 : }
296 : else
297 : {
298 4 : newSource.type = jbvObject;
299 4 : newSource.val.object.nPairs = 0;
300 : }
301 :
302 8 : jsonbSource = JsonbValueToJsonb(&newSource);
303 8 : *op->resnull = false;
304 : }
305 : else
306 156 : jsonbSource = DatumGetJsonbP(*op->resvalue);
307 :
308 296 : *op->resvalue = jsonb_set_element(jsonbSource,
309 164 : workspace->index,
310 : sbsrefstate->numupper,
311 : &replacevalue);
312 : /* The result is never NULL, so no need to change *op->resnull */
313 132 : }
314 :
315 : /*
316 : * Compute old jsonb element value for a SubscriptingRef assignment
317 : * expression. Will only be called if the new-value subexpression
318 : * contains SubscriptingRef or FieldStore. This is the same as the
319 : * regular fetch case, except that we have to handle a null jsonb,
320 : * and the value should be stored into the SubscriptingRefState's
321 : * prevvalue/prevnull fields.
322 : */
323 : static void
324 0 : jsonb_subscript_fetch_old(ExprState *state,
325 : ExprEvalStep *op,
326 : ExprContext *econtext)
327 : {
328 0 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
329 :
330 0 : if (*op->resnull)
331 : {
332 : /* whole jsonb is null, so any element is too */
333 0 : sbsrefstate->prevvalue = (Datum) 0;
334 0 : sbsrefstate->prevnull = true;
335 : }
336 : else
337 : {
338 0 : Jsonb *jsonbSource = DatumGetJsonbP(*op->resvalue);
339 :
340 0 : sbsrefstate->prevvalue = jsonb_get_element(jsonbSource,
341 0 : sbsrefstate->upperindex,
342 : sbsrefstate->numupper,
343 : &sbsrefstate->prevnull,
344 : false);
345 : }
346 0 : }
347 :
348 : /*
349 : * Set up execution state for a jsonb subscript operation. Opposite to the
350 : * arrays subscription, there is no limit for number of subscripts as jsonb
351 : * type itself doesn't have nesting limits.
352 : */
353 : static void
354 280 : jsonb_exec_setup(const SubscriptingRef *sbsref,
355 : SubscriptingRefState *sbsrefstate,
356 : SubscriptExecSteps *methods)
357 : {
358 : JsonbSubWorkspace *workspace;
359 : ListCell *lc;
360 280 : int nupper = sbsref->refupperindexpr->length;
361 : char *ptr;
362 :
363 : /* Allocate type-specific workspace with space for per-subscript data */
364 280 : workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
365 280 : nupper * (sizeof(Datum) + sizeof(Oid)));
366 280 : workspace->expectArray = false;
367 280 : ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
368 :
369 : /*
370 : * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
371 : * misalign the indexOid pointer
372 : */
373 280 : workspace->index = (Datum *) ptr;
374 280 : ptr += nupper * sizeof(Datum);
375 280 : workspace->indexOid = (Oid *) ptr;
376 :
377 280 : sbsrefstate->workspace = workspace;
378 :
379 : /* Collect subscript data types necessary at execution time */
380 795 : foreach(lc, sbsref->refupperindexpr)
381 : {
382 515 : Node *expr = lfirst(lc);
383 515 : int i = foreach_current_index(lc);
384 :
385 515 : workspace->indexOid[i] = exprType(expr);
386 : }
387 :
388 : /*
389 : * Pass back pointers to appropriate step execution functions.
390 : */
391 280 : methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
392 280 : methods->sbs_fetch = jsonb_subscript_fetch;
393 280 : methods->sbs_assign = jsonb_subscript_assign;
394 280 : methods->sbs_fetch_old = jsonb_subscript_fetch_old;
395 280 : }
396 :
397 : /*
398 : * jsonb_subscript_handler
399 : * Subscripting handler for jsonb.
400 : *
401 : */
402 : Datum
403 560 : jsonb_subscript_handler(PG_FUNCTION_ARGS)
404 : {
405 : static const SubscriptRoutines sbsroutines = {
406 : .transform = jsonb_subscript_transform,
407 : .exec_setup = jsonb_exec_setup,
408 : .fetch_strict = true, /* fetch returns NULL for NULL inputs */
409 : .fetch_leakproof = true, /* fetch returns NULL for bad subscript */
410 : .store_leakproof = false /* ... but assignment throws error */
411 : };
412 :
413 560 : PG_RETURN_POINTER(&sbsroutines);
414 : }
|