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
43 420 : jsonb_subscript_transform(SubscriptingRef *sbsref,
44 : List *indirection,
45 : ParseState *pstate,
46 : bool isSlice,
47 : bool isAssignment)
48 : {
49 420 : 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 1134 : foreach(idx, indirection)
57 : {
58 750 : A_Indices *ai = lfirst_node(A_Indices, idx);
59 : Node *subExpr;
60 :
61 750 : if (isSlice)
62 : {
63 30 : Node *expr = ai->uidx ? ai->uidx : ai->lidx;
64 :
65 30 : ereport(ERROR,
66 : (errcode(ERRCODE_DATATYPE_MISMATCH),
67 : errmsg("jsonb subscript does not support slices"),
68 : parser_errposition(pstate, exprLocation(expr))));
69 : }
70 :
71 720 : if (ai->uidx)
72 : {
73 720 : Oid subExprType = InvalidOid,
74 720 : targetType = UNKNOWNOID;
75 :
76 720 : subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
77 720 : subExprType = exprType(subExpr);
78 :
79 720 : if (subExprType != UNKNOWNOID)
80 : {
81 312 : 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 936 : for (int i = 0; i < 2; i++)
90 : {
91 624 : 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 306 : if (targetType != UNKNOWNOID)
98 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 :
104 306 : targetType = targets[i];
105 : }
106 : }
107 :
108 : /*
109 : * No suitable types were found, failure.
110 : */
111 312 : if (targetType == UNKNOWNOID)
112 6 : 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 408 : 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 714 : subExpr = coerce_type(pstate,
128 : subExpr, subExprType,
129 : targetType, -1,
130 : COERCION_IMPLICIT,
131 : COERCE_IMPLICIT_CAST,
132 : -1);
133 714 : if (subExpr == NULL)
134 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 : 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 :
152 714 : upperIndexpr = lappend(upperIndexpr, subExpr);
153 : }
154 :
155 : /* store the transformed lists into the SubscriptRef node */
156 384 : sbsref->refupperindexpr = upperIndexpr;
157 384 : sbsref->reflowerindexpr = NIL;
158 :
159 : /* Determine the result type of the subscripting operation; always jsonb */
160 384 : sbsref->refrestype = JSONBOID;
161 384 : sbsref->reftypmod = -1;
162 384 : }
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 456 : jsonb_subscript_check_subscripts(ExprState *state,
176 : ExprEvalStep *op,
177 : ExprContext *econtext)
178 : {
179 456 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
180 456 : 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 456 : if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
190 456 : !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
191 132 : workspace->expectArray = true;
192 :
193 : /* Process upper subscripts */
194 1224 : for (int i = 0; i < sbsrefstate->numupper; i++)
195 : {
196 786 : if (sbsrefstate->upperprovided[i])
197 : {
198 : /* If any index expr yields NULL, result is NULL or error */
199 786 : if (sbsrefstate->upperindexnull[i])
200 : {
201 18 : if (sbsrefstate->isassignment)
202 6 : ereport(ERROR,
203 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
204 : errmsg("jsonb subscript in assignment must not be null")));
205 12 : *op->resnull = true;
206 12 : 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 768 : if (workspace->indexOid[i] == INT4OID)
214 : {
215 300 : Datum datum = sbsrefstate->upperindex[i];
216 300 : char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
217 :
218 300 : workspace->index[i] = CStringGetTextDatum(cs);
219 : }
220 : else
221 468 : workspace->index[i] = sbsrefstate->upperindex[i];
222 : }
223 : }
224 :
225 438 : 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 192 : jsonb_subscript_fetch(ExprState *state,
236 : ExprEvalStep *op,
237 : ExprContext *econtext)
238 : {
239 192 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
240 192 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
241 : Jsonb *jsonbSource;
242 :
243 : /* Should not get here if source jsonb (or any subscript) is null */
244 : Assert(!(*op->resnull));
245 :
246 192 : jsonbSource = DatumGetJsonbP(*op->resvalue);
247 192 : *op->resvalue = jsonb_get_element(jsonbSource,
248 : workspace->index,
249 : sbsrefstate->numupper,
250 : op->resnull,
251 : false);
252 192 : }
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 246 : jsonb_subscript_assign(ExprState *state,
262 : ExprEvalStep *op,
263 : ExprContext *econtext)
264 : {
265 246 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
266 246 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
267 : Jsonb *jsonbSource;
268 : JsonbValue replacevalue;
269 :
270 246 : if (sbsrefstate->replacenull)
271 12 : replacevalue.type = jbvNull;
272 : else
273 234 : 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 246 : 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 12 : if (workspace->expectArray)
290 : {
291 6 : newSource.type = jbvArray;
292 6 : newSource.val.array.nElems = 0;
293 6 : newSource.val.array.rawScalar = false;
294 : }
295 : else
296 : {
297 6 : newSource.type = jbvObject;
298 6 : newSource.val.object.nPairs = 0;
299 : }
300 :
301 12 : jsonbSource = JsonbValueToJsonb(&newSource);
302 12 : *op->resnull = false;
303 : }
304 : else
305 234 : jsonbSource = DatumGetJsonbP(*op->resvalue);
306 :
307 246 : *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 198 : }
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 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
353 384 : jsonb_exec_setup(const SubscriptingRef *sbsref,
354 : SubscriptingRefState *sbsrefstate,
355 : SubscriptExecSteps *methods)
356 : {
357 : JsonbSubWorkspace *workspace;
358 : ListCell *lc;
359 384 : int nupper = sbsref->refupperindexpr->length;
360 : char *ptr;
361 :
362 : /* Allocate type-specific workspace with space for per-subscript data */
363 384 : workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
364 384 : nupper * (sizeof(Datum) + sizeof(Oid)));
365 384 : workspace->expectArray = false;
366 384 : 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 384 : workspace->index = (Datum *) ptr;
373 384 : ptr += nupper * sizeof(Datum);
374 384 : workspace->indexOid = (Oid *) ptr;
375 :
376 384 : sbsrefstate->workspace = workspace;
377 :
378 : /* Collect subscript data types necessary at execution time */
379 1098 : foreach(lc, sbsref->refupperindexpr)
380 : {
381 714 : Node *expr = lfirst(lc);
382 714 : int i = foreach_current_index(lc);
383 :
384 714 : workspace->indexOid[i] = exprType(expr);
385 : }
386 :
387 : /*
388 : * Pass back pointers to appropriate step execution functions.
389 : */
390 384 : methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
391 384 : methods->sbs_fetch = jsonb_subscript_fetch;
392 384 : methods->sbs_assign = jsonb_subscript_assign;
393 384 : methods->sbs_fetch_old = jsonb_subscript_fetch_old;
394 384 : }
395 :
396 : /*
397 : * jsonb_subscript_handler
398 : * Subscripting handler for jsonb.
399 : *
400 : */
401 : Datum
402 804 : 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 804 : PG_RETURN_POINTER(&sbsroutines);
413 : }
|