On 2016/07/15 11:48, Tom Lane wrote:
Etsuro Fujita <fujita.ets...@lab.ntt.co.jp> writes:
One thing I'm not sure about is: should we insist that a join can be
pushed down only if the checkAsUser fields of the relevant RTEs are
equal in the case where user mappings are meaningless to the FDW, like
file_fdw?

If we add a mechanism to let us know that the FDW doesn't care, we could
relax the requirement for such cases.  I don't have a strong opinion on
whether that's worthwhile.  It'd depend in part on how many FDWs there
are that don't care, versus those that do; and I have no idea about that.

So, I'd vote for leaving that for future work if necessary.

Here is a patch for that redesign proposed by you; reverts commits fbe5a3fb73102c2cfec11aaaa4a67943f4474383 and 5d4171d1c70edfe3e9be1de9e66603af28e3afe1, adds changes for that redesign to the core, and adjusts the postgres_fdw code to that changes. Also, I rearranged the postgres_fdw regression tests to match that changes.

Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2053,2207 **** SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM
     1
  (10 rows)
  
- -- create another user for permission, user mapping, effective user tests
- CREATE USER view_owner;
- -- grant privileges on ft4 and ft5 to view_owner
- GRANT ALL ON ft4 TO view_owner;
- GRANT ALL ON ft5 TO view_owner;
- -- prepare statement with current session user
- PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
- EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
-                                                                             QUERY PLAN                                                                             
- -------------------------------------------------------------------------------------------------------------------------------------------------------------------
-  Limit
-    Output: t1.c1, t2.c1
-    ->  Foreign Scan
-          Output: t1.c1, t2.c1
-          Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2)
-          Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST
- (6 rows)
- 
- EXECUTE join_stmt;
-  c1 | c1 
- ----+----
-  22 |   
-  24 | 24
-  26 |   
-  28 |   
-  30 | 30
-  32 |   
-  34 |   
-  36 | 36
-  38 |   
-  40 |   
- (10 rows)
- 
- -- change the session user to view_owner and execute the statement. Because of
- -- change in session user, the plan should get invalidated and created again.
- -- The join will not be pushed down since the joining relations do not have a
- -- valid user mapping.
- SET SESSION ROLE view_owner;
- EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
-                             QUERY PLAN                            
- ------------------------------------------------------------------
-  Limit
-    Output: t1.c1, t2.c1
-    ->  Sort
-          Output: t1.c1, t2.c1
-          Sort Key: t1.c1, t2.c1
-          ->  Hash Left Join
-                Output: t1.c1, t2.c1
-                Hash Cond: (t1.c1 = t2.c1)
-                ->  Foreign Scan on public.ft4 t1
-                      Output: t1.c1, t1.c2, t1.c3
-                      Remote SQL: SELECT c1 FROM "S 1"."T 3"
-                ->  Hash
-                      Output: t2.c1
-                      ->  Foreign Scan on public.ft5 t2
-                            Output: t2.c1
-                            Remote SQL: SELECT c1 FROM "S 1"."T 4"
- (16 rows)
- 
- RESET ROLE;
- DEALLOCATE join_stmt;
- CREATE VIEW v_ft5 AS SELECT * FROM ft5;
- -- change owner of v_ft5 to view_owner so that the effective user for scan on
- -- ft5 is view_owner and not the current user.
- ALTER VIEW v_ft5 OWNER TO view_owner;
- -- create a public user mapping for loopback server
- -- drop user mapping for current_user.
- DROP USER MAPPING FOR CURRENT_USER SERVER loopback;
- CREATE USER MAPPING FOR PUBLIC SERVER loopback;
- -- different effective user for permission check, but same user mapping for the
- -- joining sides, join pushed down, no result expected.
- PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN v_ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
- EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
-                                                                   QUERY PLAN                                                                  
- ----------------------------------------------------------------------------------------------------------------------------------------------
-  Limit
-    Output: t1.c1, ft5.c1
-    ->  Foreign Scan
-          Output: t1.c1, ft5.c1
-          Relations: (public.ft5 t1) INNER JOIN (public.ft5)
-          Remote SQL: SELECT r1.c1, r6.c1 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 4" r6 ON (((r1.c1 = r6.c1)))) ORDER BY r1.c1 ASC NULLS LAST
- (6 rows)
- 
- EXECUTE join_stmt;
-  c1 | c1 
- ----+----
- (0 rows)
- 
- -- create user mapping for view_owner and execute the prepared statement
- -- the join should not be pushed down since joining relations now use two
- -- different user mappings
- CREATE USER MAPPING FOR view_owner SERVER loopback;
- EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
-                                       QUERY PLAN                                       
- ---------------------------------------------------------------------------------------
-  Limit
-    Output: t1.c1, ft5.c1
-    ->  Merge Join
-          Output: t1.c1, ft5.c1
-          Merge Cond: (t1.c1 = ft5.c1)
-          ->  Foreign Scan on public.ft5 t1
-                Output: t1.c1, t1.c2, t1.c3
-                Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
-          ->  Materialize
-                Output: ft5.c1, ft5.c2, ft5.c3
-                ->  Foreign Scan on public.ft5
-                      Output: ft5.c1, ft5.c2, ft5.c3
-                      Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
- (13 rows)
- 
- EXECUTE join_stmt;
-  c1 | c1 
- ----+----
- (0 rows)
- 
- -- If a sub-join can't be pushed down, upper level join shouldn't be either.
- EXPLAIN (COSTS false, VERBOSE)
- SELECT t1.c1, t2.c1 FROM (ft5 t1 JOIN v_ft5 t2 ON (t1.c1 = t2.c1)) left join (ft5 t3 JOIN v_ft5 t4 ON (t3.c1 = t4.c1)) ON (t1.c1 = t3.c1);
-                             QUERY PLAN                            
- ------------------------------------------------------------------
-  Hash Join
-    Output: t1.c1, ft5.c1
-    Hash Cond: (t1.c1 = ft5.c1)
-    ->  Hash Right Join
-          Output: t1.c1
-          Hash Cond: (t3.c1 = t1.c1)
-          ->  Hash Join
-                Output: t3.c1
-                Hash Cond: (t3.c1 = ft5_1.c1)
-                ->  Foreign Scan on public.ft5 t3
-                      Output: t3.c1, t3.c2, t3.c3
-                      Remote SQL: SELECT c1 FROM "S 1"."T 4"
-                ->  Hash
-                      Output: ft5_1.c1
-                      ->  Foreign Scan on public.ft5 ft5_1
-                            Output: ft5_1.c1
-                            Remote SQL: SELECT c1 FROM "S 1"."T 4"
-          ->  Hash
-                Output: t1.c1
-                ->  Foreign Scan on public.ft5 t1
-                      Output: t1.c1
-                      Remote SQL: SELECT c1 FROM "S 1"."T 4"
-    ->  Hash
-          Output: ft5.c1
-          ->  Foreign Scan on public.ft5
-                Output: ft5.c1
-                Remote SQL: SELECT c1 FROM "S 1"."T 4"
- (27 rows)
- 
  -- non-Var items in targelist of the nullable rel of a join preventing
  -- push-down in some cases
  -- unable to push {ft1, ft2}
--- 2053,2058 ----
***************
*** 2260,2268 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
   14 |    |    |   
  (3 rows)
  
- -- recreate the dropped user mapping for further tests
- CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
- DROP USER MAPPING FOR PUBLIC SERVER loopback;
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
  EXPLAIN VERBOSE SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
--- 2111,2116 ----
***************
*** 2283,2288 **** SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5
--- 2131,2324 ----
   (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31
  (4 rows)
  
+ -- join encapsulated in a view
+ DROP USER MAPPING FOR CURRENT_USER SERVER loopback;
+ CREATE USER MAPPING FOR PUBLIC SERVER loopback;
+ CREATE USER view_owner;
+ GRANT SELECT ON ft4 TO view_owner;
+ GRANT SELECT ON ft5 TO view_owner;
+ CREATE VIEW join_view AS SELECT t1.c1, t2.c2 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ ALTER VIEW join_view OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT * FROM join_view;  -- can be pushed down
+                                                                                    QUERY PLAN                                                                                   
+ --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Subquery Scan on join_view
+    Output: join_view.c1, join_view.c2
+    ->  Limit
+          Output: t1.c1, t2.c2, t2.c1
+          ->  Foreign Scan
+                Output: t1.c1, t2.c2, t2.c1
+                Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2)
+                Remote SQL: SELECT r3.c1, r4.c2, r4.c1 FROM ("S 1"."T 3" r3 LEFT JOIN "S 1"."T 4" r4 ON (((r3.c1 = r4.c1)))) ORDER BY r3.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST
+ (8 rows)
+ 
+ SELECT * FROM join_view;
+  c1 | c2 
+ ----+----
+  22 |   
+  24 | 25
+  26 |   
+  28 |   
+  30 | 31
+  32 |   
+  34 |   
+  36 | 37
+  38 |   
+  40 |   
+ (10 rows)
+ 
+ -- join between two views
+ CREATE VIEW v4 AS SELECT * FROM ft4;
+ CREATE VIEW v5 AS SELECT * FROM ft5;
+ ALTER VIEW v5 OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down
+                               QUERY PLAN                              
+ ----------------------------------------------------------------------
+  Limit
+    Output: ft4.c1, ft5.c2, ft5.c1
+    ->  Sort
+          Output: ft4.c1, ft5.c2, ft5.c1
+          Sort Key: ft4.c1, ft5.c1
+          ->  Hash Left Join
+                Output: ft4.c1, ft5.c2, ft5.c1
+                Hash Cond: (ft4.c1 = ft5.c1)
+                ->  Foreign Scan on public.ft4
+                      Output: ft4.c1, ft4.c2, ft4.c3
+                      Remote SQL: SELECT c1 FROM "S 1"."T 3"
+                ->  Hash
+                      Output: ft5.c2, ft5.c1
+                      ->  Foreign Scan on public.ft5
+                            Output: ft5.c2, ft5.c1
+                            Remote SQL: SELECT c1, c2 FROM "S 1"."T 4"
+ (16 rows)
+ 
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+  c1 | c2 
+ ----+----
+  22 |   
+  24 | 25
+  26 |   
+  28 |   
+  30 | 31
+  32 |   
+  34 |   
+  36 | 37
+  38 |   
+  40 |   
+ (10 rows)
+ 
+ ALTER VIEW v4 OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
+                                                                                 QUERY PLAN                                                                                
+ --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Limit
+    Output: ft4.c1, ft5.c2, ft5.c1
+    ->  Foreign Scan
+          Output: ft4.c1, ft5.c2, ft5.c1
+          Relations: (public.ft4) LEFT JOIN (public.ft5)
+          Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST
+ (6 rows)
+ 
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+  c1 | c2 
+ ----+----
+  22 |   
+  24 | 25
+  26 |   
+  28 |   
+  30 | 31
+  32 |   
+  34 |   
+  36 | 37
+  38 |   
+  40 |   
+ (10 rows)
+ 
+ -- join between a view and a foreign table
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down
+                               QUERY PLAN                              
+ ----------------------------------------------------------------------
+  Limit
+    Output: ft4.c1, t2.c2, t2.c1
+    ->  Sort
+          Output: ft4.c1, t2.c2, t2.c1
+          Sort Key: ft4.c1, t2.c1
+          ->  Hash Left Join
+                Output: ft4.c1, t2.c2, t2.c1
+                Hash Cond: (ft4.c1 = t2.c1)
+                ->  Foreign Scan on public.ft4
+                      Output: ft4.c1, ft4.c2, ft4.c3
+                      Remote SQL: SELECT c1 FROM "S 1"."T 3"
+                ->  Hash
+                      Output: t2.c2, t2.c1
+                      ->  Foreign Scan on public.ft5 t2
+                            Output: t2.c2, t2.c1
+                            Remote SQL: SELECT c1, c2 FROM "S 1"."T 4"
+ (16 rows)
+ 
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+  c1 | c2 
+ ----+----
+  22 |   
+  24 | 25
+  26 |   
+  28 |   
+  30 | 31
+  32 |   
+  34 |   
+  36 | 37
+  38 |   
+  40 |   
+ (10 rows)
+ 
+ ALTER VIEW v4 OWNER TO CURRENT_USER;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
+                               QUERY PLAN                              
+ ----------------------------------------------------------------------
+  Limit
+    Output: ft4.c1, t2.c2, t2.c1
+    ->  Sort
+          Output: ft4.c1, t2.c2, t2.c1
+          Sort Key: ft4.c1, t2.c1
+          ->  Hash Left Join
+                Output: ft4.c1, t2.c2, t2.c1
+                Hash Cond: (ft4.c1 = t2.c1)
+                ->  Foreign Scan on public.ft4
+                      Output: ft4.c1, ft4.c2, ft4.c3
+                      Remote SQL: SELECT c1 FROM "S 1"."T 3"
+                ->  Hash
+                      Output: t2.c2, t2.c1
+                      ->  Foreign Scan on public.ft5 t2
+                            Output: t2.c2, t2.c1
+                            Remote SQL: SELECT c1, c2 FROM "S 1"."T 4"
+ (16 rows)
+ 
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+  c1 | c2 
+ ----+----
+  22 |   
+  24 | 25
+  26 |   
+  28 |   
+  30 | 31
+  32 |   
+  34 |   
+  36 | 37
+  38 |   
+  40 |   
+ (10 rows)
+ 
+ ALTER VIEW v4 OWNER TO view_owner;
+ -- cleanup
+ DROP OWNED BY view_owner;
+ DROP USER view_owner;
+ DROP USER MAPPING FOR PUBLIC SERVER loopback;
+ CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
  -- ===================================================================
  -- parameterized queries
  -- ===================================================================
***************
*** 5867,5872 **** AND ftoptions @> array['fetch_size=60000'];
  (1 row)
  
  ROLLBACK;
- -- Cleanup
- DROP OWNED BY view_owner;
- DROP USER view_owner;
--- 5903,5905 ----
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 67,74 **** enum FdwScanPrivateIndex
  	FdwScanPrivateRetrievedAttrs,
  	/* Integer representing the desired fetch_size */
  	FdwScanPrivateFetchSize,
- 	/* Oid of user mapping to be used while connecting to the foreign server */
- 	FdwScanPrivateUserMappingOid,
  
  	/*
  	 * String describing join i.e. names of relations being joined and types
--- 67,72 ----
***************
*** 1226,1236 **** postgresGetForeignPlan(PlannerInfo *root,
  	 * Build the fdw_private list that will be available to the executor.
  	 * Items in the list must match order in enum FdwScanPrivateIndex.
  	 */
! 	fdw_private = list_make5(makeString(sql.data),
  							 remote_conds,
  							 retrieved_attrs,
! 							 makeInteger(fpinfo->fetch_size),
! 							 makeInteger(foreignrel->umid));
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  		fdw_private = lappend(fdw_private,
  							  makeString(fpinfo->relation_name->data));
--- 1224,1233 ----
  	 * Build the fdw_private list that will be available to the executor.
  	 * Items in the list must match order in enum FdwScanPrivateIndex.
  	 */
! 	fdw_private = list_make4(makeString(sql.data),
  							 remote_conds,
  							 retrieved_attrs,
! 							 makeInteger(fpinfo->fetch_size));
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  		fdw_private = lappend(fdw_private,
  							  makeString(fpinfo->relation_name->data));
***************
*** 1262,1268 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1259,1269 ----
  	ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
  	EState	   *estate = node->ss.ps.state;
  	PgFdwScanState *fsstate;
+ 	RangeTblEntry *rte;
+ 	Oid			userid;
+ 	ForeignTable *table;
  	UserMapping *user;
+ 	int			rtindex;
  	int			numParams;
  
  	/*
***************
*** 1278,1313 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
  	node->fdw_state = (void *) fsstate;
  
  	/*
! 	 * Obtain the foreign server where to connect and user mapping to use for
! 	 * connection. For base relations we obtain this information from
! 	 * catalogs. For join relations, this information is frozen at the time of
! 	 * planning to ensure that the join is safe to pushdown. In case the
! 	 * information goes stale between planning and execution, plan will be
! 	 * invalidated and replanned.
  	 */
  	if (fsplan->scan.scanrelid > 0)
! 	{
! 		ForeignTable *table;
! 
! 		/*
! 		 * Identify which user to do the remote access as.  This should match
! 		 * what ExecCheckRTEPerms() does.
! 		 */
! 		RangeTblEntry *rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
! 		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
! 
! 		fsstate->rel = node->ss.ss_currentRelation;
! 		table = GetForeignTable(RelationGetRelid(fsstate->rel));
! 
! 		user = GetUserMapping(userid, table->serverid);
! 	}
  	else
  	{
! 		Oid			umid = intVal(list_nth(fsplan->fdw_private, FdwScanPrivateUserMappingOid));
! 
! 		user = GetUserMappingById(umid);
! 		Assert(fsplan->fs_server == user->serverid);
  	}
  
  	/*
  	 * Get connection to the foreign server.  Connection manager will
--- 1279,1301 ----
  	node->fdw_state = (void *) fsstate;
  
  	/*
! 	 * Identify which user to do the remote access as.  This should match what
! 	 * ExecCheckRTEPerms() does.
  	 */
  	if (fsplan->scan.scanrelid > 0)
! 		rtindex = fsplan->scan.scanrelid;
  	else
  	{
! 		/* Pick the lowest-numbered one as a representative. */
! 		rtindex = -1;
! 		rtindex = bms_next_member(fsplan->fs_relids, rtindex);
  	}
+ 	rte = rt_fetch(rtindex, estate->es_range_table);
+ 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+ 
+ 	/* Get info about foreign table. */
+ 	table = GetForeignTable(rte->relid);
+ 	user = GetUserMapping(userid, table->serverid);
  
  	/*
  	 * Get connection to the foreign server.  Connection manager will
***************
*** 1344,1352 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1332,1346 ----
  	 * into local representation and error reporting during that process.
  	 */
  	if (fsplan->scan.scanrelid > 0)
+ 	{
+ 		fsstate->rel = node->ss.ss_currentRelation;
  		fsstate->tupdesc = RelationGetDescr(fsstate->rel);
+ 	}
  	else
+ 	{
+ 		fsstate->rel = NULL;
  		fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+ 	}
  
  	fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
  
***************
*** 3966,3981 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	List	   *otherclauses;
  
  	/*
- 	 * Core code may call GetForeignJoinPaths hook even when the join relation
- 	 * doesn't have a valid user mapping associated with it. See
- 	 * build_join_rel() for details. We can't push down such join, since there
- 	 * doesn't exist a user mapping which can be used to connect to the
- 	 * foreign server.
- 	 */
- 	if (!OidIsValid(joinrel->umid))
- 		return false;
- 
- 	/*
  	 * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
  	 * Constructing queries representing SEMI and ANTI joins is hard, hence
  	 * not considered right now.
--- 3960,3965 ----
***************
*** 4151,4156 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 4135,4154 ----
  	fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate ||
  		fpinfo_i->use_remote_estimate;
  
+ 	/* Get user mapping */
+ 	if (fpinfo->use_remote_estimate)
+ 	{
+ 		if (fpinfo_o->use_remote_estimate)
+ 			fpinfo->user = fpinfo_o->user;
+ 		else
+ 			fpinfo->user = fpinfo_i->user;
+ 	}
+ 	else
+ 		fpinfo->user = NULL;
+ 
+ 	/* Get foreign server */
+ 	fpinfo->server = fpinfo_o->server;
+ 
  	/*
  	 * Since both the joining relations come from the same server, the server
  	 * level options should have same value for both the relations. Pick from
***************
*** 4312,4337 **** postgresGetForeignJoinPaths(PlannerInfo *root,
  	cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
  
  	/*
! 	 * If we are going to estimate the costs using EXPLAIN, we will need
! 	 * connection information. Fill it here.
  	 */
! 	if (fpinfo->use_remote_estimate)
! 		fpinfo->user = GetUserMappingById(joinrel->umid);
! 	else
! 	{
! 		fpinfo->user = NULL;
! 
! 		/*
! 		 * If we are going to estimate costs locally, estimate the join clause
! 		 * selectivity here while we have special join info.
! 		 */
  		fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses,
  														0, fpinfo->jointype,
  														extra->sjinfo);
  
- 	}
- 	fpinfo->server = GetForeignServer(joinrel->serverid);
- 
  	/* Estimate costs for bare join relation */
  	estimate_path_cost_size(root, joinrel, NIL, NIL, &rows,
  							&width, &startup_cost, &total_cost);
--- 4310,4323 ----
  	cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
  
  	/*
! 	 * If we are going to estimate costs locally, estimate the join clause
! 	 * selectivity here while we have special join info.
  	 */
! 	if (!fpinfo->use_remote_estimate)
  		fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses,
  														0, fpinfo->jointype,
  														extra->sjinfo);
  
  	/* Estimate costs for bare join relation */
  	estimate_path_cost_size(root, joinrel, NIL, NIL, &rows,
  							&width, &startup_cost, &total_cost);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 485,532 **** EXPLAIN (COSTS false, VERBOSE)
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  
- -- create another user for permission, user mapping, effective user tests
- CREATE USER view_owner;
- -- grant privileges on ft4 and ft5 to view_owner
- GRANT ALL ON ft4 TO view_owner;
- GRANT ALL ON ft5 TO view_owner;
- -- prepare statement with current session user
- PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
- EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
- EXECUTE join_stmt;
- -- change the session user to view_owner and execute the statement. Because of
- -- change in session user, the plan should get invalidated and created again.
- -- The join will not be pushed down since the joining relations do not have a
- -- valid user mapping.
- SET SESSION ROLE view_owner;
- EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
- RESET ROLE;
- DEALLOCATE join_stmt;
- 
- CREATE VIEW v_ft5 AS SELECT * FROM ft5;
- -- change owner of v_ft5 to view_owner so that the effective user for scan on
- -- ft5 is view_owner and not the current user.
- ALTER VIEW v_ft5 OWNER TO view_owner;
- -- create a public user mapping for loopback server
- -- drop user mapping for current_user.
- DROP USER MAPPING FOR CURRENT_USER SERVER loopback;
- CREATE USER MAPPING FOR PUBLIC SERVER loopback;
- -- different effective user for permission check, but same user mapping for the
- -- joining sides, join pushed down, no result expected.
- PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN v_ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
- EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
- EXECUTE join_stmt;
- -- create user mapping for view_owner and execute the prepared statement
- -- the join should not be pushed down since joining relations now use two
- -- different user mappings
- CREATE USER MAPPING FOR view_owner SERVER loopback;
- EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
- EXECUTE join_stmt;
- 
- -- If a sub-join can't be pushed down, upper level join shouldn't be either.
- EXPLAIN (COSTS false, VERBOSE)
- SELECT t1.c1, t2.c1 FROM (ft5 t1 JOIN v_ft5 t2 ON (t1.c1 = t2.c1)) left join (ft5 t3 JOIN v_ft5 t4 ON (t3.c1 = t4.c1)) ON (t1.c1 = t3.c1);
- 
  -- non-Var items in targelist of the nullable rel of a join preventing
  -- push-down in some cases
  -- unable to push {ft1, ft2}
--- 485,490 ----
***************
*** 539,553 **** EXPLAIN (COSTS false, VERBOSE)
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  
- -- recreate the dropped user mapping for further tests
- CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
- DROP USER MAPPING FOR PUBLIC SERVER loopback;
- 
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
  EXPLAIN VERBOSE SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
  
  -- ===================================================================
  -- parameterized queries
  -- ===================================================================
--- 497,547 ----
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
  EXPLAIN VERBOSE SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
  
+ -- join encapsulated in a view
+ DROP USER MAPPING FOR CURRENT_USER SERVER loopback;
+ CREATE USER MAPPING FOR PUBLIC SERVER loopback;
+ CREATE USER view_owner;
+ GRANT SELECT ON ft4 TO view_owner;
+ GRANT SELECT ON ft5 TO view_owner;
+ CREATE VIEW join_view AS SELECT t1.c1, t2.c2 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ ALTER VIEW join_view OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT * FROM join_view;  -- can be pushed down
+ SELECT * FROM join_view;
+ 
+ -- join between two views
+ CREATE VIEW v4 AS SELECT * FROM ft4;
+ CREATE VIEW v5 AS SELECT * FROM ft5;
+ ALTER VIEW v5 OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ ALTER VIEW v4 OWNER TO view_owner;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ 
+ -- join between a view and a foreign table
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ ALTER VIEW v4 OWNER TO CURRENT_USER;
+ EXPLAIN (COSTS false, VERBOSE)
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
+ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
+ ALTER VIEW v4 OWNER TO view_owner;
+ 
+ -- cleanup
+ DROP OWNED BY view_owner;
+ DROP USER view_owner;
+ DROP USER MAPPING FOR PUBLIC SERVER loopback;
+ CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
+ 
  -- ===================================================================
  -- parameterized queries
  -- ===================================================================
***************
*** 1360,1366 **** WHERE ftrelid = 'table30000'::regclass
  AND ftoptions @> array['fetch_size=60000'];
  
  ROLLBACK;
- 
- -- Cleanup
- DROP OWNED BY view_owner;
- DROP USER view_owner;
--- 1354,1356 ----
*** a/src/backend/executor/execParallel.c
--- b/src/backend/executor/execParallel.c
***************
*** 160,166 **** ExecSerializePlan(Plan *plan, EState *estate)
  	pstmt->relationOids = NIL;
  	pstmt->invalItems = NIL;	/* workers can't replan anyway... */
  	pstmt->hasRowSecurity = false;
- 	pstmt->hasForeignJoin = false;
  
  	/* Return serialized copy of our dummy PlannedStmt. */
  	return nodeToString(pstmt);
--- 160,165 ----
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
***************
*** 31,37 ****
  extern Datum pg_options_to_table(PG_FUNCTION_ARGS);
  extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS);
  
! static HeapTuple find_user_mapping(Oid userid, Oid serverid, bool missing_ok);
  
  /*
   * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
--- 31,37 ----
  extern Datum pg_options_to_table(PG_FUNCTION_ARGS);
  extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS);
  
! static HeapTuple find_user_mapping(Oid userid, Oid serverid);
  
  /*
   * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
***************
*** 223,229 **** GetUserMapping(Oid userid, Oid serverid)
  	bool		isnull;
  	UserMapping *um;
  
! 	tp = find_user_mapping(userid, serverid, false);
  
  	um = (UserMapping *) palloc(sizeof(UserMapping));
  	um->umid = HeapTupleGetOid(tp);
--- 223,229 ----
  	bool		isnull;
  	UserMapping *um;
  
! 	tp = find_user_mapping(userid, serverid);
  
  	um = (UserMapping *) palloc(sizeof(UserMapping));
  	um->umid = HeapTupleGetOid(tp);
***************
*** 250,272 **** GetUserMapping(Oid userid, Oid serverid)
   *
   * If no mapping is found for the supplied user, we also look for
   * PUBLIC mappings (userid == InvalidOid).
-  *
-  * If missing_ok is true, the function returns InvalidOid when it does not find
-  * required user mapping. Otherwise, find_user_mapping() throws error if it
-  * does not find required user mapping.
   */
  Oid
! GetUserMappingId(Oid userid, Oid serverid, bool missing_ok)
  {
  	HeapTuple	tp;
  	Oid			umid;
  
! 	tp = find_user_mapping(userid, serverid, missing_ok);
! 
! 	Assert(missing_ok || tp);
! 
! 	if (!tp && missing_ok)
! 		return InvalidOid;
  
  	/* Extract the Oid */
  	umid = HeapTupleGetOid(tp);
--- 250,263 ----
   *
   * If no mapping is found for the supplied user, we also look for
   * PUBLIC mappings (userid == InvalidOid).
   */
  Oid
! GetUserMappingId(Oid userid, Oid serverid)
  {
  	HeapTuple	tp;
  	Oid			umid;
  
! 	tp = find_user_mapping(userid, serverid);
  
  	/* Extract the Oid */
  	umid = HeapTupleGetOid(tp);
***************
*** 282,294 **** GetUserMappingId(Oid userid, Oid serverid, bool missing_ok)
   *
   * If no mapping is found for the supplied user, we also look for
   * PUBLIC mappings (userid == InvalidOid).
-  *
-  * If missing_ok is true, the function returns NULL, if it does not find
-  * the required user mapping. Otherwise, it throws error if it does not
-  * find the required user mapping.
   */
  static HeapTuple
! find_user_mapping(Oid userid, Oid serverid, bool missing_ok)
  {
  	HeapTuple	tp;
  
--- 273,281 ----
   *
   * If no mapping is found for the supplied user, we also look for
   * PUBLIC mappings (userid == InvalidOid).
   */
  static HeapTuple
! find_user_mapping(Oid userid, Oid serverid)
  {
  	HeapTuple	tp;
  
***************
*** 305,319 **** find_user_mapping(Oid userid, Oid serverid, bool missing_ok)
  						 ObjectIdGetDatum(serverid));
  
  	if (!HeapTupleIsValid(tp))
! 	{
! 		if (missing_ok)
! 			return NULL;
! 		else
! 			ereport(ERROR,
! 					(errcode(ERRCODE_UNDEFINED_OBJECT),
! 					 errmsg("user mapping not found for \"%s\"",
! 							MappingUserName(userid))));
! 	}
  
  	return tp;
  }
--- 292,301 ----
  						 ObjectIdGetDatum(serverid));
  
  	if (!HeapTupleIsValid(tp))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("user mapping not found for \"%s\"",
! 						MappingUserName(userid))));
  
  	return tp;
  }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 97,103 **** _copyPlannedStmt(const PlannedStmt *from)
  	COPY_SCALAR_FIELD(nParamExec);
  	COPY_SCALAR_FIELD(hasRowSecurity);
  	COPY_SCALAR_FIELD(parallelModeNeeded);
- 	COPY_SCALAR_FIELD(hasForeignJoin);
  
  	return newnode;
  }
--- 97,102 ----
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 273,279 **** _outPlannedStmt(StringInfo str, const PlannedStmt *node)
  	WRITE_INT_FIELD(nParamExec);
  	WRITE_BOOL_FIELD(hasRowSecurity);
  	WRITE_BOOL_FIELD(parallelModeNeeded);
- 	WRITE_BOOL_FIELD(hasForeignJoin);
  }
  
  /*
--- 273,278 ----
***************
*** 2018,2024 **** _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
  	WRITE_BOOL_FIELD(hasRowSecurity);
  	WRITE_BOOL_FIELD(parallelModeOK);
  	WRITE_BOOL_FIELD(parallelModeNeeded);
- 	WRITE_BOOL_FIELD(hasForeignJoin);
  }
  
  static void
--- 2017,2022 ----
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1402,1408 **** _readPlannedStmt(void)
  	READ_INT_FIELD(nParamExec);
  	READ_BOOL_FIELD(hasRowSecurity);
  	READ_BOOL_FIELD(parallelModeNeeded);
- 	READ_BOOL_FIELD(hasForeignJoin);
  
  	READ_DONE();
  }
--- 1402,1407 ----
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
***************
*** 213,220 **** add_paths_to_joinrel(PlannerInfo *root,
  
  	/*
  	 * 5. If inner and outer relations are foreign tables (or joins) belonging
! 	 * to the same server and using the same user mapping, give the FDW a
! 	 * chance to push down joins.
  	 */
  	if (joinrel->fdwroutine &&
  		joinrel->fdwroutine->GetForeignJoinPaths)
--- 213,220 ----
  
  	/*
  	 * 5. If inner and outer relations are foreign tables (or joins) belonging
! 	 * to the same server and assigned to the same user to check access
! 	 * permissions as, give the FDW a chance to push down joins.
  	 */
  	if (joinrel->fdwroutine &&
  		joinrel->fdwroutine->GetForeignJoinPaths)
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 3247,3261 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
  	scan_plan->fs_relids = best_path->path.parent->relids;
  
  	/*
- 	 * If a join between foreign relations was pushed down, remember it. The
- 	 * push-down safety of the join depends upon the server and user mapping
- 	 * being same. That can change between planning and execution time, in
- 	 * which case the plan should be invalidated.
- 	 */
- 	if (scan_relid == 0)
- 		root->glob->hasForeignJoin = true;
- 
- 	/*
  	 * Replace any outer-relation variables with nestloop params in the qual,
  	 * fdw_exprs and fdw_recheck_quals expressions.  We do this last so that
  	 * the FDW doesn't have to be involved.  (Note that parts of fdw_exprs or
--- 3247,3252 ----
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 220,226 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
  	glob->lastPlanNodeId = 0;
  	glob->transientPlan = false;
  	glob->hasRowSecurity = false;
- 	glob->hasForeignJoin = false;
  
  	/*
  	 * Assess whether it's feasible to use parallel mode for this query. We
--- 220,225 ----
***************
*** 417,423 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
  	result->nParamExec = glob->nParamExec;
  	result->hasRowSecurity = glob->hasRowSecurity;
  	result->parallelModeNeeded = glob->parallelModeNeeded;
- 	result->hasForeignJoin = glob->hasForeignJoin;
  
  	return result;
  }
--- 416,421 ----
***************
*** 1960,1966 **** grouping_planner(PlannerInfo *root, bool inheritance_update,
  	 * If the current_rel belongs to a single FDW, so does the final_rel.
  	 */
  	final_rel->serverid = current_rel->serverid;
! 	final_rel->umid = current_rel->umid;
  	final_rel->fdwroutine = current_rel->fdwroutine;
  
  	/*
--- 1958,1964 ----
  	 * If the current_rel belongs to a single FDW, so does the final_rel.
  	 */
  	final_rel->serverid = current_rel->serverid;
! 	final_rel->userid = current_rel->userid;
  	final_rel->fdwroutine = current_rel->fdwroutine;
  
  	/*
***************
*** 3337,3343 **** create_grouping_paths(PlannerInfo *root,
  	 * If the input rel belongs to a single FDW, so does the grouped rel.
  	 */
  	grouped_rel->serverid = input_rel->serverid;
! 	grouped_rel->umid = input_rel->umid;
  	grouped_rel->fdwroutine = input_rel->fdwroutine;
  
  	/*
--- 3335,3341 ----
  	 * If the input rel belongs to a single FDW, so does the grouped rel.
  	 */
  	grouped_rel->serverid = input_rel->serverid;
! 	grouped_rel->userid = input_rel->userid;
  	grouped_rel->fdwroutine = input_rel->fdwroutine;
  
  	/*
***************
*** 3891,3897 **** create_window_paths(PlannerInfo *root,
  	 * If the input rel belongs to a single FDW, so does the window rel.
  	 */
  	window_rel->serverid = input_rel->serverid;
! 	window_rel->umid = input_rel->umid;
  	window_rel->fdwroutine = input_rel->fdwroutine;
  
  	/*
--- 3889,3895 ----
  	 * If the input rel belongs to a single FDW, so does the window rel.
  	 */
  	window_rel->serverid = input_rel->serverid;
! 	window_rel->userid = input_rel->userid;
  	window_rel->fdwroutine = input_rel->fdwroutine;
  
  	/*
***************
*** 4071,4077 **** create_distinct_paths(PlannerInfo *root,
  	 * If the input rel belongs to a single FDW, so does the distinct_rel.
  	 */
  	distinct_rel->serverid = input_rel->serverid;
! 	distinct_rel->umid = input_rel->umid;
  	distinct_rel->fdwroutine = input_rel->fdwroutine;
  
  	/* Estimate number of distinct rows there will be */
--- 4069,4075 ----
  	 * If the input rel belongs to a single FDW, so does the distinct_rel.
  	 */
  	distinct_rel->serverid = input_rel->serverid;
! 	distinct_rel->userid = input_rel->userid;
  	distinct_rel->fdwroutine = input_rel->fdwroutine;
  
  	/* Estimate number of distinct rows there will be */
***************
*** 4279,4285 **** create_ordered_paths(PlannerInfo *root,
  	 * If the input rel belongs to a single FDW, so does the ordered_rel.
  	 */
  	ordered_rel->serverid = input_rel->serverid;
! 	ordered_rel->umid = input_rel->umid;
  	ordered_rel->fdwroutine = input_rel->fdwroutine;
  
  	foreach(lc, input_rel->pathlist)
--- 4277,4283 ----
  	 * If the input rel belongs to a single FDW, so does the ordered_rel.
  	 */
  	ordered_rel->serverid = input_rel->serverid;
! 	ordered_rel->userid = input_rel->userid;
  	ordered_rel->fdwroutine = input_rel->fdwroutine;
  
  	foreach(lc, input_rel->pathlist)
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 14,22 ****
   */
  #include "postgres.h"
  
- #include "miscadmin.h"
- #include "catalog/pg_class.h"
- #include "foreign/foreign.h"
  #include "optimizer/clauses.h"
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
--- 14,19 ----
***************
*** 130,136 **** build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
  	rel->subroot = NULL;
  	rel->subplan_params = NIL;
  	rel->serverid = InvalidOid;
! 	rel->umid = InvalidOid;
  	rel->fdwroutine = NULL;
  	rel->fdw_private = NULL;
  	rel->baserestrictinfo = NIL;
--- 127,133 ----
  	rel->subroot = NULL;
  	rel->subplan_params = NIL;
  	rel->serverid = InvalidOid;
! 	rel->userid = rte->checkAsUser;
  	rel->fdwroutine = NULL;
  	rel->fdw_private = NULL;
  	rel->baserestrictinfo = NIL;
***************
*** 170,199 **** build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
  			break;
  	}
  
- 	/* For foreign tables get the user mapping */
- 	if (rte->relkind == RELKIND_FOREIGN_TABLE)
- 	{
- 		/*
- 		 * This should match what ExecCheckRTEPerms() does.
- 		 *
- 		 * Note that if the plan ends up depending on the user OID in any way
- 		 * - e.g. if it depends on the computed user mapping OID - we must
- 		 * ensure that it gets invalidated in the case of a user OID change.
- 		 * See RevalidateCachedQuery and more generally the hasForeignJoin
- 		 * flags in PlannerGlobal and PlannedStmt.
- 		 *
- 		 * It's possible, and not necessarily an error, for rel->umid to be
- 		 * InvalidOid even though rel->serverid is set.  That just means there
- 		 * is a server with no user mapping.
- 		 */
- 		Oid			userid;
- 
- 		userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
- 		rel->umid = GetUserMappingId(userid, rel->serverid, true);
- 	}
- 	else
- 		rel->umid = InvalidOid;
- 
  	/* Save the finished struct in the query's simple_rel_array */
  	root->simple_rel_array[relid] = rel;
  
--- 167,172 ----
***************
*** 424,430 **** build_join_rel(PlannerInfo *root,
  	joinrel->subroot = NULL;
  	joinrel->subplan_params = NIL;
  	joinrel->serverid = InvalidOid;
! 	joinrel->umid = InvalidOid;
  	joinrel->fdwroutine = NULL;
  	joinrel->fdw_private = NULL;
  	joinrel->baserestrictinfo = NIL;
--- 397,403 ----
  	joinrel->subroot = NULL;
  	joinrel->subplan_params = NIL;
  	joinrel->serverid = InvalidOid;
! 	joinrel->userid = InvalidOid;
  	joinrel->fdwroutine = NULL;
  	joinrel->fdw_private = NULL;
  	joinrel->baserestrictinfo = NIL;
***************
*** 435,457 **** build_join_rel(PlannerInfo *root,
  
  	/*
  	 * Set up foreign-join fields if outer and inner relation are foreign
! 	 * tables (or joins) belonging to the same server and using the same user
! 	 * mapping.
  	 *
  	 * Otherwise those fields are left invalid, so FDW API will not be called
  	 * for the join relation.
- 	 *
- 	 * For FDWs like file_fdw, which ignore user mapping, the user mapping id
- 	 * associated with the joining relation may be invalid. A valid serverid
- 	 * distinguishes between a pushed down join with no user mapping and a
- 	 * join which can not be pushed down because of user mapping mismatch.
  	 */
  	if (OidIsValid(outer_rel->serverid) &&
  		inner_rel->serverid == outer_rel->serverid &&
! 		inner_rel->umid == outer_rel->umid)
  	{
  		joinrel->serverid = outer_rel->serverid;
! 		joinrel->umid = outer_rel->umid;
  		joinrel->fdwroutine = outer_rel->fdwroutine;
  	}
  
--- 408,425 ----
  
  	/*
  	 * Set up foreign-join fields if outer and inner relation are foreign
! 	 * tables (or joins) belonging to the same server and assigned to the same
! 	 * user to check access permissions as.
  	 *
  	 * Otherwise those fields are left invalid, so FDW API will not be called
  	 * for the join relation.
  	 */
  	if (OidIsValid(outer_rel->serverid) &&
  		inner_rel->serverid == outer_rel->serverid &&
! 		inner_rel->userid == outer_rel->userid)
  	{
  		joinrel->serverid = outer_rel->serverid;
! 		joinrel->userid = outer_rel->userid;
  		joinrel->fdwroutine = outer_rel->fdwroutine;
  	}
  
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 105,112 **** static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
  static void PlanCacheRelCallback(Datum arg, Oid relid);
  static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
  static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
! static void PlanCacheUserMappingCallback(Datum arg, int cacheid,
! 							 uint32 hashvalue);
  
  
  /*
--- 105,111 ----
  static void PlanCacheRelCallback(Datum arg, Oid relid);
  static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
  static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
! 
  
  
  /*
***************
*** 122,129 **** InitPlanCache(void)
  	CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
- 	/* User mapping change may invalidate plans with pushed down foreign join */
- 	CacheRegisterSyscacheCallback(USERMAPPINGOID, PlanCacheUserMappingCallback, (Datum) 0);
  }
  
  /*
--- 121,126 ----
***************
*** 610,628 **** RevalidateCachedQuery(CachedPlanSource *plansource)
  		plansource->is_valid = false;
  
  	/*
- 	 * If we have a join pushed down to the foreign server and the current
- 	 * user is different from the one for which the plan was created,
- 	 * invalidate the generic plan since user mapping for the new user might
- 	 * make the join unsafe to push down, or change which user mapping is
- 	 * used.
- 	 */
- 	if (plansource->is_valid &&
- 		plansource->gplan &&
- 		plansource->gplan->has_foreign_join &&
- 		plansource->planUserId != GetUserId())
- 		plansource->gplan->is_valid = false;
- 
- 	/*
  	 * If the query is currently valid, acquire locks on the referenced
  	 * objects; then check again.  We need to do it this way to cover the race
  	 * condition that an invalidation message arrives before we get the locks.
--- 607,612 ----
***************
*** 902,908 **** BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
  	bool		spi_pushed;
  	MemoryContext plan_context;
  	MemoryContext oldcxt = CurrentMemoryContext;
- 	ListCell   *lc;
  
  	/*
  	 * Normally the querytree should be valid already, but if it's not,
--- 886,891 ----
***************
*** 1010,1029 **** BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
  	plan->is_saved = false;
  	plan->is_valid = true;
  
- 	/*
- 	 * Walk through the plist and set hasForeignJoin if any of the plans have
- 	 * it set.
- 	 */
- 	plan->has_foreign_join = false;
- 	foreach(lc, plist)
- 	{
- 		PlannedStmt *plan_stmt = (PlannedStmt *) lfirst(lc);
- 
- 		if (IsA(plan_stmt, PlannedStmt))
- 			plan->has_foreign_join =
- 				plan->has_foreign_join || plan_stmt->hasForeignJoin;
- 	}
- 
  	/* assign generation number to new plan */
  	plan->generation = ++(plansource->generation);
  
--- 993,998 ----
***************
*** 1888,1927 **** PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
  }
  
  /*
-  * PlanCacheUserMappingCallback
-  *		Syscache inval callback function for user mapping cache invalidation.
-  *
-  *	Invalidates plans which have pushed down foreign joins.
-  */
- static void
- PlanCacheUserMappingCallback(Datum arg, int cacheid, uint32 hashvalue)
- {
- 	CachedPlanSource *plansource;
- 
- 	for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
- 	{
- 		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
- 
- 		/* No work if it's already invalidated */
- 		if (!plansource->is_valid)
- 			continue;
- 
- 		/* Never invalidate transaction control commands */
- 		if (IsTransactionStmtPlan(plansource))
- 			continue;
- 
- 		/*
- 		 * If the plan has pushed down foreign joins, those join may become
- 		 * unsafe to push down because of user mapping changes. Invalidate
- 		 * only the generic plan, since changes to user mapping do not
- 		 * invalidate the parse tree.
- 		 */
- 		if (plansource->gplan && plansource->gplan->has_foreign_join)
- 			plansource->gplan->is_valid = false;
- 	}
- }
- 
- /*
   * ResetPlanCache: invalidate all cached plans.
   */
  void
--- 1857,1862 ----
*** a/src/include/foreign/foreign.h
--- b/src/include/foreign/foreign.h
***************
*** 72,78 **** typedef struct ForeignTable
  extern ForeignServer *GetForeignServer(Oid serverid);
  extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
  extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
! extern Oid	GetUserMappingId(Oid userid, Oid serverid, bool missing_ok);
  extern UserMapping *GetUserMappingById(Oid umid);
  extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
  extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
--- 72,78 ----
  extern ForeignServer *GetForeignServer(Oid serverid);
  extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
  extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
! extern Oid	GetUserMappingId(Oid userid, Oid serverid);
  extern UserMapping *GetUserMappingById(Oid umid);
  extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
  extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 73,79 **** typedef struct PlannedStmt
  	bool		hasRowSecurity; /* row security applied? */
  
  	bool		parallelModeNeeded;		/* parallel mode required to execute? */
- 	bool		hasForeignJoin; /* Plan has a pushed down foreign join */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
--- 73,78 ----
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 126,133 **** typedef struct PlannerGlobal
  	bool		parallelModeOK; /* parallel mode potentially OK? */
  
  	bool		parallelModeNeeded;		/* parallel mode actually required? */
- 
- 	bool		hasForeignJoin; /* does have a pushed down foreign join */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
--- 126,131 ----
***************
*** 426,436 **** typedef struct PlannerInfo
   *		in just as for a baserel, except we don't bother with lateral_vars.
   *
   * If the relation is either a foreign table or a join of foreign tables that
!  * all belong to the same foreign server and use the same user mapping, these
!  * fields will be set:
   *
   *		serverid - OID of foreign server, if foreign table (else InvalidOid)
!  *		umid - OID of user mapping, if foreign table (else InvalidOid)
   *		fdwroutine - function hooks for FDW, if foreign table (else NULL)
   *		fdw_private - private state for FDW, if foreign table (else NULL)
   *
--- 424,434 ----
   *		in just as for a baserel, except we don't bother with lateral_vars.
   *
   * If the relation is either a foreign table or a join of foreign tables that
!  * all belong to the same foreign server and are assigned to the same user to
!  * check access permissions as (ie, checkAsUser), these fields will be set:
   *
   *		serverid - OID of foreign server, if foreign table (else InvalidOid)
!  *		userid - OID of user to check access as
   *		fdwroutine - function hooks for FDW, if foreign table (else NULL)
   *		fdw_private - private state for FDW, if foreign table (else NULL)
   *
***************
*** 528,535 **** typedef struct RelOptInfo
  
  	/* Information about foreign tables and foreign joins */
  	Oid			serverid;		/* identifies server for the table or join */
! 	Oid			umid;			/* identifies user mapping for the table or
! 								 * join */
  	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
  	struct FdwRoutine *fdwroutine;
  	void	   *fdw_private;
--- 526,532 ----
  
  	/* Information about foreign tables and foreign joins */
  	Oid			serverid;		/* identifies server for the table or join */
! 	Oid			userid;			/* identifies user to check access as */
  	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
  	struct FdwRoutine *fdwroutine;
  	void	   *fdw_private;
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
***************
*** 135,141 **** typedef struct CachedPlan
  								 * changes from this value */
  	int			generation;		/* parent's generation number for this plan */
  	int			refcount;		/* count of live references to this struct */
- 	bool		has_foreign_join;		/* plan has pushed down a foreign join */
  	MemoryContext context;		/* context containing this CachedPlan */
  } CachedPlan;
  
--- 135,140 ----
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to