diff options
author | Alexander Korotkov <akorotkov@postgresql.org> | 2023-12-05 22:53:12 +0200 |
---|---|---|
committer | Alexander Korotkov <akorotkov@postgresql.org> | 2023-12-05 22:53:12 +0200 |
commit | 824dbea3e41efa3b35094163c834988dea7eb139 (patch) | |
tree | f810eb49e750dee3b601385328faa0f4369291f6 /contrib/postgres_fdw/postgres_fdw.c | |
parent | 278eb13c48236c261ed4bab1cb4696321e346eb7 (diff) | |
download | postgresql-824dbea3e41efa3b35094163c834988dea7eb139.tar.gz postgresql-824dbea3e41efa3b35094163c834988dea7eb139.zip |
Add support for deparsing semi-joins to contrib/postgres_fdw
SEMI-JOIN is deparsed as the EXISTS subquery. It references outer and inner
relations, so it should be evaluated as the condition in the upper-level WHERE
clause. The signatures of deparseFromExprForRel() and deparseRangeTblRef() are
revised so that they can add conditions to the upper level.
PgFdwRelationInfo now has a hidden_subquery_rels field, referencing the relids
used in the inner parts of semi-join. They can't be referred to from upper
relations and should be used internally for equivalence member searches.
The planner can create semi-join, which refers to inner rel vars in its target
list. However, we deparse semi-join as an exists() subquery. So we skip the
case when the target list references to inner rel of semi-join.
Author: Alexander Pyhalov
Reviewed-by: Ashutosh Bapat, Ian Lawrence Barwick, Yuuki Fujii, Tomas Vondra
Discussion: https://postgr.es/m/c9e2a757cf3ac2333714eaf83a9cc184@postgrespro.ru
Diffstat (limited to 'contrib/postgres_fdw/postgres_fdw.c')
-rw-r--r-- | contrib/postgres_fdw/postgres_fdw.c | 94 |
1 files changed, 90 insertions, 4 deletions
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6de2bec3b7b..e9144beb62d 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -779,6 +779,7 @@ postgresGetForeignRelSize(PlannerInfo *root, fpinfo->make_outerrel_subquery = false; fpinfo->make_innerrel_subquery = false; fpinfo->lower_subquery_rels = NULL; + fpinfo->hidden_subquery_rels = NULL; /* Set the relation index. */ fpinfo->relation_index = baserel->relid; } @@ -5725,6 +5726,45 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) } /* + * Check if reltarget is safe enough to push down semi-join. Reltarget is not + * safe, if it contains references to inner rel relids, which do not belong to + * outer rel. + */ +static bool +semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel) +{ + List *vars; + ListCell *lc; + bool ok = true; + + Assert(joinrel->reltarget); + + vars = pull_var_clause((Node *) joinrel->reltarget->exprs, PVC_INCLUDE_PLACEHOLDERS); + + foreach(lc, vars) + { + Var *var = (Var *) lfirst(lc); + + if (!IsA(var, Var)) + continue; + + if (bms_is_member(var->varno, innerrel->relids) && + !bms_is_member(var->varno, outerrel->relids)) + { + /* + * The planner can create semi-join, which refers to inner rel + * vars in its target list. However, we deparse semi-join as an + * exists() subquery, so can't handle references to inner rel in + * the target list. + */ + ok = false; + break; + } + } + return ok; +} + +/* * Assess whether the join between inner and outer relations can be pushed down * to the foreign server. As a side effect, save information we obtain in this * function to PgFdwRelationInfo passed in. @@ -5741,12 +5781,19 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, List *joinclauses; /* - * 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. + * We support pushing down INNER, LEFT, RIGHT, FULL OUTER and SEMI joins. + * Constructing queries representing ANTI joins is hard, hence not + * considered right now. */ if (jointype != JOIN_INNER && jointype != JOIN_LEFT && - jointype != JOIN_RIGHT && jointype != JOIN_FULL) + jointype != JOIN_RIGHT && jointype != JOIN_FULL && + jointype != JOIN_SEMI) + return false; + + /* + * We can't push down semi-join if its reltarget is not safe + */ + if ((jointype == JOIN_SEMI) && !semijoin_target_ok(root, joinrel, outerrel, innerrel)) return false; /* @@ -5858,6 +5905,8 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, Assert(bms_is_subset(fpinfo_i->lower_subquery_rels, innerrel->relids)); fpinfo->lower_subquery_rels = bms_union(fpinfo_o->lower_subquery_rels, fpinfo_i->lower_subquery_rels); + fpinfo->hidden_subquery_rels = bms_union(fpinfo_o->hidden_subquery_rels, + fpinfo_i->hidden_subquery_rels); /* * Pull the other remote conditions from the joining relations into join @@ -5871,6 +5920,12 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, * the joinclauses, since they need to be evaluated while constructing the * join. * + * For SEMI-JOIN clauses from inner relation can not be added to + * remote_conds, but should be treated as join clauses (as they are + * deparsed to EXISTS subquery, where inner relation can be referred). A + * list of relation ids, which can't be referred to from higher levels, is + * preserved as a hidden_subquery_rels list. + * * For a FULL OUTER JOIN, the other clauses from either relation can not * be added to the joinclauses or remote_conds, since each relation acts * as an outer relation for the other. @@ -5901,6 +5956,16 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, fpinfo_i->remote_conds); break; + case JOIN_SEMI: + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo_i->remote_conds); + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo->remote_conds); + fpinfo->remote_conds = list_copy(fpinfo_o->remote_conds); + fpinfo->hidden_subquery_rels = bms_union(fpinfo->hidden_subquery_rels, + innerrel->relids); + break; + case JOIN_FULL: /* @@ -5943,6 +6008,24 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, fpinfo->joinclauses = fpinfo->remote_conds; fpinfo->remote_conds = NIL; } + else if (jointype == JOIN_LEFT || jointype == JOIN_RIGHT || jointype == JOIN_FULL) + { + /* + * Conditions, generated from semi-joins, should be evaluated before + * LEFT/RIGHT/FULL join. + */ + if (!bms_is_empty(fpinfo_o->hidden_subquery_rels)) + { + fpinfo->make_outerrel_subquery = true; + fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, outerrel->relids); + } + + if (!bms_is_empty(fpinfo_i->hidden_subquery_rels)) + { + fpinfo->make_innerrel_subquery = true; + fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, innerrel->relids); + } + } /* Mark that this join can be pushed down safely */ fpinfo->pushdown_safe = true; @@ -7692,6 +7775,8 @@ find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) { ListCell *lc; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + foreach(lc, ec->ec_members) { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); @@ -7702,6 +7787,7 @@ find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) */ if (bms_is_subset(em->em_relids, rel->relids) && !bms_is_empty(em->em_relids) && + bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) && is_foreign_expr(root, rel, em->em_expr)) return em; } |