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