diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 21087d6af75..0fe06fd7c8b 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -2105,6 +2105,16 @@ TypeCacheRelCallback(Datum arg, Oid relid) if (--typentry->tupDesc->tdrefcount == 0) FreeTupleDesc(typentry->tupDesc); typentry->tupDesc = NULL; + + /* + * Also clear tupDesc_identifier, so that anything watching + * that will realize that the tupdesc has possibly changed. + * (Alternatively, we could specify that to detect possible + * tupdesc change, one must check for tupDesc != NULL as well + * as tupDesc_identifier being the same as what was previously + * seen. That seems error-prone.) + */ + typentry->tupDesc_identifier = 0; } /* Reset equality/comparison/hashing validity information */ diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out index b9d76b5c6b7..403c6358b93 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_record.out +++ b/src/pl/plpgsql/src/expected/plpgsql_record.out @@ -447,6 +447,35 @@ alter table mutable drop column f3; select getf3(null::mutable); -- fails again ERROR: record "x" has no field "f3" \set SHOW_CONTEXT errors +-- check behavior with creating/dropping a named rowtype +set check_function_bodies = off; -- else reference to nonexistent type fails +create function sillyaddtwo(int) returns int language plpgsql as +$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$; +reset check_function_bodies; +select sillyaddtwo(42); -- fail +ERROR: type "mutable2" does not exist +LINE 1: declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end + ^ +QUERY: declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end +CONTEXT: compilation of PL/pgSQL function "sillyaddtwo" near line 1 +create table mutable2(f1 int, f2 text); +select sillyaddtwo(42); + sillyaddtwo +------------- + 44 +(1 row) + +drop table mutable2; +select sillyaddtwo(42); -- fail +ERROR: type "mutable2" does not exist +CONTEXT: PL/pgSQL function sillyaddtwo(integer) line 1 at assignment +create table mutable2(f0 text, f1 int, f2 text); +select sillyaddtwo(42); + sillyaddtwo +------------- + 44 +(1 row) + -- check access to system columns in a record variable create function sillytrig() returns trigger language plpgsql as $$begin diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index f5ca1a359f5..b4255484d8c 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -105,7 +105,8 @@ static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr, ColumnRef *cref, bool error_if_no_field); static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location); static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars); -static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, Oid collation); +static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, + Oid collation, TypeName *origtypname); static void plpgsql_start_datums(void); static void plpgsql_finish_datums(PLpgSQL_function *function); static void compute_function_hashkey(FunctionCallInfo fcinfo, @@ -423,7 +424,8 @@ do_compile(FunctionCallInfo fcinfo, /* Create datatype info */ argdtype = plpgsql_build_datatype(argtypeid, -1, - function->fn_input_collation); + function->fn_input_collation, + NULL); /* Disallow pseudotype argument */ /* (note we already replaced polymorphic types) */ @@ -573,7 +575,8 @@ do_compile(FunctionCallInfo fcinfo, (void) plpgsql_build_variable("$0", 0, build_datatype(typeTup, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); } @@ -607,7 +610,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_name", 0, plpgsql_build_datatype(NAMEOID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -617,7 +621,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_when", 0, plpgsql_build_datatype(TEXTOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -627,7 +632,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_level", 0, plpgsql_build_datatype(TEXTOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -637,7 +643,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_op", 0, plpgsql_build_datatype(TEXTOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -647,7 +654,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_relid", 0, plpgsql_build_datatype(OIDOID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -657,7 +665,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_relname", 0, plpgsql_build_datatype(NAMEOID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -667,7 +676,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_table_name", 0, plpgsql_build_datatype(NAMEOID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -677,7 +687,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_table_schema", 0, plpgsql_build_datatype(NAMEOID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -687,7 +698,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_nargs", 0, plpgsql_build_datatype(INT4OID, -1, - InvalidOid), + InvalidOid, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -697,7 +709,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_argv", 0, plpgsql_build_datatype(TEXTARRAYOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -722,7 +735,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_event", 0, plpgsql_build_datatype(TEXTOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -732,7 +746,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("tg_tag", 0, plpgsql_build_datatype(TEXTOID, -1, - function->fn_input_collation), + function->fn_input_collation, + NULL), true); Assert(var->dtype == PLPGSQL_DTYPE_VAR); var->dtype = PLPGSQL_DTYPE_PROMISE; @@ -755,7 +770,8 @@ do_compile(FunctionCallInfo fcinfo, var = plpgsql_build_variable("found", 0, plpgsql_build_datatype(BOOLOID, -1, - InvalidOid), + InvalidOid, + NULL), true); function->found_varno = var->dno; @@ -907,7 +923,8 @@ plpgsql_compile_inline(char *proc_source) var = plpgsql_build_variable("found", 0, plpgsql_build_datatype(BOOLOID, -1, - InvalidOid), + InvalidOid, + NULL), true); function->found_varno = var->dno; @@ -1567,6 +1584,7 @@ plpgsql_parse_wordtype(char *ident) { PLpgSQL_type *dtype; PLpgSQL_nsitem *nse; + TypeName *typeName; HeapTuple typeTup; /* @@ -1594,7 +1612,8 @@ plpgsql_parse_wordtype(char *ident) * Word wasn't found in the namespace stack. Try to find a data type with * that name, but ignore shell types and complex types. */ - typeTup = LookupTypeName(NULL, makeTypeName(ident), NULL, false); + typeName = makeTypeName(ident); + typeTup = LookupTypeName(NULL, typeName, NULL, false); if (typeTup) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); @@ -1607,7 +1626,8 @@ plpgsql_parse_wordtype(char *ident) } dtype = build_datatype(typeTup, -1, - plpgsql_curr_compile->fn_input_collation); + plpgsql_curr_compile->fn_input_collation, + typeName); ReleaseSysCache(typeTup); return dtype; @@ -1718,12 +1738,14 @@ plpgsql_parse_cwordtype(List *idents) /* * Found that - build a compiler type struct in the caller's cxt and - * return it + * return it. Note that we treat the type as being found-by-OID; no + * attempt to re-look-up the type name will happen during invalidations. */ MemoryContextSwitchTo(oldCxt); dtype = build_datatype(typetup, attrStruct->atttypmod, - attrStruct->attcollation); + attrStruct->attcollation, + NULL); MemoryContextSwitchTo(plpgsql_compile_tmp_cxt); done: @@ -1748,7 +1770,13 @@ plpgsql_parse_wordrowtype(char *ident) { Oid classOid; - /* Lookup the relation */ + /* + * Look up the relation. Note that because relation rowtypes have the + * same names as their relations, this could be handled as a type lookup + * equally well; we use the relation lookup code path only because the + * errors thrown here have traditionally referred to relations not types. + * But we'll make a TypeName in case we have to do re-look-up of the type. + */ classOid = RelnameGetRelid(ident); if (!OidIsValid(classOid)) ereport(ERROR, @@ -1756,7 +1784,8 @@ plpgsql_parse_wordrowtype(char *ident) errmsg("relation \"%s\" does not exist", ident))); /* Build and return the row type struct */ - return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid); + return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid, + makeTypeName(ident)); } /* ---------- @@ -1771,6 +1800,10 @@ plpgsql_parse_cwordrowtype(List *idents) RangeVar *relvar; MemoryContext oldCxt; + /* + * As above, this is a relation lookup but could be a type lookup if we + * weren't being backwards-compatible about error wording. + */ if (list_length(idents) != 2) return NULL; @@ -1786,7 +1819,8 @@ plpgsql_parse_cwordrowtype(List *idents) MemoryContextSwitchTo(oldCxt); /* Build and return the row type struct */ - return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid); + return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid, + makeTypeNameFromNameList(idents)); } /* @@ -1923,6 +1957,7 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars) break; case PLPGSQL_DTYPE_REC: + /* shouldn't need to revalidate rectypeid already... */ typoid = ((PLpgSQL_rec *) var)->rectypeid; typmod = -1; /* don't know typmod, if it's used at all */ typcoll = InvalidOid; /* composite types have no collation */ @@ -1991,13 +2026,19 @@ plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname) /* * plpgsql_build_datatype - * Build PLpgSQL_type struct given type OID, typmod, and collation. + * Build PLpgSQL_type struct given type OID, typmod, collation, + * and type's parsed name. * * If collation is not InvalidOid then it overrides the type's default * collation. But collation is ignored if the datatype is non-collatable. + * + * origtypname is the parsed form of what the user wrote as the type name. + * It can be NULL if the type could not be a composite type, or if it was + * identified by OID to begin with (e.g., it's a function argument type). */ PLpgSQL_type * -plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation) +plpgsql_build_datatype(Oid typeOid, int32 typmod, + Oid collation, TypeName *origtypname) { HeapTuple typeTup; PLpgSQL_type *typ; @@ -2006,7 +2047,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation) if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", typeOid); - typ = build_datatype(typeTup, typmod, collation); + typ = build_datatype(typeTup, typmod, collation, origtypname); ReleaseSysCache(typeTup); @@ -2015,9 +2056,11 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation) /* * Utility subroutine to make a PLpgSQL_type struct given a pg_type entry + * and additional details (see comments for plpgsql_build_datatype). */ static PLpgSQL_type * -build_datatype(HeapTuple typeTup, int32 typmod, Oid collation) +build_datatype(HeapTuple typeTup, int32 typmod, + Oid collation, TypeName *origtypname) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); PLpgSQL_type *typ; @@ -2088,6 +2131,39 @@ build_datatype(HeapTuple typeTup, int32 typmod, Oid collation) typ->typisarray = false; typ->atttypmod = typmod; + /* + * If it's a named composite type (or domain over one), find the typcache + * entry and record the current tupdesc ID, so we can detect changes + * (including drops). We don't currently support on-the-fly replacement + * of non-composite types, else we might want to do this for them too. + */ + if (typ->ttype == PLPGSQL_TTYPE_REC && typ->typoid != RECORDOID) + { + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(typ->typoid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(typ->typoid)))); + + typ->origtypname = origtypname; + typ->tcache = typentry; + typ->tupdesc_id = typentry->tupDesc_identifier; + } + else + { + typ->origtypname = NULL; + typ->tcache = NULL; + typ->tupdesc_id = 0; + } + return typ; } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 43027e6ca87..aa887197435 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -33,6 +33,7 @@ #include "optimizer/clauses.h" #include "optimizer/planner.h" #include "parser/parse_coerce.h" +#include "parser/parse_type.h" #include "parser/scansup.h" #include "storage/proc.h" #include "tcop/tcopprot.h" @@ -382,6 +383,7 @@ static void plpgsql_param_eval_generic_ro(ExprState *state, ExprEvalStep *op, static void exec_move_row(PLpgSQL_execstate *estate, PLpgSQL_variable *target, HeapTuple tup, TupleDesc tupdesc); +static void revalidate_rectypeid(PLpgSQL_rec *rec); static ExpandedRecordHeader *make_expanded_record_for_rec(PLpgSQL_execstate *estate, PLpgSQL_rec *rec, TupleDesc srctupdesc, @@ -2506,7 +2508,8 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt) t_var->datatype->atttypmod != t_typmod) t_var->datatype = plpgsql_build_datatype(t_typoid, t_typmod, - estate->func->fn_input_collation); + estate->func->fn_input_collation, + NULL); /* now we can assign to the variable */ exec_assign_value(estate, @@ -6806,6 +6809,67 @@ exec_move_row(PLpgSQL_execstate *estate, } } +/* + * Verify that a PLpgSQL_rec's rectypeid is up-to-date. + */ +static void +revalidate_rectypeid(PLpgSQL_rec *rec) +{ + PLpgSQL_type *typ = rec->datatype; + TypeCacheEntry *typentry; + + if (rec->rectypeid == RECORDOID) + return; /* it's RECORD, so nothing to do */ + Assert(typ != NULL); + if (typ->tcache && + typ->tcache->tupDesc_identifier == typ->tupdesc_id) + return; /* known up-to-date */ + + /* + * typcache entry has suffered invalidation, so re-look-up the type name + * if possible, and then recheck the type OID. If we don't have a + * TypeName, then we just have to soldier on with the OID we've got. + */ + if (typ->origtypname != NULL) + { + /* this bit should match parse_datatype() in pl_gram.y */ + typenameTypeIdAndMod(NULL, typ->origtypname, + &typ->typoid, + &typ->atttypmod); + } + + /* this bit should match build_datatype() in pl_comp.c */ + typentry = lookup_type_cache(typ->typoid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + { + /* + * If we get here, user tried to replace a composite type with a + * non-composite one. We're not gonna support that. + */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(typ->typoid)))); + } + + /* + * Update tcache and tupdesc_id. Since we don't support changing to a + * non-composite type, none of the rest of *typ needs to change. + */ + typ->tcache = typentry; + typ->tupdesc_id = typentry->tupDesc_identifier; + + /* + * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) + */ + rec->rectypeid = typ->typoid; +} + /* * Build an expanded record object suitable for assignment to "rec". * @@ -6830,6 +6894,11 @@ make_expanded_record_for_rec(PLpgSQL_execstate *estate, if (rec->rectypeid != RECORDOID) { + /* + * Make sure rec->rectypeid is up-to-date before using it. + */ + revalidate_rectypeid(rec); + /* * New record must be of desired type, but maybe srcerh has already * done all the same lookups. @@ -7218,6 +7287,11 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate, if (erh == rec->erh) return; + /* + * Make sure rec->rectypeid is up-to-date before using it. + */ + revalidate_rectypeid(rec); + /* * If we have a R/W pointer, we're allowed to just commandeer * ownership of the expanded record. If it's of the right type to @@ -7430,6 +7504,9 @@ instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) errmsg("record \"%s\" is not assigned yet", rec->refname), errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); + /* Make sure rec->rectypeid is up-to-date before using it */ + revalidate_rectypeid(rec); + /* OK, do it */ rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, estate->datum_context); diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 68e399f9cff..ad0a3285461 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -547,7 +547,8 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull plpgsql_build_variable($1.name, $1.lineno, plpgsql_build_datatype(REFCURSOROID, -1, - InvalidOid), + InvalidOid, + NULL), true); curname_def = palloc0(sizeof(PLpgSQL_expr)); @@ -1492,7 +1493,8 @@ for_control : for_variable K_IN $1.lineno, plpgsql_build_datatype(INT4OID, -1, - InvalidOid), + InvalidOid, + NULL), true); new = palloc0(sizeof(PLpgSQL_stmt_fori)); @@ -2283,7 +2285,8 @@ exception_sect : var = plpgsql_build_variable("sqlstate", lineno, plpgsql_build_datatype(TEXTOID, -1, - plpgsql_curr_compile->fn_input_collation), + plpgsql_curr_compile->fn_input_collation, + NULL), true); var->isconst = true; new->sqlstate_varno = var->dno; @@ -2291,7 +2294,8 @@ exception_sect : var = plpgsql_build_variable("sqlerrm", lineno, plpgsql_build_datatype(TEXTOID, -1, - plpgsql_curr_compile->fn_input_collation), + plpgsql_curr_compile->fn_input_collation, + NULL), true); var->isconst = true; new->sqlerrm_varno = var->dno; @@ -3667,6 +3671,7 @@ plpgsql_sql_error_callback(void *arg) static PLpgSQL_type * parse_datatype(const char *string, int location) { + TypeName *typeName; Oid type_id; int32 typmod; sql_error_callback_arg cbarg; @@ -3681,14 +3686,16 @@ parse_datatype(const char *string, int location) error_context_stack = &syntax_errcontext; /* Let the main parser try to parse it under standard SQL rules */ - parseTypeString(string, &type_id, &typmod, false); + typeName = typeStringToTypeName(string); + typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod); /* Restore former ereport callback */ error_context_stack = syntax_errcontext.previous; /* Okay, build a PLpgSQL_type data structure for it */ return plpgsql_build_datatype(type_id, typmod, - plpgsql_curr_compile->fn_input_collation); + plpgsql_curr_compile->fn_input_collation, + typeName); } /* @@ -4039,7 +4046,8 @@ make_case(int location, PLpgSQL_expr *t_expr, plpgsql_build_variable(varname, new->lineno, plpgsql_build_datatype(INT4OID, -1, - InvalidOid), + InvalidOid, + NULL), true); new->t_varno = t_var->dno; diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index fc2bff2ebf3..4064b167cf4 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -21,6 +21,7 @@ #include "commands/trigger.h" #include "executor/spi.h" #include "utils/expandedrecord.h" +#include "utils/typcache.h" /********************************************************************** @@ -207,6 +208,10 @@ typedef struct PLpgSQL_type Oid collation; /* from pg_type, but can be overridden */ bool typisarray; /* is "true" array, or domain over one */ int32 atttypmod; /* typmod (taken from someplace else) */ + /* Remaining fields are used only for named composite types (not RECORD) */ + TypeName *origtypname; /* type name as written by user */ + TypeCacheEntry *tcache; /* typcache entry for composite type */ + uint64 tupdesc_id; /* last-seen tupdesc identifier */ } PLpgSQL_type; /* @@ -373,6 +378,12 @@ typedef struct PLpgSQL_rec PLpgSQL_expr *default_val; /* end of PLpgSQL_variable fields */ + /* + * Note: for non-RECORD cases, we may from time to time re-look-up the + * composite type, using datatype->origtypname. That can result in + * changing rectypeid. + */ + PLpgSQL_type *datatype; /* can be NULL, if rectypeid is RECORDOID */ Oid rectypeid; /* declared type of variable */ /* RECFIELDs for this record are chained together for easy access */ @@ -1186,7 +1197,7 @@ extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents); extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident); extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents); extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod, - Oid collation); + Oid collation, TypeName *origtypname); extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, bool add2namespace); diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql index 46c6178c73f..e93aeac5b9a 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_record.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql @@ -288,6 +288,22 @@ alter table mutable drop column f3; select getf3(null::mutable); -- fails again \set SHOW_CONTEXT errors +-- check behavior with creating/dropping a named rowtype +set check_function_bodies = off; -- else reference to nonexistent type fails + +create function sillyaddtwo(int) returns int language plpgsql as +$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$; + +reset check_function_bodies; + +select sillyaddtwo(42); -- fail +create table mutable2(f1 int, f2 text); +select sillyaddtwo(42); +drop table mutable2; +select sillyaddtwo(42); -- fail +create table mutable2(f0 text, f1 int, f2 text); +select sillyaddtwo(42); + -- check access to system columns in a record variable create function sillytrig() returns trigger language plpgsql as