Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * arraysubs.c
4 : * Subscripting support functions for arrays.
5 : *
6 : * Portions Copyright (c) 1996-2025, 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 12108 : array_subscript_transform(SubscriptingRef *sbsref,
57 : List *indirection,
58 : ParseState *pstate,
59 : bool isSlice,
60 : bool isAssignment)
61 : {
62 12108 : List *upperIndexpr = NIL;
63 12108 : 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 24506 : foreach(idx, indirection)
75 : {
76 12398 : A_Indices *ai = lfirst_node(A_Indices, idx);
77 : Node *subexpr;
78 :
79 12398 : if (isSlice)
80 : {
81 584 : if (ai->lidx)
82 : {
83 452 : subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind);
84 : /* If it's not int4 already, try to coerce */
85 452 : subexpr = coerce_to_target_type(pstate,
86 : subexpr, exprType(subexpr),
87 : INT4OID, -1,
88 : COERCION_ASSIGNMENT,
89 : COERCE_IMPLICIT_CAST,
90 : -1);
91 452 : 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 132 : else if (!ai->is_slice)
98 : {
99 : /* Make a constant 1 */
100 54 : 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 78 : subexpr = NULL;
112 : }
113 584 : lowerIndexpr = lappend(lowerIndexpr, subexpr);
114 : }
115 : else
116 : Assert(ai->lidx == NULL && !ai->is_slice);
117 :
118 12398 : if (ai->uidx)
119 : {
120 12320 : subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
121 : /* If it's not int4 already, try to coerce */
122 12320 : subexpr = coerce_to_target_type(pstate,
123 : subexpr, exprType(subexpr),
124 : INT4OID, -1,
125 : COERCION_ASSIGNMENT,
126 : COERCE_IMPLICIT_CAST,
127 : -1);
128 12320 : 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 78 : subexpr = NULL;
139 : }
140 12398 : upperIndexpr = lappend(upperIndexpr, subexpr);
141 : }
142 :
143 : /* ... and store the transformed lists into the SubscriptRef node */
144 12108 : sbsref->refupperindexpr = upperIndexpr;
145 12108 : sbsref->reflowerindexpr = lowerIndexpr;
146 :
147 : /* Verify subscript list lengths are within implementation limit */
148 12108 : if (list_length(upperIndexpr) > MAXDIM)
149 6 : 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 12102 : if (isSlice)
162 428 : sbsref->refrestype = sbsref->refcontainertype;
163 : else
164 11674 : sbsref->refrestype = sbsref->refelemtype;
165 12102 : }
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 768426 : array_subscript_check_subscripts(ExprState *state,
182 : ExprEvalStep *op,
183 : ExprContext *econtext)
184 : {
185 768426 : SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
186 768426 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
187 :
188 : /* Process upper subscripts */
189 1537298 : for (int i = 0; i < sbsrefstate->numupper; i++)
190 : {
191 768896 : if (sbsrefstate->upperprovided[i])
192 : {
193 : /* If any index expr yields NULL, result is NULL or error */
194 768752 : if (sbsrefstate->upperindexnull[i])
195 : {
196 24 : if (sbsrefstate->isassignment)
197 12 : ereport(ERROR,
198 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
199 : errmsg("array subscript in assignment must not be null")));
200 12 : *op->resnull = true;
201 12 : return false;
202 : }
203 768728 : workspace->upperindex[i] = DatumGetInt32(sbsrefstate->upperindex[i]);
204 : }
205 : }
206 :
207 : /* Likewise for lower subscripts */
208 769284 : for (int i = 0; i < sbsrefstate->numlower; i++)
209 : {
210 894 : if (sbsrefstate->lowerprovided[i])
211 : {
212 : /* If any index expr yields NULL, result is NULL or error */
213 768 : if (sbsrefstate->lowerindexnull[i])
214 : {
215 12 : if (sbsrefstate->isassignment)
216 6 : ereport(ERROR,
217 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
218 : errmsg("array subscript in assignment must not be null")));
219 6 : *op->resnull = true;
220 6 : return false;
221 : }
222 756 : workspace->lowerindex[i] = DatumGetInt32(sbsrefstate->lowerindex[i]);
223 : }
224 : }
225 :
226 768390 : 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 766638 : array_subscript_fetch(ExprState *state,
238 : ExprEvalStep *op,
239 : ExprContext *econtext)
240 : {
241 766638 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
242 766638 : 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 1533276 : *op->resvalue = array_get_element(*op->resvalue,
248 : sbsrefstate->numupper,
249 766638 : workspace->upperindex,
250 766638 : workspace->refattrlength,
251 766638 : workspace->refelemlength,
252 766638 : workspace->refelembyval,
253 766638 : workspace->refelemalign,
254 : op->resnull);
255 766638 : }
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 354 : array_subscript_fetch_slice(ExprState *state,
266 : ExprEvalStep *op,
267 : ExprContext *econtext)
268 : {
269 354 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
270 354 : 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 684 : *op->resvalue = array_get_slice(*op->resvalue,
276 : sbsrefstate->numupper,
277 354 : workspace->upperindex,
278 354 : workspace->lowerindex,
279 : sbsrefstate->upperprovided,
280 : sbsrefstate->lowerprovided,
281 354 : workspace->refattrlength,
282 354 : workspace->refelemlength,
283 354 : workspace->refelembyval,
284 354 : workspace->refelemalign);
285 : /* The slice is never NULL, so no need to change *op->resnull */
286 330 : }
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 1104 : array_subscript_assign(ExprState *state,
296 : ExprEvalStep *op,
297 : ExprContext *econtext)
298 : {
299 1104 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
300 1104 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
301 1104 : 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 1104 : if (workspace->refattrlength > 0)
309 : {
310 36 : if (*op->resnull || sbsrefstate->replacenull)
311 18 : 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 1086 : if (*op->resnull)
321 : {
322 342 : arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
323 342 : *op->resnull = false;
324 : }
325 :
326 1062 : *op->resvalue = array_set_element(arraySource,
327 : sbsrefstate->numupper,
328 1086 : workspace->upperindex,
329 : sbsrefstate->replacevalue,
330 1086 : sbsrefstate->replacenull,
331 1086 : workspace->refattrlength,
332 1086 : workspace->refelemlength,
333 1086 : workspace->refelembyval,
334 1086 : 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 262 : array_subscript_assign_slice(ExprState *state,
346 : ExprEvalStep *op,
347 : ExprContext *econtext)
348 : {
349 262 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
350 262 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
351 262 : 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 262 : 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 262 : if (*op->resnull)
371 : {
372 56 : arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype));
373 56 : *op->resnull = false;
374 : }
375 :
376 232 : *op->resvalue = array_set_slice(arraySource,
377 : sbsrefstate->numupper,
378 262 : workspace->upperindex,
379 262 : workspace->lowerindex,
380 : sbsrefstate->upperprovided,
381 : sbsrefstate->lowerprovided,
382 : sbsrefstate->replacevalue,
383 262 : sbsrefstate->replacenull,
384 262 : workspace->refattrlength,
385 262 : workspace->refelemlength,
386 262 : workspace->refelembyval,
387 262 : 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 276 : array_subscript_fetch_old(ExprState *state,
401 : ExprEvalStep *op,
402 : ExprContext *econtext)
403 : {
404 276 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
405 276 : ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace;
406 :
407 276 : if (*op->resnull)
408 : {
409 : /* whole array is null, so any element is too */
410 88 : sbsrefstate->prevvalue = (Datum) 0;
411 88 : sbsrefstate->prevnull = true;
412 : }
413 : else
414 188 : sbsrefstate->prevvalue = array_get_element(*op->resvalue,
415 : sbsrefstate->numupper,
416 188 : workspace->upperindex,
417 188 : workspace->refattrlength,
418 188 : workspace->refelemlength,
419 188 : workspace->refelembyval,
420 188 : workspace->refelemalign,
421 : &sbsrefstate->prevnull);
422 276 : }
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 143706 : array_exec_setup(const SubscriptingRef *sbsref,
475 : SubscriptingRefState *sbsrefstate,
476 : SubscriptExecSteps *methods)
477 : {
478 143706 : 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 143706 : 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 143706 : if (sbsrefstate->numlower != 0 &&
494 420 : 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 143706 : workspace = (ArraySubWorkspace *) palloc(sizeof(ArraySubWorkspace));
501 143706 : sbsrefstate->workspace = workspace;
502 :
503 : /*
504 : * Collect datatype details we'll need at execution.
505 : */
506 143706 : workspace->refelemtype = sbsref->refelemtype;
507 143706 : workspace->refattrlength = get_typlen(sbsref->refcontainertype);
508 143706 : 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 143706 : methods->sbs_check_subscripts = array_subscript_check_subscripts;
517 143706 : if (is_slice)
518 : {
519 420 : methods->sbs_fetch = array_subscript_fetch_slice;
520 420 : methods->sbs_assign = array_subscript_assign_slice;
521 420 : methods->sbs_fetch_old = array_subscript_fetch_old_slice;
522 : }
523 : else
524 : {
525 143286 : methods->sbs_fetch = array_subscript_fetch;
526 143286 : methods->sbs_assign = array_subscript_assign;
527 143286 : methods->sbs_fetch_old = array_subscript_fetch_old;
528 : }
529 143706 : }
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 154300 : 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 154300 : 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 1514 : 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 1514 : 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 2 : array_subscript_handler_support(PG_FUNCTION_ARGS)
587 : {
588 2 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
589 2 : Node *ret = NULL;
590 :
591 2 : 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 2 : SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
601 2 : Param *refexpr = (Param *) linitial(req->args);
602 :
603 2 : if (refexpr && IsA(refexpr, Param) &&
604 2 : refexpr->paramkind == PARAM_EXTERN &&
605 2 : refexpr->paramid == req->paramid &&
606 2 : lsecond(req->args) != NULL)
607 2 : ret = (Node *) refexpr;
608 : }
609 :
610 2 : PG_RETURN_POINTER(ret);
611 : }
|