Hi,
Thanks for your comments, I appreciate them.

As I continued to deal with the topic of working with temp tables of
other sessions, I noticed something like a bug. For example
(REL_17_STABLE):
Session 1:
=# CREATE TEMP TABLE test(id int);

Session 2:
=# INSERT INTO pg_temp_0.test VALUES (1);
=# INSERT INTO pg_temp_0.test VALUES (2);

Second INSERT command will end with an error "cannot access temporary
tables of other sessions". I checked why this is happening and found
errors in several places.
So, I attach two files to this email :
1) Isolation test, that shows an error in REL_17_STABLE (iso_1.patch)
2) Patch that fixes code that mistakenly considered temporary tables
to be permanent (I will be glad to receive feedback on these fixes) +
isolation test, which shows that now any action with temp table of
other session leads to error (temp_tbl_fix.patch)

Tests look kinda ugly, but I think it's inevitable, given that we
don't know exactly what the name of the temporary schema of other
session will be.

--
Best regards,
Daniil Davydov
From 995002793b0af57b660565e6027615c083d8ce5e Mon Sep 17 00:00:00 2001
From: Daniil Davidov <davydovdaniil501@gmail.com>
Date: Mon, 28 Oct 2024 17:19:07 +0700
Subject: [PATCH] Add isolaton test for work with other session's temp table

---
 src/test/isolation/expected/temp-tables.out |  75 +++++++++++++++
 src/test/isolation/isolation_schedule       |   1 +
 src/test/isolation/specs/temp-tables.spec   | 100 ++++++++++++++++++++
 3 files changed, 176 insertions(+)
 create mode 100644 src/test/isolation/expected/temp-tables.out
 create mode 100644 src/test/isolation/specs/temp-tables.spec

diff --git a/src/test/isolation/expected/temp-tables.out b/src/test/isolation/expected/temp-tables.out
new file mode 100644
index 0000000000..dbd6ffc2e5
--- /dev/null
+++ b/src/test/isolation/expected/temp-tables.out
@@ -0,0 +1,75 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_st1 s1_st2 s2_st1 s1_st3 s2_st2
+step s1_st1: CREATE TEMP TABLE test_tmp(id int);
+step s1_st2: INSERT INTO temp_schema_id SELECT pg_my_temp_schema();
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+step s2_st1: 
+    DO $$
+        DECLARE
+            schema_name text;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Execute few insert operations. We expect that behavior to be the
+            -- same (both will complete successfully or both will fail). Since
+            -- we have RELATION_IS_OTHER_TEMP() check in PrefetchBuffer and
+            -- ReadBufferExtended functions (src/backend/storage/buffer/bufmgr.c),
+            -- let's assume that both operations must fail (this is reflected
+            -- in expected file)
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (1);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (2);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+
+step s1_st3: INSERT INTO test_tmp VALUES (3);
+s2: NOTICE:  (3)
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+step s2_st2: 
+    DO $$
+        DECLARE
+            schema_name text;
+            result RECORD;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Before this step call, first session inserted few tuples into
+            -- test_tmp table. Let's assume that SELECT result must contain all
+            -- of these tuples (based on current logic)
+            FOR result IN
+                       EXECUTE format('SELECT * FROM %I.test_tmp;', schema_name)
+                          LOOP
+                               RAISE NOTICE '%', result;
+                           END LOOP;
+
+            -- Now lets try to update or delete tuples from test_tmp. If we
+            -- cannot insert into this table, lets assume that both UPDATE and
+            -- DELETE operations must return same error as INSERT
+            BEGIN
+                EXECUTE format('UPDATE %I.test_tmp SET id = 100 WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 143109aa4d..8ed3f821a4 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -115,3 +115,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: temp-tables
\ No newline at end of file
diff --git a/src/test/isolation/specs/temp-tables.spec b/src/test/isolation/specs/temp-tables.spec
new file mode 100644
index 0000000000..e77c871c79
--- /dev/null
+++ b/src/test/isolation/specs/temp-tables.spec
@@ -0,0 +1,100 @@
+# Test access to temporary tables of other sessions
+
+# We need to know oid of first session's temporary schema
+setup { CREATE TABLE temp_schema_id(oid oid); }
+
+teardown { DROP TABLE temp_schema_id; }
+
+session s1
+
+# In first session we only need to create temporary table and "remember" its oid
+step s1_st1 { CREATE TEMP TABLE test_tmp(id int); }
+step s1_st2 { INSERT INTO temp_schema_id SELECT pg_my_temp_schema(); }
+step s1_st3 { INSERT INTO test_tmp VALUES (3); }
+
+session s2
+
+# In this DO block we will try to access to temp table created before via
+# INSERT operation
+step s2_st1
+{
+    DO $$
+        DECLARE
+            schema_name text;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Execute few insert operations. We expect that behavior to be the
+            -- same (both will complete successfully or both will fail). Since
+            -- we have RELATION_IS_OTHER_TEMP() check in PrefetchBuffer and
+            -- ReadBufferExtended functions (src/backend/storage/buffer/bufmgr.c),
+            -- let's assume that both operations must fail (this is reflected
+            -- in expected file)
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (1);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (2);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+}
+
+# In this DO block we will try to access to temp table created before via
+# SELECT/UPDATE/DELETE operations
+step s2_st2
+{
+    DO $$
+        DECLARE
+            schema_name text;
+            result RECORD;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Before this step call, first session inserted few tuples into
+            -- test_tmp table. Let's assume that SELECT result must contain all
+            -- of these tuples (based on current logic)
+            FOR result IN
+                EXECUTE format('SELECT * FROM %I.test_tmp;', schema_name) LOOP
+                    RAISE NOTICE '%', result;
+                END LOOP;
+
+            -- Now lets try to update or delete tuples from test_tmp. If we
+            -- cannot insert into this table, lets assume that both UPDATE and
+            -- DELETE operations must return same error as INSERT
+            BEGIN
+                EXECUTE format('UPDATE %I.test_tmp SET id = 100 WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            BEGIN
+                EXECUTE format('DELETE FROM %I.test_tmp WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+}
+
+permutation
+    s1_st1
+    s1_st2
+    s2_st1
+    s1_st3
+    s2_st2
\ No newline at end of file
-- 
2.43.0

From abc0da111060b4e63f4e83835859e9322c397ee4 Mon Sep 17 00:00:00 2001
From: Daniil Davidov <davydovdaniil501@gmail.com>
Date: Sun, 27 Oct 2024 16:15:50 +0700
Subject: [PATCH] Add fixes so now we cannot access to temporary tables of
 other sessions

---
 src/backend/catalog/namespace.c             |  52 ++++------
 src/backend/nodes/makefuncs.c               |   6 +-
 src/backend/parser/gram.y                   |  11 ++-
 src/test/isolation/expected/temp-tables.out |  92 +++++++++++++++++
 src/test/isolation/isolation_schedule       |   1 +
 src/test/isolation/specs/temp-tables.spec   | 103 ++++++++++++++++++++
 6 files changed, 231 insertions(+), 34 deletions(-)
 create mode 100644 src/test/isolation/expected/temp-tables.out
 create mode 100644 src/test/isolation/specs/temp-tables.spec

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 30807f9190..c06d978afc 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -499,28 +499,19 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 		 */
 		if (relation->relpersistence == RELPERSISTENCE_TEMP)
 		{
-			if (!OidIsValid(myTempNamespace))
-				relId = InvalidOid; /* this probably can't happen? */
-			else
+			if (relation->schemaname)
 			{
-				if (relation->schemaname)
-				{
-					Oid			namespaceId;
+				Oid			namespaceId;
 
-					namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
-
-					/*
-					 * For missing_ok, allow a non-existent schema name to
-					 * return InvalidOid.
-					 */
-					if (namespaceId != myTempNamespace)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-								 errmsg("temporary tables cannot specify a schema name")));
-				}
-
-				relId = get_relname_relid(relation->relname, myTempNamespace);
+				namespaceId = LookupExplicitNamespace(relation->schemaname,
+													  missing_ok);
+				if (namespaceId != myTempNamespace)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("you cannot access to temporary relations of other session")));
 			}
+
+			relId = get_relname_relid(relation->relname, myTempNamespace);
 		}
 		else if (relation->schemaname)
 		{
@@ -3387,17 +3378,14 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
 	Oid			namespaceId;
 	AclResult	aclresult;
 
-	/* check for pg_temp alias */
 	if (strcmp(nspname, "pg_temp") == 0)
 	{
-		if (OidIsValid(myTempNamespace))
-			return myTempNamespace;
-
 		/*
-		 * Since this is used only for looking up existing objects, there is
-		 * no point in trying to initialize the temp namespace here; and doing
-		 * so might create problems for some callers --- just fall through.
+		 * If namespace specified in 'pg_temp' format
+		 * (without owner proc number specification), we assume that this is
+		 * our temporary namespace
 		 */
+		return myTempNamespace;
 	}
 
 	namespaceId = get_namespace_oid(nspname, missing_ok);
@@ -3553,21 +3541,19 @@ get_namespace_oid(const char *nspname, bool missing_ok)
 RangeVar *
 makeRangeVarFromNameList(const List *names)
 {
-	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
+	RangeVar   *rel;
 
 	switch (list_length(names))
 	{
 		case 1:
-			rel->relname = strVal(linitial(names));
+			rel = makeRangeVar(NULL, strVal(linitial(names)), -1);
 			break;
 		case 2:
-			rel->schemaname = strVal(linitial(names));
-			rel->relname = strVal(lsecond(names));
+			rel = makeRangeVar(strVal(linitial(names)), strVal(lsecond(names)), -1);
 			break;
 		case 3:
+			rel = makeRangeVar(strVal(lsecond(names)), strVal(lthird(names)), -1);
 			rel->catalogname = strVal(linitial(names));
-			rel->schemaname = strVal(lsecond(names));
-			rel->relname = strVal(lthird(names));
 			break;
 		default:
 			ereport(ERROR,
@@ -3774,6 +3760,8 @@ GetTempNamespaceProcNumber(Oid namespaceId)
 		return INVALID_PROC_NUMBER; /* no such namespace? */
 	if (strncmp(nspname, "pg_temp_", 8) == 0)
 		result = atoi(nspname + 8);
+	else if (strncmp(nspname, "pg_temp", 7) == 0)
+		result = MyProcNumber;
 	else if (strncmp(nspname, "pg_toast_temp_", 14) == 0)
 		result = atoi(nspname + 14);
 	else
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 61ac172a85..e60044c9d5 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -429,10 +429,14 @@ makeRangeVar(char *schemaname, char *relname, int location)
 	r->schemaname = schemaname;
 	r->relname = relname;
 	r->inh = true;
-	r->relpersistence = RELPERSISTENCE_PERMANENT;
 	r->alias = NULL;
 	r->location = location;
 
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	return r;
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bca627c546..02c13fdd16 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -19184,7 +19184,11 @@ makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner)
 			break;
 	}
 
-	r->relpersistence = RELPERSISTENCE_PERMANENT;
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	r->location = position;
 
 	return r;
@@ -19224,6 +19228,11 @@ makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
 			break;
 	}
 
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	return r;
 }
 
diff --git a/src/test/isolation/expected/temp-tables.out b/src/test/isolation/expected/temp-tables.out
new file mode 100644
index 0000000000..328e7d34b9
--- /dev/null
+++ b/src/test/isolation/expected/temp-tables.out
@@ -0,0 +1,92 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_st1 s1_st2 s2_st1
+step s1_st1: CREATE TEMP TABLE test_tmp(id int);
+step s1_st2: INSERT INTO temp_schema_id SELECT pg_my_temp_schema();
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+s2: NOTICE:  cannot access temporary tables of other sessions
+step s2_st1: 
+    DO $$
+        DECLARE
+            schema_name text;
+            result      RECORD;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Both INSERT operation must fail due to 'cannot access temporary
+            -- tables of other sessions' error
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (1);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (2);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- SELECT operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                FOR result IN
+                    EXECUTE format('SELECT * FROM %I.test_tmp;', schema_name) LOOP
+                        RAISE NOTICE '%', result;
+                    END LOOP;
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- UPDATE operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('UPDATE %I.test_tmp SET id = 100 WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- DELETE operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('DELETE FROM %I.test_tmp WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- LOCK operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('BEGIN;
+                                LOCK TABLE %I.test_tmp;
+                                COMMIT;',
+                                schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- DROP operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('DROP TABLE %I.test_tmp;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 143109aa4d..8ed3f821a4 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -115,3 +115,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: temp-tables
\ No newline at end of file
diff --git a/src/test/isolation/specs/temp-tables.spec b/src/test/isolation/specs/temp-tables.spec
new file mode 100644
index 0000000000..009bd9845b
--- /dev/null
+++ b/src/test/isolation/specs/temp-tables.spec
@@ -0,0 +1,103 @@
+# Test access to temporary tables of other sessions
+
+# We need to know oid of first session's temporary schema
+setup { CREATE TABLE temp_schema_id(oid oid); }
+
+teardown { DROP TABLE temp_schema_id; }
+
+session s1
+
+# In first session we only need to create temporary table and "remember" its oid
+step s1_st1 { CREATE TEMP TABLE test_tmp(id int); }
+step s1_st2 { INSERT INTO temp_schema_id SELECT pg_my_temp_schema(); }
+
+session s2
+
+# In this DO block we will try to access to temp table created before via
+# INSERT/UPDATE/DELETE/SELECT/TRUNCATE/LOCK operations
+step s2_st1
+{
+    DO $$
+        DECLARE
+            schema_name text;
+            result      RECORD;
+        BEGIN
+            -- Find out name of temporary schema of first session
+            SELECT nspname INTO schema_name
+              FROM pg_namespace
+             WHERE oid = (SELECT oid FROM temp_schema_id LIMIT 1);
+
+            -- Both INSERT operation must fail due to 'cannot access temporary
+            -- tables of other sessions' error
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (1);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            BEGIN
+                EXECUTE format('INSERT INTO %I.test_tmp VALUES (2);', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- SELECT operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                FOR result IN
+                    EXECUTE format('SELECT * FROM %I.test_tmp;', schema_name) LOOP
+                        RAISE NOTICE '%', result;
+                    END LOOP;
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- UPDATE operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('UPDATE %I.test_tmp SET id = 100 WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- DELETE operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('DELETE FROM %I.test_tmp WHERE id = 3;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- LOCK operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('BEGIN;
+                                LOCK TABLE %I.test_tmp;
+                                COMMIT;',
+                                schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+
+            -- DROP operation must fail due to 'cannot access temporary tables
+            -- of other sessions' error
+            BEGIN
+                EXECUTE format('DROP TABLE %I.test_tmp;', schema_name);
+            EXCEPTION
+                WHEN feature_not_supported
+                THEN RAISE NOTICE 'cannot access temporary tables of other sessions';
+            END;
+        END
+    $$;
+}
+
+permutation
+    s1_st1
+    s1_st2
+    s2_st1
\ No newline at end of file
-- 
2.43.0

Reply via email to