Skip to content

Commit b0253c7

Browse files
author
Commitfest Bot
committed
[CF 6074] v5 - Disallow BEGIN ATOMIC SQL functions depending on temp relations
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/6074 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/[email protected] Author(s): Jim Jones
2 parents d3111cb + f90b375 commit b0253c7

File tree

7 files changed

+340
-46
lines changed

7 files changed

+340
-46
lines changed

src/backend/catalog/dependency.c

Lines changed: 127 additions & 19 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"
@@ -1553,10 +1554,41 @@ void
15531554
recordDependencyOnExpr(const ObjectAddress *depender,
15541555
Node *expr, List *rtable,
15551556
DependencyType behavior)
1557+
{
1558+
ObjectAddresses *addrs;
1559+
1560+
addrs = new_object_addresses();
1561+
1562+
/* Collect all dependencies from the expression */
1563+
collectDependenciesFromExpr(addrs, expr, rtable);
1564+
1565+
/* And record 'em */
1566+
recordMultipleDependencies(depender,
1567+
addrs->refs, addrs->numrefs,
1568+
behavior);
1569+
1570+
free_object_addresses(addrs);
1571+
}
1572+
1573+
/*
1574+
* collectDependenciesFromExpr - collect expression dependencies
1575+
*
1576+
* This function analyzes an expression or query in node-tree form to find all
1577+
* the objects it refers to (tables, columns, operators, functions, etc.) and
1578+
* adds them to the provided ObjectAddresses structure. Unlike recordDependencyOnExpr,
1579+
* this function does not immediately record the dependencies, allowing the caller
1580+
* to examine, filter, or modify the collected dependencies before recording them.
1581+
*
1582+
* This is particularly useful when dependency recording needs to be conditional
1583+
* or when dependencies from multiple sources need to be merged before recording.
1584+
*/
1585+
void
1586+
collectDependenciesFromExpr(ObjectAddresses *addrs,
1587+
Node *expr, List *rtable)
15561588
{
15571589
find_expr_references_context context;
15581590

1559-
context.addrs = new_object_addresses();
1591+
context.addrs = addrs;
15601592

15611593
/* Set up interpretation for Vars at varlevelsup = 0 */
15621594
context.rtables = list_make1(rtable);
@@ -1565,14 +1597,7 @@ recordDependencyOnExpr(const ObjectAddress *depender,
15651597
find_expr_references_walker(expr, &context);
15661598

15671599
/* Remove any duplicates */
1568-
eliminate_duplicate_dependencies(context.addrs);
1569-
1570-
/* And record 'em */
1571-
recordMultipleDependencies(depender,
1572-
context.addrs->refs, context.addrs->numrefs,
1573-
behavior);
1574-
1575-
free_object_addresses(context.addrs);
1600+
eliminate_duplicate_dependencies(addrs);
15761601
}
15771602

15781603
/*
@@ -1599,10 +1624,12 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
15991624
DependencyType self_behavior,
16001625
bool reverse_self)
16011626
{
1627+
ObjectAddresses *addrs;
16021628
find_expr_references_context context;
16031629
RangeTblEntry rte = {0};
16041630

1605-
context.addrs = new_object_addresses();
1631+
addrs = new_object_addresses();
1632+
context.addrs = addrs;
16061633

16071634
/* We gin up a rather bogus rangetable list to handle Vars */
16081635
rte.type = T_RangeTblEntry;
@@ -1617,11 +1644,11 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
16171644
find_expr_references_walker(expr, &context);
16181645

16191646
/* Remove any duplicates */
1620-
eliminate_duplicate_dependencies(context.addrs);
1647+
eliminate_duplicate_dependencies(addrs);
16211648

16221649
/* Separate self-dependencies if necessary */
16231650
if ((behavior != self_behavior || reverse_self) &&
1624-
context.addrs->numrefs > 0)
1651+
addrs->numrefs > 0)
16251652
{
16261653
ObjectAddresses *self_addrs;
16271654
ObjectAddress *outobj;
@@ -1630,11 +1657,11 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
16301657

16311658
self_addrs = new_object_addresses();
16321659

1633-
outobj = context.addrs->refs;
1660+
outobj = addrs->refs;
16341661
outrefs = 0;
1635-
for (oldref = 0; oldref < context.addrs->numrefs; oldref++)
1662+
for (oldref = 0; oldref < addrs->numrefs; oldref++)
16361663
{
1637-
ObjectAddress *thisobj = context.addrs->refs + oldref;
1664+
ObjectAddress *thisobj = addrs->refs + oldref;
16381665

16391666
if (thisobj->classId == RelationRelationId &&
16401667
thisobj->objectId == relId)
@@ -1644,13 +1671,13 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
16441671
}
16451672
else
16461673
{
1647-
/* Keep it in context.addrs */
1674+
/* Keep it in addrs */
16481675
*outobj = *thisobj;
16491676
outobj++;
16501677
outrefs++;
16511678
}
16521679
}
1653-
context.addrs->numrefs = outrefs;
1680+
addrs->numrefs = outrefs;
16541681

16551682
/* Record the self-dependencies with the appropriate direction */
16561683
if (!reverse_self)
@@ -1675,10 +1702,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
16751702

16761703
/* Record the external dependencies */
16771704
recordMultipleDependencies(depender,
1678-
context.addrs->refs, context.addrs->numrefs,
1705+
addrs->refs, addrs->numrefs,
16791706
behavior);
16801707

1681-
free_object_addresses(context.addrs);
1708+
free_object_addresses(addrs);
16821709
}
16831710

16841711
/*
@@ -2462,6 +2489,87 @@ eliminate_duplicate_dependencies(ObjectAddresses *addrs)
24622489
addrs->numrefs = newrefs;
24632490
}
24642491

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+
24652573
/*
24662574
* qsort comparator for ObjectAddress items
24672575
*/

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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,17 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
114114
Node *expr, List *rtable,
115115
DependencyType behavior);
116116

117+
extern void collectDependenciesFromExpr(ObjectAddresses *addrs,
118+
Node *expr, List *rtable);
119+
117120
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
118121
Node *expr, Oid relId,
119122
DependencyType behavior,
120123
DependencyType self_behavior,
121124
bool reverse_self);
122125

126+
extern void filter_temp_objects(ObjectAddresses *addrs);
127+
123128
extern ObjectAddresses *new_object_addresses(void);
124129

125130
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

0 commit comments

Comments
 (0)