Thread: BUG #18131: PL/pgSQL: regclass procedure parameter wrongly memoized(?)

BUG #18131: PL/pgSQL: regclass procedure parameter wrongly memoized(?)

From
PG Bug reporting form
Date:
The following bug has been logged on the website:

Bug reference:      18131
Logged by:          Christian Stork
Email address:      cstork+postgresql@gmail.com
PostgreSQL version: 14.9
Operating system:   Ubuntu 22.04
Description:

The following condensed recipe reproduces the error:

create procedure callee(t regclass)
language plpgsql as
$body$
begin
    raise notice 'callee: oid = %', t::oid;
    execute 'table ' || t;
end;
$body$;

create procedure caller()
language plpgsql as
$body$
begin
    create table table_name ();
    raise notice 'caller: oid = %', 'table_name'::regclass::oid;
    call callee('table_name');
    drop table table_name;
end;
$body$;

call caller(); -- OK
call caller(); -- ERROR: callee executed with OID of previous invocation!

-- Relevant output (Ubuntu 14.9-1.pgdg22.04+1):
-- ...
-- NOTICE:  caller: oid = 24769724
-- NOTICE:  callee: oid = 24769724
-- CALL                              <---- first statement completed
successfully
-- NOTICE:  caller: oid = 24769727
-- NOTICE:  callee: oid = 24769724   <---- should be the same as line
above
-- ERROR:  syntax error at or near "24769724"
-- LINE 1: table 24769724            <---- no more table with this OID in
pg_catalog, hence OID instead of table name, I assume
-- ...


Re: BUG #18131: PL/pgSQL: regclass procedure parameter wrongly memoized(?)

From
Tom Lane
Date:
PG Bug reporting form <noreply@postgresql.org> writes:
> The following condensed recipe reproduces the error:

> create procedure callee(t regclass)
> language plpgsql as
> $body$
> begin
>     raise notice 'callee: oid = %', t::oid;
>     execute 'table ' || t;
> end;
> $body$;

> create procedure caller()
> language plpgsql as
> $body$
> begin
>     create table table_name ();
>     raise notice 'caller: oid = %', 'table_name'::regclass::oid;
>     call callee('table_name');
>     drop table table_name;
> end;
> $body$;

> call caller(); -- OK
> call caller(); -- ERROR: callee executed with OID of previous invocation!

Hmm.  It's not just regclass: we're failing to track *any* dependencies
of the compiled CallStmt.  So you can also break it by, say, dropping
and recreating the "callee" procedure.

The attached quick hack fixes the reported symptoms, but I wonder
if there is a better place to do it.

            regards, tom lane

diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5700bfb5cd..9b54ed5c70 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -3564,9 +3564,22 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)

         if (query->commandType == CMD_UTILITY)
         {
+            /* CALL requires bespoke processing */
+            if (IsA(query->utilityStmt, CallStmt))
+            {
+                CallStmt   *callstmt = (CallStmt *) query->utilityStmt;
+
+                /* We need not examine funccall, just the transformed exprs */
+                (void) extract_query_dependencies_walker((Node *) callstmt->funcexpr,
+                                                         context);
+                (void) extract_query_dependencies_walker((Node *) callstmt->outargs,
+                                                         context);
+                return false;
+            }
+
             /*
-             * Ignore utility statements, except those (such as EXPLAIN) that
-             * contain a parsed-but-not-planned query.
+             * Ignore other utility statements, except those (such as EXPLAIN)
+             * that contain a parsed-but-not-planned query.
              */
             query = UtilityContainsQuery(query->utilityStmt);
             if (query == NULL)