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