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