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

Commit 9d6d2b2

Browse files
committed
Ensure that foreign scans with lateral refs are planned correctly.
As reported in bug #15613 from Srinivasan S A, file_fdw and postgres_fdw neglected to mark plain baserel foreign paths as parameterized when the relation has lateral_relids. Other FDWs have surely copied this mistake, so rather than just patching those two modules, install a band-aid fix in create_foreignscan_path to rectify the mistake centrally. Although the band-aid is enough to fix the visible symptom, correct the calls in file_fdw and postgres_fdw anyway, so that they are valid examples for external FDWs. Also, since the band-aid isn't enough to make this work for parameterized foreign joins, throw an elog(ERROR) if such a case is passed to create_foreignscan_path. This shouldn't pose much of a problem for existing external FDWs, since it's likely they aren't trying to make such paths anyway (though some of them may need a defense against joins with lateral_relids, similar to the one this patch installs into postgres_fdw). Add some assertions in relnode.c to catch future occurrences of the same error --- in particular, as backstop against core-code mistakes like the one fixed by commit bdd9a99. Discussion: https://postgr.es/m/15613-092be1be9576c728@postgresql.org
1 parent 8722d2c commit 9d6d2b2

File tree

6 files changed

+131
-5
lines changed

6 files changed

+131
-5
lines changed

contrib/file_fdw/file_fdw.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ fileGetForeignPaths(PlannerInfo *root,
556556
* Create a ForeignPath node and add it as only possible path. We use the
557557
* fdw_private list of the path to carry the convert_selectively option;
558558
* it will be propagated into the fdw_private list of the Plan node.
559+
*
560+
* We don't support pushing join clauses into the quals of this path, but
561+
* it could still have required parameterization due to LATERAL refs in
562+
* its tlist.
559563
*/
560564
add_path(baserel, (Path *)
561565
create_foreignscan_path(root, baserel,
@@ -564,7 +568,7 @@ fileGetForeignPaths(PlannerInfo *root,
564568
startup_cost,
565569
total_cost,
566570
NIL, /* no pathkeys */
567-
NULL, /* no outer rel either */
571+
baserel->lateral_relids,
568572
NULL, /* no extra plan */
569573
coptions));
570574

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3435,6 +3435,62 @@ select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum fr
34353435
(2 rows)
34363436

34373437
reset enable_hashagg;
3438+
-- bug #15613: bad plan for foreign table scan with lateral reference
3439+
EXPLAIN (VERBOSE, COSTS OFF)
3440+
SELECT ref_0.c2, subq_1.*
3441+
FROM
3442+
"S 1"."T 1" AS ref_0,
3443+
LATERAL (
3444+
SELECT ref_0."C 1" c1, subq_0.*
3445+
FROM (SELECT ref_0.c2, ref_1.c3
3446+
FROM ft1 AS ref_1) AS subq_0
3447+
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
3448+
) AS subq_1
3449+
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
3450+
ORDER BY ref_0."C 1";
3451+
QUERY PLAN
3452+
---------------------------------------------------------------------------------------------------------
3453+
Nested Loop
3454+
Output: ref_0.c2, ref_0."C 1", (ref_0.c2), ref_1.c3, ref_0."C 1"
3455+
-> Nested Loop
3456+
Output: ref_0.c2, ref_0."C 1", ref_1.c3, (ref_0.c2)
3457+
-> Index Scan using t1_pkey on "S 1"."T 1" ref_0
3458+
Output: ref_0."C 1", ref_0.c2, ref_0.c3, ref_0.c4, ref_0.c5, ref_0.c6, ref_0.c7, ref_0.c8
3459+
Index Cond: (ref_0."C 1" < 10)
3460+
-> Foreign Scan on public.ft1 ref_1
3461+
Output: ref_1.c3, ref_0.c2
3462+
Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text))
3463+
-> Materialize
3464+
Output: ref_3.c3
3465+
-> Foreign Scan on public.ft2 ref_3
3466+
Output: ref_3.c3
3467+
Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text))
3468+
(15 rows)
3469+
3470+
SELECT ref_0.c2, subq_1.*
3471+
FROM
3472+
"S 1"."T 1" AS ref_0,
3473+
LATERAL (
3474+
SELECT ref_0."C 1" c1, subq_0.*
3475+
FROM (SELECT ref_0.c2, ref_1.c3
3476+
FROM ft1 AS ref_1) AS subq_0
3477+
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
3478+
) AS subq_1
3479+
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
3480+
ORDER BY ref_0."C 1";
3481+
c2 | c1 | c2 | c3
3482+
----+----+----+-------
3483+
1 | 1 | 1 | 00001
3484+
2 | 2 | 2 | 00001
3485+
3 | 3 | 3 | 00001
3486+
4 | 4 | 4 | 00001
3487+
5 | 5 | 5 | 00001
3488+
6 | 6 | 6 | 00001
3489+
7 | 7 | 7 | 00001
3490+
8 | 8 | 8 | 00001
3491+
9 | 9 | 9 | 00001
3492+
(9 rows)
3493+
34383494
-- Check with placeHolderVars
34393495
explain (verbose, costs off)
34403496
select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b);

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -930,14 +930,17 @@ postgresGetForeignPaths(PlannerInfo *root,
930930
* baserestrict conditions we were able to send to remote, there might
931931
* actually be an indexscan happening there). We already did all the work
932932
* to estimate cost and size of this path.
933+
*
934+
* Although this path uses no join clauses, it could still have required
935+
* parameterization due to LATERAL refs in its tlist.
933936
*/
934937
path = create_foreignscan_path(root, baserel,
935938
NULL, /* default pathtarget */
936939
fpinfo->rows,
937940
fpinfo->startup_cost,
938941
fpinfo->total_cost,
939942
NIL, /* no pathkeys */
940-
NULL, /* no outer rel either */
943+
baserel->lateral_relids,
941944
NULL, /* no extra plan */
942945
NIL); /* no fdw_private list */
943946
add_path(baserel, (Path *) path);
@@ -4978,7 +4981,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
49784981
startup_cost,
49794982
total_cost,
49804983
useful_pathkeys,
4981-
NULL,
4984+
rel->lateral_relids,
49824985
sorted_epq_path,
49834986
NIL));
49844987
}
@@ -5115,6 +5118,13 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
51155118
if (joinrel->fdw_private)
51165119
return;
51175120

5121+
/*
5122+
* This code does not work for joins with lateral references, since those
5123+
* must have parameterized paths, which we don't generate yet.
5124+
*/
5125+
if (!bms_is_empty(joinrel->lateral_relids))
5126+
return;
5127+
51185128
/*
51195129
* Create unfinished PgFdwRelationInfo entry which is used to indicate
51205130
* that the join relation is already considered, so that we won't waste
@@ -5206,7 +5216,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
52065216
startup_cost,
52075217
total_cost,
52085218
NIL, /* no pathkeys */
5209-
NULL, /* no required_outer */
5219+
joinrel->lateral_relids,
52105220
epq_path,
52115221
NIL); /* no fdw_private */
52125222

@@ -5534,7 +5544,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
55345544
startup_cost,
55355545
total_cost,
55365546
NIL, /* no pathkeys */
5537-
NULL, /* no required_outer */
5547+
grouped_rel->lateral_relids,
55385548
NULL,
55395549
NIL); /* no fdw_private */
55405550

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,32 @@ select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum fr
884884
select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1;
885885
reset enable_hashagg;
886886

887+
-- bug #15613: bad plan for foreign table scan with lateral reference
888+
EXPLAIN (VERBOSE, COSTS OFF)
889+
SELECT ref_0.c2, subq_1.*
890+
FROM
891+
"S 1"."T 1" AS ref_0,
892+
LATERAL (
893+
SELECT ref_0."C 1" c1, subq_0.*
894+
FROM (SELECT ref_0.c2, ref_1.c3
895+
FROM ft1 AS ref_1) AS subq_0
896+
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
897+
) AS subq_1
898+
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
899+
ORDER BY ref_0."C 1";
900+
901+
SELECT ref_0.c2, subq_1.*
902+
FROM
903+
"S 1"."T 1" AS ref_0,
904+
LATERAL (
905+
SELECT ref_0."C 1" c1, subq_0.*
906+
FROM (SELECT ref_0.c2, ref_1.c3
907+
FROM ft1 AS ref_1) AS subq_0
908+
RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
909+
) AS subq_1
910+
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
911+
ORDER BY ref_0."C 1";
912+
887913
-- Check with placeHolderVars
888914
explain (verbose, costs off)
889915
select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b);

src/backend/optimizer/util/pathnode.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,27 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
20692069
{
20702070
ForeignPath *pathnode = makeNode(ForeignPath);
20712071

2072+
/*
2073+
* Since the path's required_outer should always include all the rel's
2074+
* lateral_relids, forcibly add those if necessary. This is a bit of a
2075+
* hack, but up till early 2019 the contrib FDWs failed to ensure that,
2076+
* and it's likely that the same error has propagated into many external
2077+
* FDWs. Don't risk modifying the passed-in relid set here.
2078+
*/
2079+
if (rel->lateral_relids && !bms_is_subset(rel->lateral_relids,
2080+
required_outer))
2081+
required_outer = bms_union(required_outer, rel->lateral_relids);
2082+
2083+
/*
2084+
* Although this function is only designed to be used for scans of
2085+
* baserels, before v12 postgres_fdw abused it to make paths for join and
2086+
* upper rels. It will work for such cases as long as required_outer is
2087+
* empty (otherwise get_baserel_parampathinfo does the wrong thing), which
2088+
* fortunately is the expected case for now.
2089+
*/
2090+
if (!bms_is_empty(required_outer) && !IS_SIMPLE_REL(rel))
2091+
elog(ERROR, "parameterized foreign joins are not supported yet");
2092+
20722093
pathnode->path.pathtype = T_ForeignScan;
20732094
pathnode->path.parent = rel;
20742095
pathnode->path.pathtarget = target ? target : rel->reltarget;

src/backend/optimizer/util/relnode.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,9 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
12471247
double rows;
12481248
ListCell *lc;
12491249

1250+
/* If rel has LATERAL refs, every path for it should account for them */
1251+
Assert(bms_is_subset(baserel->lateral_relids, required_outer));
1252+
12501253
/* Unparameterized paths have no ParamPathInfo */
12511254
if (bms_is_empty(required_outer))
12521255
return NULL;
@@ -1342,6 +1345,9 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
13421345
double rows;
13431346
ListCell *lc;
13441347

1348+
/* If rel has LATERAL refs, every path for it should account for them */
1349+
Assert(bms_is_subset(joinrel->lateral_relids, required_outer));
1350+
13451351
/* Unparameterized paths have no ParamPathInfo or extra join clauses */
13461352
if (bms_is_empty(required_outer))
13471353
return NULL;
@@ -1533,6 +1539,9 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
15331539
{
15341540
ParamPathInfo *ppi;
15351541

1542+
/* If rel has LATERAL refs, every path for it should account for them */
1543+
Assert(bms_is_subset(appendrel->lateral_relids, required_outer));
1544+
15361545
/* Unparameterized paths have no ParamPathInfo */
15371546
if (bms_is_empty(required_outer))
15381547
return NULL;

0 commit comments

Comments
 (0)