Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 9320cfd

Browse files
author
Etsuro Fujita
committed
postgres_fdw: Avoid 'variable not found in subplan target list' error.
The tlist of the EvalPlanQual outer plan for a ForeignScan node is adjusted to produce a tuple whose descriptor matches the scan tuple slot for the ForeignScan node. But in the case where the outer plan contains an extra Sort node, if the new tlist contained columns required only for evaluating PlaceHolderVars or columns required only for evaluating local conditions, this would cause setrefs.c to fail with the error. The cause of this is that when creating the outer plan by injecting the Sort node into an alternative local join plan that could emit such extra columns as well, we fail to arrange for the outer plan to propagate them up through the Sort node, causing setrefs.c to fail to match up them in the new tlist to what is available from the outer plan. Repair. Per report from Alexander Pyhalov. Richard Guo and Etsuro Fujita, reviewed by Alexander Pyhalov and Tom Lane. Backpatch to all supported versions. Discussion: http://postgr.es/m/cfb17bf6dfdf876467bd5ef533852d18%40postgrespro.ru
1 parent b0526d2 commit 9320cfd

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,85 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
24742474

24752475
RESET enable_nestloop;
24762476
RESET enable_hashjoin;
2477+
-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to
2478+
-- return columns needed by the parent ForeignScan node
2479+
EXPLAIN (VERBOSE, COSTS OFF)
2480+
SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl;
2481+
QUERY PLAN
2482+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2483+
LockRows
2484+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ctid, ft1.*, ft2.*
2485+
-> Merge Left Join
2486+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ctid, ft1.*, ft2.*
2487+
Merge Cond: (local_tbl.c1 = ft1.c1)
2488+
-> Index Scan using local_tbl_pkey on public.local_tbl
2489+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
2490+
-> Materialize
2491+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text))
2492+
-> Foreign Scan
2493+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)
2494+
Relations: (public.ft1) INNER JOIN (public.ft2)
2495+
Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c3 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r4."C 1" = r5."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4."C 1" ASC NULLS LAST
2496+
-> Result
2497+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c3
2498+
-> Sort
2499+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), ft2.c3
2500+
Sort Key: ft1.c1
2501+
-> Hash Join
2502+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text), ft2.c3
2503+
Hash Cond: (ft1.c1 = ft2.c1)
2504+
-> Foreign Scan on public.ft1
2505+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
2506+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100))
2507+
-> Hash
2508+
Output: ft2.*, ft2.c1, ft2.c3
2509+
-> Foreign Scan on public.ft2
2510+
Output: ft2.*, ft2.c1, ft2.c3
2511+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
2512+
(29 rows)
2513+
2514+
ALTER SERVER loopback OPTIONS (DROP extensions);
2515+
ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0');
2516+
EXPLAIN (VERBOSE, COSTS OFF)
2517+
SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND ft1.c1 = postgres_fdw_abs(ft2.c2))) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl;
2518+
QUERY PLAN
2519+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2520+
LockRows
2521+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ctid, ft1.*, ft2.*
2522+
-> Nested Loop Left Join
2523+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ctid, ft1.*, ft2.*
2524+
Join Filter: (local_tbl.c3 = ft1.c3)
2525+
-> Index Scan using local_tbl_pkey on public.local_tbl
2526+
Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
2527+
-> Materialize
2528+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*
2529+
-> Foreign Scan
2530+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*
2531+
Filter: (ft1.c1 = postgres_fdw_abs(ft2.c2))
2532+
Relations: (public.ft1) INNER JOIN (public.ft2)
2533+
Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c2 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r4."C 1" = r5."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4.c3 ASC NULLS LAST
2534+
-> Sort
2535+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2
2536+
Sort Key: ft1.c3
2537+
-> Merge Join
2538+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2
2539+
Merge Cond: ((ft1.c1 = (postgres_fdw_abs(ft2.c2))) AND (ft1.c1 = ft2.c1))
2540+
-> Sort
2541+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
2542+
Sort Key: ft1.c1
2543+
-> Foreign Scan on public.ft1
2544+
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
2545+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100))
2546+
-> Sort
2547+
Output: ft2.*, ft2.c1, ft2.c2, (postgres_fdw_abs(ft2.c2))
2548+
Sort Key: (postgres_fdw_abs(ft2.c2)), ft2.c1
2549+
-> Foreign Scan on public.ft2
2550+
Output: ft2.*, ft2.c1, ft2.c2, postgres_fdw_abs(ft2.c2)
2551+
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
2552+
(32 rows)
2553+
2554+
ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost);
2555+
ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
24772556
DROP TABLE local_tbl;
24782557
-- check join pushdown in situations where multiple userids are involved
24792558
CREATE ROLE regress_view_owner SUPERUSER;

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5784,6 +5784,55 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
57845784

57855785
useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
57865786

5787+
/*
5788+
* Before creating sorted paths, arrange for the passed-in EPQ path, if
5789+
* any, to return columns needed by the parent ForeignScan node so that
5790+
* they will propagate up through Sort nodes injected below, if necessary.
5791+
*/
5792+
if (epq_path != NULL && useful_pathkeys_list != NIL)
5793+
{
5794+
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
5795+
PathTarget *target = copy_pathtarget(epq_path->pathtarget);
5796+
5797+
/* Include columns required for evaluating PHVs in the tlist. */
5798+
add_new_columns_to_pathtarget(target,
5799+
pull_var_clause((Node *) target->exprs,
5800+
PVC_RECURSE_PLACEHOLDERS));
5801+
5802+
/* Include columns required for evaluating the local conditions. */
5803+
foreach(lc, fpinfo->local_conds)
5804+
{
5805+
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
5806+
5807+
add_new_columns_to_pathtarget(target,
5808+
pull_var_clause((Node *) rinfo->clause,
5809+
PVC_RECURSE_PLACEHOLDERS));
5810+
}
5811+
5812+
/*
5813+
* If we have added any new columns, adjust the tlist of the EPQ path.
5814+
*
5815+
* Note: the plan created using this path will only be used to execute
5816+
* EPQ checks, where accuracy of the plan cost and width estimates
5817+
* would not be important, so we do not do set_pathtarget_cost_width()
5818+
* for the new pathtarget here. See also postgresGetForeignPlan().
5819+
*/
5820+
if (list_length(target->exprs) > list_length(epq_path->pathtarget->exprs))
5821+
{
5822+
/* The EPQ path is a join path, so it is projection-capable. */
5823+
Assert(is_projection_capable_path(epq_path));
5824+
5825+
/*
5826+
* Use create_projection_path() here, so as to avoid modifying it
5827+
* in place.
5828+
*/
5829+
epq_path = (Path *) create_projection_path(root,
5830+
rel,
5831+
epq_path,
5832+
target);
5833+
}
5834+
}
5835+
57875836
/* Create one path for each set of pathkeys we found above. */
57885837
foreach(lc, useful_pathkeys_list)
57895838
{

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,19 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
672672
AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
673673
RESET enable_nestloop;
674674
RESET enable_hashjoin;
675+
676+
-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to
677+
-- return columns needed by the parent ForeignScan node
678+
EXPLAIN (VERBOSE, COSTS OFF)
679+
SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl;
680+
681+
ALTER SERVER loopback OPTIONS (DROP extensions);
682+
ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0');
683+
EXPLAIN (VERBOSE, COSTS OFF)
684+
SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND ft1.c1 = postgres_fdw_abs(ft2.c2))) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl;
685+
ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost);
686+
ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
687+
675688
DROP TABLE local_tbl;
676689

677690
-- check join pushdown in situations where multiple userids are involved

0 commit comments

Comments
 (0)