Age Owner Branch data TLA 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
1920 akorotkov@postgresql 44 :CBC 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)
1920 akorotkov@postgresql 99 [ # # ]:UBC 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 : :
1920 akorotkov@postgresql 105 :CBC 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)
1920 akorotkov@postgresql 135 [ # # ]:UBC 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 [ # # # # ]: 0 : 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 : :
1920 akorotkov@postgresql 153 :CBC 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 [ - + ]: 150 : Assert(!(*op->resnull));
246 : :
247 : 150 : jsonbSource = DatumGetJsonbP(*op->resvalue);
248 : 300 : *op->resvalue = jsonb_get_element(jsonbSource,
1920 akorotkov@postgresql 249 :GIC 150 : workspace->index,
250 : : sbsrefstate->numupper,
251 : : op->resnull,
252 : : false);
1920 akorotkov@postgresql 253 :CBC 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 : : {
1919 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);
1920 303 : 8 : *op->resnull = false;
304 : : }
305 : : else
306 : 156 : jsonbSource = DatumGetJsonbP(*op->resvalue);
307 : :
308 : 296 : *op->resvalue = jsonb_set_element(jsonbSource,
1920 akorotkov@postgresql 309 :GIC 164 : workspace->index,
310 : : sbsrefstate->numupper,
311 : : &replacevalue);
312 : : /* The result is never NULL, so no need to change *op->resnull */
1920 akorotkov@postgresql 313 :CBC 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
1920 akorotkov@postgresql 324 :UBC 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,
1920 akorotkov@postgresql 341 :UIC 0 : sbsrefstate->upperindex,
342 : : sbsrefstate->numupper,
343 : : &sbsrefstate->prevnull,
344 : : false);
345 : : }
1920 akorotkov@postgresql 346 :UBC 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
1920 akorotkov@postgresql 354 :CBC 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;
1919 tgl@sss.pgh.pa.us 374 : 280 : ptr += nupper * sizeof(Datum);
375 : 280 : workspace->indexOid = (Oid *) ptr;
376 : :
1920 akorotkov@postgresql 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 : : }
|