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