From 5ee5a218956f3ec8fd37719be4e15ec3e605e518 Mon Sep 17 00:00:00 2001
From: AyoubKAZ <kazarayoub2004@gmail.com>
Date: Fri, 13 Nov 2026 14:03:35 +0100
Subject: [PATCH] Fix CREATE SCHEMA lookup names resolving in pg_temp.

CreateSchemaCommand() put the new schema ahead of the old search path,
but finalNamespacePath() still prepended pg_temp.  Lookup names in schema
elements, such as column types and foreign key references, could therefore
resolve to temporary objects instead of objects in the new schema or the
user's path.

Append pg_temp to the temporary search_path so it remains last, while
preserving an explicit pg_temp in the old path.

Add regression tests for type and foreign key lookups.
---
 src/backend/commands/schemacmds.c           |  7 ++-
 src/test/regress/expected/create_schema.out | 48 +++++++++++++++++++++
 src/test/regress/sql/create_schema.sql      | 35 +++++++++++++++
 3 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index b016983f904..daeb2700ba2 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -156,7 +156,10 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 	CommandCounterIncrement();
 
 	/*
-	 * Prepend the new schema to the current search path.
+	 * Prepend the new schema to the current search path, making it the
+	 * default creation target namespace.  Append pg_temp so that it is not
+	 * implicitly prepended ahead of the new schema.  If the old path already
+	 * lists pg_temp, its explicit position is preserved.
 	 *
 	 * We use the equivalent of a function SET option to allow the setting to
 	 * persist for exactly the duration of the schema creation.  guc.c also
@@ -173,6 +176,8 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 	if (*nsp != '\0')
 		appendStringInfo(&pathbuf, ", %s", nsp);
 
+	appendStringInfoString(&pathbuf, ", pg_temp");
+
 	(void) set_config_option("search_path", pathbuf.data,
 							 PGC_USERSET, PGC_S_SESSION,
 							 GUC_ACTION_SAVE, true, 0, false);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 7a9b71f97b3..839e3755f80 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -170,5 +170,53 @@ drop cascades to table regress_schema_fk.t3
 drop cascades to table regress_schema_fk.t4
 drop cascades to table regress_schema_fk.t5
 drop cascades to table regress_schema_fk.t6
+-- Test that pg_temp does not implicitly shadow objects in CREATE SCHEMA
+CREATE TYPE public.regress_temp_shadow_type AS (x int);
+CREATE TYPE pg_temp.regress_temp_shadow_type AS (y text);
+CREATE TABLE pg_temp.regress_temp_parent (id int PRIMARY KEY);
+-- The temp table and the table created in the new schema have the same
+-- name, so the FK must resolve to the new schema's table.
+CREATE SCHEMA regress_schema_temp
+    CREATE TABLE t (a regress_temp_shadow_type)
+    CREATE TABLE regress_temp_parent (id int PRIMARY KEY)
+    CREATE TABLE child (p_id int REFERENCES regress_temp_parent(id));
+SELECT a.attname, t.typname, t.typnamespace::regnamespace
+FROM pg_attribute a JOIN pg_type t ON a.atttypid = t.oid
+WHERE a.attrelid = 'regress_schema_temp.t'::regclass AND a.attnum > 0;
+ attname |         typname          | typnamespace 
+---------+--------------------------+--------------
+ a       | regress_temp_shadow_type | public
+(1 row)
+
+\d regress_schema_temp.child
+         Table "regress_schema_temp.child"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ p_id   | integer |           |          | 
+Foreign-key constraints:
+    "child_p_id_fkey" FOREIGN KEY (p_id) REFERENCES regress_schema_temp.regress_temp_parent(id)
+
+-- An explicit pg_temp in the old path is honored
+SET search_path = pg_temp, public;
+CREATE SCHEMA regress_schema_temp2
+    CREATE TABLE t (a regress_temp_shadow_type);
+RESET search_path;
+SELECT a.attname, t.typname,
+       t.typnamespace = pg_my_temp_schema() AS is_temp
+FROM pg_attribute a JOIN pg_type t ON a.atttypid = t.oid
+WHERE a.attrelid = 'regress_schema_temp2.t'::regclass AND a.attnum > 0;
+ attname |         typname          | is_temp 
+---------+--------------------------+---------
+ a       | regress_temp_shadow_type | t
+(1 row)
+
+DROP SCHEMA regress_schema_temp CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table regress_schema_temp.t
+drop cascades to table regress_schema_temp.regress_temp_parent
+drop cascades to table regress_schema_temp.child
+DROP SCHEMA regress_schema_temp2 CASCADE;
+NOTICE:  drop cascades to table regress_schema_temp2.t
+DROP TYPE public.regress_temp_shadow_type CASCADE;
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 57014ac4930..45663021273 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -93,5 +93,40 @@ CREATE SCHEMA regress_schema_fk
 
 DROP SCHEMA regress_schema_fk CASCADE;
 
+-- Test that pg_temp does not implicitly shadow objects in CREATE SCHEMA
+CREATE TYPE public.regress_temp_shadow_type AS (x int);
+CREATE TYPE pg_temp.regress_temp_shadow_type AS (y text);
+CREATE TABLE pg_temp.regress_temp_parent (id int PRIMARY KEY);
+
+-- The temp table and the table created in the new schema have the same
+-- name, so the FK must resolve to the new schema's table.
+CREATE SCHEMA regress_schema_temp
+    CREATE TABLE t (a regress_temp_shadow_type)
+    CREATE TABLE regress_temp_parent (id int PRIMARY KEY)
+    CREATE TABLE child (p_id int REFERENCES regress_temp_parent(id));
+
+SELECT a.attname, t.typname, t.typnamespace::regnamespace
+FROM pg_attribute a JOIN pg_type t ON a.atttypid = t.oid
+WHERE a.attrelid = 'regress_schema_temp.t'::regclass AND a.attnum > 0;
+
+\d regress_schema_temp.child
+
+-- An explicit pg_temp in the old path is honored
+SET search_path = pg_temp, public;
+
+CREATE SCHEMA regress_schema_temp2
+    CREATE TABLE t (a regress_temp_shadow_type);
+
+RESET search_path;
+
+SELECT a.attname, t.typname,
+       t.typnamespace = pg_my_temp_schema() AS is_temp
+FROM pg_attribute a JOIN pg_type t ON a.atttypid = t.oid
+WHERE a.attrelid = 'regress_schema_temp2.t'::regclass AND a.attnum > 0;
+
+DROP SCHEMA regress_schema_temp CASCADE;
+DROP SCHEMA regress_schema_temp2 CASCADE;
+DROP TYPE public.regress_temp_shadow_type CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1

