diff options
author | Tom Lane | 2020-12-09 17:40:37 +0000 |
---|---|---|
committer | Tom Lane | 2020-12-09 17:40:37 +0000 |
commit | c7aba7c14efdbd9fc1bb44b4cb83bedee0c6a6fc (patch) | |
tree | d6980ca2951d353475957a56b58866cd4fafcdd3 /src/backend | |
parent | 8b069ef5dca97cd737a5fd64c420df3cd61ec1c9 (diff) |
Support subscripting of arbitrary types, not only arrays.
This patch generalizes the subscripting infrastructure so that any
data type can be subscripted, if it provides a handler function to
define what that means. Traditional variable-length (varlena) arrays
all use array_subscript_handler(), while the existing fixed-length
types that support subscripting use raw_array_subscript_handler().
It's expected that other types that want to use subscripting notation
will define their own handlers. (This patch provides no such new
features, though; it only lays the foundation for them.)
To do this, move the parser's semantic processing of subscripts
(including coercion to whatever data type is required) into a
method callback supplied by the handler. On the execution side,
replace the ExecEvalSubscriptingRef* layer of functions with direct
calls to callback-supplied execution routines. (Thus, essentially
no new run-time overhead should be caused by this patch. Indeed,
there is room to remove some overhead by supplying specialized
execution routines. This patch does a little bit in that line,
but more could be done.)
Additional work is required here and there to remove formerly
hard-wired assumptions about the result type, collation, etc
of a SubscriptingRef expression node; and to remove assumptions
that the subscript values must be integers.
One useful side-effect of this is that we now have a less squishy
mechanism for identifying whether a data type is a "true" array:
instead of wiring in weird rules about typlen, we can look to see
if pg_type.typsubscript == F_ARRAY_SUBSCRIPT_HANDLER. For this
to be bulletproof, we have to forbid user-defined types from using
that handler directly; but there seems no good reason for them to
do so.
This patch also removes assumptions that the number of subscripts
is limited to MAXDIM (6), or indeed has any hard-wired limit.
That limit still applies to types handled by array_subscript_handler
or raw_array_subscript_handler, but to discourage other dependencies
on this constant, I've moved it from c.h to utils/array.h.
Dmitry Dolgov, reviewed at various times by Tom Lane, Arthur Zakirov,
Peter Eisentraut, Pavel Stehule
Discussion: https://postgr.es/m/CA+q6zcVDuGBv=M0FqBYX8DPebS3F_0KQ6OVFobGJPM507_SZ_w@mail.gmail.com
Discussion: https://postgr.es/m/CA+q6zcVovR+XY4mfk-7oNk-rF91gH0PebnNfuUjuuDsyHjOcVA@mail.gmail.com
Diffstat (limited to 'src/backend')
27 files changed, 1062 insertions, 572 deletions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index c6261614085..c4594b0b095 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -3114,7 +3114,7 @@ ExecGrant_Type(InternalGrant *istmt) pg_type_tuple = (Form_pg_type) GETSTRUCT(tuple); - if (pg_type_tuple->typelem != 0 && pg_type_tuple->typlen == -1) + if (IsTrueArrayType(pg_type_tuple)) ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("cannot set privileges of array types"), @@ -4392,7 +4392,7 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how) * "True" array types don't manage permissions of their own; consult the * element type instead. */ - if (OidIsValid(typeForm->typelem) && typeForm->typlen == -1) + if (IsTrueArrayType(typeForm)) { Oid elttype_oid = typeForm->typelem; diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 245c2f4fc89..119006159b6 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -2074,6 +2074,22 @@ find_expr_references_walker(Node *node, context->addrs); /* fall through to examine arguments */ } + else if (IsA(node, SubscriptingRef)) + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + /* + * The refexpr should provide adequate dependency on refcontainertype, + * and that type in turn depends on refelemtype. However, a custom + * subscripting handler might set refrestype to something different + * from either of those, in which case we'd better record it. + */ + if (sbsref->refrestype != sbsref->refcontainertype && + sbsref->refrestype != sbsref->refelemtype) + add_object_address(OCLASS_TYPE, sbsref->refrestype, 0, + context->addrs); + /* fall through to examine arguments */ + } else if (IsA(node, SubPlan)) { /* Extra work needed here if we ever need this case */ diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 4cd7d769381..51b5c4f7f68 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1079,6 +1079,7 @@ AddNewRelationType(const char *typeName, InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ InvalidOid, /* analyze procedure - default */ + InvalidOid, /* subscript procedure - none */ InvalidOid, /* array element type - irrelevant */ false, /* this is not an array type */ new_array_type, /* array type if any */ @@ -1358,6 +1359,7 @@ heap_create_with_catalog(const char *relname, InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ F_ARRAY_TYPANALYZE, /* array analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */ new_type_oid, /* array element type - the rowtype */ true, /* yes, this is an array type */ InvalidOid, /* this has no array type */ diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index aeb4a54f635..4252875ef50 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -103,6 +103,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) values[Anum_pg_type_typisdefined - 1] = BoolGetDatum(false); values[Anum_pg_type_typdelim - 1] = CharGetDatum(DEFAULT_TYPDELIM); values[Anum_pg_type_typrelid - 1] = ObjectIdGetDatum(InvalidOid); + values[Anum_pg_type_typsubscript - 1] = ObjectIdGetDatum(InvalidOid); values[Anum_pg_type_typelem - 1] = ObjectIdGetDatum(InvalidOid); values[Anum_pg_type_typarray - 1] = ObjectIdGetDatum(InvalidOid); values[Anum_pg_type_typinput - 1] = ObjectIdGetDatum(F_SHELL_IN); @@ -208,6 +209,7 @@ TypeCreate(Oid newTypeOid, Oid typmodinProcedure, Oid typmodoutProcedure, Oid analyzeProcedure, + Oid subscriptProcedure, Oid elementType, bool isImplicitArray, Oid arrayType, @@ -357,6 +359,7 @@ TypeCreate(Oid newTypeOid, values[Anum_pg_type_typisdefined - 1] = BoolGetDatum(true); values[Anum_pg_type_typdelim - 1] = CharGetDatum(typDelim); values[Anum_pg_type_typrelid - 1] = ObjectIdGetDatum(relationOid); + values[Anum_pg_type_typsubscript - 1] = ObjectIdGetDatum(subscriptProcedure); values[Anum_pg_type_typelem - 1] = ObjectIdGetDatum(elementType); values[Anum_pg_type_typarray - 1] = ObjectIdGetDatum(arrayType); values[Anum_pg_type_typinput - 1] = ObjectIdGetDatum(inputProcedure); @@ -667,7 +670,7 @@ GenerateTypeDependencies(HeapTuple typeTuple, recordDependencyOnCurrentExtension(&myself, rebuild); } - /* Normal dependencies on the I/O functions */ + /* Normal dependencies on the I/O and support functions */ if (OidIsValid(typeForm->typinput)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typinput); @@ -710,6 +713,12 @@ GenerateTypeDependencies(HeapTuple typeTuple, add_exact_object_address(&referenced, addrs_normal); } + if (OidIsValid(typeForm->typsubscript)) + { + ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typsubscript); + add_exact_object_address(&referenced, addrs_normal); + } + /* Normal dependency from a domain to its base type. */ if (OidIsValid(typeForm->typbasetype)) { diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 483bb65ddc8..29fe52d2cef 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -115,6 +115,7 @@ static Oid findTypeSendFunction(List *procname, Oid typeOid); static Oid findTypeTypmodinFunction(List *procname); static Oid findTypeTypmodoutFunction(List *procname); static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); +static Oid findTypeSubscriptingFunction(List *procname, Oid typeOid); static Oid findRangeSubOpclass(List *opcname, Oid subtype); static Oid findRangeCanonicalFunction(List *procname, Oid typeOid); static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype); @@ -149,6 +150,7 @@ DefineType(ParseState *pstate, List *names, List *parameters) List *typmodinName = NIL; List *typmodoutName = NIL; List *analyzeName = NIL; + List *subscriptName = NIL; char category = TYPCATEGORY_USER; bool preferred = false; char delimiter = DEFAULT_TYPDELIM; @@ -167,6 +169,7 @@ DefineType(ParseState *pstate, List *names, List *parameters) DefElem *typmodinNameEl = NULL; DefElem *typmodoutNameEl = NULL; DefElem *analyzeNameEl = NULL; + DefElem *subscriptNameEl = NULL; DefElem *categoryEl = NULL; DefElem *preferredEl = NULL; DefElem *delimiterEl = NULL; @@ -183,6 +186,7 @@ DefineType(ParseState *pstate, List *names, List *parameters) Oid typmodinOid = InvalidOid; Oid typmodoutOid = InvalidOid; Oid analyzeOid = InvalidOid; + Oid subscriptOid = InvalidOid; char *array_type; Oid array_oid; Oid typoid; @@ -288,6 +292,8 @@ DefineType(ParseState *pstate, List *names, List *parameters) else if (strcmp(defel->defname, "analyze") == 0 || strcmp(defel->defname, "analyse") == 0) defelp = &analyzeNameEl; + else if (strcmp(defel->defname, "subscript") == 0) + defelp = &subscriptNameEl; else if (strcmp(defel->defname, "category") == 0) defelp = &categoryEl; else if (strcmp(defel->defname, "preferred") == 0) @@ -358,6 +364,8 @@ DefineType(ParseState *pstate, List *names, List *parameters) typmodoutName = defGetQualifiedName(typmodoutNameEl); if (analyzeNameEl) analyzeName = defGetQualifiedName(analyzeNameEl); + if (subscriptNameEl) + subscriptName = defGetQualifiedName(subscriptNameEl); if (categoryEl) { char *p = defGetString(categoryEl); @@ -483,6 +491,24 @@ DefineType(ParseState *pstate, List *names, List *parameters) analyzeOid = findTypeAnalyzeFunction(analyzeName, typoid); /* + * Likewise look up the subscripting procedure if any. If it is not + * specified, but a typelem is specified, allow that if + * raw_array_subscript_handler can be used. (This is for backwards + * compatibility; maybe someday we should throw an error instead.) + */ + if (subscriptName) + subscriptOid = findTypeSubscriptingFunction(subscriptName, typoid); + else if (OidIsValid(elemType)) + { + if (internalLength > 0 && !byValue && get_typlen(elemType) > 0) + subscriptOid = F_RAW_ARRAY_SUBSCRIPT_HANDLER; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element type cannot be specified without a valid subscripting procedure"))); + } + + /* * Check permissions on functions. We choose to require the creator/owner * of a type to also own the underlying functions. Since creating a type * is tantamount to granting public execute access on the functions, the @@ -516,6 +542,9 @@ DefineType(ParseState *pstate, List *names, List *parameters) if (analyzeOid && !pg_proc_ownercheck(analyzeOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(analyzeName)); + if (subscriptOid && !pg_proc_ownercheck(subscriptOid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, + NameListToString(subscriptName)); #endif /* @@ -551,8 +580,9 @@ DefineType(ParseState *pstate, List *names, List *parameters) typmodinOid, /* typmodin procedure */ typmodoutOid, /* typmodout procedure */ analyzeOid, /* analyze procedure */ + subscriptOid, /* subscript procedure */ elemType, /* element type ID */ - false, /* this is not an array type */ + false, /* this is not an implicit array type */ array_oid, /* array type we are about to create */ InvalidOid, /* base type ID (only for domains) */ defaultValue, /* default type value */ @@ -592,6 +622,7 @@ DefineType(ParseState *pstate, List *names, List *parameters) typmodinOid, /* typmodin procedure */ typmodoutOid, /* typmodout procedure */ F_ARRAY_TYPANALYZE, /* analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */ typoid, /* element type ID */ true, /* yes this is an array type */ InvalidOid, /* no further array type */ @@ -800,6 +831,12 @@ DefineDomain(CreateDomainStmt *stmt) /* Analysis function */ analyzeProcedure = baseType->typanalyze; + /* + * Domains don't need a subscript procedure, since they are not + * subscriptable on their own. If the base type is subscriptable, the + * parser will reduce the type to the base type before subscripting. + */ + /* Inherited default value */ datum = SysCacheGetAttr(TYPEOID, typeTup, Anum_pg_type_typdefault, &isnull); @@ -993,6 +1030,7 @@ DefineDomain(CreateDomainStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ analyzeProcedure, /* analyze procedure */ + InvalidOid, /* subscript procedure - none */ InvalidOid, /* no array element type */ false, /* this isn't an array */ domainArrayOid, /* array type we are about to create */ @@ -1033,6 +1071,7 @@ DefineDomain(CreateDomainStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ F_ARRAY_TYPANALYZE, /* analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */ address.objectId, /* element type ID */ true, /* yes this is an array type */ InvalidOid, /* no further array type */ @@ -1148,6 +1187,7 @@ DefineEnum(CreateEnumStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ InvalidOid, /* analyze procedure - default */ + InvalidOid, /* subscript procedure - none */ InvalidOid, /* element type ID */ false, /* this is not an array type */ enumArrayOid, /* array type we are about to create */ @@ -1188,6 +1228,7 @@ DefineEnum(CreateEnumStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ F_ARRAY_TYPANALYZE, /* analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */ enumTypeAddr.objectId, /* element type ID */ true, /* yes this is an array type */ InvalidOid, /* no further array type */ @@ -1476,6 +1517,7 @@ DefineRange(CreateRangeStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ F_RANGE_TYPANALYZE, /* analyze procedure */ + InvalidOid, /* subscript procedure - none */ InvalidOid, /* element type ID - none */ false, /* this is not an array type */ rangeArrayOid, /* array type we are about to create */ @@ -1519,6 +1561,7 @@ DefineRange(CreateRangeStmt *stmt) InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ F_ARRAY_TYPANALYZE, /* analyze procedure */ + F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */ typoid, /* element type ID */ true, /* yes this is an array type */ InvalidOid, /* no further array type */ @@ -1616,7 +1659,7 @@ makeRangeConstructors(const char *name, Oid namespace, /* - * Find suitable I/O functions for a type. + * Find suitable I/O and other support functions for a type. * * typeOid is the type's OID (which will already exist, if only as a shell * type). @@ -1904,6 +1947,45 @@ findTypeAnalyzeFunction(List *procname, Oid typeOid) return procOid; } +static Oid +findTypeSubscriptingFunction(List *procname, Oid typeOid) +{ + Oid argList[1]; + Oid procOid; + + /* + * Subscripting support functions always take one INTERNAL argument and + * return INTERNAL. (The argument is not used, but we must have it to + * maintain type safety.) + */ + argList[0] = INTERNALOID; + + procOid = LookupFuncName(procname, 1, argList, true); + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); + + if (get_func_rettype(procOid) != INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type subscripting function %s must return type %s", + NameListToString(procname), "internal"))); + + /* + * We disallow array_subscript_handler() from being selected explicitly, + * since that must only be applied to autogenerated array types. + */ + if (procOid == F_ARRAY_SUBSCRIPT_HANDLER) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("user-defined types cannot use subscripting function %s", + NameListToString(procname)))); + + return procOid; +} + /* * Find suitable support functions and opclasses for a range type. */ @@ -3221,8 +3303,7 @@ RenameType(RenameStmt *stmt) errhint("Use ALTER TABLE instead."))); /* don't allow direct alteration of array types, either */ - if (OidIsValid(typTup->typelem) && - get_array_type(typTup->typelem) == typeOid) + if (IsTrueArrayType(typTup)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot alter array type %s", @@ -3303,8 +3384,7 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype) errhint("Use ALTER TABLE instead."))); /* don't allow direct alteration of array types, either */ - if (OidIsValid(typTup->typelem) && - get_array_type(typTup->typelem) == typeOid) + if (IsTrueArrayType(typTup)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot alter array type %s", @@ -3869,8 +3949,7 @@ AlterType(AlterTypeStmt *stmt) /* * For the same reasons, don't allow direct alteration of array types. */ - if (OidIsValid(typForm->typelem) && - get_array_type(typForm->typelem) == typeOid) + if (IsTrueArrayType(typForm)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s is not a base type", diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 79b325c7cfb..0134ecc261e 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -40,6 +40,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" #include "optimizer/optimizer.h" #include "pgstat.h" #include "utils/acl.h" @@ -2523,19 +2524,51 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, ExprState *state, Datum *resv, bool *resnull) { bool isAssignment = (sbsref->refassgnexpr != NULL); - SubscriptingRefState *sbsrefstate = palloc0(sizeof(SubscriptingRefState)); + int nupper = list_length(sbsref->refupperindexpr); + int nlower = list_length(sbsref->reflowerindexpr); + const SubscriptRoutines *sbsroutines; + SubscriptingRefState *sbsrefstate; + SubscriptExecSteps methods; + char *ptr; List *adjust_jumps = NIL; ListCell *lc; int i; + /* Look up the subscripting support methods */ + sbsroutines = getSubscriptingRoutines(sbsref->refcontainertype, NULL); + + /* Allocate sbsrefstate, with enough space for per-subscript arrays too */ + sbsrefstate = palloc0(MAXALIGN(sizeof(SubscriptingRefState)) + + (nupper + nlower) * (sizeof(Datum) + + 2 * sizeof(bool))); + /* Fill constant fields of SubscriptingRefState */ sbsrefstate->isassignment = isAssignment; - sbsrefstate->refelemtype = sbsref->refelemtype; - sbsrefstate->refattrlength = get_typlen(sbsref->refcontainertype); - get_typlenbyvalalign(sbsref->refelemtype, - &sbsrefstate->refelemlength, - &sbsrefstate->refelembyval, - &sbsrefstate->refelemalign); + sbsrefstate->numupper = nupper; + sbsrefstate->numlower = nlower; + /* Set up per-subscript arrays */ + ptr = ((char *) sbsrefstate) + MAXALIGN(sizeof(SubscriptingRefState)); + sbsrefstate->upperindex = (Datum *) ptr; + ptr += nupper * sizeof(Datum); + sbsrefstate->lowerindex = (Datum *) ptr; + ptr += nlower * sizeof(Datum); + sbsrefstate->upperprovided = (bool *) ptr; + ptr += nupper * sizeof(bool); + sbsrefstate->lowerprovided = (bool *) ptr; + ptr += nlower * sizeof(bool); + sbsrefstate->upperindexnull = (bool *) ptr; + ptr += nupper * sizeof(bool); + sbsrefstate->lowerindexnull = (bool *) ptr; + /* ptr += nlower * sizeof(bool); */ + + /* + * Let the container-type-specific code have a chance. It must fill the + * "methods" struct with function pointers for us to possibly use in + * execution steps below; and it can optionally set up some data pointed + * to by the workspace field. + */ + memset(&methods, 0, sizeof(methods)); + sbsroutines->exec_setup(sbsref, sbsrefstate, &methods); /* * Evaluate array input. It's safe to do so into resv/resnull, because we @@ -2546,11 +2579,11 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, ExecInitExprRec(sbsref->refexpr, state, resv, resnull); /* - * If refexpr yields NULL, and it's a fetch, then result is NULL. We can - * implement this with just JUMP_IF_NULL, since we evaluated the array - * into the desired target location. + * If refexpr yields NULL, and the operation should be strict, then result + * is NULL. We can implement this with just JUMP_IF_NULL, since we + * evaluated the array into the desired target location. */ - if (!isAssignment) + if (!isAssignment && sbsroutines->fetch_strict) { scratch->opcode = EEOP_JUMP_IF_NULL; scratch->d.jump.jumpdone = -1; /* adjust later */ @@ -2559,19 +2592,6 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, state->steps_len - 1); } - /* Verify subscript list lengths are within limit */ - if (list_length(sbsref->refupperindexpr) > MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", - list_length(sbsref->refupperindexpr), MAXDIM))); - - if (list_length(sbsref->reflowerindexpr) > MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", - list_length(sbsref->reflowerindexpr), MAXDIM))); - /* Evaluate upper subscripts */ i = 0; foreach(lc, sbsref->refupperindexpr) @@ -2582,28 +2602,18 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, if (!e) { sbsrefstate->upperprovided[i] = false; - i++; - continue; + sbsrefstate->upperindexnull[i] = true; + } + else + { + sbsrefstate->upperprovided[i] = true; + /* Each subscript is evaluated into appropriate array entry */ + ExecInitExprRec(e, state, + &sbsrefstate->upperindex[i], + &sbsrefstate->upperindexnull[i]); } - - sbsrefstate->upperprovided[i] = true; - - /* Each subscript is evaluated into subscriptvalue/subscriptnull */ - ExecInitExprRec(e, state, - &sbsrefstate->subscriptvalue, &sbsrefstate->subscriptnull); - - /* ... and then SBSREF_SUBSCRIPT saves it into step's workspace */ - scratch->opcode = EEOP_SBSREF_SUBSCRIPT; - scratch->d.sbsref_subscript.state = sbsrefstate; - scratch->d.sbsref_subscript.off = i; - scratch->d.sbsref_subscript.isupper = true; - scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ - ExprEvalPushStep(state, scratch); - adjust_jumps = lappend_int(adjust_jumps, - state->steps_len - 1); i++; } - sbsrefstate->numupper = i; /* Evaluate lower subscripts similarly */ i = 0; @@ -2615,39 +2625,43 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, if (!e) { sbsrefstate->lowerprovided[i] = false; - i++; - continue; + sbsrefstate->lowerindexnull[i] = true; } + else + { + sbsrefstate->lowerprovided[i] = true; + /* Each subscript is evaluated into appropriate array entry */ + ExecInitExprRec(e, state, + &sbsrefstate->lowerindex[i], + &sbsrefstate->lowerindexnull[i]); + } + i++; + } - sbsrefstate->lowerprovided[i] = true; - - /* Each subscript is evaluated into subscriptvalue/subscriptnull */ - ExecInitExprRec(e, state, - &sbsrefstate->subscriptvalue, &sbsrefstate->subscriptnull); - - /* ... and then SBSREF_SUBSCRIPT saves it into step's workspace */ - scratch->opcode = EEOP_SBSREF_SUBSCRIPT; + /* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */ + if (methods.sbs_check_subscripts) + { + scratch->opcode = EEOP_SBSREF_SUBSCRIPTS; + scratch->d.sbsref_subscript.subscriptfunc = methods.sbs_check_subscripts; scratch->d.sbsref_subscript.state = sbsrefstate; - scratch->d.sbsref_subscript.off = i; - scratch->d.sbsref_subscript.isupper = false; scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ ExprEvalPushStep(state, scratch); adjust_jumps = lappend_int(adjust_jumps, state->steps_len - 1); - i++; } - sbsrefstate->numlower = i; - - /* 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"); if (isAssignment) { Datum *save_innermost_caseval; bool *save_innermost_casenull; + /* Check for unimplemented methods */ + if (!methods.sbs_assign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("type %s does not support subscripted assignment", + format_type_be(sbsref->refcontainertype)))); + /* * We might have a nested-assignment situation, in which the * refassgnexpr is itself a FieldStore or SubscriptingRef that needs @@ -2664,7 +2678,13 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, */ if (isAssignmentIndirectionExpr(sbsref->refassgnexpr)) { + if (!methods.sbs_fetch_old) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("type %s does not support subscripted assignment", + format_type_be(sbsref->refcontainertype)))); scratch->opcode = EEOP_SBSREF_OLD; + scratch->d.sbsref.subscriptfunc = methods.sbs_fetch_old; scratch->d.sbsref.state = sbsrefstate; ExprEvalPushStep(state, scratch); } @@ -2684,17 +2704,17 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, /* and perform the assignment */ scratch->opcode = EEOP_SBSREF_ASSIGN; + scratch->d.sbsref.subscriptfunc = methods.sbs_assign; scratch->d.sbsref.state = sbsrefstate; ExprEvalPushStep(state, scratch); - } else { /* array fetch is much simpler */ scratch->opcode = EEOP_SBSREF_FETCH; + scratch->d.sbsref.subscriptfunc = methods.sbs_fetch; scratch->d.sbsref.state = sbsrefstate; ExprEvalPushStep(state, scratch); - } /* adjust jump targets */ @@ -2702,7 +2722,7 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, { ExprEvalStep *as = &state->steps[lfirst_int(lc)]; - if (as->opcode == EEOP_SBSREF_SUBSCRIPT) + if (as->opcode == EEOP_SBSREF_SUBSCRIPTS) { Assert(as->d.sbsref_subscript.jumpdone == -1); as->d.sbsref_subscript.jumpdone = state->steps_len; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index c09371ad58f..6b9fc38134b 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -417,7 +417,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_FIELDSELECT, &&CASE_EEOP_FIELDSTORE_DEFORM, &&CASE_EEOP_FIELDSTORE_FORM, - &&CASE_EEOP_SBSREF_SUBSCRIPT, + &&CASE_EEOP_SBSREF_SUBSCRIPTS, &&CASE_EEOP_SBSREF_OLD, &&CASE_EEOP_SBSREF_ASSIGN, &&CASE_EEOP_SBSREF_FETCH, @@ -1396,12 +1396,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } - EEO_CASE(EEOP_SBSREF_SUBSCRIPT) + EEO_CASE(EEOP_SBSREF_SUBSCRIPTS) { - /* Process an array subscript */ - - /* too complex for an inline implementation */ - if (ExecEvalSubscriptingRef(state, op)) + /* Precheck SubscriptingRef subscript(s) */ + if (op->d.sbsref_subscript.subscriptfunc(state, op, econtext)) { EEO_NEXT(); } @@ -1413,37 +1411,11 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) } EEO_CASE(EEOP_SBSREF_OLD) + EEO_CASE(EEOP_SBSREF_ASSIGN) + EEO_CASE(EEOP_SBSREF_FETCH) { - /* - * Fetch the old value in an sbsref assignment, in case it's - * referenced (via a CaseTestExpr) inside the assignment - * expression. - */ - - /* too complex for an inline implementation */ - ExecEvalSubscriptingRefOld(state, op); - - EEO_NEXT(); - } - - /* - * Perform SubscriptingRef assignment - */ - EEO_CASE(EEOP_SBSREF_ASSIGN) - { - /* too complex for an inline implementation */ - ExecEvalSubscriptingRefAssign(state, op); - - EEO_NEXT(); - } - - /* - * Fetch subset of an array. - */ - EEO_CASE(EEOP_SBSREF_FETCH) - { - /* too complex for an inline implementation */ - ExecEvalSubscriptingRefFetch(state, op); + /* Perform a SubscriptingRef fetch or assignment */ + op->d.sbsref.subscriptfunc(state, op, econtext); EEO_NEXT(); } @@ -3123,200 +3095,6 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext } /* - * Process a subscript in a SubscriptingRef expression. - * - * If subscript is NULL, throw error in assignment case, or in fetch case - * set result to NULL and return false (instructing caller to skip the rest - * of the SubscriptingRef sequence). - * - * Subscript expression result is in subscriptvalue/subscriptnull. - * On success, integer subscript value has been saved in upperindex[] or - * lowerindex[] for use later. - */ -bool -ExecEvalSubscriptingRef(ExprState *state, ExprEvalStep *op) -{ - SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; - int *indexes; - int off; - - /* If any index expr yields NULL, result is NULL or error */ - if (sbsrefstate->subscriptnull) - { - 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; - } - - /* Convert datum to int, save in appropriate place */ - if (op->d.sbsref_subscript.isupper) - indexes = sbsrefstate->upperindex; - else - indexes = sbsrefstate->lowerindex; - off = op->d.sbsref_subscript.off; - - indexes[off] = DatumGetInt32(sbsrefstate->subscriptvalue); - - return true; -} - -/* - * Evaluate SubscriptingRef fetch. - * - * Source container is in step's result variable. - */ -void -ExecEvalSubscriptingRefFetch(ExprState *state, ExprEvalStep *op) -{ - SubscriptingRefState *sbsrefstate = op->d.sbsref.state; - - /* Should not get here if source container (or any subscript) is null */ - Assert(!(*op->resnull)); - - if (sbsrefstate->numlower == 0) - { - /* Scalar case */ - *op->resvalue = array_get_element(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign, - op->resnull); - } - else - { - /* Slice case */ - *op->resvalue = array_get_slice(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->lowerindex, - sbsrefstate->upperprovided, - sbsrefstate->lowerprovided, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign); - } -} - -/* - * Compute old container element/slice value for a SubscriptingRef assignment - * expression. Will only be generated if the new-value subexpression - * contains SubscriptingRef or FieldStore. The value is stored into the - * SubscriptingRefState's prevvalue/prevnull fields. - */ -void -ExecEvalSubscriptingRefOld(ExprState *state, ExprEvalStep *op) -{ - SubscriptingRefState *sbsrefstate = op->d.sbsref.state; - - if (*op->resnull) - { - /* whole array is null, so any element or slice is too */ - sbsrefstate->prevvalue = (Datum) 0; - sbsrefstate->prevnull = true; - } - else if (sbsrefstate->numlower == 0) - { - /* Scalar case */ - sbsrefstate->prevvalue = array_get_element(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign, - &sbsrefstate->prevnull); - } - else - { - /* Slice case */ - /* this is currently unreachable */ - sbsrefstate->prevvalue = array_get_slice(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->lowerindex, - sbsrefstate->upperprovided, - sbsrefstate->lowerprovided, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign); - sbsrefstate->prevnull = false; - } -} - -/* - * Evaluate SubscriptingRef assignment. - * - * Input container (possibly null) is in result area, replacement value is in - * SubscriptingRefState's replacevalue/replacenull. - */ -void -ExecEvalSubscriptingRefAssign(ExprState *state, ExprEvalStep *op) -{ - SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; - - /* - * For an assignment to a fixed-length container type, both the original - * container and the value to be assigned into it must be non-NULL, else - * we punt and return the original container. - */ - if (sbsrefstate->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) - { - *op->resvalue = PointerGetDatum(construct_empty_array(sbsrefstate->refelemtype)); - *op->resnull = false; - } - - if (sbsrefstate->numlower == 0) - { - /* Scalar case */ - *op->resvalue = array_set_element(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->replacevalue, - sbsrefstate->replacenull, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign); - } - else - { - /* Slice case */ - *op->resvalue = array_set_slice(*op->resvalue, - sbsrefstate->numupper, - sbsrefstate->upperindex, - sbsrefstate->lowerindex, - sbsrefstate->upperprovided, - sbsrefstate->lowerprovided, - sbsrefstate->replacevalue, - sbsrefstate->replacenull, - sbsrefstate->refattrlength, - sbsrefstate->refelemlength, - sbsrefstate->refelembyval, - sbsrefstate->refelemalign); - } -} - -/* * Evaluate a rowtype coercion operation. * This may require rearranging field positions. * diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index e0d53c0d0a2..3aa08a97430 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1094,23 +1094,56 @@ llvm_compile_expr(ExprState *state) break; } - case EEOP_SBSREF_OLD: - build_EvalXFunc(b, mod, "ExecEvalSubscriptingRefOld", - v_state, op); - LLVMBuildBr(b, opblocks[opno + 1]); - break; + case EEOP_SBSREF_SUBSCRIPTS: + { + int jumpdone = op->d.sbsref_subscript.jumpdone; + LLVMTypeRef v_functype; + LLVMValueRef v_func; + LLVMValueRef v_params[3]; + LLVMValueRef v_ret; - case EEOP_SBSREF_ASSIGN: - build_EvalXFunc(b, mod, "ExecEvalSubscriptingRefAssign", - v_state, op); - LLVMBuildBr(b, opblocks[opno + 1]); - break; + v_functype = llvm_pg_var_func_type("TypeExecEvalBoolSubroutine"); + v_func = l_ptr_const(op->d.sbsref_subscript.subscriptfunc, + LLVMPointerType(v_functype, 0)); + v_params[0] = v_state; + v_params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_params[2] = v_econtext; + v_ret = LLVMBuildCall(b, + v_func, + v_params, lengthof(v_params), ""); + v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, ""); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, LLVMIntEQ, v_ret, + l_sbool_const(1), ""), + opblocks[opno + 1], + opblocks[jumpdone]); + break; + } + + case EEOP_SBSREF_OLD: + case EEOP_SBSREF_ASSIGN: case EEOP_SBSREF_FETCH: - build_EvalXFunc(b, mod, "ExecEvalSubscriptingRefFetch", - v_state, op); - LLVMBuildBr(b, opblocks[opno + 1]); - break; + { + LLVMTypeRef v_functype; + LLVMValueRef v_func; + LLVMValueRef v_params[3]; + + v_functype = llvm_pg_var_func_type("TypeExecEvalSubroutine"); + v_func = l_ptr_const(op->d.sbsref.subscriptfunc, + LLVMPointerType(v_functype, 0)); + + v_params[0] = v_state; + v_params[1] = l_ptr_const(op, l_ptr(StructExprEvalStep)); + v_params[2] = v_econtext; + LLVMBuildCall(b, + v_func, + v_params, lengthof(v_params), ""); + + LLVMBuildBr(b, opblocks[opno + 1]); + break; + } case EEOP_CASE_TESTVAL: { @@ -1725,23 +1758,6 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; - case EEOP_SBSREF_SUBSCRIPT: - { - int jumpdone = op->d.sbsref_subscript.jumpdone; - LLVMValueRef v_ret; - - v_ret = build_EvalXFunc(b, mod, "ExecEvalSubscriptingRef", - v_state, op); - v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, ""); - - LLVMBuildCondBr(b, - LLVMBuildICmp(b, LLVMIntEQ, v_ret, - l_sbool_const(1), ""), - opblocks[opno + 1], - opblocks[jumpdone]); - break; - } - case EEOP_DOMAIN_TESTVAL: { LLVMBasicBlockRef b_avail, diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index e1b23d30524..fb7400e99d4 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -50,6 +50,7 @@ size_t TypeSizeT; bool TypeStorageBool; ExprStateEvalFunc TypeExprStateEvalFunc; ExecEvalSubroutine TypeExecEvalSubroutine; +ExecEvalBoolSubroutine TypeExecEvalBoolSubroutine; NullableDatum StructNullableDatum; AggState StructAggState; @@ -126,10 +127,6 @@ void *referenced_functions[] = ExecEvalSQLValueFunction, ExecEvalScalarArrayOp, ExecEvalSubPlan, - ExecEvalSubscriptingRef, - ExecEvalSubscriptingRefAssign, - ExecEvalSubscriptingRefFetch, - ExecEvalSubscriptingRefOld, ExecEvalSysVar, ExecEvalWholeRowVar, ExecEvalXmlExpr, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 910906f6396..70f8b718e0d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1548,6 +1548,7 @@ _copySubscriptingRef(const SubscriptingRef *from) COPY_SCALAR_FIELD(refcontainertype); COPY_SCALAR_FIELD(refelemtype); + COPY_SCALAR_FIELD(refrestype); COPY_SCALAR_FIELD(reftypmod); COPY_SCALAR_FIELD(refcollid); COPY_NODE_FIELD(refupperindexpr); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 687609f59eb..541e0e6b485 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -276,6 +276,7 @@ _equalSubscriptingRef(const SubscriptingRef *a, const SubscriptingRef *b) { COMPARE_SCALAR_FIELD(refcontainertype); COMPARE_SCALAR_FIELD(refelemtype); + COMPARE_SCALAR_FIELD(refrestype); COMPARE_SCALAR_FIELD(reftypmod); COMPARE_SCALAR_FIELD(refcollid); COMPARE_NODE_FIELD(refupperindexpr); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 1dc873ed255..963f71e99d4 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -66,15 +66,7 @@ exprType(const Node *expr) type = ((const WindowFunc *) expr)->wintype; break; case T_SubscriptingRef: - { - const SubscriptingRef *sbsref = (const SubscriptingRef *) expr; - - /* slice and/or store operations yield the container type */ - if (sbsref->reflowerindexpr || sbsref->refassgnexpr) - type = sbsref->refcontainertype; - else - type = sbsref->refelemtype; - } + type = ((const SubscriptingRef *) expr)->refrestype; break; case T_FuncExpr: type = ((const FuncExpr *) expr)->funcresulttype; @@ -286,7 +278,6 @@ exprTypmod(const Node *expr) case T_Param: return ((const Param *) expr)->paramtypmod; case T_SubscriptingRef: - /* typmod is the same for container or element */ return ((const SubscriptingRef *) expr)->reftypmod; case T_FuncExpr: { diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 8f5e4e71b28..d78b16ed1d9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1194,6 +1194,7 @@ _outSubscriptingRef(StringInfo str, const SubscriptingRef *node) WRITE_OID_FIELD(refcontainertype); WRITE_OID_FIELD(refelemtype); + WRITE_OID_FIELD(refrestype); WRITE_INT_FIELD(reftypmod); WRITE_OID_FIELD(refcollid); WRITE_NODE_FIELD(refupperindexpr); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 169d5581b91..0f6a77afc43 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -671,6 +671,7 @@ _readSubscriptingRef(void) READ_OID_FIELD(refcontainertype); READ_OID_FIELD(refelemtype); + READ_OID_FIELD(refrestype); READ_INT_FIELD(reftypmod); READ_OID_FIELD(refcollid); READ_NODE_FIELD(refupperindexpr); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index cb7fa661805..e3a81a7a02a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -32,6 +32,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" #include "nodes/supportnodes.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -839,13 +840,16 @@ contain_nonstrict_functions_walker(Node *node, void *context) } if (IsA(node, SubscriptingRef)) { - /* - * subscripting assignment is nonstrict, but subscripting itself is - * strict - */ - if (((SubscriptingRef *) node)->refassgnexpr != NULL) - return true; + SubscriptingRef *sbsref = (SubscriptingRef *) node; + const SubscriptRoutines *sbsroutines; + /* Subscripting assignment is always presumed nonstrict */ + if (sbsref->refassgnexpr != NULL) + return true; + /* Otherwise we must look up the subscripting support methods */ + sbsroutines = getSubscriptingRoutines(sbsref->refcontainertype, NULL); + if (!sbsroutines->fetch_strict) + return true; /* else fall through to check args */ } if (IsA(node, DistinctExpr)) @@ -1135,12 +1139,14 @@ contain_leaked_vars_walker(Node *node, void *context) case T_SubscriptingRef: { SubscriptingRef *sbsref = (SubscriptingRef *) node; - - /* - * subscripting assignment is leaky, but subscripted fetches - * are not - */ - if (sbsref->refassgnexpr != NULL) + const SubscriptRoutines *sbsroutines; + + /* Consult the subscripting support method info */ + sbsroutines = getSubscriptingRoutines(sbsref->refcontainertype, + NULL); + if (!(sbsref->refassgnexpr != NULL ? + sbsroutines->store_leakproof : + sbsroutines->fetch_leakproof)) { /* Node is leaky, so reject if it contains Vars */ if (contain_var_clause(node)) @@ -2859,6 +2865,11 @@ eval_const_expressions_mutator(Node *node, * known to be immutable, and for which we need no smarts * beyond "simplify if all inputs are constants". * + * Treating SubscriptingRef this way assumes that subscripting + * fetch and assignment are both immutable. This constrains + * type-specific subscripting implementations; maybe we should + * relax it someday. + * * Treating MinMaxExpr this way amounts to assuming that the * btree comparison function it calls is immutable; see the * reasoning in contain_mutable_functions_walker. @@ -3122,10 +3133,10 @@ eval_const_expressions_mutator(Node *node, { /* * This case could be folded into the generic handling used - * for SubscriptingRef etc. But because the simplification - * logic is so trivial, applying evaluate_expr() to perform it - * would be a heavy overhead. BooleanTest is probably common - * enough to justify keeping this bespoke implementation. + * for ArrayExpr etc. But because the simplification logic is + * so trivial, applying evaluate_expr() to perform it would be + * a heavy overhead. BooleanTest is probably common enough to + * justify keeping this bespoke implementation. */ BooleanTest *btest = (BooleanTest *) node; BooleanTest *newbtest; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index a2924e3d1ce..da6c3ae4b5f 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -26,6 +26,7 @@ #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/datum.h" /* needed for datumIsEqual() */ +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -2854,8 +2855,8 @@ find_typmod_coercion_function(Oid typeId, targetType = typeidType(typeId); typeForm = (Form_pg_type) GETSTRUCT(targetType); - /* Check for a varlena array type */ - if (typeForm->typelem != InvalidOid && typeForm->typlen == -1) + /* Check for a "true" array type */ + if (IsTrueArrayType(typeForm)) { /* Yes, switch our attention to the element type */ typeId = typeForm->typelem; diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index bf800f5937b..13e62a20156 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -667,6 +667,29 @@ assign_collations_walker(Node *node, assign_collations_context *context) &loccontext); } break; + case T_SubscriptingRef: + { + /* + * The subscripts are treated as independent + * expressions not contributing to the node's + * collation. Only the container, and the source + * expression if any, contribute. (This models + * the old behavior, in which the subscripts could + * be counted on to be integers and thus not + * contribute anything.) + */ + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + assign_expr_collations(context->pstate, + (Node *) sbsref->refupperindexpr); + assign_expr_collations(context->pstate, + (Node *) sbsref->reflowerindexpr); + (void) assign_collations_walker((Node *) sbsref->refexpr, + &loccontext); + (void) assign_collations_walker((Node *) sbsref->refassgnexpr, + &loccontext); + } + break; default: /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 1e62d31aca7..ffc96e2a6fa 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -406,10 +406,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind) result = (Node *) transformContainerSubscripts(pstate, result, exprType(result), - InvalidOid, exprTypmod(result), subscripts, - NULL); + false); subscripts = NIL; newresult = ParseFuncOrColumn(pstate, @@ -429,10 +428,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind) result = (Node *) transformContainerSubscripts(pstate, result, exprType(result), - InvalidOid, exprTypmod(result), subscripts, - NULL); + false); return result; } diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 6e98fe55fc4..e90f6c9d010 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -20,6 +20,7 @@ #include "mb/pg_wchar.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" @@ -182,23 +183,16 @@ pcb_error_callback(void *arg) /* * transformContainerType() - * Identify the types involved in a subscripting operation for container + * Identify the actual container type for a subscripting operation. * - * - * On entry, containerType/containerTypmod identify the type of the input value - * to be subscripted (which could be a domain type). These are modified if - * necessary to identify the actual container type and typmod, and the - * container's element type is returned. An error is thrown if the input isn't - * an array type. + * containerType/containerTypmod are modified if necessary to identify + * the actual container type and typmod. This mainly involves smashing + * any domain to its base type, but there are some special considerations. + * Note that caller still needs to check if the result type is a container. */ -Oid +void transformContainerType(Oid *containerType, int32 *containerTypmod) { - Oid origContainerType = *containerType; - Oid elementType; - HeapTuple type_tuple_container; - Form_pg_type type_struct_container; - /* * If the input is a domain, smash to base type, and extract the actual * typmod to be applied to the base type. Subscripting a domain is an @@ -209,35 +203,16 @@ transformContainerType(Oid *containerType, int32 *containerTypmod) *containerType = getBaseTypeAndTypmod(*containerType, containerTypmod); /* - * Here is an array specific code. We treat int2vector and oidvector as - * though they were domains over int2[] and oid[]. This is needed because - * array slicing could create an array that doesn't satisfy the - * dimensionality constraints of the xxxvector type; so we want the result - * of a slice operation to be considered to be of the more general type. + * We treat int2vector and oidvector as though they were domains over + * int2[] and oid[]. This is needed because array slicing could create an + * array that doesn't satisfy the dimensionality constraints of the + * xxxvector type; so we want the result of a slice operation to be + * considered to be of the more general type. */ if (*containerType == INT2VECTOROID) *containerType = INT2ARRAYOID; else if (*containerType == OIDVECTOROID) *containerType = OIDARRAYOID; - - /* Get the type tuple for the container */ - type_tuple_container = SearchSysCache1(TYPEOID, ObjectIdGetDatum(*containerType)); - if (!HeapTupleIsValid(type_tuple_container)) - elog(ERROR, "cache lookup failed for type %u", *containerType); - type_struct_container = (Form_pg_type) GETSTRUCT(type_tuple_container); - - /* needn't check typisdefined since this will fail anyway */ - - elementType = type_struct_container->typelem; - if (elementType == InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot subscript type %s because it is not an array", - format_type_be(origContainerType)))); - - ReleaseSysCache(type_tuple_container); - - return elementType; } /* @@ -249,13 +224,14 @@ transformContainerType(Oid *containerType, int32 *containerTypmod) * an expression that represents the result of extracting a single container * element or a container slice. * - * In a container assignment, we are given a destination container value plus a - * source value that is to be assigned to a single element or a slice of that - * container. We produce an expression that represents the new container value - * with the source data inserted into the right part of the container. + * Container assignments are treated basically the same as container fetches + * here. The caller will modify the result node to insert the source value + * that is to be assigned to the element or slice that a fetch would have + * retrieved. The execution result will be a new container value with + * the source value inserted into the right part of the container. * - * For both cases, if the source container is of a domain-over-array type, - * the result is of the base array type or its element type; essentially, + * For both cases, if the source is of a domain-over-container type, the + * result is the same as if it had been of the container type; essentially, * we must fold a domain to its base type before applying subscripting. * (Note that int2vector and oidvector are treated as domains here.) * @@ -264,48 +240,48 @@ transformContainerType(Oid *containerType, int32 *containerTypmod) * containerType OID of container's datatype (should match type of * containerBase, or be the base type of containerBase's * domain type) - * elementType OID of container's element type (fetch with - * transformContainerType, or pass InvalidOid to do it here) - * containerTypMod typmod for the container (which is also typmod for the - * elements) + * containerTypMod typmod for the container * indirection Untransformed list of subscripts (must not be NIL) - * assignFrom NULL for container fetch, else transformed expression for - * source. + * isAssignment True if this will become a container assignment. */ SubscriptingRef * transformContainerSubscripts(ParseState *pstate, Node *containerBase, Oid containerType, - Oid elementType, int32 containerTypMod, List *indirection, - Node *assignFrom) + bool isAssignment) { + SubscriptingRef *sbsref; + const SubscriptRoutines *sbsroutines; + Oid elementType; bool isSlice = false; - List *upperIndexpr = NIL; - List *lowerIndexpr = NIL; ListCell *idx; - SubscriptingRef *sbsref; /* - * Caller may or may not have bothered to determine elementType. Note - * that if the caller did do so, containerType/containerTypMod must be as - * modified by transformContainerType, ie, smash domain to base type. + * Determine the actual container type, smashing any domain. In the + * assignment case the caller already did this, since it also needs to + * know the actual container type. */ - if (!OidIsValid(elementType)) - elementType = transformContainerType(&containerType, &containerTypMod); + if (!isAssignment) + transformContainerType(&containerType, &containerTypMod); /* + * Verify that the container type is subscriptable, and get its support + * functions and typelem. + */ + sbsroutines = getSubscriptingRoutines(containerType, &elementType); + + /* + * Detect whether any of the indirection items are slice specifiers. + * * A list containing only simple subscripts refers to a single container * element. If any of the items are slice specifiers (lower:upper), then - * the subscript expression means a container slice operation. In this - * case, we convert any non-slice items to slices by treating the single - * subscript as the upper bound and supplying an assumed lower bound of 1. - * We have to prescan the list to see if there are any slice items. + * the subscript expression means a container slice operation. */ foreach(idx, indirection) { - A_Indices *ai = (A_Indices *) lfirst(idx); + A_Indices *ai = lfirst_node(A_Indices, idx); if (ai->is_slice) { @@ -315,120 +291,35 @@ transformContainerSubscripts(ParseState *pstate, } /* - * Transform the subscript expressions. - */ - 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); - } - - /* - * If doing an array store, coerce the source value to the right type. - * (This should agree with the coercion done by transformAssignedExpr.) - */ - if (assignFrom != NULL) - { - Oid typesource = exprType(assignFrom); - Oid typeneeded = isSlice ? containerType : elementType; - Node *newFrom; - - newFrom = coerce_to_target_type(pstate, - assignFrom, typesource, - typeneeded, containerTypMod, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - if (newFrom == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("array assignment requires type %s" - " but expression is of type %s", - format_type_be(typeneeded), - format_type_be(typesource)), - errhint("You will need to rewrite or cast the expression."), - parser_errposition(pstate, exprLocation(assignFrom)))); - assignFrom = newFrom; - } - - /* * Ready to build the SubscriptingRef node. */ - sbsref = (SubscriptingRef *) makeNode(SubscriptingRef); - if (assignFrom != NULL) - sbsref->refassgnexpr = (Expr *) assignFrom; + sbsref = makeNode(SubscriptingRef); sbsref->refcontainertype = containerType; sbsref->refelemtype = elementType; + /* refrestype is to be set by container-specific logic */ sbsref->reftypmod = containerTypMod; /* refcollid will be set by parse_collate.c */ - sbsref->refupperindexpr = upperIndexpr; - sbsref->reflowerindexpr = lowerIndexpr; + /* refupperindexpr, reflowerindexpr are to be set by container logic */ sbsref->refexpr = (Expr *) containerBase; - sbsref->refassgnexpr = (Expr *) assignFrom; + sbsref->refassgnexpr = NULL; /* caller will fill if it's an assignment */ + + /* + * Call the container-type-specific logic to transform the subscripts and + * determine the subscripting result type. + */ + sbsroutines->transform(sbsref, indirection, pstate, + isSlice, isAssignment); + + /* + * Verify we got a valid type (this defends, for example, against someone + * using array_subscript_handler as typsubscript without setting typelem). + */ + if (!OidIsValid(sbsref->refrestype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot subscript type %s because it does not support subscripting", + format_type_be(containerType)))); return sbsref; } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index ce68663cc2c..3dda8e2847d 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -861,7 +861,7 @@ transformAssignmentIndirection(ParseState *pstate, if (targetIsSubscripting) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("array assignment to \"%s\" requires type %s" + errmsg("subscripted assignment to \"%s\" requires type %s" " but expression is of type %s", targetName, format_type_be(targetTypeId), @@ -901,26 +901,37 @@ transformAssignmentSubscripts(ParseState *pstate, int location) { Node *result; + SubscriptingRef *sbsref; Oid containerType; int32 containerTypMod; - Oid elementTypeId; Oid typeNeeded; + int32 typmodNeeded; Oid collationNeeded; Assert(subscripts != NIL); - /* Identify the actual array type and element type involved */ + /* Identify the actual container type involved */ containerType = targetTypeId; containerTypMod = targetTypMod; - elementTypeId = transformContainerType(&containerType, &containerTypMod); + transformContainerType(&containerType, &containerTypMod); - /* Identify type that RHS must provide */ - typeNeeded = isSlice ? containerType : elementTypeId; + /* Process subscripts and identify required type for RHS */ + sbsref = transformContainerSubscripts(pstate, + basenode, + containerType, + containerTypMod, + subscripts, + true); + + typeNeeded = sbsref->refrestype; + typmodNeeded = sbsref->reftypmod; /* - * container normally has same collation as elements, but there's an - * exception: we might be subscripting a domain over a container type. In - * that case use collation of the base type. + * Container normally has same collation as its elements, but there's an + * exception: we might be subscripting a domain over a container type. In + * that case use collation of the base type. (This is shaky for arbitrary + * subscripting semantics, but it doesn't matter all that much since we + * only use this to label the collation of a possible CaseTestExpr.) */ if (containerType == targetTypeId) collationNeeded = targetCollation; @@ -933,21 +944,22 @@ transformAssignmentSubscripts(ParseState *pstate, targetName, true, typeNeeded, - containerTypMod, + typmodNeeded, collationNeeded, indirection, next_indirection, rhs, location); - /* process subscripts */ - result = (Node *) transformContainerSubscripts(pstate, - basenode, - containerType, - elementTypeId, - containerTypMod, - subscripts, - rhs); + /* + * Insert the already-properly-coerced RHS into the SubscriptingRef. Then + * set refrestype and reftypmod back to the container type's values. + */ + sbsref->refassgnexpr = (Expr *) rhs; + sbsref->refrestype = containerType; + sbsref->reftypmod = containerTypMod; + + result = (Node *) sbsref; /* If target was a domain over container, need to coerce up to the domain */ if (containerType != targetTypeId) diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index f6ec7b64cd4..ce09ad73754 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,6 +17,7 @@ OBJS = \ array_typanalyze.o \ array_userfuncs.o \ arrayfuncs.o \ + arraysubs.o \ arrayutils.o \ ascii.o \ bool.o \ diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index a7ea7656c75..4c8a739bc43 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -2044,7 +2044,8 @@ array_get_element_expanded(Datum arraydatum, * array bound. * * NOTE: we assume it is OK to scribble on the provided subscript arrays - * lowerIndx[] and upperIndx[]. These are generally just temporaries. + * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM + * even when nSubscripts is less. These are generally just temporaries. */ Datum array_get_slice(Datum arraydatum, @@ -2772,7 +2773,8 @@ array_set_element_expanded(Datum arraydatum, * (XXX TODO: allow a corresponding behavior for multidimensional arrays) * * NOTE: we assume it is OK to scribble on the provided index arrays - * lowerIndx[] and upperIndx[]. These are generally just temporaries. + * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM + * even when nSubscripts is less. These are generally just temporaries. * * NOTE: For assignments, we throw an error for silly subscripts etc, * rather than returning a NULL or empty array as the fetch operations do. diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c new file mode 100644 index 00000000000..a081288f42d --- /dev/null +++ b/src/backend/utils/adt/arraysubs.c @@ -0,0 +1,577 @@ +/*------------------------------------------------------------------------- + * + * arraysubs.c + * Subscripting support functions for arrays. + * + * Portions Copyright (c) 1996-2020, 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); +} diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index f2816e4f37f..013409aee7d 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/numeric.h" #include "utils/syscache.h" @@ -138,15 +139,14 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags) typeform = (Form_pg_type) GETSTRUCT(tuple); /* - * Check if it's a regular (variable length) array type. Fixed-length - * array types such as "name" shouldn't get deconstructed. As of Postgres - * 8.1, rather than checking typlen we check the toast property, and don't + * Check if it's a "true" array type. Pseudo-array types such as "name" + * shouldn't get deconstructed. Also check the toast property, and don't * deconstruct "plain storage" array types --- this is because we don't * want to show oidvector as oid[]. */ array_base_type = typeform->typelem; - if (array_base_type != InvalidOid && + if (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN) { /* Switch our attention to the array element type */ diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index d370348a1c5..12557ce3af5 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -26,6 +26,7 @@ #include "miscadmin.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/hsearch.h" #include "utils/json.h" #include "utils/jsonb.h" @@ -3011,7 +3012,7 @@ prepare_column_cache(ColumnIOData *column, column->io.composite.base_typmod = typmod; column->io.composite.domain_info = NULL; } - else if (type->typlen == -1 && OidIsValid(type->typelem)) + else if (IsTrueArrayType(type)) { column->typcat = TYPECAT_ARRAY; column->io.array.element_info = MemoryContextAllocZero(mcxt, diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 47a83658492..7dd49454462 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2661,8 +2661,9 @@ get_typ_typrelid(Oid typid) * * Given the type OID, get the typelem (InvalidOid if not an array type). * - * NB: this only considers varlena arrays to be true arrays; InvalidOid is - * returned if the input is a fixed-length array type. + * NB: this only succeeds for "true" arrays having array_subscript_handler + * as typsubscript. For other types, InvalidOid is returned independently + * of whether they have typelem or typsubscript set. */ Oid get_element_type(Oid typid) @@ -2675,7 +2676,7 @@ get_element_type(Oid typid) Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); Oid result; - if (typtup->typlen == -1) + if (IsTrueArrayType(typtup)) result = typtup->typelem; else result = InvalidOid; @@ -2758,7 +2759,7 @@ get_base_element_type(Oid typid) Oid result; /* This test must match get_element_type */ - if (typTup->typlen == -1) + if (IsTrueArrayType(typTup)) result = typTup->typelem; else result = InvalidOid; @@ -2993,6 +2994,64 @@ type_is_collatable(Oid typid) } +/* + * get_typsubscript + * + * Given the type OID, return the type's subscripting handler's OID, + * if it has one. + * + * If typelemp isn't NULL, we also store the type's typelem value there. + * This saves some callers an extra catalog lookup. + */ +RegProcedure +get_typsubscript(Oid typid, Oid *typelemp) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + RegProcedure handler = typform->typsubscript; + + if (typelemp) + *typelemp = typform->typelem; + ReleaseSysCache(tp); + return handler; + } + else + { + if (typelemp) + *typelemp = InvalidOid; + return InvalidOid; + } +} + +/* + * getSubscriptingRoutines + * + * Given the type OID, fetch the type's subscripting methods struct. + * Fail if type is not subscriptable. + * + * If typelemp isn't NULL, we also store the type's typelem value there. + * This saves some callers an extra catalog lookup. + */ +const struct SubscriptRoutines * +getSubscriptingRoutines(Oid typid, Oid *typelemp) +{ + RegProcedure typsubscript = get_typsubscript(typid, typelemp); + + if (!OidIsValid(typsubscript)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot subscript type %s because it does not support subscripting", + format_type_be(typid)))); + + return (const struct SubscriptRoutines *) + DatumGetPointer(OidFunctionCall0(typsubscript)); +} + + /* ---------- STATISTICS CACHE ---------- */ /* diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index dca1d48e895..5883fde3675 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -406,6 +406,7 @@ lookup_type_cache(Oid type_id, int flags) typentry->typstorage = typtup->typstorage; typentry->typtype = typtup->typtype; typentry->typrelid = typtup->typrelid; + typentry->typsubscript = typtup->typsubscript; typentry->typelem = typtup->typelem; typentry->typcollation = typtup->typcollation; typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; @@ -450,6 +451,7 @@ lookup_type_cache(Oid type_id, int flags) typentry->typstorage = typtup->typstorage; typentry->typtype = typtup->typtype; typentry->typrelid = typtup->typrelid; + typentry->typsubscript = typtup->typsubscript; typentry->typelem = typtup->typelem; typentry->typcollation = typtup->typcollation; typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; |