Hi,

While reviewing a patch I noticed that SQL functions defined with BEGIN
ATOMIC can reference temporary relations, and such functions are
(rightfully) dropped at session end --- but without any notification to
the user:

$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.

postgres=# CREATE TEMPORARY TABLE tmp AS SELECT 42 AS val;
SELECT 1

postgres=# CREATE FUNCTION tmpval_atomic()
RETURNS int LANGUAGE sql
BEGIN ATOMIC;
SELECT val FROM tmp;
END;
CREATE FUNCTION

postgres=# \df
                           List of functions
 Schema |     Name      | Result data type | Argument data types | Type
--------+---------------+------------------+---------------------+------
 public | tmpval_atomic | integer          |                     | func
(1 row)

postgres=# \q

$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.

postgres=# \df
                       List of functions
 Schema | Name | Result data type | Argument data types | Type
--------+------+------------------+---------------------+------
(0 rows)


Although this behaviour is expected, it can be surprising. A NOTICE or
WARNING at CREATE FUNCTION time could save some head-scratching later.
We already have a precedent. When creating a view that depends on a
temporary relation, postgres automatically makes it a temporary view and
emits a NOTICE:

postgres=# CREATE TEMPORARY TABLE tmp AS SELECT 42 AS val;
SELECT 1

postgres=# CREATE VIEW v AS SELECT * FROM tmp;
NOTICE:  view "v" will be a temporary view
CREATE VIEW

postgres=# \d
         List of relations
   Schema   | Name | Type  | Owner
------------+------+-------+-------
 pg_temp_74 | tmp  | table | jim
 pg_temp_74 | v    | view  | jim
(2 rows)

postgres=# \q

$ /usr/local/postgres-dev/bin/psql postgres
psql (19devel)
Type "help" for help.

postgres=# \d
Did not find any relations.


Attached a PoC that issues a WARNING if a BEGIN ATOMIC function is
created using temporary objects:

postgres=# CREATE TEMPORARY TABLE tmp AS SELECT 42 AS val;
SELECT 1

postgres=# CREATE FUNCTION tmpval_atomic()
RETURNS int LANGUAGE sql
BEGIN ATOMIC;
SELECT val FROM tmp;
END;
WARNING:  function defined with BEGIN ATOMIC depends on temporary
relation "tmp"
DETAIL:  the function will be dropped automatically at session end.
CREATE FUNCTION

This PoC adds a parameter to check_sql_fn_statements() and
check_sql_fn_statement(), so I’m not entirely sure if that’s the best
approach. I’m also not sure whether a NOTICE would be a better fit than
a WARNING here. Feedback is welcome.

Any thoughts?

Best regards, Jim
From 86c13fdf447f090ba208cc93b9c68562364cac97 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 21 Sep 2025 13:07:06 +0200
Subject: [PATCH v1] Add WARNING on BEGIN ATOMIC SQL functions using temp
 relations

When a SQL function is defined with a BEGIN ATOMIC block and
references a temporary relation, the function is dropped at
session end along with the temp relation. From the user's
perspective, the function looks like a persistent database
object in a permanent schema, yet it disappears without notice.

To reduce confusion, emit a WARNING at CREATE FUNCTION time if
a BEGIN ATOMIC SQL function depends on a relation in a temporary
namespace.

This mirrors the behavior of CREATE VIEW, which issues a NOTICE
when a view depends on temporary relations and is therefore made
temporary itself.

Regular SQL functions without BEGIN ATOMIC are unaffected: they
may also reference temporary objects, but they remain present
after session end and simply fail at execution time if called.

Regression tests are updated to cover the new WARNING.
---
 src/backend/catalog/pg_proc.c           |  2 +-
 src/backend/executor/functions.c        | 55 ++++++++++++++++++++++---
 src/include/executor/functions.h        |  2 +-
 src/test/regress/expected/returning.out |  2 +
 4 files changed, 53 insertions(+), 8 deletions(-)

diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 75b17fed15..1b42a6f4ee 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -974,7 +974,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 			Oid			rettype;
 			TupleDesc	rettupdesc;
 
-			check_sql_fn_statements(querytree_list);
+			check_sql_fn_statements(querytree_list, !isnull);
 
 			(void) get_func_result_type(funcoid, &rettype, &rettupdesc);
 
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 630d708d2a..204c8f371a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -223,7 +223,7 @@ static void sql_compile_error_callback(void *arg);
 static void sql_exec_error_callback(void *arg);
 static void ShutdownSQLFunction(Datum arg);
 static void RemoveSQLFunctionCache(void *arg);
-static void check_sql_fn_statement(List *queryTreeList);
+static void check_sql_fn_statement(List *queryTreeList, bool is_atomic);
 static bool check_sql_stmt_retval(List *queryTreeList,
 								  Oid rettype, TupleDesc rettupdesc,
 								  char prokind, bool insertDroppedCols);
@@ -950,7 +950,7 @@ prepare_next_query(SQLFunctionHashEntry *func)
 	/*
 	 * Check that there are no statements we don't want to allow.
 	 */
-	check_sql_fn_statement(queryTree_list);
+	check_sql_fn_statement(queryTree_list, false);
 
 	/*
 	 * If this is the last query, check that the function returns the type it
@@ -1247,7 +1247,7 @@ sql_postrewrite_callback(List *querytree_list, void *arg)
 	 * there's no real point in this because the result can't change from what
 	 * we saw originally.  But it's cheap and maybe someday it will matter.)
 	 */
-	check_sql_fn_statement(querytree_list);
+	check_sql_fn_statement(querytree_list, false);
 
 	/*
 	 * If this is the last query, we must re-do what check_sql_stmt_retval did
@@ -2030,9 +2030,13 @@ RemoveSQLFunctionCache(void *arg)
  *
  * Check statements in an SQL function.  Error out if there is anything that
  * is not acceptable.
+ *
+ * The is_atomic flag indicates whether the function was defined using
+ * SQL-standard BEGIN ATOMIC.  In that case we additionally check for
+ * dependencies on temporary relations and issue a WARNING if found.
  */
 void
-check_sql_fn_statements(List *queryTreeLists)
+check_sql_fn_statements(List *queryTreeLists, bool is_atomic)
 {
 	ListCell   *lc;
 
@@ -2041,7 +2045,7 @@ check_sql_fn_statements(List *queryTreeLists)
 	{
 		List	   *sublist = lfirst_node(List, lc);
 
-		check_sql_fn_statement(sublist);
+		check_sql_fn_statement(sublist, is_atomic);
 	}
 }
 
@@ -2049,7 +2053,7 @@ check_sql_fn_statements(List *queryTreeLists)
  * As above, for a single sublist of Queries.
  */
 static void
-check_sql_fn_statement(List *queryTreeList)
+check_sql_fn_statement(List *queryTreeList, bool is_atomic)
 {
 	ListCell   *lc;
 
@@ -2057,6 +2061,45 @@ check_sql_fn_statement(List *queryTreeList)
 	{
 		Query	   *query = lfirst_node(Query, lc);
 
+		if (is_atomic)
+		{
+			ListCell *lc2;
+
+			/*
+			 * Walk the range table of this query to detect references to
+			 * temporary relations.
+			 *
+			 * For ordinary SQL functions, the function definition is stored
+			 * permanently even if it refers to temporary relations.  Once
+			 * the session ends and the temp relation vanishes, the function
+			 * still exists but will fail at execution time with "relation
+			 * does not exist". We do not issue any warning at creation
+			 * time in that case; the function is simply left in place.
+			 *
+			 * For functions USING SQL-standard BEGIN ATOMIC definition,
+			 * this situation is more confusing: the function appears to be
+			 * a permanent database object, but in fact its lifetime is tied
+			 * to session-local objects that vanish at session end. To avoid
+			 * silent surprises, we emit a WARNING at CREATE FUNCTION time if
+			 * an ATOMIC function depends on a temp relation.
+			 */
+			foreach (lc2, query->rtable)
+			{
+				RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc2);
+
+				if (rte->rtekind == RTE_RELATION)
+				{
+					Oid relnsp = get_rel_namespace(rte->relid);
+
+					if (isAnyTempNamespace(relnsp))
+						ereport(WARNING,
+								(errmsg("function defined with BEGIN ATOMIC depends on temporary relation \"%s\"",
+										get_rel_name(rte->relid)),
+								 errdetail("the function will be dropped automatically at session end.")));
+				}
+			}
+		}
+
 		/*
 		 * Disallow calling procedures with output arguments.  The current
 		 * implementation would just throw the output values away, unless the
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index 58bdff9b03..466e69fe24 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -43,7 +43,7 @@ extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTupl
 extern void sql_fn_parser_setup(struct ParseState *pstate,
 								SQLFunctionParseInfoPtr pinfo);
 
-extern void check_sql_fn_statements(List *queryTreeLists);
+extern void check_sql_fn_statements(List *queryTreeLists, bool is_atomic);
 
 extern bool check_sql_fn_retval(List *queryTreeLists,
 								Oid rettype, TupleDesc rettupdesc,
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index 341b689f76..731e6cb782 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -930,6 +930,8 @@ BEGIN ATOMIC
               (SELECT count(*) FROM foo WHERE foo = o),
               (SELECT count(*) FROM foo WHERE foo = n);
 END;
+WARNING:  function defined with BEGIN ATOMIC depends on temporary relation "foo"
+DETAIL:  the function will be dropped automatically at session end.
 \sf foo_update
 CREATE OR REPLACE FUNCTION public.foo_update()
  RETURNS void
-- 
2.43.0

Reply via email to