postgres_fdw planning issue: EquivalenceClass changes confuse it

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: postgres_fdw planning issue: EquivalenceClass changes confuse it
Date: 2022-12-21 21:57:18
Message-ID: 1691374.1671659838@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

I discovered that if you do this:

diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2e6f7f4852..2ae231fd90 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -366,7 +366,7 @@ CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
BEGIN
RETURN abs($1);
END
-$$ LANGUAGE plpgsql IMMUTABLE;
+$$ LANGUAGE plpgsql IMMUTABLE STRICT;
CREATE OPERATOR === (
LEFTARG = int,
RIGHTARG = int,

one of the plan changes that you get (attached) is that this query:

EXPLAIN (VERBOSE, COSTS OFF)
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;

can no longer do the join as a foreign join. There are some other
changes that are more explicable, because strictness of the function
allows the planner to strength-reduce full joins to left joins, but
what happened here?

The answer is that once postgres_fdw_abs() is marked strict,
the EquivalenceClass machinery will group these clauses as an
EquivalenceClass:

ft1.c1 = ft2.c1 AND ft1.c1 = postgres_fdw_abs(ft2.c2)

which it will then choose to implement as a restriction clause
on ft2
ft2.c1 = postgres_fdw_abs(ft2.c2)
followed by a join clause
ft1.c1 = ft2.c1
This is a good and useful transformation, because it can get rid
of ft2 rows at the scan level instead of waiting for them to be
joined. However, because we are treating postgres_fdw_abs() as
non-shippable in this particular test case, that means that ft2
now has a non-shippable restriction clause, causing foreign_join_ok
to give up here:

/*
* If joining relations have local conditions, those conditions are
* required to be applied before joining the relations. Hence the join can
* not be pushed down.
*/
if (fpinfo_o->local_conds || fpinfo_i->local_conds)
return false;

In the other formulation, "ft1.c1 = postgres_fdw_abs(ft2.c2)"
is a non-shippable join clause, which foreign_join_ok knows how
to cope with. So this seems like a fairly nasty asymmetry.

I ran into this while experimenting with the next phase in my
outer-join-vars patch set, in which the restriction that
below-outer-join Equivalence classes contain only strict members
will go away. So that breaks this test, and I need to either
fix postgres_fdw or change the test case.

I experimented with teaching foreign_join_ok to pull up the child rels'
local_conds to be join local_conds if the join is an inner join,
which seems like a legal transformation. I ran into a couple of
issues though, the hardest of which to solve is that in DML queries
we get "variable not found in subplan target lists" failures while
trying to build some EPQ queries. That's because the pulled-up
condition uses a variable that we didn't think we'd need at the join
level. That could possibly be fixed by handling these conditions
differently for the transmitted query than the EPQ query, but I'm
not sufficiently familiar with the postgres_fdw code to want to
take point on coding that. In any case, this line of thought
would lead to several other plan changes in the postgres_fdw
regression tests, and I'm not sure if any of those would be
breaking the intent of the test cases.

Or I could just hack this one test so that it continues to
not be an EquivalenceClass case.

Thoughts?

regards, tom lane

Attachment Content-Type Size
regression.diffs text/x-diff 8.9 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Andrew Dunstan 2022-12-21 22:01:30 Re: float4in_internal
Previous Message Magnus Hagander 2022-12-21 21:22:02 Re: Add LSN along with offset to error messages reported for WAL file read/write/validate header failures