Age Owner TLA 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
798 akorotkov 46 CBC 210 : jsonb_subscript_transform(SubscriptingRef *sbsref,
47 : List *indirection,
48 : ParseState *pstate,
49 : bool isSlice,
50 : bool isAssignment)
51 : {
52 210 : 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 567 : foreach(idx, indirection)
60 : {
61 375 : A_Indices *ai = lfirst_node(A_Indices, idx);
62 : Node *subExpr;
63 :
64 375 : if (isSlice)
65 : {
66 15 : Node *expr = ai->uidx ? ai->uidx : ai->lidx;
67 :
68 15 : ereport(ERROR,
69 : (errcode(ERRCODE_DATATYPE_MISMATCH),
70 : errmsg("jsonb subscript does not support slices"),
71 : parser_errposition(pstate, exprLocation(expr))));
72 : }
73 :
74 360 : if (ai->uidx)
75 : {
76 360 : Oid subExprType = InvalidOid,
77 360 : targetType = UNKNOWNOID;
78 :
79 360 : subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
80 360 : subExprType = exprType(subExpr);
81 :
82 360 : if (subExprType != UNKNOWNOID)
83 : {
84 156 : 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 468 : for (int i = 0; i < 2; i++)
93 : {
94 312 : 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 153 : if (targetType != UNKNOWNOID)
798 akorotkov 101 UBC 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 :
798 akorotkov 107 CBC 153 : targetType = targets[i];
108 : }
109 : }
110 :
111 : /*
112 : * No suitable types were found, failure.
113 : */
114 156 : if (targetType == UNKNOWNOID)
115 3 : 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 204 : 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 357 : subExpr = coerce_type(pstate,
131 : subExpr, subExprType,
132 : targetType, -1,
133 : COERCION_IMPLICIT,
134 : COERCE_IMPLICIT_CAST,
135 : -1);
136 357 : if (subExpr == NULL)
798 akorotkov 137 UBC 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 0 : 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 :
798 akorotkov 155 CBC 357 : upperIndexpr = lappend(upperIndexpr, subExpr);
156 : }
157 :
158 : /* store the transformed lists into the SubscriptRef node */
159 192 : sbsref->refupperindexpr = upperIndexpr;
160 192 : sbsref->reflowerindexpr = NIL;
161 :
162 : /* Determine the result type of the subscripting operation; always jsonb */
163 192 : sbsref->refrestype = JSONBOID;
164 192 : sbsref->reftypmod = -1;
165 192 : }
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 228 : jsonb_subscript_check_subscripts(ExprState *state,
179 : ExprEvalStep *op,
180 : ExprContext *econtext)
181 : {
182 228 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
183 228 : 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 228 : if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
193 228 : !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
194 66 : workspace->expectArray = true;
195 :
196 : /* Process upper subscripts */
197 612 : for (int i = 0; i < sbsrefstate->numupper; i++)
198 : {
199 393 : if (sbsrefstate->upperprovided[i])
200 : {
201 : /* If any index expr yields NULL, result is NULL or error */
202 393 : if (sbsrefstate->upperindexnull[i])
203 : {
204 9 : if (sbsrefstate->isassignment)
205 3 : ereport(ERROR,
206 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
207 : errmsg("jsonb subscript in assignment must not be null")));
208 6 : *op->resnull = true;
209 6 : 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 384 : if (workspace->indexOid[i] == INT4OID)
217 : {
218 150 : Datum datum = sbsrefstate->upperindex[i];
219 150 : char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
220 :
221 150 : workspace->index[i] = CStringGetTextDatum(cs);
222 : }
223 : else
224 234 : workspace->index[i] = sbsrefstate->upperindex[i];
225 : }
226 : }
227 :
228 219 : 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 96 : jsonb_subscript_fetch(ExprState *state,
239 : ExprEvalStep *op,
240 : ExprContext *econtext)
241 : {
242 96 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
243 96 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
244 : Jsonb *jsonbSource;
245 :
246 : /* Should not get here if source jsonb (or any subscript) is null */
247 96 : Assert(!(*op->resnull));
248 :
249 96 : jsonbSource = DatumGetJsonbP(*op->resvalue);
250 96 : *op->resvalue = jsonb_get_element(jsonbSource,
251 : workspace->index,
252 : sbsrefstate->numupper,
253 : op->resnull,
254 : false);
255 96 : }
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 123 : jsonb_subscript_assign(ExprState *state,
265 : ExprEvalStep *op,
266 : ExprContext *econtext)
267 : {
268 123 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
269 123 : JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
270 : Jsonb *jsonbSource;
271 : JsonbValue replacevalue;
272 :
273 123 : if (sbsrefstate->replacenull)
274 6 : replacevalue.type = jbvNull;
275 : else
276 117 : 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 123 : 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 6 : if (workspace->expectArray)
293 : {
797 294 3 : newSource.type = jbvArray;
295 3 : newSource.val.array.nElems = 0;
296 3 : newSource.val.array.rawScalar = false;
297 : }
298 : else
299 : {
300 3 : newSource.type = jbvObject;
301 3 : newSource.val.object.nPairs = 0;
302 : }
303 :
304 6 : jsonbSource = JsonbValueToJsonb(&newSource);
798 305 6 : *op->resnull = false;
306 : }
307 : else
308 117 : jsonbSource = DatumGetJsonbP(*op->resvalue);
309 :
310 123 : *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 99 : }
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
798 akorotkov 326 UBC 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
798 akorotkov 356 CBC 192 : jsonb_exec_setup(const SubscriptingRef *sbsref,
357 : SubscriptingRefState *sbsrefstate,
358 : SubscriptExecSteps *methods)
359 : {
360 : JsonbSubWorkspace *workspace;
361 : ListCell *lc;
362 192 : int nupper = sbsref->refupperindexpr->length;
363 : char *ptr;
364 :
365 : /* Allocate type-specific workspace with space for per-subscript data */
366 192 : workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
367 192 : nupper * (sizeof(Datum) + sizeof(Oid)));
368 192 : workspace->expectArray = false;
369 192 : 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 192 : workspace->index = (Datum *) ptr;
797 tgl 376 192 : ptr += nupper * sizeof(Datum);
377 192 : workspace->indexOid = (Oid *) ptr;
378 :
798 akorotkov 379 192 : sbsrefstate->workspace = workspace;
380 :
381 : /* Collect subscript data types necessary at execution time */
382 549 : foreach(lc, sbsref->refupperindexpr)
383 : {
384 357 : Node *expr = lfirst(lc);
385 357 : int i = foreach_current_index(lc);
386 :
387 357 : workspace->indexOid[i] = exprType(expr);
388 : }
389 :
390 : /*
391 : * Pass back pointers to appropriate step execution functions.
392 : */
393 192 : methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
394 192 : methods->sbs_fetch = jsonb_subscript_fetch;
395 192 : methods->sbs_assign = jsonb_subscript_assign;
396 192 : methods->sbs_fetch_old = jsonb_subscript_fetch_old;
397 192 : }
398 :
399 : /*
400 : * jsonb_subscript_handler
401 : * Subscripting handler for jsonb.
402 : *
403 : */
404 : Datum
405 402 : 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 402 : PG_RETURN_POINTER(&sbsroutines);
416 : }
|