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