| From: | Alexander Korotkov <aekorotkov(at)gmail(dot)com> |
|---|---|
| To: | Alexander Pyhalov <a(dot)pyhalov(at)postgrespro(dot)ru> |
| Cc: | solaimurugan vellaipandiyan <drsolaimurugan(dot)v(at)gmail(dot)com>, Álvaro Herrera <alvherre(at)kurilemu(dot)de>, g(dot)kashkin(at)postgrespro(dot)ru, Ashutosh Bapat <ashutosh(dot)bapat(dot)oss(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: Function scan FDW pushdown |
| Date: | 2026-07-30 21:19:03 |
| Message-ID: | CAPpHfdvhMtkY-MJ5M3_JjN5wPA7_ZoYw_vmdm97ZEvQ33rCT0g@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On Tue, Jul 7, 2026 at 12:25 PM Alexander Pyhalov
<a(dot)pyhalov(at)postgrespro(dot)ru> wrote:
>
> Alexander Korotkov писал(а) 2026-07-06 17:11:
> > Hi, Alexander!
> >
> > On Wed, May 20, 2026 at 1:17 PM Alexander Pyhalov
> > <a(dot)pyhalov(at)postgrespro(dot)ru> wrote:
> >> I've found another issue. The fact that in the new versions of the
> >> patch
> >> RTE RelOptInfo misses fdw_private seems to be unfortunate. For
> >> example,
> >> in the last version we haven't thought about classifying
> >> baserestrictinfo. And if we do, we should pass fdw_private down to
> >> foreign_expr_walker. Perhaps, we could attach it to RTE_FUNCTION rel
> >> prior to calling classifyConditions(), but should we later set it back
> >> to NULL? Another problem comes if we try to handle joins, which can
> >> crearte subqueries (like INNER/OUTER UNIQUE). In this case we should
> >> somehow cook fpinfo for get_relation_column_alias_ids(). Attaching
> >> patch
> >> which tries to handle baserestrictinfos by passing fpinfo down to
> >> foreign_expr_walker().
> >
> > I think passing fpinfo down to foreign_expr_walker() is the right
> > approach, thank you. Attaching it to RTE_FUNCTION rel, and reseting
> > it back to NULL would create excessive state and potential source of
> > bugs. The only advantage is saving existing function signatures, but
> > it doesn't worth it.
> >
> > Also, I don't think we need to care bout
> > get_relation_column_alias_ids(). As we currently pushdown function
> > only for inner joins, make_outerrel_subquery/make_innerrel_subquery
> > are always false, lower_subquery_rels also wouldn't contain
> > RTE_FUNCTION relid.
> > Therefore:
> >
> > * In deparseFromExprForRel() for functional rel deparseRangeTblRef()
> > is always called with make_subquery=false.
> > * is_subquery_var() returns false.
>
> Well, at least current patch version classifies conditions correctly.
>
> > Other changes I made:
> >
> > 1. In foreign_join_ok(), the function side's baserestrictinfo was
> > classified against the joinrel fpinfo, whose
> > server/shippable_extensions aren't set until merge_fdw_options() runs
> > later — so extension-shippable quals would be misclassified as local
> > and silently kill the pushdown. Now it: (a) copies
> > shippable_extensions from the foreign side onto the stub, and (b)
> > classifies against the stub (fpinfo_i/fpinfo_o, which already has
> > server). Built-in quals like n > 3 were unaffected; this fixes the
> > extension case. I've added the test case for this.
> >
> > 2. Dropped unused funcid. The per-RTE metadata was a 3-element list
> > (funcid, funcrettype, funccollation) but funcid was never read.
> > Reduced to list_make2(funcrettype, funccollation); updated the
> > consumer (linitial/lsecond) and both descriptive comments.
>
> With funcid we could print actual function name in explain.
> Now we print, for example,
>
> Relations: ((public.distr1_part_3 d1_3) INNER JOIN (public.distr2_part_3
> d2_3)) INNER JOIN (Function n)
>
> If funcid is preserved, we could output actual function name instead of
> alias. This is more similar to
> how we show relation names. Attaching updated patch, which preserves
> function names in explain.
>
> After looking at it once more, I'm not convinced when we can get
> not-FuncExpr in rtfunc->funcexpr.
> So added more checks to function_rte_pushdown_ok() to avoid dealing with
> such case.
I agree on your changes. I've put some additional.
1) Extracted initialization of stub fpinfo for function RTE into
init_func_stub_fpinfo(). The stub now inherits fdw_startup_cost,
fdw_tuple_cost, use_remote_estimate, fetch_size and async_capable from
the foreign side, in addition to server and shippable_extensions.
Without this, merge_fdw_options() and the cost estimator would pick up
zeroed values of fdw_startup_cost and fdw_tuple_cost when the function
RTEis on the outer side of the join, so the pushdown path appears to
be artificially cheap.
2) Added a comment on the whole-row Var CASE WHEN wrapper in
deparseColumnRef() explaining that it is a no-op under the current
INNER-only policy but kept to stay correct for future outer-join and
null-supply scenarios. Please, check if you agree with this.
3) Documented the new pushdown case in postgres-fdw.sgml, listing the
conditions under which a foreign-table x set-returning-function INNER
join is absorbed into the remote query.
4) Added a regression test verifying that a STABLE (not IMMUTABLE)
set- returning function is rejected for pushdown, alongside the
existing VOLATILE-rejection test.
------
Regards,
Alexander Korotkov
Supabase
| Attachment | Content-Type | Size |
|---|---|---|
| v12-0001-postgres_fdw-push-down-FUNCTION-RTE-into-foreign.patch | application/octet-stream | 87.3 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Greg Lamberson | 2026-07-30 21:46:11 | Re: Extensible sync handler registration (register_sync_handler) |
| Previous Message | Greg Burd | 2026-07-30 21:17:35 | Re: [PATCH] Batched clock sweep to reduce cross-socket atomic contention |