aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/cache/typcache.c10
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_record.out29
-rw-r--r--src/pl/plpgsql/src/pl_comp.c132
-rw-r--r--src/pl/plpgsql/src/pl_exec.c79
-rw-r--r--src/pl/plpgsql/src/pl_gram.y22
-rw-r--r--src/pl/plpgsql/src/plpgsql.h14
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_record.sql16
7 files changed, 265 insertions, 37 deletions
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index fc01e7876d9..11920db0d99 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2116,6 +2116,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 30c7e967dfa..d7c46e7c567 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,
@@ -425,7 +426,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) */
@@ -575,7 +577,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);
}
@@ -609,7 +612,8 @@ do_compile(FunctionCallInfo fcinfo,
var = plpgsql_build_variable("tg_name", 0,
plpgsql_build_datatype(NAMEOID,
-1,
- function->fn_input_collation),
+ function->fn_input_collation,
+ NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
@@ -619,7 +623,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;
@@ -629,7 +634,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;
@@ -639,7 +645,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;
@@ -649,7 +656,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;
@@ -659,7 +667,8 @@ do_compile(FunctionCallInfo fcinfo,
var = plpgsql_build_variable("tg_relname", 0,
plpgsql_build_datatype(NAMEOID,
-1,
- function->fn_input_collation),
+ function->fn_input_collation,
+ NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
@@ -669,7 +678,8 @@ do_compile(FunctionCallInfo fcinfo,
var = plpgsql_build_variable("tg_table_name", 0,
plpgsql_build_datatype(NAMEOID,
-1,
- function->fn_input_collation),
+ function->fn_input_collation,
+ NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
@@ -679,7 +689,8 @@ do_compile(FunctionCallInfo fcinfo,
var = plpgsql_build_variable("tg_table_schema", 0,
plpgsql_build_datatype(NAMEOID,
-1,
- function->fn_input_collation),
+ function->fn_input_collation,
+ NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
@@ -689,7 +700,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;
@@ -699,7 +711,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;
@@ -724,7 +737,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;
@@ -734,7 +748,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;
@@ -757,7 +772,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;
@@ -911,7 +927,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;
@@ -1576,6 +1593,7 @@ plpgsql_parse_wordtype(char *ident)
{
PLpgSQL_type *dtype;
PLpgSQL_nsitem *nse;
+ TypeName *typeName;
HeapTuple typeTup;
/*
@@ -1603,7 +1621,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);
@@ -1616,7 +1635,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;
@@ -1727,12 +1747,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:
@@ -1757,7 +1779,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,
@@ -1765,7 +1793,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));
}
/* ----------
@@ -1780,6 +1809,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;
@@ -1795,7 +1828,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));
}
/*
@@ -1932,6 +1966,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 */
@@ -2000,13 +2035,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;
@@ -2015,7 +2056,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);
@@ -2024,9 +2065,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;
@@ -2097,6 +2140,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 08961e2af9c..a083b03bbf6 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -32,6 +32,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.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,
@@ -2520,7 +2522,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,
@@ -6837,6 +6840,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".
*
* Caller must supply either a source tuple descriptor or a source expanded
@@ -6861,6 +6925,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.
*/
@@ -7332,6 +7401,11 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
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
* put into the record variable, do that. (Note we don't accept
@@ -7543,6 +7617,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 dea95f42308..af63fe126f9 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -551,7 +551,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));
@@ -1504,7 +1505,8 @@ for_control : for_variable K_IN
$1.lineno,
plpgsql_build_datatype(INT4OID,
-1,
- InvalidOid),
+ InvalidOid,
+ NULL),
true);
new = palloc0(sizeof(PLpgSQL_stmt_fori));
@@ -2317,7 +2319,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;
@@ -2325,7 +2328,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;
@@ -3707,6 +3711,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;
@@ -3721,14 +3726,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);
}
/*
@@ -4080,7 +4087,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 a5a5ef9de70..37ccf671e10 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"
/**********************************************************************
@@ -206,6 +207,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;
/*
@@ -372,6 +377,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 */
@@ -1227,7 +1238,8 @@ 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