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/deparse.c | |
parent | 278eb13c48236c261ed4bab1cb4696321e346eb7 (diff) |
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/deparse.c')
-rw-r--r-- | contrib/postgres_fdw/deparse.c | 234 |
1 files changed, 188 insertions, 46 deletions
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 09fd489a901..9de4db3d957 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -180,11 +180,15 @@ static void appendConditions(List *exprs, deparse_expr_cxt *context); static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, Index ignore_rel, List **ignore_conds, + List **additional_conds, List **params_list); +static void appendWhereClause(List *exprs, List *additional_conds, + deparse_expr_cxt *context); static void deparseFromExpr(List *quals, deparse_expr_cxt *context); static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, - Index ignore_rel, List **ignore_conds, List **params_list); + Index ignore_rel, List **ignore_conds, + List **additional_conds, List **params_list); static void deparseAggref(Aggref *node, deparse_expr_cxt *context); static void appendGroupByClause(List *tlist, deparse_expr_cxt *context); static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, @@ -1370,6 +1374,7 @@ deparseFromExpr(List *quals, deparse_expr_cxt *context) { StringInfo buf = context->buf; RelOptInfo *scanrel = context->scanrel; + List *additional_conds = NIL; /* For upper relations, scanrel must be either a joinrel or a baserel */ Assert(!IS_UPPER_REL(context->foreignrel) || @@ -1379,14 +1384,11 @@ deparseFromExpr(List *quals, deparse_expr_cxt *context) appendStringInfoString(buf, " FROM "); deparseFromExprForRel(buf, context->root, scanrel, (bms_membership(scanrel->relids) == BMS_MULTIPLE), - (Index) 0, NULL, context->params_list); - - /* Construct WHERE clause */ - if (quals != NIL) - { - appendStringInfoString(buf, " WHERE "); - appendConditions(quals, context); - } + (Index) 0, NULL, &additional_conds, + context->params_list); + appendWhereClause(quals, additional_conds, context); + if (additional_conds != NIL) + list_free_deep(additional_conds); } /* @@ -1598,6 +1600,42 @@ appendConditions(List *exprs, deparse_expr_cxt *context) reset_transmission_modes(nestlevel); } +/* + * Append WHERE clause, containing conditions from exprs and additional_conds, + * to context->buf. + */ +static void +appendWhereClause(List *exprs, List *additional_conds, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool need_and = false; + ListCell *lc; + + if (exprs != NIL || additional_conds != NIL) + appendStringInfoString(buf, " WHERE "); + + /* + * If there are some filters, append them. + */ + if (exprs != NIL) + { + appendConditions(exprs, context); + need_and = true; + } + + /* + * If there are some EXISTS conditions, coming from SEMI-JOINS, append + * them. + */ + foreach(lc, additional_conds) + { + if (need_and) + appendStringInfoString(buf, " AND "); + appendStringInfoString(buf, (char *) lfirst(lc)); + need_and = true; + } +} + /* Output join name for given join type */ const char * get_jointype_name(JoinType jointype) @@ -1616,6 +1654,9 @@ get_jointype_name(JoinType jointype) case JOIN_FULL: return "FULL"; + case JOIN_SEMI: + return "SEMI"; + default: /* Shouldn't come here, but protect from buggy code. */ elog(ERROR, "unsupported join type %d", jointype); @@ -1712,11 +1753,14 @@ deparseSubqueryTargetList(deparse_expr_cxt *context) * of DELETE; it deparses the join relation as if the relation never contained * the target relation, and creates a List of conditions to be deparsed into * the top-level WHERE clause, which is returned to *ignore_conds. + * + * 'additional_conds' is a pointer to a list of strings to be appended to + * the WHERE clause, coming from lower-level SEMI-JOINs. */ static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, Index ignore_rel, List **ignore_conds, - List **params_list) + List **additional_conds, List **params_list) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; @@ -1728,6 +1772,8 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, RelOptInfo *innerrel = fpinfo->innerrel; bool outerrel_is_target = false; bool innerrel_is_target = false; + List *additional_conds_i = NIL; + List *additional_conds_o = NIL; if (ignore_rel > 0 && bms_is_member(ignore_rel, foreignrel->relids)) { @@ -1764,7 +1810,8 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, initStringInfo(&join_sql_o); deparseRangeTblRef(&join_sql_o, root, outerrel, fpinfo->make_outerrel_subquery, - ignore_rel, ignore_conds, params_list); + ignore_rel, ignore_conds, &additional_conds_o, + params_list); /* * If inner relation is the target relation, skip deparsing it. @@ -1780,6 +1827,12 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, Assert(fpinfo->jointype == JOIN_INNER); Assert(fpinfo->joinclauses == NIL); appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); + /* Pass EXISTS conditions to upper level */ + if (additional_conds_o != NIL) + { + Assert(*additional_conds == NIL); + *additional_conds = additional_conds_o; + } return; } } @@ -1790,7 +1843,54 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, initStringInfo(&join_sql_i); deparseRangeTblRef(&join_sql_i, root, innerrel, fpinfo->make_innerrel_subquery, - ignore_rel, ignore_conds, params_list); + ignore_rel, ignore_conds, &additional_conds_i, + params_list); + + /* + * 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. We deparse the + * condition and pass it to upper level callers as an + * additional_conds list. Upper level callers are responsible for + * inserting conditions from the list where appropriate. + */ + if (fpinfo->jointype == JOIN_SEMI) + { + deparse_expr_cxt context; + StringInfoData str; + + /* Construct deparsed condition from this SEMI-JOIN */ + initStringInfo(&str); + appendStringInfo(&str, "EXISTS (SELECT NULL FROM %s", + join_sql_i.data); + + context.buf = &str; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + + /* + * Append SEMI-JOIN clauses and EXISTS conditions from lower + * levels to the current EXISTS subquery + */ + appendWhereClause(fpinfo->joinclauses, additional_conds_i, &context); + + /* + * EXISTS conditions, coming from lower join levels, have just + * been processed. + */ + if (additional_conds_i != NIL) + { + list_free_deep(additional_conds_i); + additional_conds_i = NIL; + } + + /* Close parentheses for EXISTS subquery */ + appendStringInfo(&str, ")"); + + *additional_conds = lappend(*additional_conds, str.data); + } /* * If outer relation is the target relation, skip deparsing it. @@ -1801,6 +1901,12 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, Assert(fpinfo->jointype == JOIN_INNER); Assert(fpinfo->joinclauses == NIL); appendBinaryStringInfo(buf, join_sql_i.data, join_sql_i.len); + /* Pass EXISTS conditions to the upper call */ + if (additional_conds_i != NIL) + { + Assert(*additional_conds == NIL); + *additional_conds = additional_conds_i; + } return; } } @@ -1809,33 +1915,65 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, Assert(!outerrel_is_target && !innerrel_is_target); /* - * For a join relation FROM clause entry is deparsed as - * - * ((outer relation) <join type> (inner relation) ON (joinclauses)) + * For semijoin FROM clause is deparsed as an outer relation. An inner + * relation and join clauses are converted to EXISTS condition and + * passed to the upper level. */ - appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, - get_jointype_name(fpinfo->jointype), join_sql_i.data); - - /* Append join clause; (TRUE) if no join clause */ - if (fpinfo->joinclauses) + if (fpinfo->jointype == JOIN_SEMI) { - deparse_expr_cxt context; + appendStringInfo(buf, "%s", join_sql_o.data); + } + else + { + /* + * For a join relation FROM clause, entry is deparsed as + * + * ((outer relation) <join type> (inner relation) ON + * (joinclauses)) + */ + appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, + get_jointype_name(fpinfo->jointype), join_sql_i.data); - context.buf = buf; - context.foreignrel = foreignrel; - context.scanrel = foreignrel; - context.root = root; - context.params_list = params_list; + /* Append join clause; (TRUE) if no join clause */ + if (fpinfo->joinclauses) + { + deparse_expr_cxt context; - appendStringInfoChar(buf, '('); - appendConditions(fpinfo->joinclauses, &context); + context.buf = buf; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + + appendStringInfoChar(buf, '('); + appendConditions(fpinfo->joinclauses, &context); + appendStringInfoChar(buf, ')'); + } + else + appendStringInfoString(buf, "(TRUE)"); + + /* End the FROM clause entry. */ appendStringInfoChar(buf, ')'); } - else - appendStringInfoString(buf, "(TRUE)"); - /* End the FROM clause entry. */ - appendStringInfoChar(buf, ')'); + /* + * Construct additional_conds to be passed to the upper caller from + * current level additional_conds and additional_conds, coming from + * inner and outer rels. + */ + if (additional_conds_o != NIL) + { + *additional_conds = list_concat(*additional_conds, + additional_conds_o); + list_free(additional_conds_o); + } + + if (additional_conds_i != NIL) + { + *additional_conds = list_concat(*additional_conds, + additional_conds_i); + list_free(additional_conds_i); + } } else { @@ -1863,11 +2001,13 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, /* * Append FROM clause entry for the given relation into buf. + * Conditions from lower-level SEMI-JOINs are appended to additional_conds + * and should be added to upper level WHERE clause. */ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, Index ignore_rel, List **ignore_conds, - List **params_list) + List **additional_conds, List **params_list) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; @@ -1925,7 +2065,8 @@ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, } else deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, - ignore_conds, params_list); + ignore_conds, additional_conds, + params_list); } /* @@ -2148,6 +2289,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, RangeTblEntry *rte = planner_rt_fetch(rtindex, root); ListCell *lc, *lc2; + List *additional_conds = NIL; /* Set up context struct for recursion */ context.root = root; @@ -2189,17 +2331,17 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, { List *ignore_conds = NIL; + appendStringInfoString(buf, " FROM "); deparseFromExprForRel(buf, root, foreignrel, true, rtindex, - &ignore_conds, params_list); + &ignore_conds, &additional_conds, params_list); remote_conds = list_concat(remote_conds, ignore_conds); } - if (remote_conds) - { - appendStringInfoString(buf, " WHERE "); - appendConditions(remote_conds, &context); - } + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); if (foreignrel->reloptkind == RELOPT_JOINREL) deparseExplicitTargetList(returningList, true, retrieved_attrs, @@ -2255,6 +2397,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, List **retrieved_attrs) { deparse_expr_cxt context; + List *additional_conds = NIL; /* Set up context struct for recursion */ context.root = root; @@ -2274,15 +2417,14 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, " USING "); deparseFromExprForRel(buf, root, foreignrel, true, rtindex, - &ignore_conds, params_list); + &ignore_conds, &additional_conds, params_list); remote_conds = list_concat(remote_conds, ignore_conds); } - if (remote_conds) - { - appendStringInfoString(buf, " WHERE "); - appendConditions(remote_conds, &context); - } + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); if (foreignrel->reloptkind == RELOPT_JOINREL) deparseExplicitTargetList(returningList, true, retrieved_attrs, |