]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Collect dependency information for parsed CallStmts.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 25 Sep 2023 18:41:57 +0000 (14:41 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 25 Sep 2023 18:41:57 +0000 (14:41 -0400)
Parse analysis of a CallStmt will inject mutable information,
for instance the OID of the called procedure, so that subsequent
DDL may create a need to re-parse the CALL.  We failed to detect
this for CALLs in plpgsql routines, because no dependency information
was collected when putting a CallStmt into the plan cache.  That
could lead to misbehavior or strange errors such as "cache lookup
failed".

Before commit ee895a655, the issue would only manifest for CALLs
appearing in atomic contexts, because we re-planned non-atomic
CALLs every time through anyway.

It is now apparent that extract_query_dependencies() probably
needs a special case for every utility statement type for which
stmt_requires_parse_analysis() returns true.  I wanted to add
something like Assert(!stmt_requires_parse_analysis(...)) when
falling out of extract_query_dependencies_walker without doing
anything, but there are API issues as well as a more fundamental
point: stmt_requires_parse_analysis is supposed to be applied to
raw parser output, so it'd be cheating to assume it will give the
correct answer for post-parse-analysis trees.  I contented myself
with adding a comment.

Per bug #18131 from Christian Stork.  Back-patch to all supported
branches.

Discussion: https://postgr.es/m/18131-576854e79c5cd264@postgresql.org

src/backend/optimizer/plan/setrefs.c
src/pl/plpgsql/src/expected/plpgsql_call.out
src/pl/plpgsql/src/sql/plpgsql_call.sql

index 15ffaa37079e2bb4ae4b11c4a85177996aa1037a..65d68d536bf6054c65f37915e2cfde8fa97ada79 100644 (file)
@@ -2649,8 +2649,25 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
                if (query->commandType == CMD_UTILITY)
                {
                        /*
-                        * Ignore utility statements, except those (such as EXPLAIN) that
-                        * contain a parsed-but-not-planned query.
+                        * This logic must handle any utility command for which parse
+                        * analysis was nontrivial (cf. stmt_requires_parse_analysis).
+                        *
+                        * Notably, CALL requires its own 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);
+                               return false;
+                       }
+
+                       /*
+                        * Ignore other utility statements, except those (such as EXPLAIN)
+                        * that contain a parsed-but-not-planned query.  For those, we
+                        * just need to transfer our attention to the contained query.
                         */
                        query = UtilityContainsQuery(query->utilityStmt);
                        if (query == NULL)
index a469d0ebb188c8a9f37ed5588f6f6f50b4c9c416..f92e108366a9e7c617975728c03162edd1747ed9 100644 (file)
@@ -376,3 +376,50 @@ BEGIN
 END;
 $$;
 NOTICE:  <NULL>
+-- check that we detect change of dependencies in CALL
+-- atomic and non-atomic call sites do this differently, so check both
+CREATE PROCEDURE inner_p (f1 int)
+AS $$
+BEGIN
+  RAISE NOTICE 'inner_p(%)', f1;
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 1 $$ LANGUAGE sql;
+CREATE PROCEDURE outer_p (f1 int)
+AS $$
+BEGIN
+  RAISE NOTICE 'outer_p(%)', f1;
+  CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION outer_f (f1 int) RETURNS void
+AS $$
+BEGIN
+  RAISE NOTICE 'outer_f(%)', f1;
+  CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+CALL outer_p(42);
+NOTICE:  outer_p(42)
+NOTICE:  inner_p(43)
+SELECT outer_f(42);
+NOTICE:  outer_f(42)
+NOTICE:  inner_p(43)
+ outer_f 
+---------
+(1 row)
+
+DROP FUNCTION f(int);
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 2 $$ LANGUAGE sql;
+CALL outer_p(42);
+NOTICE:  outer_p(42)
+NOTICE:  inner_p(44)
+SELECT outer_f(42);
+NOTICE:  outer_f(42)
+NOTICE:  inner_p(44)
+ outer_f 
+---------
+(1 row)
+
index d62d50c1eadd6adf491956a2babb4e816035247e..d3194e44c0bfc373a3661aff2d5c6af0c05c3a33 100644 (file)
@@ -357,3 +357,41 @@ BEGIN
   RAISE NOTICE '%', v_Text;
 END;
 $$;
+
+
+-- check that we detect change of dependencies in CALL
+-- atomic and non-atomic call sites do this differently, so check both
+
+CREATE PROCEDURE inner_p (f1 int)
+AS $$
+BEGIN
+  RAISE NOTICE 'inner_p(%)', f1;
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 1 $$ LANGUAGE sql;
+
+CREATE PROCEDURE outer_p (f1 int)
+AS $$
+BEGIN
+  RAISE NOTICE 'outer_p(%)', f1;
+  CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION outer_f (f1 int) RETURNS void
+AS $$
+BEGIN
+  RAISE NOTICE 'outer_f(%)', f1;
+  CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+
+CALL outer_p(42);
+SELECT outer_f(42);
+
+DROP FUNCTION f(int);
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 2 $$ LANGUAGE sql;
+
+CALL outer_p(42);
+SELECT outer_f(42);