Re: join pushdown and issue with foreign update - Mailing list pgsql-hackers

From Tom Lane
Subject Re: join pushdown and issue with foreign update
Date
Msg-id 2854976.1622583171@sss.pgh.pa.us
Whole thread Raw
In response to Re: join pushdown and issue with foreign update  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: join pushdown and issue with foreign update
Re: join pushdown and issue with foreign update
Re: join pushdown and issue with foreign update
List pgsql-hackers
I wrote:
> I think a preferable fix involves making sure that the correct
> record-type typmod is propagated to record_in in this context.
> Alternatively, maybe we could insert the foreign table's rowtype
> during execution of the input operation, without touching the
> plan as such.

Here's a draft-quality patch based on that idea.  It resolves
the offered test case, but I haven't beat on it beyond that.

            regards, tom lane

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7df30010f2..79bc08efb4 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -10231,3 +10231,50 @@ DROP TABLE result_tbl;
 DROP TABLE join_tbl;
 ALTER SERVER loopback OPTIONS (DROP async_capable);
 ALTER SERVER loopback2 OPTIONS (DROP async_capable);
+CREATE TABLE base_tbl (a int, b int);
+CREATE FOREIGN TABLE remote_tbl (a int, b int)
+  SERVER loopback OPTIONS (table_name 'base_tbl');
+INSERT INTO base_tbl SELECT a, a+1 FROM generate_series(1,10) a;
+ANALYZE base_tbl;
+ANALYZE remote_tbl;
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE remote_tbl d SET a = CASE WHEN random() >= 0 THEN 5 ELSE 6 END
+  FROM remote_tbl AS t WHERE d.a = t.a;
+
 QUERY PLAN
            

+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.remote_tbl d
+   Remote SQL: UPDATE public.base_tbl SET a = $2 WHERE ctid = $1
+   ->  Foreign Scan
+         Output: CASE WHEN (random() >= '0'::double precision) THEN 5 ELSE 6 END, d.ctid, d.*, t.*
+         Relations: (public.remote_tbl d) INNER JOIN (public.remote_tbl t)
+         Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.a, r1.b) END, CASE WHEN
(r2.*)::textIS NOT NULL THEN ROW(r2.a, r2.b) END FROM (public.base_tbl r1 INNER JOIN public.base_tbl r2 ON (((r1.a =
r2.a))))FOR UPDATE OF r1 
+         ->  Hash Join
+               Output: d.ctid, d.*, t.*
+               Hash Cond: (d.a = t.a)
+               ->  Foreign Scan on public.remote_tbl d
+                     Output: d.ctid, d.*, d.a
+                     Remote SQL: SELECT a, b, ctid FROM public.base_tbl FOR UPDATE
+               ->  Hash
+                     Output: t.*, t.a
+                     ->  Foreign Scan on public.remote_tbl t
+                           Output: t.*, t.a
+                           Remote SQL: SELECT a, b FROM public.base_tbl
+(17 rows)
+
+UPDATE remote_tbl d SET a = CASE WHEN random() >= 0 THEN 5 ELSE 6 END
+  FROM remote_tbl AS t WHERE d.a = t.a;
+SELECT * FROM base_tbl ORDER BY b;
+ a | b
+---+----
+ 5 |  2
+ 5 |  3
+ 5 |  4
+ 5 |  5
+ 5 |  6
+ 5 |  7
+ 5 |  8
+ 5 |  9
+ 5 | 10
+ 5 | 11
+(10 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c48a421e88..24ba60e00a 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1439,6 +1439,57 @@ postgresGetForeignPlan(PlannerInfo *root,
                             outer_plan);
 }

+/*
+ * Construct a tuple descriptor for the scan tuples handled by a foreign join.
+ */
+static TupleDesc
+get_tupdesc_for_join_scan_tuples(ForeignScanState *node)
+{
+    ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
+    EState       *estate = node->ss.ps.state;
+    TupleDesc    tupdesc;
+
+    /*
+     * The core code has already set up a scan tuple slot based on
+     * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough,
+     * but there's one case where it isn't.  If we have any whole-row row
+     * identifier Vars, they may have vartype RECORD, and we need to replace
+     * that with the associated table's actual composite type.  This ensures
+     * that when we read those ROW() expression values from the remote server,
+     * we can convert them to a composite type the local server knows.
+     */
+    tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
+    for (int i = 0; i < tupdesc->natts; i++)
+    {
+        Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+        Var           *var;
+        RangeTblEntry *rte;
+        Oid            reltype;
+
+        /* Nothing to do if it's not a generic RECORD attribute */
+        if (att->atttypid != RECORDOID || att->atttypmod >= 0)
+            continue;
+
+        /*
+         * If we can't identify the referenced table, do nothing.  This'll
+         * likely lead to failure later, but perhaps we can muddle through.
+         */
+        var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
+                                    i)->expr;
+        if (!IsA(var, Var))
+            continue;
+        rte = list_nth(estate->es_range_table, var->varno - 1);
+        if (rte->rtekind != RTE_RELATION)
+            continue;
+        reltype = get_rel_type_id(rte->relid);
+        if (!OidIsValid(reltype))
+            continue;
+        att->atttypid = reltype;
+        /* shouldn't need to change anything else */
+    }
+    return tupdesc;
+}
+
 /*
  * postgresBeginForeignScan
  *        Initiate an executor scan of a foreign PostgreSQL table.
@@ -1523,7 +1574,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
     else
     {
         fsstate->rel = NULL;
-        fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+        fsstate->tupdesc = get_tupdesc_for_join_scan_tuples(node);
     }

     fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
@@ -2631,7 +2682,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
         TupleDesc    tupdesc;

         if (fsplan->scan.scanrelid == 0)
-            tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+            tupdesc = get_tupdesc_for_join_scan_tuples(node);
         else
             tupdesc = RelationGetDescr(dmstate->rel);

diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 78379bdea5..a7070870c7 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -3262,3 +3262,20 @@ DROP TABLE join_tbl;

 ALTER SERVER loopback OPTIONS (DROP async_capable);
 ALTER SERVER loopback2 OPTIONS (DROP async_capable);
+
+CREATE TABLE base_tbl (a int, b int);
+CREATE FOREIGN TABLE remote_tbl (a int, b int)
+  SERVER loopback OPTIONS (table_name 'base_tbl');
+
+INSERT INTO base_tbl SELECT a, a+1 FROM generate_series(1,10) a;
+
+ANALYZE base_tbl;
+ANALYZE remote_tbl;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE remote_tbl d SET a = CASE WHEN random() >= 0 THEN 5 ELSE 6 END
+  FROM remote_tbl AS t WHERE d.a = t.a;
+UPDATE remote_tbl d SET a = CASE WHEN random() >= 0 THEN 5 ELSE 6 END
+  FROM remote_tbl AS t WHERE d.a = t.a;
+
+SELECT * FROM base_tbl ORDER BY b;

pgsql-hackers by date:

Previous
From: Robert Haas
Date:
Subject: Re: Race condition in recovery?
Next
From: Peter Eisentraut
Date:
Subject: Re: make world and install-world without docs