mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-06 07:39:19 +02:00
Fix plpgsql to re-look-up composite type names at need.
Commit4b93f5799
rearranged things in plpgsql to make it cope better with composite types changing underneath it intra-session. However, I failed to consider the case of a composite type being dropped and recreated entirely. In my defense, the previous coding didn't consider that possibility at all either --- but it would accidentally work so long as you didn't change the type's field list, because the built-at-compile-time list of component variables would then still match the type's new definition. The new coding, however, occasionally tries to re-look-up the type by OID, and then fails to find the dropped type. To fix this, we need to save the TypeName struct, and then redo the type OID lookup from that. Of course that's expensive, so we don't want to do it every time we need the type OID. This can be fixed in the same way that4b93f5799
dealt with changes to composite types' definitions: keep an eye on the type's typcache entry to see if its tupledesc has been invalidated. (Perhaps, at some point, this mechanism should be generalized so it can work for non-composite types too; but for now, plpgsql only tries to cope with intra-session redefinitions of composites.) I'm slightly hesitant to back-patch this into v11, because it changes the contents of struct PLpgSQL_type as well as the signature of plpgsql_build_datatype(), so in principle it could break code that is poking into the innards of plpgsql. However, the only popular extension of that ilk is pldebugger, and it doesn't seem to be affected. Since this is a regression for people who were relying on the old behavior, it seems worth taking the small risk of causing compatibility issues. Per bug #15913 from Daniel Fiori. Back-patch to v11 where4b93f5799
came in. Discussion: https://postgr.es/m/15913-a7e112e16dedcffc@postgresql.org
This commit is contained in:
parent
372d6a8918
commit
a4504696e1
10
src/backend/utils/cache/typcache.c
vendored
10
src/backend/utils/cache/typcache.c
vendored
@ -2116,6 +2116,16 @@ TypeCacheRelCallback(Datum arg, Oid relid)
|
|||||||
if (--typentry->tupDesc->tdrefcount == 0)
|
if (--typentry->tupDesc->tdrefcount == 0)
|
||||||
FreeTupleDesc(typentry->tupDesc);
|
FreeTupleDesc(typentry->tupDesc);
|
||||||
typentry->tupDesc = NULL;
|
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 */
|
/* Reset equality/comparison/hashing validity information */
|
||||||
|
@ -447,6 +447,35 @@ alter table mutable drop column f3;
|
|||||||
select getf3(null::mutable); -- fails again
|
select getf3(null::mutable); -- fails again
|
||||||
ERROR: record "x" has no field "f3"
|
ERROR: record "x" has no field "f3"
|
||||||
\set SHOW_CONTEXT errors
|
\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
|
-- check access to system columns in a record variable
|
||||||
create function sillytrig() returns trigger language plpgsql as
|
create function sillytrig() returns trigger language plpgsql as
|
||||||
$$begin
|
$$begin
|
||||||
|
@ -105,7 +105,8 @@ static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
|
|||||||
ColumnRef *cref, bool error_if_no_field);
|
ColumnRef *cref, bool error_if_no_field);
|
||||||
static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
|
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_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_start_datums(void);
|
||||||
static void plpgsql_finish_datums(PLpgSQL_function *function);
|
static void plpgsql_finish_datums(PLpgSQL_function *function);
|
||||||
static void compute_function_hashkey(FunctionCallInfo fcinfo,
|
static void compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||||
@ -425,7 +426,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
/* Create datatype info */
|
/* Create datatype info */
|
||||||
argdtype = plpgsql_build_datatype(argtypeid,
|
argdtype = plpgsql_build_datatype(argtypeid,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation);
|
function->fn_input_collation,
|
||||||
|
NULL);
|
||||||
|
|
||||||
/* Disallow pseudotype argument */
|
/* Disallow pseudotype argument */
|
||||||
/* (note we already replaced polymorphic types) */
|
/* (note we already replaced polymorphic types) */
|
||||||
@ -575,7 +577,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
(void) plpgsql_build_variable("$0", 0,
|
(void) plpgsql_build_variable("$0", 0,
|
||||||
build_datatype(typeTup,
|
build_datatype(typeTup,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,7 +612,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_name", 0,
|
var = plpgsql_build_variable("tg_name", 0,
|
||||||
plpgsql_build_datatype(NAMEOID,
|
plpgsql_build_datatype(NAMEOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -619,7 +623,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_when", 0,
|
var = plpgsql_build_variable("tg_when", 0,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -629,7 +634,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_level", 0,
|
var = plpgsql_build_variable("tg_level", 0,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -639,7 +645,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_op", 0,
|
var = plpgsql_build_variable("tg_op", 0,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -649,7 +656,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_relid", 0,
|
var = plpgsql_build_variable("tg_relid", 0,
|
||||||
plpgsql_build_datatype(OIDOID,
|
plpgsql_build_datatype(OIDOID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -659,7 +667,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_relname", 0,
|
var = plpgsql_build_variable("tg_relname", 0,
|
||||||
plpgsql_build_datatype(NAMEOID,
|
plpgsql_build_datatype(NAMEOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -669,7 +678,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_table_name", 0,
|
var = plpgsql_build_variable("tg_table_name", 0,
|
||||||
plpgsql_build_datatype(NAMEOID,
|
plpgsql_build_datatype(NAMEOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -679,7 +689,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_table_schema", 0,
|
var = plpgsql_build_variable("tg_table_schema", 0,
|
||||||
plpgsql_build_datatype(NAMEOID,
|
plpgsql_build_datatype(NAMEOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -689,7 +700,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_nargs", 0,
|
var = plpgsql_build_variable("tg_nargs", 0,
|
||||||
plpgsql_build_datatype(INT4OID,
|
plpgsql_build_datatype(INT4OID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -699,7 +711,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_argv", 0,
|
var = plpgsql_build_variable("tg_argv", 0,
|
||||||
plpgsql_build_datatype(TEXTARRAYOID,
|
plpgsql_build_datatype(TEXTARRAYOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -724,7 +737,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_event", 0,
|
var = plpgsql_build_variable("tg_event", 0,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -734,7 +748,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("tg_tag", 0,
|
var = plpgsql_build_variable("tg_tag", 0,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
function->fn_input_collation),
|
function->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
|
||||||
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
var->dtype = PLPGSQL_DTYPE_PROMISE;
|
||||||
@ -757,7 +772,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
var = plpgsql_build_variable("found", 0,
|
var = plpgsql_build_variable("found", 0,
|
||||||
plpgsql_build_datatype(BOOLOID,
|
plpgsql_build_datatype(BOOLOID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
function->found_varno = var->dno;
|
function->found_varno = var->dno;
|
||||||
|
|
||||||
@ -911,7 +927,8 @@ plpgsql_compile_inline(char *proc_source)
|
|||||||
var = plpgsql_build_variable("found", 0,
|
var = plpgsql_build_variable("found", 0,
|
||||||
plpgsql_build_datatype(BOOLOID,
|
plpgsql_build_datatype(BOOLOID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
function->found_varno = var->dno;
|
function->found_varno = var->dno;
|
||||||
|
|
||||||
@ -1576,6 +1593,7 @@ plpgsql_parse_wordtype(char *ident)
|
|||||||
{
|
{
|
||||||
PLpgSQL_type *dtype;
|
PLpgSQL_type *dtype;
|
||||||
PLpgSQL_nsitem *nse;
|
PLpgSQL_nsitem *nse;
|
||||||
|
TypeName *typeName;
|
||||||
HeapTuple typeTup;
|
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
|
* Word wasn't found in the namespace stack. Try to find a data type with
|
||||||
* that name, but ignore shell types and complex types.
|
* 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)
|
if (typeTup)
|
||||||
{
|
{
|
||||||
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||||
@ -1616,7 +1635,8 @@ plpgsql_parse_wordtype(char *ident)
|
|||||||
}
|
}
|
||||||
|
|
||||||
dtype = build_datatype(typeTup, -1,
|
dtype = build_datatype(typeTup, -1,
|
||||||
plpgsql_curr_compile->fn_input_collation);
|
plpgsql_curr_compile->fn_input_collation,
|
||||||
|
typeName);
|
||||||
|
|
||||||
ReleaseSysCache(typeTup);
|
ReleaseSysCache(typeTup);
|
||||||
return dtype;
|
return dtype;
|
||||||
@ -1727,12 +1747,14 @@ plpgsql_parse_cwordtype(List *idents)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Found that - build a compiler type struct in the caller's cxt and
|
* 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);
|
MemoryContextSwitchTo(oldCxt);
|
||||||
dtype = build_datatype(typetup,
|
dtype = build_datatype(typetup,
|
||||||
attrStruct->atttypmod,
|
attrStruct->atttypmod,
|
||||||
attrStruct->attcollation);
|
attrStruct->attcollation,
|
||||||
|
NULL);
|
||||||
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
@ -1757,7 +1779,13 @@ plpgsql_parse_wordrowtype(char *ident)
|
|||||||
{
|
{
|
||||||
Oid classOid;
|
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);
|
classOid = RelnameGetRelid(ident);
|
||||||
if (!OidIsValid(classOid))
|
if (!OidIsValid(classOid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1765,7 +1793,8 @@ plpgsql_parse_wordrowtype(char *ident)
|
|||||||
errmsg("relation \"%s\" does not exist", ident)));
|
errmsg("relation \"%s\" does not exist", ident)));
|
||||||
|
|
||||||
/* Build and return the row type struct */
|
/* 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;
|
RangeVar *relvar;
|
||||||
MemoryContext oldCxt;
|
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)
|
if (list_length(idents) != 2)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -1795,7 +1828,8 @@ plpgsql_parse_cwordrowtype(List *idents)
|
|||||||
MemoryContextSwitchTo(oldCxt);
|
MemoryContextSwitchTo(oldCxt);
|
||||||
|
|
||||||
/* Build and return the row type struct */
|
/* 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;
|
break;
|
||||||
|
|
||||||
case PLPGSQL_DTYPE_REC:
|
case PLPGSQL_DTYPE_REC:
|
||||||
|
/* shouldn't need to revalidate rectypeid already... */
|
||||||
typoid = ((PLpgSQL_rec *) var)->rectypeid;
|
typoid = ((PLpgSQL_rec *) var)->rectypeid;
|
||||||
typmod = -1; /* don't know typmod, if it's used at all */
|
typmod = -1; /* don't know typmod, if it's used at all */
|
||||||
typcoll = InvalidOid; /* composite types have no collation */
|
typcoll = InvalidOid; /* composite types have no collation */
|
||||||
@ -2000,13 +2035,19 @@ plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* plpgsql_build_datatype
|
* 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
|
* If collation is not InvalidOid then it overrides the type's default
|
||||||
* collation. But collation is ignored if the datatype is non-collatable.
|
* 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_type *
|
||||||
plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation)
|
plpgsql_build_datatype(Oid typeOid, int32 typmod,
|
||||||
|
Oid collation, TypeName *origtypname)
|
||||||
{
|
{
|
||||||
HeapTuple typeTup;
|
HeapTuple typeTup;
|
||||||
PLpgSQL_type *typ;
|
PLpgSQL_type *typ;
|
||||||
@ -2015,7 +2056,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation)
|
|||||||
if (!HeapTupleIsValid(typeTup))
|
if (!HeapTupleIsValid(typeTup))
|
||||||
elog(ERROR, "cache lookup failed for type %u", typeOid);
|
elog(ERROR, "cache lookup failed for type %u", typeOid);
|
||||||
|
|
||||||
typ = build_datatype(typeTup, typmod, collation);
|
typ = build_datatype(typeTup, typmod, collation, origtypname);
|
||||||
|
|
||||||
ReleaseSysCache(typeTup);
|
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
|
* 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 *
|
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);
|
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||||
PLpgSQL_type *typ;
|
PLpgSQL_type *typ;
|
||||||
@ -2097,6 +2140,39 @@ build_datatype(HeapTuple typeTup, int32 typmod, Oid collation)
|
|||||||
typ->typisarray = false;
|
typ->typisarray = false;
|
||||||
typ->atttypmod = typmod;
|
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;
|
return typ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#include "optimizer/optimizer.h"
|
#include "optimizer/optimizer.h"
|
||||||
#include "parser/parse_coerce.h"
|
#include "parser/parse_coerce.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
#include "parser/scansup.h"
|
#include "parser/scansup.h"
|
||||||
#include "storage/proc.h"
|
#include "storage/proc.h"
|
||||||
#include "tcop/tcopprot.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,
|
static void exec_move_row(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_variable *target,
|
PLpgSQL_variable *target,
|
||||||
HeapTuple tup, TupleDesc tupdesc);
|
HeapTuple tup, TupleDesc tupdesc);
|
||||||
|
static void revalidate_rectypeid(PLpgSQL_rec *rec);
|
||||||
static ExpandedRecordHeader *make_expanded_record_for_rec(PLpgSQL_execstate *estate,
|
static ExpandedRecordHeader *make_expanded_record_for_rec(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_rec *rec,
|
PLpgSQL_rec *rec,
|
||||||
TupleDesc srctupdesc,
|
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->atttypmod != t_typmod)
|
||||||
t_var->datatype = plpgsql_build_datatype(t_typoid,
|
t_var->datatype = plpgsql_build_datatype(t_typoid,
|
||||||
t_typmod,
|
t_typmod,
|
||||||
estate->func->fn_input_collation);
|
estate->func->fn_input_collation,
|
||||||
|
NULL);
|
||||||
|
|
||||||
/* now we can assign to the variable */
|
/* now we can assign to the variable */
|
||||||
exec_assign_value(estate,
|
exec_assign_value(estate,
|
||||||
@ -6836,6 +6839,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".
|
* Build an expanded record object suitable for assignment to "rec".
|
||||||
*
|
*
|
||||||
@ -6860,6 +6924,11 @@ make_expanded_record_for_rec(PLpgSQL_execstate *estate,
|
|||||||
|
|
||||||
if (rec->rectypeid != RECORDOID)
|
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
|
* New record must be of desired type, but maybe srcerh has already
|
||||||
* done all the same lookups.
|
* done all the same lookups.
|
||||||
@ -7331,6 +7400,11 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
|
|||||||
if (erh == rec->erh)
|
if (erh == rec->erh)
|
||||||
return;
|
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
|
* 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
|
* ownership of the expanded record. If it's of the right type to
|
||||||
@ -7543,6 +7617,9 @@ instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec)
|
|||||||
errmsg("record \"%s\" is not assigned yet", rec->refname),
|
errmsg("record \"%s\" is not assigned yet", rec->refname),
|
||||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
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 */
|
/* OK, do it */
|
||||||
rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1,
|
rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1,
|
||||||
estate->datum_context);
|
estate->datum_context);
|
||||||
|
@ -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_variable($1.name, $1.lineno,
|
||||||
plpgsql_build_datatype(REFCURSOROID,
|
plpgsql_build_datatype(REFCURSOROID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
curname_def = palloc0(sizeof(PLpgSQL_expr));
|
curname_def = palloc0(sizeof(PLpgSQL_expr));
|
||||||
@ -1504,7 +1505,8 @@ for_control : for_variable K_IN
|
|||||||
$1.lineno,
|
$1.lineno,
|
||||||
plpgsql_build_datatype(INT4OID,
|
plpgsql_build_datatype(INT4OID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
new = palloc0(sizeof(PLpgSQL_stmt_fori));
|
new = palloc0(sizeof(PLpgSQL_stmt_fori));
|
||||||
@ -2317,7 +2319,8 @@ exception_sect :
|
|||||||
var = plpgsql_build_variable("sqlstate", lineno,
|
var = plpgsql_build_variable("sqlstate", lineno,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
plpgsql_curr_compile->fn_input_collation),
|
plpgsql_curr_compile->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
var->isconst = true;
|
var->isconst = true;
|
||||||
new->sqlstate_varno = var->dno;
|
new->sqlstate_varno = var->dno;
|
||||||
@ -2325,7 +2328,8 @@ exception_sect :
|
|||||||
var = plpgsql_build_variable("sqlerrm", lineno,
|
var = plpgsql_build_variable("sqlerrm", lineno,
|
||||||
plpgsql_build_datatype(TEXTOID,
|
plpgsql_build_datatype(TEXTOID,
|
||||||
-1,
|
-1,
|
||||||
plpgsql_curr_compile->fn_input_collation),
|
plpgsql_curr_compile->fn_input_collation,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
var->isconst = true;
|
var->isconst = true;
|
||||||
new->sqlerrm_varno = var->dno;
|
new->sqlerrm_varno = var->dno;
|
||||||
@ -3707,6 +3711,7 @@ plpgsql_sql_error_callback(void *arg)
|
|||||||
static PLpgSQL_type *
|
static PLpgSQL_type *
|
||||||
parse_datatype(const char *string, int location)
|
parse_datatype(const char *string, int location)
|
||||||
{
|
{
|
||||||
|
TypeName *typeName;
|
||||||
Oid type_id;
|
Oid type_id;
|
||||||
int32 typmod;
|
int32 typmod;
|
||||||
sql_error_callback_arg cbarg;
|
sql_error_callback_arg cbarg;
|
||||||
@ -3721,14 +3726,16 @@ parse_datatype(const char *string, int location)
|
|||||||
error_context_stack = &syntax_errcontext;
|
error_context_stack = &syntax_errcontext;
|
||||||
|
|
||||||
/* Let the main parser try to parse it under standard SQL rules */
|
/* 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 */
|
/* Restore former ereport callback */
|
||||||
error_context_stack = syntax_errcontext.previous;
|
error_context_stack = syntax_errcontext.previous;
|
||||||
|
|
||||||
/* Okay, build a PLpgSQL_type data structure for it */
|
/* Okay, build a PLpgSQL_type data structure for it */
|
||||||
return plpgsql_build_datatype(type_id, typmod,
|
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_variable(varname, new->lineno,
|
||||||
plpgsql_build_datatype(INT4OID,
|
plpgsql_build_datatype(INT4OID,
|
||||||
-1,
|
-1,
|
||||||
InvalidOid),
|
InvalidOid,
|
||||||
|
NULL),
|
||||||
true);
|
true);
|
||||||
new->t_varno = t_var->dno;
|
new->t_varno = t_var->dno;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "utils/expandedrecord.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 */
|
Oid collation; /* from pg_type, but can be overridden */
|
||||||
bool typisarray; /* is "true" array, or domain over one */
|
bool typisarray; /* is "true" array, or domain over one */
|
||||||
int32 atttypmod; /* typmod (taken from someplace else) */
|
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;
|
} PLpgSQL_type;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -372,6 +377,12 @@ typedef struct PLpgSQL_rec
|
|||||||
PLpgSQL_expr *default_val;
|
PLpgSQL_expr *default_val;
|
||||||
/* end of PLpgSQL_variable fields */
|
/* 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 */
|
PLpgSQL_type *datatype; /* can be NULL, if rectypeid is RECORDOID */
|
||||||
Oid rectypeid; /* declared type of variable */
|
Oid rectypeid; /* declared type of variable */
|
||||||
/* RECFIELDs for this record are chained together for easy access */
|
/* 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_wordrowtype(char *ident);
|
||||||
extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
|
extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
|
||||||
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
|
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,
|
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
||||||
PLpgSQL_type *dtype,
|
PLpgSQL_type *dtype,
|
||||||
bool add2namespace);
|
bool add2namespace);
|
||||||
|
@ -288,6 +288,22 @@ alter table mutable drop column f3;
|
|||||||
select getf3(null::mutable); -- fails again
|
select getf3(null::mutable); -- fails again
|
||||||
\set SHOW_CONTEXT errors
|
\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
|
-- check access to system columns in a record variable
|
||||||
|
|
||||||
create function sillytrig() returns trigger language plpgsql as
|
create function sillytrig() returns trigger language plpgsql as
|
||||||
|
Loading…
Reference in New Issue
Block a user