/*------------------------------------------------------------------------- * * 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); }