Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * arraysubs.c
4 : * Subscripting support functions for arrays.
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/utils/adt/arraysubs.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 "nodes/supportnodes.h"
22 : #include "parser/parse_coerce.h"
23 : #include "parser/parse_expr.h"
24 : #include "utils/array.h"
25 : #include "utils/fmgrprotos.h"
26 : #include "utils/lsyscache.h"
27 :
28 :
29 : /* SubscriptingRefState.workspace for array subscripting execution */
30 : typedef struct ArraySubWorkspace
31 : {
32 : /* Values determined during expression compilation */
33 : Oid refelemtype; /* OID of the array element type */
34 : int16 refattrlength; /* typlen of array type */
35 : int16 refelemlength; /* typlen of the array element type */
36 : bool refelembyval; /* is the element type pass-by-value? */
37 : char refelemalign; /* typalign of the element type */
38 :
39 : /*
40 : * Subscript values converted to integers. Note that these arrays must be
41 : * of length MAXDIM even when dealing with fewer subscripts, because
42 : * array_get/set_slice may scribble on the extra entries.
43 : */
44 : int upperindex[MAXDIM];
45 : int lowerindex[MAXDIM];
46 : } ArraySubWorkspace;
47 :
48 :
49 : /*
50 : * Finish parse analysis of a SubscriptingRef expression for an array.
51 : *
52 : * Transform the subscript expressions, coerce them to integers,
53 : * and determine the result type of the SubscriptingRef node.
54 : */
55 : static void
56 6845 : array_subscript_transform(SubscriptingRef *sbsref,
57 : List *indirection,
58 : ParseState *pstate,
59 : bool isSlice,
60 : bool isAssignment)
61 : {
62 6845 : List *upperIndexpr = NIL;
63 6845 : List *lowerIndexpr = NIL;
64 : ListCell *idx;
65 :
66 : /*
67 : * Transform the subscript expressions, and separate upper and lower
68 : * bounds into two lists.
69 : *
70 : * If we have a container slice expression, we convert any non-slice
71 : * indirection items to slices by treating the single subscript as the
72 : * upper bound and supplying an assumed lower bound of 1.
73 : */
74 13838 : foreach(idx, indirection)
75 : {
76 6993 : A_Indices *ai = lfirst_node(A_Indices, idx);
77 : Node *subexpr;
78 :
79 6993 : if (isSlice)
80 : {
81 310 : if (ai->lidx)
82 : {
83 244 : subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind);
84 : /* If it's not int4 already, try to coerce */
85 244 : subexpr = coerce_to_target_type(pstate,
86 : subexpr, exprType(subexpr),
87 : INT4OID, -1,
88 : COERCION_ASSIGNMENT,
89 : COERCE_IMPLICIT_CAST,
90 : -1);
91 244 : if (subexpr == NULL)
92 0 : ereport(ERROR,
93 : (errcode(ERRCODE_DATATYPE_MISMATCH),
94 : errmsg("array subscript must have type integer"),
95 : parser_errposition(pstate, exprLocation(ai->lidx))));
96 : }
97 66 : else if (!ai->is_slice)
98 : {
99 : /* Make a constant 1 */
100 27 : subexpr = (Node *) makeConst(INT4OID,
101 : -1,
102 : InvalidOid,
103 : sizeof(int32),
104 : Int32GetDatum(1),
105 : false,
106 : true); /* pass by value */
107 : }
108 : else
109 : {
110 : /* Slice with omitted lower bound, put NULL into the list */
111 39 : subexpr = NULL;
112 : }
113 310 : lowerIndexpr = lappend(lowerIndexpr, subexpr);
114 : }
115 : else
116 : Assert(ai->lidx == NULL && !ai->is_slice);
117 :
118 6993 : if (ai->uidx)
119 : {
120 6954 : subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
121 : /* If it's not int4 already, try to coerce */
122 6954 : subexpr = coerce_to_target_type(pstate,
123 : subexpr, exprType(subexpr),
124 : INT4OID, -1,
125 : COERCION_ASSIGNMENT,
126 : COERCE_IMPLICIT_CAST,
127 : -1);
128 6954 : if (subexpr == NULL)
129 0 : ereport(ERROR,
130 : (errcode(ERRCODE_DATATYPE_MISMATCH),
131 : errmsg("array subscript must have type integer"),
132 : parser_errposition(pstate, exprLocation(ai->uidx))));
133 : }
134 : else
135 : {
136 : /* Slice with omitted upper bound, put NULL into the list */
137 : Assert(isSlice && ai->is_slice);
138 39 : subexpr = NULL;
139 : }
140 6993 : upperIndexpr = lappend(upperIndexpr, subexpr);
141 : }
142 :
143 : /* ... and store the transformed lists into the SubscriptingRef node */
144 6845 : sbsref->refupperindexpr = upperIndexpr;
145 6845 : sbsref->reflowerindexpr = lowerIndexpr;
146 :
147 : /* Verify subscript list lengths are within implementation limit */
148 6845 : if (list_length(upperIndexpr) > MAXDIM)
149 3 : ereport(ERROR,
150 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
151 : errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
152 : list_length(upperIndexpr), MAXDIM)));
153 : /* We need not check lowerIndexpr separately */
154 :
155 : /*
156 : * Determine the result type of the subscripting operation. It's the same
157 : * as the array type if we're slicing, else it's the element type. In
158 : * either case, the typmod is the same as the array's, so we need not
159 : * change reftypmod.
160 : */
161 6842 : if (isSlice)
162 229 : sbsref->refrestype = sbsref->refcontainertype;
163 : else
164 6613 : sbsref->refrestype = sbsref->refelemtype;
165 6842 : }
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 : * We convert all the subscripts to plain integers and save them in the
178 : * sbsrefstate->workspace arrays.
179 : */
180 : static bool
181 444580 : array_subscript_check_subscripts(ExprState *state,
182 : ExprEvalStep *op,
183 : ExprContext *econtext)
184 : {
185 444580 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
186 444580 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
187 :
188 : /* Process upper subscripts */
189 889386 : for (int i = 0; i < sbsrefstate->numupper; i++)
190 : {
191 444818 : if (sbsrefstate->upperprovided[i])
192 : {
193 : /* If any index expr yields NULL, result is NULL or error */
194 444746 : if (sbsrefstate->upperindexnull[i])
195 : {
196 12 : if (sbsrefstate->isassignment)
197 6 : ereport(ERROR,
198 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
199 : errmsg("array subscript in assignment must not be null")));
200 6 : *op->resnull = true;
201 6 : return false;
202 : }
203 444734 : workspace->upperindex[i] = DatumGetInt32(sbsrefstate->upperindex[i]);
204 : }
205 : }
206 :
207 : /* Likewise for lower subscripts */
208 445024 : for (int i = 0; i < sbsrefstate->numlower; i++)
209 : {
210 462 : if (sbsrefstate->lowerprovided[i])
211 : {
212 : /* If any index expr yields NULL, result is NULL or error */
213 399 : if (sbsrefstate->lowerindexnull[i])
214 : {
215 6 : if (sbsrefstate->isassignment)
216 3 : ereport(ERROR,
217 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
218 : errmsg("array subscript in assignment must not be null")));
219 3 : *op->resnull = true;
220 3 : return false;
221 : }
222 393 : workspace->lowerindex[i] = DatumGetInt32(sbsrefstate->lowerindex[i]);
223 : }
224 : }
225 :
226 444562 : return true;
227 : }
228 :
229 : /*
230 : * Evaluate SubscriptingRef fetch for an array element.
231 : *
232 : * Source container is in step's result variable (it's known not NULL, since
233 : * we set fetch_strict to true), and indexes have already been evaluated into
234 : * workspace array.
235 : */
236 : static void
237 443674 : array_subscript_fetch(ExprState *state,
238 : ExprEvalStep *op,
239 : ExprContext *econtext)
240 : {
241 443674 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
242 443674 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
243 :
244 : /* Should not get here if source array (or any subscript) is null */
245 : Assert(!(*op->resnull));
246 :
247 887348 : *op->resvalue = array_get_element(*op->resvalue,
248 : sbsrefstate->numupper,
249 443674 : workspace->upperindex,
250 443674 : workspace->refattrlength,
251 443674 : workspace->refelemlength,
252 443674 : workspace->refelembyval,
253 443674 : workspace->refelemalign,
254 : op->resnull);
255 443674 : }
256 :
257 : /*
258 : * Evaluate SubscriptingRef fetch for an array slice.
259 : *
260 : * Source container is in step's result variable (it's known not NULL, since
261 : * we set fetch_strict to true), and indexes have already been evaluated into
262 : * workspace array.
263 : */
264 : static void
265 189 : array_subscript_fetch_slice(ExprState *state,
266 : ExprEvalStep *op,
267 : ExprContext *econtext)
268 : {
269 189 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
270 189 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
271 :
272 : /* Should not get here if source array (or any subscript) is null */
273 : Assert(!(*op->resnull));
274 :
275 366 : *op->resvalue = array_get_slice(*op->resvalue,
276 : sbsrefstate->numupper,
277 189 : workspace->upperindex,
278 189 : workspace->lowerindex,
279 : sbsrefstate->upperprovided,
280 : sbsrefstate->lowerprovided,
281 189 : workspace->refattrlength,
282 189 : workspace->refelemlength,
283 189 : workspace->refelembyval,
284 189 : workspace->refelemalign);
285 : /* The slice is never NULL, so no need to change *op->resnull */
286 177 : }
287 :
288 : /*
289 : * Evaluate SubscriptingRef assignment for an array element assignment.
290 : *
291 : * Input container (possibly null) is in result area, replacement value is in
292 : * SubscriptingRefState's replacevalue/replacenull.
293 : */
294 : static void
295 552 : array_subscript_assign(ExprState *state,
296 : ExprEvalStep *op,
297 : ExprContext *econtext)
298 : {
299 552 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
300 552 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
301 552 : Datum arraySource = *op->resvalue;
302 :
303 : /*
304 : * For an assignment to a fixed-length array type, both the original array
305 : * and the value to be assigned into it must be non-NULL, else we punt and
306 : * return the original array.
307 : */
308 552 : if (workspace->refattrlength > 0)
309 : {
310 18 : if (*op->resnull || sbsrefstate->replacenull)
311 9 : return;
312 : }
313 :
314 : /*
315 : * For assignment to varlena arrays, we handle a NULL original array by
316 : * substituting an empty (zero-dimensional) array; insertion of the new
317 : * element will result in a singleton array value. It does not matter
318 : * whether the new element is NULL.
319 : */
320 543 : if (*op->resnull)
321 : {
322 171 : arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
323 171 : *op->resnull = false;
324 : }
325 :
326 531 : *op->resvalue = array_set_element(arraySource,
327 : sbsrefstate->numupper,
328 543 : workspace->upperindex,
329 : sbsrefstate->replacevalue,
330 543 : sbsrefstate->replacenull,
331 543 : workspace->refattrlength,
332 543 : workspace->refelemlength,
333 543 : workspace->refelembyval,
334 543 : workspace->refelemalign);
335 : /* The result is never NULL, so no need to change *op->resnull */
336 : }
337 :
338 : /*
339 : * Evaluate SubscriptingRef assignment for an array slice assignment.
340 : *
341 : * Input container (possibly null) is in result area, replacement value is in
342 : * SubscriptingRefState's replacevalue/replacenull.
343 : */
344 : static void
345 131 : array_subscript_assign_slice(ExprState *state,
346 : ExprEvalStep *op,
347 : ExprContext *econtext)
348 : {
349 131 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
350 131 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
351 131 : Datum arraySource = *op->resvalue;
352 :
353 : /*
354 : * For an assignment to a fixed-length array type, both the original array
355 : * and the value to be assigned into it must be non-NULL, else we punt and
356 : * return the original array.
357 : */
358 131 : if (workspace->refattrlength > 0)
359 : {
360 0 : if (*op->resnull || sbsrefstate->replacenull)
361 0 : return;
362 : }
363 :
364 : /*
365 : * For assignment to varlena arrays, we handle a NULL original array by
366 : * substituting an empty (zero-dimensional) array; insertion of the new
367 : * element will result in a singleton array value. It does not matter
368 : * whether the new element is NULL.
369 : */
370 131 : if (*op->resnull)
371 : {
372 28 : arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
373 28 : *op->resnull = false;
374 : }
375 :
376 116 : *op->resvalue = array_set_slice(arraySource,
377 : sbsrefstate->numupper,
378 131 : workspace->upperindex,
379 131 : workspace->lowerindex,
380 : sbsrefstate->upperprovided,
381 : sbsrefstate->lowerprovided,
382 : sbsrefstate->replacevalue,
383 131 : sbsrefstate->replacenull,
384 131 : workspace->refattrlength,
385 131 : workspace->refelemlength,
386 131 : workspace->refelembyval,
387 131 : workspace->refelemalign);
388 : /* The result is never NULL, so no need to change *op->resnull */
389 : }
390 :
391 : /*
392 : * Compute old array element value for a SubscriptingRef assignment
393 : * expression. Will only be called if the new-value subexpression
394 : * contains SubscriptingRef or FieldStore. This is the same as the
395 : * regular fetch case, except that we have to handle a null array,
396 : * and the value should be stored into the SubscriptingRefState's
397 : * prevvalue/prevnull fields.
398 : */
399 : static void
400 138 : array_subscript_fetch_old(ExprState *state,
401 : ExprEvalStep *op,
402 : ExprContext *econtext)
403 : {
404 138 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
405 138 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
406 :
407 138 : if (*op->resnull)
408 : {
409 : /* whole array is null, so any element is too */
410 44 : sbsrefstate->prevvalue = (Datum) 0;
411 44 : sbsrefstate->prevnull = true;
412 : }
413 : else
414 94 : sbsrefstate->prevvalue = array_get_element(*op->resvalue,
415 : sbsrefstate->numupper,
416 94 : workspace->upperindex,
417 94 : workspace->refattrlength,
418 94 : workspace->refelemlength,
419 94 : workspace->refelembyval,
420 94 : workspace->refelemalign,
421 : &sbsrefstate->prevnull);
422 138 : }
423 :
424 : /*
425 : * Compute old array slice value for a SubscriptingRef assignment
426 : * expression. Will only be called if the new-value subexpression
427 : * contains SubscriptingRef or FieldStore. This is the same as the
428 : * regular fetch case, except that we have to handle a null array,
429 : * and the value should be stored into the SubscriptingRefState's
430 : * prevvalue/prevnull fields.
431 : *
432 : * Note: this is presently dead code, because the new value for a
433 : * slice would have to be an array, so it couldn't directly contain a
434 : * FieldStore; nor could it contain a SubscriptingRef assignment, since
435 : * we consider adjacent subscripts to index one multidimensional array
436 : * not nested array types. Future generalizations might make this
437 : * reachable, however.
438 : */
439 : static void
440 0 : array_subscript_fetch_old_slice(ExprState *state,
441 : ExprEvalStep *op,
442 : ExprContext *econtext)
443 : {
444 0 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
445 0 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
446 :
447 0 : if (*op->resnull)
448 : {
449 : /* whole array is null, so any slice is too */
450 0 : sbsrefstate->prevvalue = (Datum) 0;
451 0 : sbsrefstate->prevnull = true;
452 : }
453 : else
454 : {
455 0 : sbsrefstate->prevvalue = array_get_slice(*op->resvalue,
456 : sbsrefstate->numupper,
457 0 : workspace->upperindex,
458 0 : workspace->lowerindex,
459 : sbsrefstate->upperprovided,
460 : sbsrefstate->lowerprovided,
461 0 : workspace->refattrlength,
462 0 : workspace->refelemlength,
463 0 : workspace->refelembyval,
464 0 : workspace->refelemalign);
465 : /* slices of non-null arrays are never null */
466 0 : sbsrefstate->prevnull = false;
467 : }
468 0 : }
469 :
470 : /*
471 : * Set up execution state for an array subscript operation.
472 : */
473 : static void
474 14046 : array_exec_setup(const SubscriptingRef *sbsref,
475 : SubscriptingRefState *sbsrefstate,
476 : SubscriptExecSteps *methods)
477 : {
478 14046 : bool is_slice = (sbsrefstate->numlower != 0);
479 : ArraySubWorkspace *workspace;
480 :
481 : /*
482 : * Enforce the implementation limit on number of array subscripts. This
483 : * check isn't entirely redundant with checking at parse time; conceivably
484 : * the expression was stored by a backend with a different MAXDIM value.
485 : */
486 14046 : if (sbsrefstate->numupper > MAXDIM)
487 0 : ereport(ERROR,
488 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
489 : errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
490 : sbsrefstate->numupper, MAXDIM)));
491 :
492 : /* Should be impossible if parser is sane, but check anyway: */
493 14046 : if (sbsrefstate->numlower != 0 &&
494 222 : sbsrefstate->numupper != sbsrefstate->numlower)
495 0 : elog(ERROR, "upper and lower index lists are not same length");
496 :
497 : /*
498 : * Allocate type-specific workspace.
499 : */
500 14046 : workspace = palloc_object(ArraySubWorkspace);
501 14046 : sbsrefstate->workspace = workspace;
502 :
503 : /*
504 : * Collect datatype details we'll need at execution.
505 : */
506 14046 : workspace->refelemtype = sbsref->refelemtype;
507 14046 : workspace->refattrlength = get_typlen(sbsref->refcontainertype);
508 14046 : get_typlenbyvalalign(sbsref->refelemtype,
509 : &workspace->refelemlength,
510 : &workspace->refelembyval,
511 : &workspace->refelemalign);
512 :
513 : /*
514 : * Pass back pointers to appropriate step execution functions.
515 : */
516 14046 : methods->sbs_check_subscripts = array_subscript_check_subscripts;
517 14046 : if (is_slice)
518 : {
519 222 : methods->sbs_fetch = array_subscript_fetch_slice;
520 222 : methods->sbs_assign = array_subscript_assign_slice;
521 222 : methods->sbs_fetch_old = array_subscript_fetch_old_slice;
522 : }
523 : else
524 : {
525 13824 : methods->sbs_fetch = array_subscript_fetch;
526 13824 : methods->sbs_assign = array_subscript_assign;
527 13824 : methods->sbs_fetch_old = array_subscript_fetch_old;
528 : }
529 14046 : }
530 :
531 : /*
532 : * array_subscript_handler
533 : * Subscripting handler for standard varlena arrays.
534 : *
535 : * This should be used only for "true" array types, which have array headers
536 : * as understood by the varlena array routines, and are referenced by the
537 : * element type's pg_type.typarray field.
538 : */
539 : Datum
540 19952 : array_subscript_handler(PG_FUNCTION_ARGS)
541 : {
542 : static const SubscriptRoutines sbsroutines = {
543 : .transform = array_subscript_transform,
544 : .exec_setup = array_exec_setup,
545 : .fetch_strict = true, /* fetch returns NULL for NULL inputs */
546 : .fetch_leakproof = true, /* fetch returns NULL for bad subscript */
547 : .store_leakproof = false /* ... but assignment throws error */
548 : };
549 :
550 19952 : PG_RETURN_POINTER(&sbsroutines);
551 : }
552 :
553 : /*
554 : * raw_array_subscript_handler
555 : * Subscripting handler for "raw" arrays.
556 : *
557 : * A "raw" array just contains N independent instances of the element type.
558 : * Currently we require both the element type and the array type to be fixed
559 : * length, but it wouldn't be too hard to relax that for the array type.
560 : *
561 : * As of now, all the support code is shared with standard varlena arrays.
562 : * We may split those into separate code paths, but probably that would yield
563 : * only marginal speedups. The main point of having a separate handler is
564 : * so that pg_type.typsubscript clearly indicates the type's semantics.
565 : */
566 : Datum
567 939 : raw_array_subscript_handler(PG_FUNCTION_ARGS)
568 : {
569 : static const SubscriptRoutines sbsroutines = {
570 : .transform = array_subscript_transform,
571 : .exec_setup = array_exec_setup,
572 : .fetch_strict = true, /* fetch returns NULL for NULL inputs */
573 : .fetch_leakproof = true, /* fetch returns NULL for bad subscript */
574 : .store_leakproof = false /* ... but assignment throws error */
575 : };
576 :
577 939 : PG_RETURN_POINTER(&sbsroutines);
578 : }
579 :
580 : /*
581 : * array_subscript_handler_support()
582 : *
583 : * Planner support function for array_subscript_handler()
584 : */
585 : Datum
586 1 : array_subscript_handler_support(PG_FUNCTION_ARGS)
587 : {
588 1 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
589 1 : Node *ret = NULL;
590 :
591 1 : if (IsA(rawreq, SupportRequestModifyInPlace))
592 : {
593 : /*
594 : * We can optimize in-place subscripted assignment if the refexpr is
595 : * the array being assigned to. We don't need to worry about array
596 : * references within the refassgnexpr or the subscripts; however, if
597 : * there's no refassgnexpr then it's a fetch which there's no need to
598 : * optimize.
599 : */
600 1 : SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
601 1 : Param *refexpr = (Param *) linitial(req->args);
602 :
603 1 : if (refexpr && IsA(refexpr, Param) &&
604 1 : refexpr->paramkind == PARAM_EXTERN &&
605 1 : refexpr->paramid == req->paramid &&
606 1 : lsecond(req->args) != NULL)
607 1 : ret = (Node *) refexpr;
608 : }
609 :
610 1 : PG_RETURN_POINTER(ret);
611 : }
|