On 22/11/2025 19:37, Tom Lane wrote: > After sleeping on it, I'm inclined to word the notice like > > NOTICE: function "f" will be effectively temporary > DETAIL: It depends on temporary <object descriptor>.
I changed the error level to NOTICE: postgres=# CREATE TEMPORARY TABLE temp_table AS SELECT 1 AS val; SELECT 1 postgres=# CREATE FUNCTION temp_func() RETURNS int LANGUAGE sql BEGIN ATOMIC; SELECT val FROM temp_table; END; NOTICE: function "temp_func" will be effectively temporary DETAIL: It depends on temporary table temp_table. CREATE FUNCTION I reverted the changes in the other test cases, with the exception of this change in returning.sql (although unrelated to this patch): diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql index cc99cb53f6..f1c85a9731 100644 --- a/src/test/regress/sql/returning.sql +++ b/src/test/regress/sql/returning.sql @@ -2,6 +2,11 @@ -- Test INSERT/UPDATE/DELETE RETURNING -- +-- This script is full of poorly-chosen object names. +-- Put them in a separate schema to avoid collisions with concurrent scripts. +CREATE SCHEMA returning_test; +SET search_path = returning_test, public; + -- Simple cases CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42); @@ -407,4 +412,7 @@ BEGIN ATOMIC END; \sf foo_update -DROP FUNCTION foo_update; + +-- Clean up +RESET search_path; +DROP SCHEMA returning_test CASCADE; -- I also significantly reduced the tests I previously added to create_function_sql.sql, leaving only tests for temporary views, sequences, temporary functions (in pg_temp schema), and domains. v8 attached. Thanks! Best, Jim
From 7a86471c0b9c5d2e1493c37fbb1faa156e6a2dc8 Mon Sep 17 00:00:00 2001 From: Jim Jones <[email protected]> Date: Sat, 22 Nov 2025 22:43:14 +0100 Subject: [PATCH v8 2/2] Disallow dependencies from non-temporary functions to temp objects. We don't have an official concept of temporary functions. (You can make one explicitly in pg_temp, but then you have to explicitly schema-qualify it on every call.) However, until now we were quite laissez-faire about whether a non-temporary function could depend on a temporary object, such as a temp table or view. If one did, it would silently go away at end of session, due to the implied DROP ... CASCADE on the session's temporary objects. People have complained that that's surprising, so let's fix it by disallowing the case. Hand-made temp functions are still allowed to reference temp objects, so long as they're in our own session's temp schema. This required modifying several existing regression tests that were depending on exactly the case that's now forbidden. Maybe that's an indication that this is a bad idea? We should probably make the implementation of temp-by-default views use the same infrastructure used here, but that's for another patch. It's unclear whether there are any other object classes that deserve similar treatment. Author: Jim Jones <[email protected]> Reviewed-by: Tom Lane <[email protected]> --- src/backend/catalog/dependency.c | 42 +++++++++++ src/backend/catalog/pg_proc.c | 43 +++++++++-- src/backend/commands/functioncmds.c | 2 + src/backend/commands/typecmds.c | 3 + src/include/catalog/dependency.h | 4 + .../expected/temp-schema-cleanup.out | 10 --- .../isolation/specs/temp-schema-cleanup.spec | 5 -- .../regress/expected/create_function_sql.out | 73 +++++++++++++++---- src/test/regress/expected/rangefuncs.out | 4 + src/test/regress/expected/returning.out | 17 ++++- src/test/regress/expected/rowtypes.out | 10 +++ src/test/regress/sql/create_function_sql.sql | 39 ++++++++++ src/test/regress/sql/returning.sql | 10 ++- 13 files changed, 222 insertions(+), 40 deletions(-) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 61bdf4a577..9cb4c64f3d 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -22,6 +22,7 @@ #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/index.h" +#include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" #include "catalog/pg_amop.h" @@ -2434,6 +2435,47 @@ process_function_rte_ref(RangeTblEntry *rte, AttrNumber attnum, attnum, rte->eref->aliasname))); } +/* + * find_temp_object - search an array of dependency references for temp objects + * + * Scan an ObjectAddresses array for references to temporary objects (objects + * in temporary namespaces), ignoring those in our own temp namespace if + * local_temp_okay is true. If one is found, return true after storing its + * address in *foundobj. + * + * There's no need to identify all such objects; the caller will throw an + * error anyway, so identifying the first one seems sufficiently courteous. + * (We'd throw the error here, except we don't know the referencing object.) + */ +bool +find_temp_object(const ObjectAddresses *addrs, bool local_temp_okay, + ObjectAddress *foundobj) +{ + for (int i = 0; i < addrs->numrefs; i++) + { + const ObjectAddress *thisobj = addrs->refs + i; + Oid objnamespace; + + /* + * Use get_object_namespace() to see if this object belongs to a + * schema. If not, we can skip it. + */ + objnamespace = get_object_namespace(thisobj); + + /* + * If the object is in a temporary namespace, complain, except if + * local_temp_okay and it's our own temp namespace. + */ + if (OidIsValid(objnamespace) && isAnyTempNamespace(objnamespace) && + !(local_temp_okay && isTempNamespace(objnamespace))) + { + *foundobj = *thisobj; + return true; + } + } + return false; +} + /* * Given an array of dependency references, eliminate any duplicates. */ diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index b89b9ccda0..e2f8d61ace 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -20,6 +20,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" @@ -141,7 +142,8 @@ ProcedureCreate(const char *procedureName, TupleDesc tupDesc; bool is_update; ObjectAddress myself, - referenced; + referenced, + temp_object; char *detailmsg; int i; ObjectAddresses *addrs; @@ -658,17 +660,42 @@ ProcedureCreate(const char *procedureName, add_exact_object_address(&referenced, addrs); } - record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); - free_object_addresses(addrs); - - /* dependency on SQL routine body */ + /* dependencies appearing in new-style SQL routine body */ if (languageObjectId == SQLlanguageId && prosqlbody) - recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL); + collectDependenciesOfExpr(addrs, prosqlbody, NIL); /* dependency on parameter default expressions */ if (parameterDefaults) - recordDependencyOnExpr(&myself, (Node *) parameterDefaults, - NIL, DEPENDENCY_NORMAL); + collectDependenciesOfExpr(addrs, (Node *) parameterDefaults, NIL); + + /* + * Now that we have all the normal dependencies, thumb through them and + * complain if any are to temporary objects. This prevents the scenario + * where a non-temp function silently goes away at session exit due to a + * dependency on a temp object. However, we allow a function created in + * our own pg_temp namespace to refer to other objects in that namespace, + * since then they'd have similar lifespans anyway (and there are field + * uses of that scenario). + */ + if (find_temp_object(addrs, isTempNamespace(procNamespace), &temp_object)) + ereport(NOTICE, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function \"%s\" will be effectively temporary", + procedureName), + errdetail("It depends on temporary %s.", + getObjectDescription(&temp_object, false)))); + + /* + * Now record all normal dependencies at once. This will also remove any + * duplicates in the list. (Role and extension dependencies are handled + * separately below. Role dependencies would have to be separate anyway + * since they are shared dependencies. An extension dependency could be + * folded into the addrs list, but pg_depend.c doesn't make that easy, and + * it won't duplicate anything we've collected so far anyway.) + */ + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + + free_object_addresses(addrs); /* dependency on owner */ if (!is_update) diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 0335e982b3..59d00638ee 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -153,6 +153,8 @@ compute_return_type(TypeName *returnType, Oid languageOid, address = TypeShellMake(typname, namespaceId, GetUserId()); rettype = address.objectId; Assert(OidIsValid(rettype)); + /* Ensure the new shell type is visible to ProcedureCreate */ + CommandCounterIncrement(); } aclresult = object_aclcheck(TypeRelationId, rettype, GetUserId(), ACL_USAGE); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5979580139..47d5047fe8 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1742,6 +1742,9 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt) false, /* Type NOT NULL */ InvalidOid); /* typcollation */ + /* Ensure these new types are visible to ProcedureCreate */ + CommandCounterIncrement(); + /* And create the constructor functions for this range type */ makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype); makeMultirangeConstructors(multirangeTypeName, typeNamespace, diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 2712befc8e..81309b8ce3 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -123,6 +123,10 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, DependencyType self_behavior, bool reverse_self); +extern bool find_temp_object(const ObjectAddresses *addrs, + bool local_temp_okay, + ObjectAddress *foundobj); + extern ObjectAddresses *new_object_addresses(void); extern void add_exact_object_address(const ObjectAddress *object, diff --git a/src/test/isolation/expected/temp-schema-cleanup.out b/src/test/isolation/expected/temp-schema-cleanup.out index d10aee53a8..321ad2c70c 100644 --- a/src/test/isolation/expected/temp-schema-cleanup.out +++ b/src/test/isolation/expected/temp-schema-cleanup.out @@ -15,11 +15,6 @@ step s1_create_temp_objects: -- table after. CREATE TEMPORARY TABLE invalidate_catalog_cache(); - -- test non-temp function is dropped when depending on temp table - CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key); - - CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$; - exec ---- @@ -72,11 +67,6 @@ step s1_create_temp_objects: -- table after. CREATE TEMPORARY TABLE invalidate_catalog_cache(); - -- test non-temp function is dropped when depending on temp table - CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key); - - CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$; - exec ---- diff --git a/src/test/isolation/specs/temp-schema-cleanup.spec b/src/test/isolation/specs/temp-schema-cleanup.spec index 72decba6cb..82416e1c76 100644 --- a/src/test/isolation/specs/temp-schema-cleanup.spec +++ b/src/test/isolation/specs/temp-schema-cleanup.spec @@ -35,11 +35,6 @@ step s1_create_temp_objects { -- that newer objects are deleted before older objects, so create a -- table after. CREATE TEMPORARY TABLE invalidate_catalog_cache(); - - -- test non-temp function is dropped when depending on temp table - CREATE TEMPORARY TABLE just_give_me_a_type(id serial primary key); - - CREATE FUNCTION uses_a_temp_type(just_give_me_a_type) RETURNS int LANGUAGE sql AS $$SELECT 1;$$; } step s1_discard_temp { diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out index 73c6730d45..da86701a77 100644 --- a/src/test/regress/expected/create_function_sql.out +++ b/src/test/regress/expected/create_function_sql.out @@ -297,6 +297,45 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement LANGUAGE SQL RETURN x[1]; ERROR: SQL function with unquoted function body cannot have polymorphic arguments +CREATE TEMPORARY TABLE temp_table AS SELECT 1 AS val; +CREATE TEMPORARY VIEW temp_view AS SELECT 42 AS val; +CREATE TEMPORARY SEQUENCE temp_seq; +CREATE DOMAIN pg_temp.temp_domain AS int CHECK (VALUE > 0); +CREATE FUNCTION pg_temp.temp_func() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT 42; +END; +-- this should work: the function is created in a temp schema +CREATE FUNCTION pg_temp.functest_temp_dep() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT val FROM temp_table; +END; +-- this should work: temp function calling temp function (both in temp schema) +CREATE FUNCTION pg_temp.functest_temp_to_temp() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT pg_temp.temp_func(); +END; +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary views +CREATE FUNCTION functest_temp_view() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT val FROM temp_view; +END; +NOTICE: function "functest_temp_view" will be effectively temporary +DETAIL: It depends on temporary view temp_view. +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary sequences +CREATE FUNCTION functest_temp_sequence() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT nextval('temp_seq'); +END; +NOTICE: function "functest_temp_sequence" will be effectively temporary +DETAIL: It depends on temporary sequence temp_seq. +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary domains +CREATE FUNCTION functest_temp_domain() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT 5::pg_temp.temp_domain; +END; +NOTICE: function "functest_temp_domain" will be effectively temporary +DETAIL: It depends on temporary type temp_domain. -- check reporting of parse-analysis errors CREATE FUNCTION functest_S_xx(x date) RETURNS boolean LANGUAGE SQL @@ -516,27 +555,30 @@ SELECT r0.routine_name, r1.routine_name SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage WHERE routine_schema = 'temp_func_test' ORDER BY 1, 2; - routine_name | sequence_name ----------------+--------------- - functest_is_5 | functest1 - functest_is_6 | functest1 -(2 rows) + routine_name | sequence_name +------------------------+--------------- + functest_is_5 | functest1 + functest_is_6 | functest1 + functest_temp_sequence | temp_seq +(3 rows) SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage WHERE routine_schema = 'temp_func_test' ORDER BY 1, 2; - routine_name | table_name | column_name ----------------+------------+------------- - functest_is_7 | functest2 | a -(1 row) + routine_name | table_name | column_name +--------------------+------------+------------- + functest_is_7 | functest2 | a + functest_temp_view | temp_view | val +(2 rows) SELECT routine_name, table_name FROM information_schema.routine_table_usage WHERE routine_schema = 'temp_func_test' ORDER BY 1, 2; - routine_name | table_name ----------------+------------ - functest_is_7 | functest2 -(1 row) + routine_name | table_name +--------------------+------------ + functest_is_7 | functest2 + functest_temp_view | temp_view +(2 rows) DROP FUNCTION functest_IS_4a CASCADE; NOTICE: drop cascades to function functest_is_4b(integer) @@ -790,7 +832,7 @@ CONTEXT: SQL function "test1" during startup RESET check_function_bodies; -- Cleanup DROP SCHEMA temp_func_test CASCADE; -NOTICE: drop cascades to 38 other objects +NOTICE: drop cascades to 41 other objects DETAIL: drop cascades to function functest_a_1(text,date) drop cascades to function functest_a_2(text[]) drop cascades to function functest_a_3() @@ -812,6 +854,9 @@ drop cascades to function functest_s_3() drop cascades to function functest_s_3a() drop cascades to function functest_s_10(text,date) drop cascades to function functest_s_13() +drop cascades to function functest_temp_view() +drop cascades to function functest_temp_sequence() +drop cascades to function functest_temp_domain() drop cascades to function functest_s_15(integer) drop cascades to function functest_b_2(bigint) drop cascades to function functest_srf0() diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 30241e22da..5cc94011e9 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2178,6 +2178,8 @@ alter table users drop column todrop; create or replace function get_first_user() returns users as $$ SELECT * FROM users ORDER BY userid LIMIT 1; $$ language sql stable; +NOTICE: function "get_first_user" will be effectively temporary +DETAIL: It depends on temporary type users. SELECT get_first_user(); get_first_user ------------------- @@ -2193,6 +2195,8 @@ SELECT * FROM get_first_user(); create or replace function get_users() returns setof users as $$ SELECT * FROM users ORDER BY userid; $$ language sql stable; +NOTICE: function "get_users" will be effectively temporary +DETAIL: It depends on temporary type users. SELECT get_users(); get_users --------------------- diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index d02c2ceab5..16012af6d1 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -1,6 +1,10 @@ -- -- Test INSERT/UPDATE/DELETE RETURNING -- +-- This script is full of poorly-chosen object names. +-- Put them in a separate schema to avoid collisions with concurrent scripts. +CREATE SCHEMA returning_test; +SET search_path = returning_test, public; -- Simple cases CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42); INSERT INTO foo (f2,f3) @@ -306,6 +310,8 @@ SELECT * FROM foo; -- Check use of a whole-row variable for an inlined set-returning function CREATE FUNCTION foo_f() RETURNS SETOF foo AS $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +NOTICE: function "foo_f" will be effectively temporary +DETAIL: It depends on temporary type foo. UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 RETURNING foo_f; foo_f @@ -930,8 +936,10 @@ BEGIN ATOMIC (SELECT count(*) FROM foo WHERE foo = o), (SELECT count(*) FROM foo WHERE foo = n); END; +NOTICE: function "foo_update" will be effectively temporary +DETAIL: It depends on temporary table foo. \sf foo_update -CREATE OR REPLACE FUNCTION public.foo_update() +CREATE OR REPLACE FUNCTION returning_test.foo_update() RETURNS void LANGUAGE sql BEGIN ATOMIC @@ -985,4 +993,9 @@ BEGIN ATOMIC FROM foo foo_1 WHERE (foo_1.* = n.*)) AS count; END -DROP FUNCTION foo_update; +-- Clean up +RESET search_path; +DROP SCHEMA returning_test CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to function returning_test.joinview_upd_trig_fn() +drop cascades to function returning_test.foo_update() diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 677ad2ab9a..956bc2d02f 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -907,6 +907,8 @@ create temp table compos (f1 int, f2 text); create function fcompos1(v compos) returns void as $$ insert into compos values (v); -- fail $$ language sql; +NOTICE: function "fcompos1" will be effectively temporary +DETAIL: It depends on temporary type compos. ERROR: column "f1" is of type integer but expression is of type compos LINE 2: insert into compos values (v); -- fail ^ @@ -914,12 +916,18 @@ HINT: You will need to rewrite or cast the expression. create function fcompos1(v compos) returns void as $$ insert into compos values (v.*); $$ language sql; +NOTICE: function "fcompos1" will be effectively temporary +DETAIL: It depends on temporary type compos. create function fcompos2(v compos) returns void as $$ select fcompos1(v); $$ language sql; +NOTICE: function "fcompos2" will be effectively temporary +DETAIL: It depends on temporary type compos. create function fcompos3(v compos) returns void as $$ select fcompos1(fcompos3.v.*); $$ language sql; +NOTICE: function "fcompos3" will be effectively temporary +DETAIL: It depends on temporary type compos. select fcompos1(row(1,'one')); fcompos1 ---------- @@ -1012,6 +1020,8 @@ select last(f) from fullname f; create function longname(fullname) returns text language sql as $$select $1.first || ' ' || $1.last$$; +NOTICE: function "longname" will be effectively temporary +DETAIL: It depends on temporary type fullname. select f.longname from fullname f; longname ---------- diff --git a/src/test/regress/sql/create_function_sql.sql b/src/test/regress/sql/create_function_sql.sql index 3d5f2a9209..7c6361d606 100644 --- a/src/test/regress/sql/create_function_sql.sql +++ b/src/test/regress/sql/create_function_sql.sql @@ -199,6 +199,45 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement LANGUAGE SQL RETURN x[1]; +CREATE TEMPORARY TABLE temp_table AS SELECT 1 AS val; +CREATE TEMPORARY VIEW temp_view AS SELECT 42 AS val; +CREATE TEMPORARY SEQUENCE temp_seq; +CREATE DOMAIN pg_temp.temp_domain AS int CHECK (VALUE > 0); +CREATE FUNCTION pg_temp.temp_func() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT 42; +END; + +-- this should work: the function is created in a temp schema +CREATE FUNCTION pg_temp.functest_temp_dep() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT val FROM temp_table; +END; + +-- this should work: temp function calling temp function (both in temp schema) +CREATE FUNCTION pg_temp.functest_temp_to_temp() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT pg_temp.temp_func(); +END; + +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary views +CREATE FUNCTION functest_temp_view() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT val FROM temp_view; +END; + +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary sequences +CREATE FUNCTION functest_temp_sequence() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT nextval('temp_seq'); +END; + +-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary domains +CREATE FUNCTION functest_temp_domain() RETURNS int LANGUAGE sql +BEGIN ATOMIC; + SELECT 5::pg_temp.temp_domain; +END; + -- check reporting of parse-analysis errors CREATE FUNCTION functest_S_xx(x date) RETURNS boolean LANGUAGE SQL diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql index cc99cb53f6..f1c85a9731 100644 --- a/src/test/regress/sql/returning.sql +++ b/src/test/regress/sql/returning.sql @@ -2,6 +2,11 @@ -- Test INSERT/UPDATE/DELETE RETURNING -- +-- This script is full of poorly-chosen object names. +-- Put them in a separate schema to avoid collisions with concurrent scripts. +CREATE SCHEMA returning_test; +SET search_path = returning_test, public; + -- Simple cases CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42); @@ -407,4 +412,7 @@ BEGIN ATOMIC END; \sf foo_update -DROP FUNCTION foo_update; + +-- Clean up +RESET search_path; +DROP SCHEMA returning_test CASCADE; -- 2.43.0
From 8f11537136a4821090b1e96695eafe6f4870dcbd Mon Sep 17 00:00:00 2001 From: Tom Lane <[email protected]> Date: Fri, 21 Nov 2025 16:05:06 -0500 Subject: [PATCH v8 1/2] Refactor dependency recording to enable dependency collection. Add new function collectDependenciesOfExpr() that collects expression dependencies into a caller-supplied ObjectAddresses structure, without immediately recording them. This enables more flexible dependency handling patterns where callers need to examine, filter, or modify dependencies before recording them. The caller is responsible for ensuring that the results are de-duplicated before being put into pg_depend. (External callers will not need to do that explicitly, since they must go through record_object_address_dependencies() which will take care of it.) This design avoids redundant de-duplication work when collecting dependencies from multiple sources. The existing recordDependencyOnExpr() function is reimplemented using the new collection function, maintaining backward compatibility. Author: Jim Jones <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/backend/catalog/dependency.c | 54 +++++++++++++++++++++++++------- src/include/catalog/dependency.h | 3 ++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 7dded634eb..61bdf4a577 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1554,25 +1554,57 @@ recordDependencyOnExpr(const ObjectAddress *depender, Node *expr, List *rtable, DependencyType behavior) { - find_expr_references_context context; - - context.addrs = new_object_addresses(); + ObjectAddresses *addrs; - /* Set up interpretation for Vars at varlevelsup = 0 */ - context.rtables = list_make1(rtable); + addrs = new_object_addresses(); - /* Scan the expression tree for referenceable objects */ - find_expr_references_walker(expr, &context); + /* Collect all dependencies from the expression */ + collectDependenciesOfExpr(addrs, expr, rtable); - /* Remove any duplicates */ - eliminate_duplicate_dependencies(context.addrs); + /* Remove duplicates */ + eliminate_duplicate_dependencies(addrs); /* And record 'em */ recordMultipleDependencies(depender, - context.addrs->refs, context.addrs->numrefs, + addrs->refs, addrs->numrefs, behavior); - free_object_addresses(context.addrs); + free_object_addresses(addrs); +} + +/* + * collectDependenciesOfExpr - collect expression dependencies + * + * This function analyzes an expression or query in node-tree form to + * find all the objects it refers to (tables, columns, operators, + * functions, etc.) and adds them to the provided ObjectAddresses + * structure. Unlike recordDependencyOnExpr, this function does not + * immediately record the dependencies, allowing the caller to add to, + * filter, or modify the collected dependencies before recording them. + * + * rtable is the rangetable to be used to interpret Vars with varlevelsup=0. + * It can be NIL if no such variables are expected. + * + * Note: the returned list may well contain duplicates. The caller should + * de-duplicate before recording the dependencies. Within this file, callers + * must call eliminate_duplicate_dependencies(). External callers typically + * go through record_object_address_dependencies() which will see to that. + * This choice allows collecting dependencies from multiple sources without + * redundant de-duplication work. + */ +void +collectDependenciesOfExpr(ObjectAddresses *addrs, + Node *expr, List *rtable) +{ + find_expr_references_context context; + + context.addrs = addrs; + + /* Set up interpretation for Vars at varlevelsup = 0 */ + context.rtables = list_make1(rtable); + + /* Scan the expression tree for referenceable objects */ + find_expr_references_walker(expr, &context); } /* diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 0ea7ccf524..2712befc8e 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -114,6 +114,9 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender, Node *expr, List *rtable, DependencyType behavior); +extern void collectDependenciesOfExpr(ObjectAddresses *addrs, + Node *expr, List *rtable); + extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, -- 2.43.0
