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-2024, 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
1169 akorotkov@postgresql 43 :CBC 210 : jsonb_subscript_transform(SubscriptingRef *sbsref,
44 : : List *indirection,
45 : : ParseState *pstate,
46 : : bool isSlice,
47 : : bool isAssignment)
48 : : {
49 : 210 : List *upperIndexpr = NIL;
50 : : 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 [ + - + + : 567 : foreach(idx, indirection)
+ + ]
57 : : {
58 : 375 : A_Indices *ai = lfirst_node(A_Indices, idx);
59 : : Node *subExpr;
60 : :
61 [ + + ]: 375 : if (isSlice)
62 : : {
63 [ + + ]: 15 : Node *expr = ai->uidx ? ai->uidx : ai->lidx;
64 : :
65 [ + - ]: 15 : ereport(ERROR,
66 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
67 : : errmsg("jsonb subscript does not support slices"),
68 : : parser_errposition(pstate, exprLocation(expr))));
69 : : }
70 : :
71 [ + - ]: 360 : if (ai->uidx)
72 : : {
73 : 360 : Oid subExprType = InvalidOid,
74 : 360 : targetType = UNKNOWNOID;
75 : :
76 : 360 : subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
77 : 360 : subExprType = exprType(subExpr);
78 : :
79 [ + + ]: 360 : if (subExprType != UNKNOWNOID)
80 : : {
81 : 156 : 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 [ + + ]: 468 : for (int i = 0; i < 2; i++)
90 : : {
91 [ + + ]: 312 : if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
92 : : {
93 : : /*
94 : : * One type has already succeeded, it means there are
95 : : * two coercion targets possible, failure.
96 : : */
97 [ - + ]: 153 : if (targetType != UNKNOWNOID)
1169 akorotkov@postgresql 98 [ # # ]:UBC 0 : ereport(ERROR,
99 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
100 : : errmsg("subscript type %s is not supported", format_type_be(subExprType)),
101 : : errhint("jsonb subscript must be coercible to only one type, integer or text."),
102 : : parser_errposition(pstate, exprLocation(subExpr))));
103 : :
1169 akorotkov@postgresql 104 :CBC 153 : targetType = targets[i];
105 : : }
106 : : }
107 : :
108 : : /*
109 : : * No suitable types were found, failure.
110 : : */
111 [ + + ]: 156 : if (targetType == UNKNOWNOID)
112 [ + - ]: 3 : ereport(ERROR,
113 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
114 : : errmsg("subscript type %s is not supported", format_type_be(subExprType)),
115 : : errhint("jsonb subscript must be coercible to either integer or text."),
116 : : parser_errposition(pstate, exprLocation(subExpr))));
117 : : }
118 : : else
119 : 204 : 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 : 357 : subExpr = coerce_type(pstate,
128 : : subExpr, subExprType,
129 : : targetType, -1,
130 : : COERCION_IMPLICIT,
131 : : COERCE_IMPLICIT_CAST,
132 : : -1);
133 [ - + ]: 357 : if (subExpr == NULL)
1169 akorotkov@postgresql 134 [ # # ]:UBC 0 : ereport(ERROR,
135 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
136 : : errmsg("jsonb subscript must have text type"),
137 : : parser_errposition(pstate, exprLocation(subExpr))));
138 : : }
139 : : else
140 : : {
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 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
148 : : errmsg("jsonb subscript does not support slices"),
149 : : parser_errposition(pstate, exprLocation(ai->uidx))));
150 : : }
151 : :
1169 akorotkov@postgresql 152 :CBC 357 : upperIndexpr = lappend(upperIndexpr, subExpr);
153 : : }
154 : :
155 : : /* store the transformed lists into the SubscriptRef node */
156 : 192 : sbsref->refupperindexpr = upperIndexpr;
157 : 192 : sbsref->reflowerindexpr = NIL;
158 : :
159 : : /* Determine the result type of the subscripting operation; always jsonb */
160 : 192 : sbsref->refrestype = JSONBOID;
161 : 192 : sbsref->reftypmod = -1;
162 : 192 : }
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 : 228 : jsonb_subscript_check_subscripts(ExprState *state,
176 : : ExprEvalStep *op,
177 : : ExprContext *econtext)
178 : : {
179 : 228 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
180 : 228 : 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 [ + - + - ]: 228 : if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
190 [ + + + + ]: 228 : !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
191 : 66 : workspace->expectArray = true;
192 : :
193 : : /* Process upper subscripts */
194 [ + + ]: 612 : for (int i = 0; i < sbsrefstate->numupper; i++)
195 : : {
196 [ + - ]: 393 : if (sbsrefstate->upperprovided[i])
197 : : {
198 : : /* If any index expr yields NULL, result is NULL or error */
199 [ + + ]: 393 : if (sbsrefstate->upperindexnull[i])
200 : : {
201 [ + + ]: 9 : if (sbsrefstate->isassignment)
202 [ + - ]: 3 : ereport(ERROR,
203 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
204 : : errmsg("jsonb subscript in assignment must not be null")));
205 : 6 : *op->resnull = true;
206 : 6 : return false;
207 : : }
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 [ + + ]: 384 : if (workspace->indexOid[i] == INT4OID)
214 : : {
215 : 150 : Datum datum = sbsrefstate->upperindex[i];
216 : 150 : char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
217 : :
218 : 150 : workspace->index[i] = CStringGetTextDatum(cs);
219 : : }
220 : : else
221 : 234 : workspace->index[i] = sbsrefstate->upperindex[i];
222 : : }
223 : : }
224 : :
225 : 219 : return true;
226 : : }
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 : 96 : jsonb_subscript_fetch(ExprState *state,
236 : : ExprEvalStep *op,
237 : : ExprContext *econtext)
238 : : {
239 : 96 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
240 : 96 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
241 : : Jsonb *jsonbSource;
242 : :
243 : : /* Should not get here if source jsonb (or any subscript) is null */
244 [ - + ]: 96 : Assert(!(*op->resnull));
245 : :
246 : 96 : jsonbSource = DatumGetJsonbP(*op->resvalue);
247 : 96 : *op->resvalue = jsonb_get_element(jsonbSource,
248 : : workspace->index,
249 : : sbsrefstate->numupper,
250 : : op->resnull,
251 : : false);
252 : 96 : }
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 : 123 : jsonb_subscript_assign(ExprState *state,
262 : : ExprEvalStep *op,
263 : : ExprContext *econtext)
264 : : {
265 : 123 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
266 : 123 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
267 : : Jsonb *jsonbSource;
268 : : JsonbValue replacevalue;
269 : :
270 [ + + ]: 123 : if (sbsrefstate->replacenull)
271 : 6 : replacevalue.type = jbvNull;
272 : : else
273 : 117 : JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue),
274 : : &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 [ + + ]: 123 : if (*op->resnull)
281 : : {
282 : : 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 [ + + ]: 6 : if (workspace->expectArray)
290 : : {
1168 291 : 3 : newSource.type = jbvArray;
292 : 3 : newSource.val.array.nElems = 0;
293 : 3 : newSource.val.array.rawScalar = false;
294 : : }
295 : : else
296 : : {
297 : 3 : newSource.type = jbvObject;
298 : 3 : newSource.val.object.nPairs = 0;
299 : : }
300 : :
301 : 6 : jsonbSource = JsonbValueToJsonb(&newSource);
1169 302 : 6 : *op->resnull = false;
303 : : }
304 : : else
305 : 117 : jsonbSource = DatumGetJsonbP(*op->resvalue);
306 : :
307 : 123 : *op->resvalue = jsonb_set_element(jsonbSource,
308 : : workspace->index,
309 : : sbsrefstate->numupper,
310 : : &replacevalue);
311 : : /* The result is never NULL, so no need to change *op->resnull */
312 : 99 : }
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
1169 akorotkov@postgresql 323 :UBC 0 : jsonb_subscript_fetch_old(ExprState *state,
324 : : ExprEvalStep *op,
325 : : ExprContext *econtext)
326 : : {
327 : 0 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
328 : :
329 [ # # ]: 0 : if (*op->resnull)
330 : : {
331 : : /* whole jsonb is null, so any element is too */
332 : 0 : sbsrefstate->prevvalue = (Datum) 0;
333 : 0 : sbsrefstate->prevnull = true;
334 : : }
335 : : else
336 : : {
337 : 0 : Jsonb *jsonbSource = DatumGetJsonbP(*op->resvalue);
338 : :
339 : 0 : sbsrefstate->prevvalue = jsonb_get_element(jsonbSource,
340 : : sbsrefstate->upperindex,
341 : : sbsrefstate->numupper,
342 : : &sbsrefstate->prevnull,
343 : : false);
344 : : }
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
1169 akorotkov@postgresql 353 :CBC 192 : jsonb_exec_setup(const SubscriptingRef *sbsref,
354 : : SubscriptingRefState *sbsrefstate,
355 : : SubscriptExecSteps *methods)
356 : : {
357 : : JsonbSubWorkspace *workspace;
358 : : ListCell *lc;
359 : 192 : int nupper = sbsref->refupperindexpr->length;
360 : : char *ptr;
361 : :
362 : : /* Allocate type-specific workspace with space for per-subscript data */
363 : 192 : workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
364 : 192 : nupper * (sizeof(Datum) + sizeof(Oid)));
365 : 192 : workspace->expectArray = false;
366 : 192 : 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 : 192 : workspace->index = (Datum *) ptr;
1168 tgl@sss.pgh.pa.us 373 : 192 : ptr += nupper * sizeof(Datum);
374 : 192 : workspace->indexOid = (Oid *) ptr;
375 : :
1169 akorotkov@postgresql 376 : 192 : sbsrefstate->workspace = workspace;
377 : :
378 : : /* Collect subscript data types necessary at execution time */
379 [ + - + + : 549 : foreach(lc, sbsref->refupperindexpr)
+ + ]
380 : : {
381 : 357 : Node *expr = lfirst(lc);
382 : 357 : int i = foreach_current_index(lc);
383 : :
384 : 357 : workspace->indexOid[i] = exprType(expr);
385 : : }
386 : :
387 : : /*
388 : : * Pass back pointers to appropriate step execution functions.
389 : : */
390 : 192 : methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
391 : 192 : methods->sbs_fetch = jsonb_subscript_fetch;
392 : 192 : methods->sbs_assign = jsonb_subscript_assign;
393 : 192 : methods->sbs_fetch_old = jsonb_subscript_fetch_old;
394 : 192 : }
395 : :
396 : : /*
397 : : * jsonb_subscript_handler
398 : : * Subscripting handler for jsonb.
399 : : *
400 : : */
401 : : Datum
402 : 402 : jsonb_subscript_handler(PG_FUNCTION_ARGS)
403 : : {
404 : : static const SubscriptRoutines sbsroutines = {
405 : : .transform = jsonb_subscript_transform,
406 : : .exec_setup = jsonb_exec_setup,
407 : : .fetch_strict = true, /* fetch returns NULL for NULL inputs */
408 : : .fetch_leakproof = true, /* fetch returns NULL for bad subscript */
409 : : .store_leakproof = false /* ... but assignment throws error */
410 : : };
411 : :
412 : 402 : PG_RETURN_POINTER(&sbsroutines);
413 : : }
|