postgresql/src/backend/utils/adt/arraysubs.c

578 lines
18 KiB
C

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