Hi,

On Sun, May 3, 2026 at 7:49 PM Jim Jones <[email protected]> wrote:
>
> On 03/05/2026 10:53, Daniil Davydov wrote:
> > Please, see the attached patch that ensures that cross-session LOCK TABLE 
> > works
> > properly.
>
> I guess you should either add it to the 0001 sent by Alexander, or
> create a 0003. Right now the cfbot is complaining, as
> 013_temp_obj_multisession.pl still does not exist :)
>

OK, I'll attach this patch as a third one, just for review purposes. After
agreement on its content, it should be included into the 0001 and 0002 patches.

--
Best regards,
Daniil Davydov
From 2ec45b8ec3a0eb56ed1f2d10a0cb89ef57a8f402 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:23:42 +0300
Subject: [PATCH v21 2/3] Prevent access to other sessions' temp tables

Commit b7b0f3f2724 ("Use streaming I/O in sequential scans") routed
sequential scans through read_stream_next_buffer(), bypassing the
RELATION_IS_OTHER_TEMP() check in ReadBufferExtended().  As a result,
a superuser can attempt to read or modify temp tables of other
sessions through the read-stream path.  When no index is present so
the planner cannot pick an index scan, SELECT/UPDATE/DELETE/MERGE
silently see no rows / report zero affected rows, and COPY produces
an empty output -- because the buffer manager has no visibility into
the owning session's local buffers and silently returns nothing.
INSERT and any path that goes through nbtree (i.e. an index scan)
still error out via the existing check in ReadBufferExtended(), which
is reached from hio.c and nbtree respectively, but this is incidental.

Fix by enforcing RELATION_IS_OTHER_TEMP() at three additional
buffer-manager entry points:

- read_stream_begin_impl() rejects the read at stream setup time,
  covering sequential and bitmap scans that go through the
  read-stream path.
- ReadBuffer_common() becomes the canonical place for the check,
  consolidating the existing one previously kept in
  ReadBufferExtended().  All ReadBufferExtended() callers go through
  ReadBuffer_common(), so the consolidation is behaviour-preserving.
- StartReadBuffersImpl() catches direct callers of StartReadBuffers()
  that bypass both of the above.  This is currently defense-in-depth
  but documents the contract for future code.

The companion test in src/test/modules/test_misc was added in the
preceding commit; this commit updates the assertions for SELECT,
UPDATE, DELETE, MERGE and COPY (which previously documented the
bug as silent success) to expect the new error.

Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
Backpatch-through: 17
---
 src/backend/storage/aio/read_stream.c         | 10 ++++++
 src/backend/storage/buffer/bufmgr.c           | 33 ++++++++++-------
 .../test_misc/t/013_temp_obj_multisession.pl  | 35 ++++++++++---------
 3 files changed, 50 insertions(+), 28 deletions(-)

diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 2374b4cd507..a318539e56c 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -776,6 +776,16 @@ read_stream_begin_impl(int flags,
 	uint32		max_possible_buffer_limit;
 	Oid			tablespace_id;
 
+	/*
+	 * Reject attempts to read non-local temporary relations; we would be
+	 * likely to get wrong data since we have no visibility into the owning
+	 * session's local buffers.
+	 */
+	if (rel && RELATION_IS_OTHER_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary tables of other sessions")));
+
 	/*
 	 * Decide how many I/Os we will allow to run at the same time.  This
 	 * number also affects how far we look ahead for opportunities to start
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1878efb4aa9..770209d606c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -791,7 +791,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
 
 	if (RelationUsesLocalBuffers(reln))
 	{
-		/* see comments in ReadBufferExtended */
+		/* see comments in ReadBuffer_common */
 		if (RELATION_IS_OTHER_TEMP(reln))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -928,19 +928,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 {
 	Buffer		buf;
 
-	/*
-	 * Reject attempts to read non-local temporary relations; we would be
-	 * likely to get wrong data since we have no visibility into the owning
-	 * session's local buffers.
-	 */
-	if (RELATION_IS_OTHER_TEMP(reln))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot access temporary tables of other sessions")));
-
 	/*
 	 * Read the buffer, and update pgstat counters to reflect a cache hit or
-	 * miss.
+	 * miss.  The other-session temp-relation check is enforced by
+	 * ReadBuffer_common().
 	 */
 	buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
 							forkNum, blockNum, mode, strategy);
@@ -1292,6 +1283,18 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 	int			flags;
 	char		persistence;
 
+	/*
+	 * Reject attempts to read non-local temporary relations; we would be
+	 * likely to get wrong data since we have no visibility into the owning
+	 * session's local buffers.  This is the canonical place for the check,
+	 * covering the ReadBufferExtended() entry point and any other caller
+	 * that supplies a Relation.
+	 */
+	if (rel && RELATION_IS_OTHER_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary tables of other sessions")));
+
 	/*
 	 * Backward compatibility path, most code should use ExtendBufferedRel()
 	 * instead, as acquiring the extension lock inside ExtendBufferedRel()
@@ -1382,6 +1385,12 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 	Assert(*nblocks > 0);
 	Assert(*nblocks <= MAX_IO_COMBINE_LIMIT);
 
+	/* see comments in ReadBuffer_common */
+	if (operation->rel && RELATION_IS_OTHER_TEMP(operation->rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary tables of other sessions")));
+
 	if (operation->persistence == RELPERSISTENCE_TEMP)
 	{
 		io_context = IOCONTEXT_NORMAL;
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index 0d211700977..b4442836bef 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -55,25 +55,20 @@ my ($stdout, $stderr);
 # DML and SELECT have to read the table's data and therefore go through
 # the buffer manager.  With no index on the table, the planner cannot
 # use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
-# the read-stream path.
-#
-# XXX: in current code, the read-stream path bypasses the
-# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
-# rows / report zero affected rows -- the visible symptom of the bug
-# this test suite documents.  A follow-up patch will route the check
-# through read_stream_begin_impl() and these assertions will be
-# updated to expect "cannot access temporary tables of other sessions".
+# the read-stream path and are caught by read_stream_begin_impl().
 
 $node->psql(
 	'postgres',
 	"SELECT val FROM $tempschema.foo;",
-	stdout => \$stdout,
 	stderr => \$stderr);
-is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+like(
+	$stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'SELECT (seqscan via read_stream)');
 
 # INSERT goes through hio.c which calls ReadBufferExtended() to find a
-# page with free space; that hits the existing check before any data is
-# written.  This case currently errors as expected.
+# page with free space; that hits the existing check before any data
+# is written.
 $node->psql(
 	'postgres',
 	"INSERT INTO $tempschema.foo VALUES (73);",
@@ -86,21 +81,29 @@ $node->psql(
 	'postgres',
 	"UPDATE $tempschema.foo SET val = NULL;",
 	stderr => \$stderr);
-is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+like($stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'UPDATE');
 
 $node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
-is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+like($stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'DELETE');
 
 $node->psql(
 	'postgres',
 	"MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
 	  . "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
 	stderr => \$stderr);
-is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+like($stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'MERGE');
 
 $node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
 	stderr => \$stderr);
-is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+like($stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'COPY');
 
 # DDL and maintenance commands have their own command-specific checks
 # (older than the buffer-manager check above), so they fail with
-- 
2.43.0

From 05aaab6a225195246e70ad465a0995c4c0437e23 Mon Sep 17 00:00:00 2001
From: Daniil Davidov <[email protected]>
Date: Sun, 3 May 2026 15:51:06 +0700
Subject: [PATCH v21 3/3] Test cross-session LOCK TABLE scenario

---
 .../test_misc/t/013_temp_obj_multisession.pl  | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index b4442836bef..a7369700580 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -220,14 +220,39 @@ like(
 	qr/cannot drop table .*\.foo2 because other objects depend on it/,
 	'DROP TABLE blocked by cross-session dependency');
 
+my $foo2_oid = $node->safe_psql('postgres',
+	"SELECT oid FROM pg_class WHERE relname='foo2';");
+
+# Cross-session LOCK TABLE scenario.  Ensure that LockRelationOid is working
+# properly for other temp tables since this mechanism is also used by
+# autovacuum during orphaned tables cleanup.
+my $psql2 = $node->background_psql('postgres');
+$psql2->query_safe(
+	qq{
+	BEGIN;
+	LOCK TABLE $tempschema.foo2 IN ACCESS SHARE MODE;
+});
+
 # When the owner session ends, its temp objects are dropped via the
 # normal session-exit cleanup, which cascades through
 # DEPENDENCY_NORMAL and also removes the cross-session function that
 # depended on the temp row type.  This is the same mechanism
 # autovacuum relies on to clean up temp relations left behind by a
 # crashed backend.
+# Access share lock on the foo2 will block session-exit cleanup, because an
+# owner will try to acquire deletion lock all its temp objects via
+# findDependentObjects.
+my $log_offset = -s $node->logfile;
 $psql1->quit;
 
+# Check whether session-exit cleanup is blocked.
+$node->wait_for_log(qr/waiting for AccessExclusiveLock on relation $foo2_oid/,
+	$log_offset);
+
+# Release lock on foo2 and allow session-exit cleanup to finish.
+$psql2->query_safe(q(COMMIT;));
+$psql2->quit;
+
 $node->poll_query_until(
 	'postgres',
 	"SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
-- 
2.43.0

From e3739ee30d6dea0630d56a45e109b0d762cda333 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:22:26 +0300
Subject: [PATCH v21 1/3] Add tests for cross-session temp table access

Add a TAP test in src/test/modules/test_misc that documents what
happens when one session attempts to read or modify another session's
temporary table.  This commit only adds tests; it does not change
backend behaviour, so the assertions reflect current behaviour:

- SELECT, UPDATE, DELETE, MERGE, COPY on a table without an index
  silently succeed with no error and zero rows / zero affected rows.
  These commands run through the read-stream path, which currently
  bypasses the RELATION_IS_OTHER_TEMP() check.  This is the
  underlying bug to be fixed in a follow-up.
- INSERT errors with "cannot access temporary tables of other
  sessions" because hio.c calls ReadBufferExtended() to find a page
  with free space and is caught by the existing check there.
- Index scan errors via the same existing check, reached through
  nbtree -> ReadBuffer -> ReadBufferExtended.
- TRUNCATE / ALTER TABLE / ALTER INDEX / CLUSTER fail with their
  command-specific error messages.
- VACUUM is silently skipped to avoid noise during database-wide
  VACUUM (vacuum_rel() returns without warning).
- DROP TABLE is intentionally allowed: DROP does not touch the
  table's contents, and autovacuum relies on this to clean up
  temp relations orphaned by a crashed backend.
- ALTER FUNCTION / DROP FUNCTION on an owner-created function over
  its own temp row type work as catalog operations -- they don't
  read the underlying data.
- CREATE FUNCTION from a separate session, using another session's
  temp row type as an argument, is allowed but emits a NOTICE: the
  function is moved into the creator's pg_temp namespace with an
  auto-dependency on the borrowed type, so it disappears together
  with the session that created it.
- A bare DROP TABLE on a temp table that has a cross-session
  dependent function fails with a catalog-level dependency error.
- When the owner session ends, the normal session-exit cleanup
  cascades through DEPENDENCY_NORMAL and removes both the temp
  objects and any cross-session functions that depended on them.

Also document the contract for RELATION_IS_OTHER_TEMP() so that
future buffer-access entry points enforce the same rule.

Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
---
 src/include/utils/rel.h                       |   9 +
 src/test/modules/test_misc/meson.build        |   1 +
 .../test_misc/t/013_temp_obj_multisession.pl  | 235 ++++++++++++++++++
 3 files changed, 245 insertions(+)
 create mode 100644 src/test/modules/test_misc/t/013_temp_obj_multisession.pl

diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cd1e92f2302..ad50e43b801 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -664,6 +664,15 @@ RelationCloseSmgr(Relation relation)
  * RELATION_IS_OTHER_TEMP
  *		Test for a temporary relation that belongs to some other session.
  *
+ * Any code path that reads a relation's data must reject such relations:
+ * the owning session keeps the data in its private local buffer pool,
+ * which we cannot inspect.  Existing buffer-manager entry points
+ * (ReadBufferExtended(), ReadBuffer_common(), StartReadBuffersImpl(),
+ * read_stream_begin_impl(), PrefetchBuffer()) already enforce this; any
+ * new buffer-access entry point must do the same.  Command-level code
+ * (TRUNCATE, ALTER TABLE, VACUUM, CLUSTER, REINDEX, ...) additionally
+ * uses this macro for command-specific error messages.
+ *
  * Beware of multiple eval of argument
  */
 #define RELATION_IS_OTHER_TEMP(relation) \
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 356d8454b39..969e90b396d 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -21,6 +21,7 @@ tests += {
       't/010_index_concurrently_upsert.pl',
       't/011_lock_stats.pl',
       't/012_ddlutils.pl',
+      't/013_temp_obj_multisession.pl',
     ],
     # The injection points are cluster-wide, so disable installcheck
     'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
new file mode 100644
index 00000000000..0d211700977
--- /dev/null
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -0,0 +1,235 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Tests that one session cannot read or modify data in another session's
+# temporary table.  Each session keeps its temp data in its own local
+# buffer pool, and a different backend has no visibility into those
+# buffers, so any command that needs to look at the data must be
+# rejected.
+#
+# DROP TABLE is intentionally allowed: it does not touch the table's
+# contents, and autovacuum relies on this to clean up orphaned temp
+# relations left behind by a crashed backend.
+#
+# A regression caught here typically means a new buffer-access entry
+# point bypasses the RELATION_IS_OTHER_TEMP() check.  See
+# ReadBuffer_common(), StartReadBuffersImpl(), and read_stream_begin_impl()
+# for the existing checks.  When adding a new command or buffer-access
+# path, also add a corresponding case below.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::BackgroundPsql;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('temp_lock');
+$node->init;
+$node->start;
+
+# Owner session.  Created via background_psql so it stays alive while
+# the second session probes its temp objects.
+my $psql1 = $node->background_psql('postgres');
+
+# Initially create the table without an index, so read paths go straight
+# through the read-stream / buffer-manager entry points without being
+# masked by an index scan that would hit ReadBuffer_common from nbtree.
+$psql1->query_safe(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;));
+
+# Resolve the owner's temp schema so the probing session can refer to
+# the table by a fully-qualified name.
+my $tempschema = $node->safe_psql(
+	'postgres',
+	q{
+      SELECT n.nspname
+      FROM pg_class c
+      JOIN pg_namespace n ON n.oid = c.relnamespace
+      WHERE relname = 'foo' AND relpersistence = 't';
+    }
+);
+chomp $tempschema;
+ok($tempschema =~ /^pg_temp_\d+$/, "got temp schema: $tempschema");
+
+my ($stdout, $stderr);
+
+# DML and SELECT have to read the table's data and therefore go through
+# the buffer manager.  With no index on the table, the planner cannot
+# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
+# the read-stream path.
+#
+# XXX: in current code, the read-stream path bypasses the
+# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
+# rows / report zero affected rows -- the visible symptom of the bug
+# this test suite documents.  A follow-up patch will route the check
+# through read_stream_begin_impl() and these assertions will be
+# updated to expect "cannot access temporary tables of other sessions".
+
+$node->psql(
+	'postgres',
+	"SELECT val FROM $tempschema.foo;",
+	stdout => \$stdout,
+	stderr => \$stderr);
+is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+
+# INSERT goes through hio.c which calls ReadBufferExtended() to find a
+# page with free space; that hits the existing check before any data is
+# written.  This case currently errors as expected.
+$node->psql(
+	'postgres',
+	"INSERT INTO $tempschema.foo VALUES (73);",
+	stderr => \$stderr);
+like($stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'INSERT (caught via hio.c)');
+
+$node->psql(
+	'postgres',
+	"UPDATE $tempschema.foo SET val = NULL;",
+	stderr => \$stderr);
+is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+
+$node->psql(
+	'postgres',
+	"MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
+	  . "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
+	stderr => \$stderr);
+is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
+	stderr => \$stderr);
+is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+
+# DDL and maintenance commands have their own command-specific checks
+# (older than the buffer-manager check above), so they fail with
+# command-specific error messages.  Verifying them here documents the
+# expected behaviour and guards against accidental removal of those
+# checks.
+
+$node->psql('postgres', "TRUNCATE TABLE $tempschema.foo;",
+	stderr => \$stderr);
+like($stderr,
+	qr/cannot truncate temporary tables of other sessions/,
+	'TRUNCATE');
+
+$node->psql(
+	'postgres',
+	"ALTER TABLE $tempschema.foo ALTER COLUMN val TYPE bigint;",
+	stderr => \$stderr);
+like($stderr,
+	qr/cannot alter temporary tables of other sessions/,
+	'ALTER TABLE');
+
+# VACUUM silently skips other sessions' temp tables (vacuum_rel() returns
+# without warning to avoid noise during database-wide VACUUM).  Verify
+# that no error is reported, and that no buffer-access path is hit.
+$node->psql('postgres', "VACUUM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'VACUUM is silently skipped');
+
+$node->psql('postgres', "CLUSTER $tempschema.foo;", stderr => \$stderr);
+like($stderr,
+	qr/cannot execute CLUSTER on temporary tables of other sessions/,
+	'CLUSTER');
+
+# Now create an index to exercise the index-scan path.  nbtree calls
+# ReadBuffer (which is ReadBufferExtended -> ReadBuffer_common), so
+# this exercises a different chain of buffer-manager entry points.
+$psql1->query_safe(q(CREATE INDEX ON foo(val);));
+
+$node->psql(
+	'postgres',
+	"SET enable_seqscan = off; SELECT val FROM $tempschema.foo WHERE val = 42;",
+	stderr => \$stderr);
+like(
+	$stderr,
+	qr/cannot access temporary tables of other sessions/,
+	'index scan (ReadBuffer_common via nbtree)');
+
+# ALTER INDEX goes through the same CheckAlterTableIsSafe() path as
+# ALTER TABLE, so it produces the same error.
+$node->psql(
+	'postgres',
+	"ALTER INDEX $tempschema.foo_val_idx SET (fillfactor = 50);",
+	stderr => \$stderr);
+like($stderr,
+	qr/cannot alter temporary tables of other sessions/,
+	'ALTER INDEX');
+
+# A function created by the owner in its own pg_temp using its own
+# row type can be observed via the catalog by a separate session.
+# ALTER FUNCTION and DROP FUNCTION on it must work as catalog
+# operations -- they don't read the underlying table -- which
+# documents the boundary between catalog and data access for temp
+# objects.
+$psql1->query_safe(
+	q[CREATE FUNCTION pg_temp.foo_id(r foo) RETURNS int LANGUAGE SQL ]
+	  . q[AS 'SELECT r.val';]);
+
+$node->psql(
+	'postgres',
+	"ALTER FUNCTION $tempschema.foo_id($tempschema.foo) "
+	  . "SET search_path = pg_catalog;",
+	stderr => \$stderr);
+is($stderr, '', 'ALTER FUNCTION on function over other session\'s row type');
+
+$node->psql(
+	'postgres',
+	"DROP FUNCTION $tempschema.foo_id($tempschema.foo);",
+	stderr => \$stderr);
+is($stderr, '', 'DROP FUNCTION on function over other session\'s row type');
+
+# DROP TABLE on another session's temp table is intentionally permitted.
+# DROP doesn't touch the table's contents, and autovacuum relies on this
+# to remove temp relations orphaned by a crashed backend.  Verify that
+# the bare DROP succeeds without error.
+$node->psql('postgres', "DROP TABLE $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DROP TABLE is allowed');
+
+# Cross-session CREATE FUNCTION scenario.  The owner creates a fresh
+# temp table foo2 in its pg_temp namespace, and a separate session
+# then creates a function whose argument type is that row type.
+# PostgreSQL allows this and emits a NOTICE: the function is moved
+# into the creator's pg_temp namespace with an auto-dependency on
+# the borrowed type, so it disappears together with the session that
+# created it.
+$psql1->query_safe(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;));
+
+$node->psql(
+	'postgres',
+	"CREATE FUNCTION public.cross_session_func(r $tempschema.foo2) "
+	  . "RETURNS int LANGUAGE SQL AS 'SELECT 1';",
+	stderr => \$stderr);
+like(
+	$stderr,
+	qr/function "cross_session_func" will be effectively temporary/,
+	'CREATE FUNCTION using other session\'s row type is effectively temporary'
+);
+
+# A bare DROP TABLE on foo2 now fails because cross_session_func
+# depends on its row type.  This is normal SQL dependency behaviour
+# and documents that DROP itself is not blocked by buffer-manager
+# checks -- we get a catalog-level error instead.
+$node->psql('postgres', "DROP TABLE $tempschema.foo2;", stderr => \$stderr);
+like(
+	$stderr,
+	qr/cannot drop table .*\.foo2 because other objects depend on it/,
+	'DROP TABLE blocked by cross-session dependency');
+
+# When the owner session ends, its temp objects are dropped via the
+# normal session-exit cleanup, which cascades through
+# DEPENDENCY_NORMAL and also removes the cross-session function that
+# depended on the temp row type.  This is the same mechanism
+# autovacuum relies on to clean up temp relations left behind by a
+# crashed backend.
+$psql1->quit;
+
+$node->poll_query_until(
+	'postgres',
+	"SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
+) or die "cross_session_func was not cleaned up after owner session exit";
+
+ok(1, 'cross_session_func cleaned up when owner session ends');
+
+done_testing();
-- 
2.43.0

Reply via email to