Skip to content

Commit f90b375

Browse files
jimjonesbrCommitfest Bot
authored andcommitted
Prevent SQL functions with BEGIN ATOMIC from depending on temporary objects
SQL functions with BEGIN ATOMIC bodies are intended to be permanently definable and should not depend on session-specific temporary objects. This commit implements dependency validation to enforce this restriction. Key changes: - Add filter_temp_objects() to detect temporary objects (tables, views, types, functions, sequences, domains) and raise descriptive errors - Integrate temp object filtering into ProcedureCreate() for SQL functions with BEGIN ATOMIC bodies - Allow temp-to-temp references: functions in temporary schemas can reference temporary objects since both have the same session lifecycle - Skip filtering during bootstrap and pg_upgrade to avoid interfering with system operations - Preserve existing behavior for regular SQL functions and parameter defaults The implementation leverages the existing collectDependenciesFromExpr() infrastructure to collect dependencies before applying temp object validation, using a collect-then-filter-then-record pattern for SQL function bodies.
1 parent 063e4ce commit f90b375

File tree

7 files changed

+292
-27
lines changed

7 files changed

+292
-27
lines changed

src/backend/catalog/dependency.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "catalog/pg_language.h"
4545
#include "catalog/pg_largeobject.h"
4646
#include "catalog/pg_namespace.h"
47+
#include "catalog/namespace.h"
4748
#include "catalog/pg_opclass.h"
4849
#include "catalog/pg_operator.h"
4950
#include "catalog/pg_opfamily.h"
@@ -2488,6 +2489,87 @@ eliminate_duplicate_dependencies(ObjectAddresses *addrs)
24882489
addrs->numrefs = newrefs;
24892490
}
24902491

2492+
/*
2493+
* filter_temp_objects - detect and reject temporary objects in an ObjectAddresses array
2494+
*
2495+
* This function checks if any dependencies on temporary objects (objects in
2496+
* temporary namespaces) exist in the given ObjectAddresses array. If temp objects
2497+
* are found, it raises an error to prevent them from being used in SQL functions
2498+
* with BEGIN ATOMIC bodies, as such dependencies would be inappropriate for
2499+
* permanent function definitions.
2500+
*
2501+
* Currently checks for temporary tables, views, types, and functions by examining
2502+
* their containing namespaces. The function raises an error with a descriptive
2503+
* message if any temporary object dependency is detected.
2504+
*/
2505+
void filter_temp_objects(ObjectAddresses *addrs)
2506+
{
2507+
int oldref;
2508+
2509+
if (addrs->numrefs <= 0)
2510+
return; /* nothing to do */
2511+
2512+
/* Check all dependencies for temp objects */
2513+
for (oldref = 0; oldref < addrs->numrefs; oldref++)
2514+
{
2515+
ObjectAddress *thisobj = addrs->refs + oldref;
2516+
bool is_temp = false;
2517+
char *objname = NULL;
2518+
2519+
/* Check if this dependency is on a temporary object */
2520+
if (thisobj->classId == RelationRelationId)
2521+
{
2522+
/* For relations, check if they're in a temp namespace */
2523+
Oid relnamespace = get_rel_namespace(thisobj->objectId);
2524+
if (OidIsValid(relnamespace) && isAnyTempNamespace(relnamespace))
2525+
{
2526+
is_temp = true;
2527+
objname = get_rel_name(thisobj->objectId);
2528+
}
2529+
}
2530+
else if (thisobj->classId == TypeRelationId)
2531+
{
2532+
/* For types, check if they're in a temp namespace */
2533+
HeapTuple tup;
2534+
Form_pg_type typform;
2535+
Oid typnamespace = InvalidOid;
2536+
2537+
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(thisobj->objectId));
2538+
if (HeapTupleIsValid(tup))
2539+
{
2540+
typform = (Form_pg_type)GETSTRUCT(tup);
2541+
typnamespace = typform->typnamespace;
2542+
if (OidIsValid(typnamespace) && isAnyTempNamespace(typnamespace))
2543+
{
2544+
is_temp = true;
2545+
objname = NameStr(typform->typname);
2546+
}
2547+
ReleaseSysCache(tup);
2548+
}
2549+
}
2550+
else if (thisobj->classId == ProcedureRelationId)
2551+
{
2552+
/* For functions, check if they're in a temp namespace */
2553+
Oid funcnamespace = get_func_namespace(thisobj->objectId);
2554+
if (OidIsValid(funcnamespace) && isAnyTempNamespace(funcnamespace))
2555+
{
2556+
is_temp = true;
2557+
objname = get_func_name(thisobj->objectId);
2558+
}
2559+
}
2560+
2561+
/* Raise error if temp object found */
2562+
if (is_temp)
2563+
{
2564+
ereport(ERROR,
2565+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2566+
errmsg("cannot use temporary object \"%s\" in SQL function with BEGIN ATOMIC",
2567+
objname ? objname : "unknown"),
2568+
errdetail("SQL functions with BEGIN ATOMIC cannot depend on temporary objects.")));
2569+
}
2570+
}
2571+
}
2572+
24912573
/*
24922574
* qsort comparator for ObjectAddress items
24932575
*/

src/backend/catalog/pg_proc.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "catalog/pg_language.h"
2525
#include "catalog/pg_namespace.h"
2626
#include "catalog/pg_proc.h"
27+
#include "catalog/namespace.h"
2728
#include "catalog/pg_transform.h"
2829
#include "catalog/pg_type.h"
2930
#include "executor/functions.h"
@@ -663,7 +664,33 @@ ProcedureCreate(const char *procedureName,
663664

664665
/* dependency on SQL routine body */
665666
if (languageObjectId == SQLlanguageId && prosqlbody)
666-
recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
667+
{
668+
ObjectAddresses *body_addrs;
669+
670+
/*
671+
* For SQL functions with BEGIN ATOMIC, we use a collect-then-filter-then-record
672+
* approach to handle temp object dependencies appropriately.
673+
*/
674+
body_addrs = new_object_addresses();
675+
collectDependenciesFromExpr(body_addrs, prosqlbody, NIL);
676+
677+
/*
678+
* Check for temp objects that are referenced in the function body.
679+
* For SQL functions with BEGIN ATOMIC bodies, we need to prevent
680+
* dependencies on temporary objects since such functions should be
681+
* permanently definable and not depend on session-specific temp objects.
682+
* This will raise an error if any temp objects are found. If the function
683+
* itself is being created in a temporary schema, then it's OK for it to
684+
* reference temp objects.
685+
*/
686+
if (!IsBootstrapProcessingMode() && !IsBinaryUpgrade &&
687+
!isAnyTempNamespace(procNamespace))
688+
filter_temp_objects(body_addrs);
689+
690+
/* Record the filtered dependencies */
691+
record_object_address_dependencies(&myself, body_addrs, DEPENDENCY_NORMAL);
692+
free_object_addresses(body_addrs);
693+
}
667694

668695
/* dependency on parameter default expressions */
669696
if (parameterDefaults)

src/include/catalog/dependency.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
123123
DependencyType self_behavior,
124124
bool reverse_self);
125125

126+
extern void filter_temp_objects(ObjectAddresses *addrs);
127+
126128
extern ObjectAddresses *new_object_addresses(void);
127129

128130
extern void add_exact_object_address(const ObjectAddress *object,

src/test/regress/expected/create_function_sql.out

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,86 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
297297
LANGUAGE SQL
298298
RETURN x[1];
299299
ERROR: SQL function with unquoted function body cannot have polymorphic arguments
300+
CREATE TEMPORARY TABLE temp_table AS SELECT 1 AS val;
301+
CREATE TEMPORARY VIEW temp_view AS SELECT 42 AS val;
302+
CREATE TYPE pg_temp.temp_type AS (x int, y text);
303+
CREATE TEMPORARY SEQUENCE temp_seq;
304+
CREATE DOMAIN pg_temp.temp_domain AS int CHECK (VALUE > 0);
305+
CREATE FUNCTION pg_temp.temp_func() RETURNS int LANGUAGE sql
306+
BEGIN ATOMIC;
307+
SELECT 42;
308+
END;
309+
-- these should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary tables
310+
CREATE FUNCTION functest_temp_dep() RETURNS int LANGUAGE sql
311+
BEGIN ATOMIC;
312+
SELECT val FROM temp_table;
313+
END;
314+
ERROR: cannot use temporary object "temp_table" in SQL function with BEGIN ATOMIC
315+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
316+
CREATE FUNCTION functest_temp_dep_subquery() RETURNS int LANGUAGE sql
317+
BEGIN ATOMIC;
318+
SELECT (SELECT COUNT(*) FROM temp_table);
319+
END;
320+
ERROR: cannot use temporary object "temp_table" in SQL function with BEGIN ATOMIC
321+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
322+
CREATE FUNCTION functest_temp_dep_join() RETURNS int LANGUAGE sql
323+
BEGIN ATOMIC;
324+
SELECT t1.val FROM temp_table t1
325+
JOIN temp_view t2 ON t1.val = t2.val;
326+
END;
327+
ERROR: cannot use temporary object "temp_view" in SQL function with BEGIN ATOMIC
328+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
329+
CREATE FUNCTION functest_temp_indirect_dep() RETURNS int LANGUAGE sql
330+
BEGIN ATOMIC;
331+
SELECT * FROM pg_class WHERE oid = 'temp_table'::regclass;
332+
END;
333+
ERROR: cannot use temporary object "temp_table" in SQL function with BEGIN ATOMIC
334+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
335+
-- this should work: the function is created in a temp schema
336+
CREATE FUNCTION pg_temp.functest_temp_dep() RETURNS int LANGUAGE sql
337+
BEGIN ATOMIC;
338+
SELECT val FROM temp_table;
339+
END;
340+
-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary functions
341+
CREATE FUNCTION functest_temp_func_dep() RETURNS int LANGUAGE sql
342+
BEGIN ATOMIC;
343+
SELECT pg_temp.temp_func();
344+
END;
345+
ERROR: cannot use temporary object "temp_func" in SQL function with BEGIN ATOMIC
346+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
347+
-- this should work: temp function calling temp function (both in temp schema)
348+
CREATE FUNCTION pg_temp.functest_temp_to_temp() RETURNS int LANGUAGE sql
349+
BEGIN ATOMIC;
350+
SELECT pg_temp.temp_func();
351+
END;
352+
-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary views
353+
CREATE FUNCTION functest_temp_view() RETURNS int LANGUAGE sql
354+
BEGIN ATOMIC;
355+
SELECT val FROM temp_view;
356+
END;
357+
ERROR: cannot use temporary object "temp_view" in SQL function with BEGIN ATOMIC
358+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
359+
-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary types
360+
CREATE FUNCTION functest_temp_type() RETURNS int LANGUAGE sql
361+
BEGIN ATOMIC;
362+
SELECT (ROW(1,'test')::pg_temp.temp_type).x;
363+
END;
364+
ERROR: cannot use temporary object "temp_type" in SQL function with BEGIN ATOMIC
365+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
366+
-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary sequences
367+
CREATE FUNCTION functest_temp_sequence() RETURNS int LANGUAGE sql
368+
BEGIN ATOMIC;
369+
SELECT nextval('temp_seq');
370+
END;
371+
ERROR: cannot use temporary object "temp_seq" in SQL function with BEGIN ATOMIC
372+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
373+
-- this should fail: BEGIN ATOMIC SQL-functions cannot depend on temporary domains
374+
CREATE FUNCTION functest_temp_domain() RETURNS int LANGUAGE sql
375+
BEGIN ATOMIC;
376+
SELECT 5::pg_temp.temp_domain;
377+
END;
378+
ERROR: cannot use temporary object "temp_domain" in SQL function with BEGIN ATOMIC
379+
DETAIL: SQL functions with BEGIN ATOMIC cannot depend on temporary objects.
300380
-- check reporting of parse-analysis errors
301381
CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
302382
LANGUAGE SQL

src/test/regress/expected/returning.out

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-- Test INSERT/UPDATE/DELETE RETURNING
33
--
44
-- Simple cases
5-
CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42);
5+
CREATE TABLE foo (f1 serial, f2 text, f3 int default 42);
66
INSERT INTO foo (f2,f3)
77
VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9)
88
RETURNING *, f1+f3 AS sum;
@@ -447,7 +447,7 @@ INSERT INTO foo VALUES (4)
447447
new.tableoid::regclass, new.ctid, new.*, *;
448448
QUERY PLAN
449449
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
450-
Insert on pg_temp.foo
450+
Insert on public.foo
451451
Output: (old.tableoid)::regclass, old.ctid, old.f1, old.f2, old.f3, old.f4, (new.tableoid)::regclass, new.ctid, new.f1, new.f2, new.f3, new.f4, foo.f1, foo.f2, foo.f3, foo.f4
452452
-> Result
453453
Output: 4, NULL::text, 42, '99'::bigint
@@ -471,7 +471,7 @@ INSERT INTO foo VALUES (4, 'conflict'), (5, 'ok')
471471
n.tableoid::regclass, n.ctid, n.*, *;
472472
QUERY PLAN
473473
----------------------------------------------------------------------------------------------------------------------------------------------------------
474-
Insert on pg_temp.foo
474+
Insert on public.foo
475475
Output: (o.tableoid)::regclass, o.ctid, o.f1, o.f2, o.f3, o.f4, (n.tableoid)::regclass, n.ctid, n.f1, n.f2, n.f3, n.f4, foo.f1, foo.f2, foo.f3, foo.f4
476476
Conflict Resolution: UPDATE
477477
Conflict Arbiter Indexes: foo_f1_idx
@@ -498,12 +498,12 @@ UPDATE foo SET f4 = 100 WHERE f1 = 5
498498
old.f4::text||'->'||new.f4::text AS change;
499499
QUERY PLAN
500500
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
501-
Update on pg_temp.foo
501+
Update on public.foo
502502
Output: (old.tableoid)::regclass, old.ctid, old.f1, old.f2, old.f3, old.f4, old.*, (new.tableoid)::regclass, new.ctid, new.f1, new.f2, new.f3, new.f4, new.*, (((old.f4)::text || '->'::text) || (new.f4)::text)
503-
Update on pg_temp.foo foo_1
503+
Update on public.foo foo_1
504504
-> Result
505505
Output: '100'::bigint, foo_1.tableoid, foo_1.ctid
506-
-> Seq Scan on pg_temp.foo foo_1
506+
-> Seq Scan on public.foo foo_1
507507
Output: foo_1.tableoid, foo_1.ctid
508508
Filter: (foo_1.f1 = 5)
509509
(8 rows)
@@ -524,10 +524,10 @@ DELETE FROM foo WHERE f1 = 5
524524
new.tableoid::regclass, new.ctid, new.*, *;
525525
QUERY PLAN
526526
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
527-
Delete on pg_temp.foo
527+
Delete on public.foo
528528
Output: (old.tableoid)::regclass, old.ctid, old.f1, old.f2, old.f3, old.f4, (new.tableoid)::regclass, new.ctid, new.f1, new.f2, new.f3, new.f4, foo_1.f1, foo_1.f2, foo_1.f3, foo_1.f4
529-
Delete on pg_temp.foo foo_1
530-
-> Seq Scan on pg_temp.foo foo_1
529+
Delete on public.foo foo_1
530+
-> Seq Scan on public.foo foo_1
531531
Output: foo_1.tableoid, foo_1.ctid
532532
Filter: (foo_1.f1 = 5)
533533
(6 rows)
@@ -547,7 +547,7 @@ INSERT INTO foo VALUES (5, 'subquery test')
547547
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
548548
QUERY PLAN
549549
---------------------------------------------------------------
550-
Insert on pg_temp.foo
550+
Insert on public.foo
551551
Output: (SubPlan expr_1), (SubPlan expr_2)
552552
-> Result
553553
Output: 5, 'subquery test'::text, 42, '99'::bigint
@@ -580,12 +580,12 @@ UPDATE foo SET f4 = 100 WHERE f1 = 5
580580
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
581581
QUERY PLAN
582582
----------------------------------------------------------------
583-
Update on pg_temp.foo
583+
Update on public.foo
584584
Output: (SubPlan expr_1), (SubPlan expr_2), (SubPlan expr_3)
585-
Update on pg_temp.foo foo_1
585+
Update on public.foo foo_1
586586
-> Result
587587
Output: '100'::bigint, foo_1.tableoid, foo_1.ctid
588-
-> Seq Scan on pg_temp.foo foo_1
588+
-> Seq Scan on public.foo foo_1
589589
Output: foo_1.tableoid, foo_1.ctid
590590
Filter: (foo_1.f1 = 5)
591591
SubPlan expr_1
@@ -620,10 +620,10 @@ DELETE FROM foo WHERE f1 = 5
620620
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
621621
QUERY PLAN
622622
---------------------------------------------------------------
623-
Delete on pg_temp.foo
623+
Delete on public.foo
624624
Output: (SubPlan expr_1), (SubPlan expr_2)
625-
Delete on pg_temp.foo foo_1
626-
-> Seq Scan on pg_temp.foo foo_1
625+
Delete on public.foo foo_1
626+
-> Seq Scan on public.foo foo_1
627627
Output: foo_1.tableoid, foo_1.ctid
628628
Filter: (foo_1.f1 = 5)
629629
SubPlan expr_1
@@ -656,15 +656,15 @@ EXPLAIN (verbose, costs off)
656656
DELETE FROM foo WHERE f1 = 4 RETURNING old.*,new.*, *;
657657
QUERY PLAN
658658
---------------------------------------------------------------------------------------------------------------------------------------
659-
Update on pg_temp.foo
659+
Update on public.foo
660660
Output: old.f1, old.f2, old.f3, old.f4, new.f1, new.f2, new.f3, new.f4, foo_2.f1, foo_2.f2, foo_2.f3, foo_2.f4
661-
Update on pg_temp.foo foo_2
661+
Update on public.foo foo_2
662662
-> Nested Loop
663663
Output: (foo_2.f2 || ' (deleted)'::text), '-1'::integer, '-1'::bigint, foo_1.ctid, foo_1.tableoid, foo_2.tableoid, foo_2.ctid
664-
-> Seq Scan on pg_temp.foo foo_2
664+
-> Seq Scan on public.foo foo_2
665665
Output: foo_2.f2, foo_2.f1, foo_2.tableoid, foo_2.ctid
666666
Filter: (foo_2.f1 = 4)
667-
-> Seq Scan on pg_temp.foo foo_1
667+
-> Seq Scan on public.foo foo_1
668668
Output: foo_1.ctid, foo_1.f1, foo_1.tableoid
669669
Filter: (foo_1.f1 = 4)
670670
(11 rows)
@@ -681,9 +681,9 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
681681
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
682682
QUERY PLAN
683683
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
684-
Update on pg_temp.foo
684+
Update on public.foo
685685
Output: old.f1, old.f2, old.f3, old.f4, joinme.other, new.f1, new.f2, new.f3, new.f4, joinme.other, foo_1.f1, foo_1.f2, foo_1.f3, foo_1.f4, joinme.other, (new.f3 - old.f3)
686-
Update on pg_temp.foo foo_1
686+
Update on public.foo foo_1
687687
-> Hash Join
688688
Output: foo_2.f1, (foo_2.f3 + 1), joinme.ctid, foo_2.ctid, joinme_1.ctid, joinme.other, foo_1.tableoid, foo_1.ctid, foo_2.tableoid
689689
Hash Cond: (foo_1.f2 = joinme.f2j)
@@ -694,7 +694,7 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
694694
Output: joinme_1.ctid, joinme_1.f2j
695695
-> Hash
696696
Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
697-
-> Seq Scan on pg_temp.foo foo_1
697+
-> Seq Scan on public.foo foo_1
698698
Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
699699
-> Hash
700700
Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
@@ -705,7 +705,7 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
705705
Output: joinme.ctid, joinme.other, joinme.f2j
706706
-> Hash
707707
Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
708-
-> Seq Scan on pg_temp.foo foo_2
708+
-> Seq Scan on public.foo foo_2
709709
Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
710710
Filter: (foo_2.f3 = 57)
711711
(27 rows)
@@ -768,7 +768,7 @@ UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
768768
Output: joinme.other, joinme.ctid, joinme.f2j
769769
-> Hash
770770
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
771-
-> Seq Scan on pg_temp.foo
771+
-> Seq Scan on public.foo
772772
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
773773
Filter: (foo.f3 = 58)
774774
(12 rows)
@@ -986,3 +986,5 @@ BEGIN ATOMIC
986986
WHERE (foo_1.* = n.*)) AS count;
987987
END
988988
DROP FUNCTION foo_update;
989+
DROP TABLE foo CASCADE;
990+
NOTICE: drop cascades to view voo

0 commit comments

Comments
 (0)