aboutsummaryrefslogtreecommitdiff
path: root/contrib/postgres_fdw/postgres_fdw.c
diff options
context:
space:
mode:
authorAlexander Korotkov <akorotkov@postgresql.org>2023-12-05 22:53:12 +0200
committerAlexander Korotkov <akorotkov@postgresql.org>2023-12-05 22:53:12 +0200
commit824dbea3e41efa3b35094163c834988dea7eb139 (patch)
treef810eb49e750dee3b601385328faa0f4369291f6 /contrib/postgres_fdw/postgres_fdw.c
parent278eb13c48236c261ed4bab1cb4696321e346eb7 (diff)
downloadpostgresql-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.c94
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;
}