Thread

  1. Re: Function scan FDW pushdown

    Alexander Pyhalov <a.pyhalov@postgrespro.ru> — 2026-05-18T20:06:31Z

    Alexander Korotkov писал(а) 2026-05-18 13:34:
    > Hi, Alexander!
    > 
    > The revised patch is attached.
    > 
    > On Tue, May 12, 2026 at 11:09 AM Alexander Pyhalov
    > <a.pyhalov@postgrespro.ru> wrote:
    >> 1) deparseColumnRef() doesn't account for whole row vars.
    >> In queries like
    >> 
    >> UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))']) 
    >> AS
    >> t (bx) WHERE r.a = area(t.bx)
    >> 
    >> it fails with assert that varattno should be > 0. When we lock
    >> non-relation RTE, we select whole row var, and we have to deparse it 
    >> for
    >> function RTE.
    >> 
    >> You've removed check for function return type. This seems to be
    >> dangerous. Old example
    >> 
    >> CREATE OR REPLACE FUNCTION f_ret_record() RETURNS record AS $$ SELECT
    >> (1,2)::record $$ language SQL IMMUTABLE;
    >> ALTER EXTENSION postgres_fdw ADD function f_ret_record();
    >> EXPLAIN (VERBOSE, COSTS OFF)
    >> SELECT s FROM remote_tbl rt, f_ret_record() AS s(a int, b int)
    >> WHERE s.a = rt.a;
    >> 
    >> fails with
    >> 
    >> ERROR:  a column definition list is required for functions returning
    >> "record"
    > 
    > function_rte_pushdown_ok() now calls get_expr_result_type() and
    > rejects anything that isn't TYPEFUNC_SCALAR (also RECORDOID/VOIDOID),
    > so f_ret_record() no longer reaches the remote side.
    > deparseColumnRef() now handles varattno == 0 for RTE_FUNCTION and
    > emits ROW(f<rti>.c1, ..., f<rti>.c<N>) from rte->eref->colnames.
    > 
    >> 2) postgresBeginForeignScan() can step on function RTE, and doesn't 
    >> know
    >> what to do with it:
    >> SELECT * FROM unnest(array[2,3,4]) n, remote_tbl r WHERE r.a = n;
    >> ERROR:  cache lookup failed for foreign table 0
    >> 
    >> So, we need to look for the first RTE_RELATION, as in older patch
    >> version.
    > 
    > The scanrelid == 0 branch in postgresBeginForeignScan() now scans
    > fs_base_relids until it finds an RTE_RELATION.  An explicit
    > elog(ERROR) guards the (theoretically impossible) case where no
    > foreign RTE is found.
    > 
    >> 3) A lot of complexity in the old patch version was in making it
    >> possible to find out RTE_FUNCTION attribute types after planing, as 
    >> it's
    >> necessary to correctly handle joins. In this version
    >> get_tupdesc_for_join_scan_tuples() doesn't handle function RTEs.  This
    >> means, when we try to find out type for attribute types for joins, 
    >> we'll
    >> get errors. This can be seen in queries like
    >> 
    >> UPDATE remote_tbl r SET b=CASE WHEN random()>=0 THEN 5 ELSE 0 END FROM
    >> UNNEST(array[box '((2,3),(-2,-3))']) AS t (bx) WHERE r.a = area(t.bx)
    >>   RETURNING a,b;
    >> 
    >> Now it fails on earlier stages (with "column f2.c0 does not exist"), 
    >> but
    >> if we fix it, we'll get something like
    >> "ERROR:  input of anonymous composite types is not implemented"
    >> 
    >> Overall, function_rte_pushdown_ok() now allows more strange
    >> constructions. Could it skip Vars from outside of joinrel->relids? Can
    >> we safely ship function with parameters in arguments? I'm not sure.
    > 
    > Restored the per-function metadata you had in v2/v3.
    > FdwScanPrivateFunctions (list of (funcid, funcrettype, funccollation)
    > indexed by RTI-offset) and FdwScanPrivateMinRTIndex are now saved in
    > fdw_private by postgresGetForeignPlan().
    > get_tupdesc_for_join_scan_tuples() now has an RTE_FUNCTION branch that
    > rebuilds the tuple descriptor from this metadata, exactly as in your
    > patch.
    
    Hi. I am a bit confused about this comment (and code):
    
                            /*
                             * DirectModify on a foreign join: pass NIL/0 for 
    the function
                             * metadata.  We don't currently push function 
    RTEs through the
                             * direct-modify path, so there are no whole-row 
    Vars pointing at
                             * function-RTE tuples to reconstruct.
                             */
                            tupdesc = get_tupdesc_for_join_scan_tuples(node, 
    NIL, 0);
    
    We evidently go through this code path when executing example
    
    UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))']) AS 
    t (bx) WHERE r.a = area(t.bx)
      RETURNING a,b;
    
    But don't need whole row var in returning list.... However, we still can 
    step on this issue.
    
    UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))'], 
    array[int '1']) AS t (bx,  i) WHERE r.a = area(t.bx)
    RETURNING a,b,t;
    
    ERROR:  input of anonymous composite types is not implemented
    CONTEXT:  whole-row reference to foreign table "t"
    
    -- 
    Best regards,
    Alexander Pyhalov,
    Postgres Professional