*** a/contrib/postgres_fdw/deparse.c --- b/contrib/postgres_fdw/deparse.c *************** *** 1003,1008 **** deparseReturningList(StringInfo buf, PlannerInfo *root, --- 1003,1009 ---- List **retrieved_attrs) { Bitmapset *attrs_used = NULL; + bool has_returning = false; if (trig_after_row) { *************** *** 1021,1031 **** deparseReturningList(StringInfo buf, PlannerInfo *root, --- 1022,1072 ---- &attrs_used); } + /* + * Check to see whether the remote query has a RETURNING clause. + * + * XXX be careful to keep this in sync with deparseTargetList. + */ if (attrs_used != NULL) { + if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, + attrs_used)) + has_returning = true; + else + { + TupleDesc tupdesc = RelationGetDescr(rel); + bool have_wholerow; + int i; + + /* If there's a whole-row reference, we'll need all the columns. */ + have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, + attrs_used); + + for (i = 1; i <= tupdesc->natts; i++) + { + Form_pg_attribute attr = tupdesc->attrs[i - 1]; + + /* Ignore dropped attributes. */ + if (attr->attisdropped) + continue; + + if (have_wholerow || + bms_is_member(i - FirstLowInvalidHeapAttributeNumber, + attrs_used)) + { + has_returning = true; + break; + } + } + } + } + + if (has_returning != false) + { appendStringInfoString(buf, " RETURNING "); deparseTargetList(buf, root, rtindex, rel, attrs_used, retrieved_attrs); + Assert(*retrieved_attrs != NIL); } else *retrieved_attrs = NIL; *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 2408,2413 **** SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; --- 2408,2466 ---- 1104 | 204 | ddd | (819 rows) + EXPLAIN (verbose, costs off) + INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; + QUERY PLAN + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + Output: (tableoid)::regclass + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + -> Result + Output: 9999, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum + (5 rows) + + INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; + tableoid + ---------- + ft2 + (1 row) + + EXPLAIN (verbose, costs off) + UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; + QUERY PLAN + ------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: (tableoid)::regclass + Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 + -> Foreign Scan on public.ft2 + Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE + (6 rows) + + UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; + tableoid + ---------- + ft2 + (1 row) + + EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; + QUERY PLAN + ------------------------------------------------------------------------------------ + Delete on public.ft2 + Output: (tableoid)::regclass + Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 + -> Foreign Scan on public.ft2 + Output: ctid + Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE + (6 rows) + + DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; + tableoid + ---------- + ft2 + (1 row) + -- Test that trigger on remote table works as expected CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ BEGIN *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** *** 413,418 **** EXPLAIN (verbose, costs off) --- 413,427 ---- DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + EXPLAIN (verbose, costs off) + INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; + INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; + EXPLAIN (verbose, costs off) + UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; + UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; + EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; + DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; -- Test that trigger on remote table works as expected CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 308,313 **** ExecInsert(ModifyTableState *mtstate, --- 308,319 ---- /* FDW might have changed tuple */ tuple = ExecMaterializeSlot(slot); + /* + * AFTER ROW Triggers or RETURNING expressions might reference the + * tableoid column, so initialize t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(resultRelationDesc); + newId = InvalidOid; } else *************** *** 561,566 **** ExecDelete(ItemPointer tupleid, --- 567,574 ---- } else if (resultRelInfo->ri_FdwRoutine) { + HeapTuple tuple; + /* * delete from foreign table: let the FDW do it * *************** *** 579,584 **** ExecDelete(ItemPointer tupleid, --- 587,601 ---- if (slot == NULL) /* "do nothing" */ return NULL; + + /* + * RETURNING expressions might reference the tableoid column, so + * initialize t_tableOid before evaluating them. + */ + if (slot->tts_isempty) + ExecStoreAllNullTuple(slot); + tuple = ExecMaterializeSlot(slot); + tuple->t_tableOid = RelationGetRelid(resultRelationDesc); } else { *************** *** 838,843 **** ExecUpdate(ItemPointer tupleid, --- 855,866 ---- /* FDW might have changed tuple */ tuple = ExecMaterializeSlot(slot); + + /* + * AFTER ROW Triggers or RETURNING expressions might reference the + * tableoid column, so initialize t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(resultRelationDesc); } else {