| /*------------------------------------------------------------------------- |
| * |
| * arraysubs.c |
| * Subscripting support functions for arrays. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/arraysubs.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "executor/execExpr.h" |
| #include "nodes/makefuncs.h" |
| #include "nodes/nodeFuncs.h" |
| #include "nodes/subscripting.h" |
| #include "parser/parse_coerce.h" |
| #include "parser/parse_expr.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| |
| |
| /* SubscriptingRefState.workspace for array subscripting execution */ |
| typedef struct ArraySubWorkspace |
| { |
| /* Values determined during expression compilation */ |
| Oid refelemtype; /* OID of the array element type */ |
| int16 refattrlength; /* typlen of array type */ |
| int16 refelemlength; /* typlen of the array element type */ |
| bool refelembyval; /* is the element type pass-by-value? */ |
| char refelemalign; /* typalign of the element type */ |
| |
| /* |
| * Subscript values converted to integers. Note that these arrays must be |
| * of length MAXDIM even when dealing with fewer subscripts, because |
| * array_get/set_slice may scribble on the extra entries. |
| */ |
| int upperindex[MAXDIM]; |
| int lowerindex[MAXDIM]; |
| } ArraySubWorkspace; |
| |
| |
| /* |
| * Finish parse analysis of a SubscriptingRef expression for an array. |
| * |
| * Transform the subscript expressions, coerce them to integers, |
| * and determine the result type of the SubscriptingRef node. |
| */ |
| static void |
| array_subscript_transform(SubscriptingRef *sbsref, |
| List *indirection, |
| ParseState *pstate, |
| bool isSlice, |
| bool isAssignment) |
| { |
| List *upperIndexpr = NIL; |
| List *lowerIndexpr = NIL; |
| ListCell *idx; |
| |
| /* |
| * Transform the subscript expressions, and separate upper and lower |
| * bounds into two lists. |
| * |
| * If we have a container slice expression, we convert any non-slice |
| * indirection items to slices by treating the single subscript as the |
| * upper bound and supplying an assumed lower bound of 1. |
| */ |
| foreach(idx, indirection) |
| { |
| A_Indices *ai = lfirst_node(A_Indices, idx); |
| Node *subexpr; |
| |
| if (isSlice) |
| { |
| if (ai->lidx) |
| { |
| subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind); |
| /* If it's not int4 already, try to coerce */ |
| subexpr = coerce_to_target_type(pstate, |
| subexpr, exprType(subexpr), |
| INT4OID, -1, |
| COERCION_ASSIGNMENT, |
| COERCE_IMPLICIT_CAST, |
| -1); |
| if (subexpr == NULL) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("array subscript must have type integer"), |
| parser_errposition(pstate, exprLocation(ai->lidx)))); |
| } |
| else if (!ai->is_slice) |
| { |
| /* Make a constant 1 */ |
| subexpr = (Node *) makeConst(INT4OID, |
| -1, |
| InvalidOid, |
| sizeof(int32), |
| Int32GetDatum(1), |
| false, |
| true); /* pass by value */ |
| } |
| else |
| { |
| /* Slice with omitted lower bound, put NULL into the list */ |
| subexpr = NULL; |
| } |
| lowerIndexpr = lappend(lowerIndexpr, subexpr); |
| } |
| else |
| Assert(ai->lidx == NULL && !ai->is_slice); |
| |
| if (ai->uidx) |
| { |
| subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); |
| /* If it's not int4 already, try to coerce */ |
| subexpr = coerce_to_target_type(pstate, |
| subexpr, exprType(subexpr), |
| INT4OID, -1, |
| COERCION_ASSIGNMENT, |
| COERCE_IMPLICIT_CAST, |
| -1); |
| if (subexpr == NULL) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("array subscript must have type integer"), |
| parser_errposition(pstate, exprLocation(ai->uidx)))); |
| } |
| else |
| { |
| /* Slice with omitted upper bound, put NULL into the list */ |
| Assert(isSlice && ai->is_slice); |
| subexpr = NULL; |
| } |
| upperIndexpr = lappend(upperIndexpr, subexpr); |
| } |
| |
| /* ... and store the transformed lists into the SubscriptRef node */ |
| sbsref->refupperindexpr = upperIndexpr; |
| sbsref->reflowerindexpr = lowerIndexpr; |
| |
| /* Verify subscript list lengths are within implementation limit */ |
| if (list_length(upperIndexpr) > MAXDIM) |
| ereport(ERROR, |
| (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
| errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", |
| list_length(upperIndexpr), MAXDIM))); |
| /* We need not check lowerIndexpr separately */ |
| |
| /* |
| * Determine the result type of the subscripting operation. It's the same |
| * as the array type if we're slicing, else it's the element type. In |
| * either case, the typmod is the same as the array's, so we need not |
| * change reftypmod. |
| */ |
| if (isSlice) |
| sbsref->refrestype = sbsref->refcontainertype; |
| else |
| sbsref->refrestype = sbsref->refelemtype; |
| } |
| |
| /* |
| * During execution, process the subscripts in a SubscriptingRef expression. |
| * |
| * The subscript expressions are already evaluated in Datum form in the |
| * SubscriptingRefState's arrays. Check and convert them as necessary. |
| * |
| * If any subscript is NULL, we throw error in assignment cases, or in fetch |
| * cases set result to NULL and return false (instructing caller to skip the |
| * rest of the SubscriptingRef sequence). |
| * |
| * We convert all the subscripts to plain integers and save them in the |
| * sbsrefstate->workspace arrays. |
| */ |
| static bool |
| array_subscript_check_subscripts(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| |
| /* Process upper subscripts */ |
| for (int i = 0; i < sbsrefstate->numupper; i++) |
| { |
| if (sbsrefstate->upperprovided[i]) |
| { |
| /* If any index expr yields NULL, result is NULL or error */ |
| if (sbsrefstate->upperindexnull[i]) |
| { |
| if (sbsrefstate->isassignment) |
| ereport(ERROR, |
| (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
| errmsg("array subscript in assignment must not be null"))); |
| *op->resnull = true; |
| return false; |
| } |
| workspace->upperindex[i] = DatumGetInt32(sbsrefstate->upperindex[i]); |
| } |
| } |
| |
| /* Likewise for lower subscripts */ |
| for (int i = 0; i < sbsrefstate->numlower; i++) |
| { |
| if (sbsrefstate->lowerprovided[i]) |
| { |
| /* If any index expr yields NULL, result is NULL or error */ |
| if (sbsrefstate->lowerindexnull[i]) |
| { |
| if (sbsrefstate->isassignment) |
| ereport(ERROR, |
| (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
| errmsg("array subscript in assignment must not be null"))); |
| *op->resnull = true; |
| return false; |
| } |
| workspace->lowerindex[i] = DatumGetInt32(sbsrefstate->lowerindex[i]); |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Evaluate SubscriptingRef fetch for an array element. |
| * |
| * Source container is in step's result variable (it's known not NULL, since |
| * we set fetch_strict to true), and indexes have already been evaluated into |
| * workspace array. |
| */ |
| static void |
| array_subscript_fetch(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| |
| /* Should not get here if source array (or any subscript) is null */ |
| Assert(!(*op->resnull)); |
| |
| *op->resvalue = array_get_element(*op->resvalue, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign, |
| op->resnull); |
| } |
| |
| /* |
| * Evaluate SubscriptingRef fetch for an array slice. |
| * |
| * Source container is in step's result variable (it's known not NULL, since |
| * we set fetch_strict to true), and indexes have already been evaluated into |
| * workspace array. |
| */ |
| static void |
| array_subscript_fetch_slice(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| |
| /* Should not get here if source array (or any subscript) is null */ |
| Assert(!(*op->resnull)); |
| |
| *op->resvalue = array_get_slice(*op->resvalue, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| workspace->lowerindex, |
| sbsrefstate->upperprovided, |
| sbsrefstate->lowerprovided, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign); |
| /* The slice is never NULL, so no need to change *op->resnull */ |
| } |
| |
| /* |
| * Evaluate SubscriptingRef assignment for an array element assignment. |
| * |
| * Input container (possibly null) is in result area, replacement value is in |
| * SubscriptingRefState's replacevalue/replacenull. |
| */ |
| static void |
| array_subscript_assign(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| Datum arraySource = *op->resvalue; |
| |
| /* |
| * For an assignment to a fixed-length array type, both the original array |
| * and the value to be assigned into it must be non-NULL, else we punt and |
| * return the original array. |
| */ |
| if (workspace->refattrlength > 0) |
| { |
| if (*op->resnull || sbsrefstate->replacenull) |
| return; |
| } |
| |
| /* |
| * For assignment to varlena arrays, we handle a NULL original array by |
| * substituting an empty (zero-dimensional) array; insertion of the new |
| * element will result in a singleton array value. It does not matter |
| * whether the new element is NULL. |
| */ |
| if (*op->resnull) |
| { |
| arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype)); |
| *op->resnull = false; |
| } |
| |
| *op->resvalue = array_set_element(arraySource, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| sbsrefstate->replacevalue, |
| sbsrefstate->replacenull, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign); |
| /* The result is never NULL, so no need to change *op->resnull */ |
| } |
| |
| /* |
| * Evaluate SubscriptingRef assignment for an array slice assignment. |
| * |
| * Input container (possibly null) is in result area, replacement value is in |
| * SubscriptingRefState's replacevalue/replacenull. |
| */ |
| static void |
| array_subscript_assign_slice(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| Datum arraySource = *op->resvalue; |
| |
| /* |
| * For an assignment to a fixed-length array type, both the original array |
| * and the value to be assigned into it must be non-NULL, else we punt and |
| * return the original array. |
| */ |
| if (workspace->refattrlength > 0) |
| { |
| if (*op->resnull || sbsrefstate->replacenull) |
| return; |
| } |
| |
| /* |
| * For assignment to varlena arrays, we handle a NULL original array by |
| * substituting an empty (zero-dimensional) array; insertion of the new |
| * element will result in a singleton array value. It does not matter |
| * whether the new element is NULL. |
| */ |
| if (*op->resnull) |
| { |
| arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype)); |
| *op->resnull = false; |
| } |
| |
| *op->resvalue = array_set_slice(arraySource, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| workspace->lowerindex, |
| sbsrefstate->upperprovided, |
| sbsrefstate->lowerprovided, |
| sbsrefstate->replacevalue, |
| sbsrefstate->replacenull, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign); |
| /* The result is never NULL, so no need to change *op->resnull */ |
| } |
| |
| /* |
| * Compute old array element value for a SubscriptingRef assignment |
| * expression. Will only be called if the new-value subexpression |
| * contains SubscriptingRef or FieldStore. This is the same as the |
| * regular fetch case, except that we have to handle a null array, |
| * and the value should be stored into the SubscriptingRefState's |
| * prevvalue/prevnull fields. |
| */ |
| static void |
| array_subscript_fetch_old(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| |
| if (*op->resnull) |
| { |
| /* whole array is null, so any element is too */ |
| sbsrefstate->prevvalue = (Datum) 0; |
| sbsrefstate->prevnull = true; |
| } |
| else |
| sbsrefstate->prevvalue = array_get_element(*op->resvalue, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign, |
| &sbsrefstate->prevnull); |
| } |
| |
| /* |
| * Compute old array slice value for a SubscriptingRef assignment |
| * expression. Will only be called if the new-value subexpression |
| * contains SubscriptingRef or FieldStore. This is the same as the |
| * regular fetch case, except that we have to handle a null array, |
| * and the value should be stored into the SubscriptingRefState's |
| * prevvalue/prevnull fields. |
| * |
| * Note: this is presently dead code, because the new value for a |
| * slice would have to be an array, so it couldn't directly contain a |
| * FieldStore; nor could it contain a SubscriptingRef assignment, since |
| * we consider adjacent subscripts to index one multidimensional array |
| * not nested array types. Future generalizations might make this |
| * reachable, however. |
| */ |
| static void |
| array_subscript_fetch_old_slice(ExprState *state, |
| ExprEvalStep *op, |
| ExprContext *econtext) |
| { |
| SubscriptingRefState *sbsrefstate = op->d.sbsref.state; |
| ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; |
| |
| if (*op->resnull) |
| { |
| /* whole array is null, so any slice is too */ |
| sbsrefstate->prevvalue = (Datum) 0; |
| sbsrefstate->prevnull = true; |
| } |
| else |
| { |
| sbsrefstate->prevvalue = array_get_slice(*op->resvalue, |
| sbsrefstate->numupper, |
| workspace->upperindex, |
| workspace->lowerindex, |
| sbsrefstate->upperprovided, |
| sbsrefstate->lowerprovided, |
| workspace->refattrlength, |
| workspace->refelemlength, |
| workspace->refelembyval, |
| workspace->refelemalign); |
| /* slices of non-null arrays are never null */ |
| sbsrefstate->prevnull = false; |
| } |
| } |
| |
| /* |
| * Set up execution state for an array subscript operation. |
| */ |
| static void |
| array_exec_setup(const SubscriptingRef *sbsref, |
| SubscriptingRefState *sbsrefstate, |
| SubscriptExecSteps *methods) |
| { |
| bool is_slice = (sbsrefstate->numlower != 0); |
| ArraySubWorkspace *workspace; |
| |
| /* |
| * Enforce the implementation limit on number of array subscripts. This |
| * check isn't entirely redundant with checking at parse time; conceivably |
| * the expression was stored by a backend with a different MAXDIM value. |
| */ |
| if (sbsrefstate->numupper > MAXDIM) |
| ereport(ERROR, |
| (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
| errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", |
| sbsrefstate->numupper, MAXDIM))); |
| |
| /* Should be impossible if parser is sane, but check anyway: */ |
| if (sbsrefstate->numlower != 0 && |
| sbsrefstate->numupper != sbsrefstate->numlower) |
| elog(ERROR, "upper and lower index lists are not same length"); |
| |
| /* |
| * Allocate type-specific workspace. |
| */ |
| workspace = (ArraySubWorkspace *) palloc(sizeof(ArraySubWorkspace)); |
| sbsrefstate->workspace = workspace; |
| |
| /* |
| * Collect datatype details we'll need at execution. |
| */ |
| workspace->refelemtype = sbsref->refelemtype; |
| workspace->refattrlength = get_typlen(sbsref->refcontainertype); |
| get_typlenbyvalalign(sbsref->refelemtype, |
| &workspace->refelemlength, |
| &workspace->refelembyval, |
| &workspace->refelemalign); |
| |
| /* |
| * Pass back pointers to appropriate step execution functions. |
| */ |
| methods->sbs_check_subscripts = array_subscript_check_subscripts; |
| if (is_slice) |
| { |
| methods->sbs_fetch = array_subscript_fetch_slice; |
| methods->sbs_assign = array_subscript_assign_slice; |
| methods->sbs_fetch_old = array_subscript_fetch_old_slice; |
| } |
| else |
| { |
| methods->sbs_fetch = array_subscript_fetch; |
| methods->sbs_assign = array_subscript_assign; |
| methods->sbs_fetch_old = array_subscript_fetch_old; |
| } |
| } |
| |
| /* |
| * array_subscript_handler |
| * Subscripting handler for standard varlena arrays. |
| * |
| * This should be used only for "true" array types, which have array headers |
| * as understood by the varlena array routines, and are referenced by the |
| * element type's pg_type.typarray field. |
| */ |
| Datum |
| array_subscript_handler(PG_FUNCTION_ARGS) |
| { |
| static const SubscriptRoutines sbsroutines = { |
| .transform = array_subscript_transform, |
| .exec_setup = array_exec_setup, |
| .fetch_strict = true, /* fetch returns NULL for NULL inputs */ |
| .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ |
| .store_leakproof = false /* ... but assignment throws error */ |
| }; |
| |
| PG_RETURN_POINTER(&sbsroutines); |
| } |
| |
| /* |
| * raw_array_subscript_handler |
| * Subscripting handler for "raw" arrays. |
| * |
| * A "raw" array just contains N independent instances of the element type. |
| * Currently we require both the element type and the array type to be fixed |
| * length, but it wouldn't be too hard to relax that for the array type. |
| * |
| * As of now, all the support code is shared with standard varlena arrays. |
| * We may split those into separate code paths, but probably that would yield |
| * only marginal speedups. The main point of having a separate handler is |
| * so that pg_type.typsubscript clearly indicates the type's semantics. |
| */ |
| Datum |
| raw_array_subscript_handler(PG_FUNCTION_ARGS) |
| { |
| static const SubscriptRoutines sbsroutines = { |
| .transform = array_subscript_transform, |
| .exec_setup = array_exec_setup, |
| .fetch_strict = true, /* fetch returns NULL for NULL inputs */ |
| .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ |
| .store_leakproof = false /* ... but assignment throws error */ |
| }; |
| |
| PG_RETURN_POINTER(&sbsroutines); |
| } |